Операционная система Microsoft Windows 3.1 для программиста. Дополнительные главы

         

Функция обратного вызова DDEML


Когда сервер или клиент регистрирует себя в библиотеке DDEML при помощи функции DdeInitialize, он указывает адрес переходника, созданного для функции обратного вызова. Функция обратного вызова предназначена для обработки всех событий, возникающих в процессе создания каналов связи и передачи данных.

В простейшем случае функция обратного вызова сервера DDEML может выглядеть следующим образом:

HDDEDATA EXPENTRY _export DDEServerCallback( WORD wType, // код транзакции WORD wFmt, // формат данных HCONV hConv, // идентификатор канала HSZ hsz1, // первый идентификатор строки HSZ hsz2, // второй идентификатор строки HDDEDATA hData, // идентификатор глобальной области данных DWORD dwData1, // первое дополнительное двойное слово DWORD dwData2) // второе дополнительное двойное слово { switch(wType) { // Создание канала передачи данных case XTYP_CONNECT: { ........... return((HDDEDATA)TRUE); }

// Запрос данных от сервера case XTYP_REQUEST: { ........... return(hData); }

// Запрос на выполнение команды case XTYP_EXECUTE: ........... break;

// Передача данных серверу case XTYP_POKE: { ........... return((HDDEDATA)DDE_FACK); }

// Подтверждение создания канала case XTYP_CONNECT_CONFIRM: { ........... break; }

// Завершение работы канала case XTYP_DISCONNECT: { ........... break; }

// Ошибка case XTYP_ERROR: { ........... break; } } return((HDDEDATA)NULL); }

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

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

Второй параметр задает код формата передаваемых данных. Для кодов формата используются те же значения, что и для форматов Clipboard, например, CF_TEXT.

Через параметр hConv передается идентификатор канала передачи данных (который мы еще не научились создавать).




Назначение остальных параметров функции обратного вызова зависит от кода транзакции. В приведенном выше фрагменте кода используются символические имена кодов транзакций, определенные в файле ddeml.h, и имеющие префикс имени XTYP. В дальнейшем мы подробно расскажем о некоторых транзакциях, использованных в наших приложениях.

Функция обратного вызова для клиента DDEML выглядит точно также, отличаясь лишь составом обрабатываемых транзакций. Приведем для примера исходный текст такой функции из нашего приложения DDEMLCL:

HDDEDATA EXPENTRY DDEClientCallback( WORD wType, WORD wFmt, HCONV hConv, HSZ hsz1, HSZ hsz2, HDDEDATA hData, DWORD dwData1, DWORD dwData2) { switch(wType) { case XTYP_DISCONNECT: return((HDDEDATA) NULL);

case XTYP_ERROR: break;

case XTYP_XACT_COMPLETE: break; } return((HDDEDATA)NULL); }

При регистрации приложения в библиотеке DDEML функцией DdeInitialize можно указать флаги, запрещающие или разрешающие поступление транзакций некоторых типов в функцию обратного вызова. Запретив вызов функции обратного вызова для необрабатываемых транзакций, можно ускорить работу приложения.




Функция окна Document Window


Функция окна Document Window очень похожа на функцию обычного дочернего окна, но все необработанные ей сообщения должны передаваться функции DefMDIChildProc (а не функции DefWindowProc). Функция DefMDIChildProc обрабатывает сообщения WM_CHILDACTIVATE, WM_GETMINMAXINFO, WM_MENUCHAR, WM_MOVE, WM_NEXTMENU, WM_SETFOCUS, WM_SIZE, WM_SYSCOMMAND. Перечисленные сообщения должны в обязательном порядке передаваться функции DefMDIChildProc, даже если функция окна Document Window обрабатывает их самостоятельно.

Основная задача функции окна Document Window - обработка сообщений, предназначенных активному окну Document Window, в том числе поступающих из функции окна Frame Window. В частности, эта функция выполняет обработку сообщения WM_COMMAND.



Функция окна Frame Window


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

Во-первых, вместо функции DefWindowProc все необработанные сообщения должны передаваться специальной функции DefFrameProc. Последняя выполняет обработку таких сообщений, как WM_COMMAND, WM_MENUCHAR, WM_NEXTMENU, WM_SETFOCUS, WM_SIZE, обеспечивая соответствующую реакцию на них окон Document Window.

Во-вторых, только что перечисленные сообщения нужно всегда передавать функции DefFrameProc, даже если функция окна Frame Window обрабатывает их самостоятельно. Изъятие этих сообщений приведет к неправильной работе MDI-приложения.

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



Функция WinHelp


Приложения Windows обращаются к справочной системе с помощью специально предназначенной для этого функции WinHelp. Когда приложение вызывает эту функцию, происходит запуск приложения winhelp.exe. Вызывая функцию WinHelp, приложение может отобразить на экране любую информацию, обычно доступную пользователю через меню или кнопки окна Toolbar приложения winhelp.exe.

Хорошо спроектированное приложение имеет меню "Help", с помощью которого пользователь может получить доступ к оглавлению и предметному указателю справочной системы. Кроме этого, обычно имеется возможность получения так называемой контекстно-зависимой справочной информации. Если вы работали со справочной системой, входящей в состав Borland C++ или Microsoft Visual C++, то вы уже знаете, что это такое.

Когда пользователь нажимает клавишу <F1> или <Shift+F1>, приложение выдает справочную информацию, которая зависит от того, в каком состоянии находятся органы управления приложения, какая строка какого меню выделена, около какого слова в редактируемом тексте находится курсор и т.п.

Например, если вы раскрыли меню "File" и выделили строку "Open...", а затем нажали клавишу <F1>, скорее всего, в любом стандартном приложении вы увидите на экране раздел справочной системы, рассказывающей о том, как пользоваться строкой "Open..." из меню "File".

И это именно такое поведение, которое пользователь ожидает от вашего приложения. Для пользователя важно получить подсказку в каждой конкретной ситуации, нажимая при этом на одну и ту же (для всех приложений Windows) функциональную клавишу <F1>. И если вместо этого пользователь каждый раз будет попадать в раздел оглавления и искать нужную информацию в дебрях гипертекстовой системы, он едва ли будет в восторге.

Функция WinHelp позволяет организовать поиск нужного раздела в справочной системе и отображение его на экране, но и только. Задача отслеживания текущего состояния органов управления и организации контекстно-зависимой подсказки целиком ложится на плечи разработчика приложения.



Гиперграфика


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

Для создания гиперграфического изображения используется специальный редактор Microsoft Hotspot Editor (приложение shed.exe, поставляется в составе практически всех средств разработки приложений Windows). Получая на входе файл с битовым изображением или метафайлом, этот редактор создает файл с расширением имени shg, содержащий кроме изображения описание размеров и расположения чувствительных зон и соответствующих им строк контекста.

Так как практически гиперграфика используется достаточно редко, ради экономии места в книге мы оставим этот материал на самостоятельное изучение. Всю необходимую информацию вы сможете получить в руководстве Help Compiler Guide, которое поставляется в составе SDK (а также из справочной системы редактора Microsoft Hotspot Editor).



Глобальные переменные


Если вы создаете собственные макрокоманды (как функции DLL-библиотеки), вам может пригодиться информация о внутренних переменных winhelp.exe. Эти переменные можно передавать как параметры стандартным и созданным вами макрокомандам.

Приведем список внутренних переменных.

Имя переменной Тип Описание
hwndApp U Идентификатор главного окна приложения winhelp.exe. Можно пользоваться только во время выполнения функции, вызываемой из DLL-библиотеки
hwndContext U Идентификатор текущего активного окна приложения winhelp.exe
qchPath S Путь к hlp-файлу
qError S Дальний указатель на структуру, содержащую информацию об ошибке, возникшей при последнем обращении к winhelp.exe
lTopicNo U Номер раздела
hfs U Идентификатор файловой системы для текущего hlp-файла
coForeground U Основной цвет окна
coBackground U Фоновый цвет окна



Операционная система Microsoft Windows 3.1 для программиста. Дополнительные главы


Переход на отметку в тексте (закладку) с именем marktext, установленную макрокомандой SaveMark.



Графические изображения


Встроенная в Windows справочная система позволяет использовать 16-цветные графические изображения (битовые и метафайлы) как для иллюстрации, так и в качестве средства организации ссылок на разделы.



HelpOn()


Отображение содержимого справочной системы, содержащей информацию об использовании приложения winhelp.exe.



History()


Отображение диалоговой панели "History", позволяющей выбрать один из нескольких только что просмотренных разделов справочной системы.



Идентификация окон Document Window


Обычно приложение связывает с каждым окном Document Window определенную структуру, в которой хранятся сведения о содержимом и состоянии каждого конкретного экземпляра окна Document Window.

При регистрации класса окна Document Window вы можете использовать поле cbClsExtra структуры WNDCLASS, определив в нем место, достаточное для хранения указателя на структуру, содержащую перечисленную выше информацию. Эта структура должна инициализироваться каждый раз при создании нового окна Document Window. Для записи адреса структуры в память, зарезервированную для окна, используйте функцию SetWindowLong. Для доступа к структуре в этом случае можно вызвать функцию GetWindowLong.

Если вы получаете память для структуры динамически, не забывайте отдавать ее системе при уничтожении окна Document Window.



IfThen(...)


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

IfThen(IsMark("markertext", "macro")

Параметры:

Параметр Описание
markertext Маркер, который проверяется макрокомандой IsMark
macro Имя макрокоманды, которая будет выполнена, если указанный маркер существует



IfThenElse(...)


Запуск одной из двух макрокоманд в зависимости от существования маркера. Обычно используется вместе с макрокомандой IsMark:

IfThenElse(IsMark("markertext", "macro1", "macro2")

Параметры:

Параметр Описание
markertext Маркер, который проверяется макрокомандой IsMark
macro1 Имя макрокоманды, которая будет выполнена, если указанный маркер существует
macro2 Имя макрокоманды, которая будет выполнена, если указанный маркер не существует



Инициализация драйвера


Sys_Critical_Init

Системная критическая инициализация в состоянии с запрещенными прерываниями

Device_Init

Инициализация в состоянии с разрешенными прерываниями

Init_Complete

Завершение инициализации



Инициализация и создание канала связи


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

В процессе инициализации сервер должен выполнить такие действия:

зарегистрировать себя в библиотеке DDEML;

зарегистрировать предоставляемый сервис, которым сможет воспользоваться приложение-клиент

Клиент должен сделать следующее:

зарегистрировать себя в библиотеке DDEML;

создать канал связи с сервером, указав необходимый сервис

Рассмотрим эти действия подробнее.



Инициализация MDI-приложения


В процессе инициализации MDI-приложения вам надо зарегистрировать как минимум два класса окна - класс окна Frame Window (главного окна приложения) и класс окна Document Window.

Регистрация окна Frame Window может выполняться, например, следующим образом:

ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации класса окна memset(&wc, 0, sizeof(wc)); wc.lpszMenuName = "APP_MENU"; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC)FrameWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APP_ICON"); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1); wc.lpszClassName = (LPSTR)szFrameClassName; aWndClass = RegisterClass(&wc);

В классе окна Frame Window, как правило, указывается меню приложения, хотя это меню может быть создано динамически. В поле lpfnWndProc следует записать адрес функции окна Frame Window. Эта функция имеет особенности, о которых мы расскажем позже.

Остальные поля структуры WNDCLASS заполняются обычным образом. Отметим только, что для цвета фона окна имеет смысл использовать константу COLOR_APPWORKSPACE. В этом случае для управления фоном окна можно будет использовать приложение Control Panel, что даст пользователю возможность настраивать цвета по своему вкусу.

Регистрация класса для окна Document Window выполняется, например, так:

memset(&wc, 0, sizeof(wc)); wc.lpszMenuName = 0; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC)ChildWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = sizeof(WORD); wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APPCLIENT_ICON"); wc.hCursor = LoadCursor(NULL, IDC_ARROW); wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); wc.lpszClassName = (LPSTR)szChildClassName; aWndClass = RegisterClass(&wc);

В поле lpfnWndProc вы должны указать адрес функции окна Document Window, которая, как и функция окна Frame Window, имеет свои особенности.




Обратите внимание на поле hIcon. Так как окно Document Window может быть минимизировано пользователем (свернуто в пиктограмму), вы должны определить эту пиктограмму в классе окна Document Window. Если приложение создает окна Document Window на базе нескольких классов и эти окна будут использованы для отображения документов различного типа, для каждого класса имеет смысл определить свою пиктограмму.

Для определения цвета фона окна Document Window мы рекомендуем воспользоваться константой COLOR_WINDOW. При этом пользователь сможет управлять цветом фона окна Document Window при помощи приложения Control Panel.

Заметим, что приложение, которое "ведет себя хорошо", не навязывает пользователю вкусы разработчика приложения, а позволяет ему выполнить настройку внешнего вида самостоятельно. Для того чтобы ваше MDI-приложение было похоже по внешнему виду на стандартные (такие как, например, Program Manager), используйте системные цвета.

Итак, мы зарегистрировали класс для главного окна приложения Frame Window и один или несколько классов для создания окон Document Window. На следующем этапе инициализации нужно создать окна Frame Window и Client Window.

Окно Frame Window создается точно также, как и главное окно обычного приложения. Например:

hwndFrame = CreateWindow( szFrameClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, 0, // задаем размеры и расположение CW_USEDEFAULT, 0, // окна, принятые по умолчанию 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL); // указатель на дополнительные параметры

Для создания окна Client Window необходимо использовать предопределенный класс окна "MDICLIENT":

CLIENTCREATESTRUCT clcs; clcs.hWindowMenu = GetSubMenu(GetMenu(hwnd), ID_WINDOWMENU); clcs.idFirstChild = ID_MDIWINDOW;

hwndClient = CreateWindow( "MDICLIENT", // имя класса окна NULL, // заголовок окна WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | // стиль окна WS_HSCROLL | WS_VSCROLL, 0, 0, 0, 0, hwnd, // идентификатор родительского окна (HMENU)ID_CLIENTWINDOW, // идентификатор дочернего окна hInst, // идентификатор приложения (LPSTR)&clcs); // указатель на дополнительные параметры



Для окна Client Window не нужно указывать заголовок, зато следует использовать стиль WS_CLIPCHILDREN (так как функция этого окна не будет рисовать поверх окон Document Window). Размеры окна не имеют значения, потому что они автоматически устанавливаются равными размерам внутренней области (client region) окна Frame Window.

Так как окно Client Window является дочерним по отношению к окну Frame Window, в девятом параметре функции CreateWindow необходимо указать идентификатор. Можно использовать произвольное значение, не конфликтующее с идентификаторами других дочерних окон, создаваемых окном Frame Window (например, с идентификаторами окон Toolbar и Statusbar).

Через последний параметр функции CreateWindow следует передать указатель на предварительно проинициализированную структуру CLIENTCREATESTRUCT.

Поле hWindowMenu этой структуры должно содержать идентификатор временного меню "Window", которое определено для любого стандартного MDI-приложения. По мере создания окон Document Window это меню будет дополняться снизу строками, состоящими из заголовков окон Document Window. Эти строки можно использовать для выбора и активизации нужного окна Document Window.

Как получить нужный идентификатор?

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

Далее, пользуясь этим идентификатором и зная порядок расположения временных меню (pop up menu) в главном меню приложения, с помощью функции GetSubMenu можно легко получить идентификатор для любого временного меню. В качестве второго параметра функции GetSubMenu следует передать порядковый номер временного меню. Самому левому временному меню соответствует номер 0, следующему - 1, и т.


д. Поэтому идентификатор ID_WINDOWMENU должен быть равен порядковому номеру временного меню "Window" в главном меню приложения.

Теперь о поле idFirstChild.

При создании окон Document Window они получают идентификаторы (как и обычные дочерние окна). Первое созданное окно Document Window получает идентификатор, определенный в поле idFirstChild при создании окна Client Window. Идентификаторы других окон получаются последовательным увеличением значения, заданного в этом поле.

А что произойдет, если одно из окон Document Window будет уничтожено?

В этом случае идентификаторы оставшихся окон Document Window будут изменены таким образом, чтобы они по-прежнему монотонно возрастали начиная со значения idFirstChild.

Основное правило для выбора идентификатора первого окна Document Window состоит в том, что идентификаторы окон Document Window не должны конфликтовать с идентификаторами органов управления и строк меню, создаваемых приложением. Поэтому для инициализации поля idFirstChild следует выбрать такое значение, которое заведомо больше значений других идентификаторов.


Инициализация реального режима


Процедура инициализации реального режима выводит текстовую строку, пользуясь функцией 09h прерывания INT21h. Как правило, виртуальные драйверы не отображают никаких сообщений, если инициализация выполняется без ошибок.



Инициализация виртуальной машины


Create_VM

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

VM_Critical_Init

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

VM_Init

Третье сообщение, которое посылается драйверу при создании новой виртуальной машины. Используется для инициализации аппаратуры, используемой виртуальной машиной

Sys_VM_Init

Это сообщение приходит после сообщения Init_Complete. Используется для инициализации программного и аппаратного обеспечения в системной виртуальной машине. Если перед выходом из процедуры установить флаг переноса в единицу, создание системной виртуальной машины будет отменено, что приведет к завершению работы Windows



InsertItem("menuid"


Вставка новой строки в существующее меню в заданной позиции.

Параметры:

Параметр Описание
menuid Имя меню, использованное в макрокоманде InsertMenu, или одно из имен стандартных меню: mnu_file, mnu_edit, mnu_bookmark, mnu_helpon.
itemid Имя, идентифицирующее строку меню
itemname Текстовая строка, отображаемая в новой строке меню
macro Имя макрокоманды, которая будет выполнена после того как пользователь выберет из меню вставленную строку
pos Позиция, где будет вставлена строка. Значение 0 соответствует самой верхней позиции в меню



InsertMenu("menuid", "menuname", pos)


Добавление нового временного меню к главному меню приложения winhelp.exe.

Параметры:

Параметр Описание
menuid Идентификатор добавляемого меню
menuname Текстовая строка названия меню
pos Позиция, где будет вставлено меню. Значение 0 соответствует самой левой позиции в главном меню приложения winhelp.exe



IsMark("marktext")


Эта макрокоманда проверяет, существует ли маркер (закладка), указанная в параметре marktext и определенная макрокомандой SaveMark. Используется совместно с макрокомандами IfThen и IfThenElse.



Использование изображений для ссылок на раздел


Ранее мы рассмотрели способ организации гипертекстовых ссылок между разделами с использованием специальным образом выделенных текстовых строк. Однократно или двукратно подчеркнутая строка в сочетании со строкой контекста, оформленной скрытым текстом, служит в качестве чувствительной точки (hot spot). При выборе такого изображения мышью произойдет переход к другому разделу (или временному окну).

В качестве такой чувствительной точки можно использовать графическое изображение, вставленное при помощи ссылки на имя bmp-файла:

В данном примере битовое изображение logo.bmp используется для организации гипертекстового перехода к разделу с контекстом "missing".



Изменение состояния виртуальной машины


VM_Suspend

Работа виртуальной машины приостановлена

VM_Resume

Работа виртуальной машины возобновлена

Set_Device_Focus

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

Begin_Message_Mode

Это сообщение поступает, когда виртуальный драйвер Shell отображает диалоговую панель с сообщением для пользователя и не может использовать для этого системную виртуальную машину

End_Message_Mode

Сообщение приходит, когда больше не нужно отображать указанную выше диалоговую панель

Reboot_Processor

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



Изменения в цикле обработки сообщений


Время от времени мы вносили небольшие изменения в самую "устойчивую" часть приложения - цикл обработки сообщений. Наши очередные нововведения касаются трансляции сообщений для MDI-приложений. Специальная трансляция сообщений требуется для обеспечения стандартного клавиатурного интерфейса MDI-приложений.

В простейшем случае цикл обработки сообщений MDI-приложения может выглядеть следующим образом:

while(GetMessage(&msg, NULL, 0, 0)) { if(!TranslateMDISysAccel(hwndClient, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }

Как видите, перед тем как передать сообщение функции TranslateMessage, приложение предварительно обрабатывает его функцией TranslateMDISysAccel:

BOOL TranslateMDISysAccel(HWND hwndClient, MSG FAR* lpmsg);

В качестве первого параметра функции передается идентификатор окна Client Window, который был возвращен функцией CreateWindow при создании этого окна. Второй параметр - указатель на структуру MSG, содержащую обрабатываемое сообщение.

Назначение функции TranslateMDISysAccel заключается в преобразовании клавиатурных сообщений WM_KEYDOWN и WM_KEYUP в сообщения WM_SYSCOMMAND, что необходимо для нормальной работы акселераторов, назначенных для строк системного меню окон Document Window.

Заметьте, что строки системного меню имеют акселераторы, аналогичные строкам системного меню главного окна приложения. Вместо клавиши <Alt> в них используется клавиша <Ctrl>. Например, для выбора строки "Close" в системном меню обычного окна используется комбинация клавиш <Alt + F4>, а для выбора этой же строки в системном меню окна Document Window предназначена комбинация клавиш <Ctrl + F4>.

Если функция TranslateMDISysAccel выполнила преобразование, она возвращает значение TRUE. В этом случае для данного сообщения уже не нужно вызывать функции TranslateMessage и DispatchMessage.

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

while(GetMessage(&msg, NULL, 0, 0)) { if(!TranslateMDISysAccel(hwndClient, &msg) && !TranslateAccelerator(hwndFrame, hAccel, &msg)) { TranslateMessage(&msg); DispatchMessage(&msg); } }



Элементы справочной системы


В этом разделе мы опишем элементы справочной системы, доступные пользователю (о том, как работать с приложением winhelp.exe, вы можете также прочитать в руководстве пользователя Windows или в нашей книге "Операционная система Microsoft Windows. Руководство пользователя", которая вышла вторым томом в составе серии "Персональный компьютер - шаг за шагом").



JumpContents("hlp_filename")


Отображение раздела, выполняющего роль оглавления справочной системы, расположенной в hlp-файле с именем hlp_filename.



JumpContext("filename", contextnumber)


Переход к разделу, номер контекста которого равен contextnumber. Расположение раздела (имя hlp-файла) определяется параметром filename.

Алиас: JC.

Параметры:

Параметр Описание
filename Имя hlp-файла
contextnumber Номер контекста раздела в указанном hlp-файле. Этот номер должен быть определен в разделе MAP файла проекта *.hpj справочной системы

Немного о секции MAP и о номере контекста раздела.

Секция файла проекта справочной системы MAP предназначена для установки соответствия между строками контекста и номерами контекста. Номер контекста - это численное значение, которое используется приложением для ссылки на раздел справочной системы.

Приведем пример оформления секции MAP:

[MAP] file_wnd 0x01 edit_wnd 0x02 view_wnd 0x03 content_topic 100

Для номеров контекста можно использовать шестнадцатиричные или десятичные числа.

Допускается также включать в секцию MAP incude-файлы в стандарте Си, содержащие определения констант. Для этого необходимо использовать обычный оператор #include:

#include <menuid.h>



JumpHelpOn()


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



JumpId("filename", "contextstring")


Переход к разделу с контекстом contextstring. Расположение раздела (имя hlp-файла) определяется параметром filename.

Алиас: JI.

Параметры:

Параметр Описание
filename Имя hlp-файла
contextstring Строка контекста раздела, на который выполняется переход



JumpKeyword("filename", "keyword")


Поиск раздела по ключевому слову keyword. Расположение раздела (имя hlp-файла) определяется параметром filename.

Алиас: JK.

Параметры:

Параметр Описание
filename Имя hlp-файла
keyword Ключевое слово, по которому выполняется поиск раздела справочной системы



Как учесть разрешение монитора


Разработчики приложений Windows работают на мощных компьютерах, оснащенных современными 17-дюймовыми мониторами и видеоконтроллерами, способными работать при разрешении, скажем, 1280х1024 пикселов (ну хорошо, не все разработчики так хорошо живут, но многие). И вот, подготовив в одном из режимов работы монитора с высоким разрешением прекрасное битовое изображение, они вставляют его в справочную систему и поставляют пользователям. А у тех обычно техника выглядит поскромнее.

Запустив такую справочную систему на компьютере с монитором EGA (что еще не самый плохой вариант!), пользователи сталкиваются с искажениями графических изображений. Рисунки, подготовленные в режиме с высоким разрешением, не помещаются на экране компьютера!

Для выхода из подобной ситуации Microsoft разработал специальный формат файла, в котором предусмотрено хранение нескольких битовых изображений с разным разрешением в одном файле.

Компилятор Microsoft Windows Multiple Resolution Bitmap Compiler (файл mrbc.exe) может объединить несколько bmp-файлов, подготовленных в режимах с разным разрешением, в один файл с расширением имени mrb.

Если такой файл вставить в исходный текст справочной системы при помощи ссылки (команды bmc, bml, bmr и т. п.), при отображении будет использоваться то изображение, разрешение которого соответствует текущему разрешению монитора.

Мы рассмотрим самый простой способ использования компилятора mrbc.exe - пакетный (есть и другой, интерактивный).

Подготовьте 4 варианта одного и того же графического изображения (в формате DIB) в режимах CGA, EGA, VGA и 8514. Вы должны создать 4 bmp-файла.

Затем измените расширение имени файла так, чтобы первая буква расширения указывала на использованное при подготовке файла разрешение:

Первая буква расширения имени Разрешение Пример имени файла
c CGA nicebmp.cga
e EGA nicebmp.ega
v VGA nicebmp.vga
8 8514 nicebmp.854

Затем запустите компилятор в пакетном режиме:

mrbc /s nicebmp.cga nicebmp.ega nicebmp.vga nicebmp.854

Получившийся в результате файл nicebmp.mrb можно использовать для ссылки в исходном тексте справочной системы.



Компиляция исходных файлов проекта


Последний шаг на пути к созданию hlp-файла - компиляция исходных файлов, описанных в файле проекта *.hpj. Это самый простой шаг, который сводится к вызову компилятора Help Compiler:

hc31 helpprj

где helpprj - имя файла проекта справочной системы (расширение имени .hpj можно не указывать).

Для того чтобы у вас не было проблем с нехваткой оперативной памяти и совместимостью с Windows версии 3.1, используйте компилятор Windows Help Compiler версии 3.10.504 или более свежей версии. Лучше всего запускать компилятор из DOS-окна в среде Windows, так как в этом случае ему будет доступна расширенная память.

Сделайте текущим каталог hlpfile и запустите компилятор hc31.exe, передав ему в качестве параметра имя файла hlpfile.hpj. В результате работы компилятора при условии отсутствия грубых ошибок в исходных файлах будет создан файл справочной системы hlpfile.hlp.

Запустите приложение winhelp.exe и выберите при помощи меню "File" только что созданный файл hlpfile.hlp. Вы увидите раздел оглавления справочной системы (рис. 4.20).

Рис. 4.20. Раздел оглавления файла hlpfile.hlp

Проверьте работу кнопок просмотра последовательностей разделов и ссылки, убедившись в том, что все работает правильно. Если что-то не так, скорее всего, вы допустили ошибку при подготовке исходных файлов.



Контекст виртуальной машины


Для каждой виртуальной машины при переключении задачи сохраняется текущее состояние всех ее регистров (контекст виртуальной машины) в стеке нулевого кольца защиты. Адрес области памяти, содержащей контекст виртуальной машины, передается в регистре EBP.

Форматы соответствующих структур определены в файле vmm.inc (структуры Client_Reg_Struc, Client_Word_Reg_Struc, Client_Byte_Reg_Struc). В них определены следующие поля:

Client_EDI Client_SS
Client_ESI Client_ES
Client_EBP Client_DS
Client_EBX Client_FS
Client_EDX Client_GS
Client_ECX Client_Alt_EIP
Client_EAX Client_Alt_CS
Client_Error Client_Alt_ESP
Client_EIP Client_Alt_SS
Client_CS Client_Alt_ES
Client_EFlags Client_Alt_DS
Client_ESP Client_Alt_FS
Client_Alt_EFlags Client_Alt_GS
Client_DI Client_IP
Client_SI Client_Flags
Client_BP Client_SP
Client_BX Client_Alt_IP
Client_DX Client_Alt_Flags
Client_CX Client_Alt_SP
Client_AX  
Client_BL Client_CL
Client_BH Client_CH
Client_DL Client_AL
Client_DH Client_AH

Обращение к этим полям не вызывает никаких проблем:

movzx eax, [ebp.Client_AX]

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

Конкретные примеры процедур, работающих с контекстом виртуальной машины, мы приведем при описании исходного текста виртуального драйвера VXDSRV.



Копирование строк


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

Процедура CopyParm предназначена для копирования строки параметров запускаемого приложения Windows.

Строка параметров копируется в буфер, физически расположенный в сегменте данных DLL-библиотеки d2w.dll. Этот буфер состоит из четырех полей. Через первый байт буфера передается буква текущего диска. Следующие 64 байта предназначены для передачи текущего каталога (на момент запуска приложения Windows). Третье поле имеет размер 128 байт и содержит путь к запускаемому приложению Windows. Последнее поле также имеет размер 128 байт и содержит параметры запускаемого приложения. Именно в это поле и записывает параметры процедура CopyParm.



Файл mdiapp/mdiapp.cpp


// ============================================================ // Простейшее MDI-приложение // ============================================================

#define STRICT #include <windows.h>
#include <mem.h>
#include "mdiapp.hpp"

// Прототипы функций BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export FrameWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK _export ChildWndProc(HWND, UINT, WPARAM, LPARAM);

// Имена классов окна char const szFrameClassName[] = "MDIAppClass"; char const szChildClassName[] = "MDIchildAppClass";

// Заголовок окна char const szWindowTitle[] = "Simple MDI Application";

HINSTANCE hInst;

HWND hwndFrame; // окно Frame Window HWND hwndClient; // окно Client Window HWND hwndChild; // окно Child Window

// ===================================== // Функция WinMain // ===================================== #pragma argsused

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями

hInst = hInstance; // сохраняем идентификатор приложения

if(hPrevInstance) // может быть запущена return FALSE; // только одна копия приложения

// Инициализируем приложение if(!InitApp(hInstance)) return FALSE;

// Создаем главное окно приложения - Frame Window hwndFrame = CreateWindow( szFrameClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, 0, // задаем размеры и расположение CW_USEDEFAULT, 0, // окна, принятые по умолчанию 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL);
// указатель на дополнительные параметры

// Если создать окно не удалось, завершаем приложение if(!hwndFrame) return FALSE;

// Рисуем главное окно ShowWindow(hwndFrame, nCmdShow);
UpdateWindow(hwndFrame);

// Запускаем цикл обработки сообщений while(GetMessage(&msg, NULL, 0, 0)) { // Трансляция для MDI-приложения if(!TranslateMDISysAccel(hwndClient, &msg)) { TranslateMessage(&msg);
DispatchMessage(&msg);
} } return msg.wParam; }




// ===================================== // Функция InitApp // Выполняет регистрацию классов окон // =====================================

BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна

// Регистрируем класс для главного окна приложения // (т. е. для окна Frame Window) memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = "APP_MENU"; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC)FrameWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APP_ICON");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
wc.lpszClassName = (LPSTR)szFrameClassName; aWndClass = RegisterClass(&wc);

if(!aWndClass) return FALSE;

// Регистрируем класс окна для дочернего окна Document Window memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = 0; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC)ChildWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APPCLIENT_ICON");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szChildClassName; aWndClass = RegisterClass(&wc);

if(!aWndClass) return FALSE;

return TRUE; }

// ===================================== // Функция FrameWndProc // =====================================

LRESULT CALLBACK _export FrameWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Структура для создания окна Client Window CLIENTCREATESTRUCT clcs;

// Структура для создания дочернего окна Document Window MDICREATESTRUCT mdics;

switch (msg) { // При создании окна Frame Window создаем // окно Client Window, внутри которого будут создаваться // дочерние окна Document Window case WM_CREATE: { // Получаем и сохраняем в структуре clcs идентификатор // временного меню "Window". Так как это меню второе // слева, его позиция равна 1 (меню "File" имеет // позицию 0) clcs.hWindowMenu = GetSubMenu(GetMenu(hwnd), 1);



// Идентификатор первого дочернего окна Document Window clcs.idFirstChild = 500;

// Создаем окно Client Window hwndClient = CreateWindow( "MDICLIENT", // имя класса окна NULL, // заголовок окна WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | // стиль окна WS_HSCROLL | WS_VSCROLL, 0, 0, 0, 0, hwnd, // идентификатор родительского окна (HMENU)1, // идентификатор меню hInst, // идентификатор приложения (LPSTR)&clcs);
// указатель на дополнительные параметры break; }

// Обработка сообщений от главного меню приложения case WM_COMMAND: { switch (wParam) { // Создание нового окна Document Window case CM_FILENEW: { // Заполняем структуру MDICREATESTRUCT mdics.szClass = szChildClassName; // класс окна mdics.szTitle = "MDI Child Window"; // заголовок окна mdics.hOwner = hInst; // идентификатор приложения mdics.x = CW_USEDEFAULT; // размеры окна mdics.y = CW_USEDEFAULT; // Document Window mdics.cx = CW_USEDEFAULT; mdics.cy = CW_USEDEFAULT; mdics.style = 0; // дополнительные стили mdics.lParam = NULL; // 32-битное значение

// Посылаем сообщение WM_MDICREATE окну Client // Window. В результате будет создано // новое окно Document Window hwndChild = (HWND)SendMessage(hwndClient, WM_MDICREATE, 0, (LPARAM)&mdics);

break; }

// Размещение окон Document Window рядом друг с другом case CM_WINDOWTILE: { SendMessage(hwndClient, WM_MDITILE, 0, NULL);
break; }

// Размещение окон Document Window с перекрытием case CM_WINDOWCASCADE: { SendMessage(hwndClient, WM_MDICASCADE, 0, NULL);
break; }

