Книги

1. Пустое окно


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

  1. Написать оконную функцию минимального вида.
  2. Сформировать и зарегистрировать в системе собственный оконный класс.
  3. Создать окно и организовать цикл обработки сообщений.

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


1.1 Оконная функция

Минимальная оконная функция будет иметь следующий вид:

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() является код возврата приложения. По традиции нулевое значение кода возврата означает нормальное завершение.

Замечательно - минимальную оконную функцию, которая нам когда-нибудь послужит каркасом для более сложных, мы написали. Что теперь с ней делать?


1.2 Класс окна

Займемся определением класса окна. По сути - это набор некоторых атрибутов, которые будут одинаковы для всех окон данного типа. Например, вышеупомянутая оконная функция определяет одинаковую реакцию на сообщения.

Следующий код реализует создание нового класса окна:

. . . . .
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, в таком случае освобождается автоматически операционной системой, поскольку находится в локальной куче приложения.


1.3 Создание окна

Итак, мы зарегистрировали класс и теперь можно создать наше окно.

. . . . .
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() возвращает дескриптор свежесозданного окна, при помощи которого мы сможем в дальнейшем к окну обращаться.


1.4 Цикл обработки сообщений

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

. . . . .
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() передает сообщения соответствующим окнам на обработку.


1.5 Резюме

Собственно, все необходимое для создания приложения "Hello, World!" мы разобрали по косточкам. Осталось свести воедино.

В модуле HelloWindow (hellowindow.pp) находится оконная функция и регистрация класса. Создание окна и цикл обработки сообщений - в главном модуле программы Hello (hello.pp). См. архив.

Советую обратить внимание на директиву компилятора {$APPTYPE GUI} в начале файла hello.pp - в Win32 существует два типа приложений: графические (как наше) и консольные.

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

Актуальные версии
FPC3.2.2release
Lazarus3.2release
MSE5.10.0release
fpGUI1.4.1release
links
https://www.mediccity.ru остеопороз симптомы и лечение как лечить остеопороз.
Купить шлифовальные круги electrocrystall.com.
С хорошей скидкой продажа участков в казани под любые нужды.