Смекни!
smekni.com

Подклассы окон (стр. 1 из 4)

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

Именно это и называется порождением подкласса окон. Основная идея заключается в использовании собственной функции обработки сообщений, которая выполняла бы требуемую обработку, отличную от стандартной. При этом в качестве процедуры обработки сообщений по умолчанию должна выступать процедура, определенная в уже существующем классе.

Для реализации этого метода нам надо сделать три вещи:

· узнать адрес процедуры обработки сообщений заданного окна (или заданного класса).

· научиться вызывать нужную процедуру вместо процедуры обработки сообщений по умолчанию.

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

Первую и третью задачи удобно решать с помощью функции

LONG SetWindowLong( hWnd, GWL_WNDPROC, lpfnNewProc );

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

Теперь нам надо только организовать обращение к старой процедуре обработки сообщений вместо процедуры по умолчанию (DefWindowProc). Сделать это непосредственно мы не можем, так как при вызове оконной процедуры мы должны связать ее с приложением, зарегистрировавшем этот класс. Вместо этого нам надо воспользоваться функцией:

LONG CallWindowProc( lpfnProc, hWnd, wMsg, wPar, lPar );

Итак, приведем небольшой пример:

static HANDLE hInstance;

static FARPROC lpfnNewProc;

static FARPROC lpfnOldProc;

LONG WINAPI ChildProc( HWND, UINT, UINT, LONG );

// функция обработки сообщений главного окна

LONG WINAPI _export WinProc(

HWND hWnd, UINT wMsg, UINT wPar, LONG lPar

) {

static HWND hChild;

switch ( wMsg ) {

case WM_CREATE:

lpfnNewProc= MakeProcInstance( (FARPROC)ChildProc, hInstance );

hChild= CreateWindow(

“BUTTON”, “Btn A”,

BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE,

10,10, 50,50,

hWnd, 0, hInstance, NULL

);

// заменяем процедуру обработки сообщений дочернего окна

lpfnOldProc= (FARPROC)SetWindowLong(

hChild,GWL_WNDPROC,(LONG)lpfnNewProc

);

break;

case WM_DESTROY:

DestroyWindow( hChild );

FreeProcInstance( lpfnNewProc );

break;

...

}

return DefWindowProc( hWnd, wMsg, wPar, lPar );

}

LONG WINAPI _export ChildProc(

HWND hWnd, UINT wMsg, UINT wPar, LONG lPar

) {

// специфичная обработка сообщений

// и вызов прежней функции, а не функции DefWindowProc

return CallWindowProc( lpfnOldProc, hWnd, wMsg, wPar, lPar );

}

Конечно, рассмотренный нами вариант не единственный. Так, например, мы можем заменять функцию обработки сообщений не окна, а класса. Тогда все вновь создаваемые окна этого класса будут применять нашу процедуру. Для этого мы должны использовать функцию

LONG SetClassLong( hWnd, GCW_WNDPROC, lpfnNewProc );

Что неудобно, так это то, что мы должны сначала создать окно, а только затем заменять процедуру обработки сообщений. Мы можем поступить и иначе - сначала узнать адрес процедуры обработки сообщений, используя функцию

GetClassInfo( hInstance, lpszClassName, lpWndClass );

которая заполняет структуру WNDCLASS информацией о данном классе, а затем создать свой класс, который будет применять вместо процедуры обработки сообщений по умолчанию процедуру этого класса.

Связывание данных с окном

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

Для такого связывания в Windows предусмотрено два разных метода. Первый метод основан на выделении дополнительного пространства для данных пользователя в структуре, описывающей окно или класс окон. Этот способ достаточно эффективен, но ограниченно применим, так как выделенное пространство статично и не может изменить свой размер.

Для использования данных окна (или класса) мы должны, при регистрации класса окон указать размеры дополнительного пространства, выделяемого в струткуре окна (поле .cbWndExtra структуры WNDCLASS) и в структуре класса (поле .cbClsExra). При выделении пространства оно автоматически обнуляется. Подробнее об этом смотри лекцию 2.

Для доступа к элементам описаний класса и окна можно применять функции:

UINT GetWindowWord( hWnd, nOffset );

LONG GetWindowLong( hWnd, nOffset );

UINT SetWindowWord( hWnd, nOffset, wNewValue );

LONG SetWindowLong( hWnd, nOffset, dwNewValue );

UINT GetClassWord( hWnd, nOffset );

LONG GetClassLong( hWnd, nOffset );

UINT SetClassWord( hWnd, nOffset, wNewValue );

LONG SetClassLong( hWnd, nOffset, dwNewValue );

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

