Win32 C++ Создать окно и процедуру внутри класса
Предтекст / Вопрос
Я пытаюсь сделать довольно простой инструмент, чтобы помочь отладить значения переменных. Я стремлюсь к тому, чтобы он был полностью самодостаточным в классе. Конечный продукт Я могу использовать функцию в классе, как 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;
}
}
Окна сообщений появляются в следующем порядке:
- Это сделало это так далеко?
- Сделано это в окно процесса
- DefWindowProc возвращается
- Это сделало это так далеко? // повторяется?
- Сделано это в окно процесса
- DefWindowProc возвращается
- Проблема создания окна
Спасибо за помощь. Вы знаете, где может быть проблема?
5 ответов
Основной цикл сообщений не должен быть в вашем классе, особенно в функции "CreateTestWindow", так как вы не вернетесь из этой функции, пока ваш поток не получит WM_QUIT
сообщение, которое делает GetMessage
возвращает 0
Вот простая реализация вашего viewvars
учебный класс. Ключевые моменты:
- Window Proc является статическим членом.
- Связь между Window Proc и объектом осуществляется с помощью GWLP_USERDATA. Смотрите SetWindowLongPtr.
- Класс DTOR уничтожает окно, если оно все еще существует. Сообщение WM_DESTROY устанавливает член HWND в 0.
- Добавить методы OnMsgXXX в класс очень просто: затем объявите / определите и просто вызовите их из WindowProc, используя указатель "this", хранящийся в GWLP_USERDATA.
РЕДАКТИРОВАТЬ:
- Согласно предложению г-на Чена, более раннее связывание 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;
}
Для использования нестатического метода класса в качестве оконной процедуры требуется динамически размещаемый блок, что является продвинутой техникой, и я не буду вдаваться в нее здесь.
Альтернатива - объявить метод класса как 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);
}
}
К сожалению, использование метода экземпляра в качестве функции обратного вызова в стиле C для WndProc не будет работать. По крайней мере, не прямым путем.
Причина, по которой это не работает, заключается в том, что метод экземпляра требует this
указатель, который будет передан (чтобы указать на экземпляр), и это не будет правильно установлено кодом, вызывающим WndProc. Win32 API был изначально разработан с учетом C, так что это одна из областей, где вы должны использовать некоторые обходные пути.
Одним из способов обойти это было бы создание статического метода, который будет служить оконным процессом и отправлять сообщения экземплярам вашего класса. Экземпляры класса должны быть зарегистрированы (чтение добавлено в статическую коллекцию), чтобы статический метод знал, что нужно отправлять сообщения WndProc экземплярам. Экземпляры регистрируются в статическом диспетчере в конструкторе и удаляются в деструкторе.
Конечно, все накладные расходы на регистрацию, отмену регистрации и диспетчеризацию необходимы только в том случае, если ваш обработчик WndProc должен вызывать другие функции-члены экземпляра или обращаться к переменным-членам. В противном случае вы можете просто сделать это статичным, и все готово.
Ваша оконная процедура вызывается во время 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;
Шаг через код в отладчике. Когда вы доберетесь до линии
MessageBox(NULL,"DefWindowProc Returned.","Error",0);
return DefWindowProc(hWindow, message, wParam, lParam);
Вы увидите что-то не так: hWindow
это мусор. Вы используете неинициализированную переменную.