Применение классов С++ совместно с WinApi при программировании для Windows (исходники)

Источник: ADSoft
Андрей Куковинец

Операционная система Windows является объектно-ориентированной. Все элементы управления (объекты) являются окнами в том или ином виде. Каждый такой элемент имеет свои параметры состояния, входные и выходные сообщения. Традиционно при написании программ с использованием чистого WinApi применяются методы структурного программирования. Эта статья покажет как можно использовать совместно WinApi и классы С++ на примере написания собственной библиотеки классов. В данной статье для проверки и компиляции написанного кода будет использоваться компилятор MS Visual C++6.
Рассмотрим принцип работы простейшего приложения Windows

#include <windows.h>

char szTitle[]="Title";
char szWindowclass[]="Base";

BOOL MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);

int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                     LPSTR lpCmdLine, int nCmdShow)
{
    if (!MyRegisterClass (hInstance)) return FALSE;
    if (!InitInstance (hInstance, nCmdShow)) return FALSE;

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    {
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
    return msg.wParam;
}

BOOL MyRegisterClass(HINSTANCE hInstance)
{
    WNDCLASS wndclass;
    wndclass.style           = CS_HREDRAW / CS_VREDRAW;
    wndclass.lpfnWndProc     = (WNDPROC)WndProc;
    wndclass.cbClsExtra      = 0;
    wndclass.cbWndExtra      = 0;
    wndclass.hInstance       = hInstance;
    wndclass.hIcon           = LoadIcon(hInstance, IDI_APPLICATION);
    wndclass.hCursor         = LoadCursor(NULL, IDC_ARROW);
    wndclass.hbrBackground   = (HBRUSH)(COLOR_WINDOW+1);
    wndclass.lpszMenuName    = 0;
    wndclass.lpszClassName   = szWindowclass;
    return RegisterClass(&wndclass);
}

BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
HWND hWnd = CreateWindow(
            szWindowclass,
            szTitle,
            WS_OVERLAPPEDWINDOW,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            CW_USEDEFAULT,
            NULL,
            NULL,
            hInstance,
            NULL);
    if (!hWnd)
        return FALSE;
    ShowWindow(hWnd, nCmdShow);
    UpdateWindow(hWnd);
return TRUE;
}

LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    switch (message)
    {
    case WM_CREATE:
        break;
    case WM_DESTROY:
        PostQuitMessage(0);
        break;
    default:
        return DefWindowProc(hWnd, message, wParam, lParam);
   }
   return 0;
}


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

if (!MyRegisterClass (hInstance)) 
    return FALSE; 
if (!InitInstance (hInstance, nCmdShow)) 
    return FALSE;

Первая из этих функций регистрирует оконные классы, вторая создает само окно.
Возложим на плечи класса TApplication регистрацию оконных классов.
Регистрацией будет заниматься функция
BOOL Initialize( HINSTANCE hInstance, LPSTR lpCmdLine, int nCmdShow).
Для этого нам понадобится добавить в класс внутренний параметр HINSTANCE m_hInstance, он будет отвечать за дескриптор образца приложения. Но в функции WinMain есть ещё два других параметра.
Первый из них это командная строка приложения,второй - способ отображения окна на экране. Добавим эти параметры в наш класс.

	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;
}   

Разберем поподробнее что делает эта функция.
Переменные m_hIcon и m_hIconSmall нам пригодятся в дальнейшем, когда мы захотим чтобы наша программа имела иконку. А пока будем довольствоваться тем, что нам может предоставить система.
m_hIcon = m_hIconSmall = ::LoadIcon( NULL, IDI_APPLICATION );
Далее мы присваиваем всем внутренним переменным переданные значения.
 m_lpCmdLine = lpCmdLine; m_nCmdShow = nCmdShow; m_hInstance = hInstance;
После чего определяем структуру wClass содержащую информацию о оконном классе который мы будем регистрировать.
Заполняем эту структуру стандартными значениями и пытаемся зарегистрировать.
Здесь MAINWINDOWCLASS это строка определенная в файле Globals.h:

//-------Предотвращение многократного включения заголовочного файла---------
#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)) 