// Размещение пиктограмм минимизированных // окон Document Window // в нижней части окна Client Window case CM_WINDOWICONS: { SendMessage(hwndClient, WM_MDIICONARRANGE, 0, NULL);
break; }

// Уничтожение всех окон Document Window case CM_WINDOWCLOSEALL: { HWND hwndTemp;

// Скрываем окно Client Window для того чтобы // избежать многократной перерисовки окон Document // Window во время их уничтожения ShowWindow(hwndClient, SW_HIDE);

for(;;) { // Получаем идентификатор дочернего окна // для окна Client Window hwndTemp = GetWindow(hwndClient, GW_CHILD);
// Если дочерних окон больше нет, выходим из цикла if(!hwndTemp) break;



// Пропускаем окна-заголовки while(hwndTemp && GetWindow(hwndTemp, GW_OWNER)) hwndTemp = GetWindow(hwndTemp, GW_HWNDNEXT);

// Удаляем дочернее окно Document Window if(hwndTemp) SendMessage(hwndClient, WM_MDIDESTROY, (WPARAM)hwndTemp, NULL);
else break; }

// Отображаем окно Client Window ShowWindow(hwndClient, SW_SHOW);
break; }

case CM_HELPABOUT: { MessageBox(hwnd, "Приложение MDIAPP\n(C) Фролов А.В., 1995", "Simple MDI Application", MB_OK | MB_ICONINFORMATION);
break; }

