Смекни!
smekni.com

Ознакомление с приложениями Windows (стр. 14 из 18)

Особенности ООП в Windows

На самом деле Windows не является настоящей объектно–ориентированной средой. Хотя окно и может быть названо объектом ООП, но лишь с достаточной натяжкой. Самое существенное отличие окна в Windows от объекта ООП заключается в том, что сообщение, обрабатываемое оконной функцией, во многих случаях не выполняет действий, а является “информационным”, указывая на то, что над окном выполняется та или иная операция какой–либо внешней функцией.

Поясним это на примере создания окна. В случае ООП для уничтожения объекта он должен получить сообщение “destroy”, обработка которого приведет к его уничтожению. В Windows сообщение WM_DESTROY не выполняет никаких функций по уничтожению окна. Оно только информирует окно о том, что в это время окно уничтожается средствами обычной функциональной библиотеки, например посредством функции DestroyWindow. Вы можете вообще игнорировать это сообщение, возвращать любое значение, вызывать или не вызывать функцию обработки по умолчанию — окно все равно будет уничтожено.

С точки зрения реализации объектов это приводит к тому, что большая часть методов представлена в двух вариантах — один вызывает соответствующую функцию API, а другой вызывается при обработке соответствующего сообщения. Так в случае функции DestroyWindow и сообщения WM_DESTROY для класса, представляющего окно, будет существовать два метода: метод Destroy и метод OnDestroy (названия методов условны, в реальных библиотеках они могут отличаться). Метод Destroy будет соответствовать вызову функции DestroyWindow, а метод OnDestroy — обработчику сообщения WM_DESTROY.

На этом, к сожалению, сложности не кончаются. Допустим, что вы хотите сделать так, что бы окно не уничтожалось. На первый взгляд надо переопределить метод Destroy, что бы он не вызывал функцию DestroyWindow; при этом вызов метода Destroy не будет уничтожать окно. Однако все гораздо сложнее: окно по–прежнему может быть уничтожено прямым обращением к функции API — DestroyWindow. Более того, стандартные обработчики сообщений (то есть принадлежащие Windows, а не библиотеке классов) так и делают. Так стандартная обработка сообщения WM_CLOSE приводит к вызову функции DestroyWindow (а не метода Destroy). То есть для решения подобной задачи надо переопределить все методы объекта и все обработчики сообщений, которые ссылаются на соответствующую функцию API. Задача фактически не решаемая — особенно с учетом недостаточно подробной и точной документации.

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

Базовые классы приложения

Когда разрабатывается обычное приложение на C или C++ без использования классов, то надо разработать как функцию WinMain, определяющую работу приложения в целом, так и оконную процедуру, определяющую реакцию окна на внешние воздействия. При применении объектов эти функции возлагаются на методы классов. Естественно, что и в MFC, и в OWL существуют классы, предназначенные как для описания приложения в целом, так и для описания окон. Эти классы должны использоваться в качестве классов–предков, от которых порождаются классы, описывающие ваше приложение и его главное окно.

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

MFC

В библиотеке Microsoft Foundation Classes для описания приложения используются следующие классы:

Рисунок 5. Классы MFC, описывающие окно и приложение.

Данная версия MFC рассчитана на разработку многопотоковых приложений для Win32. Это наложило свой отпечаток на иерархию классов — в качестве одного из базовых выступает класс CWinThread, описывающий отдельный поток. И только на его основе построен класс CWinApp, описывающий приложение (в Win32 существует понятие основного потока, который обслуживает функцию WinMain, именно он и будет представлен объектом класса CWinThread, на основе которого порождается объект класса CWinApp).

OWL

В этой библиотеке иерархия классов несколько иная — библиотека рассчитана на разработку 16ти разрядных приложений Windows 3.x, не поддерживающую потоков.

Рисунок 6. Классы OWL, описывающие окно и приложение.

Окна ООП и окна Windows

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

· существуют методы классов, соответствующие вызову функций API;

