Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых
[Назад] [Содержание] [Дальше]

Визуальное проектирование приложений C#


А.В. Фролов, Г.В. Фролов

Глава 1. Принципы работы приложений Windows. 2

Разновидности программ.. 2

Синхронное и асинхронное выполнение программ.. 2

Однопоточные и многопоточные программы.. 3

Сообщения. 3

Создание сообщений. 3

Очередь сообщений. 3

Обработка сообщений. 4

Фокус ввода. 4

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

Функция окна. 5

Передача сообщений. 6

Создание и уничтожение окна. 7

Структура приложения с обработкой сообщений. 8

Ресурсы приложений Microsoft Windows. 12

Как все непросто в мире Windows. 12

 

Глава 1. Принципы работы приложений Windows

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

Если Вы никогда раньше не создавали программы для Microsoft Windows, механизм сообщений может показаться довольно запутанным. Среда разработки приложений Microsoft Visual Studio .NET скрывает от программиста многие второстепенные детали, поэтому нет необходимости вникать во все тонкости этого механизма. Однако  хотя бы краткое знакомство с ним будет крайне полезно.

Если же Вы раньше создавали приложения Microsoft Windows на языках C или C++, и хорошо знакомы с механизмом  передачи и обработки сообщений, можете пропустить этот материал и сразу переходить к чтению следующей главы.

Разновидности программ

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

Синхронное и асинхронное выполнение программ

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

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

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

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

Нормальный ход выполнения программы MS-DOS могут нарушать так называемые аппаратные и программные прерывания (interrupt).

Аппаратные прерывания (hardware interrupt) вырабатываются устройствами ввода или вывода, когда им требуется обслуживание со стороны операционной системы. Использование аппаратных прерываний позволяет совместить относительно медленные операции ввода и вывода с работой других программ или модулей. Программные прерывания (software interrupt) инициируются самой программой.

Рассмотрим, например, процесс печати документа.

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

Асинхронная процедура печати выполняется следующим образом.

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

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

Однопоточные и многопоточные программы

Операционная система MS-DOS допускает в любой момент времени работу только одной программы. Это означает, что после запуска программы пользователь должен дождаться ее завершения, и только потом он сможет запустить другую программу.

В отличие от MS-DOS, современные операционные системы, такие как Microsoft Windows и Linux, работают в так называемом многопоточном (multithreading) режиме.

При этом ресурсы одного или нескольких центральных процессоров используются для одновременного выполнения нескольких программ. Этим процессом управляет специальный системный процесс — планировщик, периодически выделяя каждому потоку выполнения свой квант времени. Кванты времени выделяются по прерываниям таймера.

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

Сообщения

Обрисованную выше картину дополнительно усложняют так называемые сообщения Microsoft Windows (Windows Messages), вырабатываемые в программах Microsoft Windows.

Что это за сообщения и откуда они берутся?

Чтобы ответить на этот вопрос, вспомним, что в среде Microsoft Windows могут выполняться консольные программы и программы с графическим оконным интерфейсом.

Создание консольных программ на языке C# было нами детально описано в [3]. В том, что касается пользовательского интерфейса, такие программы напоминают программы MS-DOS и утилиты Unix-подобных операционных систем. Пользователь запускает программу, вводя путь к ее загрузочному файлу в командном приглашении ОС. Далее программа может выводить на консоль текстовые строки и воспринимать ввод данных с клавиатуры.

Консольные программы очень хорошо подходят для изучения языков программирования, однако все современные настольные приложения Microsoft Windows снабжаются графическим оконным интерфейсом. Мы полагаем, что Вы хорошо владеете приемами работы с готовыми приложениями Windows на уровне опытного пользователя, поэтому не будем вдаваться в подробное описание элементов пользовательского интерфейса оконных программ.

Для нас сейчас важен внутренний механизм, обеспечивающий работу таких элементов управления оконными программами, как меню, поля ввода, списки, кнопки, флажки и т.п. Этот механизм предполагает создание и обработку упомянутых выше сообщений Microsoft Windows.

Создание сообщений

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

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

Очередь сообщений

Куда направляются сообщения, созданные драйверами?

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

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

Обработка сообщений

Основная работа, которую должно выполнять приложение, заключается в обслуживании собственной очереди сообщений. Обычно приложение в цикле опрашивает свою очередь сообщений. Обнаружив сообщение, приложение с помощью специальной функции из программного интерфейса Windows (Win32 API) распределяет его нужному программному модулю, называемому функции окна. Эта функция и выполняет обработку сообщения.