// Завершаем работу приложения case CM_FILEEXIT: { DestroyWindow(hwnd);
break; }

default: break; }

// Определяем идентификатор // активного окна Document Window HWND hwndChild = (HWND)LOWORD(SendMessage(hwndClient, WM_MDIGETACTIVE, 0, 0l));

// Если это окно, посылаем ему сообщение WM_COMMAND if(IsWindow(hwndChild)) SendMessage(hwndChild, WM_COMMAND, wParam, lParam);

return DefFrameProc(hwnd, hwndClient, msg, wParam, lParam);
}

case WM_DESTROY: { PostQuitMessage(0);
break; }

default: break; } return DefFrameProc(hwnd, hwndClient, msg, wParam, lParam);
}

// ===================================== // Функция ChildWndProc // =====================================

LRESULT CALLBACK _export ChildWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rc;

switch (msg) { case WM_PAINT: { hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);

DrawText(hdc, "Child Window", -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);

EndPaint(hwnd, &ps);
}

default: break; } return DefMDIChildProc(hwnd, msg, wParam, lParam);
}

Функция WinMain вызывает для инициализации приложения функцию InitApp, задачей которой является регистрация классов окон Frame Window и Document Window. После инициализации создается главное окно приложения Frame Window, для чего используется функция CreateWindow.

После отображения главного окна запускается цикл обработки сообщений. В этом цикле для обеспечения стандартного клавиатурного интерфейса MDI-приложения мы вызываем функцию TranslateMDISysAccel.



