Создание нестандартных элементов управления с использованием макросов FASM

Дата публикации 11 апр 2004

Создание нестандартных элементов управления с использованием макросов FASM — Архив WASM.RU

Всё изложенное в данном разделе
не является догмой, поправки и замечания
принимаются по электронной почте pas1@e-mail.ru.

С использованием директивы fix появилась возможность создать свои контролы (элементы управления) без использования библиотек dll и obj-файлов. На мой взгляд плюс в включении контролов в виде макросов в том, что есть возможность изменять их функциональность так, как нужно в каждом конкретном случае. Ещё большим плюсом на мой взгляд является то, что всегда известно как данный элемент будет себя вести т.к. известен его исходный код.

Для начала создадим контрол CheckListBox определённый в Borland C++ Builder 5. Данный контрол представляет собой список строк с CheckBox-ами.
CheckListBox

То, что Вы видите на рисунке это контрол уже созданный с помощью макросов.

Для начала определимся с необходимыми функциями создаваемого контрола.

Самые очевидные:

  1. Создание контрола.
  2. Уничтожение контрола.
  3. Добавление строки.
  4. Получение текущего состояния чекбокса строки.
  5. Установка состояния чекбокса строки.

Но для начала рассмотрим переменные, необходимые для работы контрола.

Переменные необходимые для работы контрола.

Сначала рассмотрим переменные общие для всех контролов (не забываёте, что нам может понадобится создать несколько контролов одновременно).

