Программы для создания в Windows 95 окон, способных запоминать информацию о своем расположении на экране
PC Magazine/RE logo
(С) СК Пресс 6/96
PC Magazine, November 7, 1995, p. 355

Укрощение строптивых окон

Джефф Просис


Познакомьтесь с программами на Си и Си++ дл создания в Windows 95 окон, способных запоминать информацию о своем расположении на экране.

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

Достаточно взглянуть на пример программы в практически любом учебнике по программированию дл Windows, как сразу же бросается в глаза одна особенность: при создании перекрывающегося окна с помощью функций CreateWindow или CreateWindowEx их параметрам size (размер) и position (расположение) присваивается значение CW_USEDEFAULT. В результате право решать, какие значения будут иметь эти параметры, предоставляется самой системе Windows. В обучающем руководстве такой подход оправданн, однако он не отражает потребностей реальной жизни. В настоящее врем все более распространенной - и даже предполагаемой - становится практика разработки прикладных программ, которые сохраняли бы размер и расположение своего главного окна на момент окончания сеанса работы и затем восстанавливали окно в прежнем состоянии при повторном запуске. Все, кто любит размещать элементы "рабочего стола" вполне определенным образом, по достоинству оценят внедрение прикладных программ, умеющих восстанавливаться на прежнем месте; это равнозначно дл них глотку свежего воздуха в атмосфере душных, загазованных городов.

Если до сих пор задача сохранения текущих параметров окна не входила в круг ваших проблем, то вам на первый взгляд все может показаться очень простым. Следует вызвать функцию GetWindowRect, извлечь координаты прямоугольной области, ограничивающей окно, а затем сохранить эти значения, чтобы в дальнейшем их можно было передать при обращении к функции CreateWindow. Однако все гораздо сложнее. Что, если на момент закрытия прикладной программы ее окно было развернуто до максимальных размеров? Ведь если впоследствии разрешить функции CreateWindow выполнить масштабирование окна на весь экран, размеры будут соблюдены, но в строке заголовка вместо кнопки Restore (восстановить до первоначальных размеров) будет находиться кнопка Maximize (развернуть). Правда, это не самое страшное. Хуже то, что изменить размеры такого окна с помощью мыши будет невозможно, поскольку его границы расположены вне экрана. А как быть, если окно, наоборот, было свернуто? Удастся ли снова получить такое же свернутое окно при следующем запуске, если закрыть его (завершив сеанс работы с прикладной программой), а потом открыть вновь? И удастся ли восстановить окно до нормальных размеров, когда будет нажата кнопка Restore?

Оказываентся, научить программу запоминать прежнее расположение окна не так то просто, но задача вполне разрешима. Чтобы облегчить понимание материала, текст программ для получения в Windows 95 таких "устойчивых" окон представлен на двух языках - Си и Си++. В соответствии с правилами хорошего тона, относящимися к программированию для Windows 95, в наших примерах все установочные параметры окна сохранялись в Реестре (Registry), а не в INI-файлах. Тем, кто привык работать с INI-файлами и пока не составлял прикладных программ, основанных на интерфейсе (API) Win32 и использующих Реестр, в дополнение к информации о создании окон предлагается вводное описание API-функций Win32, предназначенных для чтения и записи данных Реестра.

Структура WINDOWPLACEMENT

Чтобы сохранить, а затем восстановить полную информацию о размерах, расположении на экране и текущем состоянии окна, не обойтись без использования двух функций - GetWindowPlacement и SetWindowPlacement, - впервые появившихся в Windows 3.1. Их входными параметрами служат дескриптор окна и адрес структуры WINDOWPLACEMENT; ее определение дается в заголовочном файле WINUSER.H: typedef struct tagWINDOWPLACEMENT{ UINT length; UINT flags; UINT showCmd; POINT ptMinPosition; POINT ptMaxPosition; RECT rcNormalPosition; } WINDOWPLACEMENT; Структура WINDOWPLACEMENT объединяет всю информацию, которая нужна системе Windows для получения полной характеристики некоторого окна, включая его текущие размеры и расположение на экране. Благодаря этим данным Windows может восстанавливать первоначальное состояние окна из развернутого до максимальных размеров или свернутого состояния.