Структура приложения Microsoft Windows состоит из инициализирующей части, вслед за которой идет так называемый цикл обработки сообщений (message loop).

Обращаем внимание, что инициализирующая часть не выполняет никакой работы, имеющей отношение к поведению приложения. Ее задача заключается к подготовке цикла обработки сообщений и в запуске этого цикла. Что же касается реализации функциональности приложения Microsoft Windows, то она целиком ложится на программные модули (функции), вызываемые внутри цикла обработки сообщений.

В этом смысле приложения Microsoft Windows похожи на загружаемые драйверы MS-DOS. Эти драйверы также состоят из инициализирующего фрагмента и функции, обрабатывающей команды, выдаваемые драйверу. Приложение Microsoft Windows после инициализации переходит в состояние постоянного опроса собственной очереди сообщений. Как только происходит какое-либо событие, имеющее отношение к приложению, в очереди приложения появляется сообщение и приложение начинает его обрабатывать. После обработки приложение вновь возвращается к опросу собственной очереди сообщений. Иногда функция окна может получать сообщения непосредственно, минуя очередь приложения.

Фокус ввода

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

В ОС Microsoft Windows существует понятие фокуса ввода (input focus), помогающее в распределении сообщений.

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

Нажимая определенные клавиши, пользователь может перемещать фокус ввода от одного окна или поля к другому.

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

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

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

Итак, теперь Вы знаете, что работа приложений Microsoft Windows управляется сообщениями, обрабатываемыми в специальном цикле, называемом циклом обработки сообщений (message loop).

Простейший цикл обработки сообщений состоит из вызовов двух функций Win32 API с именами GetMessage и DispatchMessage.

Функция GetMessage предназначена для выборки сообщения из очереди приложения. Сообщение выбирается из очереди и записывается в область данных, принадлежащую приложению.

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

ОС Microsoft Windows оказывает приложению существенную помощь в решении этой задачи — для распределения приложению достаточно вызвать функцию DispatchMessage.

Вот как выглядит простейший вариант цикла обработки сообщений в программе, составленной на языке C:

MSG msg;
while(GetMessage(&msg, 0, 0, 0))
{
  DispatchMessage(&msg);
}

Завершение цикла обработки сообщений происходит при выборке из очереди специального сообщения, в ответ на которое функция GetMessage возвращает нулевое значение.

Структура MSG хранит данные сообщения. Вот как она определена:

typedef struct tagMSG
{
  HWND hwnd;
  UINT message;
  WPARAM wParam;
  LPARAM lParam;
  DWORD  time;
  POINT  pt;
} MSGMSG;

Структура содержит уникальный для Microsoft Windows код сообщения message и другие параметры, имеющие отношение к сообщению.

В поле hwnd хранится идентификатор получателя сообщения, т.е. идентификатор окна, для которого это сообщение предназначено.

Через поля wParam и lParam передается информация, связанная с сообщением. Смысл содержимого данных полей зависит от кода сообщения message. Если сообщение вызвано, например, нажатием клавиши на клавиатуре компьютера, эти поля содержат код нажатой клавиши. Сообщения, генерируемые мышью, содержат код клавиши и координаты курсора.

Поле time хранит время отправления сообщения, а поле pt — координаты, связанные с сообщением.

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

Функция окна

Итак, теперь Вы знаете, что в основе приложений Microsoft Windows с графическим интерфейсом лежит цикл обработки сообщений. Когда приложение инициализируется, оно создает цикл обработки сообщений, а также выделяет функцию, которой будут передаваться сообщения. Эта функция называется функцией окна (window procedure).

Почему такое название?

Работа окон Microsoft Windows основана на обработке сообщений. Каждое окно имеет свой цикл обработки сообщений, а также свою функцию окна, которой передаются сообщения, извлеченные из очереди приложения.

Функция окна анализирует  код сообщения, записанный в поле message только что описанной структуры MSG, и в зависимости от этого кода выполняет те или иные действия. При этом функция окна может использовать (а может игнорировать) другие поля сообщения: hwnd, wParam, lParam, time и pt.

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

Процесс обработки сообщений схематически показан на рис. 1-1.

Рис. 1-1. Процесс обработки сообщений

На этом рисунке слева показаны ресурсы и подсистемы ОС Microsoft Windows, справа — ресурсы и подсистемы приложения. Сообщения поступают от драйверов устройств, таких как клавиатура, мышь, таймер, и попадают в системную очередь сообщений. Из системной очереди сообщений ОС Microsoft Windows выбирает сообщения, предназначенные для приложения, и помещает их в очередь сообщения приложения.