Второй метод основан на применении специального списка свойств (property) окна. Этот список может динамически изменяться, но работа с ним медленее, чем с данными окна. Кроме того он размещается в локальной памяти модуля USER, поэтому ограничен размерами свободной памяти модуля USER.

Так как списки свойств размещаются не в нашем приложении, то перед уничтожением окна, мы должны удалить все внесенные нами свойства. Свойства состоят из имени (строка символов) и слова данных. Часто это слово рассматривается как хендл блока данных. Мы можем записывать читать, удалять и перебирать свойства, связанные с окном. Для этого предназначены следующие функции:

BOOL SetProp( hWnd, lpszName, hData );

HANDLE GetProp( hWnd, lpszName );

HANDLE RemoveProp( hWnd, lpszName );

int EnumProp( hWnd, lpfnEnumProc );

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

Так как Windows не знает, хендл какого блока данных (глобального или локального), объекта GDI или просто данные связан с конкретным элементом списка свойств, то при удалении записи эти данные не удаляются, а передаются Вам для их удаления.

Ресурсы приложения

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

Сейчас несколько обобщим эти правила и рассмотрим несколько видов ресурсов более подробно. Для описания ресурсов мы должны сформировать файл описания ресурсов, имеющий расширение .RC. В этом файле перечисляются ресурсы, которые могут быть использованы приложением.

Описание ресурсов имеет следующий вид:

ResNameId TypeNameId [load-opt] [mem-opt] ResSource

Каждый ресурс должен иметь собственное уникальное имя или номер ResNameId и имя или номер типа ресурса TypeNameId.

Эти имена задаются либо текстом (имя), либо числом (номер), либо символическим именем (номер).

Примеры:

MYBITMAP BITMAP my_bmp.bmp

100 ICON my_ico.ico

MYDATA 500 my_data.dat

#define IconID 101

IconID ICON second.ico

(компилятор ресурсов может использовать директивы препроцессора C и включать заголовочные файлы .H, для задания символических имен).

Далее, при работе с ресурсами они будут загружаться из файла приложения в память. При этом для ресурса обычно выделяется блок глобальной памяти мы можем задать некоторые характеристики ресурса как блока памяти mem-opt и определять некоторые правила его загрузки load-opt.

load-opt, описывая правила загрузки, может быть:

PRELOAD ресурс должен загружаться в памяти при запуске приложения

LOADONCALL ресурс загружается только по требованию (используется по умолчанию)

mem-opt задает характеристики выделяемого блока и может быть:

FIXED ресурс должен размещаться в фиксированном блоке памяти

MOVEABLE ресурс размещается в перемещаемом блоке памяти (используется по умолчанию)

DISCARDABLE перемещаемый ресурс может быть удален из памяти (практически любой перемещаемый ресурс может быть удален, так как его содержимое не меняется)

Наконец, нам требуются данные этого ресурса ResSource. Некоторые виды ресурсов должны быть размещены в отдельном файле, в этом случае в качестве ResSource используется имя файла, другие виды ресурсов требуют непосредственного описания непосредственно в файле описания ресурсов, в этом случае ResSource может быть:

BEGIN

данные ресурса

END

или

{

данные ресурса

}

Иногда допускается либо использования файлов, либо непосредственное описание ресурсов. Некоторый “разнобой” может быть связан с применением компиляторов (и редакторов) ресурсов разных фирм - так как многие из них используют расширенные возможности.

Так, например, редактор ресурсов Borland WorkShop может описывать практически все ресуры непосредственно в файле описания ресурсов, включая их в виде дампа ресурса, а стандартные компилятор ресурсов Microsoft RC не допускает этого, например, для курсоров или битмапов. Компилятор ресурсов Symantec позволяет применять кавычки при задании имен ресурса или типа, что позволяет составлять имена из нескольких слов, что невозможно для Borland и Microsoft и т.д.

Windows предусматривает несколько стандартных типов ресурсов, а Вы можете легко описать ресурсы собственного типа, указав собственный тип (или номер, больший 255 - номера типов от 0 до 255 зарезервированы Windows). При задании данных собственного ресурса Вы можете указать имя файла, содержащего этот ресурс - тогда этот файл включается в ресурс как он есть, либо описав в виде текста непосредственно в файле описания ресурсов:

MyResource MyType

BEGIN

“This is a 0-terminated string\0”, 1, 2, 3, 4, 5,

100, 0x1000

END

В качестве типа ресурса можно указать стандартный тип RCDATA, который соответствует включаемым в файл описания ресурсов данным пользователя в этом-же формате. Если Вы хотите получить доступ к Вашим ресурсам, то надо воспользоваться парой функций: