Win32 С++ Создание окна и процедуры внутри класса
Пред-текст/вопрос
Я пытаюсь сделать довольно простой инструмент, чтобы помочь отлаживать значения переменных. Для того, чтобы быть полностью самодостаточным в классе, к чему я стремлюсь. Конечный продукт я могу использовать функцию в классе, например ShowThisValue (независимо).
Проблема, с которой я сталкиваюсь, заключается в том, что я не могу понять, если это возможно, процедуру в классе. Вот короткая версия с этой проблемой.
-Код обновлен снова 11/29/13-
-Я поместил это в свой собственный проект сейчас.
[main.cpp]
viewvars TEST; // global
TEST.CreateTestWindow(hThisInstance); // in WinMain() right before ShowWindow(hwnd, nFunsterStil);
[viewvars.h] Все обновленные
class viewvars {
private:
HWND hWindow; // the window, a pointer to
LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK ThisWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
public:
viewvars(); // blank constructor
int CreateTestWindow(HINSTANCE hInst);
};
// blank constructor
viewvars::viewvars() {}
// create the window
int viewvars::CreateTestWindow(HINSTANCE hInst) {
// variables
char thisClassName[] = "viewVars";
MSG msg;
WNDCLASS wincl;
// check for class info and modify the info
if (!GetClassInfo(hInst, thisClassName, &wincl)) {
wincl.style = 0;
wincl.hInstance = hInst;
wincl.lpszClassName = thisClassName;
wincl.lpfnWndProc = &ThisWindowProc;
wincl.cbClsExtra = 0;
wincl.cbWndExtra = 0;
wincl.hIcon = NULL;
wincl.hCursor = NULL;
wincl.hbrBackground = (HBRUSH)COLOR_BTNSHADOW;
wincl.lpszMenuName = NULL;
if (RegisterClass(&wincl) == 0) {
MessageBox(NULL,"The window class failed to register.","Error",0);
return -1;
}
}
// create window
hWindow = CreateWindow(thisClassName, "Test", WS_POPUP | WS_CLIPCHILDREN, 10, 10, 200, 200, NULL, NULL, hInst, this);
if (hWindow == NULL) {
MessageBox(NULL,"Problem creating the window.","Error",0);
return -1;
}
// show window
ShowWindow(hWindow, TRUE);
// message loop
while (GetMessage(&msg, hWindow, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
// then quit window?
DestroyWindow(hWindow);
hWindow = NULL;
return msg.wParam;
}
// window proc
LRESULT CALLBACK viewvars::ThisWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
MessageBox(NULL,"Has it gone this far?","Bench",0);
// variable
viewvars *view;
// ????
if (message == WM_NCCREATE) {
CREATESTRUCT *cs = (CREATESTRUCT*)lParam;
view = (viewvars*) cs->lpCreateParams;
SetLastError(0);
if (SetWindowLongPtr(hwnd, GWL_USERDATA, (LONG_PTR) view) == 0) {
if (GetLastError() != 0) {
MessageBox(NULL,"There has been an error near here.","Error",0);
return FALSE;
}
}
}
else {
view = (viewvars*) GetWindowLongPtr(hwnd, GWL_USERDATA);
}
if (view) return view->WindowProc(message, wParam, lParam);
MessageBox(NULL,"If shown, the above statement did not return, and the statement below did.","Error",0);
return DefWindowProc(hwnd, message, wParam, lParam);
}
LRESULT viewvars::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) {
// you can access non-static members in here...
MessageBox(NULL,"Made it to window proc.","Error",0);
switch (message)
{
case WM_PAINT:
return 0;
break;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
default:
MessageBox(NULL,"DefWindowProc Returned.","Error",0);
return DefWindowProc(hWindow, message, wParam, lParam);
break;
}
}
Ящики сообщений отображаются в следующем порядке:
- Разве это так далеко?
- Сделано в окне proc
- Возврат DefWindowProc
- Разве это так?//повторяется?
- Сделано в окне proc
- Возврат DefWindowProc
- Проблема Создание окна
Спасибо за помощь. Вы знаете, где может быть проблема? ![enter image description here]()
Ответы
Ответ 1
Основной цикл сообщения не должен быть в вашем классе и, особенно, не в функции "CreateTestWindow", так как вы не вернетесь из этой функции, пока ваш поток не получит сообщение WM_QUIT
, которое делает GetMessage
, возвращает 0.
Вот простая реализация вашего класса viewvars
. Ключевые моменты:
- Окно Proc является статическим членом.
- Связь между окном Proc и объектом осуществляется через
использование GWLP_USERDATA. См. SetWindowLongPtr.
- Класс DTOR уничтожает окно, если оно все еще существует. WM_DESTROY
сообщение установите для члена HWND значение 0.
- Добавление методов OnMsgXXX в класс просто: объявить/определить, тогда
и просто назовите их из WindowProc с помощью указателя 'this'
хранится в GWLP_USERDATA.
EDIT:
- Согласно предложению г-на Чена, более ранняя привязка HWND к объекту (в WM_NCCREATE), чтобы позволить обработчику сообщений как методы во время создания окна.
Я изменил стили создания, чтобы показать окно и уметь его перемещать.
// VIEWVARS.H
class viewvars {
public:
static viewvars* CreateTestWindow( HINSTANCE hInstance );
viewvars() : m_hWnd( 0 ) {}
~viewvars();
private:
static LRESULT CALLBACK WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
static const char * m_pszClassName;
HWND m_hWnd;
};
// VIEWVARS.CPP
#include "viewvars.h"
const char * viewvars::m_pszClassName = "viewvars";
viewvars * viewvars::CreateTestWindow( HINSTANCE hInst ) {
WNDCLASS wincl;
if (!GetClassInfo(hInst, m_pszClassName, &wincl)) {
wincl.style = 0;
wincl.hInstance = hInst;
wincl.lpszClassName = m_pszClassName;
wincl.lpfnWndProc = WindowProc;
wincl.cbClsExtra = 0;
wincl.cbWndExtra = 0;
wincl.hIcon = NULL;
wincl.hCursor = NULL;
wincl.hbrBackground = (HBRUSH)(COLOR_BTNFACE+1);
wincl.lpszMenuName = NULL;
if (RegisterClass(&wincl) == 0) {
MessageBox(NULL,"The window class failed to register.","Error",0);
return 0;
}
}
viewvars * pviewvars = new viewvars;
HWND hWnd = CreateWindow( m_pszClassName, "Test", WS_VISIBLE | WS_OVERLAPPED, 50, 50, 200, 200, NULL, NULL, hInst, pviewvars );
if ( hWnd == NULL ) {
delete pviewvars;
MessageBox(NULL,"Problem creating the window.","Error",0);
return 0;
}
return pviewvars;
}
LRESULT CALLBACK viewvars::WindowProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam ) {
switch ( uMsg ) {
case WM_NCCREATE: {
CREATESTRUCT * pcs = (CREATESTRUCT*)lParam;
viewvars * pviewvars = (viewvars*)pcs->lpCreateParams;
pviewvars->m_hWnd = hwnd;
SetWindowLongPtr( hwnd, GWLP_USERDATA, (LONG)pcs->lpCreateParams );
return TRUE;
}
case WM_DESTROY: {
viewvars * pviewvars = (viewvars *)GetWindowLongPtr( hwnd, GWLP_USERDATA );
if ( pviewvars ) pviewvars->m_hWnd = 0;
break;
}
default:
return DefWindowProc( hwnd, uMsg, wParam, lParam );
}
return 0;
}
viewvars::~viewvars() {
if ( m_hWnd ) DestroyWindow( m_hWnd );
}
Наконец, "основной" образец, но будьте осторожны, что здесь нет способа закончить процесс. Это нужно позаботиться о регулярном коде (другие окна).
// MAIN.CPP
#include <Windows.h>
#include "viewvars.h"
int APIENTRY WinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow)
{
viewvars * pviewvars = viewvars::CreateTestWindow( hInstance );
if ( pviewvars == 0 ) return 0;
BOOL bRet;
MSG msg;
while( (bRet = GetMessage( &msg, 0, 0, 0 )) != 0)
{
if (bRet == -1)
{
// handle the error and possibly exit
}
else
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
delete pviewvars;
return 0;
}
Ответ 2
Чтобы использовать метод нестатического класса в качестве процедуры окна, требуется динамически выделенный thunk, который является передовым методом, в который я не буду входить здесь.
Альтернативой является объявить метод класса как static
, тогда он будет работать как оконная процедура. Конечно, будучи static
, он больше не может обращаться к членам нестатического класса без указателя экземпляра. Чтобы получить этот указатель, вы можете передать класс его указателю this
параметру lpParam
CreateWindow/Ex()
, тогда оконная процедура может извлечь этот указатель из сообщения WM_NCCREATE
и сохранить его в окне, используя SetWindowLong/Ptr(GWL_USERDATA)
. После этого последующие сообщения могут извлекать этот указатель с помощью GetWindowLong/Ptr(GWL_USERDATA)
и, таким образом, иметь доступ к нестационарным членам этого объекта. Например:
class viewvars
{
private:
HWND hWindow;
LRESULT WindowProc(UINT message, WPARAM wParam, LPARAM lParam);
static LRESULT CALLBACK ThisWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam);
public:
int CreateTestWindow(HINSTANCE hInst);
};
int viewvars::CreateTestWindow(HINSTANCE hInst)
{
WNDCLASS wincl;
if (!GetClassInfo(hInst, thisClassName, &wincl))
{
...
wincl.hInstance = hInst;
wincl.lpszClassName = thisClassName;
wincl.lpfnWndProc = &ThisWindowProc;
if (RegisterClass(&wincl) == 0)
return -1;
}
hWindow = CreateWindow(..., hInst, this);
if (hWindow == NULL)
return -1;
...
MSG msg;
while (GetMessage(&msg, hWindow, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
DestroyWindow(hWindow);
hWindow = NULL;
return msg.wParam;
}
LRESULT CALLBACK viewvars::ThisWindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
viewvars *view;
if (message == WM_NCCREATE)
{
CREATESTRUCT *cs = (CREATESTRUCT*) lParam;
view = (viewvars*) cs->lpCreateParams;
SetLastError(0);
if (SetWindowLongPtr(hwnd, GWL_USERDATA, (LONG_PTR) view) == 0)
{
if (GetLastError() != 0)
return FALSE;
}
}
else
{
view = (viewvars*) GetWindowLongPtr(hwnd, GWL_USERDATA);
}
if (view)
return view->WindowProc(message, wParam, lParam);
return DefWindowProc(hwnd, message, wParam, lParam);
}
LRESULT viewvars::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
// you can access non-static members in here...
switch (message)
{
case WM_PAINT:
return 0;
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hWindow, message, wParam, lParam);
}
}
Ответ 3
К сожалению, использование метода экземпляра в качестве функции обратного вызова C-стиля для WndProc не будет работать. По крайней мере, не в прямом направлении.
Причина, по которой это не работает, заключается в том, что метод экземпляра требует, чтобы указатель this
был передан (чтобы указать на экземпляр), и этот код будет неправильно установлен кодом, вызывающим WndProc. API Win32 первоначально был разработан с учетом C, поэтому это одна из областей, где вы должны использовать некоторые рабочие обходы.
Один из способов обойти это - создать статический метод, который будет служить обработкой окна и отправлять сообщения в экземпляры класса. Экземпляры класса должны быть зарегистрированы (чтение добавлено в статическую коллекцию), поэтому статический метод будет знать, чтобы отправлять сообщения WndProc в экземпляры. Экземпляры регистрировались бы со статическим диспетчером в конструкторе и удалялись в деструкторе.
Конечно, вся регистрация и отмена регистрации и накладные расходы необходимы только в том случае, если ваш обработчик WndProc должен вызывать другие функции-члены экземпляра или переменные-члены доступа. В противном случае вы можете просто сделать его статическим, и все готово.
Ответ 4
Ваша оконная процедура вызывается во время CreateWindow. Вы передаете hWindow в DefWindowProc, но hWindow устанавливается только после возврата CreateWindow, поэтому вы передаете DefWindowProc дескриптор окна мусора.
Я не вижу приятного способа сделать это. Вы можете установить hWindow внутри процедуры окна, изменив WindowProc на:
LRESULT WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(добавлен параметр hwnd), изменив вызов на:
return view->WindowProc(hwnd, message, wParam, lParam);
создание окна следующим образом:
hWindow = NULL;
hWindow = CreateWindow(..., hInst, this);
if (hWindow == NULL)
return -1;
(первое назначение - убедиться, что hWindow инициализирован, второй - в случае неудачи CreateWindow после вызова оконной процедуры), и добавив это в начало WindowProc:
if(!this->hWindow)
this->hWindow = hwnd;
Ответ 5
Пройдите код в отладчике. Когда вы дойдете до линии
MessageBox(NULL,"DefWindowProc Returned.","Error",0);
return DefWindowProc(hWindow, message, wParam, lParam);
Вы увидите что-то не так: hWindow
- мусор. Вы используете неинициализированную переменную.