Создание нестандартных элементов управления с использованием макросов FASM — Архив WASM.RU
Всё изложенное в данном разделе
не является догмой, поправки и замечания
принимаются по электронной почте pas1@e-mail.ru.
С использованием директивы fix появилась возможность создать свои контролы (элементы управления) без использования библиотек dll и obj-файлов. На мой взгляд плюс в включении контролов в виде макросов в том, что есть возможность изменять их функциональность так, как нужно в каждом конкретном случае. Ещё большим плюсом на мой взгляд является то, что всегда известно как данный элемент будет себя вести т.к. известен его исходный код.
Для начала создадим контрол CheckListBox определённый в Borland C++ Builder 5. Данный контрол представляет собой список строк с CheckBox-ами.
То, что Вы видите на рисунке это контрол уже созданный с помощью макросов.
Для начала определимся с необходимыми функциями создаваемого контрола.
Самые очевидные:
- Создание контрола.
- Уничтожение контрола.
- Добавление строки.
- Получение текущего состояния чекбокса строки.
- Установка состояния чекбокса строки.
Но для начала рассмотрим переменные, необходимые для работы контрола.
Переменные необходимые для работы контрола.
Сначала рассмотрим переменные общие для всех контролов (не забываёте, что нам может понадобится создать несколько контролов одновременно).
Код (Text):
macro CheckListBox [ID,name] { common __Buf: times 100 db 0 __IDB_UNCHECK equ 301 __IDB_CHECK equ 302 __class db 'ListBox',0 virtual at ebx __dis DRAWITEMSTRUCT end virtual __rcItem RECT __hdc dd 0 __hMem dd 0 __item dd 0 __hBtn dd 0В первой строке мы задаём имя макроса 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):
struc DRAWITEMSTRUCT { .CtlType dd ? .CtlID dd ? .itemID dd ? .itemAction dd ? .itemState dd ? .hwndItem dd ? .hDC dd ? .rcItem RECT .itemData dd ? }CtlType - Тип элемента управления требующего перерисовки:
Код (Text):
<strong>ODT_BUTTON</strong> Рисуемая пользовательской программой кнопка <strong>ODT_COMBOBOX</strong> Рисуемое пользовательской программой поле с выпадающим списком <strong>ODT_LISTBOX</strong> Рисуемый пользовательской программой список <strong>ODT_LISTVIEW</strong> Рисуемый пользовательской программой ListView <strong>ODT_MENU</strong> Рисуемый пользовательской программой пункт меню <strong>ODT_STATIC</strong> Рисуемая пользовательской программой статическая метка <strong>ODT_TAB</strong> Tab control (к сожалению я не смог перевести этот пункт)CtlID - Идентификатор элемента управления. Этот член структуры не используется, если отрисовать нужно пункт меню
itemID - Идентификатор пункта меню или индекс пункта поля с выпадающим списком или простым списком.
itemAction - Определяет какое событие произошло
Код (Text):
<strong>ODA_DRAWENTIRE</strong> Элемент управленя должен быть перерисован <strong>ODA_FOCUS</strong> Элемент управления получил или потерял фокус ввода <strong>ODA_SELECT</strong> В элементе управления произошло выделение пунктаitemState -
Код (Text):
<strong>ODS_CHECKED</strong> Пункт меню отмечен, т.е. при перерисовке мы должны нарисовать картинку соответствующую отмеченному состоянию пункта меню. Используется только для меню. <strong>ODS_COMBOBOXEDIT</strong> Перерисовать необходимо поле редактирования (Edit Control) поля с выпадающим списком <strong>ODS_DEFAULT</strong> Пункт является пунктом выбраным поумолчанию. <strong>ODS_DISABLED</strong> Пункт не активен. <strong>ODS_FOCUS</strong> Пункт имеет фокус ввода. <strong>ODS_GRAYED</strong> Пункт должен быть нарисован. Используется только для меню. <strong>ODS_SELECTED</strong> Пункт выделен.hwndItem - Хендл идентифицирующий элемент управления. Для меню этот член структуры идентифицирует меню содержащее данный пункт.
hDC - Хендл контекста элемента управления.
rcItem - Структура содержащая координаты в которых необходимо произвести перерисовку.
itemData - 32-х битное значение, определяемое приложением.
Далее следует непосредственноле определение структуры RECT
__rcItem RECT - структура хранит координаты пункта, который нужно перерисовать.
__hdc dd 0 - переменная предназначенная для временного хранения хендла контекста элемента управления.
__hMem dd 0 - Хендл контекста в памяти совместимого с контекстом элемента управления.
__item dd 0 - Временное хранилище индекса пункта (строки) в элементе управления.
__hBtn dd 0 - Временное хранилище хендла картинки соответствующей текущему состоянию строки.
Далее рассмотрим переменные индивидуальные для каждого элемента управления.
Код (Text):
forward name#.mas: times 250 db 0 name#.hList dd 0 name#.hBmp1 dd 0 name#.hBmp2 dd 0 name#.count dd 0forward - директива компилятору обработать следующие строки столько раз, сколько групп параметров будет передано в макрос.
name#.mas: times 250 db 0 - массив содержащий текущие состояния всех строк в элементе управления. Соответственно чтобы узнать состояние строки по её индексу можно загрузить в один регист адрес соответствующего массива, а в другой индекс строки если байт по полученному адресу = 1 значит строка не отмечена. Пример:
Код (Text):
........ CheckListBox 10,Spisok ........ mov ebx,Spisok.mas ; загружаем адрес массива mov ecx,[ind] ; загружаем индекс mov al,[ebx+ecx] ; загружаем значение соответствующей строки cmp al,1 ........Естественно, что загружать в элемент управления более 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):
struc TEXTMETRIC { .tmHeight dd ? .tmAscent dd ? .tmDescent dd ? .tmInternalLeading dd ? .tmExternalLeading dd ? .tmAveCharWidth dd ? .tmMaxCharWidth dd ? .tmWeight dd ? .tmOverhang dd ? .tmDigitizedAspectX dd ? .tmDigitizedAspectY dd ? .tmFirstChar db ? .tmLastChar db ? .tmDefaultChar db ? .tmBreakChar db ? .tmItalic db ? .tmUnderlined db ? .tmStruckOut db ? .tmPitchAndFamily db ? .tmCharSet db ? }Эта структура используется для обработки данных о шрифтах (например для более точного выравнивания текста выводимого не моноширинным шрифтом т.е. таким в котором буквы имеют не одинаковую ширину).
Семь из двадцати полей структуры определяют внутритекстовые интервалы.
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):
macro name#.Create x,y,sx,sy,hwnd,hinstance m_ 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 mov [name#.hList],eax _mПрежде, чем рассматривать содержимое этого макроса хочу обратить Ваше внимание на то, что фигурные скобки мы заменили на символы m_ и _m они определены в конце файла содержащего данный макрос:
Код (Text):
m_ fix { _m fix }Если Вы читали первую часть моей статьи, посвящённую общим вопросам программирования в FASM, то Вы знаете, что эти строки определяют две символьные макроконстанты m_ и _m которые на одном из этапов компиляции будут заменены на знаки фигурных скобок. Дело в том, что FASM не может определить вложенные определения макросов. Встречая вторую открывающуюся фигурную скобку до того как встретится закрывающаяся, он считает, что в исходном тексте ошибка и прекращает компиляцию. Однако замена символьных макроконстант, определённых директивой fix, происходит на более позднем этапе, чем определение объявлений макросов и такая конструкция проходит.
Однако мы несколько отвлеклись, так что продолжим, и рассмотрим переменные передаваемые в макрос:
Код (Text):
<strong>x</strong> начальная координата окна по горизонтали. <strong>y</strong> начальная координата окна по вертикали. <strong>sx</strong> размер элемента управления по горизонтали <strong>sy</strong> размер элемента управления по вертикали. <strong>hwnd</strong> хендл родительского окна, в котором создаётся элемент управления. <strong>hinstance</strong> хендл модуля, в рамках которого создаётся элемент управления.Далее рассмотрим код непосредственно создающий наш элемент управления.
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):
<strong>WS_CHILD</strong> Указывает на то, что создаваемый элемент управления является дочерним. <strong>WS_VISIBLE</strong> Указывает на то, что элемент создаётся видимым и нет необходимости вызывать процедуру ShowWindow для его отображения. <strong>WS_BORDER</strong> Указывает, что элемент имеет бордюр. <strong>WS_VSCROLL</strong> Указывает, что элемент управления будет содержать вертикальную полосу прокрутки когда это необходимо. <strong>WS_HSCROLL</strong> Указывает, что элемент управления будет содержать горизонтальную полосу прокрутки когда это необходимо. <strong>LBS_OWNERDRAWFIXED</strong> Указывает, что окно будет рисоваться пользовательской программой. <strong>LBS_HASSTRINGS</strong> Определяет, что список содержит элементы, состоящие из строк и позволяет получить текст строки при помощи сообщения <strong>LB_GETTEXT</strong>. Все элементы управления этого типа создаются с этим стилем, кроме отрисовываемых пользовательской программой. <strong>LBS_NOTIFY</strong> Определяет, что элемент управления посылает уведомительные сообщения родительскому окну.mov [name#.hList],eax - Сохраняем хендл созданного элемента управления.
Вот и вся процедура создания элемента управления, однако если мы просто поместим эту процедуру в текст программы, то мы не увидим наш элемент управления. Для того, чтобы наш элемент управления стал видимым мы должны написать макрос который его нарисует в ответ на сообщение, посылаемое системой,родительскому окну WM_DRAWITEM. Этот макрос мы назовём .onDraw
Функция рисования элемента управления.
Следующей мы рассмотрим процедеру рисования нашего элемента управления name#.onDraw. Обращение к ней можно производить следующим образом: Spisok.onDraw
Вот как определён этот макрос:
Код (Text):
macro name#.onDraw m_ cmp [wmsg],WM_DRAWITEM jne .#name#nowmdraw .#name#dr: mov ebx,[lparam] cmp [__dis.CtlID],ID jne .#name#nowmdraw cmp [__dis.itemAction],ODA_DRAWENTIRE jne .finish .#name#drugie: invoke CreateCompatibleDC,[__dis.hDC] mov [__hMem],eax mov eax,[__dis.itemID] mov ecx,name#.mas cmp byte [ecx+eax],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 invoke SendMessage,[__dis.hwndItem],LB_GETTEXT,[__dis.itemID],__Buf CountString __Buf dec eax invoke TextOut,[__dis.hDC],20,[__dis.rcItem.top],__Buf,eax invoke DeleteDC,[__hMem] mov eax,1 jmp .finish .#name#nowmdraw: _mКак видите переменные в макрос не передаются, макрос использует переменные, передаваемые процедуре окна, в котором создан элемент управления. Поместить этот макрос необходимо в начале программы после сохранения в стеке регистров:
Код (Text):
proc WindowProc, hwnd,wmsg,wparam,lparam;Окно в котором мы создаём свой элемент управления. enter push ebx esi edi Spisok.onDraw cmp [wmsg],WM_DESTROY je .wmdestroy cmp [wmsg],WM_CREATE je .wmcreate cmp [wmsg],WM_COMMAND je .wmcommand ....... .finish: pop edi esi ebx returnПредполагается, что в конце процедуры есть метка .finish и все метки в процедуре локальны. Теперь перейдём к рассмотрению самого кода:
cmp [wmsg],WM_DRAWITEM - проверяем какое сообщение послано окну если не WM_DRAWITEM jne .#name#nowmdraw - то переходим в конец макроса и позволяем процедуре обработать другие сообщения.mov ebx,[lparam] - загружаем в регистр ebx указатель на структуру DRAWITEMSTRUCTcmp [__dis.CtlID],ID - проверяем, этому ли конкретному элементу управления послано сообщение jne .#name#nowmdraw - если нет переходим в конец макроса
cmp [__dis.itemAction],ODA_DRAWENTIRE - проверяем какое событие произошло со строкой если не ODA_DRAWENTIREjne .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):
macro name#.AddString string,data m_ inc [name#.count] invoke SendMessage,[name#.hList],LB_ADDSTRING,0,string mov ecx,name#.mas mov dl,1 mov [ecx+eax],dl if data eq else invoke SendMessage,[name#.hList],LB_SETITEMDATA,eax,data end if _mВ макрос передаётся два значения 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):
WM_COMMAND wNotifyCode = HIWORD(wParam); - старшее слово параметра <strong>wParam</strong> содержит код уведомительного сообщения, посланного элементом управления. wID = LOWORD(wParam); - младшее слово параметра <strong>wParam</strong> содержит ID элемента управления, однако не стоит использовать этот параметр для идентификации конкретного элемента управления т.к. в результате ошибки могут оказаться активными в одно и тоже время несколько элементов с одинаковыми ID. hwndCtl = (HWND) lParam; - хендл элемента управления, пославшего уведомительное сообщение родительскому окну. Его можно смело использовать для идентификации конкретного элемента управления.Теперь рассмотрим какие уведомительные сообщения, может посылать элемент управления, основанный на ListBox-е:
Код (Text):
<strong>LBN_DBLCLK</strong> - пользователь сделал двойной щелчек мышью в области элемента управления. <strong>LBN_ERRSPACE</strong> - элемент управления ListBox не может выполнить запрос программы (например недостаточно места в памяти для добавления строки). <strong>LBN_SETFOCUS</strong> - элемент управления получил фокус ввода. <strong>LBN_KILLFOCUS</strong> - элемент управления потерял фокус ввода. <strong>LBN_SELCHANGE</strong> - пользователь выделил строку. <strong>LBN_SELCANCEL</strong> - пользователь отменил выделение строки.Для нас имеют значения только LBN_DBLCLK и LBN_SELCHANGE т.к. нам нужно, что бы состояние элемента изменялось при выделении (одинарный щелчек на строке) или двойном щелчке на строке.
Из всего написанного выше, Вы уже наверно догадались, что помещать этот макрос следует в начале обработчика сообщений WM_COMMAND. Например:
Код (Text):
proc WindowProc, hwnd,wmsg,wparam,lparam;Главное окно программы enter push ebx esi edi cmp [wmsg],WM_DESTROY je .wmdestroy cmp [wmsg],WM_CREATE je .wmcreate cmp [wmsg],WM_COMMAND je .wmcommand ............. .wmcommand: Spisok.onNotify .............Итак после длительного вступления перейдём таки к самомо макросу. Он определён следующим образом:
Код (Text):
macro name#.onNotify m_ mov eax,[name#.#hList] cmp eax,0 je .#name#EndNotify cmp eax,[lparam] jne .#name#EndNotify mov eax,[wparam] shr eax,16 cmp ax,LBN_DBLCLK je .#name#nofinish cmp ax,LBN_SELCHANGE jne .#name#EndNotify .#name#nofinish: invoke GetDC,[name#.hList] mov [__hdc],eax invoke SendMessage,[name#.hList],LB_GETCARETINDEX,0,0 mov [__item],eax 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] mov ecx,name#.mas mov dl,[ecx+eax] cmp dl,1 jne .#name#No1 mov dl,2 mov [ecx+eax],dl mov eax,[name#.hBmp2] mov [__hBtn],eax jmp .#name#bez .#name#No1: mov dl,1 mov [ecx+eax],dl mov eax,[name#.hBmp1] mov [__hBtn],eax .#name#bez: 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: _mТеперь более подробно рассмотрим содержание макроса
macro name#.onNotify - объявление макроса, реагирующего на события в элементе управления.
Код (Text):
mov eax,[name#.#hList] - загрузка в регистр <strong>eax</strong> хендла нашего элемента управления. cmp eax,0 - проверяем равно ли значение загруженное в регист eax нулю, т.е. проверяем создан ли элемент управления je .#name#EndNotify - если элемент управления ещё не создан, пропускаем код макроса. cmp eax,[lparam] - проверяем, какой элемент управления послал уведомительное сообщение jne .#name#EndNotify - если уведомительное сообщение послано не данным элементом управления, пропускаем код макросаДанные строки проверяют создан ли наш элемент управления, если он ещё не создан, то нет смысла выполнять макрос далее. Если элемент управления создан, то проверяем, наш ли элемент управления послал это сообщение, если не наш пропускаем код макроса.
Далее нам нужно отфильтровать только те уведомительные сообщения, на которые наш макрос должен реагировать (LBN_DBLCLK - двойной щелчёк в области нашего элемента управления и LBN_SELCHANGE - изменение выделенной строки):
Код (Text):
mov eax,[wparam] - сохраняем в регистре <strong>еах</strong> параметр <strong>wparam</strong> для дальнейшей обработки shr eax,16 - сдвигаем содержимое регистра <strong>еах</strong> вправо на 16 бит, в результате получаем код уведомительного сообщения в регистре <strong>ax</strong>. cmp ax,LBN_DBLCLK - проводим распознавание уведомительных сообщений, если сообщение послано в ответ на двойной щелчёк мышкой je .#name#nofinish - переходим к выполнению кода макроса cmp ax,LBN_SELCHANGE - опять же проверяем какое сообщение было послано, если сообщение послано в ответ на изменение выделения jne .#name#EndNotify - то мы не пропускаем код макроса, если уведомительное сообщение послано по какой либо другой причине код макроса не выполняется. .#name#nofinish: - начало собственно кода макроса.Далее следует код, который непосредственно рисует изменение состояния нашего элемента управления. Он похож на код макроса 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):
<strong>cmp dl,1</strong> - проверяем предыдущее состояние строки, если строка была не отмечена <strong>jne .#name#No1</strong> - продолжаем выполнение кода, иначе переходим на метку <strong>.#name#No1</strong>, ответственную за изменение отмеченного состояния строки на не отмеченное. <strong>mov dl,2</strong> - т.к. строка была не отмечена загружаем в регистр <strong>dl</strong> новое отмеченное состояние <strong>mov [ecx+eax],dl</strong> - сохраняем новое состояние в массиве <strong>mov eax,[name#.hBmp2]</strong> - загружаем в регистр <strong>eax</strong> хендл картинки, соответствующей новому состоянию строки <strong>mov [__hBtn],eax</strong> - сохраняем хендл картинки в переменной <strong>__hBtn</strong> <strong>jmp .#name#bez</strong> - далее пропускаем код ответственный за изменение отмеченного состояния строки на не отмеченное <strong>.#name#No1:</strong> - начало кода ответственного за изменение отмеченного состояния строки на не отмеченное. <strong>mov dl,1</strong> - загружаем в регистр <strong>dl</strong> значение соответствующее не отмеченному состоянию строки <strong>mov [ecx+eax],dl</strong> - сохраняем новое значение состояния в массиве <strong>mov eax,[name#.hBmp1]</strong> - загружаем в регистр <strong>eax</strong> картинку, соответствующую не отмеченному состоянию строки. <strong>mov [__hBtn],eax</strong> - сохраняем хендл картинки в переменной <strong>__hBtn</strong> <strong>.#name#bez:</strong> - конец кода изменения текущего состояния строки и сохранения картинки, соответствующей новому состоянию строки, в переменной <strong>__hBtn</strong>.В заключительной части макроса выполняется непосредственное рисование картинки, соответствующей новому состоянию строки, вывод на экран текста самой строки и освобождение более ненужных хендлов контекстов
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):
macro name#.Destroy m_ invoke DestroyWindow,[name#.#hList] ZeroInit name#.mas,266 _mКак видите в этом макросе нет ничего сложного. Первой строкой мы уничтожаем непосредственно элемент управления, а вторая - это макрос, инициализирующий нулями область памяти (начало области - первый параметр) определённой длины (второй параметр в байтах).
Макрос получающий состояние строки элемента управления.
На даном этапе, наш элемент управления играет больше декоративную чем функциональную роль. Действительно, он создаётся, реагирует на действия пользователя, его можно уничтожить, однако результат действий пользователя в программу пока не передаётся. Для получения состояния определённой строки мы напишем макрос под именем name#.GetState. Обращение к нему осуществяется так: Spisok.GetState id.
Здесь id - индекс строки состояние которой мы хотим проверить. А вот собственно текст самого макроса.
Код (Text):
macro name#.GetState id m_ local ..fox,..ext mov ecx,name#.mas mov eax,id mov dl,[eax+ecx] cmp dl,1 jne ..fox xor eax,eax jmp ..ext ..fox: mov eax,1 ..ext: _mВ первой строке макроса мы объявляем две локальные, для этого макроса, метки ..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):
установка отмеченного состояния строки macro set_state base { ror ax,3 shr ah,5 xor edx,edx mov dl,al mov cl,ah mov eax,1 rol eax,cl mov ecx,base or [ecx+edx],al } установка не отмеченного состояния строки macro set_unState base { ror ax,3 shr ah,5 xor edx,edx mov dl,al mov cl,ah mov eax,0xfffffffe rol eax,cl mov ecx,base and [ecx+edx],al } переход если строка отмечена macro if_set base,lab { ror ax,3 shr ah,5 xor edx,edx mov dl,al mov cl,ah mov eax,1 rol eax,cl mov ecx,base test [ecx+edx],al jnz lab } переход если строка не отмечена macro if_unset base,lab { ror ax,3 shr ah,5 xor edx,edx mov dl,al mov cl,ah mov eax,1 rol eax,cl mov ecx,base test [ecx+edx],al jz lab }Во все макросы передаётся адрес начала массива 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):
macro name#.GetData id m_ invoke SendMessage,[name#.hList],LB_GETITEMDATA,id,0 _mПосылаем элементу управления, сообщение LB_GETITEMDATA требующее 32-х битное значение, ассоциированное со строкий с индексом id. Вот собственно и весь макрос. Аналогично написан макрос устанавливающий ассоциированное значение. Значение возвращается в регистре eax.
Макрос установки ассоциированного со строкой значения - .SetData.
Код (Text):
macro name#.SetData id,data m_ invoke SendMessage,[name#.hList],LB_SETITEMDATA,id,data _mВы сами видите, что отличия не велики.В макрос передаётся параметр data - значение ассоциированное со строкой. В сообщении LB_SETITEMDATA, посылаемом элементу управления, передаются индекс строки id, с которой данное значение ассоциируется, и непосредственно значение data.
Теперь остаётся только написать макросы установки состояния CheckBox-а строки. Для простоты мы используем два макроса, которые будут устанавливать отмеченное (.SetState) и не отмеченное (.SetUnState) состояние.
Макросы установки отмеченного и неотмеченного состояний строки.
Код (Text):
macro name#.SetState id m_ mov eax,id set_state name#.mas _m macro name#.SetUnState id m_ mov eax,id set_unState name#.mas _mРабота выполняемая макросами сводится к тому, что они загружают в регистр еах индекс строки, состояние которой нужно изменить, и вызов макроса, который устанавливает отмеченное (set_state в макросе .SetState) или не отмеченное (set_unState в макросе .SetUnState) состояние строки.
Теперь вернёмся к вопросу создания элемента управления. У Вас может возникнуть вопрос, а как работать с нашим элементом управления, если он создаётся не во время выполнения программы, а был описан в секции ресурсов в составе диалогового окна на стадии проектирования программы? Для этого я решил, что необходимо создать макрос, позволяющий загрузить элемент управления из ресурса - .LoadFromResurce
Макрос загружающий элемент управления из секции ресурсов.
Данный макрос определён следующим образом:
Код (Text):
macro name#.LoadFromResurce hwnd,hinstance m_ invoke GetDlgItem,hwnd,ID mov [name#.hList],eax invoke LoadBitmap,hinstance,__IDB_UNCHECK mov [name#.hBmp1],eax invoke LoadBitmap,hinstance,__IDB_CHECK mov [name#.hBmp2],eax _mВ макрос передаются следующие параметры:
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
Создание нестандартных элементов управления с использованием макросов FASM
Дата публикации 11 апр 2004