 Книги
КнигиКак ни странно, но для того только, чтоб вывести пустое окно нам потребуется совершить довольно много действий. А именно:
Такая конструкция возникает от того, что программа для Win32 - это по сути набор окон, которые общаются друг с другом и с операционной системой посредством сообщений. Оконная функция - это и есть функция обработки сообщений.
Минимальная оконная функция будет иметь следующий вид:
function WndProc ( Wnd : HWnd; Msg : cardinal; wParam, lParam : longint ) : longint; stdcall; begin result := 0; case Msg of WM_DESTROY : PostQuitMessage (0) else result := DefWindowProc (Wnd, Msg, wParam, lParam) end end;
Рассмотрим аргументы:
Wnd : HWnd - дескриптор (handle) окна. Уникальный идентификатор нашего окна в системе. Заметим, что одна оконная функция может обрабатывать и несколько окон, поскольку является атрибутом класса, а не конкретного экземпляра окна. Тип HWnd определен в модуле Windows.Msg : cardinal - целое без знака, являющееся идентификатором сообщения, посылаемого окну и, соответственно, обрабатываемого данной функцией. Идентификаторы сообщений, посылаемых системой, равно как и сообщений, предназначенных для работы со стандартными оконными элементами управления, определены также в модуле Windows.wParam, lParam : longint - дополнительная информация, передаваемая вместе с сообщением. Конкретное содержание зависит от сообщения.Возвращаемое значение зависит от переданного сообщения. В большинстве случаев можно возвращать 0, как указание на то, что никакие дополнительные действия не потребуются.
Следует обратить внимание на тип вызова функции - stdcall. На данный момент Free Pascal использует такое соглашение о вызове по умолчанию, однако для надежности и во избежание возможных проблем в дальнейшем, я бы рекомендовал указывать его явно - как в данном примере. Напомню, что соглашение о вызове stdcall предполагает, что параметры передаются в стек справа налево, а очищает стек вызываемая подпрограмма.
Итак, что мы делаем в теле функции? Во-первых, указываем умолчательный возврат нуля. В нашем простом случае, конечно, можно его прописать и в обработке конкретного сообщения, пока оно одно, но когда нам придется обрабатывать десятки (а то и больше) сообщений, для большинства из которых нормальное выполнение должно возвращать 0, привычка устанавливать его в самом начале лишней не будет.
Затем, оператор case разбирает сообщения. В данном примере, поскольку мы хотим получить простейшее окно без каких-либо полезных действий, почти все сообщения предоставим обработке по умолчанию. Такую обработку и выполняет функция DefWindowProc(), аргументы которой (и возвращаемое значение тоже) соответствуют аргументам оконной функции. DefWindowProc() объявлена в модуле Windows.
Единственное сообщение, которое нам требуется обрабатывать - WM_DESTROY, которое система посылает при закрытии окна. Дело в том, что нам требуется закончить работу программы, получив это сообщение, тогда как обработка по умолчанию не предполагает этого. Очевидно потому, что окно в общем случае может быть и не одно. Мы же вызываем функцию PostQuitMessage(), которая устанавливает в очередь сообщений приложения сообщение WM_QUIT, по которому в дальнейшем мы должны прекратить цикл обработки сообщений и выйти из программы. Аргументом PostQuitMessage() является код возврата приложения. По традиции нулевое значение кода возврата означает нормальное завершение.
Замечательно - минимальную оконную функцию, которая нам когда-нибудь послужит каркасом для более сложных, мы написали. Что теперь с ней делать?
Займемся определением класса окна. По сути - это набор некоторых атрибутов, которые будут одинаковы для всех окон данного типа. Например, вышеупомянутая оконная функция определяет одинаковую реакцию на сообщения.
Следующий код реализует создание нового класса окна:
. . . . . const CN_MAIN = 'our_main_window'; . . . . . var WndClass : PWndClassEx; initialization WndClass := new(PWndClassEx); WndClass^.cbSize := sizeof(TWndClassEx); WndClass^.style := CS_DBLCLKS or CS_HREDRAW or CS_OWNDC or CS_VREDRAW; WndClass^.lpfnWndProc := @WndProc; WndClass^.cbClsExtra := 0; WndClass^.cbWndExtra := 0; WndClass^.hInstance := HInstance; WndClass^.hIcon := LoadIcon (0, IDI_ASTERISK); WndClass^.hCursor := LoadCursor (0, IDC_ARROW); WndClass^.hbrBackground := COLOR_BTNFACE + 1; WndClass^.lpszMenuName := nil; WndClass^.lpszClassName := CN_MAIN; WndClass^.hIconSm := 0; if RegisterClassEx (WndClass^) = 0 then begin MessageBox (0, 'Класс не регистрируется!', 'Ошибка!', MB_ICONSTOP); Halt (1) end; dispose(WndClass) . . . . .
Итак, по порядку:
Константа CN_MAIN - это имя класса. Поскольку в дальнейшем, когда класс зарегистрирован, мы будем обращаться к оконному классу сугубо по этому имени, вынос его в константу позволит нам избежать ошибок, связанных с мелкими опечатками, точнее - обнаруживать их на этапе компиляции, а не выполнения.
Переменная WndClass используется для того, чтобы создать в динамической памяти структуру, описывающую класс. Мы используем структуру типа TWndClassEx, хотя можно было бы и удовлетвориться TWndClass. Однако, варианты без Ex - это наследие Win16, и лучше отучаться (или не приучаться) их использовать. Хотя в данном случае - разница между TWndClassEx и TWndClass всего в одном поле - hIconSm, которое к тому же в настоящем примере мы просто зануляем.
Вначале мы выделяем память под структуру (new), а в конце ее освобождаем (dispose), поскольку после того, как класс зарегистрирован, нужда в ней отпадает.
Рассмотрим поля структуры:
cbSize - целое число, определяющее размер структуры.style - стиль класса, образуется побитовым сложением различных флагов. В настоящем примере мы указали флаги CS_DBLCLKS, CS_HREDRAW, CS_OWNDC и CS_VREDRAW. Впрочем, на этапе минимальной и примитивнейшей программы, которую мы создаем, можно было и 0 поставить. Эти же флаги применимы в большинстве случаев, и мы их использовали скорее в шаблонных, чем в конкретных целях. Тем не менее - стоит пояснить их значения:
  CS_DBLCLKS - определяет, будут ли окна данного класса получать сообщения о двойном щелчке мыши. Если данный флаг не указан, то для определения двойного щелчка придется анализировать интервал и расстояние между одиночными, что, согласитесь, менее удобно.CS_HREDRAW и CS_VREDRAW - определяют, нуждается ли окно в перерисовке при изменении его горизонтальных и вертикальных, соответственно, размеров.CS_OWNDC - означает, что каждое окно использует свой собственный графический контекст для рисования, а не контест родительского окна или общий для всех окон класса.lpfnWndProc - как явствует из имени данного поля, оно содержит указатель на оконную функцию. Которую мы собственно и определили в предыдущем разделе.cbClsExtra и cbWndExtra - количество дополнительных байт, отводимых системой для класса и для каждого окна соответсвенно. В дальнейшем  программа может использовать эти данные посредством функций GetClassLong()/SetClassLong() и GetWindowLong()/SetWindowLong() соответственно (есть также варианты с Word вместо Long). Мы сейчас не нуждаемся в выделении дополнительной памяти, хотя в реальных задачах часто требуется придать окну собственный блок памяти. Замечу однако, что объем памяти, который может быть выделен таким образом - весьма невелик, и обычно используется для хранения указателя на область памяти, выделенную другими средствами.hInstance - дескриптор (handle) программного модуля. При инициализации модуля System необходимое значение помещается в глобальную переменную HInstance, к которой мы здесь и обратились.hIcon - дескриптор значка, который будет присвоен окну. Поскольку мы пока не используем собственных ресурсов и собственных значков, загружаем стандартный вызовом LoadIcon() с нулевым первым параметром. Второй параметр - это имя, или идентификатор, значка в ресурсе - мы использовали предопределенное значение - константу IDI_ASTERISK.hCursor - дескриптор курсора, который будет использоваться, когда указатель мыши будет находиться в нашем окне. Опять же, за неимением своего - используем стандартный. Впрочем, и в дальнейшем, когда сможем использовать свои курсоры, лучше ими не злоупотреблять.hbrBackground - дескриптор (handle) кисти, которая будет использоваться в качестве фона клиентской части окна. Вообще, кисть (brush) - это такой графический объект, определяющий способ закраски. Может быть трех типов: цветом, штриховкой и точечным шаблоном. Однако, здесь мы использовали самый простой и самый неочевидный способ ее задания - через указание системного цвета с добавлением единицы. Константа COLOR_BTNFACE дает нам закраску стандартных кнопок, определенную текущими настройками Windows.lpszMenuName - имя ресурса меню, которое будет присвоено по умолчанию всем окнам класса. Мы пока не пользуемся меню, кроме того, существует более гибкий способ его задания - при непосредственном создании окна.lpszClassName - это поле должно указывать на строку содержащую имя класса. В нашем случае - это константа CN_MAIN.hIconSm - дескриптор малого значка, то есть того, который выводится слева от заголовка окна (да и почти во всех других случаях). Когда это поле равно нулю, используется значок, указанный hIcon.Заполнив все поля мы передаем структуру функции RegisterClassEx(). Если бы мы использовали старую версию структуры - TWndClass, то и функцию бы взяли RegisterClass(). RegisterClassEx() возвращает специальное значение - атом, соответствующий нашему классу, если регистрация прошла успешно. В противном случае возвращаемое значение - 0, что мы и проверяем.
