Proposal: Splitter control
Proposal: Splitter control
Provide a splitter control to enable resizing multiple panes. It should be flexible and must work well with different kinds of layout panels such as Grid, SplitView etc...
Summary
Many apps tend to have layouts with multiple panes that can be resized by the user if they want to see more content in a pane. Today WinUI doesn't provide anything for that so the developer must create their own. Windows Community Toolkit has a GridSplitter control however I believe it only resizing between rows or columns in a Grid and doesn't work with other layout panels which isn't ideal. Also, WCT means it can be used only by Windows apps written in C# or Microsoft Visual Basic and not all Windows apps.
Rationale
- Commonly used functionality should be built into the framework.
Scope
| Capability | Priority |
|---|---|
| This proposal will allow developers to make their multi-pane layout resizable | Must |
yes
I think this control is very important
Not sure how I wasn't tracking this one, but for context in WCT 8.0 we reworked GridSplitter and created a base class to enable two other modes of operations as well for better generalization and enable things like resizing NavigationView. Blog post here: https://devblogs.microsoft.com/ifdef-windows/announcing-windows-community-toolkit-v8-0/#sizers
GridSplitter itself is also a WPF polyfill.
Windows Community Toolkit is c# only and therefore not a solution.
This should be very important for C++/WinRT, so I must use something like the code below. I have to say that there are a lot of controls which cannot use because the community toolkit is only for C#. But for some reason, it doesn’t work properly, and I have no idea why... 😢 I’m a beginner, could someone please help me?
<Border
x:Name="ColumnSplitter"
Grid.Column="1"
Width="5"
Background="Transparent"
PointerEntered="ColumnSplitter_PointerEntered"
PointerExited="ColumnSplitter_PointerExited"
PointerMoved="ColumnSplitter_PointerMoved"
PointerPressed="ColumnSplitter_PointerPressed"
PointerReleased="ColumnSplitter_PointerReleased">
<!-- viusal spilter -->
<Border
Width="1"
HorizontalAlignment="Center"
Background="{ThemeResource SystemControlBackgroundBaseLowBrush}" />
</Border>
#pragma region Spilter
void WebViewPage::ColumnSplitter_PointerEntered(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& /*e*/)
{
try
{
//auto appWindow = WDHH::WindowHelper::GetAppWindowForElement(*this);
// or this way that use the current window to get AppWindow
//Microsoft::UI::Xaml::Window window = Microsoft::UI::Xaml::Window::Current();
//auto appWindow = window.AppWindow();
// 此 api 暂不可用。参考:
// https://learn.microsoft.com/en-us/windows/windows-app-sdk/api/winrt/microsoft.ui.input.inputsystemcursorshape
//
// 缓存光标对象为成员(可在类定义里声明:Microsoft::UI::Input::InputSystemCursor m_sizeWestEastCursor{ nullptr };)
//if (!m_sizeWestEastCursor)
//{
// m_sizeWestEastCursor = Microsoft::UI::Input::InputSystemCursor::Create(Microsoft::UI::Input::InputSystemCursorShape::SizeWestEast);
//}
//// 将光标应用到触发事件的元素上(sender 通常是分割条的 FrameworkElement)
//if (auto fe = sender.try_as<winrt::Microsoft::UI::Xaml::FrameworkElement>())
//{
// fe.PointerCursor(m_sizeWestEastCursor);
//}
auto hwnd = WDHH::WindowHelper::GetNativeWindowHandleForElement(*this);
// fallback: 非推荐,但便于快速在桌面测试
if (!hwnd)
{
hwnd = ::GetActiveWindow();
}
if (hwnd)
{
InstallCursorSubclass(hwnd);
}
// 暂时无使用解决办法,已注释,等待完整的 WinUI3 API 支持
//SetOverrideCursor(::LoadCursor(NULL, IDC_SIZEWE));
//::SetCursor(::LoadCursor(NULL, IDC_SIZEWE));
//SetCursor((HCURSOR)LoadImage(NULL, MAKEINTRESOURCE(IDC_SIZEWE), IMAGE_CURSOR, 0, 0, LR_SHARED)); // old UWP API not work at WINUI3......
// https://learn.microsoft.com/en-us/uwp/api/windows.ui.core.corewindow.pointercursor
//Microsoft::UI::Input::InputCursor::CreateFromCoreCursor()
//Windows::UI::Core::CoreWindow().PointerCursor(CoreCursor(CoreCursorType.SizeWestEast, 0));
}
catch (...)
{ /* ignore in environments without mouse */
}
}
void WebViewPage::ColumnSplitter_PointerExited(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& /*e*/)
{
// 恢复光标
if (_isDragging) return;
try
{
// 恢复为默认箭头光标
//::SetCursor(::LoadCursor(nullptr, IDC_ARROW));
}
catch (...)
{ /* ignore */
}
}
void WebViewPage::ColumnSplitter_PointerPressed(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& e)
{
_isDragging = true;
// 记录起始位置与初始宽度
_dragStartX = e.GetCurrentPoint(nullptr).Position().X;
_startWidth = TocColumn().ActualWidth();
// 捕获指针,避免拖动时丢失事件
if (auto fe = sender.try_as<FrameworkElement>())
{
fe.CapturePointer(e.Pointer());
}
// 在按下并开始拖动时也保持调整光标
//::SetCursor(::LoadCursor(nullptr, IDC_SIZEWE));
e.Handled(true);
}
void WebViewPage::ColumnSplitter_PointerMoved(winrt::Windows::Foundation::IInspectable const& /*sender*/, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& e)
{
if (!_isDragging)
return;
const double currentX = e.GetCurrentPoint(nullptr).Position().X;
const double delta = currentX - _dragStartX;
double newWidth = _startWidth + delta;
// 约束到 ColumnDefinition 的 Min/Max 范围
const double min = std::max(0.0, TocColumn().MinWidth());
const double max = TocColumn().MaxWidth(); // 若为无穷大,min/max 仍然工作良好
newWidth = std::max(min, newWidth);
if (max > 0) // 正数即表示存在最大值(无穷大也 > 0,不会改变结果)
{
newWidth = std::min(max, newWidth);
}
TocColumn().Width(GridLengthHelper::FromPixels(newWidth));
// 保持调整光标在拖动期间
//::SetCursor(::LoadCursor(nullptr, IDC_SIZEWE));
e.Handled(true);
}
void WebViewPage::ColumnSplitter_PointerReleased(winrt::Windows::Foundation::IInspectable const& sender, winrt::Microsoft::UI::Xaml::Input::PointerRoutedEventArgs const& e)
{
if (!_isDragging)
return;
_isDragging = false;
if (auto fe = sender.try_as<FrameworkElement>())
{
fe.ReleasePointerCapture(e.Pointer());
}
// 拖动结束,恢复默认光标
//::SetCursor(::LoadCursor(nullptr, IDC_ARROW));
e.Handled(true);
}
#include <commctrl.h>
#include <atomic>
#pragma comment(lib, "Comctl32.lib")
static std::atomic<HCURSOR> g_overrideCursor{ nullptr };
LRESULT CALLBACK CursorSubclassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam,
UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
{
if (msg == WM_SETCURSOR)
{
HCURSOR h = g_overrideCursor.load();
if (h)
{
::SetCursor(h);
// 返回 TRUE/非零 表示已处理(不让默认代码覆盖)
return TRUE;
}
}
// 未处理则交给默认子类处理
return DefSubclassProc(hwnd, msg, wParam, lParam);
}
void WebViewPage::InstallCursorSubclass(HWND hwnd)
{
if (hwnd)
{
SetWindowSubclass(hwnd, CursorSubclassProc, 1, 0);
}
}
void WebViewPage::RemoveCursorSubclass(HWND hwnd)
{
if (hwnd)
{
RemoveWindowSubclass(hwnd, CursorSubclassProc, 1);
}
}
void WebViewPage::SetOverrideCursor(HCURSOR hCursor)
{
g_overrideCursor.store(hCursor);
// 立即刷新当前窗口下的光标显示(触发 WM_SETCURSOR)
POINT pt;
if (::GetCursorPos(&pt))
{
HWND hwnd = ::WindowFromPoint(pt);
if (hwnd)
{
// MAKELPARAM(HTCLIENT, WM_MOUSEMOVE) 常用于模拟鼠标移动触发 WM_SETCURSOR
::SendMessage(hwnd, WM_SETCURSOR, reinterpret_cast<WPARAM>(hwnd), MAKELPARAM(HTCLIENT, WM_MOUSEMOVE));
}
}
}
#pragma endregion
}
If someone wants to use similar controls in C++, you can check out my repository: https://github.com/lgztx96/CommunityToolkit.WinUI. It ports most of the Community Toolkit controls to C++.
@lgztx96 Thank you for your work🫡, and welcome to join us at qq:479734355。https://github.com/HO-COOH/WinUIEssentials。This is a samilar work for winui3cppwinrt
@lgztx96 Thank you for your work🫡, and welcome to join us at qq:479734355。https://github.com/HO-COOH/WinUIEssentials。This is a samilar work for winui3cppwinrt Sure, I’ll take a look.