Её мы пока пропустим и перейдем на пару строк ниже. Если программа добралась до этих строк, это означает, что оконный класс был успешно зарегистрирован и окно создано. После регистрации класса и создания окна программа входит в цикл обработки сообщений.
Этим должен заниматься класс TApplication.
Для этого добавим в этот класс функцию 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;
}

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

Полный листинг класса 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;
}

С классом, отвечающим за инициализацию программы, регистрацию оконных классов и запуск цикла обработки сообщений разобрались. Займемся реализацией класса отвечающего за создание окна и обработку сообщений.
Для начала разберемся, что этот класс должен из себя представлять.
Как минимум он должен иметь процедуру обработки сообщений и функцию создания окна. Процедура, которая будет получать сообщения от системы и их обрабатывать, должна быть объявлена как static и CALLBACK. Модификатор CALLBACK указывает на то, что эта функция вызывается операционной системой, и является функцией обратного вызова.
Эта функция будет являться глобальной. То есть для всех классов унаследованных от TWindow эта функция будет одной и той же.
А как же быть с индивидуальными сообщениями, посылаемыми конкретному окну?
Для этого нам понадобятся еще пара вспомогательных классов и функций:
TObjectManager - связанный список,  в который мы будем заносить указатели на все созданные классы.
TObject -  который будет одновременно являться и узлом списка TObjectManager и базовым классом для всех объектов нашей библиотеки.
Что касается класса TWindow, являющегося базовым классом для всех оконных классов, то в него нам потребуется добавить несколько функций. Но о них позже. 
Полные листинги классов TObject и TObjectManager .
TObject.h

//-------Предотвращение многократного включения заголовочного файла---------
#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();
}

Разберемся подробнее в вышеприведенном листинге.
Что должен уметь TObjectManager?
* Добавлять объекты(окна в список).
* Удалять эти объекты из списка, имея возможность удалить не только элемент списка но и разрушить сам объект.
* Очищать список ото всех объектов.
* Осуществлять поиск объекта в списке.
* Очищать список, удаляя не только элементы списка, но и объекты также.
* Иметь внутренний счетчик объектов, увеличивая или уменьшая его при добавлении узла в список или удаляя узел из списка соответственно.
* Иметь конструктор и деструктор.
В общих чертах этот класс работает как обычный связанный список. В листинге, грубо говоря, приведен простейший связанный список, адаптированный для наших нужд.

Мы также добавили класс TWindowList. Он предназначен для того, чтобы хранить указатели на оконные объекты. Мы не должны использовать TObjectManager для оконных объектов по следующим причинам: При разрушении TObjectManager вызывает функцию Free() которая удаляет не только узлы списка но и сами объекты. В отличии от TWindowList, который вызывает функцию Empty() не разрушающую объекты а только очищающую список от узлов.

Класс TWindow

Этот класс самый сложный из всех классов описанных выше.
Разберемся в том, что должен уметь этот класс.
Представим себе, что пользователь постоянно создает все новые и новые окна. Все эти окна получают сообщения от системы посредством функции
LRESULT CALLBACK StaticWindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
Эта функция является общей для всех классов. Но нам то надо обработать события конкретного окна. Как и в обычном, структурном программировании, каждое окно имеет свою функцию обработки сообщений. Обычно она называется WindowProc. В нашем случае, каждое окно также должно иметь функцию обработки сообщений, помимо главной функции, общей для всех окон.Для этих целей добавим функцию
LRESULT WindowProc( UINT uMsg, WPARAM wParam, LPARAM lParam ).
Она будет заниматься обработкой сообщений, посланных конкретному окну. Но для того, чтобы определять какому окну конкретно адресовано сообщение, нам нужен список всех созданных окон. Этот список мы уже предусмотрели. Единственное чего не хватает, так это функции для получения дескриптора окна m_hWnd. Для доступа к дескриптору окна введем в оборот функцию GetHandle():

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 Attach(HWND hWnd,BOOL bDestroy = FALSE );
Также наш объект может являться диалогом или дочерним окном многодокументного приложения. Добавим и эти переменные.

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;
}

