Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Процедура окна — это просто функция, которая вызывается для каждого сообщения, поэтому она по своей природе без состояния. Поэтому вам нужен способ отслеживания состояния приложения из одного вызова функции к следующему.
Самый простой подход — просто поместить все в глобальные переменные. Это работает достаточно хорошо для небольших программ, и многие примеры пакета SDK используют этот подход. Однако в большой программе это приводит к распространению глобальных переменных. Кроме того, у вас может быть несколько окон, каждая из которых имеет собственную процедуру окна. Отслеживание того, какое окно должно иметь доступ к каким переменным, становится запутанным и подверженным ошибкам.
Функция CreateWindowExпредоставляет способ передачи любой структуры данных в окно. При вызове этой функции она отправляет следующие два сообщения в процедуру окна:
Эти сообщения отправляются в указанном порядке. (Это не единственные два сообщения, отправленные во время CreateWindowEx, но другие можно игнорировать в рамках этого обсуждения.)
Сообщение WM_NCCREATE и WM_CREATE отправляются до того, как окно станет видимым. Это делает их хорошим местом для инициализации пользовательского интерфейса, например для определения начального макета окна.
Последний параметр CreateWindowEx является указателем типа void*. В этом параметре можно передать любое значение указателя. Когда процедура окна обрабатывает сообщение WM_NCCREATE или WM_CREATE, оно может извлечь это значение из данных сообщения.
Давайте посмотрим, как этот параметр будет использоваться для передачи данных приложения в окно. Сначала определите класс или структуру, содержащую сведения о состоянии.
// Define a structure to hold some state information.
struct StateInfo {
// ... (struct members not shown)
};
При вызове CreateWindowExпередайте указатель на эту структуру в последнем параметре void*.
StateInfo *pState = new (std::nothrow) StateInfo;
if (pState == NULL)
{
return 0;
}
// Initialize the structure members (not shown).
HWND hwnd = CreateWindowEx(
0, // Optional window styles.
CLASS_NAME, // Window class
L"Learn to Program Windows", // Window text
WS_OVERLAPPEDWINDOW, // Window style
// Size and position
CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT,
NULL, // Parent window
NULL, // Menu
hInstance, // Instance handle
pState // Additional application data
);
При получении сообщений WM_NCCREATE и WM_CREATE параметр lParam каждого сообщения является указателем на структуру CREATESTRUCT. В свою очередь, структура CREATESTRUCT содержит указатель, который вы передали в CreateWindowEx.
схема
Вот способ извлечь указатель на вашу структуру данных. Сначала получите структуру CREATESTRUCT , приведя параметр lParam.
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
Элемент lpCreateParams структуры CREATESTRUCT является исходным указателем пустоты, указанным в CreateWindowEx. Получите указатель на собственную структуру данных путем приведения lpCreateParams.
pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);
Затем вызовите функцию SetWindowLongPtr и передайте указатель на структуру данных.
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
Цель последнего вызова функции — хранение указателя StateInfo в данных экземпляра окна. После этого можно всегда вернуть указатель из окна, вызвав GetWindowLongPtr:
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
Каждое окно имеет собственные данные экземпляра, поэтому можно создать несколько окон и предоставить каждому окну собственный экземпляр структуры данных. Этот подход особенно полезен при определении класса окон и создании нескольких окон этого класса, например при создании пользовательского класса управления. Удобно обернуть вызов GetWindowLongPtr в небольшой вспомогательной функции.
inline StateInfo* GetAppState(HWND hwnd)
{
LONG_PTR ptr = GetWindowLongPtr(hwnd, GWLP_USERDATA);
StateInfo *pState = reinterpret_cast<StateInfo*>(ptr);
return pState;
}
Теперь вы можете написать процедуру окна следующим образом.
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
StateInfo *pState;
if (uMsg == WM_CREATE)
{
CREATESTRUCT *pCreate = reinterpret_cast<CREATESTRUCT*>(lParam);
pState = reinterpret_cast<StateInfo*>(pCreate->lpCreateParams);
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pState);
}
else
{
pState = GetAppState(hwnd);
}
switch (uMsg)
{
// Remainder of the window procedure not shown ...
}
return TRUE;
}
Подход Object-Oriented
Мы можем расширить этот подход дальше. Мы уже определили структуру данных для хранения сведений о состоянии окна. Важно предоставить эту структуру данных с функциями-членами (методами), которые работают с данными. Это естественно приводит к проектированию, где структура (или класс) отвечает за все операции в окне. Затем процедура окна станет частью класса.
Иными словами, мы хотели бы перейти к этому:
// pseudocode
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
StateInfo *pState;
/* Get pState from the HWND. */
switch (uMsg)
{
case WM_SIZE:
HandleResize(pState, ...);
break;
case WM_PAINT:
HandlePaint(pState, ...);
break;
// And so forth.
}
}
Для этого выполните указанные ниже действия.
// pseudocode
LRESULT MyWindow::WindowProc(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_SIZE:
this->HandleResize(...);
break;
case WM_PAINT:
this->HandlePaint(...);
break;
}
}
Единственная проблема заключается в том, как подключить метод MyWindow::WindowProc
. Функция RegisterClassожидает, что процедура окна будет указателем функции. Невозможно передать указатель на функцию-член (нестатическую) в этом контексте. Однако можно передать указатель на статическую функцию-член и затем делегировать её члену-функции. Ниже приведен шаблон класса, показывающий этот подход:
template <class DERIVED_TYPE>
class BaseWindow
{
public:
static LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
DERIVED_TYPE *pThis = NULL;
if (uMsg == WM_NCCREATE)
{
CREATESTRUCT* pCreate = (CREATESTRUCT*)lParam;
pThis = (DERIVED_TYPE*)pCreate->lpCreateParams;
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)pThis);
pThis->m_hwnd = hwnd;
}
else
{
pThis = (DERIVED_TYPE*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
}
if (pThis)
{
return pThis->HandleMessage(uMsg, wParam, lParam);
}
else
{
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}
BaseWindow() : m_hwnd(NULL) { }
BOOL Create(
PCWSTR lpWindowName,
DWORD dwStyle,
DWORD dwExStyle = 0,
int x = CW_USEDEFAULT,
int y = CW_USEDEFAULT,
int nWidth = CW_USEDEFAULT,
int nHeight = CW_USEDEFAULT,
HWND hWndParent = 0,
HMENU hMenu = 0
)
{
WNDCLASS wc = {0};
wc.lpfnWndProc = DERIVED_TYPE::WindowProc;
wc.hInstance = GetModuleHandle(NULL);
wc.lpszClassName = ClassName();
RegisterClass(&wc);
m_hwnd = CreateWindowEx(
dwExStyle, ClassName(), lpWindowName, dwStyle, x, y,
nWidth, nHeight, hWndParent, hMenu, GetModuleHandle(NULL), this
);
return (m_hwnd ? TRUE : FALSE);
}
HWND Window() const { return m_hwnd; }
protected:
virtual PCWSTR ClassName() const = 0;
virtual LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam) = 0;
HWND m_hwnd;
};
Класс BaseWindow
является абстрактным базовым классом, из которого производные определенные классы окон. Например, вот объявление простого класса, производного от BaseWindow
:
class MainWindow : public BaseWindow<MainWindow>
{
public:
PCWSTR ClassName() const { return L"Sample Window Class"; }
LRESULT HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam);
};
Чтобы создать окно, вызовите BaseWindow::Create
:
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE, PWSTR pCmdLine, int nCmdShow)
{
MainWindow win;
if (!win.Create(L"Learn to Program Windows", WS_OVERLAPPEDWINDOW))
{
return 0;
}
ShowWindow(win.Window(), nCmdShow);
// Run the message loop.
MSG msg = { };
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
Метод чисто виртуальной BaseWindow::HandleMessage
используется для реализации процедуры обработки окна. Например, следующая реализация эквивалентна процедуре окна, показанной в начале модуля 1.
LRESULT MainWindow::HandleMessage(UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(m_hwnd, &ps);
FillRect(hdc, &ps.rcPaint, (HBRUSH) (COLOR_WINDOW+1));
EndPaint(m_hwnd, &ps);
}
return 0;
default:
return DefWindowProc(m_hwnd, uMsg, wParam, lParam);
}
return TRUE;
}
Обратите внимание, что дескриптор окна хранится в переменной-члене (m_hwnd), поэтому нам не нужно передавать его в качестве параметра в HandleMessage
.
Многие существующие платформы программирования Windows, такие как классы Microsoft Foundation (MFC) и библиотека активных шаблонов (ATL), используют подходы, которые в основном похожи на приведенный здесь подход. Конечно, полностью обобщенная платформа, например MFC, является более сложной, чем этот относительно упрощенный пример.
Следующий
Модуль 2: Использование COM в вашей программе Windows