Прежде всего, я хочу сказать всем, кто собирается сказать «зачем я изобретаю велосипед», я делаю это для развлечения и для своего проекта, над которым я сейчас работаю.
Как видно из приведенного ниже кода, я пытаюсь динамически создать окно и кнопку, но у меня возникают проблемы с добавлением функции к этой кнопке при ее нажатии.
Я знаю, что было бы очень просто войти в оконную процедуру в WM_COMMAND и сделать это там, но здесь отсутствует вся суть того, что я пытаюсь выполнить здесь, так что я могу просто вызвать btn.add(params) в определенное окно, и добавить определенную функцию к этой кнопке, вызвав, скажем, btn.click(function);, без необходимости входить в саму оконную процедуру при добавлении элементов управления.
Как бы я этого добился?
#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);
}
}
Независимо от того, что вы делаете, родительское окно кнопки будет получать уведомление BN_CLICKED через WM_COMMAND при нажатии кнопки. Если родительское окно не обрабатывает сообщение, оно переходит к DefWindowProc() и игнорируется. Итак, родительское окно должно обрабатывать WM_COMMAND. Когда код уведомления - BN_CLICKED, родитель может найти предоставленный HWND в своем списке кнопок и, если он найден, вызвать соответствующую функцию, если она была назначена.
Не могли бы вы привести пример того, о чем говорите?
Ненавижу быть одним из них, но я тот, кто проливает холодную воду на этот проект. Чтобы создать оболочку C++ для Windows API, вам лучше знать сам Windows API по его корням на основе C. Это означает, что наличие конкретных работающих примеров на основе C, а также наличие книг - способ изучить API. В противном случае вы полностью упустите то, что упоминает @RemyLebeau.





Родительское окно кнопки получит уведомление BN_CLICKED через WM_COMMAND при нажатии кнопки. Если родительское окно не обрабатывает сообщение, оно переходит к DefWindowProc() и игнорируется. Итак, родительское окно должно обрабатывать WM_COMMAND. Если код уведомления - BN_CLICKED, просто найдите предоставленную кнопку HWND в своем списке кнопок, и, если она найдена, вызовите соответствующую функцию, если она была назначена.
#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;
}
Также см
Win32: более "объектно-ориентированная" система обработки оконных сообщений.
Лучший способ сохранить этот указатель для использования в WndProc
Я не смог скомпилировать ваш код на VS2017. Я внес некоторые изменения, но есть основная проблема, заключающаяся в том, что при первом вызове This->WindowProcedure(msg, wp, lp);WindowHandle отсутствует, а затем CreateWindowEx выходит из строя.
@BarmakShemirani Я писал код по памяти, компилировать не пробовал. Не удивлюсь, если потребуется дополнительная настройка. Но должно быть ясно, что пытается продемонстрировать код. Однако я исправил проблему с WindowHandle.
для окон верхнего уровня первое сообщение - WM_GETMINMAXINFO (до WM_NCCREATE), поэтому результат будет проигнорирован. если вы хотите обработать это и любые возможные сообщения перед WM_NCCREATE, также сохраните указатель на WinForm в tls, в StaticWindowProcedure - получите его из tls, назначьте окну через SetWindowLongPtr и измените GWLP_WNDPROC на StaticWindowProcedure2. внутри мы уже безоговорочно получили указатель на WinForm через GetWindowLongPtr - никогда не будет 0.
Я вижу, чтобы это сработало, вам придется использовать WndClass.cbWndExtra = sizeof (WinForm *), иначе это не сработает. Теперь это проблема, потому что, когда вы закрываете одно окно с помощью этого метода, каждое ваше окно также закрывается.
@Joshua, вам не обязательно использовать cbWndExtra, GWLP_USERDATA тоже подойдет. GWLP_USERDATA доступен для всех HWND бесплатно, что делает его привлекательным для пользователей. В этом случае я решил использовать cbWndExtra, чтобы сохранить указатель WinForm* в секрете и оставить GWLP_USERDATA свободным для использования вызывающей стороной в своих целях. И нет, закрытие одного окна не влияет на другие окна. Каждому индивидуальному WinForm HWND, выделенному в этом коде, назначен свой собственный индивидуальный указатель WinForm*. Нет связи между несколькими экземплярами WinForm
Когда я создаю еще один экземпляр окна и закрываю его, с правильным уничтожением в WM_DESTROY является PostQuitMessage (0); Каждый экземпляр окна разрушается вместе с тем, что я закрыл отчетливо. Запустите код самостоятельно, и вы увидите, что если вы создадите другое окно и закроете одно из этих двух, они оба закроются.
@Joshua, если вы запускаете несколько экземпляров WinForm, вам не следует безоговорочно вызывать PostQuitMessage() в ответ на WM_DESTROY. Это отправляет сообщение WM_QUIT, которое завершает цикл сообщений в main(). Не вызывайте PostQuitMessage(), пока не будете готовы выйти из приложения, например, когда ваше «главное окно» закрыто. См. Закрытие окна: «В вашем главное окно приложения вы обычно отвечаете на WM_DESTROY, вызывая PostQuitMessage.» Я изменил свой пример, чтобы выделить эту ответственность.
Один из способов сделать это - заставить оконную процедуру сверяться с картой от кнопок к функциям. Когда вы добавляете кнопку, добавьте запись на карту.