Подтвердить что ты не робот

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

4b9b3361

Ответ 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 - мусор. Вы используете неинициализированную переменную.