Если Вы раньше создавали программы MS-DOS на языке программирования C, то помнете, что при запуске программы управление передается функции с именем main. В оконных приложениях ОС Microsoft Windows эту роль играет функция WinMain.

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

На рис. 1-1 для простоты показано только одно приложение, в котором определена только одна функция окна. Как мы уже говорили, реально существует много приложений, и каждое приложение может иметь несколько функций окна. Каждое приложение вызывает в цикле обработки сообщений функцию GetMessage, выбирая сообщения из своей очереди. Затем каждое приложение распределяет выбранное сообщение одной из своих функций окна, для чего используется функция DispatchMessage.

Передача сообщений

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

Как мы уже говорили, ОС Microsoft Windows содержит в себе системную очередь сообщений. Сообщения поступают туда от драйверов устройств (при завершении операции ввода или вывода) или от приложений, а также несколько очередей сообщений для каждого приложения.

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

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

Приложение Microsoft Windows постоянно анализирует содержимое своей очереди сообщений. Когда в очереди появляется сообщение от какого-либо элемента управления, приложение выполняет соответствующее действие.

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

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

Каждый элемент управления (кнопка или строка меню) имеет свой идентификатор. Когда Вы нажимаете на кнопку или выбираете строку меню, в очередь сообщений приложения ОС заносит сообщение, содержащее идентификатор использованного элемента управления.

Приложение анализирует очередь сообщений и выполняет обработку сообщений. Например, если Вы нажали кнопку с надписью Exit, приложение завершает свою работу.

Следует отметить, что в Microsoft Windows используется многоуровневая система сообщений.

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

Когда Вы нажимаете кнопку на диалоговом окне или выбираете строку из меню приложения Windows, Ваше приложение получает сообщение о том, что нажата та или иная клавиша или выбрана та или иная строка в меню. Вам не надо постоянно анализировать координаты курсора мыши или коды нажимаемых клавиш — ОС Microsoft Windows сама вырабатывает соответствующее сообщение высокого уровня. Таким образом, Вы можете возложить на Windows всю работу, связанную с «привязкой» мыши и клавиатуры к элементам управления.

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

Программы MS-DOS в «классическом» исполнении работают по-другому. Как правило, такие программы выполняются линейно, ожидая от пользователя ввода той или иной команды и блокируя нежелательные на данный момент действия.

Логика работы приложений Microsoft Windows называется логикой, управляемой событиями. Под событием понимается обнаружение в очереди сообщений приложения того или иного сообщения. В любой момент времени пользователю разрешается использовать любые элементы управления любого запущенного приложения (хотя приложение при необходимости может выполнить блокировку отдельных элементов управления).

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

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

Создание и уничтожение окна

Функция окна получает сообщения при создании окна, в процессе работы приложения, а также при разрушении окна.

Сообщение с кодом WM_CREATE передается функции окна в момент создания окна. При обработке этого сообщения функция окна выполняет инициализирующие действия (аналогично конструктору класса в языке C++). Коды сообщений определены в файле windows.h, включаемом в исходные тексты любых приложений Microsoft Windows, написанных на языке C или C++.

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

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

Структура приложения с обработкой сообщений

Рассказывая в [4] о создании оконных приложений Microsoft Windows, мы в самом начале книги привели исходный текст простейшей программы с обработкой сообщений. И хотя теперь мы будем создавать приложения Microsoft Windows совершенно по-другому и на другом языке программирования, мы повторим описание этой программы в сильно сокращенном виде. Это поможет Вам лучше понять механизм обработки сообщений. Желающие могут найти полный текст этой книги и других наших книг серии «Библиотека системного программиста» на сайте службы восстановления данных DataRecovery.Ru по адресу http://info.datarecovery.ru.

Для простоты наше приложение имеет только одно окно и одну функцию окна. Его исходный текст приведен в листинге 1-1.

Листинг 1-1. Приводится с сокращениями

#include <windows.h>

// Имя класса окна       
char const szClassName[]  = "WindowAppClass";

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

