Применение Windows API — страница 10 из 24

  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(hwnd, 0);

  break;

 }

 return FALSE; 

}

Здесь представлена красота полиморфизма в действии. Объект фабрики создан клиентом, использующим шаблонный класс. Этот объект передается конструктору ModalDialog. ModalDialog передает его процедуре диалога как пустой указатель (дело в том, что он должен пройти через Windows). Процедура Диалога получает его внутри сообщения WM_INITDIALOG как LPARAM. После прохождения пищеварительного тракта Windows он должен быть восстановлен к своей первоначальной форме, переводом его обратно к указателю на CtrlFactory — в базовый класс всех фабрик контроллера.

Когда мы вызываем его виртуальный метод MakeController, мы вызываем метод, переопределенный в шаблонном классе ControllerFactory. Он создает новый объект для класса ActualCtrl, определенного клиентом. Но снова, он возвращает этот объект к нам замаскированный как обобщенный указатель на DlgController. Так всякий раз, когда мы вызываем любой из виртуальных методов ctrl, мы выполняем клиентские переопределения, определенные в классе ActualCtrl. Это лучшее проявление полиморфизма: Вы записываете код, используя обобщенные указатели, но когда код выполнен, он вызывается с очень специфическими указателями. Когда Вы вызываете методы через эти указатели, Вы выполняете специфические методы, обеспеченные клиентом вашего кода.

Вот, что случается с фабрикой объектов, чей фактический класс

ControllerFactory 

Передается конструктору ModalDialog как void*
Передаётся от Windows к ModalDialogProcedure как LPARAM
Приведение в ModalDialogProcedure кCtrlFactory*

А вот, что случается с объектными данными, чьим фактическим классом является EditorData.

Передается конструктору фабрики как void*
Приведение в методе AcquireController класса ControllerFactory к EditorData*
Переданный конструктору EditCtrl какEditotData*

Объект класса EditCtrl, созданный в методе MakeController класса ControllerFactory возвращается из него как 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.