Рассмотрим функцию окна Frame Window, которая называется FrameWndProc.

В процессе создания окна Frame Window ей передается сообщение WM_CREATE. Обработчик этого сообщения создает окно Client Window, во внутренней области которого будут впоследствии создаваться окна Document Window.

Способ создания окна Client Window был рассмотрен нами ранее. Отметим, что, так как временное меню "Window" является вторым слева в главном меню приложения, его позиция равна 1. Поэтому поле hWindowMenu структуры CLIENTCREATESTRUCT заполняется следующим образом:

clcs.hWindowMenu = GetSubMenu(GetMenu(hwnd), 1);

Когда вы выбираете строку "New" из меню "File", функция окна Frame Window получает сообщение WM_COMMAND с параметром wParam, равным CM_FILENEW. Соответствующий обработчик создает новое окно Document Window, посылая окну Client Window сообщение WM_MDICREATE. Перед посылкой сообщения заполняется структура MDICREATESTRUCT, определяющая характеристики создаваемого окна.

Обратите внимание на реализацию обработчиков сообщения WM_COMMAND для меню "Window". Для того чтобы упорядочить расположение окон Document Window или представляющих их пиктограмм во внутренней области окна Client Window, наше приложение посылает специальные сообщения окну Client Window.

Например, если приложению нужно расположить все активные окна Document Window рядом, оно посылает окну Client Window сообщение WM_MDITILE:

SendMessage(hwndClient, WM_MDITILE, 0, NULL);

Функция окна, определенная в Windows, выполняет необходимые изменения в расположении и размерах активных окон.

Так же просто выполняется каскадное размещение окон Document Window и размещение пиктограмм свернутых окон Document Window. В этом случае окну Client Window посылаются, соответственно, сообщения WM_MDICASCADE и WM_MDIICONARRANGE:

SendMessage(hwndClient, WM_MDICASCADE, 0, NULL);
SendMessage(hwndClient, WM_MDIICONARRANGE, 0, NULL);

К сожалению, реализация обработчика сообщения WM_COMMAND для строки "Close All" меню "Window" выглядит несколько сложнее:



case CM_WINDOWCLOSEALL: { HWND hwndTemp; ShowWindow(hwndClient, SW_HIDE);
for(;;) { hwndTemp = GetWindow(hwndClient, GW_CHILD);
if(!hwndTemp) break; while(hwndTemp && GetWindow(hwndTemp, GW_OWNER)) hwndTemp = GetWindow(hwndTemp, GW_HWNDNEXT);
if(hwndTemp) SendMessage(hwndClient, WM_MDIDESTROY, (WPARAM)hwndTemp, NULL);
else break; } ShowWindow(hwndClient, SW_SHOW);
break; }

В этом фрагменте кода, прежде всего, мы делаем невидимым окно Client Window и (как следствие) его дочерние окна Document Window, для того чтобы избежать неприятной для глаз многократной перерисовки удаляемых окон. Напомним, что окно можно скрыть или показать вновь с помощью функции ShowWindow, указав ей для этого, соответственно, параметры SW_HIDE и SW_SHOW.

Далее мы организуем цикл по всем дочерним окнам окна Client Window, получая идентификатор первого дочернего окна с помощью функции GetWindow с параметром GW_CHILD.

Однако нельзя удалять все дочерние окна, созданные окном Client Window. Дело в том, что для окон Document Window, свернутых в пиктограммы, создаются два окна. Одно окно содержит саму пиктограмму, а второе - подпись под ней (заголовок пиктограммы). При удалении окон Document Window мы не должны удалять окна заголовков, так как они будут удалены автоматически при удалении соответствующего окна Document Window.

Окно-заголовок отличается от окна Document Window тем, что оно имеет окно-владельца (окно Document Window владеет окном заголовка). В это же время окно Document Window не имеет окна-владельца, так как оно является дочерним по отношению к окну Client Window. Этим обстоятельством можно воспользоваться, для того чтобы отличить окна-заголовки от обычных окон Document Window.

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



Для удаления окна Document Window окну Client Window посылается сообщение WM_MDIDESTROY, для чего вызывается функция SendMessage.

После завершения цикла удаления окно Client Window вновь делается видимым при помощи функции ShowWindow с параметром SW_SHOW.

Перед тем как вызвать функцию DefFrameProc, обработчик сообщения WM_COMMAND посылает это же сообщение активному окну Document Window. Так как пользователь может сделать активным любое окно Document Window, сначала нужно определить идентификатор активного окна. Это можно сделать, если послать окну Client Window сообщение WM_MDIGETACTIVE:

HWND hwndChild = (HWND)LOWORD(SendMessage(hwndClient, WM_MDIGETACTIVE, 0, 0l));

Функция SendMessage вернет идентификатор активного окна Document Window. После проверки этого идентификатора функцией IsWindow можно посылать сообщение в активное окна Document Window:

if(IsWindow(hwndChild)) SendMessage(hwndChild, WM_COMMAND, wParam, lParam);

В завершении своей работы обработчик сообщения WM_COMMAND обязан вызвать функцию DefFrameProc:

return DefFrameProc(hwnd, hwndClient, msg, wParam, lParam);

Заметьте, что этой функции нужно указать в первом параметре идентификатор окна Frame Window, а в качестве второго - идентификатор окна Client Window. Остальные параметры аналогичны параметрам функции DefWindowProc.

Теперь займемся функцией окна Document Window.

В нашем простейшем MDI-приложении эта функция делает только одну вещь - при обработке сообщения WM_PAINT пишет в центре окна Document Window текстовую строку "Child Window", вызывая для этого функцию DrawText:

LRESULT CALLBACK _export ChildWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rc; switch (msg) { case WM_PAINT: { hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);
DrawText(hdc, "Child Window", -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
} default: break; } return DefMDIChildProc(hwnd, msg, wParam, lParam);
}

Все сообщения, обработанные и необработанные, эта функция передает функции DefMDIChildProc, параметры которой аналогичны параметрам функции DefWindowProc.

Идентификаторы строк меню приложения MDIAPP определены в файле mdiapp.hpp (листинг 1.2).


Файл mdiapp/mdiapp.hpp


#define CM_HELPABOUT 100 #define CM_FILEEXIT 101 #define CM_FILENEW 102 #define CM_WINDOWTILE 103 #define CM_WINDOWCASCADE 104 #define CM_WINDOWICONS 105 #define CM_WINDOWCLOSEALL 106

Ресурсы приложения (меню и две пиктограммы) определены в файле mdiapp.rc (листинг 1.3).



Файл mdiapp/mdiapp.rc


#include "mdiapp.hpp"

APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "&New", CM_FILENEW MENUITEM SEPARATOR MENUITEM "E&xit", CM_FILEEXIT END

POPUP "&Window" BEGIN MENUITEM "&Tile", CM_WINDOWTILE MENUITEM "&Cascade", CM_WINDOWCASCADE MENUITEM "Arrange &Icons",CM_WINDOWICONS MENUITEM "Close &All", CM_WINDOWCLOSEALL END

POPUP "&Help" BEGIN MENUITEM "&About...", CM_HELPABOUT END END

APP_ICON ICON "mdiapp.ico" APPCLIENT_ICON ICON "mdiappcl.ico"

Файл определения модуля для приложения MDIAPP представлен в листинге 1.4.



Файл mdiapp/mdiapp.def


NAME MDIAPP DESCRIPTION 'Приложение MDIAPP, (C) 1995, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple



Файл mditb/mditb.cpp


// ============================================================ // MDI-приложение с окнами Toolbar и Statusbar // ============================================================ #define STRICT #include <windows.h>
#include <mem.h>
#include "mditb.hpp"

BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export FrameWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK _export ChildWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK _export TbWndProc(HWND, UINT, WPARAM, LPARAM);
LRESULT CALLBACK _export SbWndProc(HWND, UINT, WPARAM, LPARAM);

char const szFrameClassName[] = "MDITBAppClass"; char const szChildClassName[] = "MDITBChildAppClass"; char const szTbClassName[] = "MDITBCtrlAppClass"; char const szSbClassName[] = "MDISBCtrlAppClass"; char const szWindowTitle[] = "MDI Application";

HINSTANCE hInst; HWND hwndFrame; // окно Frame Window HWND hwndClient; // окно Client Window HWND hwndChild; // окно Child Window HWND hwndTb; // окно Toolbar HWND hwndSb; // окно Statusbar

// ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями hInst = hInstance; // сохраняем идентификатор приложения if(hPrevInstance) // может быть запущена return FALSE; // только одна копия приложения if(!InitApp(hInstance)) return FALSE; hwndFrame = CreateWindow( szFrameClassName, // имя класса окна szWindowTitle, // заголовок окна WS_OVERLAPPEDWINDOW, // стиль окна CW_USEDEFAULT, 0, // задаем размеры и расположение CW_USEDEFAULT, 0, // окна, принятые по умолчанию 0, // идентификатор родительского окна 0, // идентификатор меню hInstance, // идентификатор приложения NULL);
// указатель на дополнительные параметры if(!hwndFrame) return FALSE; ShowWindow(hwndFrame, nCmdShow);
UpdateWindow(hwndFrame);
while(GetMessage(&msg, NULL, 0, 0)) { if(!TranslateMDISysAccel(hwndClient, &msg)) { TranslateMessage(&msg);
DispatchMessage(&msg);
} } return msg.wParam; }




// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = "APP_MENU"; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC)FrameWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APP_ICON");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_APPWORKSPACE + 1);
wc.lpszClassName = (LPSTR)szFrameClassName; aWndClass = RegisterClass(&wc);

if(!aWndClass) return FALSE;

memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = 0; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC)ChildWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APPCLIENT_ICON");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szChildClassName; aWndClass = RegisterClass(&wc);

if(!aWndClass) return FALSE;

// Регистрируем класс для окна Toolbar memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = 0; wc.style = 0; wc.lpfnWndProc = (WNDPROC)TbWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szTbClassName; aWndClass = RegisterClass(&wc);

if(!aWndClass) return FALSE;

// Регистрируем класс для окна Statusbar memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = 0; wc.style = 0; wc.lpfnWndProc = (WNDPROC)SbWndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = NULL; wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szSbClassName; aWndClass = RegisterClass(&wc);



if(!aWndClass) return FALSE; return TRUE; }

// ===================================== // Функция FrameWndProc // ===================================== LRESULT CALLBACK _export FrameWndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { CLIENTCREATESTRUCT clcs; MDICREATESTRUCT mdics; switch (msg) { // Устанавливаем размеры окон Toolbar, Statusbar. // Уменьшаем размер окна Client Window case WM_SIZE: { // Располагаем окно Toolbar в верхней части // окна Frame Window MoveWindow(hwndTb, 0, // x-координата 0, // y-координата LOWORD(lParam), // ширина TBAR_SIZE, // высота TRUE);
// требуется перерисовка окна

// Располагаем окно Statusbar в нижней части // окна Frame Window MoveWindow(hwndSb, 0, // x-координата HIWORD(lParam) - SBAR_SIZE, // y-координата LOWORD(lParam), // ширина SBAR_SIZE, // высота TRUE);
// требуется перерисовка окна

// Если окно не свернуто в пиктограмму и его // идентификатор отличен от нуля, вызываем // функцию MoveWindow if(wParam != SIZEICONIC && hwndClient) { MoveWindow(hwndClient, 0, // x-координата TBAR_SIZE, // y-координата LOWORD(lParam), // ширина HIWORD(lParam) - (TBAR_SIZE + SBAR_SIZE), // высота TRUE);
// требуется перерисовка окна

// После уменьшения размеров окна нельзя // отдавать сообщение WM_SIZE функции DefFrameProc, // так как иначе размеры будут восстановлены return 0; } break; }

