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

Все статьи

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

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

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

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

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

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

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

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

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

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

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

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 определена следующим образом:

struc DRAWITEMSTRUCT
 {
   .CtlType    dd ?
   .CtlID      dd ?
   .itemID     dd ?
   .itemAction dd ?
   .itemState  dd ?
   .hwndItem   dd ?
   .hDC        dd ?
   .rcItem     RECT
   .itemData   dd ?
 }

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

  ODT_BUTTON	    Рисуемая пользовательской программой кнопка
  ODT_COMBOBOX	    Рисуемое пользовательской программой поле с выпадающим списком
  ODT_LISTBOX	    Рисуемый пользовательской программой список
  ODT_LISTVIEW	    Рисуемый пользовательской программой ListView
  ODT_MENU	    Рисуемый пользовательской программой пункт меню
  ODT_STATIC	    Рисуемая пользовательской программой статическая метка
  ODT_TAB	    Tab control (к сожалению я не смог перевести этот пункт)

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

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

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

  ODA_DRAWENTIRE    Элемент управленя должен быть перерисован	
  ODA_FOCUS	      Элемент управления получил или потерял фокус ввода
  ODA_SELECT	      В элементе управления произошло выделение пункта

itemState -

  ODS_CHECKED       Пункт меню отмечен, т.е. при перерисовке мы должны нарисовать картинку
                    соответствующую отмеченному состоянию пункта меню.  Используется только для меню.
  ODS_COMBOBOXEDIT  Перерисовать необходимо поле редактирования (Edit Control) поля с 
                    выпадающим списком
  ODS_DEFAULT       Пункт является пунктом выбраным поумолчанию.
  ODS_DISABLED      Пункт не активен.
  ODS_FOCUS         Пункт имеет фокус ввода.
  ODS_GRAYED        Пункт должен быть нарисован. Используется только для меню.
  ODS_SELECTED      Пункт выделен.

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

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

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

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

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

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

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

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

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

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

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

forward
     name#.mas: times 250 db  0
     name#.hList dd 0
     name#.hBmp1     dd 0
     name#.hBmp2     dd 0
     name#.count dd 0

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

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

........
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 определена следующим образом:

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, которая создаёт элемент управления. Она определена следующим образом:

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 они определены в конце файла содержащего данный макрос:

m_ fix {
_m  fix }

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

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

x          начальная координата окна по горизонтали.
y          начальная координата окна по вертикали.
sx         размер элемента управления по горизонтали
sy         размер элемента управления по вертикали.
hwnd       хендл родительского окна, в котором создаётся элемент управления.
hinstance хендл модуля, в рамках которого создаётся элемент управления.

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

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
Этой командой мы создаём наш элемент управления. Единственное, что требует пояснения, на мой взгляд, это стили с которыми мы создаём наш элемент управления:

WS_CHILD     Указывает на то, что создаваемый элемент управления является дочерним.
WS_VISIBLE   Указывает на то, что элемент создаётся видимым и нет необходимости вызывать
              процедуру ShowWindow для его отображения.
WS_BORDER    Указывает, что элемент имеет бордюр.
WS_VSCROLL   Указывает, что элемент управления будет содержать вертикальную полосу
              прокрутки когда это необходимо.
WS_HSCROLL   Указывает, что элемент управления будет содержать горизонтальную полосу
               прокрутки когда это необходимо.

LBS_OWNERDRAWFIXED  Указывает, что окно будет рисоваться пользовательской программой.

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

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

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

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

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

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

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

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

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 указатель на структуру 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.

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

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

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 оно определено следующим образом:

WM_COMMAND  
wNotifyCode = HIWORD(wParam); - старшее слово параметра wParam содержит код уведомительного
                                сообщения, посланного элементом управления.

