Верхнее окно создано с использованием класса TopWinMaker, который обеспечивает соответствующий стиль и заголовок.
class WinMaker {
public:
WinMaker(WinClass& winClass);
operator HWND() { return _hwnd; }
void AddCaption(char const * caption) {
_windowName = caption;
}
void AddSysMenu() { _style |= WS_SYSMENU; }
void AddVScrollBar() { _style |= WS_VSCROLL; }
void AddHScrollBar() { _style |= WS_HSCROLL; }
void Create();
void Show(int nCmdShow = SW_SHOWNORMAL);
protected:
WinClass& _class;
HWND _hwnd;
DWORD _exStyle; // extended window style
char const* _windowName; // pointer to window name
DWORD _style; // window style
int _x; // horizontal position of window
int _y; // vertical position of window
int _width; // window width
int _height; // window height
HWND _hWndParent; // handle to parent or owner window
HMENU _hMenu; // handle to menu, or child-window id
void * _data; // pointer to window-creation data
};
WinMaker::WinMaker(WinClass& winclass) : _hwnd(0), _class(winClass), _exStyle(0), // extended window style
_windowName (0), // pointer to window name
_style(WS_OVERLAPPED), // window style
_x(CW_USEDEFAULT), // horizontal position of window
_y(0), // vertical position of window
_width(CW_USEDEFAULT), // window width
_height(0), // window height
_hWndParent(0), // handle to parent or owner window
_hMenu(0), // handle to menu, or child-window id
_data(0) // pointer to window-creation data
{ }
void WinMaker::Create() {
_hwnd = ::CreateWindowEx(_exStyle, _class.GetName(), _windowName, _style, _x, _y, _width, _height, _hWndParent, _hMenu, _class.GetInstance(), _data);
if (_hwnd == 0) throw WinException ("Internal error: Window Creation Failed.");
}
void WinMaker::Show(int nCmdShow) {
::ShowWindow(_hwnd, nCmdShow);
::UpdateWindow(_hwnd);
}
// Makes top overlapped window with caption
TopWinMaker::TopWinMaker((WinClass& winclass, char const* caption) : WinMaker(winClass) {
_style = WS_OVERLAPPEDWINDOW | WS_VISIBLE;
_windowName = caption;
}
Классы общего назначения
Прежде, чем идти дальше, рассмотрим некоторые простые классы общего назначения. WinException — нечто, что мы хотим использовать для исключений во время сбоев Windows API. Он заботится о восстановлении кода ошибки Windows. (Между прочим, имеется простой способ преобразовать код ошибки в строку функцией API FormatMessage.)
Класс ResString просто инкапсулирует строку, хранимую в строковых ресурсах вашего приложения.
// The exception class: stores the message and the error code class
WinException {
public:
WinException(char* msg) : _err(::GetLastError()), _msg(msg) {}
DWORD GetError() const { return _err; }
char const* GetMessage() const { return _msg; }
private:
DWORD _err;
char * _msg;
};
// The out-of-memory handler: throws exception
int NewHandler(size_t size) {
throw WinException( "Out of memory");
return 0;
}
class ResString {
enum { MAX_RESSTRING = 255 };
public:
ResString(HINSTANCE hInst, int resId);
operator char const*() { return _buf; }
private:
char _buf[MAX_RESSTRING + 1];
};
ResString::ResString(hinstance hinst, int resid) {
if (!::LoadString(hinst, resid, _buf, max_resstring + 1)) throw WinException ("Load String failed");
}
Controller
Контроллер — нервная система отдельного экземпляра окна. Он создается с этим окном, хранится с ним и, в заключение, разрушается вместе с ним. Вы можете помещать любую информацию о состоянии, имеющую отношение к специфическому экземпляру окна в его контроллер. Вообще же, контроллер содержит "Вид", который имеет дело с рисованием на поверхности окна, и он имеет доступ к "Модели", которая является мозгом вашего приложения (все это называется MVC, или образцом "Модель-Вид-Контроллер" ("Model-View-Controller"), изобретенным Smalltalk-программистами.
Если, как это часто бывает, ваше приложение имеет только одно окно верхнего уровня, Вы можете непосредственно включать модель в ее контроллер. Это упрощает управление ресурсами, но ценой усиления связи контроллера с моделью. В больших проектах нужно избегать таких связей. Предпочтительнее использовать внутри контроллера "интеллектуальный" указатель на модель.
Большинство методов контроллера требует дескриптора окна, с которым они взаимодействуют. Этот дескриптор передается с каждым сообщением Windows, но проще сохранить его один раз внутри контроллера и использовать всякий раз, когда он необходим. Помните — имеется взаимно однозначное соответствие между контроллерами и экземплярами окон (а следовательно, и их дескрипторами).
class Controller {
public:
Controller(HWND hwnd, CREATESTRUCT * pCreate);
~Controller();
void Size(int x, int y);
void Paint();
void Command(int cmd);
private:
HWND _hwnd;
Model _model;
View _view;
};
Оконная процедура — основной коммутационный узел Windows приложения. Вы не вызываете ее из вашей программы — ее вызывает Windows! Каждый раз когда случается что-то интересное, Windows посылает вашей программе сообщение. Это сообщение передается оконной процедуре. Вы можете обработать его, или передать оконной процедуре, заданной по умолчанию.
Оконная процедура вызывается с указанием дескриптора к окна, к которому направлено данное сообщение. Этот дескриптор однозначно идентифицирует внутреннюю структуру данных Windows, которая соответствует экземпляру окна. Это так часто случОконная процедура вызывается с указанием дескриптора к окна, к которому направлено данное сообщение. Этот дескриптор однозначно идентифицирует внутреннюю структуру данных Windows, которая соответствует экземпляру окна. Это так часто случается, что мы можем обращаться к этой структуре данных и использовать ее, чтобы сохранить некоторые специфические для экземпляра данные. Имеется типовой безопасный способ доступа к этой структуре. Между прочим, элемент GWL_USERDATA этой структуры гарантированно присутствует во всех окнах, включая окна сообщения, диалоговые окна и даже кнопки.
template <class T> inline T
WinGetLong(hwnd hwnd, int which = gwl_userdata) {
return reinterpret_cast<T>(::GetWindowLong (hwnd, which));
}
template <class T> inline void
WinSetLong(hwnd hwnd, t value, int which = gwl_userdata) {
::SetWindowLong(hwnd, which, reinterpret_cast<long>(value));
}
Каждый раз, когда Windows вызывает нашу оконную процедуру, мы хотим сначала восстановить ее контроллер. Вспомните, что может быть несколько окон, совместно использующих ту же самую оконную процедуру, и мы хотим иметь отдельный контроллер для каждого окна. Как мы узнаем, какой из контроллеров используетсять, когда произходит обратный вызов оконной процедуры? Мы можем выяснить это, рассмотрев дескриптор окна. В этом дескрипторе мы сохраняем указатель на контроллер данного окна, используя функцию Win[Set/Get]Long.
Оконная процедура сначала вызывается с сообщением WM_CREATE. В этот момент мы создаем экземпляр контроллера, нициализируем его дескриптором окна и специальной структурой данных по имени CREATESTRUCT, которая передана нам от Windows. Если же мы уже имеем контроллер, то сохраняем указатель на его в соответствующей внутренней Windows-структуре данных помеченной текущим hwnd. В следующий раз оконная процедура вызывается с сообщением, отличным от WM_CREATE, и мы просто восстанавливаем (отыскиваем) указатель на наш контроллер, используя hwnd.
Остальное просто. Оконная процедура интерпретирует параметры сообщения и вызывает соответствующие методы контроллера.
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
Controller * pCtrl = WinGetLong<Controller *>(hwnd);
switch (message) {
case WM_CREATE: // Have to catch exception in case new throws!
try {
pCtrl = new Controller(hwnd, reinterpret_cast<CREATESTRUCT *>(lParam));
WinSetLong<Controller *>(hwnd, pCtrl);
} catch (WinException e) {
::MessageBox(hwnd, e.GetMessage(), "Initialization", MB_ICONEXCLAMATION | MB_OK);
return –1;
} catch (…) {
:: MessageBox(hwnd, "Unknown Error", "Initialization", MB_ICONEXCLAMATION | MB_OK);
return –1;
}
return 0;
case WM_SIZE:
pCtrl->Size(LOWORD(lParam), HIWORD(lParam));
return 0;
case WM_PAINT:
pCtrl->Paint();
return 0;
case WM_COMMAND:
pCtrl->Command(LOWORD(wParam));
return 0;
case WM_DESTROY:
WinSetLong<Controller *>(hwnd, 0);
delete pCtrl;
return 0;
}
return ::DefWindowProc(hwnd, message, wparam, lparam);
}
Ниже представлены примеры простых реализаций нескольких методов построения контроллеров. Конструктор должен помнить дескриптор окна для более позднего использования, деструктор должен посылать сообщение выхода (quit), метод Size передает его параметр Просмотру (Экрану), и т.д. Мы будем говорить о рисовании в окне немного позже. Теперь, обратите внимание, что контроллер готовит поверхность "Холста" для работы "Вида".