case WM_CREATE: { clcs.hWindowMenu = GetSubMenu(GetMenu(hwnd), 1);
clcs.idFirstChild = 500;

// Создаем окно Client Window hwndClient = CreateWindow( "MDICLIENT", // имя класса окна NULL, // заголовок окна WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | // стиль окна WS_HSCROLL | WS_VSCROLL, 0, 0, 0, 0, hwnd, // идентификатор родительского окна (HMENU)1, // идентификатор меню hInst, // идентификатор приложения (LPSTR)&clcs);
// указатель на дополнительные параметры

// Создаем окно Toolbar hwndTb = CreateWindow( szTbClassName, // имя класса окна NULL, // заголовок окна WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | WS_DLGFRAME, 0, 0, 0, 0, hwnd, // идентификатор родительского окна (HMENU)2, // идентификатор меню hInst, // идентификатор приложения NULL);
// указатель на дополнительные параметры



// Создаем окно Statusbar hwndSb = CreateWindow( szSbClassName, // имя класса окна NULL, // заголовок окна WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | WS_DLGFRAME, 0, 0, 0, 0, hwnd, // идентификатор родительского окна (HMENU)3, // идентификатор меню hInst, // идентификатор приложения NULL);
// указатель на дополнительные параметры

break; }

case WM_COMMAND: { switch (wParam) { case CM_FILENEW: { mdics.szClass = szChildClassName; // класс окна mdics.szTitle = "MDI Child Window"; // заголовок окна mdics.hOwner = hInst; // идентификатор приложения mdics.x = CW_USEDEFAULT; // размеры окна mdics.y = CW_USEDEFAULT; // Document Window mdics.cx = CW_USEDEFAULT; mdics.cy = CW_USEDEFAULT; mdics.style = 0; // дополнительные стили mdics.lParam = NULL; // 32-битное значение

// Посылаем сообщение WM_MDICREATE окну // Client Window. В результате будет создано новое // окно Document Window hwndChild = (HWND)SendMessage(hwndClient, WM_MDICREATE, 0, (LPARAM)&mdics);
break; }

case CM_WINDOWTILE: { SendMessage(hwndClient, WM_MDITILE, 0, NULL);
break; } case CM_WINDOWCASCADE: { SendMessage(hwndClient, WM_MDICASCADE, 0, NULL);
break; } case CM_WINDOWICONS: { SendMessage(hwndClient, WM_MDIICONARRANGE, 0, NULL);
break; } case CM_WINDOWCLOSEALL: { HWND hwndTemp; ShowWindow(hwndClient, SW_HIDE);
for(;;) { hwndTemp = GetWindow(hwndClient, GW_CHILD);
if(!hwndTemp) break; while(hwndTemp && GetWindow(hwndTemp, GW_OWNER)) hwndTemp = GetWindow(hwndTemp, GW_HWNDNEXT);
if(hwndTemp) SendMessage(hwndClient, WM_MDIDESTROY, (WPARAM)hwndTemp, NULL);
else break; } ShowWindow(hwndClient, SW_SHOW);
break; } case CM_HELPABOUT: { MessageBox(hwnd, "Приложение MDIAPP\n(C) Фролов А.В., 1995", "Simple MDI Application", MB_OK | MB_ICONINFORMATION);
break; } case CM_FILEEXIT: { DestroyWindow(hwnd);
break; } default: break; }

HWND hwndChild = (HWND)LOWORD(SendMessage(hwndClient, WM_MDIGETACTIVE, 0, 0l));
if(IsWindow(hwndChild)) SendMessage(hwndChild, WM_COMMAND, wParam, lParam);
return DefFrameProc(hwnd, hwndClient, msg, wParam, lParam);
} case WM_DESTROY: { PostQuitMessage(0);
break; } default: break; } return DefFrameProc(hwnd, hwndClient, msg, wParam, lParam);
}



// ===================================== // Функция ChildWndProc // ===================================== LRESULT CALLBACK _export ChildWndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rc; switch (msg) { case WM_PAINT: { hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);
DrawText(hdc, "Child Window", -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
} default: break; } return DefMDIChildProc(hwnd, msg, wParam, lParam);
}

// ===================================== // Функция TbWndProc // ===================================== LRESULT CALLBACK _export TbWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rc; switch (msg) { case WM_PAINT: { hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);
DrawText(hdc, "Toolbar Window", -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
} default: break; } return DefMDIChildProc(hwnd, msg, wParam, lParam);
}

// ===================================== // Функция SbWndProc // ===================================== LRESULT CALLBACK _export SbWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rc; switch (msg) { case WM_PAINT: { hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);
DrawText(hdc, "Statusbar Window", -1, &rc, DT_SINGLELINE | DT_CENTER | DT_VCENTER);
EndPaint(hwnd, &ps);
} default: break; } return DefMDIChildProc(hwnd, msg, wParam, lParam);
}

На этапе инициализации приложения функция InitApp регистрирует классы для окон Frame Window, Document Window, а также для окон Toolbar и Statusbar. Эта процедура не имеет никаких особенностей. Отметим только, что вы можете изменить форму курсора мыши для окна Toolbar, указав нужный идентификатор курсора в поле hCursor структуры WNDCLASS перед регистрацией класса, или задать цвет окна Toolbar.

Окна Toolbar и Statusbar создаются в функции окна Frame Window при обработке сообщения WM_CREATE:




