microsoft-ui-xaml icon indicating copy to clipboard operation
microsoft-ui-xaml copied to clipboard

Proposal: Splitter control

Open jaigak opened this issue 4 years ago • 8 comments

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

jaigak avatar Jul 02 '21 11:07 jaigak

yes

FireCubeStudios avatar Jul 02 '21 11:07 FireCubeStudios

I think this control is very important

shelllet avatar Jul 03 '21 03:07 shelllet

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.

michael-hawker avatar Mar 28 '25 19:03 michael-hawker

Windows Community Toolkit is c# only and therefore not a solution.

tpoint75 avatar Apr 02 '25 05:04 tpoint75

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

}

hoshiizumiya avatar Sep 24 '25 14:09 hoshiizumiya

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++.

Image

lgztx96 avatar Nov 02 '25 16:11 lgztx96

@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

hoshiizumiya avatar Nov 02 '25 17:11 hoshiizumiya

@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.

lgztx96 avatar Nov 03 '25 11:11 lgztx96