Написание приложений 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.