Мы намеренно поместили регистрацию класса в секцию initialization, таким образом она происходит гарантированно один раз при начале работы программы. В случае, если регистрация неудачна, дальнейшее выполнение программы не имеет никакого смысла, посему вызываем процедуру Halt(). Замечу, что память, выделенная средствами Free Pascal, в таком случае освобождается автоматически операционной системой, поскольку находится в локальной куче приложения.
Итак, мы зарегистрировали класс и теперь можно создать наше окно.
. . . . .
wndMain := CreateWindowEx (
           WS_EX_APPWINDOW or WS_EX_CONTROLPARENT or WS_EX_WINDOWEDGE,
           CN_MAIN,
           'Hello, World!',
           WS_CAPTION or WS_CLIPCHILDREN or WS_MAXIMIZEBOX or WS_MINIMIZEBOX
                      or WS_OVERLAPPED or WS_SIZEBOX or WS_SYSMENU
                      or WS_VISIBLE,
           CW_USEDEFAULT, CW_USEDEFAULT,
           CW_USEDEFAULT, CW_USEDEFAULT,
           0,
           0,
           HInstance,
           nil
           );
. . . . .
Что мы делаем? Для того, чтобы создать окно, мы вызываем функцию WinAPI CreateWindowEx(). Как и во многих других случаях, мы могли бы использовать вариант без Ex - функцию CreateWindow() и были бы лишены первого параметра - расширенных стилей окна.
Разберем аргументы функции:
dwExStyle - расширенные стили. Представляет из себя комбинацию флагов стилей. В нашем случае были использованы следующие флаги:
  WS_EX_APPWINDOW - употребляется для главного окна приложения - явное указание того, что окно должно иметь кнопку на панели задач.WS_EX_CONTROLPARENT - флаг используется для того, чтобы между дочерними окнами данного можно было переключаться по клавише Tab. В нашем минимальном примере, конечно, эта возможность не используется, флаг я указал в "шаблонных" целях.WS_EX_WINDOWEDGE - флаг "объемной" кромки окна. Именно такой вид границы окна, как правило, используется программами Win32.lpClassName - имя класса окна. Тут-то нам предусмотрительно введенная константа CN_MAIN и пригодилась.lpWindowName - заголовок (имя) окна. Строка, которая будет выведена в заголовке окна. Она же в дальнейшем может быть использована для поиска окна, если неизвестен его дескриптор.dwStyle - стили окна. Набор флагов, определяющих различные свойства окна. Вообще, таких стилей огромное количество. Поясню использованные:
  WS_CAPTION - указание выводить заголовок окна.WS_CLIPCHILDREN - связан с механизмом прорисовки содержимого окна - указывает на то, что из области перерисовки исключаются области, занятые дочерними окнами, что логично - все равно те перерисовывают себя сами. В нашем случае - добавлен на вырост.WS_MAXIMIZEBOX - окно имеет кнопку максимизации.WS_MINIMIZEBOX - окно имеет кнопку минимизации (сворачивания).WS_OVERLAPPED - нормальное перекрывающееся окно. В отличие от окон со стилями WS_CHILD и WS_POPUP окна с данным стилем перекрывают друг друга в зависимости только от того, какое активно (точнее от позиции Z-Order), не обращая внимание на отношения "родительское-дочернее".WS_SIZEBOX, несмотря на BOX, кнопок не добавляет. Обозначает то, что размеры окна могут быть изменены пользователем.WS_SYSMENU - окно имеет системное меню - то, которое открывается по щелчку на иконке слева от заголовка. Без стиля WS_CAPTION смысла не имеет.WS_VISIBLE - флаг видимости - окно отображается сразу после создания.x, y - позиция окна. Точнее - позиция его левого верхнего угла. Мы использовали константу CW_USEDEFAULT, чтобы система сама определила, где ей удобнее его выводить.width, height - ширина и высота окна. И снова отдаем право решать системе.hWndParent - дескриптор родительского окна. Для главного окна программы естественно указать ноль.hMenu - дескриптор (handle) меню окна. Мы пока с меню не заморачиваемся и указываем ноль.hInstance - дескриптор программного модуля. Как и в случае регистрации класса, мы используем глобальную переменную HInstance.lpParam - указатель на некие дополнительные данные, которые должны быть переданы окну после его создания. В нашем случае - не используется.Функция CreateWindowEx() возвращает дескриптор свежесозданного окна, при помощи которого мы сможем в дальнейшем к окну обращаться.