Элемент length предназначен для хранения размера этой структуры. Заполнить данное поле следует до вызова любой из функций, связанных с операциями по размещению окна на экране, иначе такая функция просто не сможет работать. Элемент flags состоит из битов-флажков с информацией об окне в свернутом состоянии. Если указан флажок WPF_RESTORETOMAXIMIZED, то при восстановлении свернутое окно будет раскрыто до максимальных размеров. Данный флажок выставляется в том случае, когда свертывание окна производится из состояния с максимальными размерами. Элемент showCmd служит дл индикации текущего состояния окна. Ему присваиваютс значения: SW_SHOWMINIMIZED - если окно свернуто; SW_SHOWMAXIMIZED - если оно раскрыто до максимальных размеров; SW_SHOWNORMAL - если не выполняются ни первое, ни второе условия.

Элементы ptMinPosition и ptMaxPosition содержат координаты верхнего левого угла окна, когда оно свернуто или раскрыто до максимальных размеров соответственно. Однако при работе в Windows 95 не рассчитывайте узнать что-либо полезное из значени элемента ptMinPosition - теперь при свертывании окно удаляется с экрана во избежание его "заслонения" окнами других программ. Если же для свернутого окна прочитать ptMinPosition, то там обнаруживается значение без определенного смысла (например, 3000,3000). Элемент rcNormalPosition служит для хранения координат окна в его "нормальном" состоянии, т. е. когда оно ни свернуто, ни раскрыто до максимальных размеров. В элементе rcNormalPosition, при условии, что флажок WPF_RESTORETOMAXIMIZED не выставлен, хранятся размер и координаты расположения на экране, которые используютс при восстановлении окна из свернутого или раскрытого до максимальных размеров состояния; это будет происходить, если в строке заголовка нажать кнопку Restore (восстановить) либо выбрать команду Restore из системного меню.

Чтобы можно было восстановить настройку окна в последующем сеансе работы, достаточно сохранить следующие параметры: flags, showCmd и rcNormalPosition; когда окно будет создаваться вновь, эти значения будут восстановлены. Сохранять ptMinPosition и ptMaxPosition необходимости нет, поскольку они заполняютс непосредственно системой Windows, когда окно свернуто или раскрыто до максимальных размеров. Процедура сохранения параметров, отражающих размеры, расположение и текущее состояние окна, может состоять из следующих трех этапов.

  1. Выделить память для структуры WINDOWPLACEMENT, установив в поле length значение sizeof(WINDOWPLACEMENT).
  2. Вызвать функцию GetWindowPlacement для заполнени элементов структуры WINDOWPLACEMENT текущими значениями параметров расположения данного окна.
  3. Сохранить в Реестре значения следующих элементов структуры WINDOWPLACEMENT - flags, showCmd и rcNormalPosition, чтобы позднее их можно было извлечь оттуда.
При повторном создании окна восстановление его прежних параметров может быть произведено в следующей последовательности.
  1. Выделить память для структуры WINDOWPLACEMENT, установив в поле length значение sizeof(WINDOWPLACEMENT).
  2. Вызвать функцию GetWindowPlacement для заполнени элементов структуры WINDOWPLACEMENT текущими значениями параметров расположения данного окна.
  3. Извлечь сохраненные ранее значения flags, showCmd и rcNormalPosition и поместить их в соответствующие элементы структуры WINDOWPLACEMENT.
  4. Вызвать функцию SetWindowPlacement дл активизирования новых значений параметров окна.
Если функции SetWindowPlacement передается значение SW_SHOWMINIMIZED, SW_SHOWMAXIMIZED или SW_SHOWNORMAL, хранящееся в элементе showCmd структуры WINDOWPLACEMENT, то это вызывает немедленное отображение данного окна на экране. Таким образом, после вызова SetWindowPlacement специально обращаться к функции ShowWindow процедуры WinMain для отображени окна не нужно. В реальности же приходится специально пропускать обычный вызов ShowWindow, так как передаваемый процедуре WinMain параметр nCmdShow может изменить состояние данного окна.

Реестр

