Написание приложений Win32 Apps с помощью одних классов C++ (часть 1) - Hello World: обертки GDI

ОГЛАВЛЕНИЕ

Hello World: обертки GDI

Для начала надо создать EWnd в SWnd и реализовать карту сообщений, отправляющую WM_PANT.
Затем нужен макрос карты сообщений, далее нужны избранные обертки объекта GDI, или надо включить библиотеку GDI+.

Макрос карты сообщений

В духе макросов ATL они имеют такие же имена, чтобы позволить мастеру IDE также делать свою тяжелую работу, но они не идентичны ATL (они рассчитаны на работу в обертке EWnd, а не CWnd или CWindow!). Они включены в MessageMap.h. Простейшие макросы ATL тоже работают хорошо, но не макросы WTL.

Макросы распаковщика сообщений имеют такие же имена и параметры, что и у WTL, но иную реализацию.

Примечание: Макросы были получены путем интенсивного использования "поиска и замены" из первоисточников. Они являются сотыми, поэтому не были все протестированы единолично, но поскольку процесс был одинаковым для всех и так как протестированные макросы работали, было предположено, что все они работают.

Но не забывайте тестировать макросы по мере использования.

Ниже приведена очень простая программа "hello world":

// W2.cpp: тестовое приложение W32

#include "stdafx.h"

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

using namespace GE_;

class CMainWnd: public NWin::EWnd
{
public:
    BEGIN_MSG_MAP(CMainWnd)
        GETMSG_WM_PAINT(OnPaint)
    END_MSG_MAP()
    void OnPaint();
};


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);
    //создается экземпляр класса окна
   
    CMainWnd 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;
}

void CMainWnd::OnPaint()
{
    PAINTSTRUCT ps;
    BeginPaint(*this, &ps);
   
    NWin::SRect R;
    GetClientRect(*this, &R);
    DrawText(ps.hdc, _T("Hallo World !"),-1,
          R, DT_CENTER|DT_VCENTER|DT_SINGLELINE);

    EndPaint(Value(), &ps);
}

Разве код не хорош? Еще нет: что такое пары BeginPaint - EndPaint? Почему бы не обернуть HDC в обертку, правильно управляющую парами «начало-конец» или «получить-освободить»? И как насчет сохранения и восстановления состояния перед выходом?

Обертывание HDC(описатель контекста устройства)

Идея сохранить состояние DC(контекст устройства) при обертывании кажется хорошей, но где должно сохраниться состояние?

Есть два возможных ответа:
•    В самой обертке: каждая обертка одного и того же DC хранит свое собственное состояние, сохраняя его при Attach и восстанавливая его при Detach. Это полезно для временной обертки, прикрепляемой в начале функции, уничтожение которой в конце восстановит DC и закроет его. Вызовы вложенных функций создают серию оберток, уничтожаемых в обратном порядке, что приемлемо.
•    В объекте счетчика ссылок: сохраненное состояние совместно используется всеми обертками одного и того же HDC и восстанавливается при отцеплении последнего владельца. Это хорошо для долговечных оберток, передаваемых в виде значений между функциями или сохраняющих обернутый объект, пока другие объекты ссылаются на него. Это имеет место для "SuperWndProc" в EWnd, но не так для HDC: его нормальный срок службы не выдерживает обработку команды.

С учётом вышесказанного был предусмотрен набор оберток HDC, способных взаимодействовать (они используют один и тот же XRefCount), но с разной политикой прикрепления/отцепления.

Все классы основаны на SHdcSave_base<W>, переопределяющем OnAddRef и OnRelease для сохранения и восстановления состояния DC. Затем:
•    SHdcSave(HDC): закрытие SHdcSave_base, с конструктором, принимающим HDC в виде аргумента.
•    SHdcPaint(HWND): переопределяет Create, вызывая BeginPaint, и Destroy, вызывая EndPaint. Конструктор хранит HWND. PAINSTRUCT доступен как read_only.
•    SHdcClient(HWND): Create вызывает GetDC, а Destroy вызывает ReleaseDC.
•    SHdcWindow(HWND): Create вызывает GetWindowDC, а Destroy вызывает ReleaseDC.
•    SHdcMem(SIZE): создает контекст устройства памяти, совместимый с экраном, и хранит совместимый битовый массив с выбранным в него заданным SIZE. Годится для двойной буферизации.
С SHdcPaint метод OnPaint становится таким:

void CMainWnd::OnPaint()
{
    NGDI::SHdcPaint dc(*this);

    NWin::SRect R;
    GetClientRect(*this, R);
    DrawText(dc, _T("Hallo World !"),-1,
         R, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
}

HPEN, HBRUSH, HFONT 

Объекты GDI(интерфейс графического устройства) могут создаваться разными способами, но всегда уничтожаются с помощью одного и того же API: DeleteObject.

Следовательно, SGdiObj<H> обертывает любой HGDIOBJECT посредством HndBase, переопределяя обратный вызов Destroy путем вызова DeleteObject.

Важно, что объекты GDI уничтожаются, когда объекты не выбраны в некоторый HDC. Это достигается путем инициализации оберток GDI до инициализации обертки HDC. Такой прием позволит обертке HDC уничтожиться перед восстановлением своего состояния и освободить объект GDI, который уничтожат его собственные обертки.

Ниже приведен метод "OnPaint" с другим шрифтом:

void CMainWnd::OnPaint()
{
    NWin::SRect R;
    GetClientRect(*this, R);

    NGDI::TFont font(CreateFont(60,0,0,0, FW_BOLD,
        0,0,0, 0,0,0,ANTIALIASED_QUALITY, FF_SWISS, NULL));
   
    NGDI::SHdcPaint dc(*this);
    SetTextColor(dc, GetSysColor(COLOR_WINDOWTEXT));
    SetBkColor(dc, GetSysColor(COLOR_WINDOW));
    SelectObject(dc, font);

    DrawText(dc, _T("Hello World !"),-1,
        R, DT_CENTER|DT_VCENTER|DT_SINGLELINE);
}

Заметьте, что font создается раньше dc, значит dc будет уничтожен первым. Когда это происходит, вызываются "RestoreDC и затем EndPaint". RestoreDC отменяет выбор шрифта, EndPaint закрывает рисование. В этот момент уничтожение font удаляет объект GDI.