int PASCAL WinMain(… [параметры опущены]…)
{
  MSG  msg; // структура для работы с сообщениями
  HWND hwnd;  // идентификатор главного окна приложения

  // Проверяем, не запускалось ли это приложение ранее
  if(!hPrevInstance)
  {
     // Если нет, вызываем функцию InitApp для инициализации
     // приложения. Если инициализацию выполнить не удалось,
     // завершаем приложение
     if(!InitApp(hInstance))
       return FALSE;
  }

  // Если данное приложение уже работает,
  // выводим сообщение о том, что допускается
  // запуск только одной копии приложения, и
  // затем завершаем приложение
  else
  {
     MessageBox(NULL,
      "Можно запускать только одну копию приложения",
      "Ошибка", MB_OK | MB_ICONSTOP);
     return FALSE;
  }

  // После успешной инициализации приложения создаем
  // главное окно приложения
  hwnd = CreateWindow(
     szClassName,       // имя класса окна
     szWindowTitle,       // заголовок окна
     … [параметры приведены с сокращениями] );

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

  // Рисуем окно. Для этого после функции ShowWindow,
  // рисующей окно, вызываем функцию UpdateWindows,
  // посылающую сообщение WM_PAINT в функцию окна

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

  // Запускаем цикл обработки сообщений
  while(GetMessage(&msg, 0, 0, 0))
  {
     DispatchMessage(&msg);
  }

  return msg.wParam;
}

// =====================================
// Функция InitApp
// Вызывается из функции WinMain для
// инициализации приложения.
// Выполняет регистрацию класса окна
// =====================================
BOOL InitApp(HINSTANCE hInstance)
{
  ATOM aWndClass; // атом для кода возврата
  WNDCLASS wc;     // структура для регистрации класса окна
  memset(&wc, 0, sizeof(wc));


  // Указатель на функцию окна, обрабатывающую
  // сообщения, предназначенные для всех окон,
  // созданных на основе данного класса
  wc.lpfnWndProc = (WNDPROC) WndProc;


  // Имя, которое присваивается создаваемому
  // классу и используется при создании
  // окон данного класса
  wc.lpszClassName = (LPSTR)szClassName;

  // Регистрация класса
  aWndClass = RegisterClass(&wc);

  // Возврат результата регистрации класса
  return (aWndClass != 0);
}

// =====================================
// Функция окна WndProc
// НЕ ВЫЗЫВАЕТСЯ ни из одной функции приложения. Эту функцию вызывает
// ОС в процессе обработки сообщений. Для этого адрес функции WndProc
// указывается при регистрации класса окна. Функция выполняет
// обработку сообщений главного окна приложения
// =====================================
LRESULT CALLBACK _export
WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
  // Выполняем обработку сообщений. Идентификатор
  // сообщения передается через параметр msg
  switch (msg)
  {
     // Это сообщение приходит, когда вы поместили курсор
     // мыши в область главного окна приложения и нажали
     // левую клавишу мыши
     case WM_LBUTTONDOWN:
     {
       MessageBox(NULL,
        "Нажата левая клавиша мыши",
        "Сообщение", MB_OK | MB_ICONINFORMATION);
       return 0;
     }
    
     // Это сообщение приходит, когда вы поместили курсор
     // мыши в область главного окна приложения и нажали
     // правую клавишу мыши
     case WM_RBUTTONDOWN:
     {
       MessageBeep(-1); // звуковой сигнал
       MessageBox(NULL,
          "Нажата правая клавиша мыши",
          "Сообщение", MB_OK | MB_ICONINFORMATION);
       return 0;
     }
    
     // Это сообщение приходит, когда вы завершаете
     // работу приложения стандартным для
     // Windows способом
     case WM_DESTROY:
     {
       // Инициируем завершение работы приложения,
       // помещая в очередь приложения сообщение
       // WM_QUIT. Это приведет к завершению
       // цикла обработки сообщений в функции WinMain
       PostQuitMessage(0);
       return 0;
     }
  }

  // Все сообщения, которые не обрабатываются нашей
  // функцией окна, ДОЛЖНЫ передаваться функции
  // DefWindowProc
  return DefWindowProc(hwnd, msg, wParam, lParam);
}

Если посмотреть исходный текст нашего приложения, то легко заметить, что оно имеет несколько необычную (с точки зрения программиста, составляющего программы для MS-DOS) структуру. В частности, функция WinMain после выполнения инициализирующих действий входит в цикл обработки сообщений. После выхода из этого цикла работа приложения завершается. Функция WndProc вообще не вызывается ни из какой другой функции приложения, хотя именно она выполняет всю «полезную» работу.

Составляя программы для MS-DOS, программисты привыкли к тому, что за весь сценарий работы программы отвечает функция main. Эта функция выполняет вызов всех остальных функций (за исключением функций обработки прерываний), из которых и состоит программа.

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

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

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