Сохранять информацию структуры WINDOWPLACEMENT удобнее всего в Реестре, поскольку Windows 95 имеет развитые функции для чтения и записи его данных. Сам Реестр представляет собой некоторую централизованную базу данных, обрабатываемую системой, но доступную и для прикладных программ. Он имеет иерархическую структуру, напоминающую систему каталогов на жестком диске. Такие параметры, как keys (ключи) и subkeys (подключи), служат указателями на некоторое размещение данных в Реестре, подобно тому как каталоги и подкаталоги описывают организацию данных на жестком диске. Параметры values (значения) в некотором смысле аналогичны файлам: они содержат сами данные Реестра. Согласно "Руководству по программированию в Windows 95", установочные параметры, спцифичные дл конкретного пользователя, должны храниться в Реестре в подключе: HKEY_CURRENT_USER\Software\CompanyName\ProductName\Version Например, если компания имеет название Widgets Inc., ее программный продукт называется WidgetMaster, а номер версии - 1.0, то соответствующий подключ Реестра должен выглядеть следующим образом: HKEY_CURRENT_USER\Software\Widgets Inc.\WidgetMaster\V1.0 HKEY_CURRENT_USER - псевдоним подключа, выделенного для текущего пользователя в поддереве HKEY_USERS Реестра. Если некоторая прикладная программа сохраняет свои установочные параметры в подключе HKEY_CURRENT_USER, то данные, относящиеся к пользователю А, не затрагивают данные, касающиес пользователя B, но лишь при условии, что каждый из них входил в систему под своим именем и конфигураци Windows предусматривает сохранение параметров настройки отдельных пользователей. (Более подробно о самом Реестре и о принципах сохранения и восстановлени индивидуальных параметров настройки пользователей см. PC Magazine/Russian Edition, 4/96, с. 198.)

Подключи Реестра создаются с помощью API-функции Win32 под названием RegCreateKeyEx. Например, нижеследующие предложения создают рассматриваемый в предыдущем абзаце подключ, а затем открывают его с правом доступа KEY_SET_VALUE, разрешающим вводить в подключ новые значения, если он уже создан: HKEY hKey; RegCreateKeyEx (HKEY_CURRENT_USER, "Software\\ Widgets Inc.\\WidgetMaster\\V1.0", 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, NULL); Если получен код возврата ERROR_SUCCESS, значит, вызов функции прошел успешно. Получение других значений свидетельствует об ошибке. При успешном выполнении функции RegCreateKeyEx через ее параметр hKey будет передан дескриптор нового ключа. Если требуемый ключ существовал до того, как была вызвана функци RegCreateKeyEx, то ошибки не произойдет: функция просто откроет существующий ключ и запишет его дескриптор в переменную hKey.

После создания или открытия подключа с правом доступа KEY_SET_VALUE в него можно вносить новые данные с помощью функции RegSetValueEx. Число значений, относящихся к отдельному подключу, неограниченно. Каждое значение имеет свое имя, с помощью которого его можно отличить от других. Например, использу дескриптор, переданный из функции RegCreateKeyEx через параметр hKey, можно создать новое поле под именем "Author" в подключе HKEY_CURRENT_USER\Software\Widgets Inc.\WidgetMaster\V1.0 и присвоить ему значение - строку, записанную в переменной szAuthorName: RegSetValueEx (hKey, "Author", 0, REG_SZ, (PBYTE)szAuthorName, lstrlen(szAuthorName)+1); Если поле с именем "Author" уже существует, то старое значение заменяется на новое. Параметр REG_SZ сообщает, что передаваемое значение дается в кодах ASCIIZ (стандарты ANSI или Unicode). Это - один из возможных вариантов типов данных, которые предусматривает функция RegSetValueEx. Также допускаются следующие, широко используемые типы данных: REG_DWORD, указывающий на передачу значений с типом DWORD, и REG_BINARY, информирующий о передаче прямой последовательности байтов произвольной длины.

Наконец, в последнем параметре указывается число байтов, которое требуется сохранить. Если используетс строка в кодах ASCIIZ, то необходимо предусмотреть место для расположенного в конце строки нулевого символа; именно по этой причине RETURN-значение, передаваемое функцией lstrlen, увеличивается на 1. Следует отметить, что итоговое значение необходимо удвоить, если в переменной szAuthorName содержитс строка в формате Unicode. Данное требование не существенно для Windows 95 (как и для Windows 3.x), где текстовые строки хранятся в кодах ANSI, но актуально для Windows NT, так как здесь допускаются оба формата - ANSI и Unicode.

Записав данные в Реестр, ключ следует закрыть с помощью функции RegCloseKey. Она имеет только один входной параметр - дескриптор HKEY. Операция закрыти ключа аналогична процедуре закрытия файла. Операционной системе сообщается, что работа с ключом завершена и ей разрешено сохранить обновленный Реестр на диске. Однако запись информации на диск не всегда выполняетс немедленно; в документации к API Win32 предупреждается, что возможна задержка в несколько секунд. При настоятельной необходимости немедленно сохранить всю информацию на диске следует использовать функцию RegFlushKey.