Код (Text):
  1.  
  2. macro CheckListBox [ID,name]
  3. {
  4. common
  5.   __Buf: times 100 db 0
  6.   __IDB_UNCHECK equ 301
  7.   __IDB_CHECK equ 302
  8.   __class db 'ListBox',0
  9.   virtual at ebx
  10.        __dis DRAWITEMSTRUCT
  11.   end virtual
  12.   __rcItem   RECT
  13.   __hdc dd       0
  14.   __hMem      dd 0
  15.   __item       dd 0
  16.   __hBtn        dd 0
  17.  

В первой строке мы задаём имя макроса CheckListBox и получаемые им параметры [ID,name]. Квадратные скобки, в которые заключены параметры, говорят о том, что нам может понадобиться создать несколько элементов управления.

Первым в макрос будет передоваться идентификатор контрола, а затем его имя, по которому мы будем к нему обращаться. Например:

CheckListBox 10,Spisok - объявлен элемент управления типа CheckListBox с идентификатором 10

Что бы его создать необходимо написать следующую команду Spisok.Create <параметры>. Параметры передаваемые в макрос создания элемента управления мы рассмотрим чуть позже.

Далее следует директива common, как Вы помните она сообщает компилятору, что следующие строки макроса нужно обработать только один раз.

__Buf: times 100 db 0 - в этой строке мы резервируем буфер для временного хранения строк соответствующих отрисовываемому пункту элемента управления. Длинна строки не может быть больше 100 байт иначе наши элементы управления будут работать неправильно. Если предполагается хранение строк большего размера необходимо увеличить размер буфера.

__IDB_UNCHECK equ 301 - идентификатор картинки соответствующей неотмеченному пункту

__IDB_CHECK equ 302    - идентификатор картинки соответствующей отмеченному пункту

__class db 'ListBox',0 - имя стандартного элемента управления, на основе которого создаётся наш элемент управления.

Далее следует команда virtual at ebx, указывающая компилятору, что указатель на начало переменных определённых в блоке virtual - end virtual будет находиться в регистре ebx. Это сделано потому, что структура DRAWITEMSTRUCT будет находиться вне памяти нашего модуля. Указатель на неё мы будем получать из параметра lparam сообщений WM_DRAWITEM, получаемого процедурой родительского окна, на котором мы создадим свой элемент управления.

Структура DRAWITEMSTRUCT определена следующим образом:

Код (Text):
  1.  
  2. struc DRAWITEMSTRUCT
  3.  {
  4.    .CtlType    dd ?
  5.    .CtlID      dd ?
  6.    .itemID     dd ?
  7.    .itemAction dd ?
  8.    .itemState  dd ?
  9.    .hwndItem   dd ?
  10.    .hDC        dd ?
  11.    .rcItem     RECT
  12.    .itemData   dd ?
  13.  }
  14.  

CtlType - Тип элемента управления требующего перерисовки:

Код (Text):
  1.  
  2.   <strong>ODT_BUTTON</strong>       Рисуемая пользовательской программой кнопка
  3.   <strong>ODT_COMBOBOX</strong> &nbsp; &nbsp;&nbsp;Рисуемое пользовательской программой поле с выпадающим списком
  4.   <strong>ODT_LISTBOX</strong>      Рисуемый пользовательской программой список
  5.   <strong>ODT_LISTVIEW</strong> &nbsp; &nbsp;&nbsp;Рисуемый пользовательской программой ListView
  6.   <strong>ODT_MENU</strong>     Рисуемый пользовательской программой пункт меню
  7.   <strong>ODT_STATIC</strong>       Рисуемая пользовательской программой статическая метка
  8.   <strong>ODT_TAB</strong>      Tab control (к сожалению я не смог перевести этот пункт)
  9.  

CtlID - Идентификатор элемента управления. Этот член структуры не используется, если отрисовать нужно пункт меню

itemID - Идентификатор пункта меню или индекс пункта поля с выпадающим списком или простым списком.

itemAction - Определяет какое событие произошло

Код (Text):
  1.  
  2.   <strong>ODA_DRAWENTIRE</strong>    Элемент управленя должен быть перерисован
  3.   <strong>ODA_FOCUS</strong>          Элемент управления получил или потерял фокус ввода
  4.   <strong>ODA_SELECT</strong>         В элементе управления произошло выделение пункта
  5.  

itemState -

Код (Text):
  1.  
  2.   <strong>ODS_CHECKED</strong>       Пункт меню отмечен, т.е. при перерисовке мы должны нарисовать картинку
  3.                     соответствующую отмеченному состоянию пункта меню.  Используется только для меню.
  4.   <strong>ODS_COMBOBOXEDIT</strong>  Перерисовать необходимо поле редактирования (Edit Control) поля с
  5.                     выпадающим списком
  6.   <strong>ODS_DEFAULT</strong>       Пункт является пунктом выбраным поумолчанию.
  7.   <strong>ODS_DISABLED</strong>      Пункт не активен.
  8.   <strong>ODS_FOCUS</strong>         Пункт имеет фокус ввода.
  9.   <strong>ODS_GRAYED</strong>        Пункт должен быть нарисован. Используется только для меню.
  10.   <strong>ODS_SELECTED</strong>      Пункт выделен.
  11.  

hwndItem - Хендл идентифицирующий элемент управления. Для меню этот член структуры идентифицирует меню содержащее данный пункт.

hDC - Хендл контекста элемента управления.

rcItem - Структура содержащая координаты в которых необходимо произвести перерисовку.

itemData - 32-х битное значение, определяемое приложением.

Далее следует непосредственноле определение структуры RECT

__rcItem RECT - структура хранит координаты пункта, который нужно перерисовать.

__hdc dd 0 - переменная предназначенная для временного хранения хендла контекста элемента управления.

__hMem dd 0 - Хендл контекста в памяти совместимого с контекстом элемента управления.

__item dd 0 - Временное хранилище индекса пункта (строки) в элементе управления.

__hBtn dd 0 - Временное хранилище хендла картинки соответствующей текущему состоянию строки.

Далее рассмотрим переменные индивидуальные для каждого элемента управления.

Код (Text):
  1.  
  2. forward
  3.      name#.mas: times 250 db  0
  4.      name#.hList dd 0
  5.      name#.hBmp1     dd 0
  6.      name#.hBmp2     dd 0
  7.      name#.count dd 0
  8.  

forward - директива компилятору обработать следующие строки столько раз, сколько групп параметров будет передано в макрос.

name#.mas: times 250 db 0 - массив содержащий текущие состояния всех строк в элементе управления. Соответственно чтобы узнать состояние строки по её индексу можно загрузить в один регист адрес соответствующего массива, а в другой индекс строки если байт по полученному адресу = 1 значит строка не отмечена. Пример:

Код (Text):
  1.  
  2. ........
  3. CheckListBox 10,Spisok
  4. ........
  5. mov   ebx,Spisok.mas ; загружаем адрес массива
  6. mov   ecx,[ind] ; загружаем индекс
  7. mov   al,[ebx+ecx] ; загружаем значение соответствующей строки
  8. cmp   al,1
  9. ........
  10.  

Естественно, что загружать в элемент управления более 250 строк нельзя, но можно увеличить размер массива (самое простое) или доработать алгоритм, чтобы состоянию строки соответствовало состояние бита. После рассмотрения всего алгоритма работы элемента управления, мы оптимизируем его.

name#.hList dd 0 - Хендл элемента управления. Получить его можно следующим образом: Spisok.hList.

name#.hBmp1 - Хендл картинки соответствующей не отмеченному состоянию строки

name#.hBmp2 - Хендл картинки соответствующей отмеченному состоянию строки

Полагаю Вам понятно, что картинки мы можем нарисовать свои и любого содержания, единственное требование размер должен быть стандартным 16х16 и палитра из 16 цветов в формате BMP.

name#.count dd 0 - количество строк загруженных в элемент управления. Соответственно к нему можно обратиться следующим образом: Spisok.count.

Когда я начинал писать эту статью, помимо выше указанных переменных, макрос содержал общую структуру _tm типа TEXTMETRIC и она была подробно объяснена, но по ходу написания статьи стало очевидно, что использование этой структуры не нужно, однако мне стало жаль выбрасывать описание этой структуры и я привожу его здесь в качестве общеразвивающей.

Структура TEXTMETRIC определена следующим образом:

Код (Text):
  1.  
  2. struc TEXTMETRIC
  3. {
  4.   .tmHeight              dd      ?
  5.   .tmAscent              dd      ?
  6.   .tmDescent             dd      ?
  7.   .tmInternalLeading     dd      ?
  8.   .tmExternalLeading     dd      ?
  9.   .tmAveCharWidth        dd      ?
  10.   .tmMaxCharWidth        dd      ?
  11.   .tmWeight              dd      ?
  12.   .tmOverhang            dd      ?
  13.   .tmDigitizedAspectX    dd      ?
  14.   .tmDigitizedAspectY    dd      ?
  15.   .tmFirstChar           db      ?
  16.   .tmLastChar            db      ?
  17.   .tmDefaultChar         db      ?
  18.   .tmBreakChar           db      ?
  19.   .tmItalic              db      ?
  20.   .tmUnderlined          db      ?
  21.   .tmStruckOut           db      ?
  22.   .tmPitchAndFamily      db      ?
  23.   .tmCharSet             db      ?
  24. }
  25.  

Эта структура используется для обработки данных о шрифтах (например для более точного выравнивания текста выводимого не моноширинным шрифтом т.е. таким в котором буквы имеют не одинаковую ширину).

Семь из двадцати полей структуры определяют внутритекстовые интервалы.
Внутритекстовые интервалы

tmHeight - Общая высота символа (равна сумме tmAscent и tmDescent)

tmAscent - Содержит значение высоты прописного символа вместе с надстрочным элементом.

tmDescent - Содержит значение высоты подстрочных элементов таких букв, как g, j, p и т.п.

tmInternalLeading - Содержит значение высоты надстрочных элементов, таких как дидактический знак над буквой S

tmExternalLeading - Содержит значение межстрочного интервала. Считается, что увеличение межстрочного интервала повышает удобочитаемость текста.

tmAveCharWidth - Содержит средневзвешенное значение ширины символов.

tmMaxCharWidth - Содержит значение ширины самого широкого символа, каковыми обычно являются W или M

tmWeight - Содержит значение ширины шрифта

tmOverhang - Содержит значение ширины строки символов, которую можно добавить для отображения символов жирным или наклонным шрифтом

tmDigitizedAspectX - Определяет горизонтальный размер (ширину) для которого изначально разрабатывался шрифт. Например: если данное поле содержит 12 это значит, что шрифт разрабатывался для отображения символов шириной 12.

tmDigitizedAspectY - Определяет вертикальный размер (высоту) для которого шрифт разрабатывался. Например: если данное поле содержит 12 это значит, что шрифт разрабатывался для отображения символов высотой 12.

tmFirstChar - Код первого определённого в шрифте символа. Например если это поле имеет значение 48, значит самым первым символом определённым в данном шрифте является цифра "0" и вместо символов с кодом меньше 48 будет отображаться символ определённый в поле tmDefaultChar.

tmLastChar - Код последнего определённого в шрифте символа. Например если это поле имеет значение 57, значит самым последним символом определённым в данном шрифте является цифра "9" и вместо символов с кодом больше 57 будет отображаться символ определённый в поле tmDefaultChar.

tmDefaultChar - Код символа которым будут заменяться символы не определённые в данном шрифте. Пример можно увидеть на рисунке ниже.

Замена неопределённых в шрифте символов

На этом рисунке видно, что символы строки "Первая строка" определены в шрифте Courier New (и отображаются правильно), но не определены в шрифте Marlett (и заменены на символ "прямоугольника")

tmBreakChar - Определяет код символа, который будет использоваться для определения границ слов в строке для выравнивания текста. Это позволяет произвести выравнивание текста так, что бы интервалы между буквами были меньше чем интервалы между словами.

tmItalic - Если это поле отлично от нуля текст будет выводиться наклонным.

tmUnderlined - Если это поле отлично от нуля текст будет выводиться подчёркнутым.

tmStruckOut - Если это поле отлично от нуля текст будет выводиться перечёркнутым.

tmPitchAndFamily - Определяет тип шрифта (моноширинный или нет, растровый или векторный, является ли он шрифтом TrueType) и семейство шрифта.

tmCharSet - Определяет набор символов шрифта.

Функция работы с элементом управления.

Функция создания элемента управления.

Мне показалось логичным начать рассмотрение функций работы с элементом управления с функции создающей элемент управления name#.Create, которая создаёт элемент управления. Она определена следующим образом:

Код (Text):
  1.  
  2. macro name#.Create x,y,sx,sy,hwnd,hinstance
  3. m_
  4.  invoke LoadBitmap,hinstance,__IDB_UNCHECK
  5.  mov     [name#.hBmp1],eax
  6.  invoke LoadBitmap,hinstance,__IDB_CHECK
  7.  mov     [name#.hBmp2],eax
  8.  invoke CreateWindowEx,0,__class,0,\
  9.  WS_CHILD+WS_VISIBLE+LBS_OWNERDRAWFIXED+WS_BORDER+LBS_HASSTRINGS+LBS_NOTIFY+WS_VSCROLL+WS_HSCROLL,\
  10.              x,y,sx,sy,hwnd,ID,hinstance,NULL
  11. mov     [name#.hList],eax
  12. _m
  13.  

Прежде, чем рассматривать содержимое этого макроса хочу обратить Ваше внимание на то, что фигурные скобки мы заменили на символы m_ и _m они определены в конце файла содержащего данный макрос:

Код (Text):
  1.  
  2. m_ fix {
  3. _m  fix }
  4.  

Если Вы читали первую часть моей статьи, посвящённую общим вопросам программирования в FASM, то Вы знаете, что эти строки определяют две символьные макроконстанты m_ и _m которые на одном из этапов компиляции будут заменены на знаки фигурных скобок. Дело в том, что FASM не может определить вложенные определения макросов. Встречая вторую открывающуюся фигурную скобку до того как встретится закрывающаяся, он считает, что в исходном тексте ошибка и прекращает компиляцию. Однако замена символьных макроконстант, определённых директивой fix, происходит на более позднем этапе, чем определение объявлений макросов и такая конструкция проходит.

Однако мы несколько отвлеклись, так что продолжим, и рассмотрим переменные передаваемые в макрос:

Код (Text):
  1.  
  2. <strong>x</strong>          начальная координата окна по горизонтали.
  3. <strong>y</strong>          начальная координата окна по вертикали.
  4. <strong>sx</strong>         размер элемента управления по горизонтали
  5. <strong>sy</strong>         размер элемента управления по вертикали.
  6. <strong>hwnd</strong>       хендл родительского окна, в котором создаётся элемент управления.
  7. <strong>hinstance</strong> хендл модуля, в рамках которого создаётся элемент управления.
  8.  

Далее рассмотрим код непосредственно создающий наш элемент управления.

invoke LoadBitmap,hinstance,__IDB_UNCHECK - загружаем из ресурса картинку соответствующую не отмеченному состоянию строки.

mov [name#.hBmp1],eax - сохраняем полученный хендл картинки

invoke LoadBitmap,hinstance,__IDB_CHECK - загружаем из ресурса картинку соответствующую отмеченному состоянию строки.

mov [name#.hBmp2],eax - сохраняем полученный хендл картинки

invoke CreateWindowEx,0,__class,0, WS_CHILD+WS_VISIBLE+LBS_OWNERDRAWFIXED+WS_BORDER
+LBS_HASSTRINGS+LBS_NOTIFY+WS_VSCROLL+WS_HSCROLL,x,y,sx,sy,hwnd,ID,hinstance,NULL
Этой командой мы создаём наш элемент управления. Единственное, что требует пояснения, на мой взгляд, это стили с которыми мы создаём наш элемент управления:

Код (Text):
  1.  
  2. <strong>WS_CHILD</strong>     Указывает на то, что создаваемый элемент управления является дочерним.
  3. <strong>WS_VISIBLE</strong>   Указывает на то, что элемент создаётся видимым и нет необходимости вызывать
  4.               процедуру ShowWindow для его отображения.
  5. <strong>WS_BORDER</strong>    Указывает, что элемент имеет бордюр.
  6. <strong>WS_VSCROLL</strong>   Указывает, что элемент управления будет содержать вертикальную полосу
  7.               прокрутки когда это необходимо.
  8. <strong>WS_HSCROLL</strong>   Указывает, что элемент управления будет содержать горизонтальную полосу
  9.                прокрутки когда это необходимо.
  10.  
  11. <strong>LBS_OWNERDRAWFIXED</strong>  Указывает, что окно будет рисоваться пользовательской программой.
  12.  
  13. <strong>LBS_HASSTRINGS</strong>      Определяет, что список содержит элементы, состоящие из строк и позволяет
  14.                     получить текст строки при помощи сообщения <strong>LB_GETTEXT</strong>. Все элементы
  15.                     управления этого типа создаются с этим стилем, кроме отрисовываемых
  16.                     пользовательской программой.
  17. <strong>LBS_NOTIFY</strong>  Определяет, что элемент управления посылает уведомительные сообщения родительскому
  18.               окну.
  19.  

mov [name#.hList],eax - Сохраняем хендл созданного элемента управления.

Вот и вся процедура создания элемента управления, однако если мы просто поместим эту процедуру в текст программы, то мы не увидим наш элемент управления. Для того, чтобы наш элемент управления стал видимым мы должны написать макрос который его нарисует в ответ на сообщение, посылаемое системой,родительскому окну WM_DRAWITEM. Этот макрос мы назовём .onDraw

Функция рисования элемента управления.

Следующей мы рассмотрим процедеру рисования нашего элемента управления name#.onDraw. Обращение к ней можно производить следующим образом: Spisok.onDraw

Вот как определён этот макрос:

Код (Text):
  1.  
  2. macro name#.onDraw
  3. m_
  4.  cmp    [wmsg],WM_DRAWITEM
  5.  jne .#name#nowmdraw
  6. .#name#dr:
  7.    mov      ebx,[lparam]
  8.    cmp     [__dis.CtlID],ID
  9.    jne        .#name#nowmdraw
  10.    cmp      [__dis.itemAction],ODA_DRAWENTIRE
  11.    jne  .finish
  12. .#name#drugie:
  13.    invoke    CreateCompatibleDC,[__dis.hDC]
  14.    mov       [__hMem],eax
  15.    mov         eax,[__dis.itemID]
  16.    mov       ecx,name#.mas
  17.    cmp       byte [ecx+eax],1
  18.    jne       .#name#noOne
  19.    mov       eax,[name#.hBmp1]
  20.    mov       [__hBtn],eax
  21.    jmp   .#name#noOnes
  22. .#name#noOne:
  23.    mov       eax,[name#.hBmp2]
  24.    mov       [__hBtn],eax
  25. .#name#noOnes:
  26.    invoke     SelectObject,[__hMem],[__hBtn]
  27.    invoke     BitBlt,[__dis.hDC],[__dis.rcItem.left],[__dis.rcItem.top],20,20,[__hMem],1,\
  28.                      1,SRCCOPY
  29.    invoke     SendMessage,[__dis.hwndItem],LB_GETTEXT,[__dis.itemID],__Buf
  30.    CountString __Buf
  31.    dec  eax
  32.    invoke     TextOut,[__dis.hDC],20,[__dis.rcItem.top],__Buf,eax
  33.    invoke     DeleteDC,[__hMem]
  34.    mov   eax,1
  35.    jmp   .finish
  36. .#name#nowmdraw:
  37. _m
  38.  
  39.  

Как видите переменные в макрос не передаются, макрос использует переменные, передаваемые процедуре окна, в котором создан элемент управления. Поместить этот макрос необходимо в начале программы после сохранения в стеке регистров:

Код (Text):
  1.  
  2. proc WindowProc, hwnd,wmsg,wparam,lparam;Окно в котором мы создаём свой элемент управления.
  3.     enter
  4.     push    ebx esi edi
  5.     Spisok.onDraw
  6.     cmp [wmsg],WM_DESTROY
  7.     je  .wmdestroy
  8.     cmp [wmsg],WM_CREATE
  9.     je  .wmcreate
  10.     cmp [wmsg],WM_COMMAND
  11.     je  .wmcommand
  12.     .......
  13. .finish:
  14.     pop edi esi ebx
  15. return
  16.  

Предполагается, что в конце процедуры есть метка .finish и все метки в процедуре локальны. Теперь перейдём к рассмотрению самого кода:

cmp	[wmsg],WM_DRAWITEM - проверяем какое сообщение послано окну если не  WM_DRAWITEM
jne .#name#nowmdraw        - то переходим в конец макроса и позволяем процедуре обработать 
                                другие сообщения.

mov   ebx,[lparam]         - загружаем в регистр ebx указатель на структуру DRAWITEMSTRUCT

cmp [__dis.CtlID],ID - проверяем, этому ли конкретному элементу управления послано сообщение jne .#name#nowmdraw - если нет переходим в конец макроса

cmp [__dis.itemAction],ODA_DRAWENTIRE - проверяем какое событие произошло со строкой если не ODA_DRAWENTIRE

jne .finish - то переходим в конец процедуры.

#name#drugie: invoke CreateCompatibleDC,[__dis.hDC] - создаём контекст в памяти совместимый с контекстом нашего элемента управления. mov [__hMem],eax - сохраняем хендл совместимого контекста в памяти.

mov eax,[__dis.itemID] - загружаем в регистр eax индекс строки

mov ecx,name#.mas - загружаем в регистр ecx адрес массива содержащего состояния всех строк.

cmp byte [ecx+eax],1 - проверяем состояние отрисовываемой строки если значение не равно 1 jne .#name#noOne - то переходим на метку сохранения хендла картинки отмеченного чекбокса

mov eax,[name#.hBmp1] - сохраняем хендл картинки соответствующей mov [__hBtn],eax - не отмеченному состоянию строки jmp .#name#noOnes - и пропускаем следующие две команды

.#name#noOne: mov eax,[name#.hBmp2] - сохраняем хендл картинки соответствующей mov [__hBtn],eax - отмеченному состоянию строки

.#name#noOnes: invoke SelectObject,[__hMem],[__hBtn] - выбираем в контекст в памяти картинку соответст- вующую текущему состоянию строки

invoke BitBlt,[__dis.hDC],[__dis.rcItem.left],[__dis.rcItem.top],20,20,[__hMem],\ 1,1,SRCCOPY Мы вызвали процедуру копирования рисунка из совместимого контекста в памяти (__hMem) в контекст элемента управления (__dis.hDC), проще выражаясь, мы нарисовали картинку, соответствующую текущему состоянию строки. Для отображения нашего рисунка мы использовали координаты структуры __dis.rcItem, как координаты левого верхнего угла прямоугольника в который копируется рисунок, а в качестве высоты и ширины размер нашего рисунка (16х16) плюс небольшой запас т.е. 20х20.

invoke SendMessage,[__dis.hwndItem],LB_GETTEXT,[__dis.itemID],__Buf Мы послали сообщение LB_GETTEXT процедуре обработки сообщений нашего элемента управления, что бы получить текст строки, которую мы перерисовываем.

CountString __Buf - этот макрос подсчитывает количество символов в строке (включая нулевой символ). Результат в регистре еах. dec eax - получаем чистую длинну строки без нулевого символа.

invoke TextOut,[__dis.hDC],20,[__dis.rcItem.top],__Buf,eax Выводим текст строки правее изображения CheckBox-а в контекст элемента управления. В качестве координат используем верхнюю координату структуры __dis.rcItem, а в качестве координаты х отступ в 20 точек, что бы не затереть выведенный рисунок CheckBox-а.

invoke DeleteDC,[__hMem] - удаляем совместимый контекст в памяти.

mov eax,1 - процедура обработки должна возвратить значение True т.е. не нулевое значение.

jmp .finish - переход на конец процедуры обработки сообщений окна. .#name#nowmdraw: - метка на которую мы переходим если сообщение не WM_DRAWITEM или оно относится не к этому элементу управления.

Как видите всё очень просто.

Если теперь вы добавите в свою программу макрос рисования элемента управления, он будет отображаться, но пока мы не можем добавить в него строки. Для добавления строки, в наш элемент управления, мы напишем макрос name#.AddString.

Макрос добавления строки.

Добавление новой строки в элемент управления осуществляется стандартным сообщением, посылаемым элементу управления. Макрос определён следующим образом:

Код (Text):
  1.  
  2. macro name#.AddString string,data
  3. m_
  4.  inc       [name#.count]
  5.  invoke    SendMessage,[name#.hList],LB_ADDSTRING,0,string
  6.  mov       ecx,name#.mas
  7.  mov       dl,1
  8.  mov       [ecx+eax],dl
  9.   if data eq
  10.   else
  11.    invoke    SendMessage,[name#.hList],LB_SETITEMDATA,eax,data
  12.   end if
  13.   _m
  14.  

В макрос передаётся два значения string и data, причём значение data можно не передавать.

string - указатель на строку, которую мы хотим добавить в элемент управления.

data - как уже упоминалось необязательный параметр (его можно не указывать) ассоциирует с данной строкой 32-х битное значение. Таким значением может быть указатель на структуру в памяти.

Приведу пример: этот элемент управления я создавал под конкретную программу - "Каталогизатор CD". В ней информация о дисках хранится в массиве структур, содержащих название диска, стоимость диска, количество дисков в коробке и т.п. и в числе прочего предусматривалась возможность выдачи дисков в прокат (для компьютерных клубов или прокатов). При этом рассматривалась возможность того, что клиент будет подходить с выбранными дисками несколько раз (выбрал один диск подошел посоветоваться, отдал диск, чтобы отложили пока он выбирает ещё и т.п.) и в итоге он может не захотеть брать диск выбранный ранее. По этому при окончательном расчёте выводится окошко в котором создаётся CheckListBox, содержащий список всех отложенных, для данного клиента, дисков и путём установки или снятия галочки с дисков, отменить выдачу дисков от которых клиент отказался. Таким образом в структуры переданных дисков записывается информация о клиенте и сроке на который диск взят. Для того, чтобы не производить поиск по всей базе дисков, в поиске структур дисков, отмеченных в CheckListBox-е, с каждой строкой ассоциировано 32-х битное значение, являющееся указателем на структуру данных о диске, название которого выведено в данной строке. Так же аналогичная возможность (ассоциировать 32-х битное значение) имеется и в некоторых других стандартных элементах управления.

inc [name#.count] - эта строка увеличивает значение переменной, содержащей количество строк в элементе управления.

invoke SendMessage,[name#.hList],LB_ADDSTRING,0,string - здесь мы посылаем сообщение стандартному элементу управления добавить строку. Строка добавляется в конец списка. Возвращаемое значение - отсчитываемый от нуля индекс строки в регистре еах.

mov ecx,name#.mas - загружаем в регистр ecx адрес начала массива, содержащего состояния строк.

mov dl,1 - загружаем в регистр dl 1 - строка не отмечена.

mov [ecx+eax],dl - сохраняем состояние строки в массиве.

if data eq - Здесь начинается часть макроса, которая отвечает за ассоциирование данных со строкой. В этой строке проверяется наличие переменной data. Если данные переданы выполняем строку после директивы else

else

invoke SendMessage,[name#.hList],LB_SETITEMDATA,eax,data - посылаем стандартному элементу управления сообщение ассоциирующее с данной строкой переданные в макрос данные.

end if

Если теперь добавить все рассмотренные нами макросы, то наш элемент управления будет создаваться, отображаться и в него будут добавляться строки с CheckBox-ами слева. Однако при щелчке на строках чекбоксы не переключаются. Это происходит потому, что при щелчке мышкой в рамках элемента управления, элемент управления посылает уведомительное сообщение родительскому, но сообщение WM_DRAW не посылается. Для того, что бы элемент управления реагировал на наши действия, необходимо написать макрос, который будет, в ответ на уведомительное сообщение, изменять соответствующий элемент массива состояний строк и перерисовывать изменённую строку. Уведомительные сообщения разными элементами управления могут посылаться родительскому окну либо через сообщение WM_NOTIFY либо через WM_COMMAND. Через какое именно сообщение, элемент управления посылает уведомительные сообщения, необходимо проверить по справочнику WinSDK.

Макрос реагирования на уведомительное сообщение.

Как уже было сказано, для того, что бы наш элемент управления заработал, необходимо написать макрос, который бы реагировал на уведомительное сообщение. Называться этот макрос будет name#.onNotify. Обращение к нему осуществляется следующим образом: Spisok.onNotify.

Но прежде, чем начать рассмотрение самого макроса, необходимо определить через какое сообщение посылаются уведомительные сообщения родительскому окну. ListBox посылает уведомительные сообщения через сообщение WM_COMMAND.

В WinSDK оно определено следующим образом:

Код (Text):
  1.  
  2. WM_COMMAND  
  3. wNotifyCode = HIWORD(wParam); - старшее слово параметра <strong>wParam</strong> содержит код уведомительного
  4.                                 сообщения, посланного элементом управления.
  5.  
  6. wID = LOWORD(wParam); - младшее слово параметра <strong>wParam</strong> содержит ID элемента управления, однако
  7.                         не стоит использовать этот параметр для идентификации конкретного
  8.                         элемента управления т.к. в результате ошибки могут оказаться активными
  9.                         в одно и тоже время несколько элементов с одинаковыми ID.
  10.  
  11. hwndCtl = (HWND) lParam; - хендл элемента управления, пославшего уведомительное сообщение
  12.                            родительскому окну. Его можно смело использовать для идентификации
  13.                            конкретного элемента управления.
  14.  

Теперь рассмотрим какие уведомительные сообщения, может посылать элемент управления, основанный на ListBox-е:

Код (Text):
  1.  
  2. <strong>LBN_DBLCLK</strong> - пользователь сделал двойной щелчек мышью в области элемента управления.
  3. <strong>LBN_ERRSPACE</strong> - элемент управления ListBox не может выполнить запрос программы (например
  4.                  недостаточно места в памяти для добавления строки).
  5. <strong>LBN_SETFOCUS</strong> - элемент управления получил фокус ввода.
  6. <strong>LBN_KILLFOCUS</strong> - элемент управления потерял фокус ввода.
  7. <strong>LBN_SELCHANGE</strong> - пользователь выделил строку.
  8. <strong>LBN_SELCANCEL</strong> - пользователь отменил выделение строки.
  9.  

Для нас имеют значения только LBN_DBLCLK и LBN_SELCHANGE т.к. нам нужно, что бы состояние элемента изменялось при выделении (одинарный щелчек на строке) или двойном щелчке на строке.

Из всего написанного выше, Вы уже наверно догадались, что помещать этот макрос следует в начале обработчика сообщений WM_COMMAND. Например:

Код (Text):
  1.  
  2. proc WindowProc, hwnd,wmsg,wparam,lparam;Главное окно программы
  3.     enter
  4.     push  ebx esi edi
  5.     cmp    [wmsg],WM_DESTROY
  6.     je   .wmdestroy
  7.     cmp    [wmsg],WM_CREATE
  8.     je   .wmcreate
  9.     cmp    [wmsg],WM_COMMAND
  10.     je   .wmcommand
  11. .............
  12. .wmcommand:
  13.     Spisok.onNotify
  14. .............
  15.  

Итак после длительного вступления перейдём таки к самомо макросу. Он определён следующим образом:

Код (Text):
  1.  
  2. macro name#.onNotify
  3. m_
  4.   mov     eax,[name#.#hList]
  5.   cmp     eax,0
  6.   je   .#name#EndNotify
  7.   cmp     eax,[lparam]
  8.   jne  .#name#EndNotify
  9.   mov     eax,[wparam]
  10.   shr     eax,16
  11.   cmp     ax,LBN_DBLCLK
  12.   je   .#name#nofinish
  13.   cmp     ax,LBN_SELCHANGE
  14.   jne  .#name#EndNotify
  15. .#name#nofinish:
  16.     invoke  GetDC,[name#.hList]
  17.     mov     [__hdc],eax
  18.     invoke  SendMessage,[name#.hList],LB_GETCARETINDEX,0,0
  19.     mov     [__item],eax
  20.     invoke  SendMessage,[name#.hList],LB_GETITEMRECT,eax,__rcItem
  21.     invoke  SendMessage,[name#.hList],LB_GETTEXT,[__item],__Buf
  22.     invoke  CreateCompatibleDC,[__hdc]
  23.     mov     [__hMem],eax
  24.     mov     eax,[__item]
  25.     mov     ecx,name#.mas
  26.     mov     dl,[ecx+eax]
  27.     cmp     dl,1
  28.     jne  .#name#No1
  29.     mov     dl,2
  30.     mov     [ecx+eax],dl
  31.     mov     eax,[name#.hBmp2]
  32.     mov     [__hBtn],eax
  33.     jmp   .#name#bez
  34.   .#name#No1:
  35.     mov     dl,1
  36.     mov     [ecx+eax],dl
  37.     mov     eax,[name#.hBmp1]
  38.     mov     [__hBtn],eax
  39.   .#name#bez:
  40.     invoke  SelectObject,[__hMem],[__hBtn]
  41.     invoke  BitBlt,[__hdc],[__rcItem.left],[__rcItem.top],20,20,[__hMem],1,1,SRCCOPY
  42.     CountString   __Buf
  43.     dec     eax
  44.     invoke  TextOut,[__dis.hDC],20,[__dis.rcItem.top],__Buf,eax
  45.     invoke  DeleteDC,[__hMem]
  46.     invoke  DeleteDC,[__hdc]
  47. jmp   .finish
  48. .#name#EndNotify:
  49. _m
  50.  

Теперь более подробно рассмотрим содержание макроса

macro name#.onNotify - объявление макроса, реагирующего на события в элементе управления.

Код (Text):
  1.  
  2.   mov     eax,[name#.#hList] - загрузка в регистр <strong>eax</strong> хендла нашего элемента управления.
  3.   cmp     eax,0 - проверяем равно ли значение загруженное в регист eax нулю, т.е. проверяем
  4.                   создан ли элемент управления
  5.   je   .#name#EndNotify - если элемент управления ещё не создан, пропускаем код макроса.
  6.   cmp     eax,[lparam] - проверяем, какой элемент управления послал уведомительное сообщение
  7.   jne  .#name#EndNotify - если уведомительное сообщение послано не данным элементом управления,
  8.                           пропускаем код макроса
  9.  

Данные строки проверяют создан ли наш элемент управления, если он ещё не создан, то нет смысла выполнять макрос далее. Если элемент управления создан, то проверяем, наш ли элемент управления послал это сообщение, если не наш пропускаем код макроса.

Далее нам нужно отфильтровать только те уведомительные сообщения, на которые наш макрос должен реагировать (LBN_DBLCLK - двойной щелчёк в области нашего элемента управления и LBN_SELCHANGE - изменение выделенной строки):

Код (Text):
  1.  
  2.   mov     eax,[wparam] - сохраняем в регистре <strong>еах</strong> параметр <strong>wparam</strong> для дальнейшей обработки
  3.   shr     eax,16 - сдвигаем содержимое регистра <strong>еах</strong> вправо на 16 бит, в результате получаем код
  4.                    уведомительного сообщения в регистре <strong>ax</strong>.
  5.   cmp     ax,LBN_DBLCLK - проводим распознавание уведомительных сообщений, если сообщение
  6.                           послано в ответ на двойной щелчёк мышкой
  7.   je   .#name#nofinish - переходим к выполнению кода макроса
  8.   cmp     ax,LBN_SELCHANGE - опять же проверяем какое сообщение было послано, если сообщение
  9.                              послано в ответ на изменение выделения
  10.   jne  .#name#EndNotify - то мы не пропускаем код макроса, если уведомительное сообщение
  11.                           послано по какой либо другой причине код макроса не выполняется.
  12. .#name#nofinish: - начало собственно кода макроса.
  13.  

Далее следует код, который непосредственно рисует изменение состояния нашего элемента управления. Он похож на код макроса onDraw

invoke GetDC,[name#.hList] - Вызываем функцию API получения хендла контекста нашего элемента управления.

mov [__hdc],eax - сохраняем его во временной переменной __hdc.

invoke SendMessage,[name#.hList],LB_GETCARETINDEX,0,0 - посылаем элементу управления сообщение, в ответ на которое получим индекс выделенной строки.

mov [__item],eax - сохраняем индекс выделенной строки в переменной __item.

invoke SendMessage,[name#.hList],LB_GETITEMRECT,eax,__rcItem - получаем координаты видимой части строки, индекс которой мы получили ранее.

invoke SendMessage,[name#.hList],LB_GETTEXT,[__item],__Buf - получаем текст строки, индекс которой мы получили ранее.

invoke CreateCompatibleDC,[__hdc] - создаём совместимый контекст в памяти, рисовать будем на нём, а затем уже нарисованную картинку копировать в область нашего элемента управления.

mov [__hMem],eax - сохраняем хендл созданного совместимого контекста в памяти.

mov eax,[__item] - загружаем индекс выбранной строки в регистр eax для последующего использования для доступа к элементу массива состояний строк.

mov ecx,name#.mas - загружаем адрес начала массива состояний строк.

mov dl,[ecx+eax] - получаем значение состояния выделенной строки.

Далее проводим проверку текущего состояния строки, меняем его и загружаем в переменную __hBtn картинку соответствующую изменённому состояни строки.

Код (Text):
  1.  
  2.     <strong>cmp     dl,1</strong>    - проверяем предыдущее состояние строки, если строка была не отмечена
  3.     <strong>jne  .#name#No1</strong> - продолжаем выполнение кода, иначе переходим на метку <strong>.#name#No1</strong>,
  4.                         ответственную за изменение отмеченного состояния строки на не отмеченное.
  5.     <strong>mov     dl,2</strong>    - т.к. строка была не отмечена загружаем в регистр <strong>dl</strong> новое
  6.                         отмеченное состояние
  7.     <strong>mov     [ecx+eax],dl</strong> - сохраняем новое состояние в массиве
  8.     <strong>mov     eax,[name#.hBmp2]</strong> - загружаем в регистр <strong>eax</strong> хендл картинки, соответствующей
  9.                                     новому состоянию строки
  10.     <strong>mov     [__hBtn],eax</strong> - сохраняем хендл картинки в переменной <strong>__hBtn</strong>
  11.     <strong>jmp   .#name#bez</strong>   - далее пропускаем код ответственный за изменение отмеченного состояния
  12.                            строки на не отмеченное
  13.   <strong>.#name#No1:</strong>   - начало кода ответственного за изменение отмеченного состояния строки на
  14.                    не отмеченное.
  15.     <strong>mov     dl,1</strong>   - загружаем в регистр <strong>dl</strong> значение соответствующее не отмеченному состоянию
  16.                       строки
  17.     <strong>mov     [ecx+eax],dl</strong> - сохраняем новое значение состояния в массиве
  18.     <strong>mov     eax,[name#.hBmp1]</strong> - загружаем в регистр <strong>eax</strong> картинку, соответствующую
  19.                                    не отмеченному состоянию строки.
  20.     <strong>mov     [__hBtn],eax</strong> - сохраняем хендл картинки в переменной <strong>__hBtn</strong>
  21.   <strong>.#name#bez:</strong> - конец кода изменения текущего состояния строки и сохранения картинки,
  22.                     соответствующей новому состоянию строки, в переменной <strong>__hBtn</strong>.
  23.  

В заключительной части макроса выполняется непосредственное рисование картинки, соответствующей новому состоянию строки, вывод на экран текста самой строки и освобождение более ненужных хендлов контекстов

invoke SelectObject,[__hMem],[__hBtn] - выбираем в контекст в памяти картинку соответствующую текущему состоянию строки (фактически рисуем его туда).

invoke BitBlt,[__hdc],[__rcItem.left],[__rcItem.top],20,20,[__hMem],1,1,SRCCOPY - копируем из контекста в памяти нарисованный нами рисунок.

CountString __Buf - подсчитываем длинну строки вместе с нулевым символом.

dec eax - для вывода текста нам необходим длина строки без нулевого символа.

invoke TextOut,[__dis.hDC],20,[__dis.rcItem.top],__Buf,eax - отрисовываем текст строки.

invoke DeleteDC,[__hMem] и invoke DeleteDC,[__hdc - удаляем контексты ставшие ненужными

jmp .finish - совершаем переход в конец процедуры обработки сообщений

.#name#EndNotify: - конец макроса, сюда мы переходим если сообщение не должно обрабатываться нашим макросом.

В общем на данном этапе мы имеем работоспособный элемент управления. Однако мы рассмотрели только две из, перечисленных в начале статьи, функции. На данном этапе мы можем и получить или установить состояние строки (обращение к массиву Spisok.mas), можем получить ассоциированное со строкой значение (посылка сообщения LB_GETITEMDATA элементу управления Spisok.hList), можем уничтожить наш элемент управления если необходимо (вызовом процедуры DestroyWindow с параметром Spisok.hList). Однако стоило ли писать все рассмотренные ранее макросы, если наиболее часто применяемые функции, попрежнему будут выполняться обычными средствами Windows? На мой взгляд нестоило. Если мы создаём свой элемент управления, то мы должны, как мне кажется, обеспечить единообразие доступа ко всем его функциям. Поэтому продолжим рассмотрение макросом уничтожающим наш элемент управления.

Макрос уничтожающий элемент управления.

Макрос, предназначенный для уничтожения нашего элемента управления, назовём name#.Destroy. Обращение к макросу: Spisok.Destroy.

Код (Text):
  1.  
  2. macro name#.Destroy
  3. m_
  4.  invoke DestroyWindow,[name#.#hList]
  5.  ZeroInit name#.mas,266
  6. _m
  7.  

Как видите в этом макросе нет ничего сложного. Первой строкой мы уничтожаем непосредственно элемент управления, а вторая - это макрос, инициализирующий нулями область памяти (начало области - первый параметр) определённой длины (второй параметр в байтах).

Макрос получающий состояние строки элемента управления.

На даном этапе, наш элемент управления играет больше декоративную чем функциональную роль. Действительно, он создаётся, реагирует на действия пользователя, его можно уничтожить, однако результат действий пользователя в программу пока не передаётся. Для получения состояния определённой строки мы напишем макрос под именем name#.GetState. Обращение к нему осуществяется так: Spisok.GetState id.

Здесь id - индекс строки состояние которой мы хотим проверить. А вот собственно текст самого макроса.

Код (Text):
  1.  
  2. macro name#.GetState id
  3. m_
  4. local ..fox,..ext
  5.  mov    ecx,name#.mas
  6.  mov    eax,id
  7.  mov    dl,[eax+ecx]
  8.  cmp      dl,1
  9.  jne    ..fox
  10.  xor     eax,eax
  11.  jmp   ..ext
  12. ..fox:
  13.  mov     eax,1
  14. ..ext:
  15. _m
  16.  

В первой строке макроса мы объявляем две локальные, для этого макроса, метки ..fox и ..ext. Если бы мы использовали эти метки без определения их как локальные, то в случае определения одного элемента управления, все бы скомпилировалось нормально, но если бы мы определили более одного элемента управления, то компилятор, при определении второго элемента управления, сообщил бы о том что метка ..fox уже создана ранее. Это происходит потому, что при определении первого элемента управления метки ..fox и ..ext уже были созданы. Однако при определении этих меток как локальных, компилятор при определении каждого нового элемента управления, создаёт уникальные метки для данного экземпляра макроса. Директива local работает только в рамках макроса. Может возникнуть вопрос: а почему имена макросов, и метки в телах макросов мы досих пор не определяли как локальные, а задавали их в виде: name#.GetState, .#name#No1, name#.onNotify и т.п.? Мы делали так потому, что эти метки создаются уникальными т.к. в их название добавляется имя элемента управления и если их определить локальными, то к ним будет невозможно обращение из основного текста программы.

Далее загружаем начало массива состояний в регистр ecx, а индекс строки в регистр еах и получаем состояние строки в регистре dl. После проверки состояния макрос возвращает 1 если строка отмечена и 0 если неотмечена.

Возможно Вы уже задавались вопросом:"Зачем использовать целый байт для хранения состояния строки, если состояний всего два?". Я использовал такой не эффективный способ для того, чтобы было проще понять работу алгоритма. Однако полагаю Вы уже достаточно поняли работу наших макросов и рассмотрим, как улучшить данный механизм.

Использование одного бита для хранения состояния одной строки.

Для начала создадим отдельно несколько макросов: set_state, set_unState, if_set и if_unset.

Код (Text):
  1.  
  2. установка отмеченного состояния строки
  3. macro set_state base
  4. {
  5.   ror        ax,3
  6.   shr        ah,5
  7.   xor       edx,edx
  8.   mov       dl,al
  9.   mov       cl,ah
  10.   mov       eax,1
  11.   rol       eax,cl
  12.   mov       ecx,base
  13.   or        [ecx+edx],al
  14. }
  15. установка не отмеченного состояния строки
  16. macro set_unState base
  17. {
  18.   ror        ax,3
  19.   shr        ah,5
  20.   xor       edx,edx
  21.   mov       dl,al
  22.   mov       cl,ah
  23.   mov       eax,0xfffffffe
  24.   rol       eax,cl
  25.   mov       ecx,base
  26.   and        [ecx+edx],al
  27. }
  28. переход если строка отмечена
  29. macro if_set base,lab
  30. {
  31.   ror        ax,3
  32.   shr        ah,5
  33.   xor       edx,edx
  34.   mov       dl,al
  35.   mov       cl,ah
  36.   mov       eax,1
  37.   rol       eax,cl
  38.   mov       ecx,base
  39.   test   [ecx+edx],al
  40.   jnz    lab
  41. }
  42. переход если строка не отмечена
  43. macro if_unset base,lab
  44. {
  45.   ror        ax,3
  46.   shr        ah,5
  47.   xor       edx,edx
  48.   mov       dl,al
  49.   mov       cl,ah
  50.   mov       eax,1
  51.   rol       eax,cl
  52.   mov       ecx,base
  53.   test   [ecx+edx],al
  54.   jz    lab
  55. }
  56.  

Во все макросы передаётся адрес начала массива base. Индекс элемента массива не явно передаётся в регистре eax. Как видите все макросы различаются только последними строками, поэтому сперва рассмотрим общую, для всех макросов, часть доступа к биту соответствующему состоянию строки

ror ax,3 - в регистре еах индекс строки. Для доступа к соответствующему биту необходимо определить в каком байте массива он находится. Для этого делим индекс на 8 т.е. сдвигаем значение в регистре ах вправо на три позиции, что равнозначно делению на 8. При этом мы используем циклический сдвиг, при котором самый младший бит регистра записывается в самый старший бит, а остальные сдвигаются от старшего к младшему, как показано на рисунке:

Таким образом в регистре al у нас содержится номер байта в массиве, а старшие три бита регистра ah содержат номер бита в байте.

shr ah,5 - линейно сдвигаем содержимое регистра ah, что бы получить номер бита, содержащего состояние строки. В отличии от ror, shr не записывает содержимое самого младшего бита в самый старший. Работа команды показана на рисунке:

xor edx,edx - обнуляем содержимое регистра edx т.к. мы будем использовать его как смещение в массиве.

mov dl,al - сохраняем номер байта в массиве в регистре dl

mov cl,ah - сохраняем, в регистре cl, номер бита хранящего состояние строки. На это количество бит мы должны будем сдвинуть значение, что бы получить маску доступа к биту. Для команд сдвига в качестве счётчика может использоваться, только регистр cl.

mov eax,1 - сохраняем в регистре eax значение, сдвигая, которое мы и получим маску доступа к интересующему биту. Обратите внимание, что для макроса set_unState это значение равно 0xfffffffe, это означает, что все биты, кроме нулевого, установлены в 1. Во всех остальных макросах в качестве такого значения используется 1, это значит, что в маске, на месте интересующего нас бита, будет стоять единица. Почему так мы рассмотрим чуть ниже, когда будем разбираться с отдельными макросами.

rol eax,cl - сдвигаем значение, чтобы получить маску доступа к соответствующему биту.

mov ecx,base - загружаем в регистр ecx адрес начала массива.

На этом общая, для всех макросов, часть закончилась. И мы рассмотрим особенности каждого макроса.

set_state base - этот макрос устанавливает бит, соответствующий строке с индексом, находящимся в регистре еах. В макрос явно передаётся только адрес начала массива. Установка бита осуществляется командой or [ecx+edx],al. Т.е. мы выполняем логическое "ИЛИ" над байтом, находящимся по базовому адресу из регистра ecx со смещением, относительно базового адреса, в регистре edx, и содержимым регистра al. Напомню, диаграмму выполнения логического "ИЛИ":

Бит операнда источника Бит операнда назначения Результат
1 1 1
1 0 1
0 1 1
0 0 0

Таким образом, биты операнда источника (маски доступа), которые соответствуют не интересующим нас битам операнда назначения, мы устанавливаем в ноль, а биты операнда источника (маски доступа), которые соответствуют интересующим нас битам операнда назначения, мы устанавливаем в единицу.

Для примера: нам нужно установить бит номер 5 (не забывайте, что номера битов отсчитываются от нуля и бит номер 5 будет 6 по расположению в байте). Маску задаём записывая 1 в регистр еах, а затем содержимое регистра еах, линейно сдвигаем вправо на 5 позиций. Таким образом, мы получаем значение, в котором установлен в единицу только пятый бит, а все остальные установлены в ноль, т.к. они нас не интерисуют.

Несколько иная ситуация с макросом set_unState. Он должен наоборот, сбрасывать определённый бит в ноль. Для этого подходит операция логического "И". В таблице диакграмма состояний для логического "И":

Бит операнда источника Бит операнда назначения Результат
1 1 1
1 0 0
0 1 0
0 0 0

Как Вы видите, для того, что бы бит установить в ноль, достаточно установить в ноль интерисующий нас бит операнда источника (маска доступа), а все остальные биты в единицу, и в операнде назначения обнулится только интересующий нас бит, а все остальные останутся без изменений. Соответственно маску мы получаем записав в регистр еах во все биты, кроме самого младшего, единицы и затем сдвигая наш единственный ноль в нужную позицию. Затем выполняется команда and [ecx+edx],al и соответствующий бит сброшен, т.е. состояние строки изменилось на неотмеченное.

Кроме макросов установки отмеченного и неотмеченного состояния строки, у нас есть ещё макросы проверки текущего состояния строки if_set и if_unset. В них передаётся не только базовый адрес начала массива, но и метка lab на которую необходимо перейти, если условие верно. Проверка состояния бита проверяется командой test [ecx+edx],al, в которой в качестве операнда источника используется маска доступа в, которой интересующий нас бит установлен в единицу. Переход осуществляется командами jnz lab в макросе if_set и jz lab в макросе if_unset.

Теперь, после написания макросов, необходимо заменить ими соответствующие строки в макросах .onNotify, .GetState и .onDraw.

Теперь рассмотрим такой пример: Мы заполняем CheckListBox строками, которые характеризуют определённые объекты, например каждой строке соответствует структура о почтовом сообщении, хранящемся на сервере, отмеченные строки означают необходимость удаления сообщения без загрузки с сервера. После того как пользователь отметил все письма, которые необходимо удалить, он нажимает на кнопку удалить и вот здесь встаёт вопрос: "Как определить какая строка соответствует какой структуре?". Мне кажется, что наиболее рациональным было бы присвоить строке 32-х битное значение, являющееся указателем на структуру данных, связанную с данной строкой. Такая возможность предоставляется стредствами стандартного элемента управления ListBox, на основе которого мы и создали свой элемент управления. При вызове макроса .AddString в качестве последнего параметра как раз и может передаваться такое значение. Теперь нам необходимо получить данное значение. Для этого написан следующий макрос:

Макрос получения ассоциированного со строкой значения - .GetData.

Данный макрос, как и все макросы кроме set_state, set_unState, if_set и if_unset, находится внутри основного макроса CheckListBox. Макрос не очень большой:

Код (Text):
  1.  
  2. macro name#.GetData id
  3.   m_
  4.     invoke SendMessage,[name#.hList],LB_GETITEMDATA,id,0
  5.   _m
  6.  

Посылаем элементу управления, сообщение LB_GETITEMDATA требующее 32-х битное значение, ассоциированное со строкий с индексом id. Вот собственно и весь макрос. Аналогично написан макрос устанавливающий ассоциированное значение. Значение возвращается в регистре eax.

Макрос установки ассоциированного со строкой значения - .SetData.

Код (Text):
  1.  
  2. macro name#.SetData id,data
  3.   m_
  4.     invoke SendMessage,[name#.hList],LB_SETITEMDATA,id,data
  5.   _m
  6.  

Вы сами видите, что отличия не велики.В макрос передаётся параметр data - значение ассоциированное со строкой. В сообщении LB_SETITEMDATA, посылаемом элементу управления, передаются индекс строки id, с которой данное значение ассоциируется, и непосредственно значение data.

Теперь остаётся только написать макросы установки состояния CheckBox-а строки. Для простоты мы используем два макроса, которые будут устанавливать отмеченное (.SetState) и не отмеченное (.SetUnState) состояние.

Макросы установки отмеченного и неотмеченного состояний строки.

Код (Text):
  1.  
  2. macro name#.SetState id
  3. m_
  4.   mov     eax,id
  5.   set_state name#.mas
  6. _m
  7. macro name#.SetUnState id
  8. m_
  9.   mov     eax,id
  10.   set_unState name#.mas
  11. _m
  12.  

Работа выполняемая макросами сводится к тому, что они загружают в регистр еах индекс строки, состояние которой нужно изменить, и вызов макроса, который устанавливает отмеченное (set_state в макросе .SetState) или не отмеченное (set_unState в макросе .SetUnState) состояние строки.

Теперь вернёмся к вопросу создания элемента управления. У Вас может возникнуть вопрос, а как работать с нашим элементом управления, если он создаётся не во время выполнения программы, а был описан в секции ресурсов в составе диалогового окна на стадии проектирования программы? Для этого я решил, что необходимо создать макрос, позволяющий загрузить элемент управления из ресурса - .LoadFromResurce

Макрос загружающий элемент управления из секции ресурсов.

Данный макрос определён следующим образом:

Код (Text):
  1.  
  2. macro name#.LoadFromResurce hwnd,hinstance
  3. m_
  4.   invoke GetDlgItem,hwnd,ID
  5.   mov    [name#.hList],eax
  6.   invoke LoadBitmap,hinstance,__IDB_UNCHECK
  7.   mov    [name#.hBmp1],eax
  8.   invoke LoadBitmap,hinstance,__IDB_CHECK
  9.   mov    [name#.hBmp2],eax
  10. _m
  11.  

В макрос передаются следующие параметры:

hwnd - хендл окна которому принадлежит элемент управления;

hinstance - хендл модуля в котором выполняется процедура обработки сообщений родительскому окную. Она получается обычно в начале выполнения программы вызовом функции invoke GetModuleHandle,0.

Теперь перейдём непосредственно к выполняемым внутри макроса инструкциям.

invoke GetDlgItem,hwnd,ID - получаем хендл элемента управления;

mov [name#.hList],eax - сохраняем его в переменной name#.hList. Обращение к ней Spisok.hList;

invoke LoadBitmap,hinstance,__IDB_UNCHECK - загружаем картинку, соответствующую не отмеченному состоянию строки;

mov [name#.hBmp1],eax - сохраняем хендл загруженной картинки;

invoke LoadBitmap,hinstance,__IDB_CHECK - загружаем картинку, соответствующую отмеченному состоянию строки;

mov [name#.hBmp2],eax - сохраняем хендл загруженной картинки.

Вот собственно и весь макрос. Вызывать его необходимо внутри процедуры обработки сообщений родительскому окну, которому принадлежит элемент управления.

Вместо заключения.

Полагаю это всё, что я хотел сообщить в рамках этой статьи. К статье прилагается файл pril.zip который содержит:

pfc.asm - собственно файл содержащий макросы, которые мы рассмотрели в данной статье.

CLB - папка с файлами проекта в РАДАСМ демонстрирующего работу с нашими макросами.

01.04.04 С уважением, pas.

© pas

0 1.959
archive

archive
New Member

Регистрация:
27 фев 2017
Публикаций:
532