I am working on an ImGui project, and am trying to find a way to create custom key binds. I thought of just listing every single VK key code in a massive,
if
statement, but not only would that be bulky, but I would also not take certain keys such as some unique mouse buttons or any other key I may end up missing. I want a function that will store the next mouse or keyboard input into an integer, without the use of a predefined set of available inputs. I want to dynamically recognize any input key.
Minimal example:
const char* cbind0 = "none";
static bool bbind0 = false;
static int ibind0;
if (ImGui::Button(cbind0))
bbind0 = true
if (bbind0 == true)
{
cbind0 = "press any key...";
CopyNextInputTo(ibind0); // Function to copy pressed key to our integer
}
This code would show up as a box in the GUI, and then the integer ibind0 which is containing our now determined keybind, will be used like so:
static bool option = false;
if (GetAsyncKeyState(ibind0) & 1)
{
option =! option;
}
And now we can toggle our option on and off either using a GUI checkbox or by pressing the user-determined key.
The only problem now being I have no clue how to dynamically record all possible inputs! Does anyone know any possible functions or methods? Thanks!
Assuming you are the one creating the window you're using ImGui on, you can handle WM_KEYDOWN events in your WNDPROC callback. Here's a basic example of this in action that will create a pop-up message box whenever you press a key:
#ifndef UNICODE
#define UNICODE
#endif
#include <Windows.h>
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, PWSTR pCmdLine, int nCmdShow) {
const wchar_t WINDOW_CLASS_NAME[] = L"Keyboard Listener Class";
const wchar_t WINDOW_TITLE[] = L"Keyboard Listener";
WNDCLASS windowClass{};
windowClass.lpfnWndProc = WindowProc;
windowClass.hInstance = hInstance;
windowClass.lpszClassName = WINDOW_CLASS_NAME;
RegisterClass(&windowClass);
HWND hWnd = CreateWindowEx(
0,
WINDOW_CLASS_NAME,
WINDOW_TITLE,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
hInstance,
NULL
);
if (hWnd == NULL) {
return EXIT_FAILURE;
}
ShowWindow(hWnd, nCmdShow);
MSG msg{};
while (GetMessage(&msg, NULL, 0, 0) > 0) {
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return EXIT_SUCCESS;
}
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam) {
switch (uMsg) {
case WM_DESTROY:
PostQuitMessage(0);
return EXIT_SUCCESS;
case WM_KEYDOWN:
wchar_t keyName[64];
GetKeyNameText(lParam, keyName, sizeof(keyName));
MessageBox(hWnd, keyName, L"Key Pressed!", MB_OK);
return EXIT_SUCCESS;
default:
return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return EXIT_SUCCESS;
}
Or, if you aren't the one actually creating the Window, you can hook into another Window's keyboard events using SetWindowsHookEx with the idHook parameter set to WH_KEYBOARD and passing in a KeyboardProc callback to the lpfn parameter.
I want to use the Windows API to get keyboard input from a window. From what I have heard, I can create a callback function that will be called when some events happen. I just don't know how.
ModuleManager::ModuleManager() {
HWND getGameWindow = FindWindow(NULL, TEXT("GameName"));
wndProc = (WNDPROC)SetWindowLongPtrA(getGameWindow, GWLP_WNDPROC, 0);
}
LRESULT CALLBACK ModuleManager::WndProc(const HWND hwnd, unsigned int message, uintptr_t wParam, long lparam) {
if (message == WM_CHAR) {
for (Module* m : modules) {
if (m->getKeybinding() == wParam) {
m->isActive = !m->isActive; // toggle
}
}
}
return CallWindowProcA(wndProc, hwnd, message, wParam, lparam);
}
Here is my current code. I want to set a callback on WndProc function.
It seems i figured it out.
The callback function must be a static function for some reason.
So the correct code is following:
ModuleManager::ModuleManager() {
HWND getGameWindow = FindWindow(NULL, TEXT("GameName"));
wndProc = (WNDPROC)SetWindowLongPtrA(getGameWindow, GWLP_WNDPROC, (LONG_PTR) &ModuleManager::WndProc);
}
LRESULT CALLBACK ModuleManager::WndProc(const HWND hwnd, unsigned int message, uintptr_t wParam, long lparam) {
if (message == WM_CHAR) {
for (Module* m : modules) {
if (m->getKeybinding() == wParam) {
m->isActive = !m->isActive; // toggle
}
}
}
return CallWindowProcA(wndProc, hwnd, message, wParam, lparam);
}
This is my attempt using a hook function to get the dialog window handle. Both SetWindowPos() and GetLastError() return correct values, but the dialog window is unaffected and shown at position 0,0.
#include <windows.h>
#include <iostream>
static UINT_PTR CALLBACK OFNHookProc (HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam) {
using namespace std;
switch (uiMsg) {
case WM_INITDIALOG: {
// SetWindowPos returns 1
cout << SetWindowPos(hdlg, HWND_TOPMOST, 200, 200, 0, 0, SWP_NOSIZE ) << endl;
// GetLastError returns 0
cout << GetLastError() << endl;
break;
}
}
return 0;
}
int main() {
OPENFILENAMEW ofn;
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(OPENFILENAMEW);
ofn.nMaxFile = MAX_PATH;
ofn.Flags = OFN_EXPLORER | OFN_FILEMUSTEXIST | OFN_ENABLEHOOK;
ofn.lpfnHook = OFNHookProc;
GetOpenFileNameW(&ofn);
return 0;
}
When using OFN_EXPLORER, you have to move hdlg's parent window, as the HWND passed to your callback is not the actual dialog window. This is clearly stated in the documentation:
OFNHookProc callback function
hdlg [in]
A handle to the child dialog box of the Open or Save As dialog box. Use the GetParent function to get the handle to the Open or Save As dialog box.
Also, you should wait for the callback to receive the CDN_INITDONE notification, instead of the WM_INITDIALOG message.
Try this:
static UINT_PTR CALLBACK OFNHookProc (HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam)
{
if ((uiMsg == WM_NOTIFY) &&
(reinterpret_cast<OFNOTIFY*>(lParam)->hdr.code == CDN_INITDONE))
{
SetWindowPos(GetParent(hdlg), HWND_TOPMOST, 200, 200, 0, 0, SWP_NOSIZE);
}
return 0;
}
Pre-Text/ Question
I am trying to make a fairly simple tool to help debug variable values. For it to be completely self contained within the class is what I am aiming for. The end product I can use a function in the class like ShowThisValue(whatever).
The problem I am having is that I can't figure out, if possible, to have the procedure within the class. Here is the short version, with the problem.
-Code updated again 11/29/13-
-I have put this in its own project now.
[main.cpp]
viewvars TEST; // global
TEST.CreateTestWindow(hThisInstance); // in WinMain() right before ShowWindow(hwnd, nFunsterStil);
[viewvars.h] The entire updated
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;
}
}
The message boxes appear in this order:
Has it made it this far?
Made it to window proc
DefWindowProc returned
Has it made it this far? // repeated?
Made it to window proc
DefWindowProc returned
Problem Creating the Window
Thanks for the help so far. Do you know where the problem might be?
To use a non-static class method as a window procedure requires a dynamically-allocated thunk, which is an advanced technique that I will not get into it here.
The alternative is to declare the class method as static, then it will work as a window procedure. Of course, being a static method, it can no longer access non-static class members without an instance pointer. To get that pointer, you can have the class pass its this pointer to the lpParam parameter of CreateWindow/Ex(), then the window procedure can extract that pointer from the WM_NCCREATE message and store it in the window using SetWindowLong/Ptr(GWL_USERDATA). After that, subsequent messages can retrieve that pointer using GetWindowLong/Ptr(GWL_USERDATA) and thus be able to access non-static members of that object. For example:
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);
}
}
The main message loop must not be in your class, and especially not in a "CreateTestWindow" function, as you will not return from that function until your thread receive the WM_QUIT message that makes GetMessage returns 0.
Here is simple implementation of your viewvars class. Key points:
The Window Proc is a static member.
The link between the Window Proc and the object is made through the
use of GWLP_USERDATA. See SetWindowLongPtr.
The class DTOR destroys the window if it still exists. The WM_DESTROY
message set the HWND member to 0.
Adding OnMsgXXX methods to the class is simple: declare/define then
and just call them from the WindowProc using the 'this' pointer
stored in GWLP_USERDATA.
EDIT:
As per Mr Chen suggestion, earlier binding of the HWND to the Object (in WM_NCCREATE) to allow message handler as methods during the Window Creation.
I changed the creation styles, to show the window and to be able to move it.
// 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 );
}
Finally, a "main" sample, but beware that there is here no way to end the process. That should be taken care by regular code (another windows).
// 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;
}
Unfortunately using an instance method as a C-style callback function for the WndProc won't work. At least not in any straight-forward way.
The reason it doesn't work like that is that an instance method requires the this pointer to be passed in (to point to an instance) and that won't be correctly set by the code calling the WndProc. The Win32 API was originally designed with C in mind so this is one area where you have to use some work-arounds.
One way to work around this would be to create a static method to serve as the window proc and dispatch messages to your class instances. The class instances would have to be registered (read added to a static collection) so the static method would know to dispatch WndProc messages to the instances. Instances would register themselves with the static dispatcher in the constructor and remove themselves in the destructor.
Of course all the registration and unregistration and dispatching overhead is only necessary if your WndProc handler needs to invoke other instance member functions, or access member variables. Otherwise you can just make it static and you're done.
Your window procedure is called during CreateWindow. You pass hWindow to DefWindowProc, but hWindow is only set after CreateWindow returns - so you pass DefWindowProc a garbage window handle.
I don't see a nice way to do it. You could set hWindow inside the window procedure, by changing WindowProc to:
LRESULT WindowProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
(added the hwnd parameter), changing the call to:
return view->WindowProc(hwnd, message, wParam, lParam);
creating the window like this:
hWindow = NULL;
hWindow = CreateWindow(..., hInst, this);
if (hWindow == NULL)
return -1;
(the first assignment is to make sure hWindow is initialized; the second one is in case CreateWindow fails after calling the window procedure), and adding this at the start of WindowProc:
if(!this->hWindow)
this->hWindow = hwnd;
Step through the code in the debugger. When you get to the line
MessageBox(NULL,"DefWindowProc Returned.","Error",0);
return DefWindowProc(hWindow, message, wParam, lParam);
You will see something wrong: hWindow is garbage. You are using an uninitialized variable.