2744 lines
64 KiB
C++
Raw Normal View History

2022-05-21 19:58:09 +02:00
#include "stdafx.h"
#include "Control.h"
#include "ContainerControl.h"
// System::Drawing*
#include "DrawingBase.h"
#include "BufferedGraphics.h"
namespace Forms
{
// Custom message index cache
uint32_t Control::WM_MOUSEENTER = 0;
uint32_t Control::WM_INVOKEUI = 0;
// GDI+ palette for rendering
HPALETTE Control::HalftonePalette = nullptr;
Control::Control()
: _Handle(nullptr), _WndProcBase(NULL), _RTTI(ControlTypes::Control), _Parent(nullptr), _MinimumHeight(0), _MinimumWidth(0), _MaximumHeight(0), _MaximumWidth(0), _Anchor(AnchorStyles::Top | AnchorStyles::Left), _AnchorDeltas(), _Font(nullptr), _BackColor(Drawing::GetSystemColor(Drawing::SystemColors::Control)), _ForeColor(Drawing::GetSystemColor(Drawing::SystemColors::ControlText)), _BackColorBrush(0), _TabIndex(0),\
_ControlStates((ControlStates)0), _ControlStyles((ControlStyles)0), _ClientWidth(0), _ClientHeight(0), _Width(0), _Height(0), _X(0), _Y(0), _RequiredScalingEnabled(false), _RequiredScaling(BoundsSpecified::All), _LayoutSuspendCount(0)
{
if (Control::WM_INVOKEUI == 0 || Control::WM_MOUSEENTER == 0)
{
Control::WM_MOUSEENTER = RegisterWindowMessageA("KoreMouseEnter");
Control::WM_INVOKEUI = RegisterWindowMessageA("KoreUIInvoke");
}
SetState(ControlStates::StateVisible |
ControlStates::StateEnabled |
ControlStates::StateTabstop |
ControlStates::StateCausesValidation, true);
SetStyle(ControlStyles::AllPaintingInWmPaint |
ControlStyles::UserPaint |
ControlStyles::StandardClick |
ControlStyles::StandardDoubleClick |
ControlStyles::UseTextForAccessibility |
ControlStyles::Selectable, true);
}
Control::~Control()
{
this->Dispose();
}
void Control::Show()
{
SetVisible(true);
}
void Control::Hide()
{
SetVisible(false);
}
void Control::WndProc(Message& Msg)
{
//
// This is the base processor for every single control, we support all windows control events here
//
switch (Msg.Msg)
{
case WM_CLOSE:
WmClose(Msg);
break;
case WM_MOVE:
WmMove(Msg);
break;
case WM_WINDOWPOSCHANGED:
WmWindowPosChanged(Msg);
break;
case WM_PARENTNOTIFY:
WmParentNotify(Msg);
break;
case WM_ERASEBKGND:
WmEraseBkgnd(Msg);
break;
case WM_COMMAND:
WmCommand(Msg);
break;
case WM_SHOWWINDOW:
WmShowWindow(Msg);
break;
case WM_PAINT:
if (GetStyle(ControlStyles::UserPaint))
WmPaint(Msg);
else
DefWndProc(Msg);
break;
case WM_LBUTTONDBLCLK:
WmMouseDown(Msg, MouseButtons::Left, 2);
if (GetStyle(ControlStyles::StandardDoubleClick))
SetState(ControlStates::StateDoubleClickFired, true);
break;
case WM_LBUTTONDOWN:
WmMouseDown(Msg, MouseButtons::Left, 1);
break;
case WM_LBUTTONUP:
WmMouseUp(Msg, MouseButtons::Left, 1);
break;
case WM_RBUTTONDBLCLK:
WmMouseDown(Msg, MouseButtons::Right, 2);
if (GetStyle(ControlStyles::StandardDoubleClick))
SetState(ControlStates::StateDoubleClickFired, true);
break;
case WM_RBUTTONDOWN:
WmMouseDown(Msg, MouseButtons::Right, 1);
break;
case WM_RBUTTONUP:
WmMouseUp(Msg, MouseButtons::Right, 1);
break;
case WM_MBUTTONDBLCLK:
WmMouseDown(Msg, MouseButtons::Middle, 2);
if (GetStyle(ControlStyles::StandardDoubleClick))
SetState(ControlStates::StateDoubleClickFired, true);
break;
case WM_MBUTTONDOWN:
WmMouseDown(Msg, MouseButtons::Middle, 1);
break;
case WM_MBUTTONUP:
WmMouseUp(Msg, MouseButtons::Middle, 1);
break;
case WM_SETCURSOR:
WmSetCursor(Msg);
break;
case WM_MOUSEMOVE:
WmMouseMove(Msg);
break;
case WM_MOUSELEAVE:
WmMouseLeave(Msg);
break;
case WM_MOUSEHOVER:
WmMouseHover(Msg);
break;
case WM_CHAR:
case WM_KEYDOWN:
case WM_SYSKEYDOWN:
case WM_KEYUP:
case WM_SYSKEYUP:
WmKeyChar(Msg);
break;
case WM_QUERYNEWPALETTE:
WmQueryNewPalette(Msg);
break;
case WM_NOTIFY:
WmNotify(Msg);
break;
case WM_NOTIFYFORMAT:
WmNotifyFormat(Msg);
break;
case WM_CAPTURECHANGED:
WmCaptureChanged(Msg);
break;
case WM_KILLFOCUS:
WmKillFocus(Msg);
break;
case WM_SETFOCUS:
WmSetFocus(Msg);
break;
case WM_MOUSEWHEEL:
// TrackMouseEvent doesn't handle MouseWheel properly
ResetMouseEventArgs();
WmMouseWheel(Msg);
break;
// Handle all CTLCOLOR messages and reflected ones
case WM_CTLCOLORBTN:
case WM_CTLCOLORDLG:
case WM_CTLCOLORMSGBOX:
case WM_CTLCOLORSCROLLBAR:
case WM_CTLCOLOREDIT:
case WM_CTLCOLORLISTBOX:
case WM_CTLCOLORSTATIC:
case WM_REFLECT + WM_CTLCOLORBTN:
case WM_REFLECT + WM_CTLCOLORDLG:
case WM_REFLECT + WM_CTLCOLORMSGBOX:
case WM_REFLECT + WM_CTLCOLORSCROLLBAR:
case WM_REFLECT + WM_CTLCOLOREDIT:
case WM_REFLECT + WM_CTLCOLORLISTBOX:
case WM_REFLECT + WM_CTLCOLORSTATIC:
WmCtlColorControl(Msg);
break;
default:
//
// Handle the events which aren't constant...
//
if (Msg.Msg == WM_MOUSEENTER)
{
WmMouseEnter(Msg);
break;
}
else if (Msg.Msg == WM_INVOKEUI)
{
WmInvokeOnUIThread(Msg);
break;
}
//dprintf("WinDBG: Unhandled WndProc: %d {0x%x} hWnd 0x%llx class 0x%llx\n", Msg, Msg, (intptr_t)hWnd, (uintptr_t)this);
//
// Default logic will proxy off the message to the proper base WndProc
//
DefWndProc(Msg);
break;
}
}
void Control::CreateControl(Control* Parent)
{
// Prevent duplicate calls to CreateControl()
if (GetState(ControlStates::StateCreated))
return;
this->_Parent = Parent;
// Get control params and register the class
auto Cp = this->GetCreateParams();
auto NeedsSubclass = false;
auto CName = Control::RegisterWndClass((const char*)Cp.ClassName, Cp.ClassStyle, NeedsSubclass);
// Create the control
this->_Handle = CreateWindowExA(Cp.ExStyle, (const char*)CName, (const char*)Cp.Caption, Cp.Style, Cp.X, Cp.Y, Cp.Width, Cp.Height, (this->_Parent) ? this->_Parent->GetHandle() : NULL, NULL, GetModuleHandle(NULL), (LPVOID)this);
// Ensure we have a class pointer reference
this->_WndProcBase = (NeedsSubclass) ? SetWindowLongPtrA(this->_Handle, GWLP_WNDPROC, (intptr_t)&Control::InternalWndProc) : NULL;
SetWindowLongPtrA(this->_Handle, GWLP_USERDATA, (intptr_t)this);
// Setup the default font if none was previously set
if (this->_Font == nullptr)
this->_Font = std::make_unique<Drawing::Font>(this->_Handle, (HFONT)GetStockObject(DEFAULT_GUI_FONT));
// Notify the window of the font selection
SendMessageA(this->_Handle, WM_SETFONT, (WPARAM)this->_Font->GetFontHandle(), NULL);
// Setup the state
SetState(ControlStates::StateCreated, true);
// Notify the handle was made, and is setup
OnHandleCreated();
// Update bounds
UpdateBounds();
}
bool Control::Focus()
{
if (CanFocus())
SetFocus(this->_Handle);
return Focused();
}
bool Control::Focused()
{
return (this->_Handle != nullptr) && (GetFocus() == this->_Handle);
}
bool Control::Enabled()
{
if (!GetState(ControlStates::StateEnabled))
return false;
else if (this->_Parent == nullptr)
return true;
return this->_Parent->Enabled();
}
void Control::SetEnabled(bool Value)
{
bool oValue = Enabled();
SetState(ControlStates::StateEnabled, Value);
if (oValue != Value)
{
if (!Value)
SelectNextIfFocused();
OnEnabledChanged();
}
}
bool Control::AllowDrop()
{
return GetState(ControlStates::StateAllowDrop);
}
void Control::SetAllowDrop(bool Value)
{
if (GetState(ControlStates::StateAllowDrop) != Value)
{
SetState(ControlStates::StateAllowDrop, Value);
SetAcceptDrops(Value);
}
}
bool Control::Visible()
{
return GetVisibleCore();
}
void Control::SetVisible(bool Value)
{
SetVisibleCore(Value);
}
bool Control::CanFocus()
{
if (this->_Handle == nullptr)
return false;
return (IsWindowVisible(this->_Handle)) && (IsWindowEnabled(this->_Handle));
}
bool Control::CanSelect()
{
if (!GetStyle(ControlStyles::Selectable))
return false;
for (Control* Ctl = this; Ctl != nullptr; Ctl = Ctl->_Parent)
{
if (!Ctl->Enabled() || !Ctl->Visible())
return false;
}
return true;
}
bool Control::ContainsFocus()
{
if (!GetState(ControlStates::StateCreated))
return false;
auto FocusHwnd = GetFocus();
if (FocusHwnd == NULL)
return false;
if (FocusHwnd == this->_Handle)
return true;
if (IsChild(this->_Handle, FocusHwnd))
return true;
return false;
}
void Control::SuspendLayout()
{
this->_LayoutSuspendCount++;
if (this->_LayoutSuspendCount == 1)
{
// Not needed right now for dpi scaling...
// TODO: OnLayoutSuspended();
}
}
void Control::ResumeLayout(bool PerformLayout)
{
bool PerformedLayout = false;
if (_LayoutSuspendCount > 0)
{
if (_LayoutSuspendCount == 1)
{
_LayoutSuspendCount++;
try
{
OnLayoutResuming(PerformLayout);
_LayoutSuspendCount--;
}
catch(...)
{
_LayoutSuspendCount--;
}
}
_LayoutSuspendCount--;
if (_LayoutSuspendCount == 0 && GetState(ControlStates::StateLayoutDeferred) && PerformLayout)
{
this->PerformLayout();
PerformedLayout = true;
}
}
}
bool Control::DoubleBuffered()
{
return GetStyle(ControlStyles::OptimizedDoubleBuffer);
}
void Control::SetDoubleBuffered(bool Value)
{
SetStyle(ControlStyles::OptimizedDoubleBuffer, Value);
}
bool Control::Capture()
{
return GetState(ControlStates::StateCreated) && GetCapture() == this->_Handle;
}
void Control::SetCapture(bool Value)
{
if (Capture() == Value)
return;
if (Value)
::SetCapture(this->_Handle);
else
::ReleaseCapture();
}
AnchorStyles Control::Anchor()
{
return _Anchor;
}
void Control::SetAnchor(AnchorStyles Value)
{
// Get the initial rectangle...
this->UpdateInitialPos();
// Apply the anchor value...
this->_Anchor = Value;
// Ensure delta is set...
UpdateDeltas();
// If we are a container control, enumerate children, call SetAnchor(Anchor());
if (GetStyle(ControlStyles::ContainerControl) && this->_Controls != nullptr)
{
// Store the counts on the stack
uint32_t ControlCount = this->_Controls->Count();
auto& ControlList = *this->_Controls.get();
for (uint32_t i = 0; i < ControlCount; i++)
{
auto Child = ControlList[i];
Child->SetAnchor(Child->_Anchor);
}
}
}
uint32_t Control::TabIndex()
{
return this->_TabIndex;
}
void Control::SetTabIndex(uint32_t Value)
{
if (this->_TabIndex != Value)
{
this->_TabIndex = Value;
// TODO: OnTabIndexChanged();
}
}
Drawing::Point Control::Location()
{
return Drawing::Point(this->_X, this->_Y);
}
void Control::SetLocation(Drawing::Point Value)
{
SetBounds(Value.X, Value.Y, this->_Width, this->_Height);
UpdateInitialPos();
}
Drawing::Size Control::Size()
{
return Drawing::Size(this->_Width, this->_Height);
}
void Control::SetSize(Drawing::Size Value)
{
SetBounds(this->_X, this->_Y, Value.Width, Value.Height);
UpdateInitialPos();
}
Drawing::Size Control::MaximumSize()
{
return Drawing::Size(this->_MaximumWidth, this->_MaximumHeight);
}
void Control::SetMaximumSize(Drawing::Size Value)
{
this->_MaximumWidth = Value.Width;
this->_MaximumHeight = Value.Height;
// TODO: OnMaximumSizeChanged(); / Trigger reflow...
}
Drawing::Size Control::MinimumSize()
{
return Drawing::Size(this->_MinimumWidth, this->_MinimumHeight);
}
void Control::SetMinimumSize(Drawing::Size Value)
{
this->_MinimumWidth = Value.Width;
this->_MinimumHeight = Value.Height;
// TODO: OnMinimumSizeChanged();
}
Drawing::Color Control::BackColor()
{
return this->_BackColor;
}
void Control::SetBackColor(Drawing::Color Color)
{
this->_BackColor = Color;
OnBackColorChanged();
}
Drawing::Color Control::ForeColor()
{
return this->_ForeColor;
}
void Control::SetForeColor(Drawing::Color Color)
{
this->_ForeColor = Color;
OnForeColorChanged();
}
Drawing::Font* Control::GetFont()
{
if (this->_Font != nullptr)
return this->_Font.get();
// Set to system wide default fault
this->_Font = std::make_unique<Drawing::Font>(this->_Handle, (HFONT)GetStockObject(DEFAULT_GUI_FONT));
return this->_Font.get();
}
void Control::SetFont(Drawing::Font* Font)
{
this->_Font.reset(Font);
OnFontChanged();
}
Control* Control::Parent()
{
return this->_Parent;
}
void Control::SetParent(Control* Value)
{
_Parent = Value;
if (_Parent && _Parent->GetState(ControlStates::StateCreated))
::SetParent(this->_Handle, _Parent->_Handle);
}
Drawing::Rectangle Control::ClientRectangle()
{
return Drawing::Rectangle(0, 0, this->_ClientWidth, this->_ClientHeight);
}
Drawing::Size Control::ClientSize()
{
return Drawing::Size(this->_ClientWidth, this->_ClientHeight);
}
void Control::SetClientSize(Drawing::Size Value)
{
SetSize(SizeFromClientSize(Value.Width, Value.Height));
_ClientWidth = Value.Width;
_ClientHeight = Value.Height;
OnClientSizeChanged();
}
Drawing::Point Control::PointToScreen(const Drawing::Point& Point)
{
POINT Pt;
Pt.x = Point.X;
Pt.y = Point.Y;
MapWindowPoints(this->_Handle, NULL, &Pt, 1);
return Drawing::Point(Pt.x, Pt.y);
}
Drawing::Point Control::PointToClient(const Drawing::Point& Point)
{
POINT Pt;
Pt.x = Point.X;
Pt.y = Point.Y;
MapWindowPoints(NULL, this->_Handle, &Pt, 1);
return Drawing::Point(Pt.x, Pt.y);
}
Drawing::Rectangle Control::RectangleToScreen(const Drawing::Rectangle& Rect)
{
RECT Rc;
Rc.left = Rect.X;
Rc.top = Rect.Y;
Rc.right = (Rect.X + Rect.Width);
Rc.bottom = (Rect.Y + Rect.Height);
MapWindowPoints(this->_Handle, NULL, (LPPOINT)&Rc, 2);
return Drawing::Rectangle(Rc.left, Rc.top, (Rc.right - Rc.left), (Rc.bottom - Rc.top));
}
Drawing::Rectangle Control::RectangleToClient(const Drawing::Rectangle & Rect)
{
RECT Rc;
Rc.left = Rect.X;
Rc.top = Rect.Y;
Rc.right = (Rect.X + Rect.Width);
Rc.bottom = (Rect.Y + Rect.Height);
MapWindowPoints(NULL, this->_Handle, (LPPOINT)&Rc, 2);
return Drawing::Rectangle(Rc.left, Rc.top, (Rc.right - Rc.left), (Rc.bottom - Rc.top));
}
String Control::Text()
2022-05-21 19:58:09 +02:00
{
return this->WindowText();
}
void Control::SetText(const String& Value)
2022-05-21 19:58:09 +02:00
{
this->SetWindowText(Value);
OnTextChanged();
}
void Control::BringToFront()
{
if (_Parent != nullptr)
_Parent->_Controls->SetChildIndex(this, 0);
else if (GetState(ControlStates::StateCreated) && GetState(ControlStates::StateTopLevel) && IsWindowEnabled(this->_Handle))
SetWindowPos(this->_Handle, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
void Control::SendToBack()
{
if (_Parent != nullptr)
_Parent->_Controls->SetChildIndex(this, -1);
else if (GetState(ControlStates::StateCreated) && GetState(ControlStates::StateTopLevel))
SetWindowPos(this->_Handle, HWND_BOTTOM, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
}
bool Control::GetStyle(ControlStyles Flag)
{
return ((int)this->_ControlStyles & (int)Flag) == (int)Flag;
}
void Control::SetStyle(ControlStyles Flags, bool Value)
{
this->_ControlStyles = Value ? (ControlStyles)((int)this->_ControlStyles | (int)Flags) : (ControlStyles)((int)this->_ControlStyles & ~(int)Flags);
}
bool Control::GetState(ControlStates Flag)
{
return ((int)this->_ControlStates & (int)Flag) == (int)Flag;
}
void Control::SetState(ControlStates Flags, bool Value)
{
this->_ControlStates = Value ? (ControlStates)((int)this->_ControlStates | (int)Flags) : (ControlStates)((int)this->_ControlStates & ~(int)Flags);
}
void Control::Invalidate(bool InvalidateChildren)
{
if (GetState(ControlStates::StateCreated))
{
if (InvalidateChildren)
RedrawWindow(this->_Handle, nullptr, nullptr, RDW_INVALIDATE | RDW_ERASE | RDW_ALLCHILDREN);
else
InvalidateRect(this->_Handle, nullptr, !GetStyle(ControlStyles::Opaque));
OnInvalidated(std::make_unique<InvalidateEventArgs>(this->ClientRectangle()));
}
}
void Control::Invoke(Action Method)
{
SendMessageA(this->_Handle, WM_INVOKEUI, NULL, (LPARAM)Method);
}
bool Control::InvokeRequired()
{
DWORD Pid;
DWORD HwndThread = GetWindowThreadProcessId(this->_Handle, &Pid);
DWORD CurrentThread = GetCurrentThreadId();
return (HwndThread != CurrentThread);
}
void Control::Update()
{
UpdateWindow(this->_Handle);
}
void Control::Refresh()
{
Invalidate(true);
Update();
}
HWND Control::GetHandle()
{
return this->_Handle;
}
ControlTypes Control::GetType()
{
return this->_RTTI;
}
Control* Control::FindForm()
{
auto Cur = this;
while (Cur != nullptr && (Cur->_RTTI != ControlTypes::Form))
Cur = Cur->_Parent;
return Cur;
}
Control* Control::GetContainerControl()
{
Control* C = this;
while (C != nullptr && (!IsFocusManagingContainerControl(C)))
C = C->_Parent;
return C;
}
Control* Control::GetNextControl(Control* Ctrl, bool Forward)
{
if (!Contains(Ctrl))
Ctrl = this;
if (Forward)
{
if (this->_Controls != nullptr && this->_Controls->Count() > 0 && (Ctrl == this || !IsFocusManagingContainerControl(Ctrl)))
{
auto Found = Ctrl->GetFirstChildcontrolInTabOrder(true);
if (Found != nullptr)
return Found;
}
while (Ctrl != this)
{
uint32_t TargetIndex = Ctrl->_TabIndex;
bool HitCtrl = false;
Control* Found = nullptr;
Control* P = Ctrl->_Parent;
uint32_t ParentControlCount = 0;
auto& ParentControls = *P->_Controls.get();
if (P->_Controls != nullptr)
ParentControlCount = ParentControls.Count();
for (uint32_t i = 0; i < ParentControlCount; i++)
{
if (ParentControls[i] != Ctrl)
{
if (ParentControls[i]->_TabIndex >= TargetIndex)
{
if (Found == nullptr || Found->_TabIndex > ParentControls[i]->_TabIndex)
{
if (ParentControls[i]->_TabIndex != TargetIndex || HitCtrl)
Found = ParentControls[i];
}
}
}
else
{
HitCtrl = true;
}
}
if (Found != nullptr)
return Found;
Ctrl = Ctrl->_Parent;
}
}
else
{
if (Ctrl != this)
{
uint32_t TargetIndex = Ctrl->_TabIndex;
bool HitCtrl = false;
Control* Found = nullptr;
Control* P = Ctrl->_Parent;
uint32_t ParentControlCount = 0;
auto& ParentControls = *P->_Controls.get();
if (P->_Controls != nullptr)
ParentControlCount = ParentControls.Count();
for (int32_t i = (int32_t)ParentControlCount - 1; i >= 0; i--)
{
if (ParentControls[i] != Ctrl)
{
if (ParentControls[i]->_TabIndex <= TargetIndex)
{
if (Found == nullptr || Found->_TabIndex < ParentControls[i]->_TabIndex)
{
if (ParentControls[i]->_TabIndex != TargetIndex || HitCtrl)
Found = ParentControls[i];
}
}
}
else
{
HitCtrl = true;
}
}
if (Found != nullptr)
Ctrl = Found;
else
{
if (P == this)
return nullptr;
else
return P;
}
}
auto CtrlControls = this->_Controls.get();
while (CtrlControls != nullptr && CtrlControls->Count() > 0 && (Ctrl == this || !IsFocusManagingContainerControl(Ctrl)))
{
auto Found = Ctrl->GetFirstChildcontrolInTabOrder(false);
if (Found != nullptr)
{
Ctrl = Found;
CtrlControls = Ctrl->_Controls.get();
}
else
{
break;
}
}
}
return (Ctrl == this) ? nullptr : Ctrl;
}
bool Control::Contains(Control* Ctrl)
{
while (Ctrl != nullptr)
{
Ctrl = Ctrl->_Parent;
if (Ctrl == nullptr)
return false;
if (Ctrl == this)
return true;
}
return false;
}
const std::unique_ptr<ControlCollection>& Control::Controls()
{
return this->_Controls;
}
void Control::Select()
{
Select(false, false);
}
bool Control::SelectNextControl(Control* Ctrl, bool Forward, bool TabStopOnly, bool Nested, bool Wrap)
{
auto NextCtrl = this->GetNextSelectableControl(Ctrl, Forward, TabStopOnly, Nested, Wrap);
if (NextCtrl != nullptr)
{
NextCtrl->Select(true, Forward);
return true;
}
return false;
}
void Control::UpdateZOrder()
{
if (_Parent != nullptr)
_Parent->UpdateChildZOrder(this);
}
void Control::DefWndProc(Message& Msg)
{
// We must properly proxy off the WndProc if we have a base...
if (this->_WndProcBase != NULL)
Msg.Result = CallWindowProcA((WNDPROC)this->_WndProcBase, (HWND)Msg.HWnd, Msg.Msg, Msg.WParam, Msg.LParam);
else
Msg.Result = DefWindowProcA((HWND)Msg.HWnd, Msg.Msg, Msg.WParam, Msg.LParam);
}
void Control::OnPaint(const std::unique_ptr<PaintEventArgs>& EventArgs)
{
Paint.RaiseEvent(EventArgs, this);
}
void Control::OnPaintBackground(const std::unique_ptr<PaintEventArgs>& EventArgs)
{
// Fill the back color of this control
if (this->BackColor().GetA() == 255)
{
auto DC = (EventArgs->NativeHandle() != nullptr) ? EventArgs->NativeHandle() : EventArgs->Graphics->GetHDC();
RECT Rc{ EventArgs->ClipRectangle.GetLeft(), EventArgs->ClipRectangle.GetTop(), EventArgs->ClipRectangle.GetRight(), EventArgs->ClipRectangle.GetBottom() };
FillRect(DC, &Rc, (HBRUSH)this->BackColorBrush());
if (EventArgs->NativeHandle() == nullptr)
EventArgs->Graphics->ReleaseHDC(DC);
}
else
{
auto t = Drawing::SolidBrush(this->BackColor());
EventArgs->Graphics->FillRectangle(&t, EventArgs->ClipRectangle);
}
}
void Control::OnEnabledChanged()
{
if (this->_Handle != nullptr)
{
EnableWindow(this->_Handle, this->Enabled());
// User-paint controls should repaint when their enabled state changes
if (GetStyle(ControlStyles::UserPaint))
{
Invalidate();
Update();
}
}
EnabledChanged.RaiseEvent(this);
}
void Control::OnClick()
{
Click.RaiseEvent(this);
}
void Control::OnDoubleClick()
{
DoubleClick.RaiseEvent(this);
}
void Control::OnMouseClick(const std::unique_ptr<MouseEventArgs>& EventArgs)
{
MouseClick.RaiseEvent(EventArgs, this);
}
void Control::OnMouseDoubleClick(const std::unique_ptr<MouseEventArgs>& EventArgs)
{
MouseDoubleClick.RaiseEvent(EventArgs, this);
}
void Control::OnMouseUp(const std::unique_ptr<MouseEventArgs>& EventArgs)
{
MouseUp.RaiseEvent(EventArgs, this);
}
void Control::OnMouseDown(const std::unique_ptr<MouseEventArgs>& EventArgs)
{
MouseDown.RaiseEvent(EventArgs, this);
}
void Control::OnMouseMove(const std::unique_ptr<MouseEventArgs>& EventArgs)
{
MouseMove.RaiseEvent(EventArgs, this);
}
void Control::OnInvalidated(const std::unique_ptr<InvalidateEventArgs>& EventArgs)
{
Invalidated.RaiseEvent(EventArgs, this);
}
void Control::OnMouseWheel(const std::unique_ptr<HandledMouseEventArgs>& EventArgs)
{
MouseWheel.RaiseEvent(EventArgs, this);
}
void Control::OnKeyPress(const std::unique_ptr<KeyPressEventArgs>& EventArgs)
{
KeyPress.RaiseEvent(EventArgs, this);
}
void Control::OnKeyUp(const std::unique_ptr<KeyEventArgs>& EventArgs)
{
KeyUp.RaiseEvent(EventArgs, this);
}
void Control::OnKeyDown(const std::unique_ptr<KeyEventArgs>& EventArgs)
{
KeyDown.RaiseEvent(EventArgs, this);
}
void Control::OnDragEnter(const std::unique_ptr<DragEventArgs>& EventArgs)
{
DragEnter.RaiseEvent(EventArgs, this);
}
void Control::OnDragOver(const std::unique_ptr<DragEventArgs>& EventArgs)
{
DragOver.RaiseEvent(EventArgs, this);
}
void Control::OnDragDrop(const std::unique_ptr<DragEventArgs>& EventArgs)
{
DragDrop.RaiseEvent(EventArgs, this);
}
void Control::OnDragLeave()
{
DragLeave.RaiseEvent(this);
}
void Control::OnFontChanged()
{
Invalidate();
if (GetState(ControlStates::StateCreated))
SendMessageA(this->_Handle, WM_SETFONT, (WPARAM)this->_Font->GetFontHandle(), NULL);
FontChanged.RaiseEvent(this);
}
void Control::OnVisibleChanged()
{
if (this->Visible())
SetState(ControlStates::StateTrackingMouseEvent, false);
VisibleChanged.RaiseEvent(this);
// TODO: If we are a contianer control, we must enumerate children and call
// OnParentVisibleChanged();
// https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs,8578
}
void Control::OnHandleCreated()
{
// Restore drag drop ability
SetAcceptDrops(AllowDrop());
// We must create the controls here if we are a container control
if (GetStyle(ControlStyles::ContainerControl) && this->_Controls != nullptr)
{
// Store the counts on the stack
uint32_t ControlCount = this->_Controls->Count();
auto& ControlList = *this->_Controls.get();
for (uint32_t i = 0; i < ControlCount; i++)
{
if (!ControlList[i]->GetState(ControlStates::StateCreated))
ControlList[i]->CreateControl(this);
}
SetAnchor(this->_Anchor);
}
HandleCreated.RaiseEvent(this);
}
void Control::OnHandleDestroyed()
{
HandleDestroyed.RaiseEvent(this);
}
void Control::OnTextChanged()
{
TextChanged.RaiseEvent(this);
}
void Control::OnMouseEnter()
{
MouseEnter.RaiseEvent(this);
}
void Control::OnMouseLeave()
{
MouseLeave.RaiseEvent(this);
}
void Control::OnMouseHover()
{
MouseHover.RaiseEvent(this);
}
void Control::OnLostFocus()
{
LostFocus.RaiseEvent(this);
}
void Control::OnGotFocus()
{
GotFocus.RaiseEvent(this);
}
void Control::OnStyleChanged()
{
StyleChanged.RaiseEvent(this);
}
void Control::OnLocationChanged()
{
LocationChanged.RaiseEvent(this);
}
void Control::OnSizeChanged()
{
OnResize();
SizeChanged.RaiseEvent(this);
}
void Control::OnResize()
{
if (GetStyle(ControlStyles::ResizeRedraw))
Invalidate();
PerformLayout();
Resize.RaiseEvent(this);
}
void Control::OnClientSizeChanged()
{
ClientSizeChanged.RaiseEvent(this);
}
void Control::OnMouseCaptureChanged()
{
MouseCaptureChanged.RaiseEvent(this);
}
void Control::OnBackColorChanged()
{
if (_BackColorBrush != (uintptr_t)0)
{
if (GetState(ControlStates::StateOwnCtlBrush))
DeleteObject((HGDIOBJ)_BackColorBrush);
_BackColorBrush = (uintptr_t)0;
}
Invalidate();
BackColorChanged.RaiseEvent(this);
}
void Control::OnForeColorChanged()
{
Invalidate();
ForeColorChanged.RaiseEvent(this);
}
HPALETTE Control::SetUpPalette(HDC Dc, bool Force, bool RealizePalette)
{
if (Control::HalftonePalette == nullptr)
Control::HalftonePalette = Gdiplus::Graphics::GetHalftonePalette();
auto Result = SelectPalette(Dc, Control::HalftonePalette, (Force ? FALSE : TRUE));
if (Result != nullptr && RealizePalette)
::RealizePalette(Dc);
return Result;
}
MouseButtons Control::GetMouseButtons()
{
auto Result = (MouseButtons)0;
if (GetKeyState((int)Keys::LButton) < 0)
Result = Result | MouseButtons::Left;
if (GetKeyState((int)Keys::RButton) < 0)
Result = Result | MouseButtons::Right;
if (GetKeyState((int)Keys::MButton) < 0)
Result = Result | MouseButtons::Middle;
if (GetKeyState((int)Keys::XButton1) < 0)
Result = Result | MouseButtons::XButton1;
if (GetKeyState((int)Keys::XButton2) < 0)
Result = Result | MouseButtons::XButton2;
return Result;
}
Drawing::Point Control::GetMousePosition()
{
POINT Pt{};
GetCursorPos(&Pt);
return Drawing::Point(Pt.x, Pt.y);
}
Keys Control::GetModifierKeys()
{
uint32_t Result = 0;
if (GetKeyState((int)Keys::ShiftKey) < 0)
Result |= (int)Keys::Shift;
if (GetKeyState((int)Keys::ControlKey) < 0)
Result |= (int)Keys::Control;
if (GetKeyState((int)Keys::Menu) < 0)
Result |= (int)Keys::Alt;
return (Keys)Result;
}
void Control::UpdateBounds()
{
RECT Rc{};
GetClientRect(this->_Handle, &Rc);
if (!this->_AnchorDeltas.InitialRectSet && GetState(ControlStates::StateTopLevel))
{
std::memcpy(&this->_AnchorDeltas.InitialRect, &Rc, sizeof(Rc));
this->_AnchorDeltas.InitialRectSet = true;
}
auto ClientWidth = Rc.right;
auto ClientHeight = Rc.bottom;
GetWindowRect(this->_Handle, &Rc);
UpdateDeltas();
if (!GetState(ControlStates::StateTopLevel))
MapWindowPoints(NULL, GetParent(this->_Handle), (LPPOINT)&Rc, 2);
if (!this->_AnchorDeltas.InitialRectSet && !GetState(ControlStates::StateTopLevel))
{
std::memcpy(&this->_AnchorDeltas.InitialRect, &Rc, sizeof(Rc));
this->_AnchorDeltas.InitialRectSet = true;
}
UpdateBounds(Rc.left, Rc.top, Rc.right - Rc.left, Rc.bottom - Rc.top, ClientWidth, ClientHeight);
}
void Control::UpdateBounds(uint32_t X, uint32_t Y, uint32_t Width, uint32_t Height, uint32_t ClientWidth, uint32_t ClientHeight)
{
bool nLocation = this->_X != X || this->_Y != Y;
bool nSize = this->_Width != Width || this->_Height != Height ||
this->_ClientWidth != ClientWidth || this->_ClientHeight != ClientHeight;
this->_X = X;
this->_Y = Y;
this->_Width = Width;
this->_Height = Height;
this->_ClientWidth = ClientWidth;
this->_ClientHeight = ClientHeight;
if (nLocation)
OnLocationChanged();
if (nSize)
{
OnSizeChanged();
OnClientSizeChanged();
}
}
void Control::UpdateBounds(uint32_t X, uint32_t Y, uint32_t Width, uint32_t Height)
{
RECT Rect{};
auto Cp = this->GetCreateParams();
AdjustWindowRectEx(&Rect, Cp.Style, false, Cp.ExStyle);
int ClientWidth = Width - (Rect.right - Rect.left);
int ClientHeight = Height - (Rect.bottom - Rect.top);
UpdateBounds(X, Y, Width, Height, ClientWidth, ClientHeight);
}
void Control::SetBounds(uint32_t X, uint32_t Y, uint32_t Width, uint32_t Height)
{
if (!GetState(ControlStates::StateCreated))
UpdateBounds(X, Y, Width, Height);
else
{
if (!GetState(ControlStates::StateSizeLockedByOS))
{
auto Flags = SWP_NOZORDER | SWP_NOACTIVATE;
if (this->_X == X && this->_Y==Y)
Flags |= SWP_NOMOVE;
if (this->_Width == Width && this->_Height == Height)
Flags |= SWP_NOSIZE;
::SetWindowPos(this->_Handle, NULL, X, Y, Width, Height, Flags);
}
}
}
void Control::UpdateDeltas()
{
bool AnchorLeft = ((int)this->_Anchor & (int)AnchorStyles::Left) == (int)AnchorStyles::Left;
bool AnchorRight = ((int)this->_Anchor & (int)AnchorStyles::Right) == (int)AnchorStyles::Right;
bool AnchorTop = ((int)this->_Anchor & (int)AnchorStyles::Top) == (int)AnchorStyles::Top;
bool AnchorBottom = ((int)this->_Anchor & (int)AnchorStyles::Bottom) == (int)AnchorStyles::Bottom;
if (AnchorLeft && AnchorRight)
{
this->_AnchorDeltas.XMoveFrac = 0.f;
this->_AnchorDeltas.XSizeFrac = 1.f;
}
else if (AnchorLeft)
{
this->_AnchorDeltas.XMoveFrac = 0.f;
this->_AnchorDeltas.XSizeFrac = 0.f;
}
else if (AnchorRight)
{
this->_AnchorDeltas.XMoveFrac = 1.f;
this->_AnchorDeltas.XSizeFrac = 0.f;
}
else
{
this->_AnchorDeltas.XMoveFrac = 0.5f;
this->_AnchorDeltas.XSizeFrac = 0.f;
}
if (AnchorTop && AnchorBottom)
{
this->_AnchorDeltas.YMoveFrac = 0.f;
this->_AnchorDeltas.YSizeFrac = 1.f;
}
else if (AnchorTop)
{
this->_AnchorDeltas.YMoveFrac = 0.f;
this->_AnchorDeltas.YSizeFrac = 0.f;
}
else if (AnchorBottom)
{
this->_AnchorDeltas.YMoveFrac = 1.f;
this->_AnchorDeltas.YSizeFrac = 0.f;
}
else
{
this->_AnchorDeltas.YMoveFrac = 0.5f;
this->_AnchorDeltas.YSizeFrac = 0.f;
}
}
void Control::UpdateInitialPos()
{
// Get the initial rectangle...
if (GetState(ControlStates::StateTopLevel))
{
::GetClientRect(this->_Handle, &this->_AnchorDeltas.InitialRect);
}
else
{
::GetWindowRect(this->_Handle, &this->_AnchorDeltas.InitialRect);
::MapWindowPoints(NULL, GetParent(this->_Handle), (LPPOINT)&this->_AnchorDeltas.InitialRect, 2);
}
}
void Control::Select(bool Directed, bool Forward)
{
auto C = (ContainerControl*)GetContainerControl();
if (C != nullptr)
C->SetActiveControl(this);
}
void Control::Scale(Drawing::SizeF IncludedFactor, Drawing::SizeF ExcludedFactor, Control* Ctrl)
{
ScaleControl(IncludedFactor, ExcludedFactor, Ctrl);
ScaleChildControls(IncludedFactor, ExcludedFactor, Ctrl);
PerformLayout();
}
void Control::OnLayoutResuming(bool PerformLayout)
{
if (this->_Parent != nullptr)
{
this->_Parent->OnChildLayoutResuming(this, PerformLayout);
}
}
void Control::OnChildLayoutResuming(Control* Child, bool PerformLayout)
{
if (this->_Parent != nullptr)
{
this->_Parent->OnChildLayoutResuming(Child, PerformLayout);
}
}
uint32_t Control::WindowStyle()
{
return GetWindowLong(this->_Handle, GWL_STYLE);
}
void Control::SetWindowStyle(uint32_t Value)
{
SetWindowLong(this->_Handle, GWL_STYLE, Value);
}
uint32_t Control::WindowExStyle()
{
return GetWindowLong(this->_Handle, GWL_EXSTYLE);
}
void Control::SetWindowExStyle(uint32_t Value)
{
SetWindowLong(this->_Handle, GWL_EXSTYLE, Value);
}
Drawing::Size Control::SizeFromClientSize(int32_t Width, int32_t Height)
{
RECT Rc{ 0, 0, Width, Height };
auto Cp = GetCreateParams();
AdjustWindowRectEx(&Rc, Cp.Style, FALSE, Cp.ExStyle);
return Drawing::Size(Rc.right - Rc.left, Rc.bottom - Rc.top);
}
Drawing::Size Control::ScaleSize(Drawing::Size Start, float X, float Y)
{
Drawing::Size Result = Start;
if (!GetStyle(ControlStyles::FixedWidth))
{
Result.Width = (int)std::roundf((float)Result.Width * X);
}
if (!GetStyle(ControlStyles::FixedHeight))
{
Result.Height = (int)std::roundf((float)Result.Height * Y);
}
return Result;
}
void Control::ScaleControl(Drawing::SizeF IncludedFactor, Drawing::SizeF ExcludedFactor, Control* Ctrl)
{
auto IncludedSpecified = BoundsSpecified::None;
auto ExcludedSpecified = BoundsSpecified::None;
if (!IncludedFactor.Empty())
{
IncludedSpecified = this->_RequiredScaling;
}
if (!ExcludedFactor.Empty())
{
ExcludedSpecified = (BoundsSpecified)(~(uint32_t)this->_RequiredScaling & (uint32_t)BoundsSpecified::All);
}
if (IncludedSpecified != BoundsSpecified::None)
{
ScaleControl(IncludedFactor, IncludedSpecified);
}
if (ExcludedSpecified != BoundsSpecified::None)
{
ScaleControl(ExcludedFactor, ExcludedSpecified);
}
if (!IncludedFactor.Empty())
{
this->_RequiredScaling = BoundsSpecified::None;
}
}
void Control::ScaleChildControls(Drawing::SizeF IncludedFactor, Drawing::SizeF ExcludedFactor, Control* Ctrl)
{
// Must be container
if (this->_Controls == nullptr)
return;
// Store the counts on the stack
uint32_t ControlCount = this->_Controls->Count();
auto& ControlList = *this->_Controls.get();
for (uint32_t i = 0; i < ControlCount; i++)
ControlList[i]->Scale(IncludedFactor, ExcludedFactor, Ctrl);
}
void Control::ScaleControl(Drawing::SizeF Factor, BoundsSpecified Specified)
{
auto Cp = this->GetCreateParams();
RECT Adornments{};
AdjustWindowRectEx(&Adornments, Cp.Style, FALSE, Cp.ExStyle);
auto MinSize = this->MinimumSize();
auto MaxSize = this->MaximumSize();
this->SetMinimumSize({ 0, 0 });
this->SetMaximumSize({ 0, 0 });
auto ScaledBounds = this->GetScaledBounds(Drawing::Rectangle(this->Location(), this->Size()), Factor, Specified);
auto AdornmentSize = Drawing::Size(Adornments.right - Adornments.left, Adornments.bottom - Adornments.top);
if (!MinSize.Empty())
{
MinSize = MinSize - AdornmentSize;
MinSize = this->ScaleSize(Drawing::UnionSizes(Drawing::Size{}, MinSize), Factor.Width, Factor.Height) + AdornmentSize;
}
if (!MaxSize.Empty())
{
MaxSize = MaxSize - AdornmentSize;
MaxSize = this->ScaleSize(Drawing::UnionSizes(Drawing::Size{}, MaxSize), Factor.Width, Factor.Height) + AdornmentSize;
}
auto FMaxSize = Drawing::ConvertZeroToUnbounded(MaxSize);
auto ScaledSize = Drawing::IntersectSizes({ ScaledBounds.Width, ScaledBounds.Height }, FMaxSize);
ScaledSize = Drawing::UnionSizes(ScaledSize, MinSize);
this->SetBounds(ScaledBounds.X, ScaledBounds.Y, ScaledSize.Width, ScaledSize.Height);
this->SetMinimumSize(MinSize);
this->SetMaximumSize(MaxSize);
}
Drawing::Rectangle Control::GetScaledBounds(Drawing::Rectangle Bounds, Drawing::SizeF Factor, BoundsSpecified Specified)
{
// We should not include the window adornments in our calculation,
// because windows scales them for us.
RECT Adornments{};
auto Cp = this->GetCreateParams();
AdjustWindowRectEx(&Adornments, Cp.Style, FALSE, Cp.ExStyle);
float Dx = Factor.Width;
float Dy = Factor.Height;
int32_t Sx = Bounds.X;
int32_t Sy = Bounds.Y;
// Don't reposition top level controls. Also, if we're in
// design mode, don't reposition the root component.
bool ScaleLoc = !GetState(ControlStates::StateTopLevel);
if (ScaleLoc)
{
if (((uint32_t)Specified & (uint32_t)BoundsSpecified::X) != 0)
{
Sx = (int)std::roundf(Bounds.X * Dx);
}
if (((uint32_t)Specified & (uint32_t)BoundsSpecified::Y) != 0)
{
Sy = (int)std::roundf(Bounds.Y * Dy);
}
}
int32_t Sw = Bounds.Width;
int32_t Sh = Bounds.Height;
if (!GetStyle(ControlStyles::FixedWidth) && ((uint32_t)Specified & (uint32_t)BoundsSpecified::Width) != 0)
{
auto adornmentWidth = (Adornments.right - Adornments.left);
auto localWidth = Bounds.Width - adornmentWidth;
Sw = (int)std::roundf(localWidth * Dx) + adornmentWidth;
}
if (!GetStyle(ControlStyles::FixedHeight) && ((uint32_t)Specified & (uint32_t)BoundsSpecified::Height) != 0)
{
auto adornmentHeight = (Adornments.bottom - Adornments.top);
auto localHeight = Bounds.Height - adornmentHeight;
Sh = (int)std::roundf(localHeight * Dy) + adornmentHeight;
}
return Drawing::Rectangle(Sx, Sy, Sw, Sh);
}
uintptr_t Control::BackColorBrush()
{
if (_BackColorBrush != (uintptr_t)0)
return _BackColorBrush;
if (_Parent != nullptr && _Parent->BackColor().ToCOLORREF() == BackColor().ToCOLORREF())
return _Parent->BackColorBrush();
auto Color = this->BackColor();
auto ColorWin = Drawing::ColorToWin32(Color);
// Convert the color to a system color if possible...
int i = 0;
for (; i <= 30; i++)
{
auto SysColor = GetSysColor(i);
if (SysColor == ColorWin)
break;
}
if (i <= 30)
{
_BackColorBrush = (uintptr_t)GetSysColorBrush(i);
SetState(ControlStates::StateOwnCtlBrush, false);
}
else
{
_BackColorBrush = (uintptr_t)CreateSolidBrush(Color.ToCOLORREF());
SetState(ControlStates::StateOwnCtlBrush, true);
}
return _BackColorBrush;
}
bool Control::RequiredScalingEnabled()
{
return this->_RequiredScalingEnabled;
}
void Control::SetRequiredScalingEnabled(bool Value)
{
this->_RequiredScalingEnabled = Value;
}
String Control::WindowText()
2022-05-21 19:58:09 +02:00
{
if (!GetState(ControlStates::StateCreated))
return _Text;
auto Length = GetWindowTextLengthA(this->_Handle);
auto Result = String(Length);
2022-05-21 19:58:09 +02:00
GetWindowTextA(this->_Handle, (char*)Result, Length + 1);
return Result;
}
void Control::SetWindowText(const String& Value)
2022-05-21 19:58:09 +02:00
{
if (GetState(ControlStates::StateCreated))
SetWindowTextA(this->_Handle, (char*)Value);
else
_Text = Value;
}
void Control::UpdateStyles()
{
if (GetState(ControlStates::StateCreated))
{
auto Cp = this->GetCreateParams();
auto WinStyle = WindowStyle();
auto ExStyle = WindowExStyle();
if (GetState(ControlStates::StateVisible))
Cp.Style |= WS_VISIBLE;
if (WinStyle != Cp.Style)
SetWindowStyle(Cp.Style);
if (ExStyle != Cp.ExStyle)
SetWindowExStyle(Cp.ExStyle);
SetWindowPos(this->_Handle, NULL, 0, 0, 0, 0, SWP_DRAWFRAME | SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER);
Invalidate(true);
}
OnStyleChanged();
}
void Control::SetAcceptDrops(bool Accept)
{
if (Accept != GetState(ControlStates::StateDropTarget) && GetState(ControlStates::StateCreated))
{
if (Accept)
{
this->_DropTarget = std::make_unique<DropTarget>(this);
RegisterDragDrop(this->_Handle, (LPDROPTARGET)this->_DropTarget.get());
}
else
{
RevokeDragDrop(this->_Handle);
this->_DropTarget.reset();
}
SetState(ControlStates::StateDropTarget, Accept);
}
}
void Control::ResetMouseEventArgs()
{
if (GetState(ControlStates::StateTrackingMouseEvent))
{
SetState(ControlStates::StateTrackingMouseEvent, false);
if (!GetState(ControlStates::StateTrackingMouseEvent))
{
SetState(ControlStates::StateTrackingMouseEvent, true);
TRACKMOUSEEVENT mEvt{};
mEvt.cbSize = sizeof(mEvt);
mEvt.dwFlags = TME_LEAVE | TME_HOVER;
mEvt.hwndTrack = this->_Handle;
TrackMouseEvent(&mEvt);
}
}
}
void Control::DestroyHandle()
{
if (_Handle != nullptr)
{
DestroyWindow(_Handle);
_Handle = nullptr;
}
}
void Control::PerformLayout()
{
// This only applies if we house controls
if (!GetStyle(ControlStyles::ContainerControl) || this->_Controls == nullptr)
return;
// We may need to not layout here...
if (this->_LayoutSuspendCount > 0)
{
SetState(ControlStates::StateLayoutDeferred, true);
return;
}
// By default, we will move all controls at once
HDWP DeferHandle = nullptr;
// Store the counts on the stack
uint32_t ControlCount = this->_Controls->Count();
auto& ControlList = *this->_Controls.get();
RECT nRect{};
::GetClientRect(this->_Handle, &nRect);
// Calculate delta, must be signed integer
int32_t DeltaX = ((nRect.right - nRect.left) - (this->_AnchorDeltas.InitialRect.right - this->_AnchorDeltas.InitialRect.left));
int32_t DeltaY = ((nRect.bottom - nRect.top) - (this->_AnchorDeltas.InitialRect.bottom - this->_AnchorDeltas.InitialRect.top));
for (uint32_t i = 0; i < ControlCount; i++)
{
auto Child = ControlList[i];
auto ChildHwnd = Child->GetHandle();
// Important: we must never defer null handles, OR the tooltip
// otherwise the defer will never commit the changes...
if (ChildHwnd == nullptr || Child->_RTTI == ControlTypes::ToolTip)
continue;
auto& ChildDeltas = Child->_AnchorDeltas;
RECT RcNew(ChildDeltas.InitialRect);
::OffsetRect(&RcNew, (int32_t)(DeltaX * ChildDeltas.XMoveFrac), (int32_t)(DeltaY * ChildDeltas.YMoveFrac));
RcNew.right += (int32_t)(DeltaX * ChildDeltas.XSizeFrac);
RcNew.bottom += (int32_t)(DeltaY * ChildDeltas.YSizeFrac);
auto Width = (RcNew.right - RcNew.left);
auto Height = (RcNew.bottom - RcNew.top);
// Ensure we are within bounds, if necessary
auto Minimum = Child->MinimumSize();
auto Maximum = Child->MaximumSize();
if (!Minimum.Empty())
{
Width = max(Minimum.Width, Width);
Height = max(Minimum.Height, Height);
}
if (!Maximum.Empty())
{
Width = min(Maximum.Width, Width);
Height = min(Maximum.Height, Height);
}
if (DeferHandle == nullptr)
DeferHandle = BeginDeferWindowPos(ControlCount);
auto Flags = SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOZORDER;
// For controls with children that don't resize, we can speed this up by not invalidating the client rect
if (ChildDeltas.XSizeFrac != 0.0f || ChildDeltas.YSizeFrac != 0.0f)
Flags |= SWP_NOCOPYBITS;
DeferWindowPos(DeferHandle, ChildHwnd, NULL, RcNew.left, RcNew.top, Width, Height, Flags);
}
// Layout was performed, we can mark completed
SetState(ControlStates::StateLayoutDeferred | ControlStates::StateLayoutIsDirty, false);
// End rendering
if (DeferHandle != nullptr)
EndDeferWindowPos(DeferHandle);
}
bool Control::ProcessKeyMessage(Message& Msg)
{
if (this->_Parent != nullptr && this->_Parent->ProcessKeyPreview(Msg))
return true;
return ProcessKeyEventArgs(Msg);
}
bool Control::ProcessKeyPreview(Message& Msg)
{
return this->_Parent == nullptr ? false : this->_Parent->ProcessKeyPreview(Msg);
}
bool Control::ProcessKeyEventArgs(Message& Msg)
{
// Define the events globally for reuse in events
std::unique_ptr<KeyEventArgs> KeyEvent = nullptr;
std::unique_ptr<KeyPressEventArgs> KeyPressEvent = nullptr;
uintptr_t NewWParam = 0;
if (Msg.Msg == WM_CHAR || Msg.Msg == WM_SYSCHAR)
{
KeyPressEvent = std::make_unique<KeyPressEventArgs>((char)Msg.WParam);
OnKeyPress(KeyPressEvent);
NewWParam = (uintptr_t)KeyPressEvent->KeyChar;
}
else
{
KeyEvent = std::make_unique<KeyEventArgs>((Keys)((int)Msg.WParam | (int)GetModifierKeys()));
if (Msg.Msg == WM_KEYDOWN || Msg.Msg == WM_SYSKEYDOWN)
OnKeyDown(KeyEvent);
else
OnKeyUp(KeyEvent);
}
if (KeyPressEvent != nullptr)
{
Msg.WParam = NewWParam;
return KeyPressEvent->Handled();
}
else if (KeyEvent != nullptr)
{
if (KeyEvent->SuppressKeyPress())
{
RemovePendingMessages(WM_CHAR, WM_CHAR);
RemovePendingMessages(WM_SYSCHAR, WM_SYSCHAR);
}
return KeyEvent->Handled();
}
// Security just incase we don't handle IME stuff...
return false;
}
bool Control::GetVisibleCore()
{
if (!GetState(ControlStates::StateVisible))
return false;
else if (this->_Parent == nullptr)
return true;
else
return this->_Parent->Visible();
}
void Control::SetVisibleCore(bool Value)
{
if (Visible() != Value)
{
bool fChange = false;
if (GetState(ControlStates::StateTopLevel))
{
if (Value)
ShowWindow(this->_Handle, SW_SHOW);
}
else if (this->_Handle != nullptr || Value && this->_Parent != nullptr && this->_Parent->GetState(ControlStates::StateCreated))
{
SetState(ControlStates::StateVisible, Value);
fChange = true;
SetWindowPos(this->_Handle, NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | (Value ? SWP_SHOWWINDOW : SWP_HIDEWINDOW));
}
if (Visible() != Value)
{
SetState(ControlStates::StateVisible, Value);
fChange = true;
}
if (fChange)
{
OnVisibleChanged();
}
}
else
{
if (!GetState(ControlStates::StateVisible) && !Value && this->_Handle != nullptr)
{
if (!IsWindowVisible(this->_Handle))
return;
}
SetState(ControlStates::StateVisible, Value);
if (this->_Handle != nullptr)
SetWindowPos(this->_Handle, NULL, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE | (Value ? SWP_SHOWWINDOW : SWP_HIDEWINDOW));
}
}
CreateParams Control::GetCreateParams()
{
CreateParams Result{};
Result.X = this->_X;
Result.Y = this->_Y;
Result.Width = this->_Width;
Result.Height = this->_Height;
Result.Caption = this->_Text;
Result.Style = WS_CLIPCHILDREN;
if (GetStyle(ControlStyles::ContainerControl))
Result.ExStyle |= WS_EX_CONTROLPARENT;
Result.ClassStyle = CS_DBLCLKS;
if (!GetState(ControlStates::StateTopLevel))
{
Result.Parent = (this->_Parent != nullptr) ? (uintptr_t)this->_Parent->GetHandle() : (uintptr_t)0;
Result.Style |= WS_CHILD | WS_CLIPSIBLINGS;
}
if (GetState(ControlStates::StateTabstop))
Result.Style |= WS_TABSTOP;
if (GetState(ControlStates::StateVisible))
Result.Style |= WS_VISIBLE;
if (!Enabled())
Result.Style |= WS_DISABLED;
return Result;
}
LRESULT Control::InternalWndProc(HWND hWnd, UINT Msg, WPARAM wParam, LPARAM lParam)
{
// Catch WM_CREATE here so that we can properly set the class pointer
if (Msg == WM_CREATE)
{
auto ControlClassPtr = (Control*)(((LPCREATESTRUCT)lParam)->lpCreateParams);
SetWindowLongPtrA(hWnd, GWLP_USERDATA, (LONG_PTR)ControlClassPtr);
}
// Fetch the class data
auto ClassPtr = GetWindowLongPtrA(hWnd, GWLP_USERDATA);
auto ControlMessage = Message(hWnd, Msg, wParam, lParam);
// Reflect into the proper WndProc
if (ClassPtr == NULL)
ControlMessage.Result = (uintptr_t)DefWindowProcA(hWnd, Msg, wParam, lParam);
else
((Control*)ClassPtr)->WndProc(ControlMessage);
return (LRESULT)ControlMessage.Result;
}
void Control::AddControl(Control* Ctrl)
{
// Prepare to add the control, if we are created, create it, otherwise, wait
this->_Controls->Add(Ctrl);
// Check to initialize the control
if (GetState(ControlStates::StateCreated))
{
Ctrl->CreateControl(this);
SetAnchor(this->_Anchor);
}
}
void Control::Dispose()
{
SetState(ControlStates::StateDisposing, true);
if (GetState(ControlStates::StateOwnCtlBrush) && _BackColorBrush != (uintptr_t)0)
{
DeleteObject((HGDIOBJ)_BackColorBrush);
_BackColorBrush = (uintptr_t)0;
}
this->DestroyHandle();
OnHandleDestroyed();
SetState(ControlStates::StateDisposed, true);
SetState(ControlStates::StateDisposing, false);
}
uintptr_t Control::InitializeDCForWmCtlColor(HDC Dc, int32_t Message)
{
if (!GetStyle(ControlStyles::UserPaint))
{
SetTextColor(Dc, this->ForeColor().ToCOLORREF());
SetBkColor(Dc, this->BackColor().ToCOLORREF());
return BackColorBrush();
}
else
{
return (uintptr_t)GetStockObject(HOLLOW_BRUSH);
}
}
bool Control::IsContainerControl()
{
return false;
}
HRGN Control::CreateCopyOfRgn(HRGN InRgn)
{
RECT Rect{};
HRGN DestRgn;
if (InRgn)
{
DestRgn = CreateRectRgnIndirect(&Rect);
if (CombineRgn(DestRgn, InRgn, NULL, RGN_COPY) != ERROR)
return DestRgn;
DeleteObject(DestRgn);
}
return NULL;
}
void Control::WmMouseDown(Message& Msg, MouseButtons Button, uint32_t Clicks)
{
// Track that a key is pressed
SetState(ControlStates::StateMousePressed, true);
// Check for control message processing
if (!GetStyle(ControlStyles::UserMouse))
DefWndProc(Msg);
else
{
// DefWndProc would normally set focus to the control, but
// since we're skipping DefWndProc, we need to do it ourselves.
if (Button == MouseButtons::Left && GetStyle(ControlStyles::Selectable))
this->Focus();
}
// TODO: If not State2(MAINTAINSOWNCAPTUREMODE)
SetCapture(true);
if (this->Enabled())
OnMouseDown(std::make_unique<MouseEventArgs>(Button, Clicks, LOWORD(Msg.LParam), HIWORD(Msg.LParam), 0));
}
void Control::WmMouseUp(Message& Msg, MouseButtons Button, uint32_t Clicks)
{
POINT Point;
Point.x = LOWORD(Msg.LParam);
Point.y = HIWORD(Msg.LParam);
MapWindowPoints(this->_Handle, NULL, &Point, 1); // Faster Control::PointToScreen();
// Check for control message processing
if (!GetStyle(ControlStyles::UserMouse))
{
DefWndProc(Msg);
}
else
{
// DefWndProc would normally trigger a context menu here
// (for a right button click), but since we're skipping DefWndProc
// we have to do it ourselves.
if (Button == MouseButtons::Right)
SendMessageA(this->_Handle, WM_CONTEXTMENU, (WPARAM)this->_Handle, MAKELPARAM(0, 0));
}
// Track whether or not it was a real click
bool fClick = false;
if (GetStyle(ControlStyles::StandardClick))
{
if (GetState(ControlStates::StateMousePressed) && WindowFromPoint(Point) == this->_Handle)
fClick = true;
}
if (fClick)
{
if (!GetState(ControlStates::StateDoubleClickFired))
{
OnClick();
OnMouseClick(std::make_unique<MouseEventArgs>(Button, Clicks, LOWORD(Msg.LParam), HIWORD(Msg.LParam), 0));
}
else
{
OnDoubleClick();
OnMouseDoubleClick(std::make_unique<MouseEventArgs>(Button, 2, LOWORD(Msg.LParam), HIWORD(Msg.LParam), 0));
}
}
// Call MouseUp after the click event
OnMouseUp(std::make_unique<MouseEventArgs>(Button, Clicks, LOWORD(Msg.LParam), HIWORD(Msg.LParam), 0));
// Reset the states
SetState(ControlStates::StateDoubleClickFired |
ControlStates::StateMousePressed |
ControlStates::StateValidationCancelled, false);
SetCapture(false);
}
void Control::WmMouseEnter(Message& Msg)
{
DefWndProc(Msg);
OnMouseEnter();
}
void Control::WmMouseLeave(Message& Msg)
{
// We stop tracking once we leave the element
SetState(ControlStates::StateTrackingMouseEvent, false);
DefWndProc(Msg);
OnMouseLeave();
}
void Control::WmMouseHover(Message& Msg)
{
DefWndProc(Msg);
OnMouseHover();
}
void Control::WmClose(Message& Msg)
{
if (this->_Parent != nullptr)
{
HWND ParentHandle = this->_Handle;
HWND LastParentHandle = ParentHandle;
while (ParentHandle != nullptr)
{
LastParentHandle = ParentHandle;
ParentHandle = GetParent(ParentHandle);
auto Style = GetWindowLongA(LastParentHandle, GWL_STYLE);
if ((Style & WS_CHILD) == 0)
break;
}
if (LastParentHandle != nullptr)
PostMessageA(LastParentHandle, WM_CLOSE, NULL, NULL);
}
DefWndProc(Msg);
}
void Control::WmEraseBkgnd(Message& Msg)
{
if (GetStyle(ControlStyles::UserPaint))
{
if (!GetStyle(ControlStyles::AllPaintingInWmPaint))
{
uintptr_t Dc = Msg.WParam;
if (Dc == NULL)
{
Msg.Result = (uintptr_t)0;
return;
}
RECT rcClient{};
GetClientRect(this->_Handle, &rcClient);
OnPaintBackground(std::make_unique<PaintEventArgs>((HDC)Dc, Drawing::Rectangle(rcClient.left, rcClient.top, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top)));
}
Msg.Result = (uintptr_t)1;
}
else
{
DefWndProc(Msg);
}
}
void Control::WmPaint(Message& Msg)
{
auto IsDoubleBuffered = this->DoubleBuffered() || GetStyle(ControlStyles::AllPaintingInWmPaint) && GetStyle(ControlStyles::DoubleBuffer | ControlStyles::UserPaint);
HWND hWnd = nullptr;
HDC Dc = nullptr;
PAINTSTRUCT Ps{};
Drawing::Rectangle Clip;
bool NeedsDisposeDc = false;
if (Msg.WParam == NULL)
{
hWnd = this->_Handle;
Dc = BeginPaint(hWnd, &Ps);
if (!Dc)
return;
NeedsDisposeDc = true;
Clip = Drawing::Rectangle(Ps.rcPaint.left, Ps.rcPaint.top, Ps.rcPaint.right - Ps.rcPaint.left, Ps.rcPaint.bottom - Ps.rcPaint.top);
}
else
{
Dc = (HDC)Msg.WParam;
Clip = this->ClientRectangle();
}
if (!IsDoubleBuffered || (Clip.Width > 0 && Clip.Height > 0))
{
HPALETTE OldPal = nullptr;
std::unique_ptr<Drawing::BufferedGraphics> BufferedGraphics;
std::unique_ptr<PaintEventArgs> PaintEvent;
Drawing::GraphicsState State = NULL;
if (IsDoubleBuffered || Msg.WParam == NULL)
OldPal = SetUpPalette(Dc, false, false);
if (IsDoubleBuffered)
{
BufferedGraphics = std::make_unique<Drawing::BufferedGraphics>(Dc, this->ClientRectangle());
}
if (BufferedGraphics != nullptr)
{
BufferedGraphics->Graphics->SetClip(Clip);
PaintEvent = std::make_unique<PaintEventArgs>(BufferedGraphics->Graphics.get(), Clip);
State = PaintEvent->Graphics->Save();
}
else
{
PaintEvent = std::make_unique<PaintEventArgs>(Dc, Clip);
}
if ((Msg.WParam == NULL) && GetStyle(ControlStyles::AllPaintingInWmPaint) || IsDoubleBuffered)
OnPaintBackground(PaintEvent);
if (State != NULL)
PaintEvent->Graphics->Restore(State);
else
PaintEvent->ResetGraphics();
OnPaint(PaintEvent);
if (BufferedGraphics != nullptr)
{
BufferedGraphics->Render();
PaintEvent->Graphics.release();
}
if (OldPal != nullptr)
SelectPalette(Dc, OldPal, FALSE);
}
if (NeedsDisposeDc)
EndPaint(hWnd, &Ps);
}
void Control::WmCreate(Message& Msg)
{
DefWndProc(Msg);
}
void Control::WmShowWindow(Message& Msg)
{
DefWndProc(Msg);
if (!GetState(ControlStates::StateRecreate))
{
bool isVisible = (Msg.WParam != NULL);
bool oVisibleProperty = Visible();
if (isVisible)
{
// This doesn't match .NET because we don't create the control here, we just ensure the bit is set
SetState(ControlStates::StateVisible, true);
}
else
{
bool pVisible = GetState(ControlStates::StateTopLevel);
if (this->_Parent != nullptr)
pVisible = this->_Parent->Visible();
if (pVisible)
SetState(ControlStates::StateVisible, false);
}
if (!GetState(ControlStates::StateParentRecreating) && (oVisibleProperty != isVisible))
OnVisibleChanged();
}
}
void Control::WmMove(Message& Msg)
{
DefWndProc(Msg);
UpdateBounds();
}
void Control::WmParentNotify(Message& Msg)
{
auto Mid = LOWORD(Msg.WParam);
HWND hWnd = nullptr;
switch (Mid)
{
case WM_CREATE:
hWnd = (HWND)Msg.LParam;
break;
case WM_DESTROY:
break;
default:
hWnd = GetDlgItem(this->_Handle, HIWORD(Msg.WParam));
break;
}
if (hWnd == nullptr || !ReflectMessageInternal(hWnd, Msg))
DefWndProc(Msg);
}
void Control::WmCommand(Message& Msg)
{
if (Msg.LParam == NULL)
{
// TODO: Command.DispatchID();
}
else
{
if (ReflectMessageInternal((HWND)Msg.LParam, Msg))
return;
}
DefWndProc(Msg);
}
void Control::WmQueryNewPalette(Message& Msg)
{
auto Dc = GetDC(this->_Handle);
SetUpPalette(Dc, true, true);
ReleaseDC(this->_Handle, Dc);
Invalidate(true);
Msg.Result = (uintptr_t)1;
DefWndProc(Msg);
}
void Control::WmNotify(Message& Msg)
{
auto nMDR = (NMHDR*)Msg.LParam;
if (!ReflectMessageInternal(nMDR->hwndFrom, Msg))
{
if (nMDR->code == TTN_SHOW)
{
SendMessageA(nMDR->hwndFrom, WM_REFLECT + Msg.Msg, (WPARAM)Msg.WParam, (LPARAM)Msg.LParam);
return;
}
if (nMDR->code == TTN_POP)
SendMessageA(nMDR->hwndFrom, WM_REFLECT + Msg.Msg, (WPARAM)Msg.WParam, (LPARAM)Msg.LParam);
DefWndProc(Msg);
}
}
void Control::WmNotifyFormat(Message& Msg)
{
if (!ReflectMessageInternal((HWND)Msg.WParam, Msg))
DefWndProc(Msg);
}
void Control::WmCaptureChanged(Message& Msg)
{
OnMouseCaptureChanged();
DefWndProc(Msg);
}
void Control::WmCtlColorControl(Message& Msg)
{
auto ControlPtr = GetWindowLongPtrA((HWND)Msg.LParam, GWLP_USERDATA);
if (ControlPtr != NULL)
{
Msg.Result = ((Control*)ControlPtr)->InitializeDCForWmCtlColor((HDC)Msg.WParam, Msg.Msg);
if (Msg.Result != NULL)
return;
}
DefWndProc(Msg);
}
void Control::WmKillFocus(Message& Msg)
{
DefWndProc(Msg);
OnLostFocus();
}
void Control::WmSetFocus(Message& Msg)
{
ContainerControl* C = (ContainerControl*)GetContainerControl();
if (C != nullptr)
{
if (!C->ActivateControl(this))
return;
}
DefWndProc(Msg);
OnGotFocus();
}
void Control::WmMouseMove(Message& Msg)
{
if (!GetState(ControlStates::StateTrackingMouseEvent))
{
SetState(ControlStates::StateTrackingMouseEvent, true);
TRACKMOUSEEVENT mEvent{};
mEvent.cbSize = sizeof(mEvent);
mEvent.dwFlags = TME_LEAVE | TME_HOVER;
mEvent.hwndTrack = this->_Handle;
TrackMouseEvent(&mEvent);
if (!GetState(ControlStates::StateMouseEnterPending))
SendMessageA(this->_Handle, WM_MOUSEENTER, 0, 0);
else
SetState(ControlStates::StateMouseEnterPending, false);
}
if (!GetStyle(ControlStyles::UserMouse))
DefWndProc(Msg);
else
OnMouseMove(std::make_unique<MouseEventArgs>(Control::GetMouseButtons(), 0, (int16_t)LOWORD(Msg.LParam), (int16_t)HIWORD(Msg.LParam), 0));
}
void Control::WmSetCursor(Message& Msg)
{
if ((HWND)Msg.WParam == this->_Handle && LOWORD(Msg.LParam) == HTCLIENT)
{
// TODO: Cursor.CursorInternal = Cursor;
// Remove defwndproc...
DefWndProc(Msg);
}
else
{
DefWndProc(Msg);
}
}
void Control::WmMouseWheel(Message& Msg)
{
auto mPoint = this->PointToClient({ LOWORD(Msg.LParam), HIWORD(Msg.LParam) });
auto eArgs = std::make_unique<HandledMouseEventArgs>(MouseButtons::None, 0, mPoint.X, mPoint.Y, (int16_t)HIWORD(Msg.WParam));
OnMouseWheel(eArgs);
if (!eArgs->Handled)
{
DefWndProc(Msg);
}
}
void Control::WmKeyChar(Message& Msg)
{
if (ProcessKeyMessage(Msg))
return;
DefWndProc(Msg);
}
void Control::WmWindowPosChanged(Message& Msg)
{
DefWndProc(Msg);
UpdateBounds();
if (this->_Parent != nullptr && GetParent(this->_Handle) == this->_Parent->GetHandle() && !GetState(ControlStates::StateNoZOrder))
{
auto wPos = (WINDOWPOS*)Msg.LParam;
if ((wPos->flags & SWP_NOZORDER) == 0)
{
this->_Parent->UpdateChildControlIndex(this);
}
}
}
void Control::WmInvokeOnUIThread(Message& Msg)
{
if (Msg.LParam == NULL)
return;
// Invoke the function here
((void(*)(void))Msg.LParam)();
}
void Control::RemovePendingMessages(uint32_t MsgMin, uint32_t MsgMax)
{
MSG Msg{};
while (PeekMessageA(&Msg, this->_Handle, MsgMin, MsgMax, PM_REMOVE));
}
void Control::UpdateChildZOrder(Control* Ctrl)
{
if (!GetState(ControlStates::StateCreated) || Ctrl->_Parent != this)
return;
HWND PrevHandle = (HWND)HWND_TOP;
auto& ControlList = *this->_Controls.get();
for (int32_t i = this->_Controls->IndexOf(Ctrl); --i >= 0;)
{
Control* C = ControlList[i];
if (C->GetState(ControlStates::StateCreated) && C->_Parent == this)
{
PrevHandle = C->_Handle;
break;
}
}
if (GetWindow(Ctrl->_Handle, GW_HWNDPREV) != PrevHandle)
{
SetState(ControlStates::StateNoZOrder, true);
SetWindowPos(Ctrl->_Handle, PrevHandle, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE);
SetState(ControlStates::StateNoZOrder, false);
}
}
void Control::UpdateChildControlIndex(Control* Ctrl)
{
// TODO: If we implement a tab control, we MUST prevent this from continuing here...
// See: https://referencesource.microsoft.com/#System.Windows.Forms/winforms/Managed/System/WinForms/Control.cs,12635
int32_t NewIndex = 0;
int32_t CurIndex = this->_Controls->IndexOf(Ctrl);
auto Hwnd = Ctrl->_Handle;
while ((Hwnd = GetWindow(Hwnd, GW_HWNDPREV)) != NULL)
{
Control* C = Control::FromHandle(Hwnd);
if (C != nullptr)
{
NewIndex = this->_Controls->IndexOf(C) + 1;
break;
}
}
if (NewIndex > CurIndex)
NewIndex--;
if (NewIndex != CurIndex)
this->_Controls->SetChildIndex(Ctrl, NewIndex);
}
void Control::SelectNextIfFocused()
{
if (ContainsFocus() && this->_Parent != nullptr)
{
auto C = (ContainerControl*)this->_Parent->GetContainerControl();
if (C != nullptr)
C->SelectNextControl(this, true, true, true, true);
}
}
Control* Control::GetNextSelectableControl(Control* Ctrl, bool Forward, bool TabStopOnly, bool Nested, bool Wrap)
{
if (!Contains(Ctrl) || !Nested && Ctrl->_Parent != this)
Ctrl = nullptr;
bool AlreadyWrapped = false;
Control* Start = Ctrl;
do
{
Ctrl = GetNextControl(Ctrl, Forward);
if (Ctrl == nullptr)
{
if (!Wrap)
break;
if (AlreadyWrapped)
return nullptr;
AlreadyWrapped = true;
}
else
{
if (Ctrl->CanSelect() && (Nested || Ctrl->_Parent == this)) // TODO: TabStopOnly / TabStop...
return Ctrl;
}
} while (Ctrl != Start);
return nullptr;
}
Control* Control::GetFirstChildcontrolInTabOrder(bool Forward)
{
if (this->_Controls == nullptr)
return nullptr;
Control* Found = nullptr;
auto& ControlList = *this->_Controls.get();
if (Forward)
{
for (uint32_t i = 0; i < this->_Controls->Count(); i++)
{
if (Found == nullptr || Found->_TabIndex > ControlList[i]->_TabIndex)
Found = ControlList[i];
}
}
else
{
for (int32_t i = (int32_t)this->_Controls->Count() - 1; i >= 0; i--)
{
if (Found == nullptr || Found->_TabIndex < ControlList[i]->_TabIndex)
Found = ControlList[i];
}
}
return Found;
}
Control* Control::FromHandle(HWND hWnd)
{
if (hWnd == NULL)
return nullptr;
return (Control*)GetWindowLongPtrA(hWnd, GWLP_USERDATA);
}
Control* Control::FromChildHandle(HWND hWnd)
{
while (hWnd != NULL)
{
auto Ctrl = Control::FromHandle(hWnd);
if (Ctrl != nullptr)
return Ctrl;
hWnd = GetAncestor(hWnd, GA_PARENT);
}
return nullptr;
}
bool Control::ReflectMessageInternal(HWND hWnd, Message& Msg)
{
auto Control = GetWindowLongPtrA(hWnd, GWLP_USERDATA);
if (Control == NULL)
return false;
Msg.Result = SendMessageA(hWnd, WM_REFLECT + Msg.Msg, Msg.WParam, Msg.LParam);
return true;
}
bool Control::IsFocusManagingContainerControl(Control* Ctrl)
{
return ((Ctrl->GetStyle(ControlStyles::ContainerControl) && Ctrl->IsContainerControl()));
}
String Control::RegisterWndClass(const char* ClassName, DWORD ClassStyle, bool& Subclass)
2022-05-21 19:58:09 +02:00
{
// Check for a built-in class name...
WNDCLASSEXA ExInfo{};
ExInfo.cbSize = sizeof(WNDCLASSEXA);
// Default windows classes
const char* const DefWndClasses[] =
{
"Button", // A standard button, checkbox, radio, etc
"ComboBox", // A combobox control
"Edit", // A textbox / multiline edit box
"ListBox", // A listbox control
"MDIClient", // MDIClient child window
"ScrollBar", // A scrollbar
"Static", // Any static text
"msctls_progress32", // A progress bar
"tooltips_class32", // Creates tooltip controls
"msctls_trackbar32", // Creates a range slider control
"msctls_updown32", // Creates an integral range edit box
"SysListView32", // Creates listview controls, extended listbox
"SysTabControl32", // Creates a tabcontrol
"SysTreeView32", // Creates a treeview control
};
for (auto& Class : DefWndClasses)
{
if (_strnicmp(ClassName, Class, strlen(ClassName)) == 0)
{
Subclass = true;
return ClassName;
}
}
// Check if we already registered this class...
if (GetClassInfoExA(GetModuleHandle(NULL), ClassName, &ExInfo))
{
// This is an existing class...
if (ExInfo.style == ClassStyle && ExInfo.lpfnWndProc == (WNDPROC)&Control::InternalWndProc)
return ClassName;
// We need to make a modified class, using the ClassName + Style
auto nClassName = String(ClassName) + String::Format(".%x", ClassStyle);
2022-05-21 19:58:09 +02:00
ExInfo.style = ClassStyle;
ExInfo.lpszClassName = (const char*)nClassName;
ExInfo.hCursor = LoadCursor(NULL, IDC_ARROW);
Subclass = true;
RegisterClassExA(&ExInfo);
return nClassName;
}
else
{
// This is a non-existing class, register everything
ExInfo.style = ClassStyle;
ExInfo.lpszClassName = ClassName;
ExInfo.lpfnWndProc = (WNDPROC)&Control::InternalWndProc;
ExInfo.hInstance = GetModuleHandle(NULL);
ExInfo.hbrBackground = (HBRUSH)GetStockObject(HOLLOW_BRUSH);
ExInfo.hCursor = LoadCursor(NULL, IDC_ARROW);
Subclass = false;
RegisterClassExA(&ExInfo);
return String(ClassName);
2022-05-21 19:58:09 +02:00
}
}
}