hwndTb = CreateWindow( szTbClassName, // имя класса окна NULL, // заголовок окна WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | WS_DLGFRAME, 0, 0, 0, 0, hwnd, // идентификатор родительского окна (HMENU)2, // идентификатор меню hInst, // идентификатор приложения NULL);
// указатель на дополнительные параметры hwndSb = CreateWindow( szSbClassName, // имя класса окна NULL, // заголовок окна WS_CHILD | WS_CLIPCHILDREN | WS_VISIBLE | WS_DLGFRAME, 0, 0, 0, 0, hwnd, // идентификатор родительского окна (HMENU)3, // идентификатор меню hInst, // идентификатор приложения NULL);
// указатель на дополнительные параметры

Обратите внимание, что для этих окон мы указали нулевые размеры, так как в момент их создания размеры внутренней области окна Frame Window еще неизвестны.

Вы можете указать для окон Toolbar и Statusbar любые стили, применимые к дочерним окнам. Для того чтобы эти окна появились на экране сразу после их создания, мы использовали стиль WS_VISIBLE.

Займемся теперь обработчиком сообщения WM_SIZE.

Прежде всего, он располагает окно Toolbar в верхней части внутренней области окна Frame Window, вызывая функцию MoveWindow:

MoveWindow(hwndTb, 0, // x-координата 0, // y-координата LOWORD(lParam), // ширина TBAR_SIZE, // высота TRUE);
// требуется перерисовка окна

Координаты верхнего левого угла окна Toolbar устанавливаются равными значению (0,0), поэтому верхний левый угол этого окна совмещается с верхним левым углом внутренней области окна Frame Window.

Для определения ширины окна Toolbar анализируется параметр lParam, который для сообщения WM_SIZE равен ширине внутренней области окна.

Высота окна Toolbar в нашем приложении задается константой TBAR_SIZE, которая определена в файле mditb.hpp (листинг 1.6).

Аналогичным образом устанавливаются координаты и размеры окна Statusbar:

MoveWindow(hwndSb, 0, // x-координата HIWORD(lParam) - SBAR_SIZE, // y-координата LOWORD(lParam), // ширина SBAR_SIZE, // высота TRUE);
// требуется перерисовка окна

Высота окна Statusbar задается константой SBAR_SIZE.




Затем мы проверяем, создано ли окно Client Window, и не свернуто ли оно в пиктограмму. Если с окном все в порядке, устанавливаем для него новое расположение и размеры, оставляя место для окон Toolbar и Statusbar:

if(wParam != SIZEICONIC && hwndClient) { MoveWindow(hwndClient, 0, // x-координата TBAR_SIZE, // y-координата LOWORD(lParam), // ширина HIWORD(lParam) - (TBAR_SIZE + SBAR_SIZE), // высота TRUE);
// требуется перерисовка окна return 0; } break;

Еще раз обращаем ваше внимание на то, что обработанное сообщение WM_SIZE нельзя отдавать функции DefFrameProc, поэтому после вызова функции MoveWindow мы выполняем возврат из функции окна оператором return.

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

В листинге 1.6 вы найдете файл mditb.hpp, содержащий определения констант для размеров окон Toolbar и Statusbar, а также для работы с меню.


Файл mditb/mditb.hpp


#define TBAR_SIZE 35 #define SBAR_SIZE 35 #define CM_HELPABOUT 100 #define CM_FILEEXIT 101 #define CM_FILENEW 102 #define CM_WINDOWTILE 103 #define CM_WINDOWCASCADE 104 #define CM_WINDOWICONS 105 #define CM_WINDOWCLOSEALL 106

Файл ресурсов приложения MDITB приведен в листинге 1.7.



Файл mditb/mditb.rc


#include "mditb.hpp" APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "&New", CM_FILENEW MENUITEM SEPARATOR MENUITEM "E&xit", CM_FILEEXIT END POPUP "&Window" BEGIN MENUITEM "&Tile", CM_WINDOWTILE MENUITEM "&Cascade", CM_WINDOWCASCADE MENUITEM "Arrange &Icons",CM_WINDOWICONS MENUITEM "Close &All", CM_WINDOWCLOSEALL END POPUP "&Help" BEGIN MENUITEM "&About...", CM_HELPABOUT END END APP_ICON ICON "mditb.ico" APPCLIENT_ICON ICON "mditbcl.ico"

Файл определения модуля приложения MDITB приведен в листинге 1.8.



Файл mditb/mditb.def


NAME MDITB DESCRIPTION 'Приложение MDITB, (C) 1995, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple



Файл cliptxt/cliptxt.cpp


// ---------------------------------------- // Запись данных в текстовом формате в Clipboard // и чтение текстовых данных из Clipboard // ---------------------------------------- #define STRICT #include <windows.h>
#include <mem.h>
#include "cliptxt.hpp"

BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

char const szClassName[] = "CliptxtClass"; char const szWindowTitle[] = "Clipboard Demo"; char const szClipboardText[] = "Этот текст будет записан\r\n" "в универсальный буфер обмена Clipboard\r\n" "приложением CLIPTXT";

// ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения

// Разрешаем запуск нескольких копий приложения if(!hPrevInstance) if(!InitApp(hInstance)) return FALSE;

hwnd = CreateWindow( szClassName, szWindowTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);

if(!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);

while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg);
DispatchMessage(&msg);
} return msg.wParam; }

// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = "APP_MENU"; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APP_ICON");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szClassName;




aWndClass = RegisterClass(&wc);
return (aWndClass != 0);
}

// ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rc;

static HGLOBAL hglbTextCopyBuf; LPSTR lpTextCopy; static HGLOBAL hglbTextPasteBuf; LPSTR lpTextPaste; static HGLOBAL hglbClipBuf; LPSTR lpClipBuf;

switch (msg) { case WM_CREATE: { // Заказываем буфер для хранения текста, // прочитанного из Clipboard. // Начальный размер этого буфера - 1 байт hglbTextPasteBuf = GlobalAlloc(GHND, 1);
if(hglbTextPasteBuf == NULL) return -1;

// Фиксируем буфер в памяти и получаем его адрес lpTextPaste = (LPSTR)GlobalLock(hglbTextPasteBuf);
if(hglbTextPasteBuf == NULL) return -1;

// Записываем в буфер пустую строку lpTextPaste[0] = '\0';

// Расфиксируем буфер GlobalUnlock(hglbTextPasteBuf);
return 0; } case WM_PAINT: { hdc = BeginPaint(hwnd, &ps);

// Фиксируем текстовый буфер в памяти, // в случае успеха отображаем его содержимое // во внутренней области главного окна lpTextPaste = (LPSTR)GlobalLock(hglbTextPasteBuf);
if(lpTextPaste != NULL) { GetClientRect(hwnd, &rc);
DrawText(hdc, lpTextPaste, -1, &rc, DT_LEFT | DT_EXPANDTABS);
GlobalUnlock(hglbTextPasteBuf);
} EndPaint(hwnd, &ps);
} case WM_COMMAND: { switch (wParam) { // Копируем строку текста в Clipboard case CM_EDITCOPY: { // Заказываем глобальный блок памяти для строки hglbTextCopyBuf = GlobalAlloc(GHND, sizeof(szClipboardText) + 1);
if(hglbTextCopyBuf != NULL) { // Фиксируем блок памяти lpTextCopy = (LPSTR)GlobalLock(hglbTextCopyBuf);
if(lpTextCopy != NULL) { // Копируем строку текста в блок памяти lstrcpy(lpTextCopy, szClipboardText);

// Расфиксируем блок памяти GlobalUnlock(hglbTextCopyBuf);

// Открываем Clipboard и очищаем его OpenClipboard(hwnd);
EmptyClipboard();

// Записываем данные в Clipboard SetClipboardData(CF_TEXT, hglbTextCopyBuf);

// Закрываем Clipboard CloseClipboard();
} else MessageBox(hwnd, "Мало памяти", (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);
} else MessageBox(hwnd, "Мало памяти", (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);
return 0; }



// Чтение текстовых данных из Clipboard case CM_EDITPASTE: { // Открываем Clipboard OpenClipboard(hwnd);

// Получаем идентификатор блока памяти, // содержащего текстовые данные Clipboard hglbClipBuf = GetClipboardData(CF_TEXT);

// Если в Clipboard есть данные в текстовом // формате, читаем их if(hglbClipBuf != NULL) { // Фиксируем блок памяти Clipboard lpClipBuf = (LPSTR)GlobalLock(hglbClipBuf);
if(lpClipBuf != NULL) { // Изменяем размер буфера, предназначенного для // хранения данных, прочитанных из Clipboard, // устанавливая его равным размеру блока // данных Clipboard hglbTextPasteBuf = GlobalReAlloc(hglbTextPasteBuf, GlobalSize(hglbClipBuf), GMEM_NODISCARD);

// Фиксируем буфер в памяти и переписываем // в него содержимое Clipboard lpTextPaste = (LPSTR)GlobalLock(hglbTextPasteBuf);
if(lpTextPaste != NULL) { lstrcpy(lpTextPaste, lpClipBuf);

// Перерисовываем главное окно приложения InvalidateRect(hwnd, NULL, TRUE);

// Расфиксируем буфер GlobalUnlock(hglbTextPasteBuf);
} else MessageBox(hwnd, "Мало памяти", (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);

// Расфиксируем блок памяти Clipboard GlobalUnlock(hglbClipBuf);
} else MessageBox(hwnd, "Мало памяти", (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);
} else MessageBox(hwnd, "Формат CF_TEXT недоступен", (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);

// Закрываем Clipboard CloseClipboard();
return 0; }

case CM_HELPABOUT: { MessageBox(hwnd, "Приложение CLIPTXT\n(C) Фролов А.В., 1995", (LPSTR)szWindowTitle, MB_OK | MB_ICONINFORMATION);
return 0; } case CM_FILEEXIT: { DestroyWindow(hwnd);
return 0; } default: return 0; } } case WM_DESTROY: { // Освобождаем буфер, предназначенный для хранения // данных, прочитанных из Clipboard if(hglbTextPasteBuf != NULL) GlobalFree(hglbTextPasteBuf);

PostQuitMessage(0);
return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam);
}

Обработчик сообщения WM_CREATE заказывает буфер hglbTextPasteBuf, в который будет записан текст, вставленный из Clipboard.


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

После записи пустой строки буфер расфиксируется.

Задача обработчика сообщения WM_PAINT заключается в рисовании содержимого буфера hglbTextPasteBuf. Для рисования используется функция DrawText.

Заметьте, что мы фиксируем буфер hglbTextPasteBuf в памяти только на время рисования. Перед возвратом управления обработчик сообщения WM_PAINT расфиксирует буфер.

Когда пользователь выбирает строку "Copy" из меню "Edit", обработчик сообщения WM_COMMAND выполняет описанные нами ранее действия по копированию текстовой строки szClipboardText в Clipboard.

Прежде всего, он заказывает глобальный блок памяти hglbTextCopyBuf, размер которого равен размеру строки с учетом двоичного нуля, закрывающего строку. Полученный блок памяти фиксируется, после чего в него с помощью функции lstrcpy копируется содержимое строки szClipboardText. Далее блок памяти hglbTextCopyBuf расфиксируется.

Теперь блок памяти подготовлен для записи в Clipboard. Приложение открывает Clipboard и очищает его с помощью функций OpenClipboard и EmptyClipboard:

OpenClipboard(hwnd);
EmptyClipboard();

После этого блок памяти hglbTextCopyBuf передается функции SetClipboardData. Для завершения процесса Clipboard закрывается функцией CloseClipboard:

SetClipboardData(CF_TEXT, hglbTextCopyBuf);
CloseClipboard();

Все! Теперь Clipboard содержит следующий текст:

Этот текст будет записан в универсальный буфер обмена Clipboard приложением CLIPTXT

Вы можете убедиться в этом с помощью приложения Clipboard или ClipBook Viewer (если вы используете Windows for Workgroups).

Если пользователь выберет строку "Paste" из меню "Edit", обработчик сообщения WM_COMMAND скопирует данные из Clipboard в буфер hglbTextPasteBuf, подготовленный приложением на этапе создания главного окна.



В процессе копирования приложение открывает Clipboard функцией OpenClipboard и получает из него текстовые данные, вызывая функцию GetClipboardData:

hglbClipBuf = GetClipboardData(CF_TEXT);

Для получения адреса данных блок фиксируется функцией GlobalLock:

lpClipBuf = (LPSTR)GlobalLock(hglbClipBuf);

Перед тем как скопировать данные из Clipboard в буфер hglbTextPasteBuf его размер делается равным размеру блока памяти hglbClipBuf при помощи функции GlobalReAlloc:

hglbTextPasteBuf = GlobalReAlloc(hglbTextPasteBuf, GlobalSize(hglbClipBuf), GMEM_NODISCARD);

Если изменение размера блока прошло успешно, блок фиксируется в памяти, вслед за чем в него копируются данные при помощи функции lstrcpy.

Чтобы отразить новое содержимое блока памяти hglbTextPasteBuf, приложение перерисовывает главное окно, вызывая функцию InvalidateRect.

В завершении процесса копирования данных из Clipboard блоки памяти hglbTextPasteBuf и hglbClipBuf расфиксируются функцией GlobalUnlock. Затем Clipboard закрывается функцией CloseClipboard.

При завершении работы приложения обработчик сообщения WM_DESTROY уничтожает блок памяти hglbTextPasteBuf, который использовался для хранения данных, прочитанных из Clipboard:

if(hglbTextPasteBuf != NULL) GlobalFree(hglbTextPasteBuf);

Файл cliptxt.hpp (листинг 2.2) содержит определения констант для идентификации строк меню приложения CLIPTXT.


Файл cliptxt/cliptxt.hpp


#define CM_HELPABOUT 24000 #define CM_EDITPASTE 24001 #define CM_EDITCOPY 24002 #define CM_FILEEXIT 24003

В файле cliptxt.rc (листинг 2.3) определено главное меню приложения и пиктограмма, в которую превращается главное окно приложения при свертке.



Файл cliptxt/cliptxt.rc


#include "cliptxt.hpp" APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", CM_FILEEXIT END POPUP "&Edit" BEGIN MENUITEM "&Copy", CM_EDITCOPY MENUITEM "&Paste", CM_EDITPASTE END POPUP "&Help" BEGIN MENUITEM "&About...", CM_HELPABOUT END END APP_ICON ICON "cliptxt.ico"

Файл определения модуля приложения CLIPTXT представлен в листинге2.4.



Файл cliptxt/cliptxt.def


NAME CLIPTXT DESCRIPTION 'Приложение CLIPTXT, (C) 1995, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple



Файл bmpinfo/bmpinfo.cpp


// ---------------------------------------- // Приложение BMPINFO // Просмотр и анализ bmp-файлов в формате DIB // с возможностью копирования // соответствующего DDB и палитры в Clipboard // ----------------------------------------

#define STRICT #include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <mem.h>
#pragma hdrstop #include "dib.hpp" #include "bmpinfo.hpp"

BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
char const szClassName[] = "BmpInfoClass"; char const szWindowTitle[] = "Bitmap Information"; short cxClient, cyClient; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; HWND hwnd; if(!hPrevInstance) if(!InitApp(hInstance)) return FALSE; hwnd = CreateWindow( szClassName, szWindowTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);
if(!hwnd) return FALSE; ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);
while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg);
DispatchMessage(&msg);
} return msg.wParam; } // ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = "APP_MENU"; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APP_ICON");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)GetStockObject(LTGRAY_BRUSH);
wc.lpszClassName = (LPSTR)szClassName; aWndClass = RegisterClass(&wc);
return (aWndClass != 0);
} // ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; static HFILE hfDIBFile; static HDIB hDib; static HPALETTE hPal, hOldPal; static DWORD dwFileSize; switch (msg) { case WM_CREATE: { hfDIBFile = NULL; hDib = NULL; hPal = NULL; return 0; } case WM_SIZE: { cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return 0; } case WM_PAINT: { hdc = BeginPaint(hwnd, &ps);
if((hDib != NULL) && (DIBType(hDib) == WINRGB_DIB)) { if(hPal) { hOldPal = SelectPalette(hdc, hPal, FALSE);
RealizePalette(hdc);
} DIBPaint(hdc, 0, 0, hDib);
if(hPal) { SelectPalette(hdc, hOldPal, FALSE);
} } else { if(hDib) DIBInfo(hDib, dwFileSize);
} EndPaint(hwnd, &ps);
return 0; } case WM_COMMAND: { switch (wParam) { case CM_HELPABOUT: { MessageBox(hwnd, "Bitmap Information, v.1.1\n" "(C) Frolov A.V., 1995", "About BMPINFO", MB_OK | MB_ICONINFORMATION);
return 0; } case CM_FILEOPEN: { hfDIBFile = DIBSelectFile();
if(hfDIBFile != NULL) { hDib = DIBReadFile(hfDIBFile, &dwFileSize);
if((hDib != NULL) && (DIBType(hDib) == WINRGB_DIB)) { hPal = DIBCreatePalette(hDib);
} InvalidateRect(hwnd, NULL, TRUE);
} return 0; } case CM_FILEINFO: { if(hDib != NULL) DIBInfo(hDib, dwFileSize);
return 0; } case CM_FILEEXIT: { DestroyWindow(hwnd);
return 0; } // Копирование рисунка в Clipboard case CM_EDITCOPY: { if(hDib == NULL) return 0; DIBCopyToClipboard(hDib, hwnd);
return 0; } } } case WM_PALETTECHANGED: { if (hwnd == (HWND) wParam) break; } case WM_QUERYNEWPALETTE: { HDC hdc; HPALETTE hOldPal; int nChanged; hdc = GetDC(hwnd);
hOldPal = SelectPalette(hdc, hPal, (msg == WM_QUERYNEWPALETTE) ? FALSE : TRUE);
nChanged = RealizePalette(hdc);
SelectPalette(hdc, hOldPal, TRUE);
ReleaseDC(hwnd, hdc);
if(nChanged) InvalidateRect(hwnd, NULL, TRUE);
return nChanged; } case WM_DESTROY: { if(hPal) DeletePalette(hPal);
if(hDib) GlobalFree(hDib);
PostQuitMessage(0);
return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam);
}

Обработчик сообщения от строки "Copy" временного меню "Edit" вызывает функцию DIBCopyToClipboard, определенную в файле dib.cpp. Эта функция копирует изображение в Clipboard сразу в нескольких форматах. Через первый параметр функции передается идентификатор загруженного изображения DIB, через второй - идентификатор главного окна приложения.

В листинге 2.6 мы привели фрагмент файла dib.cpp, содержащий новые функции, добавленные для работы с Clipboard.



Фрагмент файла bmpinfo/dib.cpp


// ----------------------------------------------------- // Функции для работы с файлами в формате DIB // ----------------------------------------------------- #define STRICT #include <windows.h>
#include <windowsx.h>
#include <commdlg.h>
#include <mem.h>
#pragma hdrstop #include "dib.hpp"

// ------------------------------- // Функция SizeToHiMetric // Преобразование ширины и высоты для // режима отображения MM_HIMETRIC // ------------------------------- static void SizeToHiMetric(int *width, int *height) { HDC hDC = GetDC(0);

if(hDC) { // Определяем количество пикселов на один // логический дюйм по горизонтали и вертикали int dpiX = GetDeviceCaps(hDC, LOGPIXELSX);
int dpiY = GetDeviceCaps(hDC, LOGPIXELSY);

// Константа для пересчета const long HiMetricPerInch = 2540;

// Выполняем пересчет if(width) *width = int (*width * HiMetricPerInch / dpiX);
if(height) *height = int (*height * HiMetricPerInch / dpiY);

ReleaseDC(0, hDC);
} }

