template <class T> inline T
GetWinLong(HWND hwnd, int which = GWL_USERDATA) {
return reinterpret_cast<T>(::GetWindowLong(hwnd, which));
}
template <class T> inline void
SetWinLong(HWND hwnd, T value, int which = GWL_USERDATA) {
::SetWindowLong(hwnd, which, reinterpret_cast<long>(value));
}
Мы должны быть, хотя бы внимательными. Прежде всего мы должны освободить контроллер после того, как использовали его. Мы делаем это при обработке WM_DESTROY.
Во-вторых, Windows имеет неудачную идею (привычку) посылать сообщения WM_COMMAND и WM_NOTIFY перед WM_INITDIALOG и после WM_DESTROY. Что можно здесь сказать? Я бы побил менеджера, который ответствен за эти дела. Но раз это есть, мы должны защитить себя, проверяя, является ли ctrl ненулевым перед вызовом OnCommand и OnNotify.
BOOL CALLBACK ModalDialog::ModalDialogProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
DlgController* ctrl = GetWinLong<DlgController *>(hwnd);
switch (message) {
case WM_INITDIALOG:
{
CtrlFactory *ctrlFactory = reinterpret_cast<CtrlFactory *>(lParam);
ctrl = ctrlFactory->MakeController(hwnd);
SetWinLong<DlgController *>(hwnd, ctrl);
ctrl->OnInitDialog (hwnd);
}
return TRUE;
case WM_COMMAND:
if (ctrl && ctrl->OnCommand(hwnd, LOWORD(wParam), HIWORD (wParam))) return TRUE;
break;
case WM_NOTIFY:
if (ctrl && ctrl->OnNotify(hwnd, wParam, (NMHDR *)lParam))
return TRUE;
break;
case WM_DESTROY:
delete ctrl;
SetWinLong<DlgController *>(hwnd, 0);
break;
}
return FALSE;
}
Здесь представлена красота полиморфизма в действии. Объект фабрики создан клиентом, использующим шаблонный класс. Этот объект передается конструктору ModalDialog. ModalDialog передает его процедуре диалога как пустой указатель (дело в том, что он должен пройти через Windows). Процедура Диалога получает его внутри сообщения WM_INITDIALOG как LPARAM. После прохождения пищеварительного тракта Windows он должен быть восстановлен к своей первоначальной форме, переводом его обратно к указателю на CtrlFactory — в базовый класс всех фабрик контроллера.
Когда мы вызываем его виртуальный метод MakeController, мы вызываем метод, переопределенный в шаблонном классе ControllerFactory. Он создает новый объект для класса ActualCtrl, определенного клиентом. Но снова, он возвращает этот объект к нам замаскированный как обобщенный указатель на DlgController. Так всякий раз, когда мы вызываем любой из виртуальных методов ctrl, мы выполняем клиентские переопределения, определенные в классе ActualCtrl. Это лучшее проявление полиморфизма: Вы записываете код, используя обобщенные указатели, но когда код выполнен, он вызывается с очень специфическими указателями. Когда Вы вызываете методы через эти указатели, Вы выполняете специфические методы, обеспеченные клиентом вашего кода.
Вот, что случается с фабрикой объектов, чей фактический класс ControllerFactory <EditorCtrl, EditorData>
Передается конструктору ModalDialog как void* Передаётся от Windows к ModalDialogProcedure как LPARAM Приведение в ModalDialogProcedure к CtrlFactory*
А вот, что случается с объектными данными, чьим фактическим классом является EditorData.
Передается конструктору фабрики как void* Приведение в методе AcquireController класса ControllerFactory<EditorCtrl, EditorData> к EditorData* Переданный конструктору EditCtrl как EditotData*
Объект класса EditCtrl, созданный в методе MakeController класса ControllerFactory<EditorCtrl, EditorData> возвращается из него как DlgController* и сохраняется в этой форме как статический член данных ModalDialog.
Если Вы имеете проблемы после моего объяснения, не отчаивайтесь. Объектно ориентированные методы, которые я только описал, трудны, но необходимы. Они названы образцами проектирования. Я настоятельно рекомендую читать книгу: Gamma, Helm, Johnson and Vlissides — Design Patterns, Elements of Reusable Object-Oriented Software или посмотреть Patterns Home Page (домашнюю страницу образцов). Там описано много творческих способов использования полиморфизма, наследования и шаблонизации, чтобы делать программное обеспечение более пригодным для многократного использования.
Далее: Более подробный разговор о холсте.
Обертка для контекста устройств
Холст или контекст устройств Windows
Перевод А. И. Легалова
Англоязычный оригинал находится на сервере компании Reliable Software
Чтобы раукрашивать, рисовать или печатать в окне, Вам необходим контекст устройств (device context или, кратко, DC). DC — это ресурс, который заимствуется у Windows и, как предполагается, возвращается сразу же после того, как вы сделаете свою работу. Отсюда и берет корни объект Canvas (Холст). Конструктор Холста получает DC, а деструктор освобождает его. Важно то, что Вы создаете объекты Canvas как автоматические (стековые) переменные. Это гарантирует, что, когда программа выйдет из локальной области (контекста), всегда вызовется их деструктор, в которой определены ресурсы (предлагаемый класс является примером более общей методологии Управления ресурсами). Типичное использование объекта Canvas демонстрируется следующем кодом (вы его уже видели в программе Generic):
void Controller::Paint() {
// prepare the canvas and let View do the rest
PaintCanvas canvas(_hwnd);
_view.Paint(canvas, _model);
// Notice: The destructor of PaintCanvas called automatically!
}
Различные типы Холста совместно используют общего предка — класс Canvas. Обратите внимание, что конструктор Холста защищен. Фактически, Вы не можете сформировать объект этого класса. Однако, наследующие классы открыты, чтобы обеспечить доступ к их собственным конструкторам. Между прочим, Вы можете осуществлять добавление новых методов к Холсту, по мере возникновения такой потребности.
class Canvas {
public:
// operator cast to HDC
// (used when passing Canvas to Windows API)
operator HDC() { return _hdc; }
void Point(int x, int y, COLORREF color) {
::SetPixel(_hdc, x, y, color);
}
void MoveTo(int x, int y) {
::MoveToEx(_hdc, x, y, 0);
}
void Line(int x1, int y1, int x2, int y2 ) {
MoveToEx(_hdc, x1, y1, 0);
LineTo(_hdc, x2, y2);
}
void Rectangle(int left, int top, int right, int bottom) {
// draw rectangle using current pen
// and fill it using current brush
::Rectangle(_hdc, left, top, right, bottom);
}
void GetTextSize(int& cxChar, int& cyChar) {
TEXTMETRIC tm;
GetTextMetrics(_hdc, &tm);
cxChar = tm.tmAveCharWidth;
cyChar = tm.tmHeight + tm.tmExternalLeading;
}
void Text(int x, int y, char const * buf, int cBuf) {
::TextOut(_hdc, x, y, buf, cbuf);
}
void Char(int x, int y, char c) {
TextOut(_hdc, x, y, &c, 1);
}
void SelectObject(void* pObj) {
::SelectObject(_hdc, pobj);
}
protected:
Canvas(HDC hdc): _hdc(hdc) {}
HDC _hdc;
};
В ответ на сообщение WM_PAINT нужно создать объект PaintCanvas. Обратите внимание на способ получения и освобождения DC объектом PaintCanvas.
class PaintCanvas: public Canvas {
public:
// Constructor obtains the DC
PaintCanvas(HWND hwnd) : Canvas(BeginPaint(hwnd, &_paint)), _hwnd(hwnd) {}
// Destructor releases the DC
~PaintCanvas() {
EndPaint(_hwnd, &_paint);
}
protected:
PAINTSTRUCT _paint;
HWND _hwnd;
};
Другой важный пример — класс UpdateCanvas, который используется для графических операций вне контекста обработки сообщения WM_PAINT. Конечно, ваша программа может всегда инициировать перерисовку, вызывая InvalidateRect, но во многих случаях это было бы массовым убийством. Если ваша программа осуществляет перерисовку новых объектов, когда они обрабатываются или в ответ на действия пользователя, Вы можете модифицировать окно, используя UpdateCanvas.
class UpdateCanvas: public Canvas {
public:
UpdateCanvas(HWND hwnd) : Canvas(GetDC(hwnd)), _hwnd(hwnd) {}
~UpdateCanvas() {
ReleaseDC(_hwnd, _hdc);
}
protected:
HWND _hwnd;
};
Можно создать и другие типы Холста: DrawItemCanvas используется для рисования элементов управления их владельцем, MemCanvas — для рисования во фрагментах памяти, и т.д.
Далее: Использование перьев и кистей для рисования на холсте.
Перья и кисти внутри классов
Рисование перьями и раскрашивание кистями
Перевод А. И. Легалова