Control and events
As in all GUI frameworks, an xtd application is event based.
Events are either generated by the system or by a user action (for example when a property changed).
The event system of xtd is based on the Windows event system. This means that native toolkits have to convert their events to match Windows events. This is not important for the end user of xtd but for the developers of xtd.
Basic Operation
The Operating System uses an event loop to send and receive events.
This is how the Windows event loop works.
In main we will create the different controls necessary for the application and then we will create an event loop that will send and receive events until we quit the application.
For each created control we will assign a method (usually called WndProc
) as well as a fallback method (usually called DefWndProc
) to receive the events related to the control.
The fallback method is necessary to be able to use the default event handling for
Remarks
Some events like WM_COMMAND, WM_NOTIFY, ... are not sent to the control directly but to the parent control.
Here is a Win32 example of a basic application
In the example below, we will just create a window with two buttons and two static texts. We will associate a WndProc only to the window because pressing the button results in a WM_COMMAND event that is sent to the parent control (in this case the window). At the end of the main function we will execute the event loop until the window is closed by the user.
#pragma comment(linker,"\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
#include <string>
#include <Windows.h>
#include <CommCtrl.h>
using namespace std;
using namespace std::literals;
// Handles of controls
HWND window = nullptr;
HWND button1 = nullptr;
HWND button2 = nullptr;
HWND staticText1 = nullptr;
HWND staticText2 = nullptr;
WNDPROC defWndProc = nullptr;
// Window close event
LRESULT OnWindowClose(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
PostQuitMessage(0);
return CallWindowProc(defWndProc, hwnd, message, wParam, lParam);
}
// button1 click event
LRESULT OnButton1Click(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
static auto buttonClicked = 0;
auto result = L"button1 clicked "s + to_wstring(++buttonClicked) + L" times"s;
SendMessage(staticText1, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(result.c_str()));
return CallWindowProc(defWndProc, hwnd, message, wParam, lParam);
}
// button2 click event
LRESULT OnButton2Click(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
static auto buttonClicked = 0;
auto result = L"button2 clicked "s + to_wstring(++buttonClicked) + L" times"s;
SendMessage(staticText2, WM_SETTEXT, 0, reinterpret_cast<LPARAM>(result.c_str()));
return CallWindowProc(defWndProc, hwnd, message, wParam, lParam);
}
// WNndProc associate to the Window
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) {
if (message == WM_CLOSE && hwnd == window) return OnWindowClose(hwnd, message, wParam, lParam);
if (message == WM_COMMAND && HIWORD(wParam) == BN_CLICKED && reinterpret_cast<HWND>(lParam) == button1) return OnButton1Click(hwnd, message, wParam, lParam);
if (message == WM_COMMAND && HIWORD(wParam) == BN_CLICKED && reinterpret_cast<HWND>(lParam) == button2) return OnButton2Click(hwnd, message, wParam, lParam);
return CallWindowProc(defWndProc, hwnd, message, wParam, lParam);
}
// Main entry point
auto main() -> int {
// Controls creation
window = CreateWindowEx(0, WC_DIALOG, L"Button example", WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, CW_USEDEFAULT, 300, 300, nullptr, nullptr, nullptr, nullptr);
button1 = CreateWindowEx(0, WC_BUTTON, L"button1", WS_CHILD | WS_VISIBLE, 50, 50, 75, 25, window, nullptr, nullptr, nullptr);
button2 = CreateWindowEx(0, WC_BUTTON, L"button2", WS_CHILD | WS_VISIBLE, 50, 100, 200, 75, window, nullptr, nullptr, nullptr);
staticText1 = CreateWindowEx(0, WC_STATIC, L"button1 clicked 0 times", WS_CHILD | WS_VISIBLE, 50, 200, 200, 23, window, nullptr, nullptr, nullptr);
staticText2 = CreateWindowEx(0, WC_STATIC, L"button2 clicked 0 times", WS_CHILD | WS_VISIBLE, 50, 230, 200, 23, window, nullptr, nullptr, nullptr);
// WndProc association to the window and assignment of the defWndProc
defWndProc = reinterpret_cast<WNDPROC>(SetWindowLongPtr(window, GWLP_WNDPROC, reinterpret_cast<LONG_PTR>(WndProc)));
// Displays window
ShowWindow(window, SW_SHOW);
// Run Windows loop message until window closed
MSG message = {0};
while (GetMessage(&message, nullptr, 0, 0)) {
TranslateMessage(&message);
DispatchMessage(&message);
}
}
The following code show the same example with xtd
#include <xtd/xtd>
using namespace xtd;
using namespace xtd::forms;
// Main entry point
auto main() -> int {
// Controls creation
form form1;
form1.text("Button example");
button button1;
button1.parent(form1).text("button 1").bounds({50, 50, 75, 25});
button button2;
button2.parent(form1).text("button 2").bounds({50, 100, 200, 75});
label label1;
label1.parent(form1).text("button1 clicked 0 times").bounds({50, 200, 200, 20});
label label2;
label2.parent(form1).text("button2 clicked 0 times").bounds({50, 230, 200, 20});
// button1 click event
button1.click += [&] {
static auto button_clicked = 0;
label1.text(ustring::format("button1 clicked {} times", ++button_clicked));
};
// button2 click event
button2.click += [&] {
static auto button_clicked = 0;
label2.text(ustring::format("button2 clicked {} times", ++button_clicked));
};
// Displays form and run Windows loop message until form closed
application::run(form1);
}
What can we see apart from the verbosity and the c++ lambda functions?
The code is based on the same model. Indeed, even if you can't see it when reading the code, the application::run(const form& main_form) method actually hides the Windows message loop and the display of the form passed to it as a parameter.
The control::click events of xtd are also based on the control::wnd_proc and control::def_wnd_proc] functions which are protected member functions of the control class.
The form::form_closed event automatically closes the application because it is considered to be the main window when passed as a parameter to the application::run(const form& main_form) method.
Remarks
- This behavior can be changed by using the application_context class and the application::run(application_context& context method.
- Although the event model is based on the Windows model, xtd works on Windows, macOS and Linux operating systems. Indeed the role of the native abstraction library allows to convert the toolkit event model into the Windows event model.
How to respond to an event
Two possible methods:
- Respond to an event by associating an event_handler. This method is the most ripid and usually the default choice.
- By overriding the protected method associated with the event in a derived class. This method is recommended when creating a custom control.
event_handler
Let's take a simple example of a click on a button.
The following code shows how to associate an event_handler to the control::click event.
#include <xtd/xtd>
using namespace xtd::forms;
class form1 : public form {
public:
form1() {
button1.parent(*this);
button1.text("Button 1");
button1.location({10, 10});
button1.click += [&] {
message_box::show("Button clicked!");
};
}
private:
button button1;
};
auto main() -> int {
application::run(form1 {});
}
In this example we use a lambda function, but we could as well have associated an external function to the class form1
or an internal method. We could also have specified the arguments of the click event if we needed them.
The following example uses the form1::on_button_click
member function with event click arguments. Even if we don't need the arguments it's just for the example.
#include <xtd/xtd>
using namespace xtd;
using namespace xtd::forms;
class form1 : public form {
public:
form1() {
button1.parent(*this);
button1.text("Button 1");
button1.location({10, 10});
button1.click += {*this, &form1::on_button1_click};
}
private:
void on_button1_click(object& sender, const event_args& e) {
message_box::show("Button clicked!");
}
button button1;
};
auto main() -> int {
application::run(form1 {});
}
See delegates and events for more information.
Overriding the protected method associated with the event
Let's take the previous example but overload the on_click method associated to the click event. To do this we need to create a class derived from the button class.
#include <xtd/xtd>
using namespace xtd;
using namespace xtd::forms;
class my_button : public button {
public:
my_button() = default;
protected:
void on_click(const event_args& e) override {
button::on_click(e);
message_box::show("Button clicked!");
}
};
class form1 : public form {
public:
form1() {
button1.parent(*this);
button1.text("Button 1");
button1.location({10, 10});
}
private:
my_button button1;
};
auto main() -> int {
application::run(form1 {});
}
Remarks
- Each event has an associated protected method that starts with
on_
followed by the event name. - When you override a method, don't forget to call the method of the inherited class.
- Each control has its own events or not and also has those of the inherited class.
See also