// ------------------------------- // Функция DIBCopyToClipboard // Копирование DIB в Clipboard // ------------------------------- BOOL DIBCopyToClipboard(HDIB hDib, HWND hwnd) { POINT ptSize; HDC hdcMeta, hdc, hdcMem; HMETAFILE hMF; HBITMAP hBitmap; HPALETTE hPal, hOldPal; HGLOBAL hMeta; LPMETAFILEPICT lpMeta; int x, y;

// Если DIB не загружен или он имеет тип, // отличный от WINRGB_DIB, копирование не выполняем if(hDib == NULL) return FALSE; if(DIBType(hDib) != WINRGB_DIB) return FALSE;

// Открываем и очищаем Clipboard if(!OpenClipboard(hwnd)) return FALSE; EmptyClipboard();

// Определяем размеры DIB DIBGetBmpRect(hDib, &ptSize);
x = ptSize.x; y = ptSize.y;

// Создаем DDB // Копируем в него содержимое окна, // размеры копируемой области равны // размерам DIB hdc = GetDC(hwnd);
hdcMem = CreateCompatibleDC(hdc);
hBitmap = CreateCompatibleBitmap(hdc, x, y);
if(hBitmap) { SelectObject(hdcMem, hBitmap);
StretchBlt(hdcMem, 0, 0, x, y, hdc, 0, 0, x, y, SRCCOPY);
} DeleteDC(hdcMem);
ReleaseDC(hwnd, hdc);




// Создаем метафайл в памяти hdcMeta = CreateMetaFile((LPSTR)NULL);

// Устанавливаем начало координат и размеры изображения SetWindowOrgEx(hdcMeta, 0, 0, NULL);
SetWindowExtEx(hdcMeta, ptSize.x, ptSize.y, NULL);

// Создаем палитру из DIB hPal = DIBCreatePalette(hDib);
if(hPal) { // Выбираем и реализуем палитру в контекст // метафайла hOldPal = SelectPalette(hdcMeta, hPal, FALSE);
RealizePalette(hdcMeta);
}

// Рисуем DIB в контексте метафайла DIBPaint(hdcMeta, 0, 0, hDib);

// Выбираем старую палитру if(hPal) SelectPalette(hdcMeta, hOldPal, FALSE);

// Закрываем метафайл hMF = CloseMetaFile(hdcMeta);

// Заказываем память для заголовка метафайла hMeta = GlobalAlloc(GHND, sizeof(METAFILEPICT));
lpMeta = (LPMETAFILEPICT)GlobalLock(hMeta);

// Преобразуем координаты для // режима отображения MM_HIMETRIC SizeToHiMetric(&x, &y);

// Заполняем заголовок метафайла lpMeta->
mm = MM_ANISOTROPIC; lpMeta->
xExt = x; lpMeta->
yExt = y; lpMeta->
hMF = hMF;

// Расфиксируем память заголовка метафайла GlobalUnlock(hMeta);

// Записываем метафайл в Clipboard if(hMeta) SetClipboardData(CF_METAFILEPICT, hMeta);

// Записываем DDB в Clipboard if(hBitmap) SetClipboardData(CF_BITMAP, hBitmap);

// Записываем палитру в Clipboard if(hPal) SetClipboardData(CF_PALETTE, hPal);

// Закрываем Clipboard CloseClipboard();
return TRUE; }

Функция SizeToHiMetric предназначена для преобразования значений ширины и высоты изображения из пикселов в сотые доли миллиметра. Напомним, что при заполнении заголовка метафайла в изотропном и анизотропном режиме отображения размеры изображения указываются в сотых долях миллиметра.

Для выполнения преобразования функция SizeToHiMetric определяет количество пикселов в одном логическом дюйме для контекста отображения, связанного с экраном дисплея. Далее ширина (высота) изображения (в пикселах) умножается на значение 2540 (1 дюйм соответствует 25,4 миллиметра) и делится на количество пикселов в одном логическом дюйме.

Функция DIBCopyToClipboard выполняет копирование данных в Clipboard одновременно в нескольких форматах, используя методики, описанные нами в предыдущих разделах.Для определения размеров DIB вызывается функция DIBGetBmpRect, определенная в файле dib.cpp (в листинге не представлена).


Файл clipshow/clipshow.cpp


// ---------------------------------------- // Просмотр Clipboard // ---------------------------------------- #define STRICT #include <windows.h>
#include <mem.h>
#include "clipshow.hpp"

BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);
void DrawBitmap(HDC, int, int, HBITMAP);
void PrepareMetaFile(HDC, LPMETAFILEPICT, int, int);
void SizeToHiMetric(int*, int*);
void HiMetricToSize(int*, int*);
void HiMetricToSizeScaled(int*, int*, int, int);

char const szClassName[] = "ClipShowClass"; char const szWindowTitle[] = "Clipboard Show"; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения

// Не разрешаем запуск нескольких копий приложения if(hPrevInstance) return FALSE;

if(!InitApp(hInstance)) return FALSE;

hwnd = CreateWindow( szClassName, szWindowTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);
if(!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);

while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg);
DispatchMessage(&msg);
} return msg.wParam; } // ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = "APP_MENU"; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APP_ICON");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szClassName;




aWndClass = RegisterClass(&wc);
return (aWndClass != 0);
} // ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rc; static HWND hwndNextViewer; HGLOBAL hglbClipData; LPSTR lpszClipBuf; HBITMAP hBitmap; static HPALETTE hPal, hOldPal; static int cxClient, cyClient; HMETAFILE hmf; LPMETAFILEPICT lpmfp;

switch (msg) { case WM_CREATE: { // Наше окно становится окном просмотра Clipboard hwndNextViewer = SetClipboardViewer(hwnd);
return 0; } case WM_SIZE: { cxClient = LOWORD(lParam);
cyClient = HIWORD(lParam);
return 0; } case WM_PAINT: { hdc = BeginPaint(hwnd, &ps);
GetClientRect(hwnd, &rc);

// Открываем Clipboard OpenClipboard(hwnd);

// Читаем данные в формате метафайла hmf = (HMETAFILE)GetClipboardData(CF_METAFILEPICT);
if(hmf != NULL) { // Фиксируем память заголовка метафайла lpmfp = (LPMETAFILEPICT)GlobalLock(hmf);
if(lpmfp != NULL) { // Сохраняем контекст отображения SaveDC(hdc);

// Устанавливаем параметры контекста отображения // в соответствии с содержимым заголовка метафайла PrepareMetaFile(hdc, lpmfp, cxClient, cyClient);

// Проигрываем метафайл PlayMetaFile(hdc, lpmfp->
hMF);

// Восстанавливаем контекст отображения RestoreDC(hdc, -1);

// Освобождаем память заголовка метафайла GlobalUnlock(hmf);
} } else { // Читаем данные в формате CF_BITMAP hBitmap = (HBITMAP)GetClipboardData(CF_BITMAP);
if(hBitmap != NULL) { // Читаем палитру hPal = (HPALETTE)GetClipboardData(CF_PALETTE);
if(hPal) { // Если Clipboard содержит палитру, // выбираем и реализуем ее hOldPal = SelectPalette(hdc, hPal, FALSE);
RealizePalette(hdc);
} // Рисуем изображение в окне DrawBitmap(hdc, 0, 0, hBitmap);
} // Читаем текстовые данные else { hglbClipData = GetClipboardData(CF_TEXT);
if(hglbClipData) { // Фиксируем блок памяти и рисуем текст lpszClipBuf = (LPSTR)GlobalLock(hglbClipData);
DrawText(hdc, lpszClipBuf, -1, &rc, DT_EXPANDTABS);



// Расфиксируем блок памяти GlobalUnlock(hglbClipData);
} } } // Закрываем Clipboard CloseClipboard();
EndPaint(hwnd, &ps);
} // Произошли изменения в цепочке окон просмотра // Clipboard case WM_CHANGECBCHAIN: { // Если идентификатор удаляемого окна просмотра // равен идентификатору следующего окна в цепочке, // запоминаем идентификатор нового следующего // окна просмотра if(wParam == (WPARAM)hwndNextViewer) hwndNextViewer = (HWND)LOWORD(lParam);

// Передаем сообщение следующему окну просмотра // по цепочке else if(hwndNextViewer) SendMessage(hwndNextViewer, msg, wParam, lParam);
return 0;

} // Содержимое Clipboard изменилось, наше приложение // должно перерисовать свое окно case WM_DRAWCLIPBOARD: { // Если наше окно не последнее в цепочке, // передаем сообщение дальше по цепочке if(hwndNextViewer) SendMessage(hwndNextViewer, msg, wParam, lParam);
InvalidateRect(hwnd, NULL, TRUE);
return 0; } case WM_COMMAND: { switch (wParam) { case CM_HELPABOUT: { MessageBox(hwnd, "Приложение CLIPSHOW\n(C) Фролов А.В., 1995", (LPSTR)szWindowTitle, MB_OK | MB_ICONINFORMATION);
return 0; } case CM_FILEEXIT: { DestroyWindow(hwnd);
return 0; } default: return 0; } } // Отслеживаем изменения палитры case WM_PALETTECHANGED: { if(hwnd == (HWND) wParam) break; } case WM_QUERYNEWPALETTE: { int nChanged;

hdc = GetDC(hwnd);
hOldPal = SelectPalette(hdc, hPal, (msg == WM_QUERYNEWPALETTE) ? FALSE : TRUE);
nChanged = RealizePalette(hdc);
SelectPalette(hdc, hOldPal, TRUE);
ReleaseDC(hwnd, hdc);
if(nChanged) InvalidateRect(hwnd, NULL, TRUE);
return nChanged; } // При завершении работы приложения изменяем // цепочку окон просмотра Clipboard, // удаляя из нее окно нашего приложения case WM_DESTROY: { ChangeClipboardChain(hwnd, hwndNextViewer);
PostQuitMessage(0);
return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam);
} // ===================================== // Функция DrawBitmap // ===================================== void DrawBitmap(HDC hDC, int x, int y, HBITMAP hBitmap) { HBITMAP hbm, hOldbm; HDC hMemDC; BITMAP bm; POINT ptSize, ptOrg;



hMemDC = CreateCompatibleDC(hDC);
hOldbm = (HBITMAP)SelectObject(hMemDC, hBitmap);
if (hOldbm) { SetMapMode(hMemDC, GetMapMode(hDC));
GetObject(hBitmap, sizeof(BITMAP), (LPSTR) &bm);

ptSize.x = bm.bmWidth; // ширина ptSize.y = bm.bmHeight; // высота DPtoLP(hDC, &ptSize, 1);

ptOrg.x = 0; ptOrg.y = 0; DPtoLP(hMemDC, &ptOrg, 1);

BitBlt(hDC, x, y, ptSize.x, ptSize.y, hMemDC, ptOrg.x, ptOrg.y, SRCCOPY);
SelectObject(hMemDC, hOldbm);
} DeleteDC(hMemDC);
} // ===================================== // Функция PrepareMetaFile // ===================================== void PrepareMetaFile( HDC hdc, LPMETAFILEPICT lpmfp, int cxClient, int cyClient) { int x, y;

// Устанавливаем такой режим отображения, // какой указан в заголовке метафайла SetMapMode(hdc, lpmfp->
mm);

// Для изотропного и анизотропного режима // анализируем поля xExt и yExt заголовка if(lpmfp->
mm == MM_ISOTROPIC lpmfp->
mm == MM_ANISOTROPIC) { // Если xExt равен нулю, устанавливаем // размеры изображения равными размерам // внутренней области окна if(lpmfp->
xExt == 0) SetViewportExtEx(hdc, cxClient, cyClient, NULL);

// Если xExt больше нуля, устанавливаем // размеры изображения в соответствии со значениями, // указанными в заголовке метафайла else if(lpmfp->
xExt >
0) { x = lpmfp->
xExt; y = lpmfp->
yExt;

// Выполняем преобразование из // сотых долей мм в пикселы HiMetricToSize(&x, &y);
SetViewportExtEx(hdc, x, y, NULL);
}

// Если xExt меньше нуля, сохраняем масштаб // изображения, разрешая изменения размеров else if(lpmfp->
xExt < 0) { x = -lpmfp->
xExt; y = -lpmfp->
yExt;

// Выполняем преобразование из // сотых долей мм в пикселы с учетом // размеров окна HiMetricToSizeScaled(&x, &y, cxClient, cyClient);
SetViewportExtEx(hdc, x, y, NULL);
} } // Для остальных режимов отображения устанавливаем // размеры изображения равными размерам, // указанным в заголовке метафайла else SetViewportExtEx(hdc, lpmfp->
xExt, lpmfp->
yExt, NULL);
} // ===================================== // Функция SizeToHiMetric // ===================================== void SizeToHiMetric(int *width, int *height) { HDC hDC = GetDC(0);



