Сообщения для органа управления Trackbar
Ниже мы перечислили сообщения, которые приложение может посылать органу управления Trackbar при помощи функции SendMessage :
Сообщение | Описание |
TBM_CLEARSEL | Отмена выделения диапазона значений в окне Trackbar |
TBM_CLEARTICS | Удаление рисок |
TBM_GETCHANNELRECT | Определение координат и размеров воображаемого прямоугольника, ограничивающего область перемещения движка |
TBM_GETLINESIZE | Определение величины (в рисках) на которую перемещается движок, если пользователь нажимает клавиши с кодами VK_LEFT, VK_RIGHT, VK_UP или VK_DOWN |
TBM_GETNUMTICS | Определение количества рисок в окне Trackbar |
TBM_GETPAGESIZE | Определение величины (в рисках) на которую перемещается движок, если пользователь нажимает клавиши с кодами VK_PRIOR или VK_NEXT |
TBM_GETPOS | Определение текущего положения движка |
TBM_GETPTICS | Функция SendMessage, посылающая это сообщение, возвращает указатель на массив позиций рисок |
TBM_GETRANGEMAX | Определение максимальной позиции движка |
TBM_GETRANGEMIN | Определение минимальной позиции движка |
TBM_GETSELEND | Определение конечной позиции выделенной области |
TBM_GETSELSTART | Определение начальной позиции выделенной области |
TBM_GETTHUMBLENGTH | Определение длины движка в пикселах |
TBM_GETTHUMBRECT | Определение расположения и размеров движка |
TBM_GETTIC | Определение позиции риски с заданным номером |
TBM_GETTICPOS | Определение физического расположения указанной риски в системе координат внутренней области окна |
TBM_SETLINESIZE | Установка величины (в рисках) на которую перемещается движок, если пользователь нажимает клавиши с кодами VK_LEFT, VK_RIGHT, VK_UP или VK_DOWN |
TBM_SETPAGESIZE | Установка величины (в рисках) на которую перемещается движок, если пользователь нажимает клавиши с кодами VK_PRIOR или VK_NEXT |
TBM_SETPOS | Установка текущего положения движка |
TBM_SETRANGE | Установка минимальной и максимальной позиции движка в окне Trackbar |
TBM_SETRANGEMAX | Установка максимальной позиции движка в окне Trackbar |
TBM_SETRANGEMIN | Установка минимальной позиции движка в окне Trackbar |
TBM_SETSEL | Установка начальной и конечной позиции выделенной области |
TBM_SETSELEND | Установка конечной позиции выделенной области |
TBM_SETSELSTART | Установка начальной позиции выделенной области |
TBM_SETTHUMBLENGTH | Установка длины движка в пикселах |
TBM_SETTIC | Установка позиции риски с заданным номером. Не используется для первой и последней риски, которые создаются автоматически |
TBM_SETTICFREQ | Установка интервала, с которым располагаются риски |
Детальное описание параметров этих сообщений вы найдете в SDK. Из-за недостатка места мы подробно рассмотрим только самые важные из них.
Прежде всего, если оран управления Trackbar расположен в диалоговой панели, вы должны определить его идентификатор. Это можно сделать с помощью функции GetDlgItem (описанной в 12 томе "Библиотеки системного программиста"):
hTrackBar = GetDlgItem(hdlg, IDC_TRACKBAR);
В этом примере в переменную hTrackBar записывается идентификатор окна органа управления Trackbar, который определен в диалоговой панели с идентификатором IDC_TRACKBAR (рис. 7.4).
При инициализации органа управления Trackbar вы должны определить диапазон изменения значений, т. е. минимальную и максимальную позицию движка в окне Trackbar. Это можно сделать с помощью сообщения TBM_SETRANGE :
SendMessage(hTrackBar, TBM_SETRANGE, TRUE, MAKELPARAM(1, nTrackCnt));
Через параметр wParam вместе с этим сообщением передается флаг перерисовки. Если он равен TRUE, сразу после установки диапазона окно органа управления будет перерисовано, если FALSE - нет.
Значение MAKELPARAM(min, max), записанное в lParam, определяет минимальное min и максимальное max значения позиции движка.
Затем нужно установить расстояние, на которое перемещается движок при управлении им с помощью мыши и клавиатуры. Для этого окну органа управления Trackbar нужно послать сообщения TBM_SETPAGESIZE и TBM_SETLINESIZE , например:
SendMessage(hTrackBar, TBM_SETPAGESIZE, 0, 1); SendMessage(hTrackBar, TBM_SETLINESIZE, 0, 1);
Для этих сообщений параметр wParam должен быть равен нулю. В параметр lParam следует записать значение шага.
В данном случае мы устанавливаем одинаковый шаг, однако вы можете использовать и разные шаги. Например, для грубого позиционирования вы можете указать в сообщении TBM_SETPAGESIZE шаг, равный 10, а для тонкого - указать в сообщении TBM_SETLINESIZE шаг, равный 1.
Для принудительной установки движка Trackbar в новую позицию используйте сообщение TBM_SETPOS :
SendMessage(hTrackBar, TBM_SETPOS, TRUE, nCurTrack);
Параметр wParam этого сообщения содержит флаг позиционирования. Если он равен TRUE (как в приведенном выше примере), движок устанавливается в позицию, указанную параметром lParam. Если же этот флаг равен FALSE, движок не перемещается.
Заметьте, что есть одно отличие между полосой просмотра Scrollbar и органом управления Trackbar. Когда пользователь делает щелчок мышью по полосе просмотра, приложение должно в ответ на это установить новое положение движка с помощью функции SetScrollPos . В противоположность этому, в указанной ситуации орган управления Trackbar перемещает движок автоматически.
Создание органа управления Trackbar
Для создания органа управления Trackbar можно воспользоваться функцией CreateWindowEx , указав ей предопределенный класс окна TRACKBAR_CLASS . При этом следует использовать следующие стили, определяющие расположение и внешний вид органа управления Trackbar:
Стиль | Описание |
TBS_HORZ | Горизонтальное расположение окна Trackbar |
TBS_VERT | Вертикальное расположение окна Trackbar |
TBS_TOP | Риски будут расположены сверху (используется вместе со стилем TBS_HORZ) |
TBS_BOTTOM | Риски будут расположены снизу (используется вместе со стилем TBS_HORZ) |
TBS_LEFT | Риски будут расположены слева (используется вместе со стилем TBS_VERT) |
TBS_RIGHT | Риски будут расположены справа (используется вместе со стилем TBS_VERT) |
TBS_BOTH | Риски будут расположены с обеих сторон |
TBS_AUTOTICKS | Если указан этот стиль, риски создаются для каждого возможного значения из диапазона значений, определенных для Trackbar |
TBS_NOTICKS | Сразу после создания окна Trackbar риски не отображаются, однако позже вы их сможете определить при помощи сообщений TBM_SETTIC и TBM_SETTICFREQ, которые будут рассмотрены ниже |
TBS_ENABLESELRANGE | Возможно выделение диапазона значений |
Не забудьте также инициализировать библиотеку стандартных органов управления, вызвав функцию InitCommonControls :
InitCommonControls();
Чаще всего, однако, орган управления Trackbar создается не функцией CreateWindowEx, а редактором диалоговых панелей. Именно так мы и поступили в приложении Compact Disk Player, просто переместив изображение Trackbar из палитры органов управления в диалоговую панель.
Если для работы вы используете систему Microsoft Visual C++ версии 2.0 , предназначенную для Microsoft WindowsNT, то вам потребуется отредактировать регистрационную базу данных (иначе в указанной палитре не будет новых органов управления). Процесс редактирования несложен и описан в SDK.
Прежде всего вам нужно запустить приложение REGEDIT , что можно сделать, например, с помощью всемогущей кнопки Start. На экране появится содержимое регистрационной базы данных в виде дерева просмотра (рис. 7.2).
Рис. 7.2. Просмотр содержимого регистрационной базы данных
Теперь вам нужно раскрыть ветвь этого дерева, которая ведет к набору параметров редактора диалогов. Эта ветвь обозначается как HKEY_CURRENT_USER\Software\Microsoft\Visual C++ 2.0 Dialog Editor.
Далее из меню Edit выберите строки New и Binary Value. Задайте имя для нового элемента данных как ChicagoControls. После этого из меню Edit выберите строку Modify и введите значение 01 00 00 00. Результат показан на рис. 7.3.
Рис. 7.3. Внесенные изменения в регистрационную базу данных, позволяющие использовать новые органы управления в системе разработки Microsoft Visual C++ версии 2.0
Завершите работу редактора регистрационной базы данных. Теперь после перезапуска Microsoft Visual C++ в соответствующей палитре вам будут доступны новые органы управления.
После перемещения изображения органа управления Trackbar в проектируемую диалоговую панель вы можете сделать по этому изображению двойной щелчок левой клавишей мыши и определить стили в блокноте Trackbar Properties (рис. 7.4).
Рис. 7.4. Определение стилей органа управления Trackbar
Операционная система Windows 95 для программиста
В этом разделе мы расскажем вам об очень простом органе управления Up-Down , который мы будем называть органом циклического просмотра.
Внешне этот орган управления похож на вырожденную полосу просмотра Scrollbar, у которой нет движка, а верхняя и нижняя кнопка расположены рядом. |
Вы можете создать вертикальный или горизонтальный орган циклического просмотра, просто указав соответствующий стиль окна органа Up-Down.
Обычно орган управления Up-Down используется для изменения значения какого-либо параметра (например, количества копий при печати). При этом он комбинируется с однострочным текстовым редактором, как это показано на рис. 7.7.
Комбинирование органа управления Up-Down и однострочного текстового редактора
В этом случае можно сделать так, что когда пользователь нажимает кнопки органа управления Up-Down, в окне текстового редактора появляются значения, которые находятся в заранее заданном диапазоне. Пользователь может также ввести нужное значение непосредственно в окне текстового редактора.
Вы также можете объединить орган циклического просмотра с другим органом управления, например, с органом управления Tab или Trackbar. Способ такого объединения органов управления будет описан ниже.
Извещение от органа управления Up-Down
Когда позиция органа управления Up-Down (т. е. текущее значение, связанное с органом) изменяется, родительское окно получает извещение UDN_DELTAPOS , которое поступает в форме сообщения WM_NOTIFY .
При этом в параметре lParam передается адрес структуры NM_UPDOWN , содержащей информацию об изменении:
typedef struct _NM_UPDOWN { NMHDR hdr; // заголовок извещения int iPos; // текущая позиция int iDelta; // предполагаемая величина изменения позиции } NM_UPDOWNW;
Обработчик этого извещения может разрешить или запретить изменение позиции, возвратив, соответственно, значение TRUE или FALSE.Кроме того, обработчик извещения может при необходимости изменить содержимое поля iDelta.
Сообщения для органа управления Up-Down
Для органа управления Up-Down определены следующие сообщения:
Сообщение | Описание |
UDM_GETACCEL | Получение параметров режима ускорения работы органа управления Up-Down |
UDM_GETBASE | С помощью этого сообщения приложение может определить, какая система счисления (десятичная или шестнадцатиричная) используется для органа Up-Down |
UDM_GETBUDDY | Определение идентификатора окна органа управления, сцепленного с органом Up-Down |
UDM_GETPOS | Определение текущего значения |
UDM_GETRANGE | Определение интервала значений |
UDM_SETACCEL | Установка параметров режима ускорения работы органа управления Up-Down |
UDM_SETBASE | Установка десятичной или шестнадцатиричной системы счисления |
UDM_SETBUDDY | Подключение сцепленного органа управления |
UDM_SETPOS | Установка текущего значения |
UDM_SETRANGE | Установка интервала значений |
Сообщение UDM_SETACCEL позволяет установить режим ускорения. В этот режим орган управления Up-Down переходит в том случае, если пользователь держит одну из кнопок органа нажатой в течении определенного интервала времени. После этого текущее значение, связанное с органом Up-Down, начинает изменяться автоматически. Подробности вы можете найти в справочной системе SDK.
Для подключения органа Up-Down к окну другого органа управления вам может пригодиться сообщение UDM_SETBUDDY. Параметр wParam этого сообщения должен содержать идентификатор окна органа управления, к которому будет выполняться подключение.
С помощью сообщения UDM_SETRANGE вы можете установить диапазон значений (такая операция может вам пригодиться при инициализации органа Up-Down, созданного функцией CreateWindowEx). Параметр wParam этого сообщения должен быть равен нулю, а параметр lParam необходимо указать следующим образом:
lParam = (LPARAM) MAKELONG((short)nMax, (short)nMin);
Здесь nMin и nMax задают, соответственно, нижний и верхний предел изменения значения. Параметр nMin не должен быть меньше константы UD_MINVAL, а параметр nMax - больше константы UD_MAXVAL. Кроме того, минимальное и максимальное значение не должны отличаться друг от друга больше чем на значение константы UD_MAXVAL.
С помощью сообщения UDM_SETPOS можно задать текущее значение. При этом в параметр wParam нужно записать ноль, а параметр lParam подготовить так:
lParam = (LPARAM) MAKELONG((short)nPos, 0);
Параметр nPos должен содержать новое значение.
При установке системы счисления вы должны в параметр wParam сообщения UDM_SETBASE записать значение 10 (для десятичной системы счисления) или 16 (для шестнадцатиричной). В параметр lParam нужно записать нулевое значение.
Создание органа управления Up-Down
Орган управления Up-Down создается функцией CreateWindowEx на базе предопределенного класса окна UPDOWN_CLASS . При этом следует сохранить полученный от функции CreateWindowEx идентификатор для посылки окну органа Up-Down управляющих сообщений.
Вы можете также переместить изображение пиктограммы органа Up-Down из палитры редактора диалогов в проектируемую диалоговую панель. Если вы работаете с системой Microsoft Visual C++ версии 2.0 , не забудьте внести изменения в регистрационную базу данных, описанные в этой главе.
Для определения идентификатора окна органа управления Up-Down, расположенного в диалоговой панели, вы можете воспользоваться функцией GetDlgItem .
Есть еще один способ, который удобен при объединении органа Up-Down с каким-либо другим органом управления. Этот способ основан на использовании функции CreateUpDownControl :
HWND CreateUpDownControl( DWORD dwStyle, // стиль окна органа Up-Down int x, // расположение окна int y, int cx, // размеры окна int cy, HWND hParent, // иднтификатор родительского окна int nID, // идентификатор органа Up-Down HINSTANCE hInst, // идентифкатор приложения HWND hBuddy, // идентификатор сцепленного органа int nUpper, // верхняя граница значений int nLower, // нижняя граница значений int nPos); // начальное значение
Функция CreateUpDownControl выполняет несколько действий.
Прежде всего, она создает орган управления Up-Down с помощью функции CreateWindowEx на базе предопределенного класса окна UPDOWN_CLASS . Затем она устанавливает диапазон возможных значений и начальное значение, посылая окну органа управляющие сообщения. Затем функция CreateUpDownControl подключает орган Up-Down к органу управления с идентификатором hBuddy.
Остановимся подробнее на объединении органов управления .
Окно органа управления, с которым сцепляется орган Up-Down, называется сцепленным окном (buddy window ). Так как при сцеплении органы действуют как единое целое и находятся внутри одной рамки (рис.7.7), пользователю кажется, что он работает с одним органом управления.
Вы можете сцепить окна органов управления двумя различными способами.
Во-первых, для органа управления Up-Down можно задать стиль окна UDS_AUTOBUDDY . При этом последний сцепляется с органом управления, расположенным под ним (с предыдущим по порядку в шаблоне диалоговой панели).
Во-вторых, вы можете сцепить органы управления, послав окну органа Up-Down сообщение UDM_SETBUDDY . Именно этот способ и используется функцией CreateUpDownControl.
При создании органа Up-Down вы должны указывать обычные стили окна, такие как WS_CHILD , WS_BORDER и WS_VISIBLE , а также дополнительные, определяющие поведение этого органа управления:
Стиль | Описание |
UDS_ALIGNLEFT | Орган Up-Down будет выровнен по левой границе окна сцепленного с ним органа управления |
UDS_ALIGNRIGHT | То же, но по правой границе |
UDS_SETBUDDYINT | В заголовок окна органа управления, сцепленного с органом Up-Down, будет автоматически записываться текущее значение, установленное для органа Up-Down. Если же орган Up-Down сцеплен с органом Listbox, заголовок изменяться не будет. Вместо этого будет изменяться номер выделенного элемента списка |
UDS_NOTHOUSANDS | Используется вместе со стилем UDS_SETBUDDYINT. Указывает, что при отображении текущего значения не нужно разделять разряды тысяч десятичной точкой |
UDS_ARROWKEYS | Если указан этот стиль, для изменения текущего значения можно использовать клавиши перемещения курсора по вертикали |
UDS_HORZ | Размещение окна органа Up-Down по горизонтали |
UDS_WRAP | При достижении в процессе перебора значений верхней или нижней границы будет происходить переход, соответственно, к нижнему или верхнему значению. Таким образом, возможна организация циклического перебора возможных значений |
hwndUpDown = CreateUpDownControl ( WS_CHILD | WS_BORDER | WS_VISIBLE | UDS_WRAP | UDS_ARROWKEYS | UDS_ALIGNRIGHT | UDS_SETBUDDYINT, 0, 0, 0, 0, hdlg, IDC_UPDOWN, hInst, hwndEdit,10, 1, 1);
Обратите внимание, что мы не указали размеры и расположение окна органа Up-Down, так как при сцеплении они устанавливаются автоматически.
Операционная система Windows 95 для программиста
В этом разделе мы собрали основные сведения, которые нужны, чтобы быстро перейти от 16-разрядного программирования в среде Windows версии 3.1 к 32-разрядному для операционных систем Microsoft Windows95 и Microsoft Windows NT.
Для краткости далее мы будем называть 16-разрядный программный интерфейс Microsoft Windowsверсии 3.1 интерфейсом Win16 , а новый, 32-разрядный, - интерфейсом Win32 . Эта терминология соответствует использованной в Microsoft SDK.
Итак, начнем с самого начала - с функции WinMain.
Так как приложения Win32 работают
Так как приложения Win32 работают с 32-разрядными идентификаторами, а также 32-разрядными координатами (графические функции), очевидно, что в Microsoft Windows95 и Microsoft Windows NT должны были измениться многие функции программного интерфейса.
Например, функции, возвращающие 32-разрядное слово, в старшей и младшей половине которого упакованы 16-разрядные значения, должны быть изменены или заменены.
Если раньше в приложении Win16 вы могли вызывать функции прерывания INT 21h для выполнения вызовов DOS, теперь вам придется пользоваться соответствующим набором функций интерфейса Win32.
Так как по сравнению с Microsoft Windows версии 3.1 в операционных системах Microsoft Windows 95 и Microsoft Windows NT полностью изменились структура памяти и методы управления памятью, многие привычные вам функции больше не нужны (например, функция GlobalFix ).
Функция окна WndProc
Для приложений Win32 вы можете использовать одно из двух приведенных ниже описаний функции окна:
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
Сравним это со старым описанием, которым мы пользовались в приложениях Win16:
LRESULT CALLBACK __export WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
Обратите внимание, что в новом варианте отсутствует ключевое слово __export. В 32-разрядных приложениях необходимость в явном экспортировании функции окна отпала. Транслятор Microsoft Visual C++ версии 2.0 вообще не распознает __export как ключевое слово.
Идентификатор окна hWnd, имеющий тип HWND, стал в Microsoft Windows95 и Microsoft Windows NT 32-разрядным, так же как и все остальные идентификаторы. Код сообщения msg, имеющий тип UINT, также стал 32-разрядным.
Однако наибольшие изменения затронули параметры wParam и lParam. Они не только оба стали 32-разрядными, но и изменили свой формат по сравнению с тем, что был в приложениях Win16.
Напомним, что в приложениях Win16 параметр wParam был 16-разрядный, а lParam - 32-разрядный. Теперь оба эти параметра стали 32-разрядными.
Казалось бы, это не должно вызвать никаких существенных изменений, однако давайте вспомним, что все идентификаторы в Win32 стали 32-разрядными. В результате изменился формат сообщений.
Функция WinMain
В 11 томе "Библиотеки системного программиста", мы описывали функцию WinMain для приложений Win16 следующим образом:
int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);
Для приложений Win32 это описание немного изментися:
int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow);
На первый взгляд, изменений немного - мы заменили PASCAL на APIENTRY . Однако есть и другие, невидимые невооруженным глазом изменения.
Прежде всего, вспомним определение типа int. Разрядность переменной этого типа зависит от системы. Для DOS и Microsoft Windows версии 3.1 эта разрядность равна 16 бит, для Microsoft Windows 95 и Microsoft Windows NT - 32 бит. Если вы собираетесь составлять программу таким образом, чтобы она работала в разных системах (или даже на разных платформах), не следует делать никаких предположений относительно разрядности переменных типа int.
Что же касается типов PASCAL и pascal, то в 32-разрядной системе разработки Microsoft Visual C++ версии 2.0 они не рекомендуются для использования, хотя и определены для совместимости следующим образом:
#define PASCAL __stdcall #define pascal __stdcall
Вместо этих типов вы должны указывать типы APIENTRY, WINAPI, CALLBACK (последний используется в функциях обратного вызова, например, в функции окна):
#define CALLBACK __stdcall #define WINAPI __stdcall #define APIENTRY WINAPI
Согласно документации, ключевое слово __stdcall определяет функцию, аргументы которой помещаются в стек справа налево.
Займемся теперь параметрами функции WinMain.
Приложениям Win16 через первые два параметра передаются, соответственно, идентификатор приложения hInstance и идентификатор предыдущей копии приложения hPrevInstance, если в системе одновременно работает несколько копий одного приложения. Если же запущена только одна копия приложения, параметр hPrevInstance имеет значение NULL.
Последнее обстоятельство часто используется приложениями Win16 для того чтобы разрешать пользователю запускать только одну копию приложения.
Приложения Win32 работают в разных адресных пространствах, поэтому они не могут воспользоваться идентификатором своей предыдущей копии, как приложения Win16. Более того, такое приложение вообще никак не сможет использовать параметр hPrevInstance , так как его значение для 32-разрядных приложений Microsoft Windows 95 и Microsoft Windows NT всегда равно NULL.
Сказанное, однако, не означает, что теперь вы не сможете запретить пользователям запускать несколько копий вашего приложения. Просто для этого потребуются другие средства (например, семафоры или прямой поиск окон функцией FindWindow). Соответствующие примеры приложений будет приведены ниже.
Другое новшество касается не только идентификаторов приложений, но и всех других идентификаторов в 32-разрядных версиях Microsoft Windows. Идентификаторы теперь стали 32-разрядными. И если раньше вы могли определять переменные для хранения идентификаторов как int, unsigned и т. п., то теперь необходимо пользоваться специально предназначенными для этого типами (HINSTANCE, HICON, HBRUSH и т. д.). Впрочем, так нужно было поступать и раньше, при создании приложений Win16.
Что же касается параметров lpCmdLine и nCmdShow, то здесь практически ничего не изменилось.
Параметр lpCmdLine по-прежнему указывает на строку параметров, а параметр nCmdShow содержит их количество. Однако теперь тип LPSTR соответствует 32-разрядному смещению переменной типа CHAR, а не дальнему указателю на эту переменную в формате <селектор:смещение>.
Параметр nCmdShow определяет способ отображения окна (минимизированное SW_MINIMIZE, нормальное SW_SHOW и т. д.). Мы уже рассказывали об использовании этого параметра в 11 томе "Библиотеки системного программиста".
Обработка 32-разрядных сообщений
В приложениях Win16 параметр lParam часто использовался для передачи сразу двух значений, соответственно, через младшее и старшее слово. Для удобства выделения этих значений предназначались специальные макрокоманды LOWORD и HIWORD .
Например, для сообщения WM_COMMAND в младшем слове параметра lParam передавался 16-разрядный идентификатор дочернего окна hWnd (органа управления, пославшего это сообщение), в старшем - 16-разрядный код извещения wCmd. Через параметр wParam передавался 16-разрядный идентификатор органа управления wId (рис. 1.14).
Рис. 1.14. Формат параметров wParam и lParam для сообщения WM_COMMAND в приложениях Win16
В приложениях Win32 идентификатор окна hWnd стал 32-разрядным, поэтому его никак нельзя разместить в старшем слове параметра lParam. Теперь приходится отводить для идентификатора окна все разряды параметра lParam.
А что делать с кодом извещения wCmd, который оказался "выселен" из хорошо обжитого им параметра lParam?
Его пришлось перенести в старшее слово параметра wParam, удвоившего свою разрядность (рис. 1.15).
Рис. 1.15. Формат параметров wParam и lParam для сообщения WM_COMMAND в приложениях Win32
Таким образом, идентификатор окна остался на месте, а код извещения "переехал" в старшее слово параметра wParam. Аналогичным образом изменился формат и других сообщений.
Приведем два фрагмента обработчика сообщения WM_COMMAND, первый из которых используется в приложениях Win16, а второй - в приложениях Win32.
Итак, первый фрагмент.
case WM_COMMAND: { wId = wParam; hWnd = LOWORD(lParam); nCmd = HIWORD(lParam); ... }
Приложения Win32 должны разбирать сообщение WM_COMMAND на составные части по-другому:
case WM_COMMAND: { wId = LOWORD(wParam); hWnd = (HWND)(UINT)lParam; nCmd = HIWORD(wParam); ... }
В файле windowsx.h определены макрокоманды разборки сообщений (message crackers), которые позволяют "извлечь" из сообщений отдельные параметры.
Приведем для примера определения макрокоманд разборки сообщения WM_COMMAND из файла windowsx.h:
#define GET_WM_COMMAND_ID(wp, lp) LOWORD(wp) #define GET_WM_COMMAND_HWND(wp, lp) (HWND)(lp) #define GET_WM_COMMAND_CMD(wp, lp) HIWORD(wp) #define GET_WM_COMMAND_MPS(id, hwnd, cmd) \ (WPARAM)MAKELONG(id, cmd), (LONG)(hwnd)
Есть и более впечатляющие макрокоманды, позволяющие значительно упростить внешний вид обработчика сообщений в функции окна.
Предположим, нам надо организовать обработку сообщений WM_CREATE и WM_LBUTTONDOWN . Мы готовим две функции с именами WndProc_OnCreate и WndProc_OnLButtonDown, которые будут заниматься обработкой этих сообщений:
BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct); void WndProc_OnLButtonDown(HWND hWnd, BOOL fDoubleClick, int x, int y, UINT keyFlags);
Имена функций не играют никакой роли, однако Microsoft рекомендует составлять их из префикса имени функции окна (например, WndProc) с добавлением строки _On и названия сообщения, в котором удален префикс WM_.
Затем в функции окна мы используем макрокоманду HANDLE_MSG , указывая ей в качестве первого параметра идентификатор окна, в качестве второго - номер сообщения и, наконец, в качестве третьего - имя функции, которая будет обрабатывать сообщение:
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate); HANDLE_MSG(hWnd, WM_LBUTTONDOWN, WndProc_OnLButtonDown); default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } }
Теперь оператор switch будет выглядеть компактнее, так как для обработки каждого сообщения в него добавляется только одна строка.
Следует, однако, заметить, что для работы с макрокомандой HANDLE_MSG вы должны использовать в качестве имен для последних двух параметров функции WndProc имена wParam и lParam (только эти имена).
Как выглядит функция обработчика сообщения, вызываемая макрокомандой HANDLE_MSG?
Так же, как и обычная функция, за одним исключением - она должна оканчиваться вызовом макрокоманды FORWARD_<имя сообщения> в операторе return (даже если функция имеет тип void):
void WndProc_OnLButtonDown(HWND hWnd, BOOL fDoubleClick, int x, int y, UINT keyFlags) { ... return FORWARD_WM_LBUTTONDOWN(hWnd, fDoubleClick, x, y, keyFlags, DefWindowProc); }
Но при обработке сообщения WM_CREATE вы должны в случае успешной инициализации данных окна вернуть значение TRUE, иначе окно так и не будет создано:
BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { ... return TRUE; }
Вы можете создать перечисленные выше макрокоманды и для обработки собственных сообщений, взяв за основу определения из файла windowsx.h.
В этой книге мы приведем несколько примеров, демонстрирующих применение макрокоманд разборки сообщений. Для отключения предупреждающего сообщения о том, что функция типа void возвращает управление при помощи оператора return, перед определением функции обработчика сообщения добавляем следующую строку:
#pragma warning(disable: 4098)
Операционная система Windows 95 для программиста
К настоящему моменту мы перечислили только основные отличия, которые сразу бросаются в глаза при изучении программного интерфейса Win32. Более тонкие нюансы мы разберем дальше на примерах конкретных приложений.
Итак, перейдем к практике. Поставим перед собой задачу создать простейшее приложение Win32 с одним главным окном, пока без меню, линейки инструментов Toolbar и других элементов пользовательского интерфейса, специфических для Microsoft Windows95.
предназначена для Windows NT,
Система разработки Microsoft Visual C++ версии 2. 0 предназначена для Windows NT, поэтому, хотя вы и можете работать с ней в среде Windows 95, у вас могут возникнуть некоторые проблемы.
Если в процессе компиляции файла ресурсов приложения *.rc происходит зацикливание, отредактируйте файл system.ini. Найдите в нем раздел [keyboard] и удалите (либо закройте символом комментария) в строке oemansi.bin= имя файла xlat866.bin:
[keyboard] subtype= type=4 keyboard.dll= oemansi.bin=xlat866.bin
Вот что у вас должно получиться в результате:
oemansi.bin=
Вторая проблема, которая может перед вами возникнуть, вызвана тем, что Microsoft Visual C++ версии 2.0 отмечает загрузочные модули создаваемых приложений как предназначенные для работы в среде Windows NT, в то время как приложения Microsoft Windows 95 должны быть отмечены по-другому. В результате окна приложения могут не получать некоторые извещения. Могут возникнуть проблемы и с диалоговыми панелями.
Выход заключается в ручном редактировании параметров редактора связи для каждого создаваемого вновь приложения. Для этого из меню Project выберите строку Settings. На экране появится блокнот Project Settings. Откройте в этом блокноте страницу Link (рис. 1.18).
Рис. 1.18. Страница Link в блокноте Project Settings системы разработки Microsoft Visual C++ версии 2.0
Выбирая по одному проекты Win32 Debug и Win32 Release в списке Settings For в списке Project Options укажите параметр:
/SUBSYSTEM:windows,4.0
Кроме того, в список объектных модулей и библиотек Object/Library Modules добавьте библиотеку comctl32.lib. Эта библиотека необходима, если вы собираетесь работать с новыми органами управления Microsoft Windows 95.
Для того чтобы в палитре органов управления редактора диалогов появились новые органы управления, необходимо внести изменения в регистрационную базу данных. Эти изменения описаны в разделе "Орган управления Trackbar" седьмой главы этой книги.
Если вы приобрели SDK для Microsoft Windows 95, необходимо настроить параметры системы разработки Microsoft Visual C++ версии 2.0 таким образом, чтобы вначале использовались h- и lib-файлы из SDK, а только потом из Microsoft Visual C++.
Для этого выберите строку Options из меню Tools и в появившемся блокноте откройте страницу Directories. В списке Show Directories for выберите строку Include files. Затем нажмите кнопку Add и добавьте путь к каталогу include системы SDK. С помощью кнопок Move Up и Move Down добейтесь, чтобы этот каталог был указан до каталогов msvc20\include и msvc20\mfc\include.
Аналогичную операцию следует выполнить и для файлов библиотек, выбрав из списка Show Directories for строку Library files.
Если в вашем компьютере установлено мало оперативной памяти, вы не сможете использовать интегрировнную среду разработки Microsoft Visual C++. Тем не менее, вам будет доступна пакетная программа nmake.exe, с помощью которой можно выполнить трансляцию исходных текстов приложений. Образцы файла makefile, который необходимо подготовить для этой программы, вы найдете в каталоге с исходными текстами приложений, которые поставляются в составе SDK.
И последнее. Вместо встроенного отладчика, который не всегда работает в среде Microsoft Windows 95, используйте отладчик WinDbg, который поставляется в составе SDK для Microsoft Windows 95.
Window Application
За основу мы возьмем приложение WINDOW из 11 тома "Библиотеки системного программиста".
Внешний вид главного окна 32-разрядного варианта приложения WINDOW, запущенного в среде Microsoft Windows95, показан на рис. 1.16.
Рис. 1.16. Внешний вид приложения Window Application
Если сделать щелчок левой клавишей мыши по внутренней области окна, на экране появится сообщение (рис. 1.17).
Рис. 1.17. Сообщение, которое появляется после щелчка левой клавишей мыши внутри окна приложения Window Application
Если вы обратили внимание на внешний вид системного меню окон Microsoft Windows 95, то заметили, что вместо ни о чем не говорящей горизонтальной черточки в левой части заголовка окна отображается маленькая пиктограмма. По внешнему виду этой пиктограммы пользователь сможет легко узнать окно вашего приложения.
Надпись на заголовке окна выровнена влево, а не отцентрирована, как это было раньше в Microsoft Windows версии 3.1.
В правой части заголовка появились пиктограммы, с помощью которых можно выполнить следующие действия:
Пиктограмма | Назначение |
Минимизация окна приложения | |
Восстановление нормального размера окна | |
Удаление окна, которое приведет к завершению работы приложения (если удаляется главное окно) |
Исходные тексты приложения Window Application
Теперь мы перейдем к исходным текстам приложения Window Application. Основной файл исходного текста приложения приведен в листинге 1.1.
Листинг 1.1. Файл window\window.c
#include <windows.h> #include <windowsx.h> #include "afxres.h" #include "resource.h"
// ----------------------------------------------------- // Описание функций // -----------------------------------------------------
// Функция главного окна LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam);
// Функция для обработки сообщения WM_CREATE BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct);
// Функция для обработки сообщения WM_LBUTTONDOWN void WndProc_OnLButtonDown(HWND hWnd, BOOL fDoubleClick, int x, int y, UINT keyFlags);
// Функция для обработки сообщения WM_DESTROY void WndProc_OnDestroy(HWND hWnd);
// ----------------------------------------------------- // Глобальные переменные // -----------------------------------------------------
// Идентификатор приложения HINSTANCE hInst;
// Название приложения char szAppName[] = "Window";
// Заголовок главного окна приложения char szAppTitle[] = "Window Application";
// ----------------------------------------------------- // Функция WinMain // ----------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg;
hInst = hInstance;
// Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { // Если окно приложения было свернуто в пиктограмму, // восстанавливаем его if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE);
// Выдвигаем окно приложения на передний план SetForegroundWindow(hWnd); return FALSE; }
// Регистрируем класс окна memset(&wc, 0, sizeof(wc));
// Поля wc.cbSize и wc.hIconSm определены в структуре // WNDCLASSEX, которой можно пользоваться для // регистрации класса окна в Windows 95 wc.cbSize = sizeof(WNDCLASSEX);
// Поле wc.hIconSm задает идентификатор маленькой // пиктограммы, которая будет отображаться в левой // части заголовка окна (в области системного меню). // Загружаем пиктограмму из ресурсов приложения при // помощи функции LoadImage, так как функция // LoadIcon может загрузить только обычную пиктограмму wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON_SM), IMAGE_ICON, 16, 16, 0);
// Завершаем заполнение структуры WNDCLASSEX wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst;
// Для загрузки обычной пиктограммы вы можете // использовать функцию LoadImage wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0);
wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = NULL; wc.lpszClassName = szAppName;
// Вызываем функцию RegisterClassEx, которая выполняет // регистрацию окна if(!RegisterClassEx(&wc))
// В случае ошибки пытаемся зарегистрировать окно // функцией RegisterClass if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE;
// Создаем главное окно приложения hWnd = CreateWindow(szAppName, szAppTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL); if(!hWnd) return(FALSE);
// Отображаем окно ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd);
// Запускаем цикл обработки сообщений while(GetMessage (&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
// ----------------------------------------------------- // Функция WndProc // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { // Для сообщения WM_CREATE назначаем обработчик, // расположенный в функции WndProc_OnCreate HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate);
// Для сообщения WM_LBUTTONDOWN назначаем обработчик, // расположенный в функции WndProc_OnLButtonDown HANDLE_MSG(hWnd, WM_LBUTTONDOWN, WndProc_OnLButtonDown);
// Для сообщения WM_DESTROY назначаем обработчик, // расположенный в функции WndProc_OnDestroy HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy);
default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } }
// ----------------------------------------------------- // Функция WndProc_OnCreate // ----------------------------------------------------- BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { return TRUE; }
// ----------------------------------------------------- // Функция WndProc_OnDestroy // ----------------------------------------------------- // Отключаем предупреждающее сообщение о том, что // функция типа void возвращает управление при помощи // оператора return. Этот оператор нужен для // использования макрокоманды FORWARD_WM_LBUTTONDOWN #pragma warning(disable: 4098) void WndProc_OnDestroy(HWND hWnd) { PostQuitMessage(0); return FORWARD_WM_DESTROY(hWnd, DefWindowProc); }
// ----------------------------------------------------- // Функция WndProc_OnLButtonDown // -----------------------------------------------------
#pragma warning(disable: 4098) void WndProc_OnLButtonDown( HWND hWnd, BOOL fDoubleClick, int x, int y, UINT keyFlags) { MessageBox(NULL, "Hello, 32-bit world!", "Window", MB_OK);
return FORWARD_WM_LBUTTONDOWN(hWnd, fDoubleClick, x, y, keyFlags, DefWindowProc); }
Файл resource.h (листинг 1.2) был создан автоматически системой Microsoft Visual C++ и содержит символические определения идентификаторов ресурсов приложения.
Листинг 1.2. Файл window\resource.h
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by window.rc // #define IDI_APPICON 101 #define IDI_APPICON_SM 102
// Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NO_MFC 1 #define _APS_NEXT_RESOURCE_VALUE 105 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
В файле ресурсов определены две пиктограммы - стандартная (с идентификатором IDI_APPICON) и маленькая (с идентификатором IDI_APPICON_SM).
Файл resource.h автоматически включается в файл определения ресурсов window.rc (листинг 1.3). Последний был создан также автоматически системой Microsoft Visual C++.
Листинг 1.3. Файл window\window.rc
//Microsoft Visual C++ generated resource script. // #include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h"
////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS
#ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // // TEXTINCLUDE //
1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END
2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END
3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END
////////////////////////////////////////////////////////////// #endif // APSTUDIO_INVOKED
////////////////////////////////////////////////////////////// // // Icon // IDI_APPICON ICON DISCARDABLE "window.ico" IDI_APPICON_SM ICON DISCARDABLE "windowsm.ico"
#ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // // Generated from the TEXTINCLUDE 3 resource. // ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
Функция WinMain
Прежде всего приложение сохраняет свой идентификатор в глобальной переменной hInst. Затем оно выполняет проверку, не было ли это приложение запущено ранее.
Напомним, что для выполнения проверки мы не можем воспользоваться параметром hPrevInstance, так как для приложений Win32 этот параметр всегда равен NULL.
Воспользуемся функцией FindWindow , которая позволяет найти окно верхнего уровня по имени класса окна или по заголовку окна:
HWND FindWindow( LPCTSTR lpClassName, // указатель на имя класса LPCTSTR lpWindowName // указатель на имя окна );
Через параметр lpClassName передается указатель на текстовую строку, закрытую двоичным нулем, в которую необходимо записать имя класса искомого окна. Вместо имени можно также передать через этот параметр атом, созданный функцией GlobalAddAtom , соответствующей строке имени класса. С атомами вы уже встречались в 11 томе "Библиотеки системного программиста".
Напомним, что атом является 16-разряной величиной, поэтому вы должны записать его значение в младшую половину 32-разрядного параметра lpClassName. Старшая половина при этом должна содержать нулевое значение.
На базе одного и того же класса можно создать несколько окон. Если вам нужно найти окно с заданным заголовком, то имя заголовка следует передать через параметр lpWindowName. Если же подойдет любое окно, параметр lpWindowName может иметь значение NULL.
Если окно будет найдено, функция FindWindow вернет его идентификатор. В противном случае она возвратит значение NULL.
В нашем случае мы можем ограничится указанием только первого параметра:
hWnd = FindWindow(szAppName, NULL); if(hWnd) { if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return FALSE; }
Пользователь может не помнить, какие приложения уже запущены, а какие нет. Когда он запускает приложение, делая двойной щелчок по соответствующей пиктограмме, то ожидает что на экране появится его главное окно. И в этом он абсолютно прав. Если приложение Window Application было запущено ранее, целесообразно активизировать и выдвинуть на передний план его главное окно. Это именно то, к чему приготовился пользователь.
Прежде всего мы проверяем, не было ли окно приложения минимизировано. Для этого мы вызываем известную вам из предыдущих томов "Библиотеки системного программиста" функцию IsIconic . Если окно минимизировано, восстанавливаем его нормальный размер с помощью функции ShowWindow , передавая ей в качестве первого параметра идентификатор найденного окна, а в качестве второго - константу SW_RESTORE .
Затем вне зависимости от того, было окно минимизировано или нет, выдвигаем его на передний план и активизируем с помощью функции SetForegroundWindow :
BOOL SetForegroundWindow( HWND hwnd // идентификатор активизируемого окна );
В случае успеха эта функция возвращает значение TRUE, при ошибке - FALSE.
Если была найдена работающая копия приложения Window Application, мы завершаем работу текущей копии приложения. Если же копия работающего приложения не найдена, функция WinMain приступает к инициализации приложения.
Прежде всего выполняется регистрация класса окна. Приложения Win32 должны выполнять такую регистрацию немного не так, как это делают приложения Win16.
Напомним, что для регистрации класса окна приложения Win16 должны заполнить структуру типа WNDCLASS и затем передать ее адрес функции RegisterClass . В операционной системе Microsoft Windows 95 эта структура была расширена и теперь имеет тип WNDCLASSEX :
typedef struct _WNDCLASSEX { UINT cbSize; UINT style; // -------------------------------- WNDPROC lpfnWndProc; // Эта часть структуры WNDCLASSEX int cbClsExtra; // идентична структуре WNDCLASS int cbWndExtra; // HANDLE hInstance; // HICON hIcon; // HCURSOR hCursor; // HBRUSH hbrBackground; // LPCTSTR lpszMenuName; // LPCTSTR lpszClassName; // -------------------------------- HICON hIconSm; } WNDCLASSEX;
В начало структуры было добавлено поле cbSize (размер структуры WNDCLASSEX), в конец - поле hIconSm (идентификатор маленькой пиктограммы, которая отображается в левом верхнем углу заголовка главного окна приложения). В остальном структура WNDCLASSEX в точности соответствует структуре WNDCLASS.
Для того чтобы зарегистрировать класс окна в операционной системе Microsoft Windows 95, вы должны заполнить структуру WNDCLASSEX и передать ее адрес функции RegisterClassEx . Заполнение структуры выполняется следующим образом:
memset(&wc, 0, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON_SM), IMAGE_ICON, 16, 16, 0); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = NULL; wc.lpszClassName = szAppName;
Для получения идентификатора маленькой пиктограммы мы воспользовались новой функцией LoadImage , так как функция LoadIcon позволяет загрузить только стандартную пиктограмму.
Функция LoadImage - это возможно именно та функция, которой вам не хватало для работы с изображениями в программном интерфейсе Win16. С ее помощью вы можете загрузить пиктограмму, курсор, а также битовое изображение, причем не только из ресурсов приложения, но и из файла:
HANDLE LoadImage( HINSTANCE hInst, // идентификатор модуля, // содержащего изображение LPCTSTR lpszName, // имя или идентификатор изображения UINT uType, // тип изображения int cxDesired, // желаемая ширина изображения int cyDesired, // желаемая высота изображения UINT fuLoad // флаги загрузки );
Прежде всего мы перечислим возможные значения параметра fuLoad, определяющие выполняемые этой функцией действия.
Параметр | Описание |
LR_LOADFROMFILE | Функция загружает изображение из файла, заданного параметром lpszName |
LR_DEFAULTSIZE | Если значение параметров cxDesired и cyDesired равны нулю, при загрузке изображения используются размеры по умолчанию |
LR_LOADREALSIZE | Если значение параметров cxDesired и cyDesired равны нулю, при загрузке пиктограммы или курсора используются размеры по умолчанию, взятые из системных метрик |
LR_DEFAULTCOLOR | Для отображения используется текущий цветовой формат |
LR_MONOCROME | Изображение загружается как черно-белое (монохромное) |
LR_LOADMAP3DCOLORS | При загрузке функция ищет в изображении таблицу цветов и заменяет темно-серый, серый и светло-серый цвета на системные, которые используются для того чтобы придать изображению трехмерный вид. Конкретно, выполняется такая замена:Исходный цвет RGB На что меняется128, 128, 128 COLOR_3DSHADOW 192, 192, 192 COLOR_3DFACE 223, 223, 223 COLOR_3DLIGHT |
LR_LOADTRANSPARENT | Загрузка в "прозрачном" режиме. В процессе загрузки выбирается значение цвета первого пиксела изображения и заменяется соответствующим значением из таблицы цветов, содержащим цвет окна по умолчанию COLOR_WINDOW . Все пикселы изображения, которые используют это значение таблицы, приобретают цвет COLOR_WINDOW |
LR_SHARED | Если изображение загружается несколько раз, его идентификатор будет использован повторно. Этот флаг не рекомендуется использовать для изображений с нестандартными размерами, которые могут изменяться, а также для изображений, загружаемых из файла |
Загружая пиктограмму из ресурсов приложения при заполнении структуры WNDCLASSEX мы установили значение параметра fuLoad равным 0, что соответствует LR_DEFAULTCOLOR.
Через параметр hInst передается идентификатор модуля, в ресурсах которого находится изображение. В нашем случае это идентификатор приложения, полученный от функции WinMain.
С помощью функции LoadImage вы можете загрузить системные ресурсы (битовые изображения, пиктограммы и курсоры, которые используются операционной системой). Соответствующие изображения называются OEM-изображениями. Их идентификаторы определены в файле winuser.h. Идентификаторы пиктограмм имеют префикс OIC_, курсоров - OCR_, битовых изображений - OBM_.
Если вы загружаете системные ресурсы, через параметр hInst необходимо передать нулевое значение. Идентификатор ресурса при этом должен передаваться через младшее слово параметра lpszName.
Параметр uType определяет тип загружаемого изображения и может принимать следующие значения:
Значение | Описание |
IMAGE_BITMAP | Битовое изображение |
IMAGE_CURSOR | Курсор |
IMAGE_ICON | Пиктограмма |
При заполнении структуры WNDCLASSEX мы вызываем функцию LoadImage два раза. В первый раз мы загружаем маленькую пиктограмму с размерами 16 х 16 пикселов, во второй раз - стандартную пиктограмму с размерами 32 х 32 пиксела.
И наконец, через параметр lpszName необходимо передать идентификатор ресурса (если изображение загружается из ресурсов модуля) или адрес текстовой строки, содержащий имя файла (если изображение загружается из файла).
Заполнив поля структуры WNDCLASSEX, мы вызываем функцию RegisterClassEx :
if(!RegisterClassEx(&wc)) if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE;
В среде Microsoft Windows 95 функция RegisterClassEx должна выполнить все необходимые для регистрации действия и вернуть значение атома, который идентифицирует зарегистрированный класс.
Однако если вы запустите наше приложение в среде Microsoft Windows NT версии 3.5, функция RegisterClassEx вернет значение NULL. В программном интерфейсе операционной системы этой версии регистрация класса окна должны выполняться функцией RegisterClass.
В следующих версиях Microsoft Windows NT, когда в эту операционную систему будет встроена объектно-ориентированная оболочка, аналогичная оболочке Microsoft Windows 95, вы сможете выполнить расширенную регистрацию с помощью функции RegisterClassEx. Поэтому, для того чтобы наша программа смогла работать и в среде Microsoft Windows 95, и в среде Microsoft Windows NT версии 3.5 (а также более новых версий), мы вначале пытаемся использовать для регистрации функцию RegisterClassEx, а в случае неудачи - функцию RegisterClass, передавая последней адрес обычной структуры WNDCLASS.
После регистрации приложение Window Application создает главное окно, отображает его и запускает цикл обработки сообщений. Все эти действия были подробно описаны в 11 томе "Библиотеки системного программиста".
Функция WndProc
Функция WndProc предназначена для обработки сообщений, поступающих в очередь нашего приложения:
LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate); HANDLE_MSG(hWnd, WM_LBUTTONDOWN, WndProc_OnLButtonDown); HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy); default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } }
Ее особенностью является использование макрокоманды HANDLE_MSG, сокращающей объем листинга функции.
Макрокоманда HANDLE_MSG определена в файле windowsx.h следующим образом:
#define HANDLE_MSG(hwnd, msg, fn) \ case (msg): return HANDLE_##msg((hwnd), \ (wParam), (lParam), (fn))
Согласно этому определению, для обработки сообщения WM_CREATE вызывается макрокоманда HANDLE_WM_CREATE. Аналогично, для обработки сообщения WM_LBUTTONDOWN вызывается макрокоманда HANDLE_WM_LBUTTONDOWN, а для обработки сообщения WM_DESTROY - макрокоманда HANDLE_.WM_DESTROY.
Эти макрокоманды, наряду с аналогичными для других сообщений, определены в файле windowsx.h:
#define HANDLE_WM_CREATE(hwnd, wParam, lParam, fn) \ ((fn)((hwnd), (LPCREATESTRUCT)(lParam)) ? 0L : (LRESULT)-1L) #define HANDLE_WM_DESTROY(hwnd, wParam, lParam, fn) \ ((fn)(hwnd), 0L) #define HANDLE_WM_LBUTTONDOWN(hwnd, wParam, lParam, fn) \ ((fn)((hwnd), FALSE, (int)(short)LOWORD(lParam), \ (int)(short)HIWORD(lParam), (UINT)(wParam)), 0L)
Макрокоманда HANDLE_WM_CREATE вызывает функцию обарботки сообщения WM_CREATE, адрес которой передается ей через последний параметр. Прототип этой функции вы сможете найти все в том же файле windowsx.h рядом с определением макрокоманды HANDLE_WM_CREATE:
BOOL Cls_OnCreate(HWND hwnd, LPCREATESTRUCT lpCreateStruct)
Аналогичные прототипы определены и для других функций обработки сообщений.
Функции обработки сообщений
Функции обработки сообщений обычно выполняют всю работу, для которой создается приложение. Приложение Window Application практически ничего не делает, поэтому и функции обработки сообщений несложны:
BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { return TRUE; }
void WndProc_OnDestroy(HWND hWnd) { PostQuitMessage(0); return FORWARD_WM_DESTROY(hWnd, DefWindowProc); }
void WndProc_OnLButtonDown(HWND hWnd, BOOL fDoubleClick, int x, int y, UINT keyFlags) { MessageBox(NULL, "Hello, 32-bit world!", "Window", MB_OK); return FORWARD_WM_LBUTTONDOWN(hWnd, fDoubleClick, x, y, keyFlags, DefWindowProc); }
Функция WndProc_OnCreate вызывается при создании окна и предназначена для инициализации данных, связанных с этим окном. В нашем случае она просто возвращает значение TRUE, сообщая тем самым, что инициализация завершилась успешно.
Функция WndProc_OnLButtonDown в ответ на щелчок мышью внутри окна выводит диалоговую панель с сообщением. Она завершается вызовом макрокоманды FORWARD_WM_LBUTTONDOWN, определенной в файле windowsx.h следующим образом:
#define FORWARD_WM_LBUTTONDOWN \ (hwnd, fDoubleClick, x, y, keyFlags, fn) \ (void)(fn)((hwnd), (fDoubleClick) ? WM_LBUTTONDBLCLK : \ WM_LBUTTONDOWN, (WPARAM)(UINT)(keyFlags), \ MAKELPARAM((x), (y)))
В качестве последнего параметра этой макрокоманде передается адрес функции, которая используется для обработки сообщений по умолчанию. В нашем случае это адрес функции DefWindowProc.
Макрокоманда FORWARD_WM_LBUTTONDOWN вызывает функцию DefWindowProc , передавая ей все необходимые параметры.
При уничтожении главного окна приложения вызывается функция WndProc_OnDestroy, которая помещает в очередь сообщение WM_QUIT. Это приводит к завершению цикла обработки сообщений, и, соответственно, к завершению работы приложения.
Операционная система Windows 95 для программиста
В качестве примера практического использования органов управления Trackbar, Progressbar и Animation мы предлагаем вам приложение Compact Disk Player, предназначенное для проигрывания дорожек звукового компакт-диска.
Попутно мы продемонстрируем использование MCI-интерфейса в операционной системе Microsoft Windows95. Напомним, что MCI-интерфейс используется для работы с устройствами мультимедиа. Более подробно вы можете об этом узнать из 15 тома "Библиотеки системного программиста".
Вы также узнаете, как создать приложение Win32, не имеющее главное окно, а представляющее из себя обычную диалоговую панель.
Внешний вид приложения Compact Disk Player показан на рис. 7.6.
Приложение Compact Disk Player
В верхней части окна слева находятся органы управления Trackbar и Progressbar. Первый из них используется для перемещения по дорожкам звукового компакт-диска, второй показывает текущую дорожку. По мере проигрывания дорожек полоса Progressbar и движок Trackbar сдвигаются вправо.
Вы можете переключаться на произвольную дорожку компакт-диска, просто передвигая движок органа Trackbar в нужное место или делая щелчки мышью слева или справа от него в окне Trackbar.
Для перехода к следующей дорожке можно использовать клавишу PgUp, клавиши перемещения курсора вверх и вправо, а для перехода к предыдущей - клавишу PgDn и клавиши перемещения курсора вниз и влево.
Если нажать на клавишу Home, приложение начнет проигрывание с первой дорожки, а если End - с последней.
Справа вверху в окне нашего приложения расположен орган управления Animation. Когда проигрывание остановлено, в окне этого органа управления изображается неподвижный компакт-диск. Когда же начинается проигрывание, этот диск начинает вращаться.
В нижней части окна находятся кнопки, с помощью которых можно управлять процессом проигрывания:
Кнопка | Назначение |
Переход к проигрыванию следующей дорожки | |
Переход к проигрыванию предыдущей дорожки | |
Play | Запуск проигрывания |
Stop | Останов проигрывания |
Pause | Временный останов проигрывания |
Resume | Продолжение проигрывания после временного останова |
Eject | Выталкивание компакт-диска (работает не на всех типах устройств чтения компакт-диска) |
Exit | Завершение работы приложения |
При смене компакт-диска количество рисок в окне органа управления Trackbar автоматически устанавливается равным количеству звуковых дорожек.
Еще одно замечание.
Когда вы вставляете музыкальный компакт-диск, операционная система Microsoft Windows95 автоматически запускает приложение и начинает проигрывание вставленного диска. Для работы с нашим приложением просто остановите проигрывание и завершите приложение CD Player.
Исходные тексты приложения Compact Disk Player
Приложение Compact Disk Player сделано на базе приложения MCICDPL, которое было описано в 15 томе "Библиотеки системного программиста".
Функции приложения Compact Disk Player определены в файлах cdplay.c (листинг 7.1) и cdproc.c (листинг 7.2).
Листинг 7.1. Файл cdplay\cdplay.c
#define STRICT #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" #include "afxres.h" #include "cdplay.h"
HINSTANCE hInst; char szAppName[] = "CdPlayApp"; char szAppTitle[] = "CD Player"; UINT nTimerID; UINT nCurTrack = 0; UINT nTrackCnt = 0; HWND hwndCurTrack; HWND hProgressBar; HWND hTrackBar;
// ----------------------------------------------------- // Функция WinMain // ----------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { HWND hWnd; hInst = hInstance;
// Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return FALSE; }
// Инициализируем библиотеку стандартных органов управления InitCommonControls();
// Отображаем диалоговую панель, которая служит // главным окном приложения DialogBox(hInstance, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc);
return FALSE; }
// ----------------------------------------------------- // Функция DlgProc // ----------------------------------------------------- BOOL APIENTRY DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc_OnInitDialog); HANDLE_MSG(hdlg, WM_COMMAND, DlgProc_OnCommand); HANDLE_MSG(hdlg, WM_HSCROLL, DlgProc_OnHScroll); HANDLE_MSG(hdlg, WM_TIMER, DlgProc_OnTimer); default: break; } return FALSE; }
// ----------------------------------------------------- // Функция DlgProc_OnInitDialog // ----------------------------------------------------- BOOL DlgProc_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam) { // Определяем идентификатор поля, которое используется // для отображения номера текущей дорожки hwndCurTrack = GetDlgItem(hdlg, IDT_CURTRACK);
// Получаем идентификатор органа Progressbar hProgressBar = GetDlgItem(hdlg, IDC_PROGRESSBAR);
// Устанавливаем диапазон изменения значений // и шаг для органа Progressbar SendMessage(hProgressBar, PBM_SETRANGE, 0, MAKELPARAM(0, nTrackCnt)); SendMessage(hProgressBar, PBM_SETSTEP, 1, 0);
// Получаем идентификатор органа Trackbar hTrackBar = GetDlgItem(hdlg, IDC_TRACKBAR);
// Устанавливаем диапазон изменения значений // для органа Trackbar SendMessage(hTrackBar, TBM_SETRANGE, TRUE, MAKELPARAM(1, nTrackCnt));
// Устанавливаем шаг изменений SendMessage(hTrackBar, TBM_SETPAGESIZE, 0, 1); SendMessage(hTrackBar, TBM_SETLINESIZE, 0, 1);
// Инициализируем устройство проигрывания // звуковых компакт-дисков CdInit();
// Создаем таймер для периодического определения // состояния устройства проигрывания nTimerID = SetTimer(hdlg, 1, 1000, NULL);
// Открываем AVI-файл с видеоклипом (вращающийся // компакт-диск) Animate_Open(GetDlgItem(hdlg, IDC_ANIMATE), "CD.AVI");
return TRUE; }
// ----------------------------------------------------- // Функция DlgProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void DlgProc_OnCommand(HWND hdlg, int id, HWND hwndCtl, UINT codeNotify) { switch (id) { case IDCANCEL: case IDB_EXIT: { KillTimer(hdlg, nTimerID); // удаляем таймер
CdClose(); // закрываем устройство чтения компакт-диска
EndDialog(hdlg, TRUE); // завершаем работу приложения break; }
// Выполнение команд проигрывания, останова, // паузы, возобновления проигрывания после паузы и т. д. case IDB_PLAY: CdPlay(hdlg, 1); break; case IDB_STOP: CdStop(); break; case IDB_PAUSE: CdPause(); break; case IDB_RESUME: CdResume(hdlg); break; case IDB_NEXT: CdPlayNext(hdlg); break; case IDB_PREV: CdPlayPrev(hdlg); break; case IDB_EJECT: CdEject(); break;
default: return FALSE; } return TRUE; }
// ----------------------------------------------------- // Функция DlgProc_OnTimer // ----------------------------------------------------- void DlgProc_OnTimer(HWND hwnd, UINT id) { BYTE szBuf[20];
// Если окно свернуто в пиктограмму, ничего не делаем, // чтобы не снижать производительность системы if(IsIconic(hwnd)) return;
// Если состояние устройства проигрывания изменилось, // отображаем изменения в диалоговой панели if(CdUpdateState(hwnd)) { // Отображаем номер текущей дорожки if(nCurTrack != 0) { itoa(nCurTrack, szBuf, 10); SetWindowText(hwndCurTrack, szBuf); } else SetWindowText(hwndCurTrack, "");
// Изменяем положение движка Trackbar SendMessage(hTrackBar, TBM_SETPOS, TRUE, nCurTrack);
// Изменяем диапазон значений и положение // полосы Progressbar SendMessage(hProgressBar, PBM_SETRANGE, 0, MAKELPARAM(0, nTrackCnt)); SendMessage(hProgressBar, PBM_SETPOS, nCurTrack, 0); } }
// ----------------------------------------------------- // Функция DlgProc_OnHScroll // ----------------------------------------------------- void DlgProc_OnHScroll(HWND hdlg, HWND hwndCtl, UINT code, int pos) { switch(code) { // Отрабатываем команды, поступающие от органа // управления Trackbar case TB_LINEDOWN: case TB_PAGEDOWN: CdPlayNext(hdlg); break;
case TB_LINEUP: case TB_PAGEUP: CdPlayPrev(hdlg); break;
case TB_BOTTOM: CdPlay(hdlg, nTrackCnt); break;
case TB_TOP: CdPlay(hdlg, 1); break;
case TB_THUMBPOSITION: CdPlay(hdlg, pos); break; default: break; } }
В файле cdproc.c (листинг 7.2) собраны функции управления устройством чтения компакт-диска.
Листинг 7.2. Файл cdplay\cdproc.c
#define STRICT #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" #include "afxres.h" #include "cdplay.h" #include <mmsystem.h>
DWORD dwrc; MCI_OPEN_PARMS MCIOpen; MCI_SET_PARMS MCISet; MCI_STATUS_PARMS MCIStatus; MCI_PLAY_PARMS MCIPlay; BOOL bMediaPresent = FALSE; BOOL bPaused = FALSE; UINT nMode = 0; HWND hwndCurTrack = NULL; extern UINT nCurTrack; extern UINT nTrackCnt; extern HWND hTrackBar;
// ----------------------------------------------------- // Функция CdInit // ----------------------------------------------------- BOOL CdInit(void) { // Открываем устройство чтения компакт-дисков MCIOpen.lpstrDeviceType = (LPSTR)MCI_DEVTYPE_CD_AUDIO; dwrc = mciSendCommand(0, MCI_OPEN, MCI_OPEN_TYPE | MCI_OPEN_TYPE_ID, (DWORD)&MCIOpen); if(dwrc) { mciwioError(dwrc); return FALSE; }
// Устанавливаем формат времени MCISet.dwTimeFormat = MCI_FORMAT_TMSF; dwrc = mciSendCommand(MCIOpen.wDeviceID, MCI_SET, MCI_SET_TIME_FORMAT, (DWORD)&MCISet); if(dwrc) { mciwioError(dwrc); return FALSE; } return TRUE; }
// ----------------------------------------------------- // Функция CdClose // ----------------------------------------------------- void CdClose(void) { // Закрываем устройство чтения компакт-дисков dwrc = mciSendCommand(MCIOpen.wDeviceID, MCI_CLOSE, 0, 0); if(dwrc) mciwioError(dwrc); }
//----------------------------------------------------- // mciwioError // Обработка ошибок //----------------------------------------------------- void mciwioError(DWORD dwrc) { BYTE szBuf[MAXERRORLENGTH];
if(mciGetErrorString(dwrc, (LPSTR)szBuf, MAXERRORLENGTH)) MessageBox(NULL, szBuf, "MCIWAVE Error", MB_ICONEXCLAMATION); else MessageBox(NULL, "Неизвестная ошибка", "MCIWAVE Error", MB_ICONEXCLAMATION); }
// ----------------------------------------------------- // Функция CdUpdateState // ----------------------------------------------------- BOOL CdUpdateState(HWND hdlg) { BOOL fNeedUpdate = FALSE; UINT nCurMode;
// Определяем текущее состояние проигрывателя CD MCIStatus.dwItem = MCI_STATUS_MODE; mciSendCommand(MCIOpen.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD)&MCIStatus);
// Проверяем, готово ли устройство чтения к работе if((MCIStatus.dwReturn == MCI_MODE_NOT_READY) (MCIStatus.dwReturn == MCI_MODE_OPEN)) { // Устройство не готово nCurMode = CD_EMPTY; } else if((MCIStatus.dwReturn == MCI_MODE_STOP) && bPaused) { // Устройство остановлено nCurMode = CD_PAUSED; } else if(MCIStatus.dwReturn == MCI_MODE_PLAY) { // Устройство находится в режиме проигрывания nCurMode = CD_PLAYING; } else { // Устройство готово nCurMode = CD_READY; }
// Если с момента последней проверки произошло // изменение режима, записываем код нового режима if(nMode != nCurMode) { fNeedUpdate = TRUE; nMode = nCurMode;
// Если устройство находится в режиме проигрывания, // запускаем видеоклип. В противном случае // останавливаем видеоклип в его текущей позиции if(nCurMode == CD_PLAYING) Animate_Play(GetDlgItem(hdlg, IDC_ANIMATE), 0, -1, -1); else Animate_Stop(GetDlgItem(hdlg, IDC_ANIMATE)); }
// Проверяем, вставлен ли компакт-диск MCIStatus.dwItem = MCI_STATUS_MEDIA_PRESENT; mciSendCommand(MCIOpen.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD)&MCIStatus);
// Если компакт-диск вставлен, определяем // количество звуковых дорожек if((!bMediaPresent) && MCIStatus.dwReturn) { bMediaPresent = TRUE; bPaused = FALSE; nCurTrack = 0;
MCIStatus.dwItem = MCI_STATUS_NUMBER_OF_TRACKS; mciSendCommand(MCIOpen.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD)&MCIStatus);
nTrackCnt = MCIStatus.dwReturn;
// Устанавливаем диапазон изменения значений // для органа управления Trackbar SendMessage(hTrackBar, TBM_SETRANGE, TRUE, MAKELPARAM(1, nTrackCnt)); }
// Если компакт-диск не вставлен, сбрасываем // номер текущей дорожки в поле диалоговой панели else if((bMediaPresent) && !MCIStatus.dwReturn) { bMediaPresent = FALSE; bPaused = FALSE; }
// Если приложение находится в режиме проигрывания, // определяем номер текущей дорожки if(nCurMode == CD_PLAYING) { // Определяем текущую позицию MCIStatus.dwItem = MCI_STATUS_POSITION; mciSendCommand(MCIOpen.wDeviceID, MCI_STATUS, MCI_STATUS_ITEM | MCI_WAIT, (DWORD)&MCIStatus);
// Если номер дорожки изменился, отображаем новое // значение в соответствующем поле диалоговой панели if(nCurTrack != (UINT)MCI_TMSF_TRACK(MCIStatus.dwReturn)) { nCurTrack = (UINT)MCI_TMSF_TRACK(MCIStatus.dwReturn); fNeedUpdate = TRUE; } } return fNeedUpdate; }
//----------------------------------------------------- // CdPlay // Запуск проигрывания дорожки //----------------------------------------------------- void CdPlay(HWND hwnd, UINT nTrack) { if(bMediaPresent) { bPaused = FALSE;
MCIPlay.dwCallback = (DWORD)hwnd; MCIPlay.dwFrom = MCI_MAKE_TMSF(nTrack, 0, 0, 0);
dwrc = mciSendCommand(MCIOpen.wDeviceID, MCI_PLAY, MCI_FROM | MCI_NOTIFY, (DWORD)&MCIPlay); if(dwrc) mciwioError(dwrc); } }
//----------------------------------------------------- // CdStop // Останов проигрывания дорожки //----------------------------------------------------- void CdStop(void) { if(bMediaPresent) { bPaused = FALSE; nCurTrack = 0; mciSendCommand(MCIOpen.wDeviceID, MCI_STOP, 0, 0); } }
//----------------------------------------------------- // CdPause // Временный останов проигрывания дорожки //----------------------------------------------------- void CdPause(void) { if(bMediaPresent) { if(!bPaused) { bPaused = TRUE; mciSendCommand(MCIOpen.wDeviceID, MCI_PAUSE, 0, 0); } } }
//----------------------------------------------------- // CdResume // Возобновление проигрывания после временного останова //----------------------------------------------------- void CdResume(HWND hwnd) { if(bMediaPresent) { if(bPaused) { bPaused = FALSE; MCIPlay.dwCallback = (DWORD)hwnd; mciSendCommand(MCIOpen.wDeviceID, MCI_PLAY, MCI_NOTIFY, (DWORD)&MCIPlay); } } }
//----------------------------------------------------- // CdPlayNext // Проигрывание следующей дорожки //----------------------------------------------------- void CdPlayNext(HWND hwnd) { UINT nNewTrack;
if(bMediaPresent) { // Если текущая дорожка - последняя, // начинаем проигрывание с первой дорожки. // Если нет - проигрываем следующую дорожку if(nCurTrack == nTrackCnt) nNewTrack = 1; else nNewTrack = nCurTrack + 1;
CdPlay(hwnd, nNewTrack); } }
//----------------------------------------------------- // CdPlayPrev // Проигрывание предыдущей дорожки //----------------------------------------------------- void CdPlayPrev(HWND hwnd) { UINT nNewTrack; if(bMediaPresent) { // Если текущая дорожка - первая, // проигрываем последнюю дорожку if(nCurTrack <= 1) nNewTrack = nTrackCnt; else nNewTrack = nCurTrack - 1;
CdPlay(hwnd, nNewTrack); } }
//----------------------------------------------------- // CdEject // Выталкивание компакт-диска //----------------------------------------------------- void CdEject(void) { mciSendCommand(MCIOpen.wDeviceID, MCI_SET, MCI_SET_DOOR_OPEN, 0); }
В файле cdplay.h (листинг 7.3) находятся описания функций, определенных в приложении Compact Disk Player.
Листинг 7.3. Файл cdplay\cdplay.h
BOOL APIENTRY DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL DlgProc_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam); void DlgProc_OnCommand(HWND hdlg, int id, HWND hwndCtl, UINT codeNotify); void DlgProc_OnTimer(HWND hwnd, UINT id); void DlgProc_OnHScroll(HWND hwnd,HWND hwndCtl,UINT code, int pos); void UpdateDlgControls(void); BOOL CdInit(void); void CdClose(void); void mciwioError(DWORD dwrc); BOOL CdUpdateState(HWND hdlg); void CdPlay(HWND hwnd, UINT nTrack); void CdStop(void); void CdPause(void); void CdResume(HWND hwnd); void CdPlayNext(HWND hwnd); void CdPlayPrev(HWND hwnd); void CdEject(void);
#define CD_EMPTY 0 #define CD_READY 1 #define CD_PLAYING 2 #define CD_PAUSED 3
В файле resource.h ( создается автоматически системой Microsoft Visual C++) находятся определения констант для работы с ресурсами приложения Compact Disk Player. Этот файл представлен в листинге 7.4.
Листинг 7.4. Файл cdplay\resource.h
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by cdplay.rc // #define IDD_DIALOG1 101 #define IDB_NEXT 1000 #define IDB_PREV 1001 #define IDB_PLAY 1002 #define IDB_STOP 1003 #define IDB_PAUSE 1004 #define IDB_RESUME 1005 #define IDB_EJECT 1006 #define IDT_CURTRACK 1008 #define IDB_EXIT 1009 #define IDC_PROGRESSBAR 1012 #define IDC_TRACKBAR 1014 #define IDC_ANIMATE 1015 // Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_3D_CONTROLS 1 #define _APS_NEXT_RESOURCE_VALUE 102 #define _APS_NEXT_COMMAND_VALUE 40001 #define _APS_NEXT_CONTROL_VALUE 1016 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
Файл cdplay.rc (листинг 7.5) содержит определение ресурсов приложения Compact Disk Player.
Листинг 7.5. Файл cdplay\cdplay.rc
//Microsoft Visual C++ generated resource script. // #include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h"
////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS
////////////////////////////////////////////////////////////// // Dialog //
IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 278, 138 STYLE DS_MODALFRAME | WS_MINIMIZEBOX | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Compact disk player, (C) A. Frolov, 1995" FONT 8, "MS Sans Serif" BEGIN PUSHBUTTON ">>",IDB_NEXT,22,89,50,14 PUSHBUTTON "<<",IDB_PREV,76,89,50,14 PUSHBUTTON "Play",IDB_PLAY,129,89,50,14 PUSHBUTTON "Stop",IDB_STOP,22,106,50,14 PUSHBUTTON "Pause",IDB_PAUSE,76,106,50,14 PUSHBUTTON "Resume",IDB_RESUME,129,106,50,14 PUSHBUTTON "Eject",IDB_EJECT,203,89,50,14 PUSHBUTTON "Exit",IDB_EXIT,203,106,50,14 LTEXT "",IDT_CURTRACK,79,20,92,10 GROUPBOX "",IDC_STATIC,11,6,256,75 CONTROL "Generic1",IDC_PROGRESSBAR, "msctls_progress32", 0x3,25,39,184,9 CONTROL "Generic3",IDC_TRACKBAR, "msctls_trackbar32", WS_TABSTOP | 0x21,19,50,195,25 LTEXT "Track number:",IDC_STATIC,25,20,47,8 GROUPBOX "",IDC_STATIC,11,76,181,53 CONTROL "Animate",IDC_ANIMATE,"SysAnimate32", WS_BORDER | WS_TABSTOP | 0x1,219,34,33,33 GROUPBOX "",IDC_STATIC,191,76,76,53 END
#ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // TEXTINCLUDE //
1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END
2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END
3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END
////////////////////////////////////////////////////////////// #endif // APSTUDIO_INVOKED
#ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 3 resource. ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
Кратко опишем функции приложения Compact
Кратко опишем функции приложения Compact Disk Player, имеющие отношение к органам управления Trackbar, Progressbar и Animation. Особенности использования интерфейса MCI были описаны в 15 томе "Библиотеки системного программиста".
WinMain
После сохранения идентификатора приложения и проверки на повторный запуск функция WinMain вызывает функцию InitCommonControls, выполняющую инициализацию библиотеки стандартных органов управления.
Далее вызывается функция DialogBox, создающая диалоговую панель. Функция этой диалоговой панели DlgProc выполняет всю полезную работу.
DlgProc
Функция DlgProc обрабатывает сообщения WM_INITDIALOG, WM_COMMAND, WM_HSCROLL и WM_TIMER, вызывая для них, соответственно, функции DlgProc_OnInitDialog, DlgProc_OnCommand, DlgProc_OnHScroll и DlgProc_OnTimer.
DlgProc1_OnInitDialog
Функция DlgProc_OnInitDialog определяет идентификаторы органов управления, расположенных в диалоговой панели, и сохраняет их для дальнейшего использования.
После этого она устанавливает диапазон изменения значений и шаг для органа Progressbar, посылая ему сообщения PBM_SETRANGE и PBM_SETSTEP. В качестве максимального используется значение из переменной nTrackCnt. Как только в устройство чтения компакт-дисков будет вставлен звуковой компакт-диск, обработчик сообщения WM_TIMER запишет в эту переменную количество дорожек.
Шаг изменения значений для органа управления Progressbar устанавливается равным единице.
Затем выполняется инициализация органа управления Trackbar. Максимальное значение устанавливается равным содержимому переменной nTrackCnt (как и для органа Progressbar), а шаг изменения - равным единице. Благодаря этому пользователь сможет перемещаться по всем дорожкам компакт-диска.
После этого обработчик сообщения открывает и инициализирует устройство чтения компакт-дисков, вызывая функцию CdInit, и затем запускает таймер с интервалом, равным примерно одной секунде. Таймер нужен для периодического определения состояния компакт-диска и, в частности, для периодического определения номера текущей дорожки.
На последнем шаге инициализации открывается файл с видеоклипом CD.AVI, который должен находится в текущем каталоге. Этот файл открывается макрокомандой Animate_Open.
DlgProc_OnCommand
Функция DlgProc1_OnCommand обрабатывает сообщение WM_COMMAND, поступающее от органов управления диалоговой панели.
Когда пользователь нажимает кнопку Exit или клавишу <Esc> обработчик уничтожает таймер, закрывает устройство чтения компакт-диска при помощи функции CdClose и затем закрывает диалоговую панель, вызывая функцию EndDialog. В результате приложение завершает свою работу.
Обработка остальных кнопок выполняется соответствующими функциями, определенными в файле cdproc.c (листинг 7.2). Действия, которые выполняют эти функции, были описаны нами в 15 томе "Библиотеки системного программиста", поэтому для экономии места мы не будем рассказывать о них еще раз.
DlgProc_OnTimer
Функция DlgProc_OnTimer обрабатывает сообщение WM_TIMER , поступающее примерно один раз в секунду. Если диалоговая панель находится в минимизированном состоянии (что определяется с помощью функции IsIconic), обработчик сразу возвращает управление.
В противном случае вызывается функция CdUpdateState, определяющая текущее состояние устройства чтения компакт-дисков и выполняющая некоторые другие действия, связанные с обновлением состояния органов управления диалоговой панели.
Если в результате вызова этой функции выяснилось, что состояние изменилось, функция DlgProc_OnTimer отображает номер текущей дорожки в статическом органе управления с идентификатором hwndCurTrack. После этого она изменяет положение движка Trackbar, чтобы он указывал на текущую дорожку, а также устанавливает диапазон значений и новое положение полосы Progressbar.
DlgProc_OnHScroll
Функция DlgProc_OnHScroll обрабатывает сообщение WM_HSCROLL , которое вырабатывает орган управления Trackbar.
В ответ на извещения TB_LINEDOWN и TB_PAGEDOWN обработчик вызывает функцию CdPlayNext, которая проигрывает следующую дорожку. При обработке извещений TB_LINEUP и TB_PAGEUP вызывается функция CdPlayPrev, которая проигрывает предыдущую дорожку.
Когда пользователь нажимает клавиши <Home> или <End>, обработчики извещений TB_TOP и TB_BOTTOM запускают проигрывание, соответственно, первой и последней дорожки звукового компакт-диска.
Если пользователь перемещает движок органа управления Trackbar в новое положение, обработчик извещения TB_THUMBPOSITION запускает проигрывание той дорожки, которая была выбрана и номер которой был передан вместе с этим извещением.
Операционная система Windows 95 для программиста
В качестве примера мы приведем исходные тексты приложения List Application, отображающего список приложений из 15 тома "Библиотеки системного программиста", посвященного созданию систем мультимедиа для Microsoft Windows.
В режиме детального отчета (рис. 3.1) для каждого приложения отображается его пиктограмма, название приложения, а также такие дополнительные элементы, как имя файла, содержащего приложение и стоимость в USD (не подумайте только, что мы и в самом деле продаем их по такой бешеной цене!).
Просмотр списка в виде детального отчета
Нажимая заголовки столбцов, вы сможете отсортировать список по названию, по имени файла пиктограммы, или по цене. Ширина столбцов поддается регулировке. Причем, если сделать двойной щелчок левой клавишей мыши по разделителю заголовка столбцов, ширина столбца, расположенного слева от разделителя, станет такой, чтобы в столбце поместилась самая длинная текстовая строка.
Если из меню Options выбрать строку Icon view, режим просмотра списка изменится (рис. 3.2).
Просмотр списка в виде пиктограмм стандартного размера
С помощью строки Small icon view можно просматривать список в виде пиктограмм уменьшенного размера (рис. 3.3).
Просмотр списка в виде пиктограмм уменьшенного размера
И, наконец, выбрав из меню Options строку List view, вы можете перевести окно органа управления List View в режим просмотра простого списка (рис. 3.4).
Простой список с пиктограммами уменьшенного размера
В любом режиме просмотра вы сможете редактировать название приложения, выбрав его и затем сделав по имени щелчок левой клавишей мыши.
Если же вы сделаете двойной щелчок левой клавишей мыши по пиктограмме или названию любого приложения, на экране появится диалоговая панель, в которой отображаются атрибуты выбранного вами элемента списка (рис. 3.5).
Отображение информации, связанной с выбранным элементом списка
Исходные тексты приложения List Application
Все функции приложения List Application определены в файле list.c (листинг 3.1).
Листинг 3.1. Файл list\list.c
#define STRICT #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" #include "afxres.h" #include "list.h"
typedef struct tagAPPLINFO { char szAppName[40]; char szIconName[20]; UINT iCost; } APPLINFO;
// ----------------------------------------------------- // Глобальные переменные // ----------------------------------------------------- APPLINFO rgApplInfo[]= { {"Generic", "appicon.ico ", 5}, {"Book", "book1.ico ", 2}, {"Driver List", "drvlist.ico ", 22}, {"MCI CD Player", "mcicdpl.ico ", 345}, {"MCI String Player", "mcistrvw.ico", 54}, {"MCI Wave Player", "mciwaver.ico", 32}, {"MCI Window Demo", "mciwnd.ico ", 0}, {"Sound Play", "sndplay.ico ", 0}, {"Wave Play", "wave.ico ", 4} }; HINSTANCE hInst; char szAppName[] = "ListApp"; char szAppTitle[] = "List Application"; HWND hwndList;
// ----------------------------------------------------- // Функция WinMain // ----------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg;
hInst = hInstance;
// Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return FALSE; }
// Регистрируем класс окна memset(&wc, 0, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0); wc.style = 0; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU); wc.lpszClassName = szAppName; if(!RegisterClassEx(&wc)) if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE;
// Создаем главное окно приложения hWnd = CreateWindow(szAppName,szAppTitle,WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,0,CW_USEDEFAULT,0, NULL, NULL, hInst, NULL); if(!hWnd) return(FALSE);
// Отображаем окно и запускаем цикл обработки сообщений ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while(GetMessage (&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
// ----------------------------------------------------- // Функция WndProc // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate); HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy); HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand); HANDLE_MSG(hWnd, WM_NOTIFY, WndProc_OnNotify); HANDLE_MSG(hWnd, WM_SIZE, WndProc_OnSize);
default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } }
// ----------------------------------------------------- // Функция WndProc_OnCreate // ----------------------------------------------------- BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { int i; RECT rc; HIMAGELIST himlSmall; HIMAGELIST himlLarge; HICON hIcon; LV_COLUMN lvc; LV_ITEM lvi;
// Определяем размеры внутренней области главного окна GetClientRect(hWnd, &rc);
// Инициализируем библиотеку стандартных органов управления InitCommonControls();
// Создаем орган управления List View hwndList = CreateWindowEx(0L, WC_LISTVIEW, "", WS_VISIBLE | WS_CHILD | WS_BORDER | LVS_REPORT | LVS_EDITLABELS, 0, 0, rc.right - rc.left, rc.bottom - rc.top, hWnd, (HMENU) IDC_LISTVIEW, hInst, NULL);
if(hwndList == NULL) return FALSE;
// Создаем список изображений himlSmall = ImageList_Create( GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON), ILC_MASK, 9, 1); himlLarge = ImageList_Create( GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), ILC_MASK, 9, 1);
for(i = IDI_ICON1; i <= IDI_ICON9; i++) { hIcon = LoadIcon(hInst, MAKEINTRESOURCE(i)); ImageList_AddIcon(himlSmall, hIcon); ImageList_AddIcon(himlLarge, hIcon); }
// Добавляем списки изображений ListView_SetImageList(hwndList, himlSmall, LVSIL_SMALL); ListView_SetImageList(hwndList, himlLarge, LVSIL_NORMAL);
// Вставляем столбцы memset(&lvc, 0, sizeof(lvc));
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; lvc.fmt = LVCFMT_LEFT; lvc.cx = (rc.right - rc.left) / 4;
lvc.iSubItem = 0; lvc.pszText = "Application Name"; ListView_InsertColumn(hwndList, 0, &lvc);
lvc.iSubItem = 1; lvc.pszText = "Icon Name"; ListView_InsertColumn(hwndList, 1, &lvc);
lvc.iSubItem = 2; lvc.pszText = "Cost, USD"; ListView_InsertColumn(hwndList, 2, &lvc); ListView_SetColumnWidth(hwndList,2,(rc.right-rc.left) / 8);
// Вставляем строки memset(&lvi, 0, sizeof(lvi));
lvi.mask = LVIF_IMAGE | LVIF_TEXT | LVIF_PARAM; lvi.pszText = LPSTR_TEXTCALLBACK;
for(i=0; i<9; i++) { lvi.iItem = i; lvi.iSubItem = 0; lvi.cchTextMax = 40; lvi.lParam = (LPARAM)&rgApplInfo[i];
lvi.iImage = i; ListView_InsertItem(hwndList, &lvi);
lvi.iItem = i; lvi.iSubItem = 1; ListView_InsertItem(hwndList, &lvi);
lvi.iItem = i; lvi.iSubItem = 2; ListView_InsertItem(hwndList, &lvi); } return TRUE; }
// ----------------------------------------------------- // Функция WndProc_OnDestroy // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnDestroy(HWND hWnd) { DestroyWindow(hwndList); PostQuitMessage(0); return 0L; }
// ----------------------------------------------------- // Функция WndProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnCommand( HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { DWORD dwStyle = 0; switch (id) { case ID_OPTIONS_ICONVIEW: { dwStyle = GetWindowLong(hwndList, GWL_STYLE); if((dwStyle & LVS_TYPEMASK) != LVS_ICON) SetWindowLong(hwndList, GWL_STYLE, (dwStyle & ~LVS_TYPEMASK) | LVS_ICON); break; }
case ID_OPTIONS_SMALLICONVIEW: { dwStyle = GetWindowLong(hwndList, GWL_STYLE);
if((dwStyle & LVS_TYPEMASK) != LVS_SMALLICON) SetWindowLong(hwndList, GWL_STYLE, (dwStyle & ~LVS_TYPEMASK) | LVS_SMALLICON); break; }
case ID_OPTIONS_LISTVIEW: { dwStyle = GetWindowLong(hwndList, GWL_STYLE);
if((dwStyle & LVS_TYPEMASK) != LVS_LIST) SetWindowLong(hwndList, GWL_STYLE, (dwStyle & ~LVS_TYPEMASK) | LVS_LIST); break; }
case ID_OPTIONS_REPORTVIEW: { dwStyle = GetWindowLong(hwndList, GWL_STYLE);
if((dwStyle & LVS_TYPEMASK) != LVS_REPORT) SetWindowLong(hwndList, GWL_STYLE, (dwStyle & ~LVS_TYPEMASK) | LVS_REPORT); break; }
case ID_FILE_EXIT: PostQuitMessage(0); return 0L; break;
case ID_HELP_ABOUT: break;
default: break; } return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc); }
// ----------------------------------------------------- // Функция WndProc_OnNotify // ----------------------------------------------------- LRESULT WndProc_OnNotify( HWND hWnd, int idFrom, NMHDR* pnmhdr) { LV_DISPINFO * lpLvdi = (LV_DISPINFO *)pnmhdr; APPLINFO * lpAppinfo = (APPLINFO *)(lpLvdi->item.lParam); static char szBuf[20]; NM_LISTVIEW *lpNm = (NM_LISTVIEW *)pnmhdr;
if(idFrom != IDC_LISTVIEW) return 0L;
switch(pnmhdr->code) { case LVN_GETDISPINFO: { if(lpLvdi->item.mask & LVIF_TEXT) { switch(lpLvdi->item.iSubItem) { case 0: lpLvdi->item.pszText = lpAppinfo->szAppName; break;
case 1: lpLvdi->item.pszText = lpAppinfo->szIconName; break;
case 2: itoa(lpAppinfo->iCost, szBuf, 10); lpLvdi->item.pszText = szBuf; break;
default: break; } break; } }
case LVN_COLUMNCLICK: { ListView_SortItems(lpNm->hdr.hwndFrom, LVCompareProc, (LPARAM)(lpNm->iSubItem)); return 0L; break; }
case LVN_BEGINLABELEDIT: { return 0L; break; }
case LVN_ENDLABELEDIT: { if((lpLvdi->item.iItem != -1) && (lpLvdi->item.pszText != NULL)) lstrcpy(lpAppinfo->szAppName, lpLvdi->item.pszText); return 0L; break; }
case NM_DBLCLK: { int index; LV_ITEM lvi; char szBuf[256];
strcpy(szBuf, "Selected item:\n");
// Определяем номер выделенного элемента index = ListView_GetNextItem(hwndList, -1, LVNI_ALL | LVNI_SELECTED);
if(index == -1) return 0;
// Подготавливаем структуру типа LV_ITEM // для получения текстовой информации об элементах memset(&lvi, 0, sizeof(lvi)); lvi.mask = LVIF_TEXT;
// Получаем название элемента lvi.iItem = index; lvi.iSubItem = 0; ListView_GetItem(hwndList, &lvi); strcat(szBuf, lvi.pszText);
// Получаем текстовую строку, связанную // с первым и вторым дополнительным элементом lvi.iItem = index; lvi.iSubItem = 1; ListView_GetItem(hwndList, &lvi); strcat(szBuf, " : "); strcat(szBuf, lvi.pszText);
lvi.iItem = index; lvi.iSubItem = 2; ListView_GetItem(hwndList, &lvi); strcat(szBuf, " : $"); strcat(szBuf, lvi.pszText);
// Выводим на экран текстовые строки // для выбранного элемента MessageBox(hWnd, szBuf, szAppName, MB_OK); return 0L; break; } } return 0L; }
// ----------------------------------------------------- // Функция WndProc_OnSize // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnSize(HWND hwnd, UINT state, int cx, int cy) { MoveWindow(hwndList, 0, 0, cx, cy, TRUE); return FORWARD_WM_SIZE(hwnd, state, cx, cy, DefWindowProc); }
// ----------------------------------------------------- // Функция LVCompareProc // ----------------------------------------------------- int CALLBACK LVCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort) { APPLINFO *pAppInfo1 = (APPLINFO *)lParam1; APPLINFO *pAppInfo2 = (APPLINFO *)lParam2; LPSTR lpStr1, lpStr2; int iResult;
if(pAppInfo1 && pAppInfo2) { switch(lParamSort) { case 0: lpStr1 = pAppInfo1->szAppName; lpStr2 = pAppInfo2->szAppName; iResult = strcmpi(lpStr1, lpStr2); break;
case 1: lpStr1 = pAppInfo1->szIconName; lpStr2 = pAppInfo2->szIconName; iResult = lstrcmpi(lpStr1, lpStr2); break;
case 2: iResult = pAppInfo1->iCost - pAppInfo2->iCost; break;
default: iResult = 0; break; } } return(iResult); }
Файл list.h (листинг 3.2) содержит описание функций и определение константы IDC_LISTVIEW (идентификатора органа управления List View).
Листинг 3.2. Файл list\list.h
// ----------------------------------------------------- // Описание функций // ----------------------------------------------------- LRESULT WINAPI WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct); void WndProc_OnDestroy(HWND hWnd); void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); LRESULT WndProc_OnNotify(HWND hWnd, int idFrom, NMHDR FAR * pnmhdr); void WndProc_OnSize(HWND hwnd, UINT state, int cx, int cy); void WndProc_OnDrawItem(HWND hwnd, const DRAWITEMSTRUCT * lpDrawItem); int CALLBACK LVCompareProc(LPARAM lParam1, LPARAM lParam2, LPARAM lParamSort); #define IDC_LISTVIEW 1234
Файл описания ресурсов приложения, созданный для нашего проекта автоматически системой разработки Microsoft Visual C++, приведен в листинге 3.3. В нем определено главное меню приложения, пиктограммы, из которых формируются списки изображений для органа управления List View и таблица строк описания меню (в нашем приложении не используется, однако вы можете найти для нее применение, добавив орган управления Statusbar).
Листинг 3.3. Файл list\list.rc
//Microsoft Visual C++ generated resource script. #include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h"
////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS
////////////////////////////////////////////////////////////// // Menu // IDR_APPMENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", ID_FILE_EXIT END POPUP "&Options" BEGIN MENUITEM "&Icon view", ID_OPTIONS_ICONVIEW MENUITEM "&Small icon view", ID_OPTIONS_SMALLICONVIEW MENUITEM "&List view", ID_OPTIONS_LISTVIEW MENUITEM "&Report view", ID_OPTIONS_REPORTVIEW END POPUP "&Help" BEGIN MENUITEM "&About List Application...", ID_HELP_ABOUT END END
#ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // TEXTINCLUDE // 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END
2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END
3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END
////////////////////////////////////////////////////////////// #endif // APSTUDIO_INVOKED
////////////////////////////////////////////////////////////// // Icon // IDI_APPICON ICON DISCARDABLE "list.ico" IDI_APPICONSM ICON DISCARDABLE "listsm.ico" IDI_ICON1 ICON DISCARDABLE "appicon.ico" IDI_ICON2 ICON DISCARDABLE "book1.ico" IDI_ICON3 ICON DISCARDABLE "drvlist.ico" IDI_ICON4 ICON DISCARDABLE "mcicdpl.ico" IDI_ICON5 ICON DISCARDABLE "mcistrwv.ico" IDI_ICON6 ICON DISCARDABLE "mciwaver.ico" IDI_ICON7 ICON DISCARDABLE "mciwnd.ico" IDI_ICON8 ICON DISCARDABLE "sndplay.ico" IDI_ICON9 ICON DISCARDABLE "wave.ico"
////////////////////////////////////////////////////////////// // String Table // STRINGTABLE DISCARDABLE BEGIN ID_FILE_EXIT "Quits the application" ID_HELP_ABOUTLISTAPPLICATION " Displays program information and copyright" ID_OPTIONS_ICONVIEW "Each item appears as a full-sized icon" ID_OPTIONS_SMALLICONVIEW "Each item appears as a small icon" ID_OPTIONS_LISTVIEW "Each item appears as a small icon arranged in columns" ID_OPTIONS_REPORTVIEW "Each item appears with subitems arranged in columns" END
#ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 3 resource. ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
Файл resource.h (листинг 3.4) также создается автоматически. В нем находятся определения констант для ресурсов приложения.
Листинг 3.4. Файл list\resource.h
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by list.rc // #define IDR_APPMENU 102 #define IDI_APPICON 103 #define IDI_APPICONSM 104 #define IDI_ICON1 105 #define IDI_ICON2 106 #define IDI_ICON3 107 #define IDI_ICON4 108 #define IDI_ICON5 109 #define IDI_ICON6 110 #define IDI_ICON7 111 #define IDI_ICON8 112 #define IDI_ICON9 113 #define ID_FILE_EXIT 40001 #define ID_HELP_ABOUTLISTAPPLICATION 40002 #define ID_HELP_ABOUT 40003 #define ID_OPTIONS_ICONVIEW 40004 #define ID_OPTIONS_SMALLICONVIEW 40005 #define ID_OPTIONS_LISTVIEW 40006 #define ID_OPTIONS_REPORTVIEW 40007
// Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 115 #define _APS_NEXT_COMMAND_VALUE 40008 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
Займемся описанием функций приложения List
Займемся описанием функций приложения List View. Так как раньше мы уже приводили отдельные фрагменты этих функций, ограничимся кратким описанием.
Глобальные переменные
Массив структур rgApplInfo предназначен для хранения элементов списка. Соответствующая структура имеет тип APPLINFO и определена следующим образом:
typedef struct tagAPPLINFO { char szAppName[40]; // название приложения char szIconName[20]; // имя файла пиктограммы UINT iCost; // стоимость приложения } APPLINFO;
В переменной hInst хранится идентификатор приложения, полученный функцией WinMain. Строчные массивы szAppName и szAppTitle хранят, соответственно, имя и заголовок приложения.
Переменная hwndList нужна для хранения идентификатора созданного органа управления List View.
WinMain
Функция WinMain сохраняет идентификатор приложения и проверяет, не было ли это приложение запущено ранее. Если было, то активизируется окно работающего приложения.
Далее функция регистрирует класс главного окна приложения, создает и отображает это окно, а затем запускает обычный цикл обработки сообщений.
WndProc
В задачу функции WndProc входит обработка следующих сообщений: WM_CREATE, WM_DESTROY, WM_COMMAND, WM_NOTIFY, WM_SIZE. Обработка выполняется с использованием макрокоманды HANDLE_MSG.
WndProc_OnCreate
Эта функция обрабатывает сообщение WM_CREATE, создавая и инициализируя орган управления List View. Размеры окна органа управления устанавливаются равными размерам внутренней области главного окна приложения и в дальнейшем изменяются соответствующим образом обработчиком сообщения WM_SIZE.
WndProc_OnDestroy
Функция WndProc_OnDestroy вызывается, когда пользователь завершает работу приложения. Она уничтожает окно органа управления List View и останавливает цикл обработки сообщений, вызывая функцию PostQuitMessage.
WndProc_OnCommand
Эта функция обрабатывает сообщение WM-COMMAND, поступающее от главного меню приложения. Строки временного меню Options (Icon view, Small icon view, List view и Report view) имеют идентификаторы, соответственно, ID_OPTIONS_ICONVIEW, ID_OPTIONS_SMALLICONVIEW, ID_OPTIONS_LISTVIEW и ID_OPTIONS_REPORTVIEW. Обработчики изменяют режим отображения списка с помощью функций GetWindowLong и SetWindowLong, как это было описано раньше.
WndProc_OnNotify
Функция WndProc_OnNotify обрабатывает сообщение WM_NOTIFY, поступающее от органа управления List View.
Процедура обработки извещений была описана ранее. Отметим только, что из-за того что различные извещения используют разный формат структуры данных, адрес которой передается через параметр lParam сообщения WM_NOTIFY, мы выполняем преобразование указателя NMHDR* pnmhdr к типам LV_DISPINFO и NM_LISTVIEW.
Кроме того, функция WndProc_OnNotify проверяет параметр idFrom чтобы убедиться в том, что извещение пришло именно от органа управления List View с идентификатором IDC_LISTVIEW.
WndProc_OnSize
Для того чтобы размеры органа управления List View всегда соответствовали размерам внутренней области главного окна приложения List Application, обработчик сообщения WM_SIZE, расположенный в функции WndProc_OnSize, изменяет размеры окна органа управления. Изменения выполняются функцией WndProc_OnSize.
LVCompareProc
Функция LVCompareProc нужна для сортировки элементов списка. Она выполняет сравнение двух элементов списка. Через первые два параметра передаются адреса структур данных, соответствующих сравниваемым элементам. Последний параметр содержит номер дополнительного элемента или нуль, если сравниваются названия элементов.
Текстовые строки сравниваются при помощи функции strcmpi. Для сравнения численных значений мы использовали обычную операцию вычитания.
Операционная система Windows 95 для программиста
Приложение Property Sheet Demo демонстрирует способ создания простейшего блокнота, который появляется при выборе строки Options из меню File.
С помощью блокнота заполняется произвольно выбранная нами структура параметров, приведенная ниже:
typedef struct { int nBold; int nItalic; int nUnderline; int nUseTabs; char szKeyWord[80]; } OPTIONS;
Первая страница блокнота, которая называется Set Effects, показана на рис. 6.2.
Первая страница блокнота
На ней расположены переключатели с независимой фиксацией Bold, Italic и Underline. Если изменить состояние одного из этих переключателей, разблокируется кнопка Apply (которая изначально находится в заблокированном состоянии).
На второй странице блокнота с названием Using Tabs (рис. 6.3) находятся два переключателя с зависимой фиксацией Use Tabs и Don't use Tabs.
Вторая страница блокнота
Кнопка Apply разблокируется автоматически при выборе страницы Using Tabs, что достигается соответствующей обработкой извещения PSN_SETACTIVE.
С помощью страницы Keyword (рис. 6.4) пользователь может ввести некоторое ключевое слово, длина которого не должна превышать 8 символов.
Третья страница блокнота
При выборе этой страницы кнопка Apply разблокируется только в том случае, когда ключевое слово было изменено.
Если длина нового ключевого слова превышает 8 символов, на экране появляется сообщение, показанное на рис. 6.5.
Сообщение о превышении длины ключевого слова
При этом вы не сможете переключиться на другие страницы блокнота до тех пор, пока не будет введено ключевое слово нужной длины.
Исходные тексты приложения Property Sheet Demo
Функции приложения Property Sheet Demo определены в файле psheet.c (листинг 6.1).
Листинг 6.1. Файл psheet\psheet.c
#define STRICT #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" #include "afxres.h" #include "psheet.h"
// Структура, в которой хранятся параметры typedef struct { int nBold; int nItalic; int nUnderline; int nUseTabs; char szKeyWord[80]; } OPTIONS;
OPTIONS opt;
// Массив описаний страниц блокнота PROPSHEETPAGE psheetPage[3];
// Заголовок блокнота PROPSHEETHEADER psheetHeader;
// Идентификаторы страниц блокнота HPROPSHEETPAGE hPage[3];
HINSTANCE hInst; char szAppName[] = "PropSheetApp"; char szAppTitle[] = "Property Sheet Demo";
// ----------------------------------------------------- // Функция WinMain // ----------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg;
hInst = hInstance;
// Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return FALSE; }
// Регистрируем класс окна memset(&wc, 0, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0); wc.style = 0; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU); wc.lpszClassName = szAppName; if(!RegisterClassEx(&wc)) if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE;
// Создаем главное окно приложения hWnd = CreateWindow(szAppName, szAppTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL); if(!hWnd) return(FALSE);
// Отображаем окно и запускаем цикл обработки сообщений ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while(GetMessage (&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
// ----------------------------------------------------- // Функция WndProc // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate); HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy); HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand);
default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } }
// ----------------------------------------------------- // Функция WndProc_OnCreate // ----------------------------------------------------- BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { // Инициализируем библиотеку стандартных органов управления InitCommonControls();
// Инициализируем параметры opt.nBold = opt.nItalic = opt.nUnderline = opt.nUseTabs = 0; strcpy(opt.szKeyWord, "Undef");
return TRUE; }
// ----------------------------------------------------- // Функция WndProc_OnDestroy // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnDestroy(HWND hWnd) { PostQuitMessage(0); return 0L; }
// ----------------------------------------------------- // Функция WndProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { switch (id) { case ID_FILE_EXIT: PostQuitMessage(0); // завершаем работу приложения return 0L; break;
case ID_HELP_ABOUT: MessageBox(hWnd, "Property Sheet Demo Application, v.1.0\n" "(C) Alexandr Frolov, 1995\n" "Email: frolov@glas.apc.org", szAppTitle, MB_OK | MB_ICONINFORMATION); return 0L; break;
case ID_FILE_OPTIONS: { // Инициализируем страницы блокнота psheetPage[0].dwSize = sizeof(PROPSHEETPAGE); psheetPage[0].hInstance = hInst; psheetPage[0].pszIcon = MAKEINTRESOURCE(IDI_EFFECTS); psheetPage[0].dwFlags = PSP_USETITLE | PSP_USEICONID; psheetPage[0].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG1); psheetPage[0].pfnDlgProc = DlgProc1; psheetPage[0].pszTitle = "Set Effects"; psheetPage[0].lParam = 0;
// Добавляем страницу в блокнот, сохраняя ее // идентификатор в массиве hPage hPage[0] = CreatePropertySheetPage(&psheetPage[0]);
psheetPage[1].dwSize = sizeof(PROPSHEETPAGE); psheetPage[1].hInstance = hInst; psheetPage[1].pszIcon = MAKEINTRESOURCE(IDI_TAB); psheetPage[1].dwFlags = PSP_USETITLE | PSP_USEICONID; psheetPage[1].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG2); psheetPage[1].pfnDlgProc = DlgProc2; psheetPage[1].pszTitle = "Using Tabs"; psheetPage[1].lParam = 0; hPage[1] = CreatePropertySheetPage(&psheetPage[1]);
psheetPage[2].dwSize = sizeof(PROPSHEETPAGE); psheetPage[2].hInstance = hInst; psheetPage[2].pszIcon = MAKEINTRESOURCE(IDI_KEYWORD); psheetPage[2].dwFlags = PSP_USETITLE | PSP_USEICONID; psheetPage[2].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG3); psheetPage[2].pfnDlgProc = DlgProc3; psheetPage[2].pszTitle = "Keyword"; psheetPage[2].lParam = 0; hPage[2] = CreatePropertySheetPage(&psheetPage[2]);
// Инициализируем заголовок блокнота psheetHeader.dwSize = sizeof(PROPSHEETHEADER); psheetHeader.hInstance = hInst; psheetHeader.pszIcon = MAKEINTRESOURCE(IDI_APPICONSM); psheetHeader.dwFlags = PSH_USEICONID; psheetHeader.hwndParent = hWnd; psheetHeader.pszCaption = "Property Sheet Sample"; psheetHeader.nPages = sizeof(psheetPage) / sizeof(PROPSHEETPAGE); psheetHeader.phpage = (HPROPSHEETPAGE FAR *)&hPage[0];
// Создаем и отображаем блокнот PropertySheet(&psheetHeader);
return 0L; break; } default: break; } return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc); }
// ----------------------------------------------------- // Функция DlgProc1 // для первой страницы блокнота // ----------------------------------------------------- BOOL APIENTRY DlgProc1( HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc1_OnInitDialog); HANDLE_MSG(hdlg, WM_COMMAND, DlgProc1_OnCommand); HANDLE_MSG(hdlg, WM_NOTIFY, DlgProc1_OnNotify); default: break; } return FALSE; }
// ----------------------------------------------------- // Функция DlgProc1_OnInitDialog // Вызывается при инициализации первой страницы блокнота // ----------------------------------------------------- BOOL DlgProc1_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam) { // Устанавливаем переключатели в соответствии // со значениями параметров, записанных в // структуре opt SendMessage(GetDlgItem(hdlg, IDC_BOLD), BM_SETCHECK, opt.nBold, 0L);
SendMessage(GetDlgItem(hdlg, IDC_ITALIC), BM_SETCHECK, opt.nItalic, 0L);
SendMessage(GetDlgItem(hdlg, IDC_UNDERLINE), BM_SETCHECK, opt.nUnderline, 0L); return TRUE; }
// ----------------------------------------------------- // Функция DlgProc1_OnNotify // Обрабатывает извещение от первой страницы блокнота // ----------------------------------------------------- LRESULT DlgProc1_OnNotify(HWND hdlg, int idFrom, NMHDR* pnmhdr) { switch(pnmhdr->code) { // Когда пользователь нажимает кнопку OK, получаем // состояние переключателей, расположенных в первой // странице блокнота, и сохраняем это состояние в // соответствующих полях структуры opt case PSN_APPLY: { opt.nBold = (int)SendMessage( GetDlgItem(hdlg, IDC_BOLD), BM_GETCHECK, 0L, 0L);
opt.nItalic = (int)SendMessage( GetDlgItem(hdlg, IDC_ITALIC), BM_GETCHECK, 0L, 0L);
opt.nUnderline = (int)SendMessage( GetDlgItem(hdlg, IDC_UNDERLINE), BM_GETCHECK, 0L, 0L); break; } default: break; } return FALSE; }
// ----------------------------------------------------- // Функция DlgProc1_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void DlgProc1_OnCommand(HWND hdlg, int id, HWND hwndCtl, UINT codeNotify) { // Разблокируем кнопку "Apply" PropSheet_Changed(GetParent(hdlg), hdlg); return NULL; }
// ----------------------------------------------------- // Функция DlgProc2 // для второй страницы блокнота // ----------------------------------------------------- BOOL APIENTRY DlgProc2(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc2_OnInitDialog); HANDLE_MSG(hdlg, WM_NOTIFY, DlgProc2_OnNotify); default: break; } return FALSE; }
// ----------------------------------------------------- // Функция DlgProc2_OnInitDialog // Вызывается при инициализации второй страницы блокнота // ----------------------------------------------------- BOOL DlgProc2_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam) { SendMessage(GetDlgItem(hdlg, IDC_USETABS), BM_SETCHECK, opt.nUseTabs, 0L);
SendMessage(GetDlgItem(hdlg, IDC_DONTUSETABS), BM_SETCHECK, (opt.nUseTabs) ? 0 : 1, 0L);
return TRUE; }
// ----------------------------------------------------- // Функция DlgProc2_OnNotify // Обрабатывает извещение от второй страницы блокнота // ----------------------------------------------------- LRESULT DlgProc2_OnNotify(HWND hdlg, int idFrom, NMHDR* pnmhdr) { switch(pnmhdr->code) { case PSN_SETACTIVE: { // Разблокируем кнопку Apply, когда страница // блокнота становится активной PropSheet_Changed(GetParent(hdlg), hdlg); break; }
case PSN_KILLACTIVE: { // Блокируем кнопку Apply, когда страница // блокнота становится неактивной PropSheet_UnChanged(GetParent(hdlg), hdlg); break; }
case PSN_APPLY: { opt.nUseTabs = (int)SendMessage( GetDlgItem(hdlg, IDC_USETABS), BM_GETCHECK, 0L, 0L); break; } default: break; } return FALSE; }
// ----------------------------------------------------- // Функция DlgProc3 // для третьей страницы блокнота // ----------------------------------------------------- BOOL APIENTRY DlgProc3(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc3_OnInitDialog); HANDLE_MSG(hdlg, WM_NOTIFY, DlgProc3_OnNotify); HANDLE_MSG(hdlg, WM_COMMAND, DlgProc3_OnCommand); default: break; } return FALSE; }
// ----------------------------------------------------- // Функция DlgProc3_OnInitDialog // Вызывается при инициализации третьей страницы блокнота // ----------------------------------------------------- BOOL DlgProc3_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam) { SetWindowText(GetDlgItem(hdlg, IDC_EDITKEYWORD), opt.szKeyWord);
return TRUE; }
// ----------------------------------------------------- // Функция DlgProc3_OnNotify // Обрабатывает извещение от третьей страницы блокнота // ----------------------------------------------------- LRESULT DlgProc3_OnNotify(HWND hdlg, int idFrom, NMHDR* pnmhdr) { switch(pnmhdr->code) { // Выполняем проверку длины ключевого слова, // которое не должно быть длиннее 8 символов case PSN_KILLACTIVE: { char szTempBuf[80];
// Получаем новое ключевое слово во временный буфер GetWindowText(GetDlgItem(hdlg, IDC_EDITKEYWORD), szTempBuf, 80);
// Проверяем его длину if(lstrlen(szTempBuf) > 8) { // Если длина больше 8 символов, выводим // предупреждающее сообщение MessageBox(NULL, "Too long keyword, " "must be shorter than 8 characters", szAppName, MB_OK | MB_ICONEXCLAMATION);
// Отменяем закрытие страницы блокнота SetWindowLong(hdlg, DWL_MSGRESULT, TRUE);
// Отменяем обновление параметров return TRUE; } else { // Если длина сообщения правильная, разрешаем // закрытие страницы блокнота и // обновление параметров SetWindowLong(hdlg, DWL_MSGRESULT, FALSE); return FALSE; } break; }
case PSN_APPLY: { GetWindowText(GetDlgItem(hdlg, IDC_EDITKEYWORD), opt.szKeyWord, 80); break; } default: break; } return FALSE; }
// ----------------------------------------------------- // Функция DlgProc3_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void DlgProc3_OnCommand(HWND hdlg, int id, HWND hwndCtl, UINT codeNotify) { // Сообщение от редактора текста if(id == IDC_EDITKEYWORD) { // Если пользователь изменил ключевое слово в окне // редактирования, разблокируем кнопку "Apply" if(codeNotify == EN_CHANGE) { PropSheet_Changed(GetParent(hdlg), hdlg); } } return FALSE; }
В файле psheet.h (листинг 6.2) находятся описания функций, определенных в приложении Property Sheet Demo.
Листинг 6.2. Файл psheet\psheet.h
// ----------------------------------------------------- // Описание функций // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct); void WndProc_OnDestroy(HWND hWnd); void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); LRESULT WndProc_OnNotify(HWND hWnd, int idFrom, NMHDR FAR * pnmhdr); BOOL APIENTRY DlgProc1(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL DlgProc1_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam);
LRESULT DlgProc1_OnNotify( HWND hWnd, int idFrom, NMHDR* pnmhdr); void DlgProc1_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); BOOL APIENTRY DlgProc2(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL DlgProc2_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam); LRESULT DlgProc2_OnNotify(HWND hWnd, int idFrom, NMHDR* pnmhdr); BOOL APIENTRY DlgProc3(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL DlgProc3_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam); LRESULT DlgProc3_OnNotify(HWND hWnd, int idFrom, NMHDR* pnmhdr); void DlgProc3_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify);
В файле resource.h (создается автоматически системой Microsoft Visual C++) находятся определения констант для работы с ресурсами приложения Property Sheet Demo. Этот файл представлен в листинге 6.3.
Листинг 6.3. Файл psheet\resource.h
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by PSHEET.RC // #define IDR_APPMENU 102 #define IDI_APPICON 103 #define IDI_APPICONSM 104 #define IDI_EFFECTS 105 #define IDI_TAB 106 #define IDI_KEYWORD 107 #define IDD_DIALOG1 121 #define IDD_DIALOG2 122 #define IDD_DIALOG3 123 #define IDC_BOLD 1000 #define IDC_ITALIC 1001 #define IDC_UNDERLINE 1002 #define IDC_USETABS 1004 #define IDC_EDITKEYWORD 1006 #define IDC_DONTUSETABS 1007 #define ID_FILE_EXIT 40001 #define ID_HELP_ABOUT 40003 #define ID_FILE_OPTIONS 40029
// Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 125 #define _APS_NEXT_COMMAND_VALUE 40030 #define _APS_NEXT_CONTROL_VALUE 1008 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
Файл psheet.rc (листинг 6.4) содержит определение ресурсов приложения Property Sheet Demo. Он создается автоматически.
Листинг 6.4. Файл psheet\psheet.rc
//Microsoft Visual C++ generated resource script. // #include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h"
////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS
////////////////////////////////////////////////////////////// // Menu //
IDR_APPMENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Options...", ID_FILE_OPTIONS MENUITEM SEPARATOR MENUITEM "E&xit", ID_FILE_EXIT END POPUP "&Help" BEGIN MENUITEM "&About...", ID_HELP_ABOUT END END
#ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // TEXTINCLUDE //
1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END
2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END
3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END
////////////////////////////////////////////////////////////// #endif // APSTUDIO_INVOKED
////////////////////////////////////////////////////////////// // Icon //
IDI_APPICON ICON DISCARDABLE "psheet.ico" IDI_APPICONSM ICON DISCARDABLE "psheetsm.ico" IDI_EFFECTS ICON DISCARDABLE "EFFECTS.ICO" IDI_TAB ICON DISCARDABLE "TAB.ICO" IDI_KEYWORD ICON DISCARDABLE "KEYWORD.ICO"
////////////////////////////////////////////////////////////// // Dialog //
IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 186, 95 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU FONT 8, "MS Sans Serif" BEGIN CONTROL "Bold",IDC_BOLD,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,17,23,35,10 CONTROL "Italic",IDC_ITALIC,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,17,34,35,10 CONTROL "Underline",IDC_UNDERLINE,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,17,45,46,10 GROUPBOX "Effects",IDC_STATIC,6,9,112,53 END
IDD_DIALOG2 DIALOG DISCARDABLE 0, 0, 186, 95 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU FONT 8, "MS Sans Serif" BEGIN CONTROL "Use Tabs", IDC_USETABS, "Button", BS_AUTORADIOBUTTON,18,22, 63,10 GROUPBOX "Tabs",IDC_STATIC,9,7,105,63 CONTROL "Don't use Tabs",IDC_DONTUSETABS,"Button", BS_AUTORADIOBUTTON,18,35,83,10 END
IDD_DIALOG3 DIALOG DISCARDABLE 0, 0, 211, 102 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU FONT 8, "MS Sans Serif" BEGIN EDITTEXT IDC_EDITKEYWORD,22,35,138,13,ES_AUTOHSCROLL GROUPBOX "Keyword",IDC_STATIC,9,15,166,52 END
////////////////////////////////////////////////////////////// // String Table //
STRINGTABLE DISCARDABLE BEGIN ID_FILE_EXIT "Quits the application" END
#ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 3 resource. ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
и функций приложения Property Sheet
Займемся описанием глобальных переменных и функций приложения Property Sheet Demo.
Глобальные переменные
Структура opt, имеющая тип OPTIONS, содержит параметры, которые настраиваются с помощью блокнота. Тип OPTIONS определен в нашем приложении.
Массив структур psheetPage типа PROPSHEETPAGE хранит описания страниц блокнота. Заголовок блокнота записан в структуре psheetHeader типа PROPSHEETHEADER.
Идентификаторы отдельных страниц, создаваемых функцией CreatePropertySheetPage, записываются в массив структур hPage типа HPROPSHEETPAGE.
В переменной hInst хранится идентификатор приложения, полученный функцией WinMain. Строчные массивы szAppName и szAppTitle хранят, соответственно, имя и заголовок приложения.
WinMain
Функция WinMain не имеет никаких особенностей, поэтому для экономии места в книге мы не будем ее описывать.
WndProc
Функция WndProc обрабатывает следующие сообщения: WM_CREATE, WM_DESTROY и WM_COMMAND. Обработка выполняется с использованием макрокоманды HANDLE_MSG.
WndProc_OnCreate
Обработчик сообщения WM_CREATE инициализирует библиотеку стандартных органов управления, вызывая для этого функцию InitCommonControls.
Затем он выполняет начальную инициализацию полей структуры параметров opt.
WndProc_OnDestroy
Функция WndProc_OnDestroy вызывается, когда пользователь завершает работу приложения. Она останавливает цикл обработки сообщений, вызывая функцию PostQuitMessage.
WndProc_OnCommand
Обработчики сообщений от строк Exit и About действуют также, как и в предыдущем приложении.
Когда пользователь выбирает из меню File строку Options, соответствующий обработчик создает блокнот и отображает его на экране. Процедура создания и отображения блокнота заключается в заполнении массива структур psheetPage, содержащих описания страниц, массива psheetHeader с описанием заголовка блокнота. Каждая страница добавляется в блокнот отдельно функцией CreatePropertySheetPage, причем идентификатор созданное страницы записывается в соответствующий элемент массива hPage.
Блокнот создается и отображается функцией PropertySheet, которой в качестве единственного параметра передается адрес заполненной структуры заголовка блокнота.
DlgProc1
Функция DlgProc1 - это функция диалога для первой страницы блокнота. Она обрабатывает сообщения WM_INITDIALOG, WM_COMMAND и WM_NOTIFY, вызывая для них, соответственно, функции DlgProc1_OnInitDialog, DlgProc1_OnCommand и DlgProc1_OnNotify.
Заметим, что ни сама функция диалога, ни одна из только что перечисленных функций обработки сообщений не вызывает функцию EndDialog, так как в противном случае блокнот будет уничтожен.
DlgProc1_OnInitDialog
Функция DlgProc1_OnInitDialog обрабатывает сообщение WM_INITDIALOG, которое поступает в функцию диалога первой страницы блокнота при инициализации последней.
Единственное, что она делает, это устанавливает переключатели, расположенные на первой странице, в соответствии с содержимым полей структуры параметров opt:
SendMessage(GetDlgItem (hdlg, IDC_BOLD), BM_SETCHECK, opt.nBold, 0L);
Метод установки состояния переключателей в диалоговой панели основан на использовании сообщения BM_SETCHECK. Это сообщение, а также функция GetDlgItem, возвращающая идентификатор окна органа управления, расположенного в диалоговой панели, были подробно описаны в 12 томе "Библиотеки системного программиста".
DlgProc1_OnNotify
Функция DlgProc1_OnNotify обрабатывает извещения, которые поступают в функцию диалога первой страницы в форме сообщения WM_NOTIFY.
В ответ на извещение PSN_APPLY функция DlgProc1_OnNotify получает состояние переключателей и записывает его в соответствующие поля структуры opt:
opt.nBold = (int)SendMessage(GetDlgItem(hdlg, IDC_BOLD), BM_GETCHECK, 0L, 0L);
Для определения состояния переключателей им посылается сообщение BM_GETCHECK.
DlgProc1_OnCommand
Функция DlgProc1_OnCommand обрабатывает сообщение WM_COMMAND, поступающее от переключателей, расположенных на первой странице.
Как только пользователь нажмет какую-либо из имеющихся на первой странице блокнота кнопку, функция DlgProc1_OnCommand разблокирует кнопку Apply, посылая блокноту сообщение PSM_CHANGED:
PropSheet_Changed(GetParent(hdlg), hdlg);
Сообщение посылается макрокомандой PropSheet_Changed. В качестве первого параметра этой макрокоманды (как и других макрокоманд, предназначенных для посылки сообщений блокноту) необходимо указать идентификатор окна блокнота. Окно блокнота является родительским по отношению к окнам страниц, поэтому мы воспользовались функцией GetParent . В качестве второго параметра необходимо указать идентификатор диалога, который передается в функцию диалога.
DlgProc2
Функция DlgProc2 является функцией диалога для второй страницы блокнота. Она обрабатывает сообщения WM_INITDIALOG и WM_NOTIFY, вызывая для них, соответственно, функции DlgProc2_OnInitDialog и DlgProc2_OnNotify.
DlgProc2_OnInitDialog
Функция DlgProc2_OnInitDialog обрабатывает сообщение WM_INITDIALOG, которое поступает в функцию диалога второй страницы блокнота при ее инициализации.
Ее задача аналогична задаче функции DlgProc1_OnInitDialog - установка переключателей, расположенных на второй странице в соответствии с содержимым полей структуры параметров opt.
Переключатель с идентификатором IDC_DONTUSETABS всегда устанавливается в состояние, противоположное состоянию переключателя IDC_USETABS.
DlgProc2_OnNotify
Функция DlgProc2_OnNotify обрабатывает извещения, которые поступают в функцию диалога второй страницы.
Когда пользователь переключается на вторую страницу, делая ее активной, функция диалога получает извещение PSN_SETACTIVE. Обработчик этого извещения разблокирует кнопку Apply, вызывая для этого макрокоманду PropSheet_Changed.
Извещение PSN_KILLACTIVE поступает в функцию диалога, когда пользователь завершил работу с текущей страницей блокнота и пытается переключиться на другую страницу. При этом кнопка Apply блокируется при помощи макрокоманды PropSheet_UnChanged :
PropSheet_UnChanged(GetParent(hdlg), hdlg);
В ответ на извещение PSN_APPLY функция DlgProc2_OnNotify получает состояние переключателя IDC_USETABS и записывает его в поле nUseTabs структуры opt.
DlgProc3
Функция DlgProc3 является функцией диалога третьей страницы блокнота. Она обрабатывает сообщения WM_INITDIALOG, WM_COMMAND и WM_NOTIFY, вызывая для них, соответственно, функции DlgProc3_OnInitDialog, DlgProc3_OnCommand и DlgProc3_OnNotify.
DlgProc3_OnInitDialog
Функция DlgProc3_OnInitDialog устанавливает содержимое текстового редактора IDC_EDITKEYWORD, записывая в него строку из поля opt.szKeyWord.
DlgProc3_OnNotify
Функция DlgProc3_OnNotify обрабатывает извещения, которые поступают в функцию диалога второй страницы.
Если пользователь изменил текст в окне редактора IDC_EDITKEYWORD, обработчик сообщения WM_COMMAND (будет описан ниже) разблокирует кнопку Apply. Если теперь пользователь попытается нажать эту кнопку или кнопку OK, либо попытается переключиться на другую страницу блокнота, функция диалога третьей страницы получит извещение PSN_KILLACTIVE.
Обработчик этого извещения записывает содержимое текстового редактора во временный буфер szTempBuf и с помощью функции lstrlen определяет его длину. Если длина больше 8 символов, на экран выводится предупреждающее сообщение, после чего выполняется блокирование страницы. Теперь пользователь будет вынужден ввести ключевое слово правильной длины, иначе он не сможет переключиться на другую страницу или завершить работу с блокнотом.
Для блокирования обработчик извещения PSN_KILLACTIVE возвращает значение TRUE, установив предварительно код завершения TRUE в структуре окна диалога с помощью функции SetWindowLong :
SetWindowLong(hdlg, DWL_MSGRESULT, TRUE); return TRUE;
Если же длина полученной текстовой строки меньше 8 символов, обработчик извещения PSN_KILLACTIVE возвращает значение FALSE:
SetWindowLong(hdlg, DWL_MSGRESULT, FALSE); return FALSE;
В результате функция диалога третьей и остальных страниц получает извещение PSN_APPLY. При этом обработчик извещения PSN_APPLY третьей страницы сохраняет новое ключевое слово в поле opt.szKeyWord.
DlgProc3_OnCommand
Кнопка Apply, расположенная на третьей странице блокнота, разблокируется только в том случае, когда пользователь изменил ключевое слово. Для этого обработчик сообщения WM_COMMAND, реализованный функцией DlgProc3_OnCommand, отслеживает извещение EN_CHANGE. Это извещение поступает от окна текстового редактора в форме сообщения WM_COMMAND в том случае, когда пользователь изменил редактируемый текст (см. том 12 "Библиотеки системного программиста").
Разблокировка кнопки Apply выполняется при помощи макрокоманды PropSheet_Changed.
Операционная система Windows 95 для программиста
Приложение RtfPad представляет собой достаточно мощный текстовый редактор, способный работать с неформатированными текстовыми файлами и файлами в формате RTF. С его помощью вы можете преобразовывать текстовые файлы в RTF-файлы и обратно (разумеется, с потерей форматирования). На рис. 5.1 представлено главное окно приложения RtfPad.
Главное окно приложения RtfPad
С помощью строк Open и Save as меню File можно, соответственно, загружать для редактирования и сохранять файлы в текстовом формате и формате RTF. Строка Print этого же меню позволяет вывести редактируемый текст на печать, причем он будет напечатан с использованием шрифтового оформления и с учетом указанного оформления параграфов.
Меню Edit предназначено для выполнения стандартных операций с универсальным буфером обмена Clipboard , такие как копирование, вставка и перемещение. С помощью строки Undo вы можете отменить последнюю выполненную операцию.
С помощью меню Format (рис. 5.2) вы можете выделить символы жирным или наклонным начертанием, сделать их подчеркнутыми, выбрать любой установленный в системе шрифт. Вы можете также выбрать для текущего параграфа или для выделенных параграфов выравнивание по правой или левой границе окна, а также центрирование.
Меню Format позволяет задать шрифтовое оформление символов, а также тип выравнивания для параграфа
Для того чтобы не загромождать исходные тексты приложения, мы не стали снабжать его такими органами управления, как Toolbar или Statusbar. При необходимости вы сможете сделать это самостоятельно, обратившись ко второй главе нашей книги. Кроме этого, мы не стремились задействовать максимально все возможности органа управления Rich Edit, так как их немало, а объем книги ограничен. Исходные тексты более сложного редактора текста вы сможете найти в SDK (приложение WritePad ).
Исходные тексты приложения RtfPad
Все функции приложения RtfPad определены в файле rtfpad.c (листинг 5.1).
Листинг 5.1. Файл rtfpad\rtfpad.c
#define STRICT #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include <richedit.h> #include "resource.h" #include "afxres.h" #include "rtfpad.h"
HINSTANCE hInst; char szAppName[] = "RtfEditApp"; char szAppTitle[] = "Rich Text Editor RtfPad"; HWND hwndEdit; HINSTANCE hRTFLib;
// ----------------------------------------------------- // Функция WinMain // ----------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg;
hInst = hInstance;
// Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return FALSE; }
// Загружаем библиотеку RICHED32.DLL hRTFLib = LoadLibrary("RICHED32.DLL"); if(!hRTFLib) return FALSE;
// Регистрируем класс окна memset(&wc, 0, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0); wc.style = 0; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU); wc.lpszClassName = szAppName; if(!RegisterClassEx(&wc)) if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE;
// Создаем главное окно приложения hWnd = CreateWindow(szAppName, szAppTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL); if(!hWnd) return(FALSE);
// Отображаем окно и запускаем цикл обработки сообщений ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while(GetMessage (&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
// ----------------------------------------------------- // Функция WndProc // ----------------------------------------------------- LRESULT WINAPI WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate); HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy); HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand); HANDLE_MSG(hWnd, WM_SIZE, WndProc_OnSize); HANDLE_MSG(hWnd, WM_SETFOCUS, WndProc_OnSetFocus);
default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } }
// ----------------------------------------------------- // Функция WndProc_OnCreate // ----------------------------------------------------- BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { RECT rc;
// Определяем размеры внутренней области главного окна GetClientRect(hWnd, &rc);
// Создаем орган управления Rich Edit hwndEdit = CreateWindowEx(0L, "RICHEDIT", "", WS_VISIBLE | WS_CHILD | WS_BORDER | WS_HSCROLL | WS_VSCROLL | ES_NOHIDESEL | ES_AUTOVSCROLL | ES_MULTILINE | ES_SAVESEL | ES_SUNKEN, 0, 0, rc.right - rc.left, rc.bottom - rc.top, hWnd, (HMENU) IDC_RTFEDIT, hInst, NULL);
if(hwndEdit == NULL) return FALSE;
// Передаем фокус ввода органу управления Rich Edit SetFocus(hwndEdit);
return TRUE; }
// ----------------------------------------------------- // Функция WndProc_OnDestroy // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnDestroy(HWND hWnd) { // Уничтожаем орган управления Rich Edit if(hwndEdit) DestroyWindow(hwndEdit);
// Освобождаем библиотеку RICHED32.DLL if(hRTFLib) FreeLibrary(hRTFLib);
PostQuitMessage(0); return 0L; }
// ----------------------------------------------------- // Функция WndProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { CHARFORMAT cf; CHOOSEFONT chfnt; LOGFONT lf; HDC hDC; PARAFORMAT pf;
switch (id) { // Изменяем жирность символов case ID_FORMAT_BOLD: { cf.cbSize = sizeof(cf);
// Определяем формат символов SendMessage(hwndEdit,EM_GETCHARFORMAT,TRUE,(LPARAM)&cf);
// Изменяем бит поля dwEffects, с помощью которого // можно выделить символы как bold (жирное начертание) cf.dwMask = CFM_BOLD;
// Инвертируем бит, определяющий жирное начертание cf.dwEffects ^= CFE_BOLD;
// Изменяем формат символов SendMessage(hwndEdit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
return 0L; break; }
// Устанавливаем или отменяем наклонное // начертание символов case ID_FORMAT_ITALIC: { cf.cbSize = sizeof(cf); SendMessage(hwndEdit, EM_GETCHARFORMAT, TRUE, (LPARAM)&cf);
cf.dwMask = CFM_ITALIC; cf.dwEffects ^= CFE_ITALIC; SendMessage(hwndEdit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
return 0L; break; }
// Устанавливаем или отменяем выделение // символов подчеркиванием case ID_FORMAT_UNDERLINE: { cf.cbSize = sizeof(cf); SendMessage(hwndEdit, EM_GETCHARFORMAT, TRUE, (LPARAM)&cf);
cf.dwMask = CFM_UNDERLINE; cf.dwEffects ^= CFE_UNDERLINE; SendMessage(hwndEdit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
return 0L; break; }
// Изменяем шрифт символов case ID_FORMAT_FONT: { cf.cbSize = sizeof(cf);
// Определяем текущий формат символов SendMessage(hwndEdit, EM_GETCHARFORMAT, TRUE, (LPARAM)&cf);
// Сбрасываем содержимое структур, которые будут // использоваться для выбора шрифта memset(&chfnt, 0, sizeof(chfnt)); memset(&lf, 0, sizeof(lf));
// Получаем контекст отображения hDC = GetDC(hWnd);
// Если было задано выделение наклоном или жирным // шрифтом,подбираем шрифт с соответствующими атрибутами lf.lfItalic = (BOOL)(cf.dwEffects & CFE_ITALIC); lf.lfUnderline = (BOOL)(cf.dwEffects & CFE_UNDERLINE);
// Преобразуем высоту из TWIPS-ов в пикселы. // Устанавливаем отрицательный знак, чтобы // выполнялось преобразование и использовалось // абсолютное значение высоты символов lf.lfHeight = - cf.yHeight/20;
// Набор символов, принятый по умолчанию lf.lfCharSet = ANSI_CHARSET;
// Качество символов, принятое по умолчанию lf.lfQuality = DEFAULT_QUALITY;
// Выбираем семейство шрифтов lf.lfPitchAndFamily = cf.bPitchAndFamily;
// Название начертания шрифта lstrcpy(lf.lfFaceName, cf.szFaceName);
// Устанавливаем вес шрифта в зависимости от того, // было использовано выделение жирным шрифтом // или нет if(cf.dwEffects & CFE_BOLD) lf.lfWeight = FW_BOLD; else lf.lfWeight = FW_NORMAL;
// Заполняем структуру для функции выбора шрифта chfnt.lStructSize = sizeof(chfnt); chfnt.Flags = CF_SCREENFONTS | CF_INITTOLOGFONTSTRUCT; chfnt.hDC = hDC; chfnt.hwndOwner = hWnd; chfnt.lpLogFont = &lf; chfnt.rgbColors = RGB(0,0,0); chfnt.nFontType = SCREEN_FONTTYPE;
// Выводим на экран диалоговую панель для // выбора шрифта if(ChooseFont(&chfnt)) { // Можно использовать все биты поля dwEffects cf.dwMask = CFM_BOLD | CFM_FACE | CFM_ITALIC | CFM_UNDERLINE | CFM_SIZE | CFM_OFFSET;
// Преобразование в TWIPS-ы cf.yHeight = - lf.lfHeight * 20;
// Устанавливаем поле dwEffects cf.dwEffects = 0; if(lf.lfUnderline) cf.dwEffects |= CFE_UNDERLINE;
if(lf.lfWeight == FW_BOLD) cf.dwEffects |= CFE_BOLD;
if(lf.lfItalic) cf.dwEffects |= CFE_ITALIC;
// Устанавливаем семейство шрифта cf.bPitchAndFamily = lf.lfPitchAndFamily;
// Устанавливаем название начертания шрифта lstrcpy(cf.szFaceName, lf.lfFaceName);
// Изменяем шрифтовое оформление символов SendMessage(hwndEdit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf); }
// Освобождаем контекст отображения ReleaseDC(hWnd, hDC);
return 0L; break; }
// Устанавливаем выравнивание параграфа по левой границе // окна органа управления Rich Edit case ID_FORMAT_PARAGRAPH_LEFT: { pf.cbSize = sizeof(pf); pf.dwMask = PFM_ALIGNMENT; pf.wAlignment = PFA_LEFT;
// Изменяем тип выравнивания текущего параграфа SendMessage(hwndEdit, EM_SETPARAFORMAT, 0, (LPARAM)&pf);
return 0L; break; }
// Устанавливаем выравнивание параграфа по правой границе // окна органа управления Rich Edit case ID_FORMAT_PARAGRAPH_RIGHT: { pf.cbSize = sizeof(pf); pf.dwMask = PFM_ALIGNMENT; pf.wAlignment = PFA_RIGHT; SendMessage(hwndEdit, EM_SETPARAFORMAT, 0, (LPARAM)&pf); return 0L; break; }
// Выполняем центровку текущего параграфа case ID_FORMAT_PARAGRAPH_CENTER: { pf.cbSize = sizeof(pf); pf.dwMask = PFM_ALIGNMENT; pf.wAlignment = PFA_CENTER; SendMessage(hwndEdit, EM_SETPARAFORMAT, 0, (LPARAM)&pf); return 0L; break; }
// Реализуем стандартные функции меню Edit case ID_EDIT_UNDO: SendMessage(hwndEdit, EM_UNDO, 0, 0L); return 0L; break;
case ID_EDIT_CUT: SendMessage(hwndEdit, WM_CUT, 0, 0L); return 0L; break;
case ID_EDIT_COPY: SendMessage(hwndEdit, WM_COPY, 0, 0L); return 0L; break;
case ID_EDIT_PASTE: SendMessage(hwndEdit, WM_PASTE, 0, 0L); return 0L; break;
case ID_EDIT_DELETE: SendMessage(hwndEdit, WM_CLEAR, 0, 0L); return 0L; break;
// Выделяем весь текст, который есть в окне // органа управления Rich Edit case ID_EDIT_SELECTALL: { CHARRANGE charr;
charr.cpMin = 0; // от начала... charr.cpMax = -1; // ... и до конца текста
SendMessage(hwndEdit, EM_EXSETSEL, 0, (LPARAM)&charr); return 0L; break; }
// При создании нового текста удаляем текущее // содержимое окна редактирования case ID_FILE_NEW: SetWindowText(hwndEdit,"\0"); return 0L; break;
case ID_FILE_OPEN: FileOpen(hWnd); // загружаем файл для редактирования return 0L; break;
case ID_FILE_SAVEAS: FileSaveAs(hWnd); // сохраняем текст в файле return 0L; break;
case ID_FILE_PRINT: FilePrint(); // печатаем текст return 0L; break;
case ID_FILE_EXIT: PostQuitMessage(0); // завершаем работу приложения return 0L; break;
case ID_HELP_ABOUT: MessageBox(hWnd, "Rich Text Editor RtfPad, v.1.0\n" "(C) Alexandr Frolov, 1995\n" "Email: frolov@glas.apc.org", szAppTitle, MB_OK | MB_ICONINFORMATION); return 0L; break;
default: break; } return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc); }
// ----------------------------------------------------- // Функция WndProc_OnSize // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnSize(HWND hwnd, UINT state, int cx, int cy) { MoveWindow(hwndEdit, 0, 0, cx, cy, TRUE); return FORWARD_WM_SIZE(hwnd, state, cx, cy, DefWindowProc); }
// ----------------------------------------------------- // Функция WndProc_OnSetFocus // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnSetFocus(HWND hwnd, HWND hwndOldFocus) { // Когда главное окно нашего приложения получает // фокус ввода, оно передает фокус ввода окну // органа управления Rich Edit SetFocus(hwndEdit); return FORWARD_WM_SETFOCUS(hwnd, hwndOldFocus, DefWindowProc); }
// ----------------------------------------------------- // Функция FileSaveAs // ----------------------------------------------------- void FileSaveAs(HWND hwnd) { OPENFILENAME ofn; char szFile[256] = "untitled.rtf"; char szDirName[512]; char szFileTitle[256];
// Фильтр допускает сохранение текста в файле с // расширением имени rtf, txt, или любым другим char szFilter[256] = "Rich Text Files\0*.rtf\0Text Files\0*.txt\0" "Any Files\0*.*\0";
HFILE hFile; OFSTRUCT of; EDITSTREAM es;
memset(&ofn, 0, sizeof(OPENFILENAME));
// Определяем путь к текущему каталогу GetCurrentDirectory(sizeof(szDirName), szDirName);
// Заполняем структуру для выбора выходного файла ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hwnd; ofn.lpstrFilter = szFilter; ofn.lpstrInitialDir = szDirName; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.lpstrDefExt = "rtf"; ofn.Flags = OFN_OVERWRITEPROMPT | OFN_HIDEREADONLY;
// Выводим на экран диалоговую панель, предназначенную // для выбора выходного файла if(GetSaveFileName(&ofn)) { // Если файл выбран, открываем его для записи или // создаем if (*ofn.lpstrFile) { hFile = OpenFile(ofn.lpstrFile, &of, OF_CREATE);
// Устанавливаем параметры функции обратного вызова, // которая будет выполнять запись es.dwCookie = (DWORD)hFile; es.dwError = 0; es.pfnCallback = SaveCallback;
// Если расширение файла rtf, файл сохраняется как // rtf-файл. В противном случае он сохраняется как // обычный текстовый файл _strupr(&ofn.lpstrFile[ofn.nFileExtension]);
if(!strncmp(&ofn.lpstrFile[ofn.nFileExtension],"RTF",3)) SendMessage(hwndEdit,EM_STREAMOUT,SF_RTF, (LPARAM)&es); else SendMessage(hwndEdit,EM_STREAMOUT,SF_TEXT, (LPARAM)&es);
// Закрываем файл _lclose(hFile);
// Сбрасываем признак изменения содержимого окна // редактора текста SendMessage(hwndEdit, EM_SETMODIFY, FALSE, 0L); } } }
// ----------------------------------------------------- // Функция SaveCallback // ----------------------------------------------------- DWORD CALLBACK SaveCallback(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb) { // Выполняем запись блока данных длиной cb байт cb = _lwrite((HFILE)dwCookie, pbBuff, cb); *pcb = cb; return 0; }
// ----------------------------------------------------- // Функция FileOpen // ----------------------------------------------------- void FileOpen(HWND hwnd) { OPENFILENAME ofn; char szFile[256]; char szDirName[256]; char szFileTitle[256]; char szFilter[256] = "Rich Text Files\0*.rtf\0Text Files\0*.txt\0" "Any Files\0*.*\0";
HFILE hFile; OFSTRUCT of; EDITSTREAM es;
memset(&ofn, 0, sizeof(OPENFILENAME)); GetCurrentDirectory(sizeof(szDirName), szDirName); szFile[0] = '\0';
// Подготавливаем структуру для выбора входного файла ofn.lStructSize = sizeof(OPENFILENAME); ofn.hwndOwner = hwnd; ofn.lpstrFilter = szFilter; ofn.lpstrInitialDir = szDirName; ofn.nFilterIndex = 1; ofn.lpstrFile = szFile; ofn.nMaxFile = sizeof(szFile); ofn.lpstrFileTitle = szFileTitle; ofn.nMaxFileTitle = sizeof(szFileTitle); ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST;
// Выводим на экран диалоговую панель, предназначенную // для выбора входного файла if(GetOpenFileName(&ofn)) { // Если файл выбран, открываем его для чтения if (*ofn.lpstrFile) { hFile = OpenFile(ofn.lpstrFile, &of, OF_READ);
// Устанавливаем параметры функции обратного вызова, // которая будет выполнять чтение es.dwCookie = (DWORD)hFile; es.dwError = 0; es.pfnCallback = OpenCallback;
// Если расширение файла rtf, файл загружается как // rtf-файл. В противном случае он загружается как // обычный текстовый файл _strupr(&ofn.lpstrFile[ofn.nFileExtension]); if(!strncmp(&ofn.lpstrFile[ofn.nFileExtension],"RTF",3)) SendMessage(hwndEdit,EM_STREAMIN,SF_RTF,(LPARAM)&es); else SendMessage(hwndEdit,EM_STREAMIN,SF_TEXT,(LPARAM)&es);
// Закрываем файл _lclose(hFile);
// Сбрасываем признак изменения содержимого окна // редактора текста SendMessage(hwndEdit, EM_SETMODIFY, FALSE, 0L); } } }
// ----------------------------------------------------- // Функция OpenCallback // ----------------------------------------------------- DWORD CALLBACK OpenCallback(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb) { // Выполняем чтение блока данных длиной cb байт *pcb = _lread((HFILE)dwCookie, pbBuff, cb); if(*pcb <= 0) *pcb = 0; return 0; }
// ----------------------------------------------------- // Функция FilePrint // ----------------------------------------------------- void FilePrint(void) { FORMATRANGE fr; DOCINFO docInfo; LONG lLastChar, lTextSize; PRINTDLG pd; int nRc; HDC hPrintDC;
// Инициализируем поля структуры PRITDLG memset(&pd, 0, sizeof(pd)); pd.lStructSize = sizeof(PRINTDLG); pd.hwndOwner = hwndEdit; pd.hInstance = (HANDLE)hInst; pd.Flags = PD_RETURNDC | PD_NOPAGENUMS | PD_NOSELECTION | PD_PRINTSETUP | PD_ALLPAGES; pd.nFromPage = 0xffff; pd.nToPage = 0xffff; pd.nMinPage = 0; pd.nMaxPage = 0xffff; pd.nCopies = 1;
// Выводим на экран диалоговую панель, предназначенную // для печати документа if(PrintDlg(&pd) == TRUE) { hPrintDC = pd.hDC;
// Инициализируем поля структуры FORMATRANGE memset(&fr, 0, sizeof(fr));
// Будем печатать с использованием контекста // принтера, полученного от функции PrintDlg fr.hdc = fr.hdcTarget = hPrintDC;
// Печатаем весь документ fr.chrg.cpMin = 0; fr.chrg.cpMax = -1;
// Устанавливаем размеры страницы в TWIPS-ах fr.rcPage.top = 0; fr.rcPage.left = 0; fr.rcPage.right = MulDiv(GetDeviceCaps(hPrintDC, PHYSICALWIDTH), 1440, GetDeviceCaps(hPrintDC, LOGPIXELSX));
fr.rcPage.bottom = MulDiv(GetDeviceCaps(hPrintDC, PHYSICALHEIGHT),1440, GetDeviceCaps(hPrintDC, LOGPIXELSY)); fr.rc = fr.rcPage;
// Оставляем поля if(fr.rcPage.right > 2*3*1440/4+1440) fr.rc.right -= (fr.rc.left = 3*1440/4); if(fr.rcPage.bottom > 3*1440) fr.rc.bottom -= (fr.rc.top = 1440);
// Заполняем поля структуры DOCINFO memset(&docInfo, 0, sizeof(DOCINFO)); docInfo.cbSize = sizeof(DOCINFO); docInfo.lpszOutput = NULL; docInfo.lpszDocName = "RtfPad document";
// Начинаем печать документа nRc = StartDoc(hPrintDC, &docInfo);
// Если произошла ошибка, получаем и выводим на экран // код ошибки if(nRc < 0) { char szErr[128]; DWORD dwErr = GetLastError(); wsprintf(szErr, "Print Error %ld", dwErr);
MessageBox(NULL, szErr, szAppTitle, MB_OK | MB_ICONEXCLAMATION);
DeleteDC(hPrintDC); return; }
// Начинаем печать страницы StartPage(hPrintDC);
lLastChar = 0;
// Определяем длину текста в байтах lTextSize = SendMessage(hwndEdit, WM_GETTEXTLENGTH, 0, 0);
// Цикл по всем страницам документа while (lLastChar < lTextSize) { // Форматируем данные для принтера и печатаем их lLastChar = SendMessage(hwndEdit, EM_FORMATRANGE, TRUE, (LPARAM) &fr);
if(lLastChar < lTextSize) { // Завершаем печать очередной страницы EndPage(hPrintDC);
// Начинаем новую страницу StartPage(hPrintDC); fr.chrg.cpMin = lLastChar; fr.chrg.cpMax = -1; } }
// Удаляем информацию, которая хранится в // органе управления Rich Edit SendMessage(hwndEdit, EM_FORMATRANGE, TRUE, (LPARAM)NULL);
// Завершаем печать страницы EndPage(hPrintDC);
// Завершаем печать документа EndDoc(hPrintDC);
// Удаляем контекст принтера DeleteDC(hPrintDC); } }
Файл rtfpad.h (листинг 5.2) содержит описание функций и определение константы IDC_RTFEDIT (идентификатор органа управленияRich Edit).
Листинг 5.2. Файл rtfpad\rtfpad.h
// ----------------------------------------------------- // Описание функций // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct); void WndProc_OnDestroy(HWND hWnd); void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); LRESULT WndProc_OnNotify(HWND hWnd, int idFrom, NMHDR FAR * pnmhdr); void WndProc_OnSize(HWND hwnd, UINT state, int cx, int cy); void WndProc_OnSetFocus(HWND hwnd, HWND hwndOldFocus); void FileSaveAs(HWND hwnd); DWORD CALLBACK SaveCallback(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb); void FileOpen(HWND hwnd); DWORD CALLBACK OpenCallback(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb); void FilePrint(void); #define IDC_RTFEDIT 1236
В файле resource.h ( который создается автоматически системой Microsoft Visual C++) находятся определения констант для работы с ресурсами приложения RtfPad. Этот файл представлен в листинге 5.3.
Листинг 5.3. Файл rtfpad\resource.h
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by RTFPAD.RC // #define IDR_APPMENU 102 #define IDI_APPICON 103 #define IDI_APPICONSM 104 #define ID_FILE_EXIT 40001 #define ID_HELP_ABOUT 40003 #define ID_FORMAT_BOLD 40010 #define ID_FORMAT_ITALIC 40011 #define ID_FORMAT_UNDERLINE 40012 #define ID_FORMAT_FONT 40013 #define ID_FORMAT_PARAGRAPH_LEFT 40014 #define ID_FORMAT_PARAGRAPH_RIGHT 40015 #define ID_FORMAT_PARAGRAPH_CENTER 40016 #define ID_EDIT_DELETE 40021 #define ID_FILE_SAVEAS 40024 #define ID_EDIT_SELECTALL 40028
// Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 121 #define _APS_NEXT_COMMAND_VALUE 40029 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
Файл rtfpad.rc (листинг 5.4) содержит определение ресурсов приложения RtfPad. Он создается автоматически.
Листинг 5.4. Файл rtfpad\rtfpad.rc
//Microsoft Visual C++ generated resource script. // #include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h"
////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS
////////////////////////////////////////////////////////////// // Menu // IDR_APPMENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&New", ID_FILE_NEW MENUITEM SEPARATOR MENUITEM "&Open...", ID_FILE_OPEN MENUITEM "&Save as...", ID_FILE_SAVEAS MENUITEM SEPARATOR MENUITEM "&Print...", ID_FILE_PRINT MENUITEM SEPARATOR MENUITEM "E&xit", ID_FILE_EXIT END POPUP "&Edit" BEGIN MENUITEM "&Undo", ID_EDIT_UNDO MENUITEM SEPARATOR MENUITEM "Cu&t", ID_EDIT_CUT MENUITEM "&Copy", ID_EDIT_COPY MENUITEM "&Paste", ID_EDIT_PASTE MENUITEM "&Delete", ID_EDIT_DELETE MENUITEM SEPARATOR MENUITEM "&Select All", ID_EDIT_SELECTALL END POPUP "&Format" BEGIN MENUITEM "&Bold", ID_FORMAT_BOLD MENUITEM "&Italic", ID_FORMAT_ITALIC MENUITEM "&Underline", ID_FORMAT_UNDERLINE MENUITEM SEPARATOR MENUITEM "&Font...", ID_FORMAT_FONT MENUITEM SEPARATOR POPUP "&Paragraph" BEGIN MENUITEM "&Left", ID_FORMAT_PARAGRAPH_LEFT MENUITEM "&Right", ID_FORMAT_PARAGRAPH_RIGHT MENUITEM "&Center", ID_FORMAT_PARAGRAPH_CENTER END END POPUP "&Help" BEGIN MENUITEM "&About...", ID_HELP_ABOUT END END
#ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // TEXTINCLUDE //
1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END
2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END
3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END
////////////////////////////////////////////////////////////// #endif // APSTUDIO_INVOKED
////////////////////////////////////////////////////////////// // Icon //
IDI_APPICON ICON DISCARDABLE "rtfpad.ico" IDI_APPICONSM ICON DISCARDABLE "rtfpadsm.ico"
////////////////////////////////////////////////////////////// // String Table //
STRINGTABLE DISCARDABLE BEGIN ID_FILE_EXIT "Quits the application" END
#ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 3 resource. // ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
В этом разделе мы опишем
В этом разделе мы опишем глобальные переменные и функции приложения RtfPad. Попутно мы приведем информацию об использовании некоторых сообщений, предназначенных для органа управления Rich Edit.
Глобальные переменные
В переменной hInst хранится идентификатор приложения, полученный функцией WinMain. Строчные массивы szAppName и szAppTitle хранят, соответственно, имя и заголовок приложения.
Переменная hwndEdit используется для хранения идентификатора созданного органа управления Rich Edit.
Для инициализации DLL-библиотеки, отвечающей за работу органа управления Rich Edit, мы используем переменную hRTFLib (в нее записывается идентификатор загруженной библиотеки RICHED32.DLL).
WinMain
Функция WinMain не имеет никаких особенностей, за исключением того что в ней выполняется явная загрузка библиотеки RICHED32.DLL . Для этого мы используем функцию LoadLibrary :
hRTFLib = LoadLibrary("RICHED32.DLL"); if(!hRTFLib) return FALSE;
WndProc
Функция WndProc обрабатывает следующие сообщения: WM_CREATE, WM_DESTROY, WM_COMMAND, WM_SIZE и WM_SETFOCUS. Обработка выполняется с использованием макрокоманды HANDLE_MSG.
WndProc_OnCreate
Обработчик сообщения WM_CREATE создает орган управления Rich Edit. Размеры окна органа управления устанавливаются равными размерам внутренней области главного окна приложения и в дальнейшем изменяются обработчиком сообщения WM_SIZE.
После создания окно органа управления Rich Edit получает фокус ввода, для чего вызывается функция SetFocus , описанная нами в 12 томе "Библиотеки системного программиста":
SetFocus(hwndEdit);
WndProc_OnDestroy
Функция WndProc_OnDestroy вызывается, когда пользователь завершает работу приложения. Она уничтожает окно органа управления Rich Edit и освобождает загруженную при инициализации приложения библиотеку RICHED32.DLL, вызывая функцию FreeLibrary :
if(hRTFLib) FreeLibrary(hRTFLib);
Затем функция WndProc_OnDestroy останавливает цикл обработки сообщений, вызывая функцию PostQuitMessage.
WndProc_OnCommand
Функция WndProc_OnCommand обрабатывает сообщение WM_COMMAND, поступающее от главного меню приложения. Рассмотрим процедуры обработки сообщений для каждой строки меню. Название строки мы будем отделять от названия меню символом "/".
Format/Bold
Когда пользователь выделяет текст и выбирает из меню Format строку Bold, выделенный текст будет оформлен жирным шрифтом. Если же выбрать эту строку без предварительного выделения текста, указанное оформление получат символы, введенные после выполнения этой операции.
После повторного выбора строки оформление жирным шрифтом отменяется.
Соответствующий обработчик вначале определяет текущее оформление символов, посылая окну органа управления сообщение EM_GETCHARFORMAT :
cf.cbSize = sizeof(cf); SendMessage(hwndEdit, EM_GETCHARFORMAT, TRUE, (LPARAM)&cf);
Ниже приведены параметры этого сообщения:
wParam = (WPARAM)(BOOL)fSelection; lParam = (LPARAM)(CHARFORMAT FAR *)lpFmt;
Параметр fSelection может принимать значения TRUE или FALSE. В первом случае будет определено оформление символов, принятое по умолчанию, во втором - оформление для выделенного текста.
Параметр lpFmt должен содержать указатель на структуру типа CHARFORMAT , в которую будут записаны атрибуты форматирования. Эта структура имеет следующий формат:
typedef struct _charformat { UINT cbSize; // размер структуры в байтах _WPAD _wPad1; // зарезервировано DWORD dwMask; // маски полей атрибутов DWORD dwEffects;// эффекты, использованные при оформлении LONG yHeight; // высота символов LONG yOffset; // смещение от базовой линии COLORREF crTextColor; // цвет текста BYTE bCharSet; // набор символов BYTE bPitchAndFamily; // семейство шрифтов TCHAR szFaceName[LF_FACESIZE]; // название шрифта _WPAD _wPad2; // зарезервировано } CHARFORMAT;
Перед использованием структуры CHARFORMAT в поле cbSize следует записать размер структуры, как мы это сделали в приведенном выше примере.
Если структура CHARFORMAT используется для установки формата, в поле dwMask следует записать маски, соответствующие устанавливаемым атрибутам оформления (сведения об этих атрибутах будут записаны в поле dwEffects, рассмотренное ниже, и в другие поля структуры CHARFORMAT).
Значение маски | Поля структуры CHARFORMAT |
CFM_BOLD | Значение CFE_BOLD поля dwEffects |
CFM_COLOR | Поле crTextColor и значение CFE_AUTOCOLOR в поле dwEffects |
CFM_FACE | Поле szFaceName |
CFM_ITALIC | Значение CFE_ITALIC поля dwEffects |
CFM_OFFSET | Поле yOffset |
CFM_PROTECTED | Значение CFE_PROTECTED поля dwEffects |
CFM_SIZE | Поле yHeight |
CFM_STRIKEOUT | Значение CFE_STRIKEOUT поля dwEffects |
CFM_UNDERLINE . | Значение CFE_UNDERLINE поля dwEffects |
Если же вы применяете структуру CHARFORMAT для определения форматирования, в поле dwMask будут записаны маски для тех полей, в которых были занесены полученные значения.
В поле dwEffects может находиться комбинация следующих значений (объединенных при помощи логической операции ИЛИ):
Значение | Описание |
CFE_AUTOCOLOR | Для отображения текста используется системный цвет COLOR_WINDOWTEXT |
CFE_BOLD | Символы выделены жирным шрифтом (bold) |
CFE_ITALIC | Символы выделены наклоном (italic) |
CFE_STRIKEOUT | Символы перечеркнуты |
CFE_UNDERLINE | Символы выделены подчеркиванием |
CFE_PROTECTED | Данная группа символов защищена от изменения. Если пользователь пытается их изменить, родительское окно получит извещение с кодом EN_PROTECTED |
Заметьте, что установив атрибут оформления CFE_PROTECTED, вы можете защитить часть текста от изменений со стороны пользователя, что может быть удобно при создании специализированных редакторов текста.
Опишем кратко остальные поля структуры CHARFORMAT.
Поле yHeight содержит высоту символов в логических единицах, соответствующих выбранному режиму отображения.
Поле yOffset содержит смещение символов от базовой линии. Смещение может быть положительное (например, для надстрочных индексов) или отрицательное (для подстрочных индексов).
В поле crTextColor заносится цвет символов. Подробное обсуждение структуры COLORREF и структуры LOGFONT вы сможете найти в 14 томе "Библиотеки системного программиста", который называется "Графический интерфейс GDI в Microsoft Windows".
В поле bCharSet может находиться одно из значений, которое определено для поля lfCharSet структуры LOGFONT :
Константа | Описание |
ANSI_CHARSET | Набор символов в кодировке ANSI |
DEFAULT_CHARSET | Не используется при отображении шрифтов. Определяется при необходимости запросить шрифт с заданным именем и размером шрифта. Следует использовать с осторожностью, так как если указанного шрифта нет, GDI может выделить шрифт с любым набором символов |
SYMBOL_CHARSET | Символьный шрифт, такой как, например, Wingdings |
SHIFTJIS_CHARSET | Шрифт, в котором для представления символов используется двухбайтовая кодировка. Нужен для работы с японской версией Windows |
OEM_CHARSET | Набор символов в кодировке OEM |
С помощью поля bPitchAndFamily можно определить, используется ли фиксированная или переменна ширина символов. Кроме этого, можно определить семейство, к которому должен принадлежать полученный шрифт.
Фиксированная или переменная ширина символов задается при помощи следующих констант:
Константа | Описание |
DEFAULT_PITCH | Не имеет значения, будет ли шрифт иметь фиксированную или переменную ширину символов |
FIXED_PITCH | Нужен шрифт с фиксированной шириной символов |
VARIABLE_PITCH | Нужен шрифт с переменной шириной символов |
Вы можете объединить при помощи логической операции ИЛИ эти константы со следующими константами, соответствующими семейству шрифта:
Константа | Описание |
FF_DECORATIVE | Шрифт, содержащий маленькие рисунки (пиктограммы). Примером такого шрифта может послужить шрифт Wingdings, поставляемый в составе Windows |
FF_DONTCARE | Семейство шрифта не имеет значения |
FF_MODERN | Семейство Modern. Фиксированная ширина символов, могут быть засечки (но могут и не быть) |
FF_ROMAN | Семейство Roman. Переменная ширина букв, есть засечки |
FF_SCRIPT | Семейство Script. Рукописный шрифт |
FF_SWISS | Семейство Swiss. Переменная ширина букв, нет засечек |
Поле szFaceName содержит строку, закрытую двоичным нулем, которая служит названием внешнего вида шрифта. Размер строки (включая закрывающий строку нуль) не должен превышать LF_FACESIZE байт.
В нашем приложении после определения текущего оформления обработчик сообщения, поступающего от строки Bold меню Format, изменяет на противоположное содержимое бита CFE_BOLD в поле dwEffects структуры CHARFORMAT. Перед этим мы устанавливаем маску CFM_BOLD в поле dwMask этой же структуры:
cf.dwMask = CFM_BOLD; cf.dwEffects ^= CFE_BOLD; SendMessage(hwndEdit,EM_SETCHARFORMAT,SCF_SELECTION, (LPARAM)&cf);
Установка формата выполняется с помощью сообщения EM_SETCHARFORMAT. Это сообщение имеет следующие параметры:
wParam = (WPARAM)(UINT)uFlags; lParam = (LPARAM)(CHARFORMAT FAR *)lpFmt;
Параметр uFlags может принимать значения SCF_SELECTION или (SCF_SELECTION | SCF_WORD). В первом случае будет установлен формат выделенного фрагмента текста или формат, используемый по умолчанию (если выделение отсутствует). Если же дополнительно указано значение SCF_WORD, будет изменен формат слова, в позиции которого находится курсор, или формат выделенной группы слов.
Параметр lpFmt должен указывать на предварительно подготовленную структуру типа CHARFORMAT, которая только что была нами описана.
Format/Italic
При выборе строки Italic из меню Format выполняется оформление с использованием наклонного начертания символов. При этом применяются только что описанные сообщения и структуры данных.
Format/Underline
Эта строка меню выделяет символы подчеркиванием. Соответствующий обработчик аналогичен тому, что вызывается для предыдущих двух строк меню Format.
Format/Font
Замечательной особенностью органа управления Rich Edit является возможность выбора для оформления символов любого шрифта. В нашем приложении пользователь может при помощи строки Font меню Format вызвать на экран стандартную диалоговую панель, с помощью которой можно выбрать шрифт.
Вначале соответствующий обработчик определяет текущее оформление символов, посылая органу управления Rich Edit сообщение EM_GETCHARFORMAT.
Затем определяется текущий контекст отображения и заполняется структура chfnt типа CHOOSEFONT, которая нужна для функции вызова стандартной диалоговой панели ChooseFont. Эта функция была описана в 14 томе "Библиотеки системного программиста", поэтому для экономии места мы не будем к ней возвращаться.
После выбора шрифта его атрибуты копируются в структуру cf типа CHARFORMAT. Адрес этой структуры передается в качестве последнего параметра функции SendMessage, посылающей окну органа управления Rich Edit сообщение EM_SETCHARFORMAT :
SendMessage(hwndEdit, EM_SETCHARFORMAT, SCF_SELECTION, (LPARAM)&cf);
После установки нового шрифта обработчик освобождает полученный ранее контекст отображения.
Format/Paragraph/Left
Для изменения оформления параграфа наше приложение посылает окну органа управления Rich Edit сообщение EM_SETPARAFORMAT :
pf.cbSize = sizeof(pf); pf.dwMask = PFM_ALIGNMENT; pf.wAlignment = PFA_LEFT; SendMessage(hwndEdit, EM_SETPARAFORMAT, 0, (LPARAM)&pf);
Параметр wParam этого сообщения не используется и должен быть равен нулю. Через параметр lParam передается адрес предварительно заполненной структуры типа PARAFORMAT:
lParam = (LPARAM)(PARAFORMAT FAR *)lpFmt;
Структура PARAFORMAT определена следующим образом:
typedef struct _paraformat { UINT cbSize; // размер структуры в байтах _WPAD _wPad1; // зарезервировано DWORD dwMask; // поле масок WORD wNumbering; // порядок нумерации WORD wReserved; // зарезервировано LONG dxStartIndent; // отступ для первой строки параграфа LONG dxRightIndent; // отступ от правой границы листа LONG dxOffset; // отступ второй и следующих // строк параграфа WORD wAlignment; // выравнивание параграфа SHORT cTabCount; // количество символов табуляции LONG rgxTabs[MAX_TAB_STOPS];// массив абсолютных позиций // для символов табуляции } PARAFORMAT;
Поле маски dwMask определяет, какие из остальных полей структуры PARAFORMAT будут использованы для установки формата параграфа. В этом поле могут находиться следующие значения:
Значение маски | Поля структуры PARAFORMAT |
PFM_ALIGNMENT | wAlignment |
PFM_NUMBERING | wNumbering |
PFM_OFFSET | dxOffset |
PFM_OFFSETINDENT | dxStartIndent |
PFM_RIGHTINDENT | dxRightIndent |
PFM_STARTINDENT | dxStartIndent |
PFM_TABSTOPS | cTabStobs и rgxTabStops |
Для поля wNumbering вы можете указать нулевое значение или константу PFN_BULLET .
Поле wAlignment структуры PARAFORMAT задает выравнивание параграфа. Вы можете указать здесь следующие значения:
Значение | Тип выравнивания параграфа |
PFA_LEFT | По левой границе окна редактирования |
PFA_RIGHT | По правой границе окна редактирования |
PFA_CENTER | Центрирование |
Format/Paragraph/Right
Эта строка меню предназначена для установки выравнивания параграфа по правой границе. Примененный в нашем приложении способ выполнения выравнивания был только что описан.
Format/Paragraph/Center
Выравнивание по центру выполняется аналогично выравниванию по левой и правой границе окна редактирования.
Edit/Undo
Edit/Cut
Edit/Copy
Edit/Paste
Edit/Delete
Обработка сообщений от строк Undo, Cut, Copy, Paste и Delete выполняется очень просто. Органу управления Rich Edit посылается соответствующее сообщение: EM_UNDO , WM_CUT , WM_COPY , WM_PASTE или WM_CLEAR . Например:
case ID_EDIT_UNDO: SendMessage(hwndEdit, EM_UNDO, 0, 0L); return 0L; break;
Edit/Select all
В некоторых случаях удобно выполнять какую-либо операцию со всем текстом сразу. Для того чтобы можно было выделить весь текст, органу управления Rich Text посылается сообщение EM_EXSETSEL :
CHARRANGE charr; charr.cpMin = 0; // от начала... charr.cpMax = -1; // ... и до конца текста SendMessage(hwndEdit, EM_EXSETSEL, 0, (LPARAM)&charr);
Через параметр lParam этого сообщения необходимо передать адрес заполненной структуры типа CHARRANGE. Формат этой структуры приведен ниже:
typedef struct _charrange { LONG cpMin; // номер первого выделяемого символа LONG cpMax; // номер последнего выделяемого символа } CHARRANGE;
Для того чтобы выделить весь текст, в поле cpMin необходимо записать нулевое значение, а в поле cpMax - значение -1.
File/New
Когда пользователь выбирает строку New из меню File, содержимое редактора удаляется простейшим способом - при помощи функции SetWindowText :
SetWindowText(hwndEdit,"\0");
Отметим, что в нашем приложении не выполняется проверка, было ли предварительно выполнено сохранение редактируемого текста. При необходимости вы сможете доработать исходные тексты приложения, сделав его более "безопасным". Например, вы можете организовать обработку извещения EN_CHANGE , которое посылается, если пользователь изменяет содержимое редактируемого текста.
File/Open
Обработчик сообщения от строки Open меню File вызывает функцию FileOpen, определенную в нашем приложении. Эта функция позволяет загружать для редактирования обычные текстовые файлы или файлы в формате RTF .
Функция FileOpen будет описана немного позже.
File/Save as
Аналогично, при выборе пользователем строки Save as из меню File, вызывается функция FileSaveAs, которая позволяет сохранить файл в обычном текстовом формате или в формате RTF. Позже мы рассмотрим исходный текст этой функции.
File/Print
Строка Print меню File позволяет пользователю распечатать содержимое редактируемого текста. Печать выполняется функцией FilePrint, определенной в нашем приложении. О ней мы расскажем позже.
File/Exit
С помощью строки Exit меню File пользователь может завершить работу приложения.
Help/About
Эта строка выдает некоторую информацию о приложении RtfPad.
WndProc_OnSize
Приложение изменяет размеры органа управления Rich Edit таким образом, чтобы они всегда соответствовали размерам внутренней области главного окна приложения. Для этого оно обрабатывает сообщение WM_SIZE , изменяя размеры окна органа управления при помощи функции MoveWindow.
WndProc_OnSetFocus
Когда главное окно приложения RtfPad получает фокус ввода, она передает его органу управления Rich Edit, вызывая для этого функцию SetFocus :
SetFocus(hwndEdit);
FileSaveAs
Когда пользователь сохраняет редактируемый текст в файле, приложение вызывает функцию FileSaveAs. Эта функция выводит на экран стандартную диалоговую панель Save as, вызывая для этого функцию GetSaveFileName . Функцией GetSaveFileName мы уже пользовались, например, в приложении TEDIT, описанном в 12 томе "Библиотеки системного программиста".
Когда пользователь выберет файл, путь к нему будет записан в переменную ofn.lpstrFile. Затем этот файл будет открыт для записи функцией OpenFile . Хотя в Microsoft Windows 95 и в Microsoft Windows NT существуют специальные средства для работы с файлами, пока мы используем этот хорошо знакомый вам способ, который тоже работает.
После того как файл будет открыт, начнется процесс записи. Он несколько необычен, так как использует механизм обратного вызова функции, выполняющей запись данных в файл.
Инициирование процесса записи выполняется посылкой сообщения EM_STREAMOUT , причем в зависимости от нужного формата выходного файла (текстовый формат или формат RTF) для этого сообщения указывается разное значение параметра wParam:
_strupr(&ofn.lpstrFile[ofn.nFileExtension]); if(!strncmp(&ofn.lpstrFile[ofn.nFileExtension], "RTF", 3)) SendMessage(hwndEdit, EM_STREAMOUT, SF_RTF, (LPARAM)&es); else SendMessage(hwndEdit, EM_STREAMOUT, SF_TEXT, (LPARAM)&es);
В любом случае через параметр lParam необходимо передать адрес предварительно подготовленной структуры типа EDITSTREAM , которая определена следующим образом:
typedef struct _editstream { DWORD dwCookie; DWORD dwError; EDITSTREAMCALLBACK pfnCallback; } EDITSTREAM;
Через параметр dwCookie можно передать функции обратного вызова любое 32-разрядное значение. Так как в нашем случае функция обратного вызова будет выполнять запись в файл, через этот параметр мы передаем идентификатор открытого файла:
es.dwCookie = (DWORD)hFile; es.dwError = 0; es.pfnCallback = SaveCallback;
Через поле dwError передается код ошибки, поэтому перед началом процесса записи мы записываем в него нулевое значение.
И, наконец, через поле pfnCallback необходимо передать адрес функции обратного вызова, которая будет выполнять запись данных в файл. Наша функция называется SaveCallback; вы можете выбрать любое другое имя. Функция обратного вызова SaveCallback будет описана ниже.
После того как функция SendMessage возвратит управление, выходной файл следует закрыть.
Дополнительно мы сбрасываем признак модификации редактируемого файла, так как все изменения были только что сохранены. Для этого мы посылаем органу управления Rich Edit сообщение EM_SETMODIFY :
SendMessage(hwndEdit, EM_SETMODIFY, FALSE, 0L);
Если параметр wParam этого сообщения имеет значение FALSE, флаг модификации сбрасывается, если TRUE - устанавливается.
В любой момент времени вы можете определить значение флага модификации, послав органу управления Rich Edit сообщение EM_GETMODIFY . Если текст был изменен, функция SendMessage вернет значение TRUE, если нет - FALSE.
SaveCallback
Функция обратного вызова SaveCallback, которая используется для сохранения текста в файле, имеет следующий прототип:
DWORD CALLBACK SaveCallback(DWORD dwCookie, LPBYTE pbBuff, LONG cb, LONG *pcb);
Через первый параметр этой функции передается значение, которое было записано в поле dwCookie структуры EDITSTREAM, подготовленной для сообщения EM_STREAMOUT.
Через параметр pbBuff передается адрес буфера, откуда необходимо взять данные для записи в файл. Размер этого буфера указывается параметром cb. Количество действительно записанных байт следует записать по адресу, который передается через последний параметр pcb.
Если все нужные данные были записаны, функция обратного вызова должна вернуть нулевое значение, в противном случае для продолжения процесса записи следует возвратить значение, отличное от нуля.
FileOpen
Функция FileOpen вызывается в том случае, когда пользователь загружает новый файл для редактирования.
Выбор файла выполняется с помощью стандартной диалоговой панели Open, которая отображается на экране функцией GetOpenFileName . Эта функция использовалась нами в 12 томе "Библиотеки системного программиста".
После того как файл выбран, он открывается функцией OpenFile для чтения. Далее мы подготавливаем структуру типа EDITSTREAM для функции обратного вызова OpenCallback, которая будет выполнять чтение файла.
Затем, в зависимости от расширения имени выбранного файла, мы загружаем этот файл как текстовый или как файл, имеющий формат RTF. Для загрузки органу управления Rich Edit посылается сообщение EM_STREAMIN .
После выполнения чтения файл закрывается, а флаг модификации содержимого редактора текста сбрасывается при помощи сообщения EM_SETMODIFY .
OpenCallback
Функция обратного вызова OpenCallback выполняет чтение файла в память органа управления Rich Edit. Прототип этой функции уже был описан, однако теперь назначение некоторых ее параметров и возвращаемое значение изменились.
Через параметр dwCookie передается идентификатор файла, из которого будет выполняться чтение. Чтение данных необходимо выполнять в буфер pbBuff, размер которого передается через параметр cb. По адресу pcb необходимо записать количество действительно прочитанных байт или 0, если достигнут конец файла.
Функция обратного вызова должна возвратить количество прочитанных байт или 0, если был достигнут конец файла. Так как мы читаем весь файл за один вызов функции _lread, то функция возвращает нулевое значение.
FilePrint
Функция FilePrint, как видно из ее названия, выполняет печать файла. При этом в приложении RtfPad используется упрощенный вариант технологии, описанной нами в 14 томе "Библиотеки системного программиста".
Функция выводит на экран стандартную диалоговую панель Print, вызывая для этого функцию PrintDlg . Кроме всего прочего, эта функция получает контекст устройства для выбранного принтера, который сохраняется в переменной hPrintDC.
Как вы знаете, процесс печати из приложений Microsoft Windows полностью отличается от того, к чему вы, возможно, привыкли в DOS. Печать выполняется теми же функциями GDI , с помощью которых вы рисуете изображение на экране видеомонитора.
Орган управления Rich Edit способен самостоятельно выполнять печать, если известен контекст устройства печати (принтера). Для этого ему достаточно переслать сообщение EM_FORMATRANGE . Это сообщение имеет следующие параметры:
wParam = (WPARAM)(BOOL)fRender; lParam = (LPARAM)(FORMATRANGE FAR *)lpFmt;
Если значение параметра fRender равно TRUE, орган управления Rich Edit выполняет форматирование текста и его печать, если же FALSE - выполняется только форматирование с целью определения геометрических размеров, которые будут получены при печати.
Через параметр lpFmt должен передаваться указатель на предварительно подготовленную структуру типа FORMATRANGE , определенную следующим образом:
typedef struct _formatrange { HDC hdc; HDC hdcTarget; RECT rc; RECT rcPage; CHARRANGE chrg; } FORMATRANGE;
В поле hdc необходимо записать идентификатор контекста устройства, на котором будет выполняться отображение (печать). Поле hdcTarget должно содержать идентификатор контекста устройства, для которого необходимо выполнить форматирование. Наше приложение записывает в эти поля идентификатор контекста принтера, полученный при вызове функции PrintDlg .
В структуру rc следует записать размеры области, в которую будет выполняться вывод, а в структуру rcPage - размеры листа бумаги. Наше приложение оставляет небольшие поля по краям листа. Размеры должны быть указаны в TWIPS -ах (один TWIPS составляет 1/1440 часть дюйма).
Структура chrg описывает фрагмент текста, который должен быть выведен на печать. Приложение RtfPad выводит текст целиком, однако при необходимости вы сможете организовать печать только выделенного фрагмента или текущего листа. Оставляем вам для упражнения реализацию этих возможностей.
Операционная система Windows 95 для программиста
Теперь настало время проверить работу органов управления Toolbar и Statusbar на практике. Для этого мы предлагаем вам ознакомится с исходными текстами приложения Smart Application, внешний вид главного окна которого показан на рис. 2.3.
Главное окно приложения Smart Application
В этом приложении есть меню, орган управления Toolbar с функциями Tool Tip, кнопки которого дублируют некоторые строки меню, а также окно Statusbar, разделенное на три области. Первая область используется для отображения текстового описания строк меню, вторая не используется, а в третьей мы нарисовали маленькие часы. В качестве упражнения вы можете попытаться их запустить.
Если сделать двойной щелчок левой клавишей мыши по окну Toolbar , на экране появится диалоговая панель настройки Customize Toolbar, показанная на рис. 2.4.
Диалоговая панель настройки Customize Toolbar
С помощью этой диалоговой панели вы можете перемещать кнопки из списка Toolbar buttons в список Available buttons и обратно. Первый список содержит все кнопки, отображаемые в настоящий момент в окне Toolbar, второй при первом отображении диалоговой панели содержит только разделитель Separator.
Выделяя кнопки из списка Toolbar buttons и нажимая кнопку Add, вы можете их скрыть. Скрытые кнопки появятся в списке Available buttons. Впоследствии вы сможете их вставить на прежнее или другое место окна Toolbar.
Кнопки Move Up и Move Down предназначены для перемещения кнопок по окну Toolbar . Впрочем, такое перемещение можно выполнить левой клавишей мыши, нажав клавишу Shift и установив курсор мыши на нужную кнопку.
Кнопки Help и Reset никак не задействованы в нашем приложении, однако вы и сами легко сможете найти для них применение. Достаточно предусмотреть обработку извещений TBN_CUSTHELP и TBN_RESET , описанных нами ранее.
Заметим, что диалоговая панель Customize Toolbar не определена в ресурсах приложения Smart Application, а встроена в орган управления Toolbar. Так что мы пользуемся готовым средством настройки Toolbar, а не создаем свое.
Исходные тексты приложения Smart Application
Основной файл исходных текстов приложения Smart Application представлен в листинге 2.1.
Листинг 2.1. Файл smart\smart.c
#define STRICT #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" #include "afxres.h" #include "smart.h"
// ----------------------------------------------------- // Глобальные переменные // ----------------------------------------------------- HINSTANCE hInst; char szAppName[] = "Smart"; char szAppTitle[] = "Smart Application";
HWND hwndTb; // идентификатор Toolbar HWND hwndSb; // идентификатор Statusbar int ptWidth[3]; // таблица ширин для Statusbar HBITMAP hSbLogoBmp; // изображение для Statusbar
// Идентификаторы строк описания временных меню UINT nIdPopup[] = { IDS_FILEMENU, IDS_EDITMENU, IDS_HELPMENU };
// Описание кнопок Toolbar TBBUTTON tbButtons[] = { { 0, ID_FILE_NEW, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0L, 0}, { 1, ID_FILE_OPEN, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0L, 0}, { 2, ID_FILE_SAVE, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0L, 0}, { 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0L, 0}, { 3, ID_EDIT_CUT, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0L, 0}, { 4, ID_EDIT_COPY, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0L, 0}, { 5, ID_EDIT_PASTE, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0L, 0}, { 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0L, 0}, { 6, ID_FILE_PRINT, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0L, 0}, { 0, 0, TBSTATE_ENABLED, TBSTYLE_SEP, 0L, 0}, { 7, ID_HELP_ABOUT, TBSTATE_ENABLED, TBSTYLE_BUTTON, 0L, 0} };
// ----------------------------------------------------- // Функция WinMain // ----------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg;
hInst = hInstance;
// Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return FALSE; }
// Регистрируем класс окна memset(&wc, 0, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0); wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU); wc.lpszClassName = szAppName; if(!RegisterClassEx(&wc)) if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE;
// Инициализируем библиотеку стандартных органов управления InitCommonControls();
// Создаем главное окно приложения hWnd=CreateWindow(szAppName,szAppTitle,WS_OVERLAPPEDWINDOW, CW_USEDEFAULT,0,CW_USEDEFAULT,0,NULL, NULL, hInst, NULL); if(!hWnd) return(FALSE);
// Отображаем окно и запускаем цикл обработки сообщений ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while(GetMessage (&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
// ----------------------------------------------------- // Функция WndProc // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate); HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy); HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand); HANDLE_MSG(hWnd, WM_NOTIFY, WndProc_OnNotify); HANDLE_MSG(hWnd, WM_SIZE, WndProc_OnSize); HANDLE_MSG(hWnd, WM_DRAWITEM, WndProc_OnDrawItem); HANDLE_MSG(hWnd, WM_MENUSELECT, WndProc_OnMenuSelect);
default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } }
// ----------------------------------------------------- // Функция WndProc_OnCreate // ----------------------------------------------------- BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { // Создаем Toolbar hwndTb = CreateToolbarEx(hWnd, WS_CHILD | WS_BORDER | WS_VISIBLE | TBSTYLE_TOOLTIPS | CCS_ADJUSTABLE, IDT_TOOLBAR, // идентификатор органа Toolbar 8, // количество пиктограмм hInst, // идентификатор приложения IDB_TBBITMAP,// идентификатор битового изображения // кнопок (LPCTBBUTTON)&tbButtons,// адрес описания кнопок 11, // количество кнопок 16,16, // ширина и высота кнопок 16,16, // ширина и высота пиктограмм sizeof(TBBUTTON)); // размер структуры в байтах
if(hwndTb == NULL) return FALSE;
// Создаем Statusbar hwndSb = CreateWindowEx( 0L, // расширенный стиль окна STATUSCLASSNAME, // класс окна для Statusbar "", // заголовок окна отсутствует WS_CHILD | WS_BORDER | // стиль окна WS_VISIBLE | SBARS_SIZEGRIP, 0, 0, 0, 0, // координаты, ширина, высота hWnd, // идентификатор родительского окна (HMENU)IDS_STATUSBAR, // идентификатор Statusbar hInst, // идентификатор приложения NULL ); // доп. данные для окна
if(hwndSb == NULL) return FALSE;
// Делим Statusbar на 3 области SendMessage(hwndSb, SB_SETPARTS, 3, (LPARAM)ptWidth);
// Инициализируем области Statusbar SendMessage(hwndSb, SB_SETTEXT, 0, (LPARAM)""); SendMessage(hwndSb, SB_SETTEXT, 1 | SBT_NOBORDERS, (LPARAM)"");
// Загружаем изображение, которое будет нарисовано // в третьей части Statusbar hSbLogoBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_SBLOGO));
if(hSbLogoBmp== NULL) return FALSE;
// Рисуем это изображение SendMessage(hwndSb, SB_SETTEXT, 2 | SBT_OWNERDRAW, (LPARAM)hSbLogoBmp);
return TRUE; }
// ----------------------------------------------------- // Функция WndProc_OnDestroy // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnDestroy(HWND hWnd) { PostQuitMessage(0); return FORWARD_WM_DESTROY(hWnd, DefWindowProc); }
// ----------------------------------------------------- // Функция WndProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { switch (id) { case ID_FILE_EXIT: PostQuitMessage(0); break;
case ID_FILE_NEW: case ID_FILE_OPEN: case ID_FILE_SAVE: case ID_FILE_SAVEAS: case ID_FILE_PRINT: case ID_EDIT_CUT: case ID_EDIT_COPY: case ID_EDIT_PASTE: case ID_HELP_ABOUT:
default: MessageBox(NULL,"Command stub","Smart Application",MB_OK); } return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc); }
// ----------------------------------------------------- // Функция WndProc_OnNotify // ----------------------------------------------------- LRESULT WndProc_OnNotify(HWND hWnd, int idFrom, NMHDR* pnmhdr) { LPTOOLTIPTEXT lpToolTipText; LPTBNOTIFY lptbn; int nItem; static CHAR szBuf[128];
switch(pnmhdr->code) { // Если получили сообщение от ToolTips, загружаем из // ресурсов соответствующую текстовую строку case TTN_NEEDTEXT: lpToolTipText = (LPTOOLTIPTEXT)pnmhdr; LoadString(hInst, lpToolTipText->hdr.idFrom, szBuf, sizeof(szBuf)); lpToolTipText->lpszText = szBuf; break;
// Возвращаем окну Toolbar характеристики кнопки, // с номером, заданным в lptbn->iItem case TBN_GETBUTTONINFO: lptbn = (LPTBNOTIFY)pnmhdr; nItem = lptbn->iItem; lptbn->tbButton.iBitmap = tbButtons[nItem].iBitmap; lptbn->tbButton.idCommand = tbButtons[nItem].idCommand; lptbn->tbButton.fsState = tbButtons[nItem].fsState; lptbn->tbButton.fsStyle = tbButtons[nItem].fsStyle; lptbn->tbButton.dwData = tbButtons[nItem].dwData; lptbn->tbButton.iString = tbButtons[nItem].iString;
// Если запрашиваются характеристики несуществующей // кнопки, возвращаем FALSE return((nItem < sizeof(tbButtons)/sizeof(tbButtons[0]))? TRUE : FALSE); break;
// Разрешаем удаление любой кнопки, кроме самой первой case TBN_QUERYDELETE: lptbn = (LPTBNOTIFY)pnmhdr; nItem = lptbn->iItem; return (nItem == 0)? FALSE : TRUE; break;
// Разрешаем вставку любой кнопки, кроме самой первой case TBN_QUERYINSERT: lptbn = (LPTBNOTIFY)pnmhdr; nItem = lptbn->iItem; return (nItem == 0)? FALSE : TRUE; break;
// В ответ на завершение операции перемещения // перерисовываем Toolbar case TBN_TOOLBARCHANGE: SendMessage(hwndTb, TB_AUTOSIZE, 0L, 0L); return TRUE; break;
default: break; } return FALSE; }
// ----------------------------------------------------- // Функция WndProc_OnSize // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnSize(HWND hwnd, UINT state, int cx, int cy) { // Изменяем размеры Toolbar и Statusbar в соответствии с // новыми размерами окна SendMessage(hwndTb, WM_SIZE, cx, cy); SendMessage(hwndSb, WM_SIZE, cx, cy);
// Рассчитываем размеры областей Statusbar ptWidth[0] = cx/2; ptWidth[1] = cx/2 + cx/4; ptWidth[2] = -1;
// Устанавливаем новые размеры Statusbar SendMessage(hwndSb, SB_SETPARTS, 3, (LPARAM)ptWidth);
return FORWARD_WM_SIZE(hwnd, state, cx, cy, DefWindowProc); }
// ----------------------------------------------------- // Функция WndProc_OnDrawItem // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnDrawItem(HWND hwnd, const DRAWITEMSTRUCT * lpDrawItem) { // Необходимо выполнить перерисовку области Statusbar, // которая была определена как SBT_OWNERDRAW if(lpDrawItem->CtlID == IDS_STATUSBAR) { LPDRAWITEMSTRUCT lpDis; HDC hdcMem; HBITMAP hbmOld; BITMAP bm;
// Рисуем битовое изображение внутри области lpDis = (LPDRAWITEMSTRUCT)lpDrawItem; hdcMem = CreateCompatibleDC(lpDis->hDC);
hbmOld = SelectObject(hdcMem, hSbLogoBmp); GetObject(hSbLogoBmp, sizeof(bm), &bm);
BitBlt(lpDis->hDC, lpDis->rcItem.left, lpDis->rcItem.top, bm.bmWidth, bm.bmHeight, hdcMem, 0, 0, SRCCOPY);
SelectObject(hdcMem, hbmOld); DeleteDC(hdcMem); }
return FORWARD_WM_DRAWITEM(hwnd, lpDrawItem, DefWindowProc); }
// ----------------------------------------------------- // Функция WndProc_OnMenuSelect // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnMenuSelect(HWND hwnd, HMENU hmenu, int item, HMENU hmenuPopup, UINT flags) { static char szBuf[128]; UINT nStrID = 0; szBuf[0] = 0;
// Пользователь закрыл меню if(flags == 0xffffffff && hmenu == NULL) nStrID = IDS_DESCRIPTION;
// Пользователь выделил в меню строку разделителя else if(flags & MFT_SEPARATOR) nStrID = 0;
// Выбрано временное меню else if(flags & MF_POPUP) { // Выбрано системное меню if(flags & MF_SYSMENU) nStrID = IDS_SYSMENU;
// Вычисляем идентификатор строки, описывающей // временное меню else nStrID = ((item < sizeof(nIdPopup)/sizeof(nIdPopup[0]))? nIdPopup[item] : 0); }
// Используем идентификатор строки, соответствующий // выделенной строке меню else nStrID = item;
// Загружаем строку из ресурсов приложения if(nStrID != 0) LoadString(hInst, nStrID, szBuf, sizeof(szBuf));
// Отображаем строку в первой области Toolbar SendMessage(hwndSb, SB_SETTEXT, 0, (LPARAM)szBuf);
return FORWARD_WM_MENUSELECT(hwnd, hmenu, item, hmenuPopup, flags, DefWindowProc); }
В листинге 2.2 мы привели файл smart.h, который содержит прототипы функций и определения констант.
Листинг 2.2. Файл smart\smart.h
// ----------------------------------------------------- // Описание функций // ----------------------------------------------------- LRESULT WINAPI WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct); void WndProc_OnDestroy(HWND hWnd); void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); LRESULT WndProc_OnNotify(HWND hWnd, int idFrom, NMHDR FAR * pnmhdr); void WndProc_OnSize(HWND hwnd, UINT state, int cx, int cy); void WndProc_OnDrawItem(HWND hwnd, const DRAWITEMSTRUCT * lpDrawItem); void WndProc_OnMenuSelect(HWND hwnd, HMENU hmenu, int item, HMENU hmenuPopup, UINT flags);
#define IDT_TOOLBAR 801 #define IDS_STATUSBAR 802 #define IDS_SCSIZE SC_SIZE #define IDS_SCMOVE SC_MOVE #define IDS_SCMINIMIZE SC_MINIMIZE #define IDS_SCCLOSE SC_CLOSE #define IDS_SCRESTORE SC_RESTORE #define IDS_SCTASKLIST SC_TASKLIST #define IDS_SCMAXIMIZE SC_MAXIMIZE
Файл resource.h (листинг 2.3) был создан автоматически системой разработки приложений Microsoft Visual C++. Он содержит определения констант, которые используются для доступа к ресурсам приложения Smart Application.
Листинг 2.3. Файл smart\resource.h
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by SMART.RC // #define IDI_APPICON 102 #define IDI_APPICONSM 103 #define IDR_APPMENU 104 #define IDB_TBBITMAP 105 #define IDB_SBLOGO 107 #define ID_FILE_EXIT 40001 #define ID_FILE_SAVEAS 40002 #define ID_HELP_ABOUT 40010 #define IDS_DESCRIPTION 57638 #define IDS_SYSMENU 57639 #define IDS_FILEMENU 57640 #define IDS_EDITMENU 57641 #define IDS_HELPMENU 57642 #define IDS_TEST 61745
// Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NO_MFC 1 #define _APS_3D_CONTROLS 1 #define _APS_NEXT_RESOURCE_VALUE 108 #define _APS_NEXT_COMMAND_VALUE 40013 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
И, наконец, файл smart.rc (листинг 2.4) содержит описание ресурсов приложения Smart Application. Как и предыдущий файл, он был создан автоматически системой разработки приложений Microsoft Visual C++.
Листинг 2.4. Файл smart\smart.rc
//Microsoft Visual C++ generated resource script. #include "resource.h" #define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 2 resource. #include "afxres.h" #include "smart.h" ////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Icon IDI_APPICON ICON DISCARDABLE "smart.ico" IDI_APPICONSM ICON DISCARDABLE "smartsm.ico"
#ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // TEXTINCLUDE 1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END
2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "#include ""smart.h""\0" END
3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END ////////////////////////////////////////////////////////////// #endif // APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Menu // IDR_APPMENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&New...", ID_FILE_NEW MENUITEM "&Open...", ID_FILE_OPEN MENUITEM "&Close", ID_FILE_CLOSE MENUITEM SEPARATOR MENUITEM "&Save", ID_FILE_SAVE MENUITEM "Save &As...", ID_FILE_SAVEAS MENUITEM SEPARATOR MENUITEM "&Print...", ID_FILE_PRINT MENUITEM SEPARATOR MENUITEM "E&xit", ID_FILE_EXIT END POPUP "&Edit" BEGIN MENUITEM "Cu&t", ID_EDIT_CUT MENUITEM "&Copy", ID_EDIT_COPY MENUITEM "&Paste", ID_EDIT_PASTE END POPUP "&Help" BEGIN MENUITEM "&About...", ID_HELP_ABOUT END END ////////////////////////////////////////////////////////////// // Bitmap // IDB_TBBITMAP BITMAP DISCARDABLE "toolbar.bmp" IDB_SBLOGO BITMAP DISCARDABLE "sblogo.bmp" ////////////////////////////////////////////////////////////// // String Table // STRINGTABLE DISCARDABLE BEGIN ID_FILE_NEW "Creates a new document" ID_FILE_OPEN "Open an existing document" ID_FILE_CLOSE "Closes the active document" ID_FILE_SAVE "Save the active document" ID_FILE_PRINT "Prints the active document" END STRINGTABLE DISCARDABLE BEGIN ID_FILE_EXIT "Exit application" ID_FILE_SAVEAS "Saves the active document under a different name" ID_HELP_ABOUT "Displays information about application" END STRINGTABLE DISCARDABLE BEGIN ID_EDIT_COPY "Copies the selection and puts it on the clipboard" ID_EDIT_CUT "Cuts the selection and puts it on the clipboard" ID_EDIT_PASTE "Inserts the clipboards contents at the insertion point" IDS_DESCRIPTION "Smart Application" IDS_SYSMENU "Move, size or close application window" IDS_FILEMENU "Create, open, save or print documents" IDS_EDITMENU "Delete, copy and insert" IDS_HELPMENU "Get help" END STRINGTABLE DISCARDABLE BEGIN IDS_SCSIZE "Changes the size of the window" END STRINGTABLE DISCARDABLE BEGIN IDS_SCMOVE "Changes the position of the window" END STRINGTABLE DISCARDABLE BEGIN IDS_SCMINIMIZE "Minimizes the window to an icon" END STRINGTABLE DISCARDABLE BEGIN IDS_SCCLOSE "Closes the window" END STRINGTABLE DISCARDABLE BEGIN IDS_SCRESTORE "Restores the window to normal size" END STRINGTABLE DISCARDABLE BEGIN IDS_SCTASKLIST "Switches to another application through the Windows Task Manager" END STRINGTABLE DISCARDABLE BEGIN IDS_SCMAXIMIZE "Enlarges the window to full size" END
#ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 3 resource. ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
Описание функций
Приведем краткое описание функций, определенных в файле smart.c приложения Smart Application.
WinMain
Функция WinMain сохраняет идентификатор приложения в переменной hInst и проверяет, было ли приложение Smart Application запущено ранее. Для этого используется функция FindWindow, с которой вы знакомы по предыдущему приложению Window Application.
После регистрации класса главного окна приложения функцией RegisterClassEx (или функцией RegisterClass, если приложение работает в среде Microsoft Windows NT версии 3.5).
Затем для инициализации библиотеки стандартных органов управления COMCTL32.DLL вызывается функция InitCommonControls .
Далее функция WinMain создает главное окно приложения и запускает цикл обработки сообщений обычным образом.
WndProc
Функция главного окна приложения Smart Application обрабатывает сообщения WM_CREATE, WM_DESTROY, WM_COMMAND, WM_NOTIFY, WM_SIZE, WM_DRAWITEM и WM_MENUSELECT. Для этого она использует макрокоманду HANDLE_MSG, передавая ей в качестве второго параметра адрес функции обработки сообщения.
WndProc_OnCreate
Эта функция обрабатывает сообщение WM_CREATE и, следовательно, вызывается при создании главного окна приложения. В задачу обработчика сообщения входит создание и инициализация органов управления Toolbar и Statusbar.
Орган управления Toolbar создается специально предназначенной для этого функцией CreateToolbarEx.
Для того чтобы пользователь смог получать краткую подсказку о назначении кнопок Toolbar , мы указали стиль TBSTYLE_TOOLTIPS . Кроме этого, был задан стиль CCS_ADJUSTABLE, позволяющий пользователю выполнять настройку Toolbar.
Так как между кнопками имеются промежутки, количество пиктограмм (восемь) больше, чем количество кнопок (одиннадцать). В массиве структур tbButtons находятся описания кнопок. Формат этих структур вы уже знаете.
Что же касается Statusbar, то этот орган управления в нашем примере создается при помощи функции CreateWindowEx с использованием стиля окна STATUSCLASSNAME . Для того чтобы правый нижний угол Statusbar можно было использовать для изменения размеров главного окна приложения, мы включили стиль SBARS_SIZEGRIP .
После создания Statusbar его окно разделяется на три области. Для этого окну Statusbar посылается сообщение SB_SETPARTS . Первые две области инициализируются пустыми строками при помощи сообщения SB_SETTEXT . Область с номером 1 отображается без рамки, так как этот номер скомбинирован при помощи логической операции ИЛИ с константой SBT_NOBORDERS .
Область с номером 2 используется для отображения небольшой картинки, загруженной из ресурса приложения с идентификатором IDB_SBLOGO. Поэтому номер области комбинируется с константой SBT_OWNERDRAW .
WndProc_OnDestroy
Эта функция обрабатывает сообщение WM_DESTROY, вызывая функцию PostQuitMessage. В результате приложение завершает свою работу.
WndProc_OnCommand
Задача функции WndProc_OnCommand - обработка сообщения WM_COMMAND, поступающего от меню приложения, а также от органа управления Toolbar .
Для всех идентификаторов, кроме ID_FILE_EXIT, в этой функции стоит "заглушка", которая выводит сообщение при помощи функции MessageBox. Если же пользователь выбирает строку Exit из меню File, функция обработки сообщения WM_COMMAND вызывает функцию PostQuitMessage для завершения работы приложения.
WndProc_OnNotify
Функция WndProc_OnNotify обрабатывает извещения, поступающие от органа управления Toolbar в виде сообщения WM_NOTIFY. Мы уже рассказывали вам о том, как нужно обрабатывать эти извещения.
Для примера наш обработчик извещений запрещает пользователю выполнять удаление и вставку самой первой кнопки в органе управления Toolbar .
WndProc_OnSize
Функция WndProc_OnSize обрабатывает сообщение WM_SIZE . Напомним, что это сообщение приходит при создании окна, а также при изменении его размеров.
Наш обработчик посылает сообщение WM_SIZE органам управления Toolbar и Statusbar. В ответ на это они изменяют свои размеры таким образом, чтобы расположиться, соответственно, в верхней и нижней части главного окна приложения.
Кроме этого, обработчик сообщения WM_SIZE рассчитывает размеры областей органа управления Statusbar, заполняя массив ptWidth.
Правая граница первой области располагается в середине окна Statusbar, а правая граница второй области - на расстоянии 3/4 длины окна Statusbar от его левой границы. Правая граница третьей области будет простираться до правой границы окна Statusbar.
Размеры устанавливаются при помощи сообщения SB_SETPARTS .
WndProc_OnDrawItem
Функция WndProc_OnDrawItem обрабатывает сообщение WM_DRAWITEM , которое посылается органом управления Statusbar при необходимости перерисовки третьей области его окна.
Соответствующая процедура рисует небольшое битовое изображение при помощи функции BitBlt, которая была нами подробно описана в 14 томе "Библиотеки системного программиста".
Идентификатор битового изображения передается через структуру типа DRAWITEMSTRUCT. Адрес структуры, в свою очередь, передается через параметр lParam сообщения WM_DRAWITEM.
WndProc_OnMenuSelect
Первая область окна Statusbar используется для отображения краткой подсказки о назначении строк системного меню, временных меню приложений и строк временных меню приложений. Для того чтобы проследить выделение строк перечисленных меню пользователем, в нашем приложении предусмотрен обработчик сообщения WM_MENUSELECT . Его роль выполняет функция WndProc_OnMenuSelect.
Обработчик сообщения WM_MENUSELECT анализирует переменные item, flags и hmenu, которые образуются из параметров сообщения следующим образом:
item = (UINT)LOWORD(wParam); flags = (UINT)HIWORD(wParam); hmenu = (HMENU)lParam;
Через 16-разрядный параметр item передается номер временного меню или идентификатор строки меню.
Параметр flags также 16-разрядный. Через него передается тип меню или тип строки. Например, константа MF_POPUP соответствует временному меню, константа MF_SYSMENU - системному меню, а константа MFT_SEPARATOR - разделительной строке во временном меню.
В результате анализа параметров обработчик сообщения записывает в переменную nStrID идентификатор текстовой строки из ресурсов приложения, содержащей нужную подсказку. Если такой строки нет, в переменную nStrID будет записано нулевое значение.
Когда пользователь закрывает меню, например, при помощи клавиши <Esc>, параметр flags содержит значение 0xffff. При этом в параметр hmenu (идентификатор меню) записывается значение NULL.
В параметрах функции WndProc_OnMenuSelect параметр item описан как имеющий тип int, поэтому его значение расширяется до 32-разрядного. Именно поэтому для того чтобы определить момент, когда пользователь закрыл меню, мы сравниваем его значение с константой 0xffffffff:
if(flags == 0xffffffff && hmenu == NULL) nStrID = IDS_DESCRIPTION;
Если пользователь открыл системное или временное меню, в переменную nStrID записывается, соответственно, идентификатор строки IDS_SYSMENU или идентификатор, взятый из массива nIdPopup. Массив nIdPopup содержит таблицу идентификаторов текстовых строк, предназначенных для краткого описания временных меню.
В том случае, когда пользователь открыл временное меню и перемещается по его строкам, в переменную nStrID записывается идентификатор соответствующей строки меню. В ресурсах приложения определены строки с идентификаторами, равными идентификаторам строк меню.
В том случае, когда нужная строка определена, она загружается из ресурсов приложения функцией LoadString и отображается в первой области окна Satusbar при помощи сообщения SB_SETTEXT .
Операционная система Windows 95 для программиста
Приложение Some Books создает дерево и отображает его в своем главном окне, как это показано на рис. 4.2.
Просмотр дерева в главном окне приложения Some Books
Вы можете открывать или закрывать отдельные уровни иерархии при помощи кнопок с изображением символов "+" и "-", а также выбирать любые элементы дерева. При выборе вложенных элементов нижней иерархии изображение закрытой книги заменяется на изображение открытой книги.
Исходные тексты приложения Some Books
Все функции приложения Some Books определены в файле somebook.c (листинг 4.1).
Листинг 4.1. Файл somebook\somebook.c
#define STRICT #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" #include "afxres.h" #include "somebook.h"
// Корневой элемент дерева char szBooks[] = "Книги издательства АО 'Диалог-МИФИ'";
// Вложенные элементы первого уровня char szBSPSeries[] = "Библиотека системного программиста, " "(C) Фролов А.В., Фролов Г.В., 1991-1995 г.";
char szStepSeries[] = "Персональный компьютер. Шаг за шагом, " "(C) Фролов А.В., Фролов Г.В., 1994-1995 г.";
typedef struct tagBOOKINFO { char szBookName[256]; UINT nVolumeNumber; } BOOKINFO;
// Вложенные элементы второго уровня BOOKINFO rgSPLBookInfo[]= { {"Microsoft Windows 3.1 для программиста", 11}, {"Графический интерфейс GDI в Microsoft Windows ", 14}, };
BOOKINFO rgStepBookInfo[]= { {"Операционная система Microsoft Windows. " "Руководство пользователя", 2}, {"Локальные сети персональных компьютеров", 3}, };
// Номера изображений для органа управления Tree View int idxBooks, idxBookClosed, idxBookOpened;
HINSTANCE hInst; char szAppName[] = "SomeBookApp"; char szAppTitle[] = "Some programmer and user books"; HWND hwndTree;
// ----------------------------------------------------- // Функция WinMain // ----------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg;
hInst = hInstance;
// Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return FALSE; }
// Регистрируем класс окна memset(&wc, 0, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0); wc.style = 0; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU); wc.lpszClassName = szAppName; if(!RegisterClassEx(&wc)) if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE;
// Создаем главное окно приложения hWnd = CreateWindow(szAppName, szAppTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL); if(!hWnd) return(FALSE);
// Отображаем окно и запускаем цикл обработки сообщений ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while(GetMessage (&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
// ----------------------------------------------------- // Функция WndProc // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate); HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy); HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand); HANDLE_MSG(hWnd, WM_SIZE, WndProc_OnSize);
default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } }
// ----------------------------------------------------- // Функция WndProc_OnCreate // ----------------------------------------------------- BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { RECT rc; HIMAGELIST himl; HBITMAP hBmp; HTREEITEM hRootItem; HTREEITEM hBSPItem; HTREEITEM hStepItem;
// Определяем размеры внутренней области главного окна GetClientRect(hWnd, &rc);
// Инициализируем библиотеку стандартных органов управления InitCommonControls();
// Создаем орган управления Tree View hwndTree = CreateWindowEx(0L, WC_TREEVIEW, "", WS_VISIBLE | WS_CHILD | WS_BORDER | TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT, 0, 0, rc.right - rc.left, rc.bottom - rc.top, hWnd, (HMENU) IDC_TREEVIEW, hInst, NULL);
if(hwndTree == NULL) return FALSE;
// Создаем список изображений himl = ImageList_Create(33, 33, FALSE, 3, 0);
// Добавляем изображения в список hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BOOKS)); idxBooks = ImageList_Add(himl, hBmp, NULL);
hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BOOK_CLOSED)); idxBookClosed = ImageList_Add(himl, hBmp, NULL);
hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BOOK_OPENED)); idxBookOpened = ImageList_Add(himl, hBmp, NULL);
// Добавляем список изображений TreeView_SetImageList(hwndTree, himl, TVSIL_NORMAL);
// Вставляем корневой элемент hRootItem = InsTreeItem((HTREEITEM)TVI_ROOT, szBooks, (HTREEITEM)TVI_FIRST, idxBooks, idxBooks);
// Вставляем вложенные элементы первого уровня hBSPItem = InsTreeItem(hRootItem, szBSPSeries, (HTREEITEM)TVI_SORT, idxBooks, idxBooks);
hStepItem = InsTreeItem(hRootItem, szStepSeries, (HTREEITEM)TVI_SORT, idxBooks, idxBooks);
// Вставляем вложенные элементы второго уровня InsTreeItem(hBSPItem, rgSPLBookInfo[0].szBookName, (HTREEITEM)TVI_SORT, idxBookClosed, idxBookOpened);
InsTreeItem(hBSPItem, rgSPLBookInfo[1].szBookName, (HTREEITEM)TVI_SORT, idxBookClosed, idxBookOpened);
InsTreeItem(hStepItem, rgStepBookInfo[0].szBookName, (HTREEITEM)TVI_SORT, idxBookClosed, idxBookOpened);
InsTreeItem(hStepItem, rgStepBookInfo[1].szBookName, (HTREEITEM)TVI_SORT, idxBookClosed, idxBookOpened);
return TRUE; }
// ----------------------------------------------------- // Функция InsTreeItem // ----------------------------------------------------- HTREEITEM InsTreeItem(HTREEITEM hParent, LPSTR szText, HTREEITEM hAfter, int iImage, int iSelectedImage) { TV_INSERTSTRUCT tvins; HTREEITEM hItem;
memset(&tvins, 0, sizeof(tvins));
tvins.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM; tvins.item.pszText = szText; tvins.item.cchTextMax = lstrlen(szText); tvins.item.iImage = iImage; tvins.item.iSelectedImage = iSelectedImage;
tvins.hInsertAfter = hAfter; tvins.hParent = hParent;
hItem = TreeView_InsertItem(hwndTree, &tvins); return hItem; }
// ----------------------------------------------------- // Функция WndProc_OnDestroy // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnDestroy(HWND hWnd) { DestroyWindow(hwndTree); PostQuitMessage(0); return 0L; }
// ----------------------------------------------------- // Функция WndProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { DWORD dwStyle = 0;
switch (id) { case ID_FILE_EXIT: PostQuitMessage(0); return 0L; break;
case ID_HELP_ABOUT: break;
default: break; } return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc); }
// ----------------------------------------------------- // Функция WndProc_OnSize // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnSize( HWND hwnd, UINT state, int cx, int cy) { MoveWindow(hwndTree, 0, 0, cx, cy, TRUE); return FORWARD_WM_SIZE(hwnd, state, cx, cy, DefWindowProc); }
Файл somebook.h (листинг 4.2) содержит писание функций и определение константы IDC_TREEVIEW (идентификатор органа управления Tree View).
Листинг 4.2. Файл somebook\somebook.h
// ----------------------------------------------------- // Описание функций // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct); void WndProc_OnDestroy(HWND hWnd); void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); LRESULT WndProc_OnNotify(HWND hWnd, int idFrom, NMHDR FAR * pnmhdr); void WndProc_OnSize(HWND hwnd, UINT state, int cx, int cy); void WndProc_OnDrawItem(HWND hwnd, const DRAWITEMSTRUCT * lpDrawItem); HTREEITEM InsTreeItem(HTREEITEM hParent, LPSTR szText, HTREEITEM hAfter, int iImage, int iSelectedImage); #define IDC_TREEVIEW 1235
В файле resource.h (который создается автоматически системой Microsoft Visual C++) находятся определения констант для работы с ресурсами приложения Some Books. Этот файл представлен в листинге 4.3.
Листинг 4.3. Файл somebook\resource.h
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by somebook.rc // #define IDR_APPMENU 102 #define IDI_APPICON 103 #define IDI_APPICONSM 104 #define IDB_BOOKS 115 #define IDB_BOOK_CLOSED 118 #define IDB_BOOK_OPENED 120 #define ID_FILE_EXIT 40001 #define ID_HELP_ABOUT 40003
// Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 121 #define _APS_NEXT_COMMAND_VALUE 40002 #define _APS_NEXT_CONTROL_VALUE 1000 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
Файл somebook.rc (листинг 4.4) содержит определение ресурсов приложения Some Books. Он создается автоматически.
Листинг 4.4. Файл somebook\somebook.rc
//Microsoft Visual C++ generated resource script. #include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h"
////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS
////////////////////////////////////////////////////////////// // Menu //
IDR_APPMENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", ID_FILE_EXIT END END
#ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // TEXTINCLUDE //
1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END
2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END
3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END
////////////////////////////////////////////////////////////// #endif // APSTUDIO_INVOKED
////////////////////////////////////////////////////////////// // Icon //
IDI_APPICON ICON DISCARDABLE "sbook.ico" IDI_APPICONSM ICON DISCARDABLE "sbooksm.ico"
////////////////////////////////////////////////////////////// // Bitmap //
IDB_BOOKS BITMAP DISCARDABLE "books.bmp" IDB_BOOK_CLOSED BITMAP DISCARDABLE "closed.bmp" IDB_BOOK_OPENED BITMAP DISCARDABLE "opened.bmp"
////////////////////////////////////////////////////////////// // String Table STRINGTABLE DISCARDABLE BEGIN ID_FILE_EXIT "Quits the application" END #ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 3 resource. // ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
В этом разделе мы кратко
В этом разделе мы кратко опишем глобальные переменные и функции приложения Some Books.
Глобальные переменные
В массиве szBooks находится текстовая строка, которая отображается как корневой элемент дерева (название библиотеки книг - "Книги издательства АО 'Диалог-МИФИ'").
Массивы szBSPSeries и szStepSeries содержат два вложенных элемента, которые являются дочерними для корневого элемента (названия двух серий книг, которые издаются в АО 'Диалог-МИФИ').
И, наконец, массивы rgSPLBookInfo и rgStepBookInfo хранят вложенные элементы самого нижнего уровня (названия некоторых книг, входящих в эти две серии). Эти элементы являются дочерними для элементов, заданных массивами szBSPSeries и szStepSeries.
В переменных idxBooks, idxBookClosed и idxBookOpened находятся номера изображений. Изображение с номером idxBooks используется для корневого элемента и элементов первого уровня вложенности. Изображения idxBookClosed и idxBookOpened (закрытая и раскрытая книга) нужны для элементов нижнего уровня иерархии (названий книг).
В переменной hInst хранится идентификатор приложения, полученный функцией WinMain. Строчные массивы szAppName и szAppTitle хранят, соответственно, имя и заголовок приложения.
Переменная hwndTree используется для хранения идентификатора созданного органа управления Tree View.
WinMain
Функция WinMain в приложении Some Books полностью аналогична функции WinMain предыдущего приложения. Она сохраняет идентификатор приложения и проверяет, не было ли это приложение запущено ранее. Если было, то активизируется окно работающего приложения.
Далее функция регистрирует класс главного окна приложения, создает и отображает это окно, а затем запускает цикл обработки сообщений.
WndProc
Функция WndProc обрабатывает следующие сообщения: WM_CREATE, WM_DESTROY, WM_COMMAND, WM_SIZE. Как и в предыдущих наших приложениях, обработка выполняется с использованием макрокоманды HANDLE_MSG.
WndProc_OnCreate
Эта функция обрабатывает сообщение WM_CREATE, создавая и инициализируя орган управления Tree View. Размеры окна органа управления устанавливаются равными размерам внутренней области главного окна приложения и в дальнейшем изменяются соответствующим образом обработчиком сообщения WM_SIZE.
В процессе инициализации функция WndProc_OnCreate создает список изображений, которые будут использованы для элементов дерева, и добавляет в этот список три изображения. При этом в глобальные переменные idxBooks, idxBookClosed и idxBookOpened записываются номера изображений. После этого список изображений подключается к органу управления Tree View.
Далее функция WndProc_OnCreate заполняет дерево, добавляя в него корневой элемент и вложенные элементы. Для этого она вызывает функцию InsTreeItem, описанную ниже.
InsTreeItem
Функция InsTreeItem добавляет элементы к дереву, вызывая для этого макрокоманду TreeView_InsertItem. Перед этим она инициализирует структуру tvins типа TV_INSERTSTRUCT, записывая в нее информацию о добавляемом элементе и его расположении в иерархии существующих элементов дерева.
Для каждого элемента, в частности, определяется пара номеров изображений iImage и iSelectedImage, которые используются для отображения элемента, соответственно, в невыбранном и выбранном состоянии.
WndProc_OnDestroy
Функция WndProc_OnDestroy вызывается, когда пользователь завершает работу приложения. Она уничтожает окно органа управления Tree View и останавливает цикл обработки сообщений, вызывая функцию PostQuitMessage.
WndProc_OnCommand
Эта функция обрабатывает сообщение WM_COMMAND, поступающее от главного меню приложения.
WndProc_OnSize
Приложение изменяет размеры органа управления Tree View таким образом, чтобы они всегда соответствовали размерам внутренней области главного окна приложения Some Books. Для этого оно обрабатывает сообщение WM_SIZE. Изменения размеров окна органа управления выполняется функцией WndProc_OnSize.
Операционная система Windows 95 для программиста
Несложное приложение UpDown демонстрирует, как это нетрудно догадаться из его названия, работу органа управления Up-Down. Дополнительно в этом приложении мы создаем комбинацию клавиш активизации, работаем с сообщением WM_SETHOTKEY и органом управления Hot Key.
Если выбрать строку Options из главного меню приложения File, на экране появится диалоговая панель Value Selection, показанная на рис. 7.9.
Диалоговая панель Value Selection
В этой панели вы можете задать с помощью органа управления Up-Down некоторое значение, и посмотреть результат, нажав кнопку OK.
В процессе инициализации своего главного окна приложение определяет комбинацию клавиш активизации Ctrl + Alt + V. Если нажать эту комбинацию клавиш, появится все та же диалоговая панель.
Для изменения комбинации клавиш активизации вы можете передать фокус ввода органу управления Hot Key, нажать новую комбинацию клавиш и затем нажать кнопку Set HotKey. После этого новая комбинация клавиш может быть использована для активизации диалоговой панели Value Selection.
Исходные тексты приложения UpDown
Исходные тексты всех функций приложения UpDown собраны в файле updown.c (листинг 7.6).
Листинг 7.6. Файл updown\updown.c
#define STRICT #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" #include "afxres.h" #include "updown.h"
HINSTANCE hInst; char szAppName[] = "UpDownApp"; char szAppTitle[] = "Up-Down and HotKey Demo";
HWND hwndEdit; HWND hwndUpDown; HWND hwndMainWindow;
HWND hwndHotKey; WORD wHotKey;
// ----------------------------------------------------- // Функция WinMain // ----------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg;
hInst = hInstance;
// Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return FALSE; }
// Регистрируем класс окна memset(&wc, 0, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0); wc.style = 0; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU); wc.lpszClassName = szAppName; if(!RegisterClassEx(&wc)) if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE;
// Создаем главное окно приложения hWnd = CreateWindow(szAppName, szAppTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL); if(!hWnd) return(FALSE);
// Сохраняем идентификатор главного окна hwndMainWindow = hWnd;
// Отображаем окно и запускаем цикл обработки сообщений ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while(GetMessage (&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
// ----------------------------------------------------- // Функция WndProc // ----------------------------------------------------- LRESULT WINAPI WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate); HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy); HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand); HANDLE_MSG(hWnd, WM_SYSCOMMAND, WndProc_OnSysCommand);
default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } }
// ----------------------------------------------------- // Функция WndProc_OnCreate // ----------------------------------------------------- BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { // Инициализируем библиотеку стандартных органов управления InitCommonControls();
// Устанавливаем комбинацию клавиш активизации // диалоговой панели <Alt+Ctrl+V> SendMessage(hWnd, WM_SETHOTKEY, (WPARAM)MAKEWORD('V', HOTKEYF_ALT | HOTKEYF_CONTROL), 0);
return TRUE; }
// ----------------------------------------------------- // Функция WndProc_OnDestroy // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnDestroy(HWND hWnd) { PostQuitMessage(0); return 0L; }
// ----------------------------------------------------- // Функция WndProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { switch (id) { case ID_FILE_EXIT: PostQuitMessage(0); // завершаем работу приложения return 0L; break;
case ID_HELP_ABOUT: MessageBox(hWnd, "Up-Down and HotKey Demo Application, v.1.0\n" "(C) Alexandr Frolov, 1995\n" "Email: frolov@glas.apc.org", szAppTitle, MB_OK | MB_ICONINFORMATION); return 0L; break;
case ID_FILE_OPTIONS: { DialogBox(hInst, MAKEINTRESOURCE(IDD_DIALOG1), NULL, DlgProc); return 0L; break; } default: break; } return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc); }
// ----------------------------------------------------- // Функция DlgProc // ----------------------------------------------------- BOOL APIENTRY DlgProc( HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc_OnInitDialog); HANDLE_MSG(hdlg, WM_COMMAND, DlgProc_OnCommand); default: break; } return FALSE; }
// ----------------------------------------------------- // Функция DlgProc_OnInitDialog // Вызывается при инициализации первой страницы // ----------------------------------------------------- BOOL DlgProc_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam) { // Получаем идентификатор редактора текста hwndEdit = GetDlgItem(hdlg, IDC_EDIT1);
// Создаем орган управления Up-Down и подключаем // его к редактору текста. Задаем начальное // и конечное значение, инициализируем // редактор текста hwndUpDown = CreateUpDownControl( WS_CHILD | WS_BORDER | WS_VISIBLE | UDS_WRAP | UDS_ARROWKEYS | UDS_ALIGNRIGHT | UDS_SETBUDDYINT, 0, 0, 0, 0, hdlg, IDC_UPDOWN, hInst, hwndEdit,10, 1, 1);
// Получаем идентификатор органа Hot Key hwndHotKey = GetDlgItem(hdlg, IDC_HOTKEY);
// Получаем код комбинации клавиш, которая // используется для активизации диалоговой панели wHotKey = (WORD)SendMessage(hwndMainWindow, WM_GETHOTKEY, 0, 0);
// Устанавливаем начальное состояние органа Hot Key // в соответствии с полученным кодом if(wHotKey != 0) SendMessage(hwndHotKey, HKM_SETHOTKEY, wHotKey, 0);
return TRUE; }
// ----------------------------------------------------- // Функция DlgProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void DlgProc_OnCommand(HWND hdlg, int id, HWND hwndCtl, UINT codeNotify) { int nValue; char szBuf[80]; BOOL fError;
switch (id) { case IDOK: { // Определяем и отображаем выбранное значение nValue = (int)GetDlgItemInt(hdlg, IDC_EDIT1, &fError, FALSE);
wsprintf(szBuf, "Value:\t%d", nValue); MessageBox(NULL, szBuf, szAppTitle, MB_OK | MB_ICONINFORMATION);
EndDialog(hdlg, TRUE); break; }
case IDC_SETHOTKEYBUTTON: { // Получаем код комбинации клавиш, которая // используется для активизации диалоговой панели wHotKey = (WORD)SendMessage(hwndHotKey, HKM_GETHOTKEY, 0, 0);
// Устанавливаем новую комбинацию клавиш для // активизации диалоговой панели SendMessage(hwndMainWindow, WM_SETHOTKEY, wHotKey, 0); break; }
case IDCANCEL: { EndDialog(hdlg, TRUE); break; }
default: return FALSE; } return TRUE; }
// ----------------------------------------------------- // Функция WndProc_OnSysCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnSysCommand(HWND hwnd, UINT cmd, int x, int y) { // Если пришло сообщение от клавиши активизации, // посылаем функции главного окна сообщение WM_COMMAND // с кодом, соответствующем строке Options меню File // для активизации диалоговой панели if(cmd == SC_HOTKEY) { SendMessage(hwndMainWindow, WM_COMMAND, (WPARAM)MAKELONG(ID_FILE_OPTIONS, 0), 0L); return 0; } return FORWARD_WM_SYSCOMMAND(hwnd, cmd, x, y, DefWindowProc); }
Описания функция и идентификатор IDC_UPDOWN органа управления Up-Down находятся в файле updown.h (листинг 7.7).
Листинг 7.7 Файл updown\updown.h
// ----------------------------------------------------- // Описание функций // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct); void WndProc_OnDestroy(HWND hWnd); void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); BOOL APIENTRY DlgProc(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL DlgProc_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam); void DlgProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); void WndProc_OnSysCommand(HWND hwnd, UINT cmd, int x, int y); #define IDC_UPDOWN 1224
В файле resource.h (листинг 7.8), созданном автоматически, определены константы идентификаторов ресурсов приложения.
Листинг 7.8 Файл updown\resource.h
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by UPDOWN.RC // #define IDR_APPMENU 102 #define IDI_APPICON 103 #define IDI_APPICONSM 104 #define IDD_DIALOG1 121 #define IDC_EDIT1 1009 #define IDC_HOTKEY 1010 #define IDC_SETHOTKEYBUTTON 1011 #define ID_FILE_EXIT 40001 #define ID_HELP_ABOUT 40003 #define ID_FILE_OPTIONS 40029
// Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 126 #define _APS_NEXT_COMMAND_VALUE 40030 #define _APS_NEXT_CONTROL_VALUE 1012 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
Файл updown.rc (листинг 7.9) содрежит определения ресурсов приложения.
Листинг 7.9 Файл updown\updown.rc
//Microsoft Visual C++ generated resource script. // #include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h"
////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS
////////////////////////////////////////////////////////////// // Menu //
IDR_APPMENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Options...", ID_FILE_OPTIONS MENUITEM SEPARATOR MENUITEM "E&xit", ID_FILE_EXIT END POPUP "&Help" BEGIN MENUITEM "&About...", ID_HELP_ABOUT END END
#ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // TEXTINCLUDE //
1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END
2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END
3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END
////////////////////////////////////////////////////////////// #endif // APSTUDIO_INVOKED
////////////////////////////////////////////////////////////// // Icon //
IDI_APPICON ICON DISCARDABLE "updown.ico" IDI_APPICONSM ICON DISCARDABLE "updownsm.ico"
////////////////////////////////////////////////////////////// // Dialog //
IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 194, 95 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU CAPTION "Value selection" FONT 8, "MS Sans Serif" BEGIN EDITTEXT IDC_EDIT1,15,9,40,13,ES_AUTOHSCROLL DEFPUSHBUTTON "OK",IDOK,130,6,50,14 PUSHBUTTON "Cancel",IDCANCEL,130,23,50,14 CONTROL "Generic1",IDC_HOTKEY,"msctls_hotkey32",WS_TABSTOP,13,66, 80,12 PUSHBUTTON "Set HotKey",IDC_SETHOTKEYBUTTON,130,65,50,14 GROUPBOX "Hot Key",IDC_STATIC,8,49,178,40 END
////////////////////////////////////////////////////////////// // String Table //
STRINGTABLE DISCARDABLE BEGIN ID_FILE_EXIT "Quits the application" END
#ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 3 resource. ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
о наиболее важных функциях, определенных
Расскажем о наиболее важных функциях, определенных в приложении Up-Down.
WndProc_OnCreate
Функция WndProc_OnCreate обрабатывает сообщение WM_CREATE , поступающее в функцию главного окна приложения при его инициализации.
После инициализации библиотеки стандартных органов управления функция WndProc_OnCreate определяет комбинацию клавиш <Alt+Ctrl+V> как комбинацию клавиш активизации диалоговой панели Value Selection (рис. 7.9). Для этого главному окну приложения посылается сообщение WM_SETHOTKEY .
WndProc_OnCommand
Функция WndProc_OnCommand содержит обработчик сообщения WM_COMMAND , поступающего от главного меню приложения, а также в том случае, если пользователь нажимает комбинацию клавиш активизации диалоговой панели Value Selection.
Если вместе с этим сообщением приходит код команды ID_FILE_OPTIONS, с помощью функции DialogBox создается указанная выше диалоговая панель.
DlgProc_OnInitDialog
Функция DlgProc_OnInitDialog вызывается при инициализации диалоговой панели Value Selection, когда в окно этой диалоговой панели приходит сообщение WM_INITDIALOG .
При этом вначале соответствующий обработчик получает идентификатор окна редактора текста, к которому будет привязан орган управления Up-Down, и сохраняет его в переменной hwndEdit.
Затем с помощью функции CreateUpDownControl создается орган управления Up-Down. Функция CreateUpDownControl привязывает созданный орган управления к редактору текста hwndEdit и выполняет все необходимые инициализирующие действия.
Далее функции главного окна приложения посылается сообщение WM_GETHOTKEY . Если для приложения определена комбинация клавиш активизации, то эта комбинация используется для инициализации органа управления Hot Key, расположенного в диалоговой панели Value Selection.
DlgProc_OnCommand
Эта функция обрабатывает сообщение WM_COMMAND , поступающее в функцию окна диалоговой панели Value Selection от органов управления, расположенных в этой диалоговой панели.
Если пользователь нажимает кнопку OK, обработчик получает целое значение, соответствующее текстовой строке из редактора IDC_EDIT1. Так как последний был привязан к органу управления Up-Down, полученное значение соответствует текущему для органа Up-Down.
В том случае, когда пользователь нажимает клавишу Set HotKey, обработчик считывает новую комбинацию клавиш активизации из органа управления Hot Key, посылая последнему сообщение HKM_GETHOTKEY , и записывает ее в переменную wHotKey.
Далее полученная комбинация клавиш устанавливается в качестве комбинации клавиш активизации при помощи сообщения WM_SETHOTKEY .
WndProc_OnSysCommand
Функция WndProc_OnSysCommand обрабатывает сообщение WM_SYSCOMMAND , поступающее в главное окно приложения в том случае, если пользователь нажмет комбинацию клавиш активизации.
Этот обработчик посылает главному окну приложения сообщение WM_COMMAND с кодом команды ID_FILE_OPTIONS. В результате на экране появится диалоговая панель Value Selection.
Операционная система Windows 95 для программиста
Немного изменив приложение Property Sheet Demo, мы превратили его в приложение Wizard Demo. Сравнив исходные тексты, попробуйте сами найти отличия.
Страницы блокнота теперь отображаются последовательно.
На рис. 6.6 показано первая страница органа управления Wizard.
Первая страница органа управления Wizard в приложении Wizard Demo
Кнопка Back для этой страницы отображается всегда в заблокированном состоянии, так как эта страница первая. Для перехода ко второй странице (рис. 6.7) вы должны нажать кнопку Next.
Вторая страница органа управления Wizard в приложении Wizard Demo
На второй странице кнопки Back и Next разблокированы, так что вы можете либо вернуться к первой странице, либо перейти к третьей (рис. 6.8).
Третья страница органа управления Wizard в приложении Wizard Demo
Третья страница - последняя, поэтому вместо кнопки Next на ней отображается кнопка Finish. Если нажать на кнопку Finish, на экране появится диалоговая панель, отображающая установленные параметры (рис. 6.9).
Отображение установленных параметров
Исходные тексты приложения Wizard Demo
Все функции приложения Wizard Demo определены в файле wizdemo.c (листинг 6.5).
Листинг 6.5. Файл wizdemo\wizdemo.c
#define STRICT #include <windows.h> #include <windowsx.h> #include <commctrl.h> #include "resource.h" #include "afxres.h" #include "wizdemo.h"
// Структура, в которой хранятся параметры typedef struct { int nBold; int nItalic; int nUnderline; int nUseTabs; char szKeyWord[80]; } OPTIONS;
OPTIONS opt;
// Массив описаний страниц блокнота Wizard PROPSHEETPAGE psheetPage[3];
// Заголовок блокнота Wizard PROPSHEETHEADER psheetHeader;
HINSTANCE hInst; char szAppName[] = "WizardApp"; char szAppTitle[] = "Wizard Demo";
// ----------------------------------------------------- // Функция WinMain // ----------------------------------------------------- int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { WNDCLASSEX wc; HWND hWnd; MSG msg;
hInst = hInstance;
// Преверяем, не было ли это приложение запущено ранее hWnd = FindWindow(szAppName, NULL); if(hWnd) { if(IsIconic(hWnd)) ShowWindow(hWnd, SW_RESTORE); SetForegroundWindow(hWnd); return FALSE; }
// Регистрируем класс окна memset(&wc, 0, sizeof(wc)); wc.cbSize = sizeof(WNDCLASSEX); wc.hIconSm = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICONSM), IMAGE_ICON, 16, 16, 0); wc.style = 0; wc.lpfnWndProc = (WNDPROC)WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInst; wc.hIcon = LoadImage(hInst, MAKEINTRESOURCE(IDI_APPICON), IMAGE_ICON, 32, 32, 0); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = MAKEINTRESOURCE(IDR_APPMENU); wc.lpszClassName = szAppName; if(!RegisterClassEx(&wc)) if(!RegisterClass((LPWNDCLASS)&wc.style)) return FALSE;
// Создаем главное окно приложения hWnd = CreateWindow(szAppName, szAppTitle, WS_OVERLAPPEDWINDOW,CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hInst, NULL); if(!hWnd) return(FALSE);
// Отображаем окно и запускаем цикл обработки сообщений ShowWindow(hWnd, nCmdShow); UpdateWindow(hWnd); while(GetMessage (&msg, NULL, 0, 0)) { TranslateMessage(&msg); DispatchMessage(&msg); } return msg.wParam; }
// ----------------------------------------------------- // Функция WndProc // ----------------------------------------------------- LRESULT WINAPI WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hWnd, WM_CREATE, WndProc_OnCreate); HANDLE_MSG(hWnd, WM_DESTROY, WndProc_OnDestroy); HANDLE_MSG(hWnd, WM_COMMAND, WndProc_OnCommand);
default: return(DefWindowProc(hWnd, msg, wParam, lParam)); } }
// ----------------------------------------------------- // Функция WndProc_OnCreate // ----------------------------------------------------- BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct) { // Инициализируем библиотеку стандартных органов управления InitCommonControls();
// Инициализируем параметры opt.nBold = opt.nItalic = opt.nUnderline = opt.nUseTabs = 0; strcpy(opt.szKeyWord, "Undef");
return TRUE; }
// ----------------------------------------------------- // Функция WndProc_OnDestroy // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnDestroy(HWND hWnd) { PostQuitMessage(0); return 0L; }
// ----------------------------------------------------- // Функция WndProc_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify) { switch (id) { case ID_FILE_EXIT: PostQuitMessage(0); // завершаем работу приложения return 0L; break;
case ID_HELP_ABOUT: MessageBox(hWnd, "Wizard Demo Application, v.1.0\n" "(C) Alexandr Frolov, 1995\n" "Email: frolov@glas.apc.org", szAppTitle, MB_OK | MB_ICONINFORMATION); return 0L; break;
case ID_FILE_OPTIONS: { // Инициализируем страницы блокнота Wizard psheetPage[0].dwSize = sizeof(PROPSHEETPAGE); psheetPage[0].hInstance = hInst; psheetPage[0].pszIcon = MAKEINTRESOURCE(IDI_EFFECTS); psheetPage[0].dwFlags = PSP_USETITLE | PSP_USEICONID; psheetPage[0].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG1); psheetPage[0].pfnDlgProc = DlgProc1; psheetPage[0].pszTitle = "Set Effects"; psheetPage[0].lParam = 0;
psheetPage[1].dwSize = sizeof(PROPSHEETPAGE); psheetPage[1].hInstance = hInst; psheetPage[1].pszIcon = MAKEINTRESOURCE(IDI_TAB); psheetPage[1].dwFlags = PSP_USETITLE | PSP_USEICONID; psheetPage[1].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG2); psheetPage[1].pfnDlgProc = DlgProc2; psheetPage[1].pszTitle = "Using Tabs"; psheetPage[1].lParam = 0;
psheetPage[2].dwSize = sizeof(PROPSHEETPAGE); psheetPage[2].hInstance = hInst; psheetPage[2].pszIcon = MAKEINTRESOURCE(IDI_KEYWORD); psheetPage[2].dwFlags = PSP_USETITLE | PSP_USEICONID; psheetPage[2].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG3); psheetPage[2].pfnDlgProc = DlgProc3; psheetPage[2].pszTitle = "Keyword"; psheetPage[2].lParam = 0;
// Инициализируем заголовок блокнота Wizard psheetHeader.dwSize = sizeof(PROPSHEETHEADER); psheetHeader.hInstance = hInst; psheetHeader.pszIcon = NULL; psheetHeader.dwFlags = PSH_PROPSHEETPAGE | PSH_WIZARD; psheetHeader.hwndParent = hWnd; psheetHeader.pszCaption = "Wizard Demo Sample"; psheetHeader.nPages = sizeof(psheetPage) / sizeof(PROPSHEETPAGE); psheetHeader.ppsp = (LPCPROPSHEETPAGE)&psheetPage;
// Создаем и отображаем блокнот Wizard PropertySheet(&psheetHeader);
return 0L; break; } default: break; } return FORWARD_WM_COMMAND(hWnd, id, hwndCtl, codeNotify, DefWindowProc); }
// ----------------------------------------------------- // Функция DlgProc1 // для первой страницы блокнота // ----------------------------------------------------- BOOL APIENTRY DlgProc1( HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc1_OnInitDialog); HANDLE_MSG(hdlg, WM_NOTIFY, DlgProc1_OnNotify); default: break; } return FALSE; }
// ----------------------------------------------------- // Функция DlgProc1_OnInitDialog // Вызывается при инициализации первой страницы // ----------------------------------------------------- BOOL DlgProc1_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam) { // Устанавливаем переключатели в соответствии // со значениями параметров, записанных в // структуре opt SendMessage(GetDlgItem(hdlg, IDC_BOLD), BM_SETCHECK, opt.nBold, 0L);
SendMessage(GetDlgItem(hdlg, IDC_ITALIC), BM_SETCHECK, opt.nItalic, 0L);
SendMessage(GetDlgItem(hdlg, IDC_UNDERLINE), BM_SETCHECK, opt.nUnderline, 0L);
return TRUE; }
// ----------------------------------------------------- // Функция DlgProc1_OnNotify // Обрабатывает извещение от первой страницы // ----------------------------------------------------- LRESULT DlgProc1_OnNotify(HWND hdlg, int idFrom, NMHDR* pnmhdr) { switch(pnmhdr->code) { case PSN_SETACTIVE: { // Разблокируем кнопку Next PropSheet_SetWizButtons(GetParent(hdlg), PSWIZB_NEXT); break; }
case PSN_WIZNEXT: { opt.nBold = (int)SendMessage( GetDlgItem(hdlg, IDC_BOLD), BM_GETCHECK, 0L, 0L);
opt.nItalic = (int)SendMessage( GetDlgItem(hdlg, IDC_ITALIC), BM_GETCHECK, 0L, 0L);
opt.nUnderline = (int)SendMessage( GetDlgItem(hdlg, IDC_UNDERLINE), BM_GETCHECK, 0L, 0L); break; } default: break; } return FALSE; }
// ----------------------------------------------------- // Функция DlgProc2 // для второй страницы // ----------------------------------------------------- BOOL APIENTRY DlgProc2(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc2_OnInitDialog); HANDLE_MSG(hdlg, WM_NOTIFY, DlgProc2_OnNotify); default: break; } return FALSE; }
// ----------------------------------------------------- // Функция DlgProc2_OnInitDialog // Вызывается при инициализации второй страницы // ----------------------------------------------------- BOOL DlgProc2_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam) { SendMessage(GetDlgItem(hdlg, IDC_USETABS), BM_SETCHECK, opt.nUseTabs, 0L);
SendMessage(GetDlgItem(hdlg, IDC_DONTUSETABS), BM_SETCHECK, (opt.nUseTabs) ? 0 : 1, 0L);
return TRUE; }
// ----------------------------------------------------- // Функция DlgProc2_OnNotify // Обрабатывает извещение от второй страницы // ----------------------------------------------------- LRESULT DlgProc2_OnNotify(HWND hdlg, int idFrom, NMHDR* pnmhdr) { switch(pnmhdr->code) { case PSN_SETACTIVE: { // Разблокируем кнопк Next и Back, когда страница // блокнота Wizard становится активной PropSheet_SetWizButtons(GetParent(hdlg), PSWIZB_NEXT | PSWIZB_BACK); break; }
case PSN_WIZNEXT: { opt.nUseTabs = (int)SendMessage( GetDlgItem(hdlg, IDC_USETABS), BM_GETCHECK, 0L, 0L); break; } default: break; } return FALSE; }
// ----------------------------------------------------- // Функция DlgProc3 // для третьей страницы // ----------------------------------------------------- BOOL APIENTRY DlgProc3( HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc3_OnInitDialog); HANDLE_MSG(hdlg, WM_NOTIFY, DlgProc3_OnNotify); HANDLE_MSG(hdlg, WM_COMMAND, DlgProc3_OnCommand); default: break; } return FALSE; }
// ----------------------------------------------------- // Функция DlgProc3_OnInitDialog // Вызывается при инициализации третьей страницы // ----------------------------------------------------- BOOL DlgProc3_OnInitDialog(HWND hdlg, HWND hwndFocus, LPARAM lParam) { SetWindowText(GetDlgItem(hdlg, IDC_EDITKEYWORD), opt.szKeyWord); return TRUE; }
// ----------------------------------------------------- // Функция DlgProc3_OnNotify // Обрабатывает извещение от третьей страницы // ----------------------------------------------------- LRESULT DlgProc3_OnNotify(HWND hdlg, int idFrom, NMHDR* pnmhdr) { switch(pnmhdr->code) { case PSN_SETACTIVE: { // Разблокируем кнопку Back, когда страница // блокнота становится активной PropSheet_SetWizButtons(GetParent(hdlg), PSWIZB_BACK); break; }
case PSN_WIZFINISH: { char szBuf[256];
GetWindowText(GetDlgItem(hdlg, IDC_EDITKEYWORD), opt.szKeyWord, 80);
// Заполнение структуры opt завершено, теперь мы // выводим на экран установленные параметры wsprintf(szBuf, "Bold \t= %d\n" "Italic \t= %d\n" "Underline\t= %d\n" "Use Tabs\t= %d\n" "Keyword\t= %s\n", opt.nBold, opt.nItalic, opt.nUnderline, opt.nUseTabs, opt.szKeyWord);
MessageBox(NULL, szBuf, "Options", MB_OK | MB_ICONINFORMATION); break; } default: break; } return FALSE; }
// ----------------------------------------------------- // Функция DlgProc3_OnCommand // ----------------------------------------------------- #pragma warning(disable: 4098) void DlgProc3_OnCommand(HWND hdlg, int id, HWND hwndCtl, UINT codeNotify) { // Сообщение от редактора текста if(id == IDC_EDITKEYWORD) { // Если пользователь изменил ключевое слово в окне // редактирования, разблокируем кнопку Finish if(codeNotify == EN_CHANGE) { PropSheet_SetWizButtons(GetParent(hdlg), PSWIZB_BACK | PSWIZB_FINISH); } } return FALSE; }
Файл wizdemo.h (листинг 6.6) содержит описание функций, определенный в нашем приложении.
Листинг 6.6. Файл wizdemo\wizdemo.h
// ----------------------------------------------------- // Описание функций // ----------------------------------------------------- LRESULT WINAPI WndProc( HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); BOOL WndProc_OnCreate(HWND hWnd, LPCREATESTRUCT lpCreateStruct); void WndProc_OnDestroy(HWND hWnd); void WndProc_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify); LRESULT WndProc_OnNotify(HWND hWnd, int idFrom, NMHDR FAR * pnmhdr); BOOL APIENTRY DlgProc1(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL DlgProc1_OnInitDialog(HWND hwnd,HWND hwndFocus, LPARAM lParam); LRESULT DlgProc1_OnNotify(HWND hWnd, int idFrom, NMHDR* pnmhdr); BOOL APIENTRY DlgProc2(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL DlgProc2_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam); LRESULT DlgProc2_OnNotify(HWND hWnd, int idFrom, NMHDR* pnmhdr); BOOL APIENTRY DlgProc3(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam); BOOL DlgProc3_OnInitDialog(HWND hwnd, HWND hwndFocus, LPARAM lParam); LRESULT DlgProc3_OnNotify(HWND hWnd, int idFrom, NMHDR* pnmhdr); void DlgProc3_OnCommand(HWND hWnd, int id, HWND hwndCtl, UINT codeNotify);
В файле resource.h (который создается автоматически системой Microsoft Visual C++) находятся определения констант для работы с ресурсами приложения Wizard Demo. Этот файл представлен в листинге 6.7.
Листинг 6.7. Файл wizdemo\resource.h
//{{NO_DEPENDENCIES}} // Microsoft Visual C++ generated include file. // Used by WIZDEMO.RC // #define IDR_APPMENU 102 #define IDI_APPICON 103 #define IDI_APPICONSM 104 #define IDI_EFFECTS 105 #define IDI_TAB 106 #define IDI_KEYWORD 107 #define IDD_DIALOG1 121 #define IDD_DIALOG2 122 #define IDD_DIALOG3 123 #define IDC_BOLD 1000 #define IDC_ITALIC 1001 #define IDC_UNDERLINE 1002 #define IDC_USETABS 1004 #define IDC_EDITKEYWORD 1006 #define IDC_DONTUSETABS 1007 #define ID_FILE_EXIT 40001 #define ID_HELP_ABOUT 40003 #define ID_FILE_OPTIONS 40029
// Next default values for new objects // #ifdef APSTUDIO_INVOKED #ifndef APSTUDIO_READONLY_SYMBOLS #define _APS_NEXT_RESOURCE_VALUE 125 #define _APS_NEXT_COMMAND_VALUE 40030 #define _APS_NEXT_CONTROL_VALUE 1008 #define _APS_NEXT_SYMED_VALUE 101 #endif #endif
Файл wizdemo.rc (листинг 6.8) содержит определение ресурсов приложения Wizard Demo. Он создается автоматически.
Листинг 6.8. Файл wizdemo\wizdemo.rc
//Microsoft Visual C++ generated resource script. // #include "resource.h"
#define APSTUDIO_READONLY_SYMBOLS ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 2 resource. // #include "afxres.h"
////////////////////////////////////////////////////////////// #undef APSTUDIO_READONLY_SYMBOLS
////////////////////////////////////////////////////////////// // Menu //
IDR_APPMENU MENU DISCARDABLE BEGIN POPUP "&File" BEGIN MENUITEM "&Options...", ID_FILE_OPTIONS MENUITEM SEPARATOR MENUITEM "E&xit", ID_FILE_EXIT END POPUP "&Help" BEGIN MENUITEM "&About...", ID_HELP_ABOUT END END
#ifdef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // TEXTINCLUDE //
1 TEXTINCLUDE DISCARDABLE BEGIN "resource.h\0" END
2 TEXTINCLUDE DISCARDABLE BEGIN "#include ""afxres.h""\r\n" "\0" END
3 TEXTINCLUDE DISCARDABLE BEGIN "\r\n" "\0" END
////////////////////////////////////////////////////////////// #endif // APSTUDIO_INVOKED
////////////////////////////////////////////////////////////// // Icon //
IDI_APPICON ICON DISCARDABLE "wizdemo.ico" IDI_APPICONSM ICON DISCARDABLE "wizdemsm.ico" IDI_EFFECTS ICON DISCARDABLE "EFFECTS.ICO" IDI_TAB ICON DISCARDABLE "TAB.ICO" IDI_KEYWORD ICON DISCARDABLE "KEYWORD.ICO"
////////////////////////////////////////////////////////////// // Dialog //
IDD_DIALOG1 DIALOG DISCARDABLE 0, 0, 186, 95 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU FONT 8, "MS Sans Serif" BEGIN CONTROL "Bold",IDC_BOLD,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,19,21,35,10 CONTROL "Italic",IDC_ITALIC,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,19,32,35,10 CONTROL "Underline",IDC_UNDERLINE,"Button", BS_AUTOCHECKBOX | WS_TABSTOP,19,43,46,10 GROUPBOX "Effects",IDC_STATIC,8,7,112,53 END
IDD_DIALOG2 DIALOG DISCARDABLE 0, 0, 186, 95 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU FONT 8, "MS Sans Serif" BEGIN CONTROL "Use Tabs", IDC_USETABS, "Button", BS_AUTORADIOBUTTON,18,22, 63,10 GROUPBOX "Tabs",IDC_STATIC,9,7,105,63 CONTROL "Don't use Tabs",IDC_DONTUSETABS,"Button", BS_AUTORADIOBUTTON,18,35,83,10 END
IDD_DIALOG3 DIALOG DISCARDABLE 0, 0, 211, 102 STYLE DS_MODALFRAME | WS_POPUP | WS_VISIBLE | WS_CAPTION | WS_SYSMENU FONT 8, "MS Sans Serif" BEGIN EDITTEXT IDC_EDITKEYWORD,24,58,138,13,ES_AUTOHSCROLL GROUPBOX "Keyword",IDC_STATIC,11,38,166,52 ICON IDI_APPICON,IDC_STATIC,11,7,18,20 END
////////////////////////////////////////////////////////////// // String Table //
STRINGTABLE DISCARDABLE BEGIN ID_FILE_EXIT "Quits the application" END
#ifndef APSTUDIO_INVOKED ////////////////////////////////////////////////////////////// // Generated from the TEXTINCLUDE 3 resource. ////////////////////////////////////////////////////////////// #endif // not APSTUDIO_INVOKED
и функций приложения Wizard Demo,
Займемся описанием глобальных переменных и функций приложения Wizard Demo, обратив внимание на отличия от приложения Property Sheet Demo.
Глобальные переменные
Мы бы могли создать орган управления Wizard точно таким же способом, что и блокнот, добавляя страницы по одной с помощью функции CreatePropertySheetPage. Однако есть и другой способ, который не требует использования этой функции. В этом случае нам не потребуется и массив структур hPage типа HPROPSHEETPAGE, который мы изъяли из приложения Wizard Demo.
WndProc_OnCommand
При создании органа управления Wizard массив структур psheetPage, описывающих страницы, заполняется точно также, как и в приложении Property Sheet Demo.
Однако заголовок блокнота Wizard инициализируется немного по-другому. Например, в поле dwFlags устанавливаются флаги PSH_PROPSHEETPAGE и PSH_WIZARD.
Первый из них говорит о том, что в поле psheetHeader.ppsp будет записан адрес заполненного массива структур, описывающих страницы. Это поле используется вместо поля psheetHeader.phpage, в который мы записывали адрес массива идентификатора страниц, созданных функцией CreatePropertySheetPage.
Флаг PSH_WIZARD говорит о том, что необходимо создать орган управления Wizard, а не обычный блокнот Property Sheet.
Блокнот Wizard создается и отображается функцией PropertySheet, как и в предыдущем приложении.
DlgProc1_OnNotify
Функция DlgProc1_OnNotify обрабатывает извещения PSN_SETACTIVE и PSN_WIZNEXT.
Обработчик извещения PSN_SETACTIVE разблокирует кнопку Next, вызывая макрокоманду PropSheet_SetWizButtons :
PropSheet_SetWizButtons(GetParent(hdlg), PSWIZB_NEXT);
Эта макрокоманда посылает окну блокнота Wizard сообщение PSM_SETWIZBUTTONS. Второй параметр макрокоманды может принимать значения PSWIZB_BACK (разблокировка кнопки Back), PSWIZB_NEXT (разблокировка кнопки Next) или PSWIZB_FINISH (разблокировка кнопки Finish). Можно также использовать любую комбинацию этих значений, объединяя их при помощи логической операции ИЛИ. Если в качестве второго параметра макрокоманды PropSheet_SetWizButtons указать нулевое значение, все перечисленные выше кнопки будут заблокированы.
Извещение PSN_WIZNEXT поступает в функцию диалога первой страницы тогда, когда пользователь переходит к следующей странице, нажимая кнопку Next. Наш обработчик получает состояние переключателей и записывает его в соответствующие поля структуры opt.
DlgProc2_OnNotify
Задача функции DlgProc2_OnNotify - разблокировка кнопок Next и Back, когда пользователь переключается на вторую страницу. Для разблокировки вызывается только что описанная макрокоманда PropSheet_SetWizButtons :
PropSheet_SetWizButtons(GetParent(hdlg), PSWIZB_NEXT | PSWIZB_BACK);
В ответ на извещение PSN_WIZNEXT функция DlgProc2_OnNotify получает состояние переключателя IDC_USETABS и записывает его в поле nUseTabs структуры opt.
DlgProc3_OnNotify
Функция DlgProc3_OnNotify обрабатывает извещения, которые поступают в функцию диалога второй страницы.
Обработчик извещения PSN_SETACTIVE разблокирует кнопку Back.
При поступлении извещения PSN_WIZFINISH функция диалога отображает установленные параметры. Однако для того чтобы такое извещение поступило в функцию диалога, кнопка Next должна быть преобразована в кнопку Finish. Эту задачу решает обработчик сообщения WM_COMMAND, описанный ниже.
DlgProc3_OnCommand
Обработчик сообщения WM_COMMAND отображает кнопки Back и Finish, когда пользователь изменил ключевое слово. Это делается так:
PropSheet_SetWizButtons(GetParent(hdlg), PSWIZB_BACK | PSWIZB_FINISH);
Операционная система Windows 95 для программиста
Для использования органа управления List View ваше приложение должно выполнить несколько действий:
создать окно на базе предопределенного класса окна WC_LISTVIEW ;
создать один, два или три списка изображений, которые будут использованы для идентификации строк или для отображения состояния строк (назначение этих списков мы рассмотрим позже);
добавить изображения (пиктограммы) в созданные списки;
подключить списки изображений к органу управления List View;
вставить столбцы и установить их ширину;
вставить элементы списка, указав данные, которые должны быть отображены в столбцах или определив, что эти данные будут предоставлены позже при обработке соответствующего извещения.
Теперь мы подробно расскажем вам о том, как выполнить перечисленные выше действия.
Подключение списков изображений к органу List View
На данном этапе мы создали и заполнили списки изображений. Теперь их надо подключить к органу управления List View, вызвав макрокоманду ListView_SetImageList :
HIMAGELIST ListView_SetImageList( HWND hwnd, // идентификатор окна органа List View HIMAGELIST himl, // идентификатор подключаемого списка int iImageList); // тип изображений в списке
Первые два параметра макрокоманды задают, соответственно, идентификатор окна органа List View и идентификатор подключаемого списка, который мы только что создали и заполнили.
Параметр iImageList может иметь следующие значения:
Значение | Содержимое списка |
LVSIL_NORMAL | Пиктограммы стандартного размера |
LVSIL_SMALL | Пиктограммы уменьшенного размера |
LVSIL_STATE | Пиктограммы состояния элементов списка |
Макрокоманда ListView_SetImageList посылает органу управления List View сообщение LVM_SETIMAGELIST и определена следующим образом:
#define ListView_SetImageList(hwnd, himl, iImageList) \ (HIMAGELIST)(UINT)SendMessage((hwnd), LVM_SETIMAGELIST, \ (WPARAM)(iImageList), (LPARAM)(UINT)(HIMAGELIST)(himl))
Вы можете посылать сообщение LVM_SETIMAGELIST непосредственно, однако макрокомандой удобнее пользоваться. Вот соответствующий фрагмент исходного текста приложения, подключающий два списка:
ListView_SetImageList(hwndList, himlSmall, LVSIL_SMALL); ListView_SetImageList(hwndList, himlLarge, LVSIL_NORMAL);
Создание окна List View
Окно органа управления List View создается функцией CreateWindowEx на базе класса окна WC_LISTVIEW , например так:
hwndList = CreateWindowEx(0L, WC_LISTVIEW, "", WS_VISIBLE | WS_CHILD | WS_BORDER | LVS_REPORT | LVS_EDITLABELS, 0, 0, rc.right - rc.left, rc.bottom - rc.top, hWnd, (HMENU)IDC_LISTVIEW, hInst, NULL);
Перед вызовом этой функции вы должны загрузить в память DLL-библиотеку COMCTL32.DLL , для чего следует вызвать функцию InitCommonControls без параметров.
В дополнение к обычным стилям окна, таким как WS_VISIBLE, WS_CHILD и WS_BORDER, необходимо указать специальные стили с префиксом имени LVS_, определяющие внешний вид и поведение органа управления List View. Как минимум, следует указать один из следующих четырех стилей: LVS_REPORT , LVS_ICON, LVS_SMALLICON или LVS_LIST.
Ниже мы привели краткое описание этих, а также остальных стилей окна List View.
Стиль | Описание |
LVS_REPORT | Список отображается в виде детального отчета, состоящего из нескольких столбцов (рис. 3.1) |
LVS_ICON | Список отображается в виде окна с пиктограммами стандартного размера (рис. 3.2) |
LVS_SMALLICON | Список отображается в виде окна с пиктограммами уменьшенного размера (рис. 3.3) |
LVS_LIST | Простой список с пиктограммами уменьшенного размера (рис. 3.4) |
LVS_ALIGNLEFT | Используется вместе с LVS_ICON и LVS_SMALLICON. Если указан этот стиль, пиктограммы будут выровнены по левой границе |
LVS_ALIGNTOP | Используется вместе с LVS_ICON и LVS_SMALLICON. Если указан этот стиль, пиктограммы будут выровнены по верхней границе. Стиль LVS_ALIGNTOP используется по умолчанию |
LVS_AUTOARRANGE | Используется вместе с LVS_ICON и LVS_SMALLICON для выполнения автоматического размещения пиктограмм внутри окна органа управления List View |
LVS_NOSCROLL | Отключение возможности свертки содержимого окна органа управления List View |
LVS_EDITLABELS | Этот стиль позволяет пользователю редактировать название элемента списка. Если указан стиль LVS_EDITLABELS, приложение должно обрабатывать извещение LVN_ENDLABELEDIT, которое будет описано позже |
LVS_NOCOLUMNHEADER | Если указан стиль LVS_NOCOLUMNHEADER, в режиме детального просмотра не отображается заголовок столбцов, с помощью которого выполняется сортировка и изменение размера столбцов |
LVS_NOLABELWRAP | Подпись под пиктограммами отображается в одной строке |
LVS_NOSORTHEADER | Если указан этот стиль, с помощью заголовка окна невозможно выполнить сортировку столбцов в режиме детального просмотра |
LVS_OWNERDRAWFIXED | Стиль позволяет родительскому окну выполнить рисование содержимого списка. Для этого родительское окно должно обрабатывать сообщение WM_DRAWITEM |
LVS_SHAREIMAGELIST | Этот стиль предназначен для организации совместного использования списков изображений несколькими органами управления List View |
LVS_SHOWSELALWAYS | Выбранные элементы списка отображаются с выделением даже в том случае, когда орган управления List View не активен |
LVS_SINGLESEL | Пользователь может выделить в списке только один элемент (по умолчанию можно выделить сразу несколько элементов) |
LVS_SORTASCENDING | Выполнение сортировки текстовых строк элементов в прямом порядке |
LVS_SORTDESCENDING | То же, но в обратном порядке |
Как мы уже говорили, стили LVS_REPORT, LVS_ICON, LVS_SMALLICON и LVS_LIST определяют режим работы органа управления List View. При создании этого органа управления вы должны указать только один из перечисленных стилей. Однако в дальнейшем если появится необходимость изменить режим работы, это можно будет легко сделать при помощи функций GetWindowLong и SetWindowLong .
Когда список отображается в виде окна со стандартными или уменьшенными пиктограммами, у вас есть возможность выбрать один из нескольких способов выравнивания пиктограмм во внутренней области этого окна. Для этого вы должны указать один из следующих стилей: LVS_ALIGNLEFT, LVS_ALIGNTOP или LVS_AUTOARRANGE.
По умолчанию (если не указан стиль LVS_ALIGNLEFT) пиктограммы выравниваются по верхней границе окна. Такое поведение соответствует стилю LVS_ALIGNTOP. Вы можете также выровнять пиктограммы по левой границе окна, задав стиль LVS_ALIGNLEFT. Разумеется, стили LVS_ALIGNTOP и LVS_ALIGNLEFT несовместимы, поэтому вы можете использовать только один из них.
Для пользователя будет удобнее, если при изменении размеров окна просмотра пиктограммы будут автоматически перемещаться таким образом, чтобы по возможности занимать всю полезную площадь окна. Вы можете организовать автоматическое размещение пиктограмм, указав стиль LVS_AUTOARRANGE. Без этого стиля пользователю придется работать с полосой просмотра (Scrollbar), что не всегда удобно.
Иногда необходимо заблокировать возможность изменения размеров окна просмотра, исключив стиль WS_BORDER. Например, вы можете создать окно просмотра фиксированного размера, расположенное в диалоговой панели. В этом случае вы не должны указывать стиль LVS_NOSCROLL, так как иначе пользователь не получит доступа ко всем элементам списка.
Списки изображений
Итак, вы создали окно органа управления List View, выбрав для него подходящий набор стилей. Теперь следует заняться наполнением этого окна изображениями и элементами списка.
В зависимости от того, в каких режимах будет работать создаваемый вами орган управления List View, вы должны создать один, два или три списка из следующего набора:
список стандартных пиктограмм;
список пиктограмм уменьшенного размера;
список пиктограмм состояния.
Как выбрать нужные списки?
Список стандартных пиктограмм нужен только в том случае, если вы будете использовать орган управления List View в режиме LVS_ICON (рис.3.2 в разделе "Приложение List Application").
Список пиктограмм уменьшенного размера нужен для всех остальных режимов (рис. 3.1, 3.3, 3.4).
Что же касается списка пиктограмм состояния (state image list), то он используется для выделения состояния отдельных элементов списка. В SDK есть исходные тексты приложения ListCtrl, создающего список пиктограмм состояния. |
Для создания списка изображения вы должны вызвать функцию ImageList_Create :
HIMAGELIST WINAPI ImageList_Create( int cx, // ширина изображения int cy, // высота изображения UINT flags, // тип изображения int cInitial, // первоначальное количество изображений int cGrow); // количество изображений, на которое // увеличится размер списка при добавлении новых изображений
Параметры cx и cy определяют, соответственно, ширину и высоту добавляемых изображений. Если вы создаете список пиктограмм стандартного или уменьшенного размера, для определения значения этих параметров следует использовать функцию GetSystemMetrics , передав ей в первом случае параметры SM_CXICON и SM_CYICON , а во втором - параметры SM_CXSMICON и SM_CYSMICON .
Функция GetSystemMetrics была нами описана в 11 томе "Библиотеки системного программиста".
Параметр flags определяет тип изображений, из которых состоит список. Вы можете создавать список из пиктограмм, аппаратно-зависимых битовых изображений DDB или аппаратно-независимых битовых изображений DIB. Приведем список возможных значений параметра flags:
Значение | Описание |
ILC_COLOR4 | 4-битовое изображение DIB |
ILC_COLOR8 | 8-битовое изображение DIB |
ILC_COLOR16 | 16-битовое изображение DIB |
ILC_COLOR24 | 24-битовое изображение DIB |
ILC_COLOR32 | 32-битовое изображение DIB |
ILC_PALETTE | Используются цветовые палитры |
ILC_COLORDDB | Изображение DDB |
ILC_MASK | Использование маски. Изображение состоит из двух изображений, причем одно из них является монохромной маской. Такой формат имеют пиктограммы и курсоры |
Параметр cInitial определяет размер блока памяти, выделяемого для хранения изображений, так как этот размер зависит от количества изображений. При добавлении в созданный список новых изображений сверх значения, указанного в этом параметре, происходит динамическое изменение размера заказанного блока памяти. Такое изменение выполняется в соответствии со значением параметра cGrow.
Функция ImageList_Create при нормальном завершении возвращает идентификатор созданного списка изображений, который надо сохранить для дальнейшего использования. В случае ошибки возвращается значение NULL.
Ниже приведен пример создания двух списков, в которые первоначально будет добавлено девять изображений:
himlSmall = ImageList_Create( GetSystemMetrics(SM_CXSMICON),GetSystemMetrics(SM_CYSMICON), ILC_MASK, 9, 1); himlLarge = ImageList_Create( GetSystemMetrics(SM_CXICON), GetSystemMetrics(SM_CYICON), ILC_MASK, 9, 1);
Вставка элементов списка
На последнем этапе вы должны вставить в список элементы. Проще всего это сделать с помощью макрокоманды ListView_InsertItem , посылающей органу управления List View сообщение LVM_INSERTITEM :
int ListView_InsertItem( HWND hwnd, // идентификатор окна List View const LV_ITEM FAR * pitem); // адрес структуры LV_ITEM
Перед тем как вставлять элемент, вы должны записать его атрибуты в структуру LV_ITEM , определенную следующим образом:
typedef struct _LV_ITEM { UINT mask; // маска использования полей структуры LV_ITEM int iItem; // номер элемента int iSubItem; // номер дополнительного элемента UINT state; // текущее состояние элемента UINT stateMask; // маска состояния элемента LPTSTR pszText; // адрес текстового буфера int cchTextMax; // размер текстового буфера int iImage; // номер пиктограммы элемента LPARAM lParam;// 32-битовое значение, связанное с элементом } LV_ITEM;
Поле маски mask определяет, какие из полей структуры LV_ITEM будут использованы при добавлении элементов. Возможны следующие значения масок (их можно объединять при помощи логической операции ИЛИ):
Маска | Заполненное поле структуры LV_ITEM |
LVIF_TEXT | pszText |
LVIF_IMAGE | iImage |
LVIF_PARAM | lParam |
LVIF_STATE | state |
Ниже мы привели фрагмент исходного текста приложения List Application, в котором к созданному ранее списку добавляется 9 элементов:
memset(&lvi, 0, sizeof(lvi)); lvi.mask = LVIF_IMAGE | LVIF_TEXT | LVIF_PARAM; lvi.pszText = LPSTR_TEXTCALLBACK; for(i=0; i<9; i++) { lvi.iItem = i; lvi.iSubItem = 0; lvi.cchTextMax = 40; lvi.lParam = (LPARAM)&rgApplInfo[i];
lvi.iImage = i; ListView_InsertItem(hwndList, &lvi);
lvi.iItem = i; lvi.iSubItem = 1; ListView_InsertItem(hwndList, &lvi);
lvi.iItem = i; lvi.iSubItem = 2; ListView_InsertItem(hwndList, &lvi); }
Так как в нашем примере пиктограммы состояния элементов не используются, мы не заполняем поля state и stateMask.
В поле iItem мы записываем номер элемента, изменяя его в цикле от 0 до 9.
Аналогичным образом мы поступаем и с полем iImage, записывая в него номер пиктограммы для элемента. Напомним, что в разных режимах работы органа управления List View будут использованы пиктограммы из разных списков (стандартного и уменьшенного размера).
В поле pszText можно было бы записать адрес текстовой строки, соответствующей названию элемента или дополнительному элементу, однако мы занесли туда значение LPSTR_TEXTCALLBACK . В этом случае адрес нужной строки будет предоставляться при обработке соответствующего извещения, о чем речь еще впереди.
На этом мы завершим описание процесса создания и наполнения органа управления List View. Из-за ограниченного объема книги мы не рассмотрели все особенности процесса. За более подробной информацией вы можете обратиться к справочной документации, которая поставляется вместе с SDK.
Вставка столбцов
Если вы собираетесь просматривать список в виде детального отчета (стиль LVS_REPORT), необходимо вставить нужное количество столбцов, а также определить для каждого столбца заголовок и ширину.
Вставка столбцов выполняется макрокомандой ListView_InsertColumn , посылающей органу управления сообщение LVM_INSERTCOLUMN :
int ListView_InsertColumn( HWND hwnd, // идентификатор органа List view int iCol, // номер столбца const LV_COLUMN FAR * pcol); // адрес структуры LV_COLUMN
Нумерация столбцов начинается с нуля.
Перед тем как вызывать макрокоманду ListView_InsertColumn, вы должны по очереди для каждого столбца заполнить структуру LV_COLUMN , определенную следующим образом:
typedef struct _LV_COLUMN { UINT mask; // маска использования полей структуры LV_COLUMN int fmt; // тип выравнивания для столбца int cx; // ширина столбца в пикселах LPTSTR pszText; // адрес строки заголовка столбца int cchTextMax; // размер буфера, адрес которого // задан в pszText int iSubItem; // номер дополнительного элемента } LV_COLUMN;
Поле маски mask определяет, какие из полей структуры LV_COLUMN будут использованы. Ниже мы перечислили возможные значения масок, которые можно объединять при помощи логической операции ИЛИ:
Маска | Заполненное поле структуры LV_COLUMN |
LVCF_FMT | fmt |
LVCF_SUBITEM | iSubItem |
LVCF_TEXT | pszText |
LVCF_WIDTH | cx |
Поле fmt определяет тип выравнивания для столбца. Можно указывать одно из следующих значений: LVCFMT_LEFT, LVCFMT_RIGHT или LVCFMT_CENTER. Они задают, соответственно, выравнивание влево, вправо или по центру столбца.
Ширина столбца в пикселах задается полем cx.
Для того чтобы задать заголовок столбца, вы должны определить буфер и записать в него соответствующую текстовую строку, закрыв ее двоичным нулем. Размер буфера должен быть указан в поле cchTextMax (в байтах).
Теперь о поле iSubItem.
Как мы уже говорили раньше, список содержит элементы, каждый из которых имеет имя и связанную с ним пиктограмму (привязка пиктограммы выполняется на этапе добавления строк). Кроме того, для каждого элемента можно задать дополнительные элементы. В SDK элементы называются item, а дополнительные элементы - subitem.
В режиме детального отчета пиктограмма и название элемента отображаются в самом левом столбце, имеющим номер 0. Дополнительные элементы, заданные в виде текстовых строк, отображаются в остальных столбцах детального отчета (с номерами 1, 2 и т. д.).
В поле iSubItem вы должны записать номер дополнительного элемента, который будет связан с текущим столбцом отчета.
Ниже приведен фрагмент кода, в котором выполняется вставка трех столбцов с номерами 0, 1 и 2:
GetClientRect(hWnd, &rc); memset(&lvc, 0, sizeof(lvc)); lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM; lvc.fmt = LVCFMT_LEFT; lvc.cx = (rc.right - rc.left) / 4;
lvc.iSubItem = 0; lvc.pszText = "Application Name"; ListView_InsertColumn(hwndList, 0, &lvc);
lvc.iSubItem = 1; lvc.pszText = "Icon Name"; ListView_InsertColumn(hwndList, 1, &lvc);
lvc.iSubItem = 2; lvc.pszText = "Cost, USD"; ListView_InsertColumn(hwndList, 2, &lvc); ListView_SetColumnWidth(hwndList,2,(rc.right - rc.left) / 8);
Ширина первого и второго столбца устанавливается равной четверти ширины главного окна приложения (которая в нашем случае равна ширине окна органа управления List View).
Ширина третьего столбца устанавливается равной одной восьмой ширины окна, причем для разнообразия мы делаем это при помощи макрокоманды ListView_SetColumnWidth . Эта макрокоманда посылает органу List View сообщение LVM_SETCOLUMNWIDTH . Такую процедуру можно выполнять и позже, а не только в момент создания окна органа управления List View.
Заполнение списков изображений
Мы только что создали списки изображений, однако пока они пустые и орган управления List View ничего о них "не знает".
Для добавления изображений вы можете воспользоваться функцией ImageList_AddIcon :
int WINAPI ImageList_AddIcon( HIMAGELIST himl, // идентификатор списка изображений HICON hicon // идентификатор добавляемого изображения );
Если вы добавляете в список пиктограммы, определенные в ресурсах приложения, вызов функции ImageList_AddIcon можно выполнять в цикле, загружая пиктограммы функцией LoadIcon :
for(i = IDI_ICON1; i <= IDI_ICON9; i++) { hIcon = LoadIcon(hInst, MAKEINTRESOURCE(i)); ImageList_AddIcon(himlSmall, hIcon); ImageList_AddIcon(himlLarge, hIcon); }
Заметьте, что после добавления пиктограммы не уничтожаются явным образом, так как при необходимости система сделает это автоматически.
Операционная система Windows 95 для программиста
Орган управления Property Sheet - это системная немодальная диалоговая панель, состоящая из нескольких страниц. Перед тем как продолжить чтение, мы рекомендуем вам обратиться к главе "Диалоговые панели", расположенной в 12 томе "Библиотеки системного программиста", и вспомнить, как работают обычные диалоговые панели.
Для создания органа управления Property Sheet вы должны сделать следующее:
подготовить шаблоны диалоговых панелей в ресурсах приложения (хотя их можно создавать и динамически);
подготовить функции диалога для каждой диалоговой панели (возможно также использование одной и той же функции диалога для нескольких панелей, содержащих одинаковые органы управления);
создать и заполнить массив структур типа PROPSHEETPAGE , каждый элемент которого описывает отдельную страницу блокнота;
создать и заполнить структуру типа PROPSHEETHEADER , описывающую блокнот;
создать отдельные страницы блокнота при помощи функции CreatePropertySheetPage и затем отобразить блокнот функцией PropertySheet (как мы покажем дальше, можно обойтись без функции CreatePropertySheetPage, что упрощает процесс создания блокнота).
Сразу отметим, что для создания органа управления Wizard необходимо использовать точно такую же процедуру. Отличия заключаются в заполнении структур и обработке сообщений в функциях диалога.
Рассмотрим перечисленные выше шаги подробнее.
Функция диалога страницы блокнота
Функция диалога страницы блокнота обычно обрабатывает сообщения WM_INITDIALOG , WM_COMMAND и WM_NOTIFY . Первое из них передается функции диалога при его инициализации, второе поступает от органов управления, расположенных на странице блокнота. Сообщение WM_NOTIFY генерируется в том случае, когда пользователь нажимает кнопки OK, Cancel, Help, Apply (а также кнопки Back, Next и Finish в органе управления Wizard) или переходит к просмотру другой страницы блокнота. Ниже мы рассмотрим эти сообщения подробнее.
В отличие от обычной функции диалога, функция диалога для страницы блокнота или органа управления Wizard не вызывает функцию EndDialog , так как это привело бы к уничтожению органа управления. Вот пример функции диалога для страницы блокнота, взятый из приложения Property Sheet Demo, полные исходные тексты которого вы найдете ниже в разделе "Приложение Property Sheet Demo":
BOOL APIENTRY DlgProc1(HWND hdlg, UINT msg, WPARAM wParam, LPARAM lParam) { switch(msg) { HANDLE_MSG(hdlg, WM_INITDIALOG, DlgProc1_OnInitDialog); HANDLE_MSG(hdlg, WM_COMMAND, DlgProc1_OnCommand); HANDLE_MSG(hdlg, WM_NOTIFY, DlgProc1_OnNotify); default: break; } return FALSE; }
Так же как и в функции главного окна приложений, в функции диалога для организации обработки сообщений мы воспользовались удобной макрокомандой HANDLE_MSG .
Подготовка массива структур PROPSHEETPAGE
Для каждой страницы блокнота или органа управления Wizard вы должны подготовить структуру PROPSHEETPAGE , записав в нее описание страницы. Структура PROPSHEETPAGE определена следующим образом:
typedef struct _PROPSHEETPAGE { DWORD dwSize; // размер структуры DWORD dwFlags; // флаги HINSTANCE hInstance; // идентификатор приложения union { LPCTSTR pszTemplate; // идентификатор ресурса // диалоговой панели LPCDLGTEMPLATE pResource; // шаблон диалоговой панели }; union { HICON hIcon; // идентификатор пиктограммы LPCTSTR pszIcon; // идентификатор ресурса пиктограммы }; LPCTSTR pszTitle; // заголовок страницы DLGPROC pfnDlgProc; // адрес функции диалога LPARAM lParam; // дополнительный параметр LPFNPSPCALLBACK pfnCallback;// адрес функции обратного вызова UINT FAR * pcRefParent;// указатель на счетчик использования } PROPSHEETPAGE, FAR *LPPROPSHEETPAGE; typedef const PROPSHEETPAGE FAR *LPCPROPSHEETPAGE;
Возможно, эта структура выглядит несколько громоздко, однако ее заполнение не вызовет у вас никаких затруднений.
В поле dwSize необходимо записать размер структуры, т. е. значение sizeof(PROPSHEETPAGE).
В поле dwFlags следует записать флаги, отмечающие задействованные поля структуры и определяющие внешний вид органа управления. Здесь можно использовать следующие значения:
Флаг | Описание |
PSP_DEFAULT | Используются все поля структуры, имеющие назначение, принятое по умолчанию |
PSP_DLGINDIRECT | Поле pResource используется вместо поля pszTemplate. Этот флаг следует указать в том случае, если вы создаете шаблон диалоговой панели для страницы динамически в памяти, а не загружаете его из ресурсов приложения |
PSP_HASHELP | Если указан этот флаг, для данной страницы отображается кнопка Help |
PSP_RTLREADING | Заголовок, определенный в поле pszTitle, отображается справа налево. Используется только в арабских версиях Microsoft Windows95 |
PSP_USECALLBACK | При создании или уничтожении страницы управление получает функция обратного вызова, адрес которой определен в поле pfnCallback |
PSP_USEHICON | Поле hIcon содержит идентификатор пиктограммы уменьшенного размера, которая будет отображаться в левой части закладки страницы |
PSP_USEICONID | Поле pszIcon содержит идентификатор ресурса пиктограммы уменьшенного размера, которая будет отображаться в левой части закладки страницы |
PSP_USEREFPARENT | Используется поле pcRefParent |
PSP_USETITLE | В качестве заголовка страницы следует использовать строку, адрес которой задан в поле pszTitle, а не ту строку, что определена в шаблоне диалоговой панели |
В нашем приложении мы использовали флаги PSP_USETITLE и PSP_USEICONID.
Поле hInstance должно содержать идентификатор приложения, который передается через соответствующий параметр функции WinMain.
Поля pszTemplate и pResource объединены, поэтому вы можете использовать только одно из них. По умолчанию вы должны записать в поле pszTemplate идентификатор ресурса, содержащего шаблон диалоговой панели. Однако указав флаг PSP_DLGINDIRECT, вы вместо этого можете создать шаблон диалоговой панели динамически в оперативной памяти и записать указатель на этот шаблон в поле pResource.
Аналогичным образом объединены поля hIcon и pszIcon. Если в поле pszIcon вы собираетесь записать идентификатор пиктограммы, которая будет отображаться в закладке, следует указать флаг PSP_USEHICON. Если же пиктограмма определена в ресурсах приложения, вы должны записать соответствующий идентификатор ресурса в поле pszIcon и указать флаг PSP_USEICONID.
В том случае, когда вам не нужно отображать пиктограмму на закладке, не указывайте флаги PSP_USEHICON и PSP_USEICONID, а в поле pszIcon (или hIcon, что одно и то же) запишите значение NULL.
При создании шаблона диалоговой панели вы можете указать ее заголовок. Однако вместо этого можно использовать поле pszTitle и флаг PSP_USEICONID.
В поле pfnDlgProc необходимо записать адрес функции диалога, подготовленной с учетом приведенных выше замечаний.
Вот как мы заполнили поля структуры PROPSHEETPAGE для трех страниц блокнота в приложении Property Sheet Demo:
PROPSHEETPAGE psheetPage[3];
psheetPage[0].dwSize = sizeof(PROPSHEETPAGE); psheetPage[0].hInstance = hInst; psheetPage[0].pszIcon = MAKEINTRESOURCE(IDI_EFFECTS); psheetPage[0].dwFlags = PSP_USETITLE | PSP_USEICONID; psheetPage[0].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG1); psheetPage[0].pfnDlgProc = DlgProc1; psheetPage[0].pszTitle = "Set Effects"; psheetPage[0].lParam = 0;
psheetPage[1].dwSize = sizeof(PROPSHEETPAGE); psheetPage[1].hInstance = hInst; psheetPage[1].pszIcon = MAKEINTRESOURCE(IDI_TAB); psheetPage[1].dwFlags = PSP_USETITLE | PSP_USEICONID; psheetPage[1].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG2); psheetPage[1].pfnDlgProc = DlgProc2; psheetPage[1].pszTitle = "Using Tabs"; psheetPage[1].lParam = 0;
psheetPage[2].dwSize = sizeof(PROPSHEETPAGE); psheetPage[2].hInstance = hInst; psheetPage[2].pszIcon = MAKEINTRESOURCE(IDI_KEYWORD); psheetPage[2].dwFlags = PSP_USETITLE | PSP_USEICONID; psheetPage[2].pszTemplate = MAKEINTRESOURCE(IDD_DIALOG3); psheetPage[2].pfnDlgProc = DlgProc3; psheetPage[2].pszTitle = "Keyword"; psheetPage[2].lParam = 0;
Подготовка шаблонов диалоговых панелей
Шаблон диалоговой панели готовится обычным способом с помощью интегрированной среды разработки приложений Microsoft Visual C++. Единственное, на что нам хотелось бы обратить внимание: на страницах блокнота и органа управления Wizard нет кнопок OK, Cancel и Help, так как эти кнопки относятся ко всему блокноту и располагаются в нижней части его окна. В остальном шаблоны страниц блокнота ничем не отличаются от шаблонов обычных диалоговых панелей.
Заполнение структуры PROPSHEETHEADER и создание блокнота
Структура PROPSHEETHEADER , как это видно из ее названия, описывает заголовок блокнота. Она имеет следующий формат:
typedef struct _PROPSHEETHEADER { DWORD dwSize; // размер структуры DWORD dwFlags; // флаги HWND hwndParent; // идентификатор родительского окна HINSTANCE hInstance; // идентификатор приложения union { HICON hIcon; // идентификатор пиктограммы LPCTSTR pszIcon; // идентификатор ресурса пиктограммы }; LPCTSTR pszCaption; // заголовок блокнота UINT nPages; // количество страниц в блокноте union { UINT nStartPage; // номер первой страницы LPCTSTR pStartPage; // имя первой страницы }; union { LPCPROPSHEETPAGE ppsp; // адрес массива структур // PROPSHEETPAGE HPROPSHEETPAGE FAR *phpage; // адрес массива }; // идентификаторов страниц блокнота PFNPROPSHEETCALLBACK pfnCallback; // адрес функции // обратного вызова } PROPSHEETHEADER, FAR *LPPROPSHEETHEADER; typedef const PROPSHEETHEADER FAR *LPCPROPSHEETHEADER;
В поле dwSize нужно записать размер структуры.
Поле dwFlags может содержать логическую комбинацию следующих значений:
Флаг | Описание |
PSH_DEFAULT | Используются все поля структуры, имеющие назначение, принятое по умолчанию |
PSH_MULTILINETABS | Закладки страниц могут располагаться в несколько рядов |
PSH_NOAPPLYNOW | Не отображается кнопка Apply |
PSH_PROPSHEETPAGE | При создании органа управления вместо поля phpage используется поле ppsp |
PSH_PROPTITLE | К заголовку, определенному в поле pszCaption, добавляется строка Properties for |
PSH_USECALLBACK | При инициализации органа управления используется функция обратного вызова, адрес которой указан в поле pfnCallback |
PSH_USEHICON | Поле hIcon содержит идентификатор пиктограммы уменьшенного размера, которая отображается в заголовке блокнота. Эта пиктограмма не нужна для органа управления Wizard |
PSH_USEICONID | Поле pszIcon содержит идентификатор htcehcf пиктограммы уменьшенного размера, которая отображается в заголовке блокнота. Эта пиктограмма не нужна для органа управления Wizard |
PSH_USEPSTARTPAGE | Вместо поля nStartPage используется поле pStartPage |
PSH_WIZARD | Создается орган управления Wizard, а не блокнот Property Sheet |
В приложении Property Sheet Demo мы заполнили структуру PROPSHEETHEADER следующим образом:
PROPSHEETHEADER psheetHeader; psheetHeader.dwSize = sizeof(PROPSHEETHEADER); psheetHeader.hInstance = hInst; psheetHeader.pszIcon = MAKEINTRESOURCE(IDI_APPICONSM); psheetHeader.dwFlags = PSH_USEICONID; psheetHeader.hwndParent = hWnd; psheetHeader.pszCaption = "Property Sheet Sample"; psheetHeader.nPages = sizeof(psheetPage) / sizeof(PROPSHEETPAGE); psheetHeader.phpage = (HPROPSHEETPAGE FAR *)&hPage[0];
Обратите внимание на поле phpage. В него мы записали адрес массива идентификаторов страниц HPROPSHEETPAGE. Этот массив заполняется функцией CreatePropertySheetPage, которой в качестве параметра передается адрес предварительно заполненной структуры PROPSHEETPAGE:
HPROPSHEETPAGE hPage[3]; hPage[0] = CreatePropertySheetPage(&psheetPage[0]); hPage[1] = CreatePropertySheetPage(&psheetPage[1]); hPage[2] = CreatePropertySheetPage(&psheetPage[2]);
После заполнения заголовка блокнота можно создать блокнот (или орган управления Wizard, если указан флаг PSH_WIZARD ) при помощи функции PropertySheet :
PropertySheet(&psheetHeader);
Есть еще один способ создания блокнота или органа управления Wizard без использования функции CreatePropertySheetPage. Мы применили этот способ в приложении Wizard Demo:
psheetHeader.dwSize = sizeof(PROPSHEETHEADER); psheetHeader.hInstance = hInst; psheetHeader.pszIcon = NULL; psheetHeader.dwFlags = PSH_PROPSHEETPAGE | PSH_WIZARD; psheetHeader.hwndParent = hWnd; psheetHeader.pszCaption = "Property Sheet Sample"; psheetHeader.nPages = sizeof(psheetPage) / sizeof(PROPSHEETPAGE); psheetHeader.ppsp = (LPCPROPSHEETPAGE)&psheetPage; PropertySheet(&psheetHeader);
Здесь мы не используем пиктограмму, так как она все равно не отображается, поэтому в поле pszIcon записано значение NULL. Кроме того, указаны флаги PSH_PROPSHEETPAGE и PSH_WIZARD.
Так как указан флаг PSH_PROPSHEETPAGE, то в поле ppsp мы записываем адрес массива структур PROPSHEETPAGE, описывающих отдельные страницы органа управления Wizard. При этом нам не нужен массив идентификаторов страниц и, соответственно, функция CreatePropertySheetPage, которая его заполняет.
Операционная система Windows 95 для программиста
Ниже мы перечислили основные действия, которое должно выполнить приложение для того чтобы создать орган управления Tree View:
создать окно на базе предопределенного класса окна WC_TREEVIEW ;
создать список изображений, которые будут использованы для отображения состояния строк;
добавить изображения в созданный список;
подключить список изображений к органу управления Tree View;
вставить элементы с учетом их иерархического расположения в дереве.
Подключение списка изображений
Созданный список изображений подключается к органу управления Tree View при помощи макрокоманды TreeView_SetImageList :
HIMAGELIST TreeView_SetImageList( HWND hwnd, // идентификатор окна органа Tree View HIMAGELIST himl, // идентификатор подключаемого списка int iImageTree); // тип изображений в списке
Первые два параметра макрокоманды задают, соответственно, идентификатор окна органа Tree View и идентификатор подключаемого списка, который мы только что создали и заполнили.
Параметр iImageTree может иметь следующие значения:
Значение | Описание |
TVSIL_NORMAL | Обычные список изображений для отображения выбранного и невыбранного состояния элементов дерева |
TVSIL_STATE | Список содержит изображения, которые используются для отображения состояния элементов дерева (из-за недостатка места в нашей книге не рассматривается) |
Ниже приведена строка исходного текста приложения, подключающая список изображений himl к органу управления hwndTree:
TreeView_SetImageList(hwndTree, himl, TVSIL_NORMAL);
Создание окна Tree View
Окно органа управления Tree View создается функцией CreateWindowEx на базе класса окна WC_TREEVIEW:
hwndTree = CreateWindowEx(0L, WC_TREEVIEW, "", WS_VISIBLE | WS_CHILD | WS_BORDER | TVS_HASLINES | TVS_HASBUTTONS | TVS_LINESATROOT, 0, 0, rc.right - rc.left, rc.bottom - rc.top, hWnd, (HMENU) IDC_TREEVIEW, hInst, NULL);
Перед вызовом этой функции (как и для других рассмотренных в этой книге органов управления) вы должны загрузить в память DLL-библиотеку COMCTL32.DLL . Это можно сделать при помощи функции InitCommonControls , вызвав ее без параметров.
Для окна органа управления Tree View вы можете использовать следующие дополнительные стили окна:
Стиль | Описание |
TVS_HASBUTTONS | Если указан стиль TVS_HASBUTTONS, создается кнопка небольших размеров, внутри которой может отображаться символ "+" или "-". Эта кнопка используется для того чтобы раскрыть или закрыть список вложенных элементов |
TVS_HASLINES | Для отображения иерархии элементов используются пунктирные линии |
TVS_LINESATROOT | Указанные выше линии используются и для присоединения корневого элемента дерева. Этот стиль должен использоваться совместно со стилем TVS_HASLINES |
TVS_EDITLABELS | Этот стиль позволяет пользователю редактировать название элемента списка |
TVS_SHOWSELALWAYS | Выбранные элементы списка отображаются с выделением даже в том случае, когда орган управления Tree View не активен |
TVS_DISABLEDROP | Если указан этот стиль, орган управления Tree View не посылает родительскому окну извещение TVN_BEGINDRAG |
Дополнительные стили окна могут комбинироваться между собой и со стандартными стилями окна при помощи логической операции ИЛИ.
С помощью пары функций GetWindowLong и SetWindowLong приложение может изменить стиль созданного ранее окна органа управления Tree View (можно использовать тот же способ, что и для органа управления List View).
Создание списка изображений
Для органа управления Tree View вы должны создать список изображений, которые будут использованы для отображения состояния элементов дерева.
Заметим, что каждый элемент может находиться в выбранном или невыбранном состоянии. Соответственно, для отображения этих состояний вы можете использовать разные изображения.
Список изображений создается рассмотренной нами ранее функцией ImageList_Create :
HIMAGELIST WINAPI ImageList_Create( int cx, // ширина изображения int cy, // высота изображения UINT flags, // тип изображения int cInitial, // первоначальное количество изображений int cGrow); // количество изображений, на которое // увеличится размер списка при добавлении новых изображений
Вот, например, как создается список из трех изображений размером 33 х 33 пиксела в нашем приложении Some Books, исходные тексты которого мы приведем немного позже:
himl = ImageList_Create(33, 33, FALSE, 3, 0);
Вставка элементов дерева
Вставка элементов в дерево выполняется при помощи макрокоманды TreeView_InsertItem , которая посылает органу управления Tree View сообщение TVM_INSERTITEM :
HTREEITEM TreeView_InsertItem( HWND hwnd, // идентификатор окна органа Tree View TV_INSERTSTRUCT lpis); // указатель на структуру TV_INSERTSTRUCT
Через параметр hwnd Этой макрокоманде необходимо передать идентификатор созданного органа управления Tree View, а через параметр lpis - адрес структуры типа TV_INSERTSTRUCT . Эта структура и указатель на нее определены так:
typedef struct _TV_INSERTSTRUCT { HTREEITEM hParent; // идентификатор родительского элемента HTREEITEM hInsertAfter; // идентификатор элемента, после // которого будет выполнена вставка TV_ITEM item; // информация о вставляемом элементе } TV_INSERTSTRUCT, FAR *LPTV_INSERTSTRUCT;
Если вставляется корневой элемент, поле hParent должно содержать значение TVI_ROOT или NULL. Если же вставляется вложенный элемент, в это поле необходимо записать идентификатор родительского элемента (т. е. элемента, расположенного выше по иерархии).
В поле hInsertAfter следует записать идентификатор элемента, после которого будет вставлен данный элемент или одно из следующих значений:
Значение | Расположение вставляемого элемента |
TVI_FIRST | В начале списка |
TVI_LAST | В конце списка |
TVI_SORT | По алфавитному порядку |
Структура item содержит информацию о вставляемом элементе. Соответствующий тип TV_ITEM определен следующим образом:
typedef struct _TV_ITEM { UINT mask // маска использования полей структуры TV_ITEM HTREEITEM hItem; // номер элемента UINT state; // текущее состояние элемента UINT stateMask; // маска состояния элемента LPSTR pszText; // адрес текстового буфера int cchTextMax; // размер текстового буфера int iImage; // номер изображения невыбранного элемента int iSelectedImage;// номер изображения выбранного элемента int cChildren; // флаг дочерних элементов LPARAM lParam;// 32-битное значение, связанное с элементом } TV_ITEM, FAR *LPTV_ITEM;
Назначение большинства полей такое же, как и одноименных полей структуры LV_ITEM.
Поле маски mask определяет, какие из полей структуры TV_ITEM будут использованы при добавлении элементов. Возможны следующие значения масок (их можно объединять при помощи логической операции ИЛИ):
Маска | Заполненное поле структуры TV_ITEM |
TVIF_TEXT | pszText |
TVIF_IMAGE | iImage |
TVIF_SELECTEDIMAGE | iSelectedImage |
TVIF_PARAM | lParam |
TVIF_CHILDREN | cChildren |
TVIF_HANDLE | hItem |
TVIF_STATE | state |
Поле cChildren содержит флаг, который показывает, есть ли у данного элемента связанные с ним дочерние элементы. Если есть, то в этом поле должно быть записано значение 1, в противном случае - 0. Если же в поле cChildren записать значение I_CHILDRENCALLBACK, приложение должно обрабатывать извещения TVN_GETDISPINFO и TVN_SETDISPINFO (аналогичные извещениям LVN_GETDISPINFO и LVN_SETDISPINFO, которые посылаются органу управления List View).
Для удобства в приложении Some Books мы добавляем элементы дерева при помощи функции InsTreeItem, исходный текст которой приведен ниже:
HTREEITEM InsTreeItem(HTREEITEM hParent, LPSTR szText, HTREEITEM hAfter, int iImage, int iSelectedImage) { TV_INSERTSTRUCT tvins; HTREEITEM hItem;
memset(&tvins, 0, sizeof(tvins)); tvins.item.mask = TVIF_TEXT | TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM; tvins.item.pszText = szText; tvins.item.cchTextMax = lstrlen(szText); tvins.item.iImage = iImage; tvins.item.iSelectedImage = iSelectedImage; tvins.hInsertAfter = hAfter; tvins.hParent = hParent;
hItem = TreeView_InsertItem(hwndTree, &tvins); return hItem; }
Процедура заполнения дерева в приложении Some Books выглядит следующим образом.
Вначале мы вставляем корневой элемент, которому соответствует строка szBooks ("Книги издательства АО 'Диалог-МИФИ'"):
hItem = InsTreeItem((HTREEITEM)TVI_ROOT, szBooks, (HTREEITEM)TVI_FIRST, idxBooks, idxBooks);
Этот элемент является корневым, поэтому параметр hParent имеет значение TVI_ROOT. Вставляемый элемент будет расположен первым по порядку, так как для параметра hAfter задано значение TVI_FIRST.
Далее мы вставляем два элемента, расположенные ниже по иерархии (первый уровень вложенности). Это текстовые строки szBSPSeries ("Библиотека системного программиста, (C) Фролов А.В., Фролов Г.В., 1991-1995 г.") и szStepSeries ("Персональный компьютер. Шаг за шагом, (C) Фролов А.В., Фролов Г.В., 1994-1995 г."):
hBSPItem = InsTreeItem(hItem, szBSPSeries, (HTREEITEM)TVI_SORT, idxBooks, idxBooks); hStepItem = InsTreeItem(hItem, szStepSeries, (HTREEITEM)TVI_SORT, idxBooks, idxBooks);
Затем для каждого элемента первого уровня вставляем по два элемента второго уровня:
InsTreeItem(hBSPItem, rgSPLBookInfo[0].szBookName, (HTREEITEM)TVI_SORT, idxBookClosed, idxBookOpened); InsTreeItem(hBSPItem, rgSPLBookInfo[1].szBookName, (HTREEITEM)TVI_SORT, idxBookClosed, idxBookOpened);
InsTreeItem(hStepItem, rgStepBookInfo[0].szBookName, (HTREEITEM)TVI_SORT, idxBookClosed, idxBookOpened); InsTreeItem(hStepItem, rgStepBookInfo[1].szBookName, (HTREEITEM)TVI_SORT, idxBookClosed, idxBookOpened);
Так как в параметре hAfter указана константа TVI_SORT, все добавленные элементы будут отображаться в алфавитном порядке.
Заполнение списка изображений
Обычно в список изображений для органа управления Tree View добавляют не пиктограммы, имеющие квадратную форму и относительно большой размер, и битовые изображения. Это можно сделать при помощи макрокоманды ImageList_Add :
hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BOOKS)); idxBooks = ImageList_Add(himl, hBmp, NULL);
hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BOOK_CLOSED)); idxBookClosed = ImageList_Add(himl, hBmp, NULL);
hBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BOOK_OPENED)); idxBookOpened = ImageList_Add(himl, hBmp, NULL);
Эта макрокоманда возвращает номера добавленных изображений, которые потребуются в дальнейшем при добавлении элементов в дерево.