Принцип работы этой функции таков:
Оперделяем имя класса окна из переданного дескриптора.
Если имя класса выяснить не удалось, делаем выводы, что дескриптор липовый и ничего не прикрепляя, выходим из функции.
Если имя класса удалось выяснить,то определяем, не является ли переданный дескриптор дескриптором диалога.
В системе набор символов #32770 говорит о том, что этот класс - диалог.
Если является, присваиваем переменной m_bIsDialog статус, по которому будем в дальнейшем определять что наш объект - диалог.
Далее просто приравниваем дескриптор класса к переданному дескриптору и флаг разрушения к переданному флагу. После чего получаем указатель на старую оконную процедуру и сохраняем её в переменной m_lpfnOldWndProc.
Проделав эте операции выясняем, существует ли старая процедура и не является ли она нашей текущей статической процедурой. Если не существует и не является, то назначаем оконному дескриптору новую оконную процедуру, которая по совместительству и наша статическая процедура.
Выходим из функции, возвращая параметр TRUE который красноречиво говорит о том, что нам удалось прикрепить переданный дескриптор к нашему объекту.

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 отсоединяет дескриптор окна от нашего класса. В этой функции мы сначала определяем существует ли старая процедура и не является ли она нашей текущей статической процедурой. Если так оно и есть, то устанавливаем дескриптору окна старую оконную процедуру. Создаем временный дескриптор окна, приравнивая его к текущему.
Говорим, что наш объект больше не является диалогом.
Удаляем текущий дескриптор окна.
Удаляем старую оконную процедуру и возвращаем отсоединенный дескриптор.
Если же условие не выполнено, то возвращаем нулевой указатель.
Вот мы и добрались до места, где пора бы добавить функцию создания самого окна.
Назовём её Create();

BOOL TWindow::Create(DWORD dwExStyle, LPCTSTR lpszClassName, LPCTSTR lpszWindowName, 
DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hwndParent, HMENU nIDorHMenu) { //Если уже имеется дескриптор if ( GetHandle() ) //Возвращаем FALSE . Окно уже создано return FALSE; //Заполняем структуру создания окна переданными параметрами CREATESTRUCT cs; cs.dwExStyle = dwExStyle; cs.lpszClass = lpszClassName; cs.lpszName = lpszWindowName; cs.style = dwStyle; cs.x = x; cs.y = y; cs.cx = nWidth; cs.cy = nHeight; cs.hwndParent = hwndParent; cs.hMenu = nIDorHMenu; cs.lpCreateParams = 0L; //Предварительное создание окна. //Если ия класса не передано заполняет его именем класса по умолчанию if ( PreCreateWindow( &cs )) { // Создаем окно m_hWnd = ::CreateWindowEx( cs.dwExStyle, cs.lpszClass, cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy, cs.hwndParent, cs.hMenu, Application->GetInstanceHandle(), ( LPVOID )this ); //если создали окно if ( m_hWnd ) { //требуем разрушить окно при отсоединении дескриптора m_bDestroyHandle = TRUE; //заполняем старую процедуру окна m_lpfnOldWndProc = ( WNDPROC )::GetWindowLong( m_hWnd, m_bIsDialog ? DWL_DLGPROC : GWL_WNDPROC ); //если старая процедура не статическая процедура этого класса if ( m_lpfnOldWndProc && m_lpfnOldWndProc != ( WNDPROC )TWindow::StaticWindowProc ) { //устанавливаем процедуру окна if (::SetWindowLong(m_hWnd, m_bIsDialog?DWL_DLGPROC:GWL_WNDPROC, ( LONG )TWindow::StaticWindowProc )) //окно создано успешно. Возвращаем TRUE return TRUE; } //процедура уже назначена else //окно создано успешно. Возвращаем TRUE return TRUE; } //возвращаем TRUE если имеется рабочий дескриптор return ( BOOL )( m_hWnd ); } //Создание окна не произошло. или оно уже было создано ранее return FALSE; }

Все параметры взяты из стандартной функции WinApi CreateWindowEx().
В самом начале стоит проверить, не пытаемся ли мы создать окно, которое уже было ранее создано.
Если пытаемся, бросаем эту затею и выходим. Если не пытаемся, заполняем структуру создания окна CREATESTRUCT переданными парамтрами. Функция PreCreateWindow, ранее не описываемая, проверяет, передано имя класса создаваемого окна или нет. Если не передано, то заполняет имя значением по умолчанию взятым из файла Globals.h

BOOL TWindow::PreCreateWindow(LPCREATESTRUCT pCS)
{
    if ( pCS->lpszClass == NULL )
        pCS->lpszClass = MAINWINDOWCLASS;

    return TRUE;
}

Эта функция виртуальная и в наследуемых классах её можно будет переопределить. Например её можно будет использовать при создании своего класса, не наследуемого от класса TWindow.

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

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 );
    }

