Применение классов С++ совместно с WinApi при программировании для Windows (исходники)Источник: ADSoft Андрей Куковинец
Операционная система Windows является объектно-ориентированной. Все элементы управления (объекты) являются окнами в том или ином виде. Каждый такой элемент имеет свои параметры состояния, входные и выходные сообщения. Традиционно при написании программ с использованием чистого WinApi применяются методы структурного программирования. Эта статья покажет как можно использовать совместно WinApi и классы С++ на примере написания собственной библиотеки классов. В данной статье для проверки и компиляции написанного кода будет использоваться компилятор MS Visual C++6. #include <windows.h> Как видно из листинга, минимальные требования для работы программы следующие: if (!MyRegisterClass (hInstance)) return FALSE; if (!InitInstance (hInstance, nCmdShow)) return FALSE; Первая из этих функций регистрирует оконные классы, вторая создает само окно. LPSTR m_lpCmdLine; int m_nCmdShow; Так же нам потребуются функции доступа к этим переменным. Для этого введем в наш класс функции которые будут давать возможность обращаться к этим переменным: inline LPSTR GetCmdLine() const { return m_lpCmdLine; } inline int GetCmdShow() const { return m_nCmdShow; } inline HINSTANCE GetInstanceHandle() const { return m_hInstance; } В функциюрегистрирующую все оконные классы,мы будем передавать все параметры из функции WinMain. BOOL TApplication::Initialize( HINSTANCE hInstance, LPSTR lpCmdLine, int nCmdShow) { HICON m_hIcon, m_hIconSmall; m_lpCmdLine = lpCmdLine; m_nCmdShow = nCmdShow; m_hInstance = hInstance; m_hIcon = m_hIconSmall = ::LoadIcon( NULL, IDI_APPLICATION ); WNDCLASSEX wClass; wClass.cbSize = sizeof( wClass ); wClass.cbClsExtra = 0; wClass.cbWndExtra = 0; wClass.lpszMenuName = NULL; wClass.hbrBackground= ::GetSysColorBrush( COLOR_WINDOW ); wClass.hCursor = ::LoadCursor( NULL, IDC_ARROW ); wClass.hIcon = wClass.hIconSm = m_hIcon; wClass.hInstance = hInstance; wClass.lpfnWndProc = TWindow::StaticWindowProc; wClass.lpszClassName= MAINWINDOWCLASS; wClass.style = CS_HREDRAW / CS_VREDRAW / CS_DBLCLKS; if ( ! RegisterClassEx( &wClass )) return FALSE; return TRUE; } Разберем поподробнее что делает эта функция. //-------Предотвращение многократного включения заголовочного файла--------- #ifndef GLOBALS_H #define GLOBALS_H //------------------------------------------------------------------------- #include <windows.h> #include <tchar.h> #define MAINWINDOWCLASS "TControl" //------------------------------------------------------------------------- // DELETE_NULL удаляет объект и устанавливает указатель на NULL // Не завершайте конец этого макроса ";" #define DELETE_NULL(ptr) { delete ptr; ptr = NULL; } //Голбальная переменная приложения. //Каждое новое приложение должно начинаться со строчки //Application=new TApplication(); если этого не сделать то переменная будет //указывать на NULL и приложение сразу некорректно завершится. class TApplication; extern TApplication* Application; //------------------------------------------------------------------------- #endif Если регистрация класса прошла успешно, функция возвращает TRUE иначе FALSE. wClass.lpfnWndProc = TWindow::StaticWindowProc; StaticWindowProc-это основная функция, посредством которой, класс TWindow будет взаимодействовать с системой, а система с классом. Эта функция должна быть обязательно объявлена как CALLBACK и быть статической. Иначе программа не будет работать. Далее в функции WinMain идет функция создания окна. InitInstance (hInstance, nCmdShow)) Её мы пока пропустим и перейдем на пару строк ниже. Если программа добралась до этих строк, это означает, что оконный класс был успешно зарегистрирован и окно создано. После регистрации класса и создания окна программа входит в цикл обработки сообщений. int TApplication::Run() { // обрабатываемое сообщение MSG msg; // получаем сообщения while ( GetMessage( &msg, NULL, 0, 0 ) > 0 ) { //елси сообщение не обработано if ( msg.hwnd == NULL) continue; //транслируем сообщение ::TranslateMessage( &msg ); //отсылаем сообщения процедурам окон ::DispatchMessage( &msg ); } //возвращаем код завершения приложения return ( LRESULT )msg.wParam; } Эта функция делает все то же самое, что и обычная программа без классов. Думаю комментарии здесь излишни. Полный листинг класса TApplication: Заголовочный файл //-------Предотвращение многократного включения заголовочного файла--------- #ifndef TAPPLICATION_H #define TAPPLICATION_H //-------------------------------------------------------------------------- #include "Globals.h" //-------------------------------------------------------------------------- class TApplication { public: int Run(); TApplication(); ~TApplication(); BOOL Initialize( HINSTANCE hInstance, LPSTR lpCmdLine, int nCmdShow); inline LPSTR GetCmdLine() const { return m_lpCmdLine; } inline int GetCmdShow() const { return m_nCmdShow; } inline HINSTANCE GetInstanceHandle() const { return m_hInstance; } protected: HINSTANCE m_hInstance; LPSTR m_lpCmdLine; int m_nCmdShow; }; //-------------------------------------------------------------------------- #endif Файл реализации #include "TApplication.h" #include "TWindow.h" // Глобальный указатель на объект TApplication // Существует в единственном виде TApplication *Application = NULL; /*============================================================================== Функция: TApplication() Параметры: Нет Возврат: Нет Назначение: Конструктор по умолчанию Примечания: ==============================================================================*/ TApplication::TApplication() { //Обнуляемвнутренние члены m_hInstance = NULL; } /*============================================================================== Функция: ~TApplication() Параметры: Нет Возврат: Нет Назначение: Виртуальный деструктор. Примечания: ==============================================================================*/ TApplication::~TApplication() { } /*============================================================================== Функция: BOOL Initialize(HINSTANCE hInstance,LPSTR lpCmdLine,int nCmdShow) Параметры: hInstance-дескриптор образца приложения lpCmdLine-командная строка приложения nCmdShow-параметр отображения приложения Возврат: TRUE приложение инициализированно нормально. FALSE произошла ошибка Назначение: Инициализировать внутренние члены приложения Примечания: ==============================================================================*/ BOOL TApplication::Initialize( HINSTANCE hInstance, LPSTR lpCmdLine, int nCmdShow) { //Заполняем внутренние данные HICON m_hIcon, m_hIconSmall; m_lpCmdLine = lpCmdLine; m_nCmdShow = nCmdShow; m_hInstance = hInstance; //Загружаем иконку из системы m_hIcon = m_hIconSmall = ::LoadIcon( NULL, IDI_APPLICATION ); //регистрируем класс главного окона WNDCLASSEX wClass; wClass.cbSize = sizeof( wClass ); wClass.cbClsExtra = 0; wClass.cbWndExtra = 0; wClass.lpszMenuName = NULL; wClass.hbrBackground = ::GetSysColorBrush( COLOR_WINDOW ); wClass.hCursor = ::LoadCursor( NULL, IDC_ARROW ); wClass.hIcon = wClass.hIconSm = m_hIcon; wClass.hInstance = hInstance; wClass.lpfnWndProc = TWindow::StaticWindowProc; wClass.lpszClassName = MAINWINDOWCLASS; wClass.style = CS_HREDRAW / CS_VREDRAW / CS_DBLCLKS; //если класс окна не зарегистрирова выходим if ( ! RegisterClassEx( &wClass )) return FALSE; return TRUE; } /*============================================================================== Функция: int Run() Параметры: Нет Возврат: Код завершения приложения Назначение: Запустить цикл обработки сообщений и вернуть код завершения приложения Примечания: ==============================================================================*/ int TApplication::Run() { // обрабатываемое сообщение MSG msg; // получаем сообщения while ( GetMessage( &msg, NULL, 0, 0 ) > 0 ) { //елси сообщение не обработано if ( msg.hwnd == NULL) continue; //транслируем сообщение ::TranslateMessage( &msg ); //отсылаем сообщения процедурам окон ::DispatchMessage( &msg ); } //возвращаем код завершения приложения return ( LRESULT )msg.wParam; } С классом, отвечающим за инициализацию программы, регистрацию оконных классов и запуск цикла обработки сообщений разобрались. Займемся реализацией класса отвечающего за создание окна и обработку сообщений. //-------Предотвращение многократного включения заголовочного файла--------- #ifndef TOBJECT_H #define TOBJECT_H //--------------------------------------------------------------------------- #include "Globals.h" //--------------------------------------------------------------------------- // TObject это базовый класс для всех объектови является частью связанного // списка.Просто определяем тривиальный класс который имеет указатель на // следующий элемент тогоже самого или производного типа и виртуальный деструктор class TObject { public: // конструктор по умолчанию TObject() { m_pNextItem = NULL; m_pPrevItem=NULL; } // виртуальный деструктор.ничего не делает virtual ~TObject() {;} // Это следующий элемент в связанном списке.Предназначен для того чтобы избежать // дополнительных затрат на функции которые можно вызвать из базового класса. TObject* m_pNextItem; TObject* m_pPrevItem; }; //--------------------------------------------------------------------------- #endif TObjectManager.h //-------Предотвращение многократного включения заголовочного файла--------- #ifndef TOBJECTMANAGER_H #define TOBJECTMANAGER_H //-------------------------------------------------------------------------- #include "Globals.h" #include "TObject.h" //-------------------------------------------------------------------------- class TObjectManager { public: virtual void Free(); virtual TObject * Find(TObject* pItem); virtual void Empty(); virtual BOOL Delete(TObject *pItem,BOOL bDeleteItem =TRUE); virtual void Add(TObject* pItem,BOOL bAddAtBeginningOfList =FALSE); TObjectManager(); virtual ~TObjectManager(); // Возвращаем число элементов в списке или объектов помещенных в список UINT GetCount() { return m_nItemCount; } // Указатели на первый и последний объект в списке TObject* m_pFirstItem; TObject* m_pLastItem; // Число элементов в списке UINT m_nItemCount; }; //-------------------------------------------------------------------------- // Это частный TObjectManager который никогда не удаляет объекты из листа class TWindowList : public TObjectManager { public: // Конструктор по умолчанию . Ничего не делает TWindowList() {;} // Очищаем лист но не удаляем объекты. Пусть они сами себя уничтожают :). ~TWindowList() { Empty(); } }; //-------------------------------------------------------------------------- #endif TObjectManager.cpp //-------------------------------------------------------------------------- #include "TObjectManager.h" /*--------------------------------------------------------------------------- Функция: TObjectManager Параметры: Нет Возврат: Нет Назначение: Конструктор по умолчанию.Инициализирует внутренние члены Примечания: //-------------------------------------------------------------------------*/ TObjectManager::TObjectManager() { Empty(); } /*--------------------------------------------------------------------------- Функция: ~TObjectManager Параметры: Нет Возврат: Нет Назначение: Виртуальный деструктор. Вызывает Free() для удаления объектов содержащихся в связанном списке Примечания: //-------------------------------------------------------------------------*/ TObjectManager::~TObjectManager() { Free(); } /*--------------------------------------------------------------------------- Функция: Add Параметры: pItem-Указатель на объект котрый следует добавить в список bAddAtBeginningOfList Возврат: Нет Назначение: Добавляет элемент в конец списка если только bAddAtBeginningOfList равен TRUE Примечания: //-------------------------------------------------------------------------*/ void TObjectManager::Add(TObject *pItem, BOOL bAddAtBeginningOfList) { if ( !pItem ) return; if ( m_pFirstItem ) { if ( bAddAtBeginningOfList ) { pItem->m_pNextItem = m_pFirstItem; m_pFirstItem = pItem; } else { m_pLastItem->m_pNextItem = pItem; m_pLastItem = pItem; } } else m_pFirstItem = m_pLastItem = pItem; // Увеличиваем счетчик m_nItemCount++; } /*--------------------------------------------------------------------------- Функция: Delete Параметры: pItem-Указатель на объект который следует удалить из связанного списка bDeleteItem-удалить только из списка или и объект также Возврат: Удален ли объект Назначение: Удаляет pItem из связанного списка. Если bDeleteItem =TRUE будет удален и pItem Примечания: //-------------------------------------------------------------------------*/ BOOL TObjectManager::Delete(TObject *pItem, BOOL bDeleteItem) { if ( !pItem ) return FALSE; // Ищем предыдущий элемент в списке TObject* pThisItem = m_pFirstItem; TObject* pPreviousItem = NULL; BOOL bResult = FALSE; while ( pThisItem ) { if ( pThisItem == pItem ) { bResult = TRUE; // Это первый элемент ? if ( pItem == m_pFirstItem ) m_pFirstItem = pItem->m_pNextItem; // Это последний элемент if ( pItem == m_pLastItem ) m_pLastItem = pPreviousItem; // Назначаем предыдущий узел указывать на следующий if ( pPreviousItem ) pPreviousItem->m_pNextItem = pItem->m_pNextItem; // Уменьшаем счетчик m_nItemCount--; //Закончили break; } else { // Сохраняем предыдущий элемент и берм следующий элемент для цикла pPreviousItem = pThisItem; pThisItem = pThisItem->m_pNextItem; } } // Удаляем pItem ? if ( bDeleteItem ) DELETE_NULL(pItem) return bResult; } /*--------------------------------------------------------------------------- Функция: Empty Параметры: Нет Возврат: Нет Назначение: Опустошаем содержание связанного списка не удаляя сами объекты Примечания: //-------------------------------------------------------------------------*/ void TObjectManager::Empty() { m_pFirstItem = m_pLastItem = NULL; m_nItemCount = 0; } /*--------------------------------------------------------------------------- Функция: Find Параметры: pItem Возврат: TObject Назначение: Ищем элемент в списке и возвращаем его или возвращаем NULL если объект не найден Примечания: //-------------------------------------------------------------------------*/ TObject * TObjectManager::Find(TObject *pItem) { if ( pItem ) { TObject* pThisItem = m_pFirstItem; while ( pThisItem ) { if ( pThisItem == pItem ) return pThisItem; pThisItem = pThisItem->m_pNextItem; } } return NULL; } /*--------------------------------------------------------------------------- Функция: Free Параметры: Нет Возврат: Нет Назначение: Освобождаем все элементы в списке. Функция также удаляет сами объекты в списке. Примечания: Если требуется только сбросить содержимое стоит вызывать Empty() //-------------------------------------------------------------------------*/ void TObjectManager::Free() { TObject* pItem = m_pFirstItem; TObject* pNextItem = NULL; while ( pItem ) { pNextItem = pItem->m_pNextItem; DELETE_NULL(pItem) pItem = pNextItem; } Empty(); } Разберемся подробнее в вышеприведенном листинге. Мы также добавили класс TWindowList. Он предназначен для того, чтобы хранить указатели на оконные объекты. Мы не должны использовать TObjectManager для оконных объектов по следующим причинам: При разрушении TObjectManager вызывает функцию Free() которая удаляет не только узлы списка но и сами объекты. В отличии от TWindowList, который вызывает функцию Empty() не разрушающую объекты а только очищающую список от узлов. Класс TWindow Этот класс самый сложный из всех классов описанных выше. HWND TWindow::GetHandle() { if ( m_hWnd && ::IsWindow( m_hWnd )) return m_hWnd; return NULL; } Что она делает ? Если объект имеет дескриптор окна и окно с этим дескриптором существует, то мы возвращаем этот дескриптор, в противном случае, возвращаем нулевое значение. При вызове функции StaticWindowProc мы будем искать это окно в списке окон, и передавать сообщение именно ему. TWindow * TWindow::FindObjectByHandle(HWND hWnd) { //дескриптор существует? if ( hWnd ) { //вдруг наш объект находится первым в списке TWindow* pWindow = (TWindow*)GLOBAL_WINDOW_LIST.m_pFirstItem; // Нет. Ищем объект в голобальном списке по дескриптору while ( pWindow ) { // дескрипторы совпадают? if ( pWindow->m_hWnd == hWnd ) //Да. Возвращаем объект return pWindow; // проходим по всему списку в поисках объекта pWindow = (TWindow*)pWindow->m_pNextItem; } } //не нашли. Возвращаем NULL return NULL; } А что если существует окно со своим дескриптором, но не является классом TWindow или его наследником, а нам так хотелось бы им управлять, так же как и свои классом. Выход есть, напишем функцию,прикрепляющую дескриптор существующего окна к нашему классу. Функцию назовем Attach. В неё мы будем передавать дескриптор существующего окна и параметр который будет определять разрушать нам дескриптор окна при его уничтожении или нет , а возвращать она будет TRUE если дескриптор прикреплен к объекту или FALSE если нет.По умолчанию нам не требуется разрушать дескриптор. BOOL m_bDestroyHandle; Декларация функции Attach: BOOL m_bIsMDIFrame; BOOL m_bIsDialog; Реализуем функцию определяющую является ли наш объект диалогом: inline BOOL IsDialog() { return m_bIsDialog; } Нам потребуется ещё одна переменная в которой мы будем сохранять адрес старой оконной процедуры WNDPROC m_lpfnOldWndProc; BOOL TWindow::Attach( HWND hWnd, BOOL bDestroy /* = FALSE */ ) { //Должны разрушать окно if ( bDestroy ) { //Ищем объект по дескриптору if ( FindObjectByHandle(hWnd ) ) { //Нашли. Не присоединяем дескриптор.Он уже присоединен return FALSE; } } //буфер для имени класса TCHAR szClassName[ 10 ]; //получаем имя класса if ( ::GetClassName( hWnd, szClassName, 10 )) { //если #32770 это имя класса то это окно окно диалога //#32770 системная переменная определяющая диалог m_bIsDialog = ( BOOL )( _tcscmp( _T( "#32770" ), szClassName ) == 0 ); //приравниваем дескриптор окна к переденному дескриптору m_hWnd = hWnd; //флаг разрушения окна равен переданному m_bDestroyHandle = bDestroy; //назначаем оконную процедуру m_lpfnOldWndProc = ( WNDPROC )::GetWindowLong( hWnd, m_bIsDialog ? DWL_DLGPROC : GWL_WNDPROC ); //если оконная процедура не равна статической процедуре окна if ( m_lpfnOldWndProc && m_lpfnOldWndProc != ( WNDPROC )TWindow::StaticWindowProc ) { //назначаем процедуру окну ::SetWindowLong( hWnd, m_bIsDialog ? DWL_DLGPROC : GWL_WNDPROC, ( LONG )TWindow::StaticWindowProc ); } // присоединили. Возвращаем TRUE return TRUE; } //не получено имя класса //обнуляемс процедуру окна m_lpfnOldWndProc = NULL; //обнуляем дескриптор окна m_hWnd = NULL; //Не присоединили дескриптор. возвращаем FALSE return FALSE; } Принцип работы этой функции таков: HWND TWindow::Detach() { //является ли оконная процедура статической процедурой if ( m_lpfnOldWndProc && m_lpfnOldWndProc != ( WNDPROC )TWindow::StaticWindowProc ) { //устанавливаем новую процедуру окна ::SetWindowLong( m_hWnd, m_bIsDialog ? DWL_DLGPROC : GWL_WNDPROC, ( LONG )m_lpfnOldWndProc ); //приравниваем дескриптор к временному HWND hWnd = m_hWnd; //мы не диалог m_bIsDialog = FALSE; //обнуляем дескриптор окна m_hWnd = NULL; //удаляем оконную процедуру m_lpfnOldWndProc = NULL; //возвращаем дескриптор return hWnd; } //нечего отсоединять return NULL; } Функция Detach отсоединяет дескриптор окна от нашего класса. В этой функции мы сначала определяем существует ли старая процедура и не является ли она нашей текущей статической процедурой. Если так оно и есть, то устанавливаем дескриптору окна старую оконную процедуру. Создаем временный дескриптор окна, приравнивая его к текущему. BOOL TWindow::Create(DWORD dwExStyle, LPCTSTR lpszClassName, LPCTSTR lpszWindowName, Все параметры взяты из стандартной функции WinApi CreateWindowEx(). BOOL TWindow::PreCreateWindow(LPCREATESTRUCT pCS) { if ( pCS->lpszClass == NULL ) pCS->lpszClass = MAINWINDOWCLASS; return TRUE; } Эта функция виртуальная и в наследуемых классах её можно будет переопределить. Например её можно будет использовать при создании своего класса, не наследуемого от класса TWindow. После вызова этой функции пытаемся создать окно на основе заполненной выше структуры. LRESULT CALLBACK TWindow::StaticWindowProc(HWND hWnd,UINT uMsg,WPARAM wParam,LPARAM lParam ) { //Это нам пригодится для обработки сообщений TWindow *pWindow = NULL; //Окно начало создаваться //Нам надо прикрепить дескриптор окна к этому объекту? if ( uMsg == WM_NCCREATE ) { //Указатель на объект этого окна должен быть передан используя поле //lParam структуры CREATESTRUCT. pWindow = ( TWindow * )(( LPCREATESTRUCT )lParam )->lpCreateParams; //прикрепляем к объекту if ( pWindow ) pWindow->Attach( hWnd, TRUE ); } else if ( uMsg == WM_INITDIALOG ) { // Сперва проверяем является ли lParam объектом в нашем списке окон pWindow = (TWindow*)GLOBAL_WINDOW_LIST.Find((TObject*)lParam) ; //Если объект не был найден в списке, он должен быть указателем в поле //PROPSHEETPAGE.В этом случае объект должен быть в поле lParam этой //структуры. if ( pWindow == NULL ) pWindow = ( TWindow * )(( PROPSHEETPAGE * )lParam )->lParam; //прикрепляем к объекту pWindow->Attach( hWnd, TRUE ); } else { //Если мы достигли этого места то дескриптор уже прикреплен к объекту pWindow = FindObjectByHandle( hWnd ); } // Мы имеем действительный указатель if ( pWindow != NULL && pWindow->GetHandle() ) { //Предварительно устанавливаем результат обработки сообщения LRESULT lResult = -1; //получаем тип сообщения switch ( uMsg ) { case WM_CREATE: //вызываем обработчик сообщения lResult = pWindow->OnCreate(( LPCREATESTRUCT )lParam ); break; case WM_CLOSE: //вызываем обработчик сообщения lResult = pWindow->OnClose(); break; case WM_DESTROY: //вызываем обработчик сообщения lResult = pWindow->OnDestroy(); break; case WM_COMMAND: { //вызываем обработчик сообщения lResult = pWindow->OnCommand(( UINT )HIWORD( wParam ), ( UINT )LOWORD( wParam ), ( HWND )lParam ); break; } } //Если сообщение не было обработано (lResult=-1) и дескриптор окна все //ещё является действительным мы вызываем процедуру окна if ((( lResult == -1 && uMsg != WM_CREATE ) // ( lResult == -2 && uMsg == WM_CREATE )) && pWindow->GetHandle()) lResult = pWindow->WindowProc( uMsg, wParam, lParam ); //Мы начинаем разрушать окно if ( uMsg == WM_NCDESTROY ) { //Дескриптор действителен if ( pWindow->GetHandle()) { ///Отсоединяем дескриптор pWindow->Detach(); //удаляем дескриптор pWindow->m_hWnd = NULL; //удаляем оконную процедуру pWindow->m_lpfnOldWndProc = NULL; } } //возвращаем результат обработки сообщения return lResult; } //Мы не смогли найти дескриптор в нашем глобальном списке окон. Это может //означать что дескриптор не был прикреплен к объекту //в этом случае мы просто оставляем это сообщение оконному дескриптору TCHAR szClassName[ 10 ]; //получаем имя класса окна if ( ::GetClassName( hWnd, szClassName, 10 )) { //это диалоговое окно? Если да то возвращаем 0 и не передаем сообщение //процедуре окна if ( _tcscmp( _T( "#32770" ), szClassName ) == 0 ) return 0; } //передаем необработанное сообщение процедуре окна по умолчанию и возвращаем //результат его обработки return ::DefWindowProc( hWnd, uMsg, wParam, lParam ); } Эта функция является самой важной. Если вы не поймете как работает эта функция вы не поймете как работает библиотека. А работает эта функция довольно просто. pWindow = ( TWindow * )((LPCREATESTRUCT)lParam)->lpCreateParams; Приводя к типу TWindow переданный параметр, мы приравниваем ранее созданный объект к нашему временному объекту. if ( pWindow)pWindow->Attach( hWnd, TRUE ); Но наше окно может быть и диалогом. В этом случае вместо обработки сообщения WM_NCCREATE нам следует обрабатывать сообщение WM_INITDIALOG. Это сообщение посылается системой, до того как диалог будет отображен. Если бы мы помимо классов окон мы могли создавать ещё и классы диалогов, то при обработке этого сообщения логично было бы вызвать функцию, которая настроила бы диалог до его отображения на экране. (об этом в следующих статьях) pWindow = ( TWindow * )(( PROPSHEETPAGE *)lParam)->lParam; При получении указателя требуется прикрепить переданный дескриптор к нашему объекту. Но до этого стоит проверить, нет ли такого объекта в нашем списке окон. pWindow = (TWindow*)GLOBAL_WINDOW_LIST.Find((TObject*)lParam); Если нет, прикрепляем дескриптор к объекту: pWindow->Attach( hWnd, TRUE ); Система вызывает эту функцию не только при создании окна, но и при отправке сообщения. По этому, если сообщения WM_NCCREATE и WM_INITDIALOG не обработаны, мы просто ищем объект в нашем списке окон по переданному дескриптору. pWindow = FindObjectByHandle( hWnd ); Далее мы проверяем существует ли объект или мы так и не получили указатель на него if ( pWindow != NULL&&pWindow->GetHandle() ) Если существует начинаем обрабатывать переданные сообщения. Обработка сообщений. Так как класс TWindow является базовым классом, то все функции которые мы будем обрабатывать должны быть виртуальными. Это требуется для того, что бы классы наследники могли их переопределить и обработать по-своему. Функции базового класса, при обработке сообщений возвращают значение -1. Это говорит системе что сообщение не обработано. Наследники при обработке этих сообщений должны возвращать значение большее или равное нулю. Попрошу обратить внимание, что при обработке сообщения WM_CREATE функция базового класса возвращает не -1 а -2. Так как если мы вернем -1 система расценит этот возврат как неудачу при создании окна и завершит работу программы. LRESULT TWindow::OnCreate( LPCREATESTRUCT /*pCS*/ ) { return -2; } В эту функцию передается структура создания окна. После обработки сообщений мы смотрим на код возврата функции. Если сообщение не было обработано (lResult=-1) и дескриптор окна все ещё является действительным, мы вызываем процедуру окна по умолчанию. lResult = pWindow->WindowProc( uMsg, wParam, lParam ); Если обрабатываемое сообщение WM_NCDESTROY. То мы разрушаем окно, отсоединяем дескриптор от объекта и удаляем оконную процедуру. Если сообщение не обработано ни в статической функции ни в функции по умолчанию мы передаем необработанное сообщение обратно в систему. return ::DefWindowProc( hWnd, uMsg, wParam, lParam ); Листинг класса TWindow TWindow.h // TWindow.h: interface for the TWindow class. // ////////////////////////////////////////////////////////////////////// #if !defined(AFX_TWINDOW_H__9CF24F11_A481_48F8_8AD5_CA7D9DCC1B83__INCLUDED_) #define AFX_TWINDOW_H__9CF24F11_A481_48F8_8AD5_CA7D9DCC1B83__INCLUDED_ #if _MSC_VER > 1000 #pragma once #endif // _MSC_VER > 1000 #include "TObject.h" class TWindow : public TObject { public: static TWindow * FindObjectByHandle(HWND hWnd ); BOOL Create( DWORD dwExStyle, LPCTSTR lpszClassName, LPCTSTR lpszWindowName, DWORD dwStyle, TWindow.cpp // TWindow.cpp: implementation of the TWindow class. // ////////////////////////////////////////////////////////////////////// #include "TApplication.h" #include "TWindow.h" ////////////////////////////////////////////////////////////////////// // Construction/Destruction ////////////////////////////////////////////////////////////////////// #include "TObjectManager.h" TWindowList GLOBAL_WINDOW_LIST; TWindow::TWindow() { m_bIsDialog = FALSE; m_hWnd = NULL; m_lpfnOldWndProc = NULL; m_bDestroyHandle = TRUE; GLOBAL_WINDOW_LIST.Add( this , FALSE); } TWindow::~TWindow() { Destroy(); GLOBAL_WINDOW_LIST.Delete( this, FALSE ); return; } /*============================================================================== Функция: LRESULT CALLBACK StaticWindowProc(HWND hWnd,UINT uMsg,WPARAM wParam, LPARAM lParam ) Параметры: hWnd-окно пославшее сообщение uMsg-обрабатываемое сообщение wParam-первый параметр сообщения lParam-второй параметр сообщения Возврат: результат обработки сообщения Назначение: Оконная процедура окна.Обрабатывает поступающие сообщения ==============================================================================*/ LRESULT CALLBACK TWindow::StaticWindowProc(HWND hWnd,UINT uMsg,WPARAM wParam, LPARAM lParam ) { //Это нам пригодится для обработки сообщений TWindow *pWindow = NULL; //Окно начало создаваться //Нам надо прикрепить дескриптор окна к этому объекту? if ( uMsg == WM_NCCREATE ) { //Указатель на объект этого окна должен быть передан используя поле //lParam структуры CREATESTRUCT. pWindow = ( TWindow * )(( LPCREATESTRUCT )lParam )->lpCreateParams; //прикрепляем к объекту if ( pWindow ) pWindow->Attach( hWnd, TRUE ); } else if ( uMsg == WM_INITDIALOG ) { // Сперва проверяем является ли lParam объектом в нашем списке окон pWindow = (TWindow*)GLOBAL_WINDOW_LIST.Find((TObject*)lParam) ; //Если объект не был найден в списке, он должен быть указателем в поле //PROPSHEETPAGE.В этом случае объект должен быть в поле lParam этой //структуры. if ( pWindow == NULL ) pWindow = ( TWindow * )(( PROPSHEETPAGE * )lParam )->lParam; //прикрепляем к объекту pWindow->Attach( hWnd, TRUE ); } else { //Если мы достигли этого места то дескриптор уже прикреплен к объекту pWindow = FindObjectByHandle( hWnd ); } // Мы имеем действительный указатель if ( pWindow != NULL && pWindow->GetHandle() ) { //Предварительно устанавливаем результат обработки сообщения LRESULT lResult = -1; //получаем тип сообщения switch ( uMsg ) { case WM_CREATE: //вызываем обработчик сообщения lResult = pWindow->OnCreate(( LPCREATESTRUCT )lParam ); break; case WM_CLOSE: //вызываем обработчик сообщения lResult = pWindow->OnClose(); break; case WM_DESTROY: //вызываем обработчик сообщения lResult = pWindow->OnDestroy(); break; case WM_COMMAND: { //вызываем обработчик сообщения lResult = pWindow->OnCommand(( UINT )HIWORD( wParam ), ( UINT )LOWORD( wParam ), ( HWND )lParam ); break; } } //Если сообщение не было обработано (lResult=-1) и дескриптор окна все //ещё является действительным мы вызываем процедуру окна if ((( lResult == -1 && uMsg != WM_CREATE ) // ( lResult == -2 && uMsg == WM_CREATE )) && pWindow->GetHandle()) lResult = pWindow->WindowProc( uMsg, wParam, lParam ); //Мы начинаем разрушать окно if ( uMsg == WM_NCDESTROY ) { //Дескриптор действителен if ( pWindow->GetHandle()) { ///Отсоединяем дескриптор pWindow->Detach(); //удаляем дескриптор pWindow->m_hWnd = NULL; //удаляем оконную процедуру pWindow->m_lpfnOldWndProc = NULL; } } //возвращаем результат обработки сообщения return lResult; } //Мы не смогли найти дескриптор в нашем глобальном списке окон. Это может //означать что дескриптор не был прикреплен к объекту //в этом случае мы просто оставляем это сообщение оконному дескриптору TCHAR szClassName[ 10 ]; //получаем имя класса окна if ( ::GetClassName( hWnd, szClassName, 10 )) { //это диалоговое окно? Если да то возвращаем 0 и не передаем сообщение //процедуре окна if ( _tcscmp( _T( "#32770" ), szClassName ) == 0 ) return 0; } //передаем необработанное сообщение процедуре окна по умолчанию и возвращаем //результат его обработки return ::DefWindowProc( hWnd, uMsg, wParam, lParam ); } /*============================================================================== Функция: HWND GetHandle() Параметры: Нет Возврат: Получить дескриптор окна связанного с объектом Назначение: Возвращает дескриптор окна Примечания: ==============================================================================*/ HWND TWindow::GetHandle() { if ( m_hWnd && ::IsWindow( m_hWnd )) return m_hWnd; return NULL; } /*============================================================================== Функция: BOOL Attach( HWND hWnd, BOOL bDestroy = FALSE ) Параметры: hWnd-дескриптор окна bDestroy-должны ли разрушить окно при отсоединении дескриптора Возврат: TRUE - присоеденили дескриптор FALSE -не присоединили Назначение: Прикрепляем дескриптор окна к этому объекту. Это будет работать только в том случае если объект ещё не имеет дескриптора окна. Примечания: ==============================================================================*/ BOOL TWindow::Attach( HWND hWnd, BOOL bDestroy /* = FALSE */ ) { //Должны разрушать окно if ( bDestroy ) { //Ищем объект по дескриптору if ( FindObjectByHandle(hWnd ) ) { //Нашли. Не присоединяем дескриптор.Он уже присоединен return FALSE; } } //буфер для имени класса TCHAR szClassName[ 10 ]; //получаем имя класса if ( ::GetClassName( hWnd, szClassName, 10 )) { //если #32770 это имя класса то это окно окно диалога //#32770 системная переменная определяющая диалог m_bIsDialog = ( BOOL )( _tcscmp( _T( "#32770" ), szClassName ) == 0 ); //приравниваем дескриптор окна к переденному дескриптору m_hWnd = hWnd; //флаг разрушения окна равен переданному m_bDestroyHandle = bDestroy; //назначаем оконную процедуру m_lpfnOldWndProc = ( WNDPROC )::GetWindowLong( hWnd, m_bIsDialog ? DWL_DLGPROC : GWL_WNDPROC ); //если оконная процедура не равна статической процедуре окна if ( m_lpfnOldWndProc && m_lpfnOldWndProc != ( WNDPROC )TWindow::StaticWindowProc ) { //назначаем процедуру окну ::SetWindowLong( hWnd, m_bIsDialog ? DWL_DLGPROC : GWL_WNDPROC, ( LONG )TWindow::StaticWindowProc ); } // присоединили. Возвращаем TRUE return TRUE; } //не получено имя класса //обнуляемс процедуру окна m_lpfnOldWndProc = NULL; //обнуляем дескриптор окна m_hWnd = NULL; //Не присоединили дескриптор. возвращаем FALSE return FALSE; } /*============================================================================== Функция: HWND Detach() Параметры: Нет Возврат: Отсоединенный дескриптор Назначение: Отсоединяет дескриптор окна от объекта НЕ УДАЛЯЯ сам дескриптор. Так окно может дальше посылать сообщения, но не являться объектом ==============================================================================*/ HWND TWindow::Detach() { //является ли оконная процедура статической процедурой if ( m_lpfnOldWndProc && m_lpfnOldWndProc != ( WNDPROC )TWindow::StaticWindowProc ) { //устанавливаем новую процедуру окна ::SetWindowLong( m_hWnd, m_bIsDialog ? DWL_DLGPROC : GWL_WNDPROC, ( LONG )m_lpfnOldWndProc ); //приравниваем дескриптор к временному HWND hWnd = m_hWnd; //мы не диалог m_bIsDialog = FALSE; //обнуляем дескриптор окна m_hWnd = NULL; //удаляем оконную процедуру m_lpfnOldWndProc = NULL; //возвращаем дескриптор return hWnd; } //нечего отсоединять return NULL; } BOOL TWindow::Create(DWORD dwExStyle, LPCTSTR lpszClassName, LPCTSTR lpszWindowName, Тестирование библиотеки. Для того чтобы протестировать то что мы написали проделаем следующее. Создадим проект с одним единственным файлом. Пусть он будет называться TetsWindow.cpp #include <globals.h> #include <TApplication.h> #include <TWindow.h> class TTestWindow :public TWindow { protected: virtual LRESULT OnClose() { PostQuitMessage( 0 ); return 0; } }; int WINAPI WinMain( HINSTANCE hInstance, // handle to current instance HINSTANCE hPrevInstance, // handle to previous instance LPSTR lpCmdLine, // command line int nCmdShow // show state ) { int ret=0; Application= new TApplication; Application->Initialize(hInstance,lpCmdLine,nCmdShow); TTestWindow *Wnd= new TTestWindow; Wnd->Create( 0L,NULL,"Пример Окна",WS_OVERLAPPEDWINDOW/WS_VISIBLE, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL, NULL); ret= Application->Run(); delete Application; delete Wnd; return ret; } Ну и как?. Объем написания программы сократился в разы? Application=new TApplication; 2. Создаем объект окна. TTestWindow *Wnd= new TTestWindow; 3. Создаем само окно. Wnd->Create( 0L,NULL,"Пример окна",WS_OVERLAPPEDWINDOW/WS_VISIBLE, CW_USEDEFAULT, 4. Входим в цикл обработки сообщений Application->Run(); Всё. Библиотекой можно начинать пользоваться. В следующих статьях я расскажу о том, как подключать меню к приложению, добавление многодокументной функциональности и многое другое. |