· существуют методы классов, соответствующие обработчикам сообщений;

а, с другой стороны:

· существуют окна, созданные с помощью классов ООП;

· существуют окна, созданные другими приложениями, модулями а также стандартными средствами Windows, не имеющими отношения к применяемой библиотеке.

Так, например, диалог “Открыть Файл” является стандартным диалогом Windows. Он создается и выполняется посредством вызова одной функции API — FileOpen. Эта функция сама, независимо от приложения и его библиотеки классов, создает необходимые окна и работает с ними. Однако у программиста может возникнуть необходимость как–то взаимодействовать с этим диалогом в процессе его работы.

Можно выделить четыре возможных ситуации, с которыми придется столкнуться во время работы приложения:

1. должна быть вызвана функция API для окна, реализованного как объект класса;

2. должна быть вызвана функция API для окна, не являющегося объектом класса;

3. окно, реализованное как объект класса, получает сообщение — то есть надо вызвать соответствующий метод–обработчик этого сообщения;

4. окно, не являющееся объектом класса, получает сообщение.

Случаи 1 и 2 решаются сравнительно просто — среди членов–данных класса должен присутствовать член, задающий хендл окна в Windows. В таком случае вызов функций API, нуждающихся в хендле, происходи элементарно. Небольшой нюанс связан с окнами, не являющимися объектами класса. Например, диалоги, включая стандартные и их элементы управления — кнопки, флажки и прочее, часто создаются как окна, принадлежащие Windows. То есть первоначально, в момент их создания, не существует объектов приложения, соответствующих этим окнам. Для этого в библиотеку вводятся средства создания объектов по хендлу. Эти средства могут несколько различаться в разных библиотеках.

Например метод CWnd* CWnd::FromHandle( HWND ), существующий в MFC, создает специальный объект, описывающий окно, связывает его с указанным хендлом и возвращает указатель на него. Этот объект считается “временным” — спустя некоторое время MFC сама его уничтожит. В OWL аналогичного эффекта можно добиться используя специальную форму конструктора объекта TWindow.

Случай 3 существенно более сложный. Когда окно получает сообщение, Windows вызывает зарегистрированную оконную процедуру, причем для этой процедуры передается только хендл окна и параметры сообщения. Указатель на объект приложения, описывающего это окно, остается в момент получения сообщения неизвестным!

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

· находит связанное с данным хендлом окно — для этого библиотеки классов поддерживают специальные таблицы соответствия хендлов окон описаниям этих окон в приложении

· распределяет пришедшее сообщение соответствующему (обычно виртуальному) методу–обработчику этого сообщения.

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

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

Это накладывает ограничения на применение методов–обработчиков сообщений — для окон, не созданных как объекты класса, эти методы вызываться не будут. В случае MFC названия таких методов обычно начинаются на On..., например OnDestroy; а в случае OWL — на Ev..., например EvDestroy. Часто можно так организовать приложение, что переопределять эти методы просто не потребуется, однако это не всегда удобно и возможно.

При необходимости как–либо изменить реакцию окна на внешние события (переопределить принятую обработку сообщений) надо, во–первых, создать соответствующий объект класса (как в случае 2). Во–вторых обычное окно, создаваемое Windows (например, какой–либо элемент управления диалогом — кнопка, флажок и пр.) или другим приложением, использует собственную оконную процедуру. Эта процедура, естественно, никак не связана с библиотекой ООП, применяемой вашим приложением. Таким образом, при получении окном сообщений, вызывается только лишь его собственная оконная процедура, не обращающаяся к методам класса. То есть необходимо осуществить подмену оконной процедуры (в Windows это называется порождением подкласса окон — subclass) с помощью специальных методов библиотек, выполняющих эту операцию: SubclassWindowFunction в OWL или SubclassWindow в MFC. После этого новая оконная функция будет обращаться к методам класса для обработки сообщений, а в качестве стандартной обработки будет использоваться та оконная функция, которая использовалась окном до ее подмены.