// Определяем количество пикселов в логическом // дюйме по горизонтали и по вертикали int dpiX = GetDeviceCaps(hDC, LOGPIXELSX);
int dpiY = GetDeviceCaps(hDC, LOGPIXELSY);

// Константа для преобразования const long HiMetricPerInch = 2540;

// Выполняем преобразование if(width) *width = int(*width * HiMetricPerInch / dpiX);
if(height) *height = int(*height * HiMetricPerInch / dpiY);

ReleaseDC(0, hDC);
} // ===================================== // Функция HiMetricToSize // ===================================== void HiMetricToSize(int *width, int *height) { HDC hDC = GetDC(0);
long int dpiX = GetDeviceCaps(hDC, LOGPIXELSX);
long int dpiY = GetDeviceCaps(hDC, LOGPIXELSY);
const long HiMetricPerInch = 2540;

if(width != NULL) *width = int(*width * dpiX / HiMetricPerInch);
if(height != NULL) *height = int(*height * dpiY / HiMetricPerInch);

ReleaseDC (0, hDC);
} // ===================================== // Функция HiMetricToSizeScaled // ===================================== void HiMetricToSizeScaled (int *width, int *height, int cxClient, int cyClient) { HDC hDC = GetDC(0);
long int dpiX = GetDeviceCaps(hDC, LOGPIXELSX);
long int dpiY = GetDeviceCaps(hDC, LOGPIXELSY);
const long HiMetricPerInch = 2540; int xPic, yPic;

// Определяем размеры в пикселах if (width != NULL) xPic = int(*width * dpiX / HiMetricPerInch);
if (height != NULL) yPic = int(*height * dpiY / HiMetricPerInch);

// Сравниваем коэффициенты масштабирования // по горизонтали и вертикали if((cxClient*100L) / xPic >
(cyClient*100L) / yPic) { // Если коэффициент масштабирования по // горизонтали больше, масштабируем // по горизонтали xPic = ((long)xPic * cyClient) / (long)yPic; yPic = cyClient; } else { // Иначе масштабируем по вертикали yPic = ((long)cxClient * yPic) / (long)xPic; xPic = cxClient; } ReleaseDC (0, hDC);

// Новые размеры *width = xPic; *height = yPic; }

Функция WinMain допускает запуск только одной копии приложения. Вы можете убрать это ограничение, однако в этом случае вам нужно будет предусмотреть способ раздельного хранения (для каждой копии приложения) идентификаторов палитр hPal и hOldPal.


Например, можно зарезервировать двойное слово в классе окна и использовать его для этой цели.

При создании главного окна приложения обработчик сообщения WM_CREATE добавляет это окно в список окон просмотра Clipboard, вызывая функцию SetClipboardViewer. Идентификатор следующего в цепочке окна просмотра сохраняется в переменной hwndNextViewer.

Обработчик сообщения WM_SIZE сохраняет текущие размеры внутренней области окна, необходимые для правильного масштабирования изображения, извлеченного из Clipboard в виде метафайла с указанием отношения ширины изображения к высоте.

Извлечение данных из Clipboard и их рисование выполняется во время обработки сообщения WM_PAINT. При этом используются описанные нами ранее процедуры.

Вначале приложение проверяет, содержит ли Clipboard данные в формате метафайла. Если да, то данные извлекаются и отображаются в окне приложения. Если нет, проверяется формат CF_BITMAP.

Если доступен формат CF_BITMAP, выполняется попытка чтения из Clipboard палитры, сопровождающей битовое изображение DDB. Найденная палитра выбирается в контекст отображения и реализуется. Затем обработчик сообщения WM_PAINT рисует битовое изображение, вызывая функцию DrawBitmap.

В том случае, когда ни один из перечисленных выше форматов недоступен, выполняется попытка прочитать данные в текстовом формате. В случае успеха прочитанный текст рисуется в окне функцией DrawText.

После завершения работы с Clipboard приложение вызывает функцию CloseClipboard, закрывая Clipboard.

Обработчики сообщений WM_CHANGECBCHAIN и WM_DRAWCLIPBOARD были описаны ранее. Их задачей является, соответственно, отслеживание изменений в списке окон просмотра Clipboard и изменений содержимого Clipboard.

Наше приложение отслеживает также изменения в системной палитре, обрабатывая сообщения WM_PALETTECHANGED и WM_QUERYNEWPALETTE. Соответствующие алгоритмы описаны в 14 томе "Библиотеки системного программиста" в главе "Цвет и цветовые палитры".

Обработчик сообщения WM_DESTROY удаляет главное окно приложения из списка окон просмотра Clipboard, вызывая функцию ChangeClipboardChain.

Файл clipshow.hpp содержит определения идентификаторов строк меню (листинг 2.8).


Файл clipshow/clipshow.hpp


#define CM_HELPABOUT 24000 #define CM_FILEEXIT 24001

В файле описания ресурсов определено меню и пиктограмма (листинг2.9).



Файл clipshow/clipshow.rc


#include "clipshow.hpp" APP_MENU MENU BEGIN POPUP "&File" BEGIN MENUITEM "E&xit", CM_FILEEXIT END POPUP "&Help" BEGIN MENUITEM "&About...", CM_HELPABOUT END END APP_ICON ICON "clipshow.ico"

Файл определения модуля приложения CLIPSHOW представлен в листинге 2.10.



Файл clipshow/clipshow.def


NAME CLIPSHOW DESCRIPTION 'Приложение CLIPSHOW, (C) 1995, Frolov A.V.' EXETYPE windows STUB 'winstub.exe' STACKSIZE 8120 HEAPSIZE 1024 CODE preload moveable discardable DATA preload moveable multiple



Файл cliprndr/cliprndr.cpp


// ---------------------------------------- // Отложенная запись данных в Clipboard // ---------------------------------------- #define STRICT #include <windows.h>
#include <mem.h>
#include "cliprndr.hpp"

BOOL InitApp(HINSTANCE);
LRESULT CALLBACK _export WndProc(HWND, UINT, WPARAM, LPARAM);

char const szClassName[] = "ClipRndrClass"; char const szWindowTitle[] = "Delayed Rendering Demo"; char const szClipboardText[] = "Этот текст будет записан\r\n" "в универсальный буфер обмена Clipboard\r\n" "приложением CLIPRNDR"; // ===================================== // Функция WinMain // ===================================== #pragma argsused int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { MSG msg; // структура для работы с сообщениями HWND hwnd; // идентификатор главного окна приложения

if(!hPrevInstance) if(!InitApp(hInstance)) return FALSE;

hwnd = CreateWindow( szClassName, szWindowTitle, WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, 0, 0, hInstance, NULL);
if(!hwnd) return FALSE;

ShowWindow(hwnd, nCmdShow);
UpdateWindow(hwnd);

while(GetMessage(&msg, 0, 0, 0)) { TranslateMessage(&msg);
DispatchMessage(&msg);
} return msg.wParam; }

// ===================================== // Функция InitApp // Выполняет регистрацию класса окна // ===================================== BOOL InitApp(HINSTANCE hInstance) { ATOM aWndClass; // атом для кода возврата WNDCLASS wc; // структура для регистрации // класса окна memset(&wc, 0, sizeof(wc));
wc.lpszMenuName = "APP_MENU"; wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon(hInstance, "APP_ICON");
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wc.lpszClassName = (LPSTR)szClassName;

aWndClass = RegisterClass(&wc);
return (aWndClass != 0);
}




// ===================================== // Функция WndProc // ===================================== LRESULT CALLBACK _export WndProc( HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { HDC hdc; PAINTSTRUCT ps; RECT rc;

static HGLOBAL hglbTextCopyBuf; LPSTR lpTextCopy; static HGLOBAL hglbTextPasteBuf; LPSTR lpTextPaste; static HGLOBAL hglbClipBuf; LPSTR lpClipBuf;

switch (msg) { case WM_CREATE: { hglbTextPasteBuf = GlobalAlloc(GHND, 1);
if(hglbTextPasteBuf == NULL) return -1; lpTextPaste = (LPSTR)GlobalLock(hglbTextPasteBuf);
if(hglbTextPasteBuf == NULL) return -1; lpTextPaste[0] = '\0'; GlobalUnlock(hglbTextPasteBuf);
return 0; } case WM_PAINT: { hdc = BeginPaint(hwnd, &ps);
lpTextPaste = (LPSTR)GlobalLock(hglbTextPasteBuf);
if(lpTextPaste != NULL) { GetClientRect(hwnd, &rc);
DrawText(hdc, lpTextPaste, -1, &rc, DT_LEFT | DT_EXPANDTABS);
GlobalUnlock(hglbTextPasteBuf);
} EndPaint(hwnd, &ps);
} case WM_COMMAND: { switch (wParam) { // Выполняем отложенное копирование данных case CM_EDITCOPY: { OpenClipboard(hwnd);
EmptyClipboard();
SetClipboardData(CF_TEXT, NULL);
CloseClipboard();
return 0; }

// Чтение текстовых данных из Clipboard case CM_EDITPASTE: { OpenClipboard(hwnd);
hglbClipBuf = GetClipboardData(CF_TEXT);
if(hglbClipBuf != NULL) { lpClipBuf = (LPSTR)GlobalLock(hglbClipBuf);
if(lpClipBuf != NULL) { hglbTextPasteBuf = GlobalReAlloc(hglbTextPasteBuf, GlobalSize(hglbClipBuf), GMEM_NODISCARD);

lpTextPaste = (LPSTR)GlobalLock(hglbTextPasteBuf);
if(lpTextPaste != NULL) { lstrcpy(lpTextPaste, lpClipBuf);
InvalidateRect(hwnd, NULL, TRUE);
GlobalUnlock(hglbTextPasteBuf);
} else MessageBox(hwnd, "Мало памяти", (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);
GlobalUnlock(hglbClipBuf);
} else MessageBox(hwnd, "Мало памяти", (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);
} else MessageBox(hwnd, "Формат CF_TEXT недоступен", (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);
CloseClipboard();
return 0; } case CM_HELPABOUT: { MessageBox(hwnd, "Приложение CLIPTXT\n(C) Фролов А.В., 1995", (LPSTR)szWindowTitle, MB_OK | MB_ICONINFORMATION);
return 0; } case CM_FILEEXIT: { DestroyWindow(hwnd);
return 0; } default: return 0; } } // Копируем данные во всех форматах case WM_RENDERALLFORMATS: { OpenClipboard(hwnd);
EmptyClipboard();



// Инициируем копирование в текстовом формате SendMessage(hwnd, WM_RENDERFORMAT, CF_TEXT, 0L);
CloseClipboard();
return 0; } // Копируем данные в нужном формате case WM_RENDERFORMAT: { // Работаем только с текстовым форматом if(wParam != CF_TEXT) return 0;

hglbTextCopyBuf = GlobalAlloc(GHND, sizeof(szClipboardText) + 1);
if(hglbTextCopyBuf != NULL) { lpTextCopy = (LPSTR)GlobalLock(hglbTextCopyBuf);
if(lpTextCopy != NULL) { lstrcpy(lpTextCopy, szClipboardText);
GlobalUnlock(hglbTextCopyBuf);

// Фактическая запись данных SetClipboardData(wParam, hglbTextCopyBuf);
} else MessageBox(hwnd, "Мало памяти", (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);
} else MessageBox(hwnd, "Мало памяти", (LPSTR)szWindowTitle, MB_OK | MB_ICONHAND);
return 0; } case WM_DESTROY: { if(hglbTextPasteBuf != NULL) GlobalFree(hglbTextPasteBuf);
PostQuitMessage(0);
return 0; } default: break; } return DefWindowProc(hwnd, msg, wParam, lParam);
}

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

Файл cliprndr.hpp содержит определения констант (листинг 2.12).


Файл cliprndr/cliprndr.hpp


#define CM_HELPABOUT 24000 #define CM_EDITPASTE 24001 #define CM_EDITCOPY 24002 #define CM_FILEEXIT 24003

Файл описания ресурсов приведен в листинге 2.13.