Если вы записали некоторую информацию в Реестр, то в дальнейшем вам потребуется суметь прочитать ее. Дл открытия подключей Реестра используется функци RegOpenKeyEx, а для чтения их значений - RegQueryValueEx. Покажем на примере, как прочитать и присвоить переменной szAuthorName значение ключа, записанное ранее под именем "Author": HKEY hKey; DWORD dwSize; if (RegOpenKeyEx (HKEY_CURRENT_USER, "Software\\Widgets Inc.\\WidgetMaster\\V1.0", 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) { dwSize = sizeof (szAuthorName); RegQueryValueEx (hKey, "Author", NULL, NULL, (PBYTE) &szAuthorName, &dwSize); RegCloseKey (hKey); } Если поле "Author" не существует, выполнение функции RegQueryValueEx заканчивается неудачно. Если указанный элемент отсутствует, то передача при возврате управления некоторого принятого по умолчанию значени здесь не предусмотрена в отличие от обработки INI-файлов такими функциями, как GetProfileString и GetProfileInt. В данном случае, если был получен результат, отличный от ERROR_SUCCESS, придетс проверять код возврата и присваивать элементу ваше собственное значение по умолчанию.

Пример 1: SMARTWIN.C

А теперь покажем, как, используя всю эту информацию, научиться на практике создавать окна в стиле WS_OVERLAPPEDWINDOW, способные восстанавливать свои первоначальные размеры и расположение на экране. Листинг такой программы показан на лист. 1. С учетом того что большинство прикладных Windows-программ все еще составляются на Си, а не на Си++, программа SmartWin также подготовлена на Си. Нас интересуют прежде всего две функции - SaveWindowState и RestoreWindowState. В них представлены все элементы, необходимые прикладной программе для сохранени особенностей отображения ее главного окна на экране.

На входе обе функции получают по четыре параметра: дескриптор окна, установочные параметры которого нужно сохранить и впоследствии восстановить, указатель на ASCIIZ-строку с наименованием фирмы, указатель на ASCIIZ-строку с названием программного продукта и указатель на строку с номером версии. На основе перечисленных строк формируется наименование подключа, где будут сохраняться все установочные параметры окна. Как видно из текста SMARTWIN.C, вызов функции SaveWindowState должен осуществляться в разделах обработки сообщений WM_DESTROY и WM_ENDSESSION; это гарантирует, что все установочные параметры окна будут непременно сохранены в Реестре вне зависимости от того, каким образом завершается выполнение прикладной программы. В Windows 95 сообщение WM_ENDSESSION с ненулевым значением параметра wParam информирует программу, что либо Windows собирается завершить работу, либо сеанс данного пользователя заканчивается и разрешается подключение другого. В случае завершени сеанса не происходит обращения к обработчику сообщени WM_DESTROY, поэтому приходится дополнительно учитывать необходимость обработки сообщения WM_ENDSESSION.

Вызов функции RestoreWindowState из процедуры WinMain должен происходить после открытия окна и до вызова функции ShowWindow, производящей его отображение на экране. Обращаться к ShowWindow следует только в том случае, если функция RestoreWindowState передает return-значение FALSE, сигнализирующее о том, что предыдущее состояние окна не восстановлено. Така ситуация возникает лишь при первом запуске данной прикладной программы; при всех последующих ее запусках установочные параметры будут извлекаться из Реестра.

Понимание алгоритма работы функции SaveWindowState не вызывает каких-либо затруднений. После формировани имени подключа в переменной szSubKey полученная строка передается функции RegCreateKeyEx, осуществляющей открытие или создание такого подключа в разделе HKEY_CURRENT_USER. Переменная szSubKey имеет тип TCHAR - на случай если придется переносить программу в среду под управлением Windows NT. (Для типа TCHAR на каждый символ выделяются либо один, либо два байта в зависимости от того, какой тип текстовых строк применяется в программе - ANSI или Unicode. Если вы предполагаете использовать подготовленную дл Windows 95 программу также и в Windows NT, то привыкайте задавать тип TCHAR вместо обычного char.) При успешном завершении RegCreateKeyEx сначала вызывается функция GetWindowPlacement для заполнени структуры WINDOWPLACEMENT, а затем - RegSetValueEx дл сохранения в Реестре значений элементов структуры flags, showCmd и rcNormalPosition. Для всех трех названных элементов указывается параметр REG_BINARY, чтобы избежать зависимости от типа данных.

Алгоритм работы функции RestoreWindowState фактически повторяет алгоритм SaveWindowState, но только в обратном порядке. Сначала вызывается функци GetWindowPlacement, чтобы заполнить структуру WINDOWPLACEMENT и получить значения ptMinPosition и ptMaxPosition, принятые по умолчанию. Затем производятся считывание значений, записанных ранее в Реестр с помощью функции SaveWindowState, и занесение их в элементы структуры flags, showCmd и rcNormalPosition. Далее вызывается функци SetWindowPlacement для активизации новых значений параметров. Обратите внимание, что обращения к функции SetWindowPlacement не происходит, если верхний левый угол данного окна в его нормальном состоянии находитс на расстоянии всего нескольких пикселов от правой или нижней границ экрана либо вовсе за их пределами. Эта проверка производится для учета следующей ситуации: если окно располагалось в районе границы экрана и использовался режим разрешения 1024х768, то при дальнейшей загрузке Windows в режимах разрешени 640х480 или 800х600 данное окно не появится на экране.

Есть хороший способ протестировать программу, устойчиво сохраняющую расположение своего окна: измените размеры окна, потом раскройте его до максимальных размеров, сверните, а затем завершите работу данной прикладной программы, когда ее окно свернуто. При последующем запуске программы ее окно должно появиться в свернутом состоянии. Если теперь щелкнуть на ее пиктограмме на Панели задач Taskbar, окно должно раскрыться до максимальных размеров. Если затем щелкнуть на кнопке Restore (восстановить), то размер и расположение окна должны оказаться такими же, как перед первым выполнением операции "Развернуть". Попробуйте проделать этот тест с программой SMARTWIN.EXE, и вы сможете убедиться, что все происходит именно так.


Лист. 1. SMARTWIN.C - прикладная программа дл интерфейса Win32, показывающая, как сохранять информацию о размерах и расположении на экране ее главного окна в последовательных сеансах работы.
//***************************************************** // SMARTWIN.C - перекрывающееся окно, которое сохраняет // информацию о своих размерах и расположении на экране // в последовательных сеансах работы. #include <windows.h> LRESULT CALLBACK WndProc (HWND, UINT, WPARAM, LPARAM); BOOL SaveWindowState (HWMD, LPCTSTR, LPCTSTR, LPCTSTR); BOOL RestoreWindowState (HWND, LPCTSTR, LPCTSTR, LPCTSTR); /////////////////////////////////////////////////////// // WinMain int WINAPI WinMain (HINSTANCE; hInstance, HINSTANCE hPrevInstance, LPSTR lpszCmdLine, int nCmdShow) { static char szAppName [] = "SmartWin"; WNDCLASS wc; HWND hwnd; MSG msg; wc.style = 0; wc.lpfnWndProc = (WNDPROC) WndProc; wc.cbClsExtra = 0; wc.cbWndExtra = 0; wc.hInstance = hInstance; wc.hIcon = LoadIcon (NULL, IDI_APPLICATION); wc.hCursor = LoadCursor (NULL, IDC ARROW); wc.hbrBackground = (HBRUSH) (COLOR_WINDOW + 1); wc.lpszMenuName = NULL; wc.lpszClassName = szAppName; RegisterClass (&wc); hwnd = CreateWindow ( szAppName, azAppName, WS_OVERLAPPEDWINDOW, CWUSEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, HWND_DESKTOP, NULL, hInstance, NULL); if { !RestoreWindowState (hwnd, "MyCompany", "MyProduct", "Version 1.0 " )) ShowWindow (hwnd, nCmdShow); UpdateWindow (hwnd); while (GetMessage (&msg, NULL, 0 , 0)) { TranslateMessage (&msg); DispatchMessage (&msg); } return msg.wParam; } /////////////////////////////////////////////////////// // Процедура обработки окна LRESULT CALLBACK WndProc (HWND hwnd, UINT msg, WPARAM wParam, LPARAM lparam) { switch (msg) { case WM_DESTROY: SaveWindowState (hwnd, "MyCompany", "MyProduct", "Version 1.0" ); PostQuitMessage (0); return 0; case WM_ENDSESSION: if (wParam) SaveWindowState (hwnd, "MyCompany", "MyProduct", "Version 1.0" ); return 0; } return DefWindowProc (hwnd, msg, wParam, lParam); } /////////////////////////////////////////////////////// // Функции обработки текущего состояния окна. BOOL SaveWindowState (HWND hwnd, LPCTSTR lpszCompanyName, LPCTSTR lpszProductName, LPCTSTR lpszVersion) { HKEY hKey; WINDOWPLACEMENT wp; TCHAR szSubkey [256]; int n1, n2, n3; if ((n1 = lstrlen (lpszCompanyName) == 0) || (n2 = lstrlen (lpszProductName) == 0) || (n3 = lstrlen (lpszVersion) == 0) || ((n1 + n2 + n3 + 12) > sizeof (szSubkey))) return FALSE; lstrcpy (szSubkey, "Software\\"); lstrcat (szSubkey, lpszCompanyName); lstrcat (szSubkey, "\\"); lstrcat (szSubkey, lpszProductName); lstrcat (szSubkey, "\\"); lstrcat (szSubkey, lpszVersion); if (RegCreateKeyEx (HKEY_CURRENT_USER, szSubkey, 0, NULL, REG_OPTION_NON_VOLATILE, KEY_SET_VALUE, NULL, &hKey, NULL) != ERROR_SUCCESS) return FALSE; wp.length = sizeof (WINDOWPLACEMENT); GetWindowPlacement (hwnd, &wp); if ((RegSetValueEx (hKey, "flags", 0, REG_BINARY, (PBYTE) &wp.flags, sizeof (wp.flags)) != ERROR_SUCCESS) || (RegSetValueEx (hKey, "showCmd", 0, REG_BINARY, (PBYTE) &wp.showCmd, sizeof (wp.showCmd)) != ERROR_SUCCEBS) || (RegSetValueEx (hKey, "rcNormal", 0, REQ_BINARY, (PBYTE) &wp.rcNormalPosition, sizeof (wp.rcNormalPosition)) != ERROR_SUCCESS)) { RegCloseKey (hKey); return FALSE; } RegCloseKey (hKey); return TRUE; } BOOL RestoreWindowState (HWND hwnd, LPCTSTR lpszCompanyName, LPCTSTR lpszProductName, LPCTSTR lpszVersion) { HKEY hKey; DWORD dwSizeFlags, dwSizeShowCmd, dwSizeRcNormal; WINDOWPLACEMENT wp; TCHAR szSubkey[256]; int n1, n2, n3; if ((n1 = lstrlen (lpszCompanyName) == 0) || (n2 = lstrlen (lpszProductName) == 0) || (n3 = lstrlen (lpszVersion) == 0) || ((n1 + n2 + n3 + 12) > sizeof (szSubkey))) return FALSE; lstrcpy (szSubkey, "Software\\"); lstrcat (szSubkey, lpszCompanyName); lstrcat (szSubkey, "\\"); lstrcat (szSubkey, lpszProductName); lstrcat (szSubkey, "\\"); lstrcat (szSubkey, lpszVersion); if (RegOpenKeyEx (HKEY_CURRENT_USER, szSubkey, 0, KEY_QUERY_VALUE, &hKey) == ERROR_SUCCESS) { wp.length = sizeof (WINDOWPLACEMENT); GetWindowPlacement (hwnd, &wp); dwSizeFlags = sizeof (wp.flags); dwSizeShowCmd = sizeof (wp.showCmd); dwSizeRcNormal = sizeof (wp.rcNormalPosition); if ((RegQueryValueEx (hKey, "flags", NULL, NULL, (pBYTE) &wp.flags, &dwSizeFlags) != ERROR_SUCCESS) || (RegQueryValueEx (hKey, "showCmd", NULL, NULL, (PBYTE) &wp.showCmd, &dwSizeShowCmd) != ERROR_SUCCESS) || (RegQueryValueEx (hKey, "rcNormal", NULL, NULL, (pBYTE) &wp.rcNormalPosition. &dwSizeRcNormal) != ERROR_SUCCESS)) { RegCloseKey (hKey); return FALSE; } RegCloseKey (hKey); if ((wp.rcNormalPosition.left <= (GetSystemMetrice (SM_CXSCREEN) - GetSystemMetrics (SM_CXICON))) && (wp.rcNormalPosition.top <= (GetSystemMetrics (SM_CYSCREEN) - GetSystemMetrics (SM_CYICON)))) { SetWindowPlacement (hwnd, &wp); return TRUE; } } return FALSE; }

Пример 2: SMARTWIN.CPP

Существует другой, более элегантный способ построения "устойчивых" окон. Для этого следует использовать язык Си++ и создать класс обработки окон, способный самостоятельно вызывать функции SaveWindowState и GetWindowState в ответ на определенные события. На лист. 2 приведены файлы заголовочный (.H) и с исходным текстом программы (.CPP) для обслуживания пользовательского класса обработки окон, именуемого CSmartFrameWnd. Его прототипом послужил широко распространенный класс CFrameWnd из библиотеки MFC. Подобно CFrameWnd, класс CSmartFrameWnd предназначен для реализации несущего (frame window) окна SDI (Single Document Interface - однодокументный интерфейс); единственное дополнение, отсутствующее в прототипе, заключается в следующем: при уничтожении окна, созданного на базе класса CSmartFrameWnd (или при завершении сеанса работы в Windows), автоматически на диске сохраняется информация о его размере и расположении на экране и при повторном создании этого окна восстанавливаются прежние значения названных параметров.

Класс CSmartFrameWnd можно использовать вместо CFrameWnd где угодно. Функционально оба класса ведут себя абсолютно одинаково, за исключением уже упомянутого свойства CSmartFrameWnd, позволяющего восстанавливать прежнее состояние окна. Достаточно внести в исходный текст своей прикладной программы команду #include "SMARTWIN.H" и либо добавить в описание проекта файл SMARTWIN.CPP, либо использовать предварительно скомпилированный объектный код при компоновке. Класс CSmartFrameWnd одинаково хорошо работает как в прикладных программах, подготовленных на базе библиотеки MFC 1.0 и создающих несущие окна явно (обычно при замещении функции InitInstance дл инициализации программного объекта), так и в прикладных программах, связанных с обработкой документов или просмотром, где право на создание несущего окна предоставляется шаблону документа.

В состав класса CSmartFrameWnd входят функции SaveWindowState и RestoreWindowState в версии для языка Си++; они присутствуют здесь как принадлежащие функции. Для обработки Реестра вместо API-функций Win32 применяются более простые GetProfileInt и WriteProfileInt. Так что единственное, о чем остаетс побеспокоиться при использовании класса CSmartFrameWnd, - это вызвать функцию SetRegistryKey из функции InitInstance объекта прикладной программы. Теперь после вызова SetRegistryKey MFC-функции обработки параметров системы - такие, как GetProfileInt и WriteProfileInt, - будут автоматически обращаться к Реестру, а не к INI-файлам. Для работы функции SetRegistryKey ей следует передать строку с наименованием фирмы, например: SetRegistryKey ("MyCompany"); Название программного продукта (обычно им исполнимого файла) автоматически вносится средствами MFC, а в строку, содержащую номер версии, по умолчанию записывается "Version 1.0". При желании класс CSmartFrameWnd можно дополнить принадлежащей функцией SetVersionNumber, позволяющей программным путем задавать номер версии - без необходимости вносить изменения в исходный текст самого класса CSmartFrameWnd.

Подход, реализуемый в классе CSmartFrameWnd дл сохранения и восстановления параметров состояния окна, похож на показанный в примере SMARTWIN.C. Процедуры обработки ситуаций OnDestroy и OnEndSession вызывают функцию CSmartFrameWnd::SaveWindowState, так что текущие установочные параметры структуры WINDOWPLACEMENT сохраняются независимо от варианта закрытия окна. Эта часть алгоритма не вызывает затруднений. Сложнее обстоит дело с выбором места, где нужно вызывать функцию CSmartFrameWnd::RestoreWindowState, чтобы состояние окна можно было восстановить исключительно средствами самого класса без помощи извне. В конце концов это место было найдено в разделе OnShowWindow. Операци восстановления исполняется здесь лишь один раз при первом вызове ShowWindow для отображения окна на экране. В начале я пытался заменить функцию ShowWindow (которая в отличие от OnShowWindow вызывается при каждом сообщении WM_SHOWWINDOW), но очень скоро обнаружил, что все происходит не так, как мне бы хотелось, поскольку функция Cwnd::ShowWindow не является виртуальной.

В ответ на первый вызов функции ShowWindow дл отображения несущего окна генерируется сообщение WM_SHOWWINDOW, приводящее к активизации функции CSmartFrameWnd::OnShowWindow. Она, в свою очередь, проверяет переменную m_bFirstTime и, если оказывается, что ее значение - TRUE, вызывает функцию RestoreWindowState и возвращает управление без передачи сообщения в родовой класс. При всех последующих вызовах OnShowWindow фрагмент программы с обращением к RestoreWindowState пропускается и управление сразу же передается в родовой класс для выполнения принятой по умолчанию обработки, потому что после первого вызова функции OnShowWindow переменной m_bFirstTime было присвоено значение FALSE. Хотя это - некоторая уловка, но все работает нормально. Таким образом, если не считать необходимости обращения к SetRegistryKey, программа остается полностью автономной.


Лист. 2. При подготовке прикладных программ с применением библиотеки MFC можно подключить вместо используемого в MFC класса CFrameWnd приведенный здесь класс окна CSmartFrameWnd, который служит дл создания несущих окон SDI, сохраняющих информацию о своих размерах и расположении на экране.
//***************************************************** // // SMARTWIN.H - заголовочный файл для программы // SMARTWIN.CPP // //***************************************************** #ifndef _SMARTWIN_H_ #define _SMARTWIN_H_ class CSmartFrameWnd : public CFrameWnd { private: BOOL m_bFirstTime; virtual BOOL SaveWindowState (LPCTSTR); virtual BOOT, RestoreWindowState (LPCTSTR); public: CSmartFrameWnd () { m_bFirstTime = TRUE; } protected: afx_msg void OnShowWindow (BOOL, UINT); afx_msg void OnDestroy (); afx_msg void OnEndSession (BOOL); DECLARE_MESSAGE_MAP () }; #endif // _SMARTWIN_H_ //***************************************************** // // SMARTWIN.CPP - программа создания несущего окна SDI, // которое сохраняет информацию о своих размерах и // расположении на экране в последовательных сеансах // работы. // //***************************************************** #include <afxwin.h> #include "smartwin.h" /////////////////////////////////////////////////////// // Принадлежащие функции и карта обрабатываемых // сообщений класса CSmartFrameWnd. BEGTN_MESSAGE_MAP (CSmartFrameWnd, CFrameWnd) ON_WM_SHOWWINDOW () ON_WM_DESTROY () ON_WM_ENDSESSION () END MESSAGE MAP { ) void CSmartFrameWnd::OnShowWindow (BOOL bShow, UINT nStatus) { if (m_bFirstTime) { m_bFirstTime = FALSE; RestoreWindowState ("Version 1.0"); return; } CFrameWnd::OnShowWindow (bShow, nStatus); } void CSmartFrameWnd::OnDestroy () { SaveWindowState ("Version 1.0"); CFrameWnd::OnDestroy (); } void CSmartFrameWnd::OnEndSession (BOOL bEnding) { if (bEnding) SaveWindowState ("Version 1.0"); CFrameWnd::OnEndSession (bEnding); } BOOL CSmartFrameWnd::SaveWindowState (LPCTSTR lpszVersion) { WINDOWPLACEMENT wp; wp.length = sizeof (WIHDOWPLACEMENT); GetWindowPlacement (&wp); BOOL bResult = TRUE; CWinApp* pApp = AfxGetApp (); if ((!pApp->WriteProfileInt (lpszVersion, "flags", wp.flags)) || (!pApp->WriteProfileInt (lpszVersion, "showCmd", wp.showCmd)) || (!pApp->WriteProfilelnt (lpszVersion, "x1", wp.rcNormalPosition.left)) || (!pApp->WriteProfileInt (lpszVersion, "y1", wp.rcNormalPosition.top)) || (!pApp->WriteProfileInt (lpszVersion, "x2", wp.rcNormalPosition.right)) || (!pApp->writeProfileInt (lpszVersion, "y2", wp.rcNormalPosition.bottom))) bResult = FALSE; return bResult; } BOOL CSmartFrameWnd::RestoreWindowState (LPCTSTR lpszVersion) { WINDOWPLACEMENT wp; wp.length = sizeof (WINDOWPLACEMENT); GetWindowPlacement (&wp); CWinApp* pApp = AfxGetApp (); wp.flags = pApp->GetProfileInt (lpszVersion, "flags", -1); wp.showCmd = pApp->GetProfileInt (lpszVersion, "showCmd", -1); wp.rcNormalPosition.left = pApp->GetProfileInt (lpszVersion, "x1", -1); wp.rcNormalPosition.top = pApp->GetProfileInt (lpszVersion, "y1", -1); wp.rcNormalPosition.right = pApp->Get.ProfileInt (lpszVersion, "x2", -1); wp.rcNormalPosition.bottom = pApp->GetProfileInt (lpszVersion, "y2", -1); if ((wp.flags ! = -1) && (wp.showCmd != -1) && (wp.rcNormalPosition.left != -1) && (wp.rcNormalPosition.top != -1) && (wp.rcNormalPosition.right != -1) && (wp.rcNormalPosition.bottom != -1)) { if ((wp.rcNormalPosition.left <= (::GetSystemMetrics (SM_CXSCREEN) - ::GetSystemMetrics (SM_CXTCON))) && (wp.rcNormalPosition.top <= (::GetSystemMetrics (SM_CYSCREEN) - ::GetSystemMetrics (SM_CYICON)))) { SetWindowPlacement (&wp); return TRUE; } } return FALSE; }
Джефф Просис - редактор журнала PC Magazine.