wID = LOWORD(wParam); - младшее слово параметра wParam содержит ID элемента управления, однако 
                        не стоит использовать этот параметр для идентификации конкретного
                        элемента управления т.к. в результате ошибки могут оказаться активными 
                        в одно и тоже время несколько элементов с одинаковыми ID.

hwndCtl = (HWND) lParam; - хендл элемента управления, пославшего уведомительное сообщение
                           родительскому окну. Его можно смело использовать для идентификации 
                           конкретного элемента управления.

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

LBN_DBLCLK - пользователь сделал двойной щелчек мышью в области элемента управления.
LBN_ERRSPACE - элемент управления ListBox не может выполнить запрос программы (например 
                 недостаточно места в памяти для добавления строки).
LBN_SETFOCUS - элемент управления получил фокус ввода.
LBN_KILLFOCUS - элемент управления потерял фокус ввода.
LBN_SELCHANGE - пользователь выделил строку.
LBN_SELCANCEL - пользователь отменил выделение строки.

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

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

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
.............

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

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 - объявление макроса, реагирующего на события в элементе управления.

  mov     eax,[name#.#hList] - загрузка в регистр eax хендла нашего элемента управления.
  cmp     eax,0 - проверяем равно ли значение загруженное в регист eax нулю, т.е. проверяем 
                  создан ли элемент управления
  je   .#name#EndNotify - если элемент управления ещё не создан, пропускаем код макроса.
  cmp     eax,[lparam] - проверяем, какой элемент управления послал уведомительное сообщение
  jne  .#name#EndNotify - если уведомительное сообщение послано не данным элементом управления, 
                          пропускаем код макроса

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

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

  mov     eax,[wparam] - сохраняем в регистре еах параметр wparam для дальнейшей обработки
  shr     eax,16 - сдвигаем содержимое регистра еах вправо на 16 бит, в результате получаем код
                   уведомительного сообщения в регистре ax.
  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 картинку соответствующую изменённому состояни строки.

    cmp     dl,1    - проверяем предыдущее состояние строки, если строка была не отмечена
    jne  .#name#No1 - продолжаем выполнение кода, иначе переходим на метку .#name#No1,
                        ответственную за изменение отмеченного состояния строки на не отмеченное.
    mov     dl,2    - т.к. строка была не отмечена загружаем в регистр dl новое
                        отмеченное состояние
    mov     [ecx+eax],dl - сохраняем новое состояние в массиве
    mov     eax,[name#.hBmp2] - загружаем в регистр eax хендл картинки, соответствующей
                                    новому состоянию строки
    mov     [__hBtn],eax - сохраняем хендл картинки в переменной __hBtn
    jmp   .#name#bez   - далее пропускаем код ответственный за изменение отмеченного состояния
                           строки на не отмеченное
  .#name#No1:   - начало кода ответственного за изменение отмеченного состояния строки на
                   не отмеченное.
    mov     dl,1   - загружаем в регистр dl значение соответствующее не отмеченному состоянию
                      строки
    mov     [ecx+eax],dl - сохраняем новое значение состояния в массиве
    mov     eax,[name#.hBmp1] - загружаем в регистр eax картинку, соответствующую
                                   не отмеченному состоянию строки.
    mov     [__hBtn],eax - сохраняем хендл картинки в переменной __hBtn
  .#name#bez: - конец кода изменения текущего состояния строки и сохранения картинки,
                    соответствующей новому состоянию строки, в переменной __hBtn.

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

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.

macro name#.Destroy
m_
 invoke DestroyWindow,[name#.#hList]
 ZeroInit name#.mas,266
_m

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

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

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

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

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.

установка отмеченного состояния строки
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. Макрос не очень большой:

macro name#.GetData id
  m_
    invoke SendMessage,[name#.hList],LB_GETITEMDATA,id,0
  _m

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

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

macro name#.SetData id,data
  m_
    invoke SendMessage,[name#.hList],LB_SETITEMDATA,id,data
  _m

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

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

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

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

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

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

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.

2002-2013 (c) wasm.ru