Skip to content

Commit c0e54a6

Browse files
committed
Add ContextMenu Support
1 parent abe0fe3 commit c0e54a6

File tree

4 files changed

+240
-0
lines changed

4 files changed

+240
-0
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
using Windows.ApplicationModel;
2+
using Windows.Storage;
3+
using Windows.System.Profile;
4+
5+
namespace CommunityToolkit.WinUI.Controls;
6+
internal static class InfoHelper
7+
{
8+
public static Version AppVersion { get; } = new Version(
9+
Package.Current.Id.Version.Major,
10+
Package.Current.Id.Version.Minor,
11+
Package.Current.Id.Version.Build,
12+
Package.Current.Id.Version.Revision
13+
);
14+
15+
public static Version SystemVersion { get; }
16+
17+
public static SystemDataPaths SystemDataPath { get; } = SystemDataPaths.GetDefault();
18+
19+
public static UserDataPaths UserDataPath { get; } = UserDataPaths.GetDefault();
20+
21+
public static string AppInstalledLocation { get; } = Package.Current.InstalledLocation.Path;
22+
23+
static InfoHelper()
24+
{
25+
string systemVersion = AnalyticsInfo.VersionInfo.DeviceFamilyVersion;
26+
ulong version = ulong.Parse(systemVersion);
27+
SystemVersion = new Version(
28+
(int)((version & 0xFFFF000000000000L) >> 48),
29+
(int)((version & 0x0000FFFF00000000L) >> 32),
30+
(int)((version & 0x00000000FFFF0000L) >> 16),
31+
(int)(version & 0x000000000000FFFFL)
32+
);
33+
}
34+
}
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
using System.Runtime.InteropServices;
2+
3+
namespace CommunityToolkit.WinUI.Controls;
4+
internal static class NativeMethods
5+
{
6+
public enum WindowMessage : int
7+
{
8+
WM_NCLBUTTONDOWN = 0x00A1,
9+
WM_NCRBUTTONDOWN = 0x00A4,
10+
WM_SYSCOMMAND = 0x0112,
11+
WM_SYSMENU = 0x0313,
12+
WM_GETMINMAXINFO = 0x0024
13+
}
14+
[Flags]
15+
public enum WindowStyle : uint
16+
{
17+
WS_SYSMENU = 0x80000
18+
}
19+
20+
[Flags]
21+
public enum WindowLongIndexFlags : int
22+
{
23+
GWL_WNDPROC = -4,
24+
GWL_STYLE = -16
25+
}
26+
27+
[Flags]
28+
public enum SetWindowPosFlags : uint
29+
{
30+
/// <summary>
31+
/// Retains the current position (ignores X and Y parameters).
32+
/// </summary>
33+
SWP_NOMOVE = 0x0002
34+
}
35+
36+
public enum SystemCommand
37+
{
38+
SC_MOUSEMENU = 0xF090,
39+
SC_KEYMENU = 0xF100
40+
}
41+
42+
[DllImport("user32.dll", EntryPoint = "GetWindowLongW", SetLastError = false)]
43+
public static extern int GetWindowLong(IntPtr hWnd, int nIndex);
44+
45+
[DllImport("user32.dll", EntryPoint = "GetWindowLongPtrW", SetLastError = false)]
46+
public static extern int GetWindowLongPtr(IntPtr hWnd, int nIndex);
47+
48+
public static int GetWindowLongAuto(IntPtr hWnd, int nIndex)
49+
{
50+
if (IntPtr.Size is 8)
51+
{
52+
return GetWindowLongPtr(hWnd, nIndex);
53+
}
54+
else
55+
{
56+
return GetWindowLong(hWnd, nIndex);
57+
}
58+
}
59+
60+
[DllImport("user32.dll", EntryPoint = "FindWindowExW", SetLastError = true, CharSet = CharSet.Unicode)]
61+
public static extern IntPtr FindWindowEx(IntPtr hWndParent, IntPtr hWndChildAfter, string lpszClass, string lpszWindow);
62+
63+
64+
[DllImport("user32.dll", EntryPoint = "SetWindowLongW", SetLastError = false)]
65+
public static extern IntPtr SetWindowLong(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
66+
67+
[DllImport("user32.dll", EntryPoint = "SetWindowLongPtrW", SetLastError = false)]
68+
public static extern IntPtr SetWindowLongPtr(IntPtr hWnd, int nIndex, IntPtr dwNewLong);
69+
70+
public static IntPtr SetWindowLongAuto(IntPtr hWnd, int nIndex, IntPtr dwNewLong)
71+
{
72+
if (IntPtr.Size is 8)
73+
{
74+
return SetWindowLongPtr(hWnd, nIndex, dwNewLong);
75+
}
76+
else
77+
{
78+
return SetWindowLong(hWnd, nIndex, dwNewLong);
79+
}
80+
}
81+
82+
[DllImport("user32.dll")]
83+
public static extern IntPtr CallWindowProc(IntPtr lpPrevWndFunc, IntPtr hWnd, WindowMessage Msg, IntPtr wParam, IntPtr lParam);
84+
}

components/TitleBar/src/TitleBar.WASDK.cs

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ namespace CommunityToolkit.WinUI.Controls;
1515

1616
public partial class TitleBar : Control
1717
{
18+
WndProcHelper WndProcHelper;
19+
MenuFlyout MenuFlyout;
1820
ContentPresenter? PART_ContentPresenter;
1921
ContentPresenter? PART_FooterPresenter;
2022

@@ -29,6 +31,14 @@ private void SetWASDKTitleBar()
2931
{
3032
Window.AppWindow.TitleBar.ExtendsContentIntoTitleBar = true;
3133

34+
if (this.ContextFlyout != null && this.ContextFlyout is MenuFlyout menuFlyout)
35+
{
36+
this.MenuFlyout = menuFlyout;
37+
WndProcHelper = new WndProcHelper(this.Window);
38+
WndProcHelper.RegisterWndProc(WindowWndProc);
39+
WndProcHelper.RegisterInputNonClientPointerSourceWndProc(InputNonClientPointerSourceWndProc);
40+
}
41+
3242
this.Window.SizeChanged -= Window_SizeChanged;
3343
this.Window.SizeChanged += Window_SizeChanged;
3444
this.Window.Activated -= Window_Activated;
@@ -161,5 +171,68 @@ public void ClearDragRegions(NonClientRegionKind nonClientRegionKind)
161171
var noninputsrc = InputNonClientPointerSource.GetForWindowId(Window.AppWindow.Id);
162172
noninputsrc.ClearRegionRects(nonClientRegionKind);
163173
}
174+
175+
private IntPtr InputNonClientPointerSourceWndProc(IntPtr hWnd, NativeMethods.WindowMessage Msg, IntPtr wParam, IntPtr lParam)
176+
{
177+
switch (Msg)
178+
{
179+
case NativeMethods.WindowMessage.WM_NCLBUTTONDOWN:
180+
{
181+
if (MenuFlyout.IsOpen)
182+
{
183+
MenuFlyout.Hide();
184+
}
185+
break;
186+
}
187+
case NativeMethods.WindowMessage.WM_NCRBUTTONDOWN:
188+
{
189+
PointInt32 pt = new PointInt32(lParam.ToInt32() & 0xFFFF, lParam.ToInt32() >> 16);
190+
FlyoutShowOptions options = new FlyoutShowOptions();
191+
options.ShowMode = FlyoutShowMode.Standard;
192+
options.Position = InfoHelper.SystemVersion.Build >= 22000 ?
193+
new Windows.Foundation.Point((pt.X - this.Window.AppWindow.Position.X - 8) / XamlRoot.RasterizationScale, (pt.Y - this.Window.AppWindow.Position.Y) / XamlRoot.RasterizationScale) :
194+
new Windows.Foundation.Point(pt.X - this.Window.AppWindow.Position.X - 8, pt.Y - this.Window.AppWindow.Position.Y);
195+
196+
MenuFlyout.ShowAt(this, options);
197+
return (IntPtr)0;
198+
}
199+
}
200+
return WndProcHelper.CallInputNonClientPointerSourceWindowProc(hWnd, Msg, wParam, lParam);
201+
}
202+
203+
private IntPtr WindowWndProc(IntPtr hWnd, NativeMethods.WindowMessage Msg, IntPtr wParam, IntPtr lParam)
204+
{
205+
switch (Msg)
206+
{
207+
case NativeMethods.WindowMessage.WM_SYSMENU:
208+
{
209+
return (IntPtr)0;
210+
}
211+
212+
case NativeMethods.WindowMessage.WM_SYSCOMMAND:
213+
{
214+
NativeMethods.SystemCommand sysCommand = (NativeMethods.SystemCommand)(wParam.ToInt32() & 0xFFF0);
215+
216+
if (sysCommand is NativeMethods.SystemCommand.SC_MOUSEMENU)
217+
{
218+
FlyoutShowOptions options = new FlyoutShowOptions();
219+
options.Position = new Windows.Foundation.Point(0, 15);
220+
options.ShowMode = FlyoutShowMode.Standard;
221+
MenuFlyout.ShowAt(null, options);
222+
return (IntPtr)0;
223+
}
224+
else if (sysCommand is NativeMethods.SystemCommand.SC_KEYMENU)
225+
{
226+
FlyoutShowOptions options = new FlyoutShowOptions();
227+
options.Position = new Windows.Foundation.Point(0, 45);
228+
options.ShowMode = FlyoutShowMode.Standard;
229+
MenuFlyout.ShowAt(null, options);
230+
return (IntPtr)0;
231+
}
232+
break;
233+
}
234+
}
235+
return WndProcHelper.CallWindowProc(hWnd, Msg, wParam, lParam);
236+
}
164237
}
165238
#endif
Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
using System.Runtime.InteropServices;
2+
using WinRT.Interop;
3+
4+
namespace CommunityToolkit.WinUI.Controls;
5+
internal class WndProcHelper
6+
{
7+
public delegate IntPtr WNDPROC(IntPtr hWnd, NativeMethods.WindowMessage Msg, IntPtr wParam, IntPtr lParam);
8+
9+
private IntPtr Handle { get; set; }
10+
private WNDPROC newMainWindowWndProc = null;
11+
private IntPtr oldMainWindowWndProc = IntPtr.Zero;
12+
13+
private WNDPROC newInputNonClientPointerSourceWndProc = null;
14+
private IntPtr oldInputNonClientPointerSourceWndProc = IntPtr.Zero;
15+
16+
public WndProcHelper(Window window)
17+
{
18+
Handle = WindowNative.GetWindowHandle(window);
19+
}
20+
21+
public IntPtr CallWindowProc(IntPtr hWnd, NativeMethods.WindowMessage Msg, IntPtr wParam, IntPtr lParam)
22+
{
23+
return NativeMethods.CallWindowProc(oldMainWindowWndProc, hWnd, Msg, wParam, lParam);
24+
}
25+
26+
public IntPtr CallInputNonClientPointerSourceWindowProc(IntPtr hWnd, NativeMethods.WindowMessage Msg, IntPtr wParam, IntPtr lParam)
27+
{
28+
return NativeMethods.CallWindowProc(oldInputNonClientPointerSourceWndProc, hWnd, Msg, wParam, lParam);
29+
}
30+
public void RegisterWndProc(WNDPROC wndProc)
31+
{
32+
newMainWindowWndProc = wndProc;
33+
oldMainWindowWndProc = NativeMethods.SetWindowLongAuto(Handle, (int)NativeMethods.WindowLongIndexFlags.GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(newMainWindowWndProc));
34+
}
35+
36+
public void RegisterInputNonClientPointerSourceWndProc(WNDPROC wndProc)
37+
{
38+
IntPtr inputNonClientPointerSourceHandle = NativeMethods.FindWindowEx(Handle, IntPtr.Zero, "InputNonClientPointerSource", null);
39+
40+
if (inputNonClientPointerSourceHandle != IntPtr.Zero)
41+
{
42+
int style = NativeMethods.GetWindowLongAuto(Handle, (int)NativeMethods.WindowLongIndexFlags.GWL_STYLE);
43+
NativeMethods.SetWindowLongAuto(Handle, (int)NativeMethods.WindowLongIndexFlags.GWL_STYLE, (IntPtr)(style & ~(int)NativeMethods.WindowStyle.WS_SYSMENU));
44+
45+
newInputNonClientPointerSourceWndProc = wndProc;
46+
oldInputNonClientPointerSourceWndProc = NativeMethods.SetWindowLongAuto(inputNonClientPointerSourceHandle, (int)NativeMethods.WindowLongIndexFlags.GWL_WNDPROC, Marshal.GetFunctionPointerForDelegate(newInputNonClientPointerSourceWndProc));
47+
}
48+
}
49+
}

0 commit comments

Comments
 (0)