p align="left">При необходимости как-либо изменить реакцию окна на внешние события (переопределить принятую обработку сообщений) надо, во-первых, создать соответствующий объект класса (как в случае 2). Во-вторых обычное окно, создаваемое Windows (например, какой-либо элемент управления диалогом -- кнопка, флажок и пр.) или другим приложением, использует собственную оконную процедуру. Эта процедура, естественно, никак не связана с библиотекой ООП, применяемой вашим приложением. Таким образом, при получении окном сообщений, вызывается только лишь его собственная оконная процедура, не обращающаяся к методам класса. То есть необходимо осуществить подмену оконной процедуры (в Windows это называется порождением подкласса окон -- subclass) с помощью специальных методов библиотек, выполняющих эту операцию: SubclassWindowFunction в OWL или SubclassWindow в MFC. После этого новая оконная функция будет обращаться к методам класса для обработки сообщений, а в качестве стандартной обработки будет использоваться та оконная функция, которая использовалась окном до ее подмены. Однако при использовании этого приема необходимо учитывать следующие нюансы: при создании объекта класса лучше использовать один из базовых классов (CWnd или TWindow), так как все порожденные от них классы переопределяют значительно большее число методов, предполагая стандартную обработку сообщений, реализованную в DefWindowProc, а не в той процедуре, которую вы подменили. Это может привести к конфликтам между новой обработкой событий и прежней оконной процедурой. Особенно опасна ошибка в назначении класса -- библиотека классов и компилятор никак не смогут проверить вас и предупредить, если вы, скажем, для кнопки, создадите объект класса “список” (LISTBOX). При такой ошибке конфликт практически неизбежен. В любом случае надо хорошо представлять себе, для какой стандартной оконной процедуры реализован какой класс библиотеки ООП и обработку каких сообщений он переопределяет, прежде чем решиться на подмену оконной процедуры. в случае Win32 для окон, созданных другим приложением, оконные процедуры (используемая окном и назначаемая вами) размещается в различных адресных пространствах разных процессов. Обращение из другого процесса по новому адресу функции приведет, скорее всего, к ошибке -- так как этот адрес задан в адресном пространстве вашего приложения, а что находится в адресном пространстве другого процесса по этому адресу вам неизвестно. Решить эту проблему можно, выделяя описание объекта класса и его процедуры в отдельную DLL, а затем внедряя ее в адресное пространство процесса, создавшего окно. Однако этот прием существенно сложнее. Пример 1C -- использование собственных классовВ этом примере используется несколько упрощенный метод реализации объектов. Главное ограничение -- невозможность назначения обработчиков сообщений для окон, не созданных в качестве объектов класса. В остальном этот вариант сохраняет все необходимые функции, причем делает это более компактным и быстрым способом. Такой способ часто применяется в приложениях-примерах, сопровождающих компиляторы.Коротко рассмотрим реализацию этого способа: вместо ведения таблиц соответствия хендлов объектам приложения можно хранить необходимые данные непосредственно в структуре описания окна в Windows (см. “Регистрация класса окон”). Так как доступ к этим данным осуществляется только с помощью функций, то размещать там все описание окна нецелесообразно, зато в этой структуре можно разместить указатель на связанный объект. Отсюда следует ограничение -- этот метод будет работать только с теми окнами, в структуре описания которых в Windows зарезервировано специальное поле для указателя. Это могут быть только окна, созданные нами.Рисунок 7. Поиск метода-обработчика сообщения в примере. Помимо этого используется еще один прием -- вместо таблиц функций-обработчиков сообщений для каждого класса окон формируется специальная виртуальная функция-диспетчер, которая осуществляет вызовы нужных методов. Если в случае MFC или OWL надо вести таблицы отклика, то в рассматриваемом примере надо разрабатывать соответствующую функцию. Кроме того, для упрощения в примере остались некоторые следы обычного программирования -- осталась, хотя и сильно измененная, функция WinMain, в которой создается объект “приложение”. Рассматриваемый пример состоит из 3х файлов: 1c.h -- общий заголовочный файл, содержащий описания базовых классов; 1c_cls.cpp -- методы и статические данные базовых классов; 1c_main.cpp -- собственно само приложение: описание собственных классов и их методов, а также функция WinMain. Файл 1c.h#define STRICT #include <windows.h> #define UNUSED_ARG(arg) (arg)=(arg) class Win0 { protected: HWND hwnd; virtual LRESULT dispatch( UINT, WPARAM, LPARAM ); virtual BOOL OnCreate( LPCREATESTRUCT ); virtual void OnDestroy( void ) = 0; virtual void OnPaint( HDC hdc ) = 0; public: Win0( void ); ~Win0( void ); BOOL create( char* ); void destroy( void ); void update( void ) { UpdateWindow( hwnd ); } void show( int nCmdShow ) { ShowWindow( hwnd, nCmdShow ); } friend LONG WINAPI _export Win0proc( HWND, UINT, WPARAM, LPARAM ); }; class App0 { public: static HINSTANCE hInstance; static HINSTANCE hPrevInstance; static LPSTR lpszCmdLine; static int nCmdShow; App0( HINSTANCE, HINSTANCE, LPSTR, int ); ~App0( void ); BOOL init( void ); int run( void ); void release( void ); }; Файл 1c_cls.cpp#include "1c.h" HINSTANCE App0::hInstance; HINSTANCE App0::hPrevInstance; LPSTR App0::lpszCmdLine; int App0::nCmdShow; static char szWndClass[]= "test window class"; static Win0* on_create_ptr; Win0::Win0( void ) { hwnd = NULL; } Win0::~Win0( void ) { destroy(); } LRESULT WINAPI _export Win0proc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { Win0* pwin; pwin = (Win0*)GetWindowLong( hWnd, 0 ); if ( !pwin ) { SetWindowLong( hWnd, 0, (LONG)(Win0 FAR*)(pwin = on_create_ptr) ); pwin->hwnd = hWnd; } return pwin->dispatch( uMsg, wParam, lParam ); } LRESULT Win0::dispatch( UINT uMsg, WPARAM wParam, LPARAM lParam ) { PAINTSTRUCT ps; switch ( uMsg ) { case WM_CREATE: return OnCreate( (LPCREATESTRUCT)lParam ) ? 0L : -1L; case WM_PAINT: OnPaint( BeginPaint( hwnd, &ps ) ); EndPaint( hwnd, &ps ); return 0L; case WM_DESTROY: OnDestroy(); return 0L; default: break; } return DefWindowProc( hwnd, uMsg, wParam, lParam ); } void Win0::destroy( void ) { if ( IsWindow( hwnd ) ) DestroyWindow( hwnd ); hwnd = (HWND)NULL; } BOOL Win0::create( char* title ) { on_create_ptr = this; CreateWindow( szWndClass, // class name title, // window name WS_OVERLAPPEDWINDOW, // window style CW_USEDEFAULT,CW_USEDEFAULT, // window position CW_USEDEFAULT,CW_USEDEFAULT, // window size NULL, // parent window NULL, // menu hInstance, // current instance NULL // user-defined parameters ); on_create_ptr = (Win0*)NULL; return IsWindow( hwnd ); } BOOL Win0::OnCreate( LPCREATESTRUCT lpCreateStruct ) { UNUSED_ARG( lpCreateStruct ); return TRUE; } App0::App0( HINSTANCE hInst, HINSTANCE hPrev, LPSTR lpszCmd, int nShow ) { hInstance = hInst; hPrevInstance = hPrev; lpszCmdLine = lpszCmd; nCmdShow = nShow; } App0::~App0( void ) { } BOOL App0::init( void ) { static BOOL done; WNDCLASS wc; if ( !done && !hPrevInstance ) { wc.style = 0; wc.lpfnWndProc = Win0proc; wc.cbClsExtra = 0; wc.cbWndExtra = sizeof(LONG); 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 = szWndClass; done = RegisterClass( &wc ) ? TRUE : FALSE; } return done; } int App0::run( void ) { MSG msg; while ( GetMessage( &msg, NULL, NULL, NULL ) ) { TranslateMessage( &msg ); DispatchMessage( &msg ); } return msg.wParam; } void App0::release( void ) { } Файл 1c_main.cpp#include "1c.h" class MainWindow : public Win0 { protected: virtual void OnDestroy( void ); virtual void OnPaint( HDC hdc ); public: MainWindow( void ); ~MainWindow( void ); }; class MyApp : public App0 { protected: MainWindow wnd; public: MyApp( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow ); ~MyApp( void ); BOOL init( void ); }; MainWindow::MainWindow( void ) : Win0() { } MainWindow::~MainWindow( void ) { } void MainWindow::OnDestroy( void ) { PostQuitMessage( 0 ); } void MainWindow::OnPaint( HDC hdc ) { TextOut( hdc, 0, 0, "Hello, world!", 13 ); } MyApp::MyApp( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow ) : App0( hInst, hPrevInst, lpszCmdLine, nCmdShow ) { } MyApp::~MyApp( void ) { } BOOL MyApp::init( void ) { if ( App0::init() ) { if ( wnd.create( "window header" ) ) { wnd.show( nCmdShow ); wnd.update(); return TRUE; } } return FALSE; } int PASCAL WinMain( HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpszCmdLine, int nCmdShow ) { int a; MyApp app( hInst, hPrevInst, lpszCmdLine, nCmdShow ); if ( app.init() ) { a = app.run(); } else a = -1; app.release(); return a; } Обзор примера 1CПример содержит два базовых класса: App0 -- описывает приложение и Win0 -- описывает окно.Класс App0 содержит 4 члена-данных: hInstance, hPrevInstance, lpszCmdLine и nCmdShow, которые являются аргументами функции WinMain. Интереснее разобраться с методами, описанными в этом классе. Конструктор просто инициализирует члены-данные для использования в последующем; деструктор вообще ничего не делает. Пара методов init и release предназначена для переопределения в дальнейшем -- метод init должен выполнять специфичную инициализацию приложения, а метод release -- операции при завершении. В классе App0 метод init осуществляет регистрацию оконной процедуры (в терминологии Windows -- класса), которая будет применяться данным приложением. Метод run выполняет цикл обработки сообщений. Класс Win0 содержит только один член-данные hwnd -- хендл окна. Конструктор устанавливает значение хендла окна равным NULL (окно не создано), деструктор проверяет существование окна и, при необходимости, закрывает его. Методы create, destroy, update и show соответствуют функциям API: CreateWindow, DestroyWindow, UpdateWindow и ShowWindow. Методы OnCreate, OnDestroy и OnPaint соответствуют обработчикам сообщений WM_CREATE, WM_DESTROY и WM_PAINT. Метод dispatch является диспетчером, который распределяет пришедшие сообщения по соответствующим методам-обработчикам.В том-же классе декларирована дружественная функция Win0proc, которая является собственно оконной процедурой.Коротко рассмотрим, как создается окно в этом примере. Для создания окна необходимо вызвать метод create, который, в свою очередь, вызовет функцию CreateWindow из Windows. Во время создания окна его оконная процедура начнет получать сообщения (в том числе и WM_CREATE, хотя, на самом деле, это будет не первое полученное сообщение). Эта процедура для нормальной работы требует, что бы в структуре описания окна в Windows был сохранен указатель на объект, описывающий окно в приложении. Но в момент первого вызова обработчика сообщений этот указатель там не находиться -- все происходит еще только во время работы функции CreateWindow. Соответственно мы используем некоторую статическую переменную (on_create_ptr), которая перед вызовом CreateWindow инициализируется указателем на объект. Тогда обработчик сообщений может быть построен по следующей схеме: LONG WINAPI _export Win0proc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) { Win0* pwin; pwin = (Win0*)GetWindowLong( hWnd, 0 ); // получаем указатель на объект if ( !pwin ) { // указатель равен NULL -- объект только создается // инициализируем объект и указатель на него SetWindowLong( hWnd, 0, (LONG)(Win0 FAR*)(pwin = on_create_ptr) ); pwin->hwnd = hWnd; } // вызываем виртуальную функцию-диспетчер return pwin->dispatch( uMsg, wParam, lParam ); } При нормальной работе первый вызов функции GetWindowLong вернет указатель на объект, так что следующий шаг -- вызов функции-диспетчера. Таким образом дополнительные затраты ресурсов на реализацию ООП таким способом оказываются минимальными. В случае разработки классов-наследников от Win0 надо разработать собственную функцию-диспетчер, которая будет вместо процедуры DefWindowProc вызывать диспетчер класса-предка.
Страницы: 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13
|