Написание приложений Win32 Apps с помощью одних классов C++ (часть 1) - Обработка во время простоя и фильтрация сообщений

ОГЛАВЛЕНИЕ

Обработка во время простоя и фильтрация сообщений

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

SIdleEvent и SMsgFilterEvent оба являются EGlobal и Event. Параметр SIdleEvent является SMsgLoop, вызывающим событие. Если вы сами создаете событие, можете передать NULL.

Параметр SMsgFilterEvent является структурой Win32 MSG. В своем обработчике события возвращайте true, чтобы указать, что переданное сообщение было отфильтровано.

Обертывание HWND: создание производных классов окон

Обертки HWND должны уметь получать сообщения окна, а значит, они должны связывать WrpBase и EChainedAction, но этого недостаточно.

Также нужно получать сообщения из системы. Это требует механизма создания производных классов/изменения классов. К тому же надо отправлять сообщения вероятно разным функциям-членам. Возможны разные подходы к этому:
•    в стиле MFC: функции-члены увязываются с адресами с соответствующими идентификаторами сообщения. Когда оконная процедура подкласса получает сообщение, она просматривает карту, ища совпадение, и затем вызывает соответствующую функцию. Можно предусмотреть набор макросов для облегчения заполнения статической карты.
•    в стиле ATL: набор макросов предоставляет фрагменты кода, необходимые для распознавания сообщений и их отправки указанным функциям. Макросы также переопределяют известную виртуальную функцию, вызываемую производным классом оконной процедуры.
•    в стиле .NET: производный класс оконной процедуры распаковывает сообщения, отправляя их набору известных членов, каждый из которых возбуждает разное событие.
Ни один из этих подходов не идеален: первые два сильно связаны с макросами, а третий требует большой структуры данных и не является симметричным: есть объект, владеющий HWND, а остальные прикрепляются к нему.

Эти изъяны компенсируются несколькими мастерами, помогающими составлять карты сообщений или связывать обработчики событий. Учитывая необходимость иметь дело с большим числом сообщений, они являются лучшим компромиссом между методиками программирования и удобством инструмента. Подход ATL/WTL более гибкий, поэтому было сделано следующее:
1.    Обертки для одного и того же HWND связаны вместе. Первое прикрепление и последнее освобождение используются для создания производного класса или изменения класса обернутого HWND.
2.    OnFirstAttach переопределяется для создания производного класса окна.
3.    Производный класс оконной процедуры вызывает Act для связанного действия, относящегося к HWND.
4.    OnAction переопределяется для вызова известной виртуальной функции с созданием такого же прототипа ATL PocessWindowMessage.
5.    Default переопределяется для вызова предыдущей оконной процедуры, если действие возвращается необработанным.
6.    Для хранения предыдущей оконной процедуры требуется место, общее для всех оберток одного и того же окна. Следовательно, приходится создавать производный класс от XRefChain для хранения этого значения.

Результат всего этого - EWnd.

Его объявление приведено ниже:

class EWnd:
    public IMessageMap,
    public NWrp::WrpBase<HWND, EWnd,NULL,traits_wrp_types<HWND>, XRefChainWnd>,
    public NWrp::EChainedAction<EWnd, EWnd::iterator, LPWndProcParams>,
    public NUtil::EAutodeleteFlag
{
public:
    void OnFirstAttach(NSmart::XRefCount_base* pRC); //создается производный класс окна
    static bool Default();  //вызывается superwndproc
    void Destroy(HWND& hWnd);  //уничтожается HWND (только владельцы уничтожаются)
    bool OnAction(const LPWndProcParams& a); //вызывается ProcessWindowMessage
   
    //copy and assign
    EWnd() {SetOwnership(false);}
    EWnd(const EWnd& w) { SetOwnership(false); AttachRefCounting(w); }
    EWnd& operator=(const EWnd& w) { AttachRefCounting(w); return *this; }
    EWnd& operator=(HWND h) { Attach(h); return *this; }
    explicit EWnd(HWND h) { SetOwnership(false);  Attach(h); }

    //реализация IMessageMap по умолчанию
    virtual bool ProcessWindowMessage(
        HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam,
        LRESULT& rResult, DWORD msgMapID) { return false; }

    //несколько вспомогательных функций, общих для всех окон
    LRESULT ForwardNotifications(UINT uMsg,
        WPARAM wParam, LPARAM lParam, BOOL& bHandled);
    LRESULT ReflectNotifications(UINT uMsg,
        WPARAM wParam, LPARAM lParam, BOOL& bHandled);
    BOOL DefaultReflectionHandler(HWND hWnd, UINT uMsg,
        WPARAM wParam, LPARAM lParam, LRESULT& lResult);

    //создание окна
    bool CreateWnd(
        HINSTANCE hInst,
        LPCTSTR clsName,
        LPCTSTR wndName,
        DWORD dwStyle,
        LPCRECT lprcPlace,
        HWND hwndParent,
        HMENU hMenu,
        LPVOID lpParams
        );
};

Примечания:
1.    IMessageMap – интерфейс, объявляющий функцию ProcessWindowMessage в стиле ATL.
2.    порождается от ERefChaining, но использует XRefChainWnd. Порождается от XRefChain, но также хранит указатель функции WNDPROC и счетчик рекурсий.
3.    порождается от EChainedAction, используя LPWndProcParams. XWndProcParams является struct, принимающей hWnd, uMsg,wParam, lParam и lResult.
4.    порождается от EAutoDeleteFlag. Если установлен в true (значение по умолчанию - false), то обертка удаляется при уничтожении окна
5.    обертка всегда создается как "невладелец" (наблюдатель). Назначение ее "владельцем" уничтожит HWND, если все владельцы отцеплены.

Бесполезная программа

Очень простая, бесполезная, но показывает все вышеописанное в действии.

#include "stdafx.h"

#include "NLib/MsgLoop.h"
#include "NLib/Wnd.h"

using namespace GE_;

int APIENTRY _tWinMain(HINSTANCE hInstance,
    HINSTANCE hPrevInstance, LPTSTR lpCmdLine, int nCmdShow)
{
    NWin::SMsgLoop loop(true);  //создается экземпляр цикла
   
    LPCTSTR wcname = _T("W1MainWnd");
    NWin::SWndClassExReg wc(true, hInstance, wcname);
    //создается экземпляр класса окна
   
    NWin::EWnd wnd;
    wnd.CreateWnd(hInstance, wcname, NULL,
        WS_VISIBLE|WS_OVERLAPPEDWINDOW,
        NWin::SRect(NWin::SPoint(CW_USEDEFAULT,CW_USEDEFAULT),
        NWin::SSize(600,400)), NULL, NULL, NULL);

    loop.Loop();

    return 0;
}

Создается окно на базе DefWndProc (передается SWndClassExReg в качестве параметра по умолчанию), не делающее ничего в исполнимом файле размером 76Кб.

Не самый маленький exe, но примите во внимание все функции типажей, std::map(карты), list(списки), и т.д. Они повышают гибкость, но имеют издержки!