Функция окна обрабатывает три сообщения с кодами WM_LBUTTONDOWN, WM_RBUTTONDOWN и WM_DESTROY.

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

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

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

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

if(приложение уже было запущено)
{
  Завершение работы текущей копии приложения
}
Создание класса окна
Создание главного окна приложения на основе созданного ранее класса
Отображение окна на экране
while(в очереди нет сообщения WM_QUIT)
{
  Выборка сообщения и распределение его функции окна
}
Завершение работы приложения

Адрес функции окна указывается при создании класса окна. Этот адрес используется ОС Microsoft Windows для вызова функции окна (напомним, что приложение само не вызывает функцию окна). Приведем алгоритм работы функции окна нашего простейшего приложения:

switch(Идентификатор сообщения)
{
  case WM_LBUTTONDOWN:
  {
     Вывод диалоговой панели с сообщением о том,
     что была нажата левая кнопка мыши
  }
  case WM_RBUTTONDOWN:
  {
     Выдача звукового сигнала
     Вывод диалоговой панели с сообщением о том,
     что была нажата правая кнопка мыши
  }
  case WM_DESTROY:
  {
     Запись в очередь приложения сообщения WM_QUIT,
     при выборке которого завершается цикл обработки   сообщений
  }
}
Вызов функции DefWindowProc, обрабатывающей все
остальные сообщения, поступающие в функцию окна

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

Если функция окна проигнорирует вызов функции DefWindowProc для тех сообщений, которые она сама не обрабатывает, Microsoft Windows не сможет обработать такие сообщения. Это может привести к неправильной работе, блокировке приложения или даже всей ОС.

Ресурсы приложений Microsoft Windows

Формат загрузочного модуля оконного приложения Microsoft Windows сложнее формата загрузочного модуля программы MS-DOS. Кроме выполняемого кода и констант в загрузочном модуле такого приложения находятся дополнительные данные — ресурсы.

Что такое ресурсы?

Приложение Microsoft Windows может хранить в виде ресурсов текстовые строки, значки (icons), курсоры различной формы, графические изображения, меню, диалоговые окна, произвольные массивы данных и т. д.

Физически ресурсы находятся внутри exe-файла приложения. Они могут загружаться в оперативную память автоматически при запуске приложения или по запросу приложения (явному или неявному). Такой механизм обеспечивает экономное расходование оперативной памяти, так как все редко используемые данные можно хранить на жестком диске и загружать в оперативную память только при необходимости.

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

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

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

Как видите, для описания принципов работы даже самого примитивного приложения Microsoft Windows c одним-единственным окном нам потребовалось больше десяти страниц текста. Очевидно, программировать такие приложения на языке C — непростая задача, особенно если приложение содержит десятки и сотни окон, множество меню, списков и других элементов управления. А ведь именно так и выглядят настоящие приложения Microsoft Windows!

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

В нашей книге мы рассмотрим средства ускоренной разработки приложений RAD, доступные программистам при использовании Microsoft Visual Studio .NET. Как Вы увидите, эти средства включают в себя не только мощные и удобные инструменты визуального проектирования дизайна приложений, но и весьма обширную библиотеку классов. Эта библиотека позволяет использовать готовые решения задач, чаще всего встающих перед разработчиком приложений для Microsoft Windows.

В частности, класс System.Windows.Forms, о котором мы еще будем рассказывать подробно, позволяет кардинально упростить задачу создания окон и управления ими. Разработчику приложений C# нет необходимости самостоятельно организовывать цикл обработки сообщений и определять функции окна — все это скрыто внутри класса System.Windows.Forms.

Программист работает на уровне обработчиков событий, возникающих, например, при использовании элементов управления. Соответствующие средства удобны и встроены непосредственно в язык C#. Более того, инструментальная система Microsoft Visual Studio .NET умеет автоматически создавать «пустые» обработчики событий, привязанные к элементам управления, так что программисту остается лишь наполнить эти обработчики необходимым содержимым.

Заметим, что объекты класса System.Windows.Forms — это больше, чем обычные окна Microsoft Windows. Перетаскивая из панели инструментов изображения элементов управления и настраивая различные параметры, программист может придать такому окну любой необходимый внешний вид.

Теперь при разработке дизайна приложения программист не ограничен серыми стандартными кнопками и списками ОС Microsoft Windows. Помимо готовых элементов управления, значки которых расположены в инструментальных панелях Microsoft Visual Studio .NET, можно использовать любые другие элементы управления, созданные при помощи все той же системы разработки.

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

[Назад] [Содержание] [Дальше]