Окно создано. Теперь требуется организовать передачу ему сообщений. Сообщения система ставит в очередь сообщений приложения, откуда нам следует их выбирать и передавать окну для обработки.
. . . . . var Msg : TMsg; . . . . . while GetMessage (Msg, 0, 0, 0) do begin TranslateMessage (Msg); DispatchMessage (Msg) end; . . . . .
Вот и наш цикл. Вообще говоря, в цикле могут производиться и дополнительные действия, например - для того, чтобы преобразовывать нажатия клавиш в команды в соответствии с таблицей акселераторов.
Тип TMsg описывает структуру, содержащую данные сообщения. Пока мы ее поля разбирать не будем. Заметим лишь, что они соответствуют аргументам оконной функции, с добавлением времени и позиции курсора на момент посылки сообщения.
Функция GetMessage() выбирает сообщение из очереди. Если это не сообщение WM_QUIT, то функция возвращает TRUE, и выполняется очередная итерация цикла. В случае же WM_QUIT, возращается FALSE и, соответственно, происходит выход из цикла и завершение программы. Первый параметр - структура, куда записываются данные сообщения, второй параметр - дескриптор окна, если мы хотим получать сообщения только для него (0 - для всех окон). Третий и четвертый параметр определют диапазон выбираемых сообщений - нули соответствуют отсутствию фильтрации.
Функция TranslateMessage() преобразует сообщения о нажатии/отпускании клавиш в сообщения о клавиатурном вводе в соответствии с текущей раскладкой и состоянием Shift, Caps Lock и т.д. В нашем случае потребности в ней нет, однако в большинстве реальных приложений использование этой функции необходимо.
Функция DispatchMessage() передает сообщения соответствующим окнам на обработку.
Собственно, все необходимое для создания приложения "Hello, World!" мы разобрали по косточкам. Осталось свести воедино.
В модуле HelloWindow (hellowindow.pp) находится оконная функция и регистрация класса. Создание окна и цикл обработки сообщений - в главном модуле программы Hello (hello.pp). См. архив.
Советую обратить внимание на директиву компилятора {$APPTYPE GUI} в начале файла hello.pp - в Win32 существует два типа приложений: графические (как наше) и консольные.
В результате должна получиться программа, выглядящая примерно так:
  
| FPC | 3.2.2 | release | 
| Lazarus | 3.2 | release | 
| MSE | 5.10.0 | release | 
| fpGUI | 1.4.1 | release |