NativeControl¶
Header: winui/ui/hosting/NativeControl.hpp. Spec: winui/ui/hosting/NativeControl.hpp.
An Element that hosts a real platform child window (an HWND on Windows) of any window class, the escape hatch for OS controls the framework does not draw itself.
The window is created lazily on the first sync_native(), moved whenever the arranged bounds change, clipped to what scrolling ancestors leave in view, hidden when it stops being shown, and destroyed with the element. It paints itself, so the theme doesn't apply to it, sits above everything the framework draws, and is outside the Tab-focus ring. A derived class answers the window's parent notifications in handle_native_notification() and configures it once in on_native_created(). The NativeHost it was last synced with must outlive it. Windows only: without a native host (a headless window, another platform) the element keeps its size and draws nothing.
Example:
// Any window class, by name, answering its WM_NOTIFY (Windows only).
class MonthCalendar final : public ui::NativeControl
{
public:
MonthCalendar()
: ui::NativeControl(ui::NativeControlDesc{.class_name = MONTHCAL_CLASSA}, ui::Size{230, 160})
{
}
bool handle_native_notification(ui::NativeNotification& n) override
{
const auto* header = reinterpret_cast<const NMHDR*>(n.lparam);
if (n.message != WM_NOTIFY || header->code != MCN_SELECT)
return false;
selected = true; // read the date with MCM_GETCURSEL on native_handle()
return true;
}
bool selected = false;
protected:
void on_native_created(ui::NativeHandle handle) override
{
RECT r{};
MonthCal_GetMinReqRect(handle, &r);
set_native_size(ui::Size{float(r.right - r.left), float(r.bottom - r.top)});
}
};
window.set_content(ui::VerticalStack{
.spacing = 8,
.content = { ui::Text{.text = "Pick a date"}, std::make_unique<MonthCalendar>() },
});
ui::Native¶
A real Win32 child window hosted in the tree. Builds a ui::NativeControl.
Use .control to reach the built control and, through NativeControl::native_handle(), its HWND. The window is created when the tree is attached to a real window host.
Example:
ui::Control<ui::Native> edit;
window.set_content(ui::Native{
.desc = { .class_name = "EDIT", .initial_text = "Hello", .style = 0x0080 /* ES_AUTOHSCROLL */ },
.native_size = { 200.0f, 24.0f },
.control = edit,
});
Every field, in the order a designated initializer names them:
ui::Native{
.desc, .native_size, .style, .margin, .width, .height, .min_width, .min_height, .max_width,
.max_height, .horizontal_alignment, .vertical_alignment, .visible, .tool_tip,
.accessibility, .on_pointer_entered, .on_pointer_exited, .on_got_focus, .on_lost_focus,
.on_key_down, .on_wheel, .control
}
Fields, in declaration order (designators must follow it):
| Field | Type | Default | |
|---|---|---|---|
desc |
ui::NativeControlDesc |
The window class, initial text and WS_* / WS_EX_* style bits to create it with. | |
native_size |
ui::Binding<ui::Size> |
The fixed size the control reports (NativeControl::set_native_size()). Native controls take no part in text measurement, so set their size here. | |
on_pointer_entered |
ui::Action |
The pointer moved onto the control. | |
on_pointer_exited |
ui::Action |
The pointer left the control. | |
on_got_focus |
ui::Action |
Keyboard focus arrived on the control, or on anything inside it, from outside it. A control with nothing focusable in it never gets focus, and never raises this. | |
on_lost_focus |
ui::Action |
Keyboard focus left the control and everything inside it. | |
on_key_down |
ui::Callback<ui::Handling(const ui::KeyEvent&)> |
A key pressed while focus is on the control or inside it, before the control's own handling; ui::Handled stops it there (Element::on_key_down()). | |
on_wheel |
ui::Callback<ui::Handling(const ui::PointerEvent&)> |
A wheel turn over the control, before the control scrolls; ui::Handled stops it there (Element::on_wheel()). | |
control |
ui::ControlOut<ui::Native> |
Out: the handle that receives the built control, the way to its HWND. |
Plus the common fields style, margin, width, height, min_width, min_height, max_width, max_height, horizontal_alignment, vertical_alignment, visible, tool_tip, accessibility every spec carries (conventions).
build¶
Consumes the spec and returns the built ui::NativeControl; the caller owns it.
ui::NativeControl¶
Derives from ui::Element.
An Element that hosts a real platform child window (an HWND on Windows) of any window class, the escape hatch for OS controls the framework does not draw itself.
The window is created lazily on the first sync_native(), moved whenever the arranged bounds change, clipped to what scrolling ancestors leave in view, hidden when it stops being shown, and destroyed with the element. It paints itself, so the theme doesn't apply to it, sits above everything the framework draws, and is outside the Tab-focus ring. A derived class answers the window's parent notifications in handle_native_notification() and configures it once in on_native_created(). The NativeHost it was last synced with must outlive it. Windows only: without a native host (a headless window, another platform) the element keeps its size and draws nothing.
Example:
// Any window class, by name, answering its WM_NOTIFY (Windows only).
class MonthCalendar final : public ui::NativeControl
{
public:
MonthCalendar()
: ui::NativeControl(ui::NativeControlDesc{.class_name = MONTHCAL_CLASSA}, ui::Size{230, 160})
{
}
bool handle_native_notification(ui::NativeNotification& n) override
{
const auto* header = reinterpret_cast<const NMHDR*>(n.lparam);
if (n.message != WM_NOTIFY || header->code != MCN_SELECT)
return false;
selected = true; // read the date with MCM_GETCURSEL on native_handle()
return true;
}
bool selected = false;
protected:
void on_native_created(ui::NativeHandle handle) override
{
RECT r{};
MonthCal_GetMinReqRect(handle, &r);
set_native_size(ui::Size{float(r.right - r.left), float(r.bottom - r.top)});
}
};
window.set_content(ui::VerticalStack{
.spacing = 8,
.content = { ui::Text{.text = "Pick a date"}, std::make_unique<MonthCalendar>() },
});
NativeControl¶
desc describes the window class/style to create; size is the fixed desired size measure() reports. Native controls take no part in text measurement, so a caller sizes them explicitly.
~NativeControl¶
Destroys the native window through the host it was last synced with, if it was created.
NativeControl¶
operator=¶
sync_native¶
Called by Window after layout and input. Creates the native window on the first call and moves it whenever bounds() changed since the last sync.
on_native_content_changed¶
Called when the hosted control reports that its content changed. On Windows that is an EN_CHANGE notification, as a RichEdit sends. The framework can't see such an edit otherwise, since the text lives inside the native control and the keystrokes go straight to the child HWND.
handle_native_notification¶
A message the hosted window sent its parent about itself (render/NativeHost.hpp's NativeNotification): WM_COMMAND, WM_NOTIFY, WM_CTLCOLOR* and a trackbar's or scroll bar's WM_HSCROLL/WM_VSCROLL on Windows. Return true, with notification.result set, to answer it; false lets the host's window procedure handle it. This is the parent half of hosting any window class: a month calendar's MCN_SELECT, a date picker's DTN_DATETIMECHANGE or the colors an EDIT asks for, none of which the element could see otherwise. EN_CHANGE still reaches on_native_content_changed() when this declines it.
native_size¶
The fixed size measure() reports. Changing it measures the control again, for a control that learns the size it wants from the native window once it exists (a month calendar's MCM_GETMINREQRECT, read in on_native_created()).
set_native_size¶
Sets the desired size (DIPs) and marks layout dirty when it changed.
visible_rect¶
The part of the control its scrolling ancestors leave in view, in arranged coordinates: empty when it is Collapsed, under a Collapsed ancestor or scrolled wholly out of view. What sync_native() shows and clips the native window to.
automation_is_native_host¶
True: the framework's automation tree leaves the control out, because the child HWND provides its own accessibility once created.
native_handle¶
The raw platform handle (HWND on Windows), for a derived control to send it control-specific messages. nullptr before the first sync_native() call, and after the window that held it was closed.
on_native_created¶
Called once, from the sync_native() that created the real window, after it has been registered with the host and moved to its first bounds. handle is native_handle()'s value and is never null here. Not called if creation failed.
This is where a derived control sends the one-time messages its window class needs to be configured with. A NativeControl's window is created lazily on the first sync, so the handle does not exist yet in the constructor.
It is also the place to read a size the native control wants (a month calendar's MCM_GETMINREQRECT) and hand it to set_native_size().
measure_override¶
Always native_size(), whatever space is available.
render_override¶
Emits nothing: the native window paints itself on top of the framework's surface.
Tests¶
tests/test_native_control.cpp