Reputation: 56
First off, I want to say, for anyone who is going to say "why am I reinventing the wheel", I am doing this for fun, and for a project of mine I'm currently working on.
As you can see from the code below, I am trying to dynamically create a window and a button, but something I'm having trouble with is adding a function to that button when it's clicked.
I know it would be really simple to go into the window procedure in WM_COMMAND
and do so there, but that's missing the whole point of what I'm trying to accomplish here, have it so I can simple call btn.add(params)
to a certain window, and add a certain function to that button by calling say btn.click(function);
without having to go into the window procedure itself when adding the controls.
How would I accomplish this?
#include <Windows.h>
#include <vector>
#include <thread>
using namespace std;
WNDCLASSEX defWndClass = { 0 };
class WinForm
{
private:
HWND WindowHandle;
std::thread Thread;
std::vector<std::tuple<std::string, std::size_t, HWND>> ControlHandles;
public:
~WinForm();
WinForm(std::string ClassName, std::string WindowName, bool Threaded = false, int Width = CW_USEDEFAULT,
int Height = CW_USEDEFAULT, WNDPROC WindowProcedure = nullptr, WNDCLASSEX WndClass = defWndClass);
bool AddButton(std::string ButtonName, POINT Location, int Width, int Height);
};
WinForm::~WinForm()
{
if (Thread.joinable())
{
Thread.join();
}
}
WinForm::WinForm(std::string ClassName, std::string WindowName, bool Threaded, int Width, int Height, WNDPROC WindowProcedure, WNDCLASSEX WndClass)
:WindowHandle(nullptr)
{
if (WindowProcedure == nullptr)
{
WindowProcedure = [](HWND window, UINT msg, WPARAM wp, LPARAM lp)->LRESULT __stdcall
{
switch (msg)
{
/*
case WM_PAINT:
break;
*/
case WM_DESTROY:
PostQuitMessage(0);
return 0;
case WM_CREATE:
break;
default:
return DefWindowProc(window, msg, wp, lp);
}
return 0;
};
}
if (WndClass.cbSize == 0)
{
WndClass.cbSize = sizeof(WNDCLASSEX);
WndClass.style = CS_DBLCLKS;
WndClass.lpfnWndProc = WindowProcedure;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = 0;
WndClass.hInstance = GetModuleHandle(nullptr);
WndClass.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
WndClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
WndClass.hbrBackground = HBRUSH(COLOR_WINDOW + 1);
WndClass.lpszMenuName = nullptr;
WndClass.lpszClassName = ClassName.c_str();
WndClass.hIconSm = LoadIcon(nullptr, IDI_APPLICATION);
}
if (RegisterClassEx(&WndClass))
{
if (Threaded)
{
// can't do that!
}
else
{
WindowHandle = CreateWindowEx(0, ClassName.c_str(), WindowName.c_str(), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, Width, Height, nullptr, nullptr, GetModuleHandle(nullptr), nullptr);
if (WindowHandle)
{
ShowWindow(WindowHandle, SW_SHOWDEFAULT);
// don't put message loop here!
}
}
}
}
bool WinForm::AddButton(std::string ButtonName, POINT Location, int Width, int Height)
{
for (std::vector<std::tuple<std::string, std::size_t, HWND>>::iterator it = ControlHandles.begin(); it != ControlHandles.end(); ++it)
{
auto& tu = *it;
auto& str = std::get<0>(tu);
if (ButtonName.compare(str) == 0) {
return false;
}
}
std::size_t ID = 1;
for (std::vector<std::tuple<std::string, std::size_t, HWND>>::iterator it = ControlHandles.begin(); it != ControlHandles.end(); ++it, ++ID)
{
if (std::get<1>(*it) != ID)
{
break;
}
}
HWND ButtonHandle = CreateWindowEx(
0, "button", ButtonName.c_str(), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, Location.x, Location.y, Width, Height,
WindowHandle, (HMENU)ID, (HINSTANCE)GetWindowLong(WindowHandle, GWL_HINSTANCE), nullptr);
ShowWindow(ButtonHandle, SW_SHOW);
ControlHandles.push_back(std::make_tuple(ButtonName, ID, ButtonHandle));
//SendMessage(WindowHandle, WM_CREATE, 0, 0);
return true;
}
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
WinForm Form("Class", "Title", false);
POINT pt = { 50, 50 };
Form.AddButton("NewButton", pt, 80, 50);
MSG msg = { nullptr };
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
Upvotes: 0
Views: 1436
Reputation: 596582
The parent window of the button is going to receive a BN_CLICKED
notification via WM_COMMAND
when the button is clicked. If the parent window doesn't handle the message, it ends up going to DefWindowProc()
and gets ignored. So, the parent window needs to handle WM_COMMAND
. When the notification code is BN_CLICKED
, simply lookup the provided button HWND
in your list of buttons, and if found then call the corresponding function, if one has been assigned.
#include <Windows.h>
#include <vector>
#include <thread>
#include <algorithm>
#include <functional>
class WinForm
{
public:
using ControlActionFunc = std::function<void(const std::string &)>;
WinForm(std::string ClassName, std::string WindowName, bool Threaded = false, int Width = CW_USEDEFAULT, int Height = CW_USEDEFAULT);
~WinForm();
bool AddButton(const std::string &ButtonName, POINT Location, int Width, int Height, ControlActionFunc OnClick);
private:
HWND WindowHandle;
std::thread Thread;
using ControlInfo = std::tuple<std::string, std::size_t, HWND, ControlActionFunc>;
using ControlInfoVector = std::vector<ControlInfo>;
ControlInfoVector Controls;
static LRESULT __stdcall StaticWindowProcedure(HWND window, UINT msg, WPARAM wp, LPARAM lp);
protected:
virtual LRESULT WindowProcedure(UINT msg, WPARAM wp, LPARAM lp);
};
class MainAppWinForm : public WInForm
{
public:
using WinForm::WinForm;
protected:
LRESULT WindowProcedure(UINT msg, WPARAM wp, LPARAM lp) override;
};
WinForm::WinForm(std::string ClassName, std::string WindowName, bool Threaded, int Width, int Height)
: WindowHandle(nullptr)
{
HINSTANCE hInstance = GetModuleHandle(nullptr);
WNDCLASSEX WndClass = {};
bool isRegistered = GetClassInfoEx(hInstance, ClassName.c_str(), &WndClass);
if ((!isRegistered) || (WndClass.lpfnWndProc != &WinForm::StaticWindowProcedure))
{
if (isRegistered)
UnregisterClass(ClassName.c_str(), hInstance);
WndClass.cbSize = sizeof(WNDCLASSEX);
WndClass.style = CS_DBLCLKS;
WndClass.lpfnWndProc = &WinForm::StaticWindowProcedure;
WndClass.cbClsExtra = 0;
WndClass.cbWndExtra = sizeof(WinForm*);
WndClass.hInstance = hInstance;
WndClass.hIcon = LoadIcon(nullptr, IDI_APPLICATION);
WndClass.hCursor = LoadCursor(nullptr, IDC_ARROW);
WndClass.hbrBackground = HBRUSH(COLOR_WINDOW + 1);
WndClass.lpszMenuName = nullptr;
WndClass.lpszClassName = ClassName.c_str();
WndClass.hIconSm = LoadIcon(nullptr, IDI_APPLICATION);
if (!RegisterClassEx(&WndClass))
return;
}
if (Threaded)
{
// can't do that!
return;
}
WindowHandle = CreateWindowEx(0, ClassName.c_str(), WindowName.c_str(), WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, Width, Height, nullptr, nullptr, hInstance, this);
if (!WindowHandle)
return;
ShowWindow(WindowHandle, SW_SHOWDEFAULT);
// don't put message loop here!
}
WinForm::~WinForm()
{
if (Thread.joinable())
{
Thread.join();
}
}
LRESULT __stdcall WinForm::StaticWindowProcedure(HWND window, UINT msg, WPARAM wp, LPARAM lp)
{
WinForm *This;
if (msg == WM_NCCREATE)
{
This = static_cast<WinForm*>(reinterpret_cast<CREATESTRUCT*>(lp)->lpCreateParams);
This->WindowHandle = window;
SetWindowLongPtr(window, 0, reinterpret_cast<LONG_PTR>(This));
}
else
This = reinterpret_cast<WinForm*>(GetWindowLongPtr(window, 0));
if (This)
return This->WindowProcedure(msg, wp, lp);
return DefWindowProc(window, msg, wp, lp);
}
LRESULT WinForm::WindowProcedure(UINT msg, WPARAM wp, LPARAM lp)
{
switch (msg)
{
/*
case WM_PAINT:
break;
*/
case WM_COMMAND:
{
if (lp != 0)
{
if (HIWORD(wp) == BN_CLICKED)
{
HWND ControlWindow = reinterpret_cast<HWND>(lp);
auto it = std::find_if(Controls.begin(), Controls.end(),
[](ControlInfo &info){ return (std::get<2>(info) == ControlWindow); }
);
if (it != Controls.end())
{
auto &tu = *it;
auto actionFunc = std::get<3>(tu);
if (actionFunc) actionFunc(std::get<0>(tu));
return 0;
}
}
}
break;
}
case WM_CREATE:
break;
}
return DefWindowProc(WindowHandle, msg, wp, lp);
}
bool WinForm::AddButton(const std::string &ButtonName, POINT Location, int Width, int Height, ControlActionFunc OnClick)
{
auto it = std::find_if(Controls.begin(), Controls.end(),
[&](ControlInfo &info){ return (std::get<0>(info).compare(ButtonName) == 0); }
);
if (it != Controls.end()) {
return false;
}
std::size_t ID = 1;
auto matchesID = [&](ControlInfo &info){ return (std::get<1>(tu) == ID); };
while (std::find_if(Controls.begin(), Controls.end(), matchesID) != Controls.end()) {
++ID;
}
HWND ButtonHandle = CreateWindowEx(
0, "button", ButtonName.c_str(), WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON, Location.x, Location.y, Width, Height,
WindowHandle, (HMENU)ID, (HINSTANCE)GetWindowLong(WindowHandle, GWL_HINSTANCE), nullptr);
if (!ButtonHandle)
return false;
Controls.push_back(std::make_tuple(ButtonName, ID, ButtonHandle, std::move(OnClick)));
return true;
}
LRESULT MainAppWinForm::WindowProcedure(UINT msg, WPARAM wp, LPARAM lp)
{
if (msg == WM_DESTROY)
{
PostQuitMessage(0);
return 0;
}
return WinForm::WindowProcedure(msg, wp, lp);
}
int WINAPI WinMain(HINSTANCE, HINSTANCE, LPSTR, int)
{
MainAppWinForm Form("Class", "Title", false);
POINT pt = { 50, 50 };
Form.AddButton("NewButton", pt, 80, 50,
[](const std::string &ButtonName){ MessageBox(NULL, ButtonName.c_str(), "button clicked", MB_OK); }
);
MSG msg = { nullptr };
while (GetMessage(&msg, nullptr, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
Also see
Win32: More "object oriented" window message handling system
Best method for storing this pointer for use in WndProc
Upvotes: 3