Эта функция является самой важной. Если вы не поймете как работает эта функция вы не поймете как работает библиотека.


А работает эта функция довольно просто.
В начале операции создания окна система посылает программе сообщение WM_NCCREATE, говорящее о том, что вскоре будет создана не клиентская часть окна. Во втором параметре сообщения передается указатель на структуру CREATESTRUCT.
Помните, мы при создании окна, в функции Create(), передали в дополнительный параметр указатель на наш объект.
Нам потребуется выделить его из переданной структуры и приравнять его к нашему временному объекту.

pWindow =    ( TWindow * )((LPCREATESTRUCT)lParam)->lpCreateParams;

Приводя к типу TWindow переданный параметр, мы приравниваем ранее созданный объект к нашему временному объекту.
После того, как мы получили указатель на наш объект, нам надо прикрепить к нему переданный дескриптор hWnd.

 if ( pWindow)pWindow->Attach( hWnd, TRUE );

Но наше окно может быть и диалогом. В этом случае вместо обработки сообщения WM_NCCREATE нам следует обрабатывать сообщение WM_INITDIALOG. Это сообщение посылается системой, до того как диалог будет отображен. Если бы мы помимо классов окон мы могли создавать ещё и классы диалогов, то при обработке этого сообщения логично было бы вызвать функцию, которая настроила бы диалог до его отображения на экране. (об этом в следующих статьях)
При отправке этого сообщения, во второй параметр записывается структура PROPSHEETPAGE, из которой мы можем получить указатель на наш объект.

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, 
int x, int y, int nWidth, int nHeight, HWND hwndParent, HMENU nIDorHMenu ); HWND GetHandle(); static LRESULT CALLBACK StaticWindowProc( HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam ); TWindow(); virtual ~TWindow(); inline BOOL IsDialog() { return m_bIsDialog; } protected: virtual LRESULT OnCommand( UINT nNotifyCode, UINT nCtrlID, HWND hWndCtrl ); virtual LRESULT OnDestroy(); virtual LRESULT OnClose(); virtual LRESULT OnCreate( LPCREATESTRUCT pCS ); virtual LRESULT WindowProc( UINT uMsg, WPARAM wParam, LPARAM lParam ); virtual void Destroy(); virtual BOOL PreCreateWindow( LPCREATESTRUCT pCS ); HWND Detach(); BOOL Attach(HWND hWnd,BOOL bDestroy = FALSE ); HWND m_hWnd; BOOL m_bIsMDIFrame; BOOL m_bIsDialog; BOOL m_bDestroyHandle; WNDPROC m_lpfnOldWndProc; }; #endif // !defined(AFX_TWINDOW_H__9CF24F11_A481_48F8_8AD5_CA7D9DCC1B83__INCLUDED_)

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, 
DWORD dwStyle, int x, int y, int nWidth, int nHeight, HWND hwndParent, HMENU nIDorHMenu) { //Если уже имеется дескриптор if ( GetHandle() ) //Возвращаем FALSE . Окно уже создано return FALSE; //Заполняем структуру создания окна переданными параметрами CREATESTRUCT cs; cs.dwExStyle = dwExStyle; cs.lpszClass = lpszClassName; cs.lpszName = lpszWindowName; cs.style = dwStyle; cs.x = x; cs.y = y; cs.cx = nWidth; cs.cy = nHeight; cs.hwndParent = hwndParent; cs.hMenu = nIDorHMenu; cs.lpCreateParams = 0L; //Предварительное создание окна. //Если ия класса не передано заполняет его именем класса по умолчанию if ( PreCreateWindow( &cs )) { // Создаем окно m_hWnd = ::CreateWindowEx( cs.dwExStyle, cs.lpszClass, cs.lpszName, cs.style, cs.x, cs.y, cs.cx, cs.cy, cs.hwndParent, cs.hMenu, Application->GetInstanceHandle(), ( LPVOID )this ); //если создали окно if ( m_hWnd ) { //требуем разрушить окно при отсоединении дескриптора m_bDestroyHandle = TRUE; //заполняем старую процедуру окна m_lpfnOldWndProc = ( WNDPROC )::GetWindowLong( m_hWnd, m_bIsDialog ? DWL_DLGPROC : GWL_WNDPROC ); //если старая процедура не статическая процедура этого класса if ( m_lpfnOldWndProc && m_lpfnOldWndProc != ( WNDPROC )TWindow::StaticWindowProc ) { //устанавливаем процедуру окна if (::SetWindowLong(m_hWnd, m_bIsDialog?DWL_DLGPROC:GWL_WNDPROC, ( LONG )TWindow::StaticWindowProc )) //окно создано успешно. Возвращаем TRUE return TRUE; } //процедура уже назначена else //окно создано успешно. Возвращаем TRUE return TRUE; } //возвращаем TRUE если имеется рабочий дескриптор return ( BOOL )( m_hWnd ); } //Создание окна не произошло. или оно уже было создано ранее return FALSE; } BOOL TWindow::PreCreateWindow(LPCREATESTRUCT pCS) { if ( pCS->lpszClass == NULL ) pCS->lpszClass = MAINWINDOWCLASS; return TRUE; } /*============================================================================== Функция: TWindow *FindObjectByHandle(HWND hWnd ) Параметры: hWnd-дескриптор окна по которому ищется объект Возврат: Указатель на TWindow или NULL если объект с таким дескриптором не найден Назначение: Получить указатель на объект по дескриптору окна Примечания: Может терпеть неудачу если требуемое окно с дескриптором hWnd не является окном библиотеки, или для объекта был вызван метод Detach ==============================================================================*/ 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; } LRESULT TWindow::OnCommand( UINT /*nNotifyCode*/, UINT /*nCtrlID*/, HWND /*hWndCtrl*/ ) { return -1; } LRESULT TWindow::OnDestroy() { return -1; } LRESULT TWindow::OnClose() { return -1; } LRESULT TWindow::OnCreate( LPCREATESTRUCT /*pCS*/ ) { return -2; } LRESULT TWindow::WindowProc( UINT uMsg, WPARAM wParam, LPARAM lParam ) { LRESULT lResult = 0; if ( m_lpfnOldWndProc && m_lpfnOldWndProc != ( WNDPROC )TWindow::StaticWindowProc ) #if __BORLANDC__==0x0550 lResult = ::CallWindowProc((FARPROC) m_lpfnOldWndProc, GetHandle(), uMsg, wParam, lParam ); #else lResult = ::CallWindowProc( m_lpfnOldWndProc, GetHandle(), uMsg, wParam, lParam ); #endif else if ( m_bIsDialog == FALSE ) { lResult = ::DefWindowProc( GetHandle(), uMsg, wParam, lParam ); } return lResult; } void TWindow::Destroy() { if ( GetHandle()) { if ( m_bDestroyHandle ) { HWND hWnd = m_hWnd; Detach(); ::DestroyWindow( hWnd ); } else Detach(); } }

Тестирование библиотеки.

Для того чтобы протестировать то что мы написали проделаем следующее. Создадим проект с одним единственным файлом. Пусть он будет называться 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;
}

Ну и как?. Объем написания программы сократился в разы?
Что же мы здесь делаем?
1. Создаем новый объект TApplication

Application=new TApplication;

2. Создаем объект окна.

TTestWindow *Wnd= new TTestWindow;

3. Создаем само окно.

Wnd->Create( 0L,NULL,"Пример окна",WS_OVERLAPPEDWINDOW/WS_VISIBLE, CW_USEDEFAULT, 
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, NULL,NULL);

4. Входим в цикл обработки сообщений

Application->Run();

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


Страница сайта http://test.interface.ru
Оригинал находится по адресу http://test.interface.ru/home.asp?artId=5656