From 796ef41e3ff6ef276be257c4d64a0ee7a2b435ee Mon Sep 17 00:00:00 2001 From: Dan Clark Date: Sun, 17 Nov 2024 21:25:27 +0000 Subject: [PATCH] Add some WinUI work --- Toolkit.Avalonia/ContentTemplate.cs | 1 - .../AsyncHandlerInitialization.cs | 8 +- Toolkit.Foundation/AsyncResponseEventArgs.cs | 2 +- Toolkit.Foundation/DefaultHostBuilder.cs | 14 +-- Toolkit.Foundation/HandlerInitialization.cs | 6 +- Toolkit.Foundation/IMessengerExtensions.cs | 24 +++- Toolkit.UI.WinUI/NotifyIconExtensions.cs | 21 ++++ Toolkit.UI.WinUI/Toolkit.UI.WinUI.csproj | 1 + Toolkit.UI.WinUI/WindowExtensions.cs | 105 +++++++++++++++--- Toolkit.WinUI/ContentTemplate.cs | 45 ++++++++ Toolkit.WinUI/IServiceCollectionExtensions.cs | 2 + Toolkit.WinUI/TemplateControl.cs | 79 +++++++++++++ Toolkit.WinUI/Toolkit.WinUI.csproj | 3 +- .../{IPointerMonitor.cs => IPointer.cs} | 2 +- Toolkit.Windows/NativeMethods.txt | 82 ++++++++------ Toolkit.Windows/NotifyIcon.cs | 27 +++-- Toolkit.Windows/NotifyIconInvokedEventArgs.cs | 3 +- Toolkit.Windows/PInvoke.cs | 5 + .../{PointerMonitor.cs => Pointer.cs} | 12 +- Toolkit.Windows/Rect.cs | 3 + Toolkit.Windows/Taskbar.cs | 4 +- Toolkit.Windows/TaskbarButtonMonitor.cs | 4 +- Toolkit.Windows/TaskbarList.cs | 6 +- Toolkit.Windows/WindowHelper.cs | 47 ++++---- Toolkit.Windows/WndProc.cs | 79 +++++++------ 25 files changed, 426 insertions(+), 159 deletions(-) create mode 100644 Toolkit.UI.WinUI/NotifyIconExtensions.cs create mode 100644 Toolkit.WinUI/ContentTemplate.cs create mode 100644 Toolkit.WinUI/TemplateControl.cs rename Toolkit.Windows/{IPointerMonitor.cs => IPointer.cs} (73%) rename Toolkit.Windows/{PointerMonitor.cs => Pointer.cs} (94%) diff --git a/Toolkit.Avalonia/ContentTemplate.cs b/Toolkit.Avalonia/ContentTemplate.cs index 3dc3d0f..ba2d464 100644 --- a/Toolkit.Avalonia/ContentTemplate.cs +++ b/Toolkit.Avalonia/ContentTemplate.cs @@ -1,6 +1,5 @@ using Avalonia.Controls; using Avalonia.Controls.Templates; -using Avalonia.Data; using Avalonia.Interactivity; using Microsoft.Extensions.DependencyInjection; using Toolkit.Foundation; diff --git a/Toolkit.Foundation/AsyncHandlerInitialization.cs b/Toolkit.Foundation/AsyncHandlerInitialization.cs index 2ba37f9..2026d1a 100644 --- a/Toolkit.Foundation/AsyncHandlerInitialization.cs +++ b/Toolkit.Foundation/AsyncHandlerInitialization.cs @@ -7,14 +7,14 @@ public class AsyncHandlerInitialization(IServiceP IInitialization where THandler : class, IAsyncHandler where TMessage : class { - public void Initialize() => WeakReferenceMessenger.Default.Register>(provider, - async (provider, args) => args.Reply(await provider.GetRequiredService().Handle(args.Message, args.CancellationToken))); + public void Initialize() => StrongReferenceMessenger.Default.Register>(provider, + (provider, args) => args.Reply(provider.GetRequiredService().Handle(args.Message, args.CancellationToken))); } public class AsyncHandlerInitialization(IServiceProvider provider) : IInitialization where THandler : class, IAsyncHandler where TMessage : class { - public void Initialize() => WeakReferenceMessenger.Default.Register>(provider, - async (provider, args) => await provider.GetRequiredService().Handle(args.Message, args.CancellationToken)); + public void Initialize() => StrongReferenceMessenger.Default.Register>(provider, + (provider, args) => provider.GetRequiredService().Handle(args.Message, args.CancellationToken)); } \ No newline at end of file diff --git a/Toolkit.Foundation/AsyncResponseEventArgs.cs b/Toolkit.Foundation/AsyncResponseEventArgs.cs index 559812d..c843e36 100644 --- a/Toolkit.Foundation/AsyncResponseEventArgs.cs +++ b/Toolkit.Foundation/AsyncResponseEventArgs.cs @@ -5,7 +5,7 @@ namespace Toolkit.Foundation; public class AsyncResponseEventArgs : AsyncRequestMessage { - public TMessage? Message { get; set; } + public required TMessage Message { get; set; } public CancellationToken CancellationToken { get; set; } } \ No newline at end of file diff --git a/Toolkit.Foundation/DefaultHostBuilder.cs b/Toolkit.Foundation/DefaultHostBuilder.cs index 082fca9..1a792bc 100644 --- a/Toolkit.Foundation/DefaultHostBuilder.cs +++ b/Toolkit.Foundation/DefaultHostBuilder.cs @@ -20,14 +20,8 @@ public class DefaultHostBuilder : ComponentHostCollection>(); services.AddSingleton(); - services.AddSingleton(_ => WeakReferenceMessenger.Default); + services.AddSingleton(_ => StrongReferenceMessenger.Default); - //services.AddScoped(); - - //services.AddTransient(); - //services.AddScoped(); - //services.AddTransient(); - //services.AddTransient(); services.AddTransient(); services.AddTransient(); @@ -57,10 +51,10 @@ public class DefaultHostBuilder : services.AddTransient(); services.AddTransient(); - //services.AddHandler(); - //services.AddHandler(); + services.AddHandler(); + services.AddHandler(); - //services.AddInitialization(); + services.AddInitialization(); services.AddHostedService(); }); } diff --git a/Toolkit.Foundation/HandlerInitialization.cs b/Toolkit.Foundation/HandlerInitialization.cs index 2748ad2..3648eec 100644 --- a/Toolkit.Foundation/HandlerInitialization.cs +++ b/Toolkit.Foundation/HandlerInitialization.cs @@ -7,7 +7,7 @@ public class HandlerInitialization(IServiceProvid IInitialization where THandler : class, IHandler where TMessage : class { - public void Initialize() => WeakReferenceMessenger.Default.Register>(provider, + public void Initialize() => StrongReferenceMessenger.Default.Register>(provider, (provider, args) => args.Reply(provider.GetRequiredService().Handle(args.Message))); } @@ -15,7 +15,7 @@ public class HandlerInitialization(IServiceProvider provider IInitialization where THandler : class, IHandler where TMessage : class { - public void Initialize() => WeakReferenceMessenger.Default.Register(provider, + public void Initialize() => StrongReferenceMessenger.Default.Register(provider, (provider, args) => provider.GetRequiredService().Handle(args)); } @@ -23,6 +23,6 @@ public class HandlerKeyedInitialization(string key, IService IInitialization where THandler : class, IHandler where TMessage : class { - public void Initialize() => WeakReferenceMessenger.Default.Register(provider, key, + public void Initialize() => StrongReferenceMessenger.Default.Register(provider, key, (provider, args) => provider.GetRequiredKeyedService(key).Handle(args)); } \ No newline at end of file diff --git a/Toolkit.Foundation/IMessengerExtensions.cs b/Toolkit.Foundation/IMessengerExtensions.cs index 32c5b7e..3fe4363 100644 --- a/Toolkit.Foundation/IMessengerExtensions.cs +++ b/Toolkit.Foundation/IMessengerExtensions.cs @@ -11,9 +11,27 @@ public static class IMessengerExtensions return args.Response; } - public static void Send(this IMessenger messenger, string key) - where TMessage : class, new() => messenger.Send(new TMessage(), key); + public static TResponse Send(this IMessenger messenger, + TMessage message) + where TMessage : class + { + ResponseEventArgs args = messenger.Send(new ResponseEventArgs { Message = message }); + return args.Response; + } + + public static void Send(this IMessenger messenger, + TMessage message, string key) where TMessage : class => + messenger.Send(message, key); + + public static void Send(this IMessenger messenger, + string key) where TMessage : class, new() => + messenger.Send(new TMessage(), key); public static async Task SendAsync(this IMessenger messenger) - where TMessage : class, new() => await messenger.Send(new AsyncResponseEventArgs { Message = new TMessage() }); + where TMessage : class, new() => + await messenger.Send(new AsyncResponseEventArgs { Message = new TMessage() }); + + public static async Task SendAsync(this IMessenger messenger, + TMessage message) where TMessage : class => + await messenger.Send(new AsyncResponseEventArgs { Message = message }); } diff --git a/Toolkit.UI.WinUI/NotifyIconExtensions.cs b/Toolkit.UI.WinUI/NotifyIconExtensions.cs new file mode 100644 index 0000000..cc8bf93 --- /dev/null +++ b/Toolkit.UI.WinUI/NotifyIconExtensions.cs @@ -0,0 +1,21 @@ +using System.Drawing; +using System.IO; +using Toolkit.Windows; +using Toolkit.WinUI; + +namespace Toolkit.UI.WinUI; + +public static class NotifyIconExtensions +{ + public static void SetIcon(this INotifyIcon notifyIcon, + Stream? stream) + { + nint shellTrayHandle = WindowHelper.GetWindowHandle("Shell_TrayWnd"); + uint dpi = WindowHelper.GetDpi(shellTrayHandle); + + if (stream?.ConvertToIcon(dpi) is Icon icon) + { + notifyIcon.SetIcon(icon.Handle); + } + } +} diff --git a/Toolkit.UI.WinUI/Toolkit.UI.WinUI.csproj b/Toolkit.UI.WinUI/Toolkit.UI.WinUI.csproj index 1e8e1cf..6c74cee 100644 --- a/Toolkit.UI.WinUI/Toolkit.UI.WinUI.csproj +++ b/Toolkit.UI.WinUI/Toolkit.UI.WinUI.csproj @@ -23,5 +23,6 @@ + diff --git a/Toolkit.UI.WinUI/WindowExtensions.cs b/Toolkit.UI.WinUI/WindowExtensions.cs index ec5ddb7..21993d6 100644 --- a/Toolkit.UI.WinUI/WindowExtensions.cs +++ b/Toolkit.UI.WinUI/WindowExtensions.cs @@ -7,6 +7,9 @@ using Windows.Win32.Graphics.Gdi; using System.Drawing; using Windows.Win32.UI.Shell; using Windows.Win32.UI.WindowsAndMessaging; +using Toolkit.Windows; +using Rect = Windows.Foundation.Rect; +using WinUIEx; namespace Toolkit.UI.WinUI; @@ -14,20 +17,78 @@ public static partial class WindowExtensions { private static SUBCLASSPROC? SubClassDelegate; - public static void SetBorderless(this Window window, + public static void Hide(this Window window) + { + nint handle = WindowNative.GetWindowHandle(window); + if (handle == 0) return; + + WindowHelper.HideWindow(new HWND(handle)); + } + + public static void IsShownInSwitchers(this Window window, bool value) { - if (window.AppWindow is AppWindow appWindow && - appWindow.Presenter is OverlappedPresenter presenter) + if (window.AppWindow is AppWindow appWindow) { - presenter.IsMaximizable = !value; - presenter.IsMinimizable = !value; - presenter.IsResizable = !value; - presenter.SetBorderAndTitleBar(!value, !value); + appWindow.IsShownInSwitchers = value; } } - public static void SetTransparency(this Window window, + public static void MoveAndResize(this Window window, + Rect rect) + { + nint handle = WindowNative.GetWindowHandle(window); + if (handle == 0) return; + + WindowHelper.MoveAndResizeWindow(new HWND(handle), + (int)rect.Left, + (int)rect.Top, + (int)rect.Width, + (int)rect.Height); + } + + public static void SetBorderless(this Window window, + bool value) + { + WindowStyle windowStyle = window.GetWindowStyle(); + + if (value) + { + windowStyle &= ~(WindowStyle.Caption | + WindowStyle.ThickFrame | + WindowStyle.Border | + WindowStyle.SysMenu); + } + else + { + windowStyle |= WindowStyle.Caption | + WindowStyle.ThickFrame | + WindowStyle.Border | + WindowStyle.SysMenu; + } + + window.SetWindowStyle(windowStyle); + } + + public static void SetForeground(this Window window) + { + nint handle = WindowNative.GetWindowHandle(window); + if (handle == 0) return; + + WindowHelper.SetForegroundWindow(new HWND(handle)); + } + + public static void SetTopMost(this Window window, + bool value) + { + if (window.AppWindow is AppWindow appWindow && + appWindow.Presenter is OverlappedPresenter presenter) + { + presenter.IsAlwaysOnTop = value; + } + } + + public static void SetTransparency(this Window window, bool value) { nint handle = WindowNative.GetWindowHandle(window); @@ -44,18 +105,12 @@ public static partial class WindowExtensions } } - private static unsafe void EnableTransparency(HWND hWnd) + public static void Show(this Window window) { - SubClassDelegate = new SUBCLASSPROC(WindowSubClass); - _ = PInvoke.SetWindowSubclass(hWnd, SubClassDelegate, 0, 0); + nint handle = WindowNative.GetWindowHandle(window); + if (handle == 0) return; - WINDOW_EX_STYLE exStyle = (WINDOW_EX_STYLE)PInvoke.GetWindowLong(hWnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE); - _ = PInvoke.SetWindowLong(hWnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, - (int)(exStyle | WINDOW_EX_STYLE.WS_EX_LAYERED)); - - COLORREF blackColor = new COLORREF((uint)ToWin32(Color.Black)); - _ = PInvoke.SetLayeredWindowAttributes(hWnd, blackColor, 0, LAYERED_WINDOW_ATTRIBUTES_FLAGS.LWA_COLORKEY | - LAYERED_WINDOW_ATTRIBUTES_FLAGS.LWA_ALPHA); + WindowHelper.ShowWindow(new HWND(handle)); } private static unsafe void DisableTransparency(HWND hWnd) @@ -70,6 +125,20 @@ public static partial class WindowExtensions _ = PInvoke.SetWindowLong(hWnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, (int)(exStyle & ~WINDOW_EX_STYLE.WS_EX_LAYERED)); } + private static unsafe void EnableTransparency(HWND hWnd) + { + SubClassDelegate = new SUBCLASSPROC(WindowSubClass); + _ = PInvoke.SetWindowSubclass(hWnd, SubClassDelegate, 0, 0); + + WINDOW_EX_STYLE exStyle = (WINDOW_EX_STYLE)PInvoke.GetWindowLong(hWnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE); + _ = PInvoke.SetWindowLong(hWnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, + (int)(exStyle | WINDOW_EX_STYLE.WS_EX_LAYERED)); + + COLORREF blackColor = new COLORREF((uint)ToWin32(Color.Black)); + _ = PInvoke.SetLayeredWindowAttributes(hWnd, blackColor, 0, LAYERED_WINDOW_ATTRIBUTES_FLAGS.LWA_COLORKEY | + LAYERED_WINDOW_ATTRIBUTES_FLAGS.LWA_ALPHA); + } + private static int ToWin32(Color c) => c.B << 16 | c.G << 8 | c.R; private static unsafe LRESULT WindowSubClass(HWND hWnd, diff --git a/Toolkit.WinUI/ContentTemplate.cs b/Toolkit.WinUI/ContentTemplate.cs new file mode 100644 index 0000000..36212c5 --- /dev/null +++ b/Toolkit.WinUI/ContentTemplate.cs @@ -0,0 +1,45 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Markup; +using Toolkit.Foundation; + +namespace Toolkit.WinUI; + +public class ContentTemplate : + DataTemplateSelector, + IContentTemplate +{ + protected override DataTemplate? SelectTemplateCore(object item) + { + if (item is IObservableViewModel observableViewModel) + { + if (observableViewModel.Provider is IServiceProvider provider) + { + Type itemType = item.GetType(); + if (provider.GetRequiredKeyedService(itemType.Name.Replace("ViewModel", "")) + is IContentTemplateDescriptor descriptor) + { + return CreateDataTemplate(descriptor); + } + } + } + + return default; + } + + protected override DataTemplate? SelectTemplateCore(object item, + DependencyObject container) => SelectTemplateCore(item); + + private static DataTemplate CreateDataTemplate(IContentTemplateDescriptor descriptor) + { + string xamlString = @$" + + + "; + + return (DataTemplate)XamlReader.Load(xamlString); + } +} \ No newline at end of file diff --git a/Toolkit.WinUI/IServiceCollectionExtensions.cs b/Toolkit.WinUI/IServiceCollectionExtensions.cs index f501cc5..1e18240 100644 --- a/Toolkit.WinUI/IServiceCollectionExtensions.cs +++ b/Toolkit.WinUI/IServiceCollectionExtensions.cs @@ -11,6 +11,8 @@ public static class IServiceCollectionExtensions services.AddTransient(); services.AddSingleton(); + services.AddTransient(); + services.AddTransient((Func>)(provider => new ProxyServiceCollection(services => { diff --git a/Toolkit.WinUI/TemplateControl.cs b/Toolkit.WinUI/TemplateControl.cs new file mode 100644 index 0000000..b939f8a --- /dev/null +++ b/Toolkit.WinUI/TemplateControl.cs @@ -0,0 +1,79 @@ +using Microsoft.Extensions.DependencyInjection; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Xaml.Data; +using Toolkit.Foundation; + +namespace Toolkit.WinUI; + +[Bindable] +public class TemplateControl : + ContentControl +{ + public TemplateControl() + { + DefaultStyleKey = typeof(TemplateControl); + Loaded += OnLoaded; + } + + private void OnLoaded(object sender, + RoutedEventArgs args) + { + Loaded -= OnLoaded; + + if (DataContext is IObservableViewModel observableViewModel) + { + if (observableViewModel.Provider is IServiceProvider provider) + { + if (provider.GetRequiredKeyedService(DataContext.GetType().Name.Replace("ViewModel", "")) + is IContentTemplateDescriptor descriptor) + { + if (provider.GetRequiredKeyedService(descriptor.TemplateType, descriptor.Key) + is FrameworkElement control) + { + void HandleLoaded(object? sender, RoutedEventArgs args) + { + control.Loaded -= HandleLoaded; + if (control.DataContext is object content) + { + if (content is IActivation activation) + { + activation.IsActive = true; + } + } + } + + void HandleDataContextChanged(FrameworkElement? sender, DataContextChangedEventArgs args) + { + if (control.DataContext is object content) + { + if (content is IActivation activation) + { + activation.IsActive = true; + } + } + } + + void HandleUnloaded(object? sender, RoutedEventArgs args) + { + control.Unloaded -= HandleUnloaded; + if (control.DataContext is object content) + { + if (content is IActivation activation) + { + activation.IsActive = false; + } + } + } + + control.Loaded += HandleLoaded; + control.Unloaded += HandleUnloaded; + control.DataContextChanged += HandleDataContextChanged; + + Content = control; + } + } + } + } + } +} diff --git a/Toolkit.WinUI/Toolkit.WinUI.csproj b/Toolkit.WinUI/Toolkit.WinUI.csproj index 7d84f5a..ea8d044 100644 --- a/Toolkit.WinUI/Toolkit.WinUI.csproj +++ b/Toolkit.WinUI/Toolkit.WinUI.csproj @@ -15,14 +15,13 @@ true 10.0.19041.41 - + - diff --git a/Toolkit.Windows/IPointerMonitor.cs b/Toolkit.Windows/IPointer.cs similarity index 73% rename from Toolkit.Windows/IPointerMonitor.cs rename to Toolkit.Windows/IPointer.cs index 739b7b2..f0990b7 100644 --- a/Toolkit.Windows/IPointerMonitor.cs +++ b/Toolkit.Windows/IPointer.cs @@ -2,6 +2,6 @@ namespace Toolkit.Windows; -public interface IPointerMonitor : +public interface IPointer : IInitialization, IDisposable; \ No newline at end of file diff --git a/Toolkit.Windows/NativeMethods.txt b/Toolkit.Windows/NativeMethods.txt index d7aac73..cf8ad71 100644 --- a/Toolkit.Windows/NativeMethods.txt +++ b/Toolkit.Windows/NativeMethods.txt @@ -1,41 +1,51 @@ -SetWindowsHookEx -GetModuleHandle -CallNextHookEx -GetPhysicalCursorPos -FindWindowEx -FindWindow -GetWindowRect -DestroyWindow -DefWindowProcW +CallNextHookEx +CreateSolidBrush CreateWindowExW -RegisterClassW -GetSystemMetrics -MonitorFromWindow -RegisterWindowMessage -GetDpiForWindow -SetWindowPos -SHCreateShellItemArrayFromDataObject -Shell_NotifyIcon -GetSystemMetricsForDpi -GetSystemMetrics +DWM_WINDOW_CORNER_PREFERENCE +DefSubclassProc +DefWindowProcW +DeleteObject +DestroyWindow +DwmSetWindowAttribute +FillRect +FindWindow +FindWindowEx +GetClientRect GetCurrentProcess -SetProcessInformation -PROCESS_POWER_THROTTLING_STATE +GetDpiForWindow +GetPhysicalCursorPos +GetSystemMetrics +GetSystemMetricsForDpi +GetWindowLong +GetWindowRect +MonitorFromWindow PROCESS_POWER_THROTTLING_CURRENT_VERSION PROCESS_POWER_THROTTLING_EXECUTION_SPEED -SetPriorityClass -WINDOW_STYLE -DefSubclassProc -SetLayeredWindowAttributes -WM_ERASEBKGND -SetWindowSubclass -GetClientRect -CreateSolidBrush -DwmSetWindowAttribute -DWM_WINDOW_CORNER_PREFERENCE -FillRect -SetLayeredWindowAttributes -DeleteObject +PROCESS_POWER_THROTTLING_STATE +RegisterClassW +RegisterWindowMessage RemoveWindowSubclass -GetWindowLong -SetWindowLong \ No newline at end of file +SHCreateShellItemArrayFromDataObject +SetLayeredWindowAttributes +SetPriorityClass +SetProcessInformation +SetWindowLong +SetWindowPos +SetWindowSubclass +SetWindowsHookEx +Shell_NotifyIcon +WINDOW_STYLE +WM_ERASEBKGND +SetForegroundWindow +ShowWindow +GetCursorPos +UnregisterClass +CreateWindowEx +RegisterClass +DefWindowProc +GetMessage +TranslateMessage +DispatchMessage +PostQuitMessage +DestroyWindow +GetModuleHandle \ No newline at end of file diff --git a/Toolkit.Windows/NotifyIcon.cs b/Toolkit.Windows/NotifyIcon.cs index e640662..0603311 100644 --- a/Toolkit.Windows/NotifyIcon.cs +++ b/Toolkit.Windows/NotifyIcon.cs @@ -1,11 +1,12 @@ using CommunityToolkit.Mvvm.Messaging; +using System.Drawing; using System.Runtime.InteropServices; using Toolkit.Foundation; +using Windows.Win32; namespace Toolkit.Windows; - -public partial class NotifyIcon(IWndProc wndProc, +public class NotifyIcon(IWndProc wndProc, IMessenger messenger) : INotifyIcon, IRecipient @@ -17,11 +18,6 @@ public partial class NotifyIcon(IWndProc wndProc, private bool isDisposed; private NotifyIconData notifyIconData; - ~NotifyIcon() - { - Dispose(false); - } - private enum NotifyIconBalloonType { None = 0x00, @@ -66,6 +62,14 @@ public partial class NotifyIcon(IWndProc wndProc, GC.SuppressFinalize(this); } + public unsafe PointerLocation GetPointerPosition() + { + Point point = new(); + _ = PInvoke.GetCursorPos(&point); + + return new PointerLocation(point.X, point.Y); + } + public void Initialize() { messenger.RegisterAll(this); @@ -79,15 +83,18 @@ public partial class NotifyIcon(IWndProc wndProc, switch (message.LParam) { case (uint)WndProcMessages.WM_LBUTTONUP: - messenger.Send(new NotifyIconInvokedEventArgs(PointerButton.Left)); + messenger.Send(new NotifyIconInvokedEventArgs(PointerButton.Left, + GetPointerPosition())); break; case (uint)WndProcMessages.WM_MBUTTONUP: - messenger.Send(new NotifyIconInvokedEventArgs(PointerButton.Middle)); + messenger.Send(new NotifyIconInvokedEventArgs(PointerButton.Middle, + GetPointerPosition())); break; case (uint)WndProcMessages.WM_RBUTTONUP: - messenger.Send(new NotifyIconInvokedEventArgs(PointerButton.Right)); + messenger.Send(new NotifyIconInvokedEventArgs(PointerButton.Right, + GetPointerPosition())); break; } } diff --git a/Toolkit.Windows/NotifyIconInvokedEventArgs.cs b/Toolkit.Windows/NotifyIconInvokedEventArgs.cs index 9801e5b..0fe0ed9 100644 --- a/Toolkit.Windows/NotifyIconInvokedEventArgs.cs +++ b/Toolkit.Windows/NotifyIconInvokedEventArgs.cs @@ -1,3 +1,4 @@ namespace Toolkit.Windows; -public record NotifyIconInvokedEventArgs(PointerButton PointerButton); +public record NotifyIconInvokedEventArgs(PointerButton Button, + PointerLocation Location); diff --git a/Toolkit.Windows/PInvoke.cs b/Toolkit.Windows/PInvoke.cs index aef9535..f13969a 100644 --- a/Toolkit.Windows/PInvoke.cs +++ b/Toolkit.Windows/PInvoke.cs @@ -59,4 +59,9 @@ public static partial class PInvoke public static void GetAppBarPosition(ref AppBarData appBarData) => SHAppBarMessage(AppBarMessage.GetTaskbarPos, ref appBarData); + + internal static bool SetWindowSubclass(HWND value1, Func value2, int v1, nuint v2) + { + throw new NotImplementedException(); + } } diff --git a/Toolkit.Windows/PointerMonitor.cs b/Toolkit.Windows/Pointer.cs similarity index 94% rename from Toolkit.Windows/PointerMonitor.cs rename to Toolkit.Windows/Pointer.cs index c3cf662..6a044d6 100644 --- a/Toolkit.Windows/PointerMonitor.cs +++ b/Toolkit.Windows/Pointer.cs @@ -7,8 +7,8 @@ using Windows.Win32.UI.WindowsAndMessaging; namespace Toolkit.Windows; -public class PointerMonitor(IMessenger messenger) : - IPointerMonitor +public class Pointer(IMessenger messenger) : + IPointer { private bool isDisposed; private bool isPointerDrag; @@ -16,7 +16,7 @@ public class PointerMonitor(IMessenger messenger) : private HOOKPROC? mouseEventDelegate; private UnhookWindowsHookExSafeHandle? mouseHandle; - ~PointerMonitor() + ~Pointer() { Dispose(false); } @@ -126,9 +126,9 @@ public class PointerMonitor(IMessenger messenger) : } } - private unsafe bool TryGetPointer(out Point point) + private unsafe bool TryGetPointer(out System.Drawing.Point point) { - fixed (Point* lpPointLocal = &point) + fixed (System.Drawing.Point* lpPointLocal = &point) { return PInvoke.GetPhysicalCursorPos(lpPointLocal); } @@ -136,7 +136,7 @@ public class PointerMonitor(IMessenger messenger) : private bool TryGetPointerLocation([MaybeNullWhen(false)] out PointerLocation location) { - if (TryGetPointer(out Point point)) + if (TryGetPointer(out System.Drawing.Point point)) { location = new PointerLocation(point.X, point.Y); return true; diff --git a/Toolkit.Windows/Rect.cs b/Toolkit.Windows/Rect.cs index d25bae6..9f974bc 100644 --- a/Toolkit.Windows/Rect.cs +++ b/Toolkit.Windows/Rect.cs @@ -16,7 +16,10 @@ public record Rect } public int X { get; } + public int Y { get; } + public int Width { get; } + public int Height { get; } } \ No newline at end of file diff --git a/Toolkit.Windows/Taskbar.cs b/Toolkit.Windows/Taskbar.cs index 19c04db..b5d8091 100644 --- a/Toolkit.Windows/Taskbar.cs +++ b/Toolkit.Windows/Taskbar.cs @@ -40,7 +40,7 @@ public class Taskbar(IMessenger messenger, return state; } - public IntPtr GetHandle() => WindowHelper.Find("Shell_TrayWnd"); + public IntPtr GetHandle() => WindowHelper.FindWindow("Shell_TrayWnd"); public void Receive(WndProcEventArgs args) { @@ -63,7 +63,7 @@ public class Taskbar(IMessenger messenger, public void Receive(PointerMovedEventArgs args) { nint taskbarHandle = GetHandle(); - if (WindowHelper.TryGetBounds(taskbarHandle, out Rect? rect)) + if (WindowHelper.TryGetWindowBounds(taskbarHandle, out Rect? rect)) { if (args.Location.IsWithinBounds(rect)) { diff --git a/Toolkit.Windows/TaskbarButtonMonitor.cs b/Toolkit.Windows/TaskbarButtonMonitor.cs index abb1dbc..8714915 100644 --- a/Toolkit.Windows/TaskbarButtonMonitor.cs +++ b/Toolkit.Windows/TaskbarButtonMonitor.cs @@ -49,7 +49,7 @@ public class TaskbarButtonMonitor : taskListHandle = taskbarList.GetHandle(); taskListElement = clientUIAutomation.ElementFromHandle(taskListHandle); - if (WindowHelper.TryGetBounds(taskListHandle, out Rect? rect)) + if (WindowHelper.TryGetWindowBounds(taskListHandle, out Rect? rect)) { taskbarRectCache = rect; } @@ -60,7 +60,7 @@ public class TaskbarButtonMonitor : private bool CheckDirtyTaskbarRegion() { - if (WindowHelper.TryGetBounds(taskListHandle, out Rect? rect)) + if (WindowHelper.TryGetWindowBounds(taskListHandle, out Rect? rect)) { if (taskbarRectCache?.Width != rect.Width || taskbarRectCache?.Height != rect.Height) diff --git a/Toolkit.Windows/TaskbarList.cs b/Toolkit.Windows/TaskbarList.cs index 2fad053..688b076 100644 --- a/Toolkit.Windows/TaskbarList.cs +++ b/Toolkit.Windows/TaskbarList.cs @@ -5,8 +5,8 @@ public class TaskbarList(ITaskbar taskbar) : { public IntPtr GetHandle() { - nint rebarHandle = WindowHelper.Find("ReBarWindow32", taskbar.GetHandle()); - nint taskHandle = WindowHelper.Find("MSTaskSwWClass", rebarHandle); - return WindowHelper.Find("MSTaskListWClass", taskHandle); + nint rebarHandle = WindowHelper.FindWindow("ReBarWindow32", taskbar.GetHandle()); + nint taskHandle = WindowHelper.FindWindow("MSTaskSwWClass", rebarHandle); + return WindowHelper.FindWindow("MSTaskListWClass", taskHandle); } } diff --git a/Toolkit.Windows/WindowHelper.cs b/Toolkit.Windows/WindowHelper.cs index 84e3ac5..34e01a4 100644 --- a/Toolkit.Windows/WindowHelper.cs +++ b/Toolkit.Windows/WindowHelper.cs @@ -7,35 +7,44 @@ namespace Toolkit.Windows; public class WindowHelper { - public static IntPtr GetHandle(string windowName) => PInvoke.FindWindow(windowName, null); + public static IntPtr FindWindow(string name) => + PInvoke.FindWindow(name, null); - public static uint GetDpi(IntPtr handle) => PInvoke.GetDpiForWindow((HWND)handle); + public static IntPtr FindWindow(string name, IntPtr handle) => + PInvoke.FindWindowEx(new HWND(handle), new HWND(), name, null); - public static void BringToForeground(HWND handle) - { - if (TryGetBoundsUnsafe(handle, out RECT bounds)) - { - PInvoke.SetWindowPos(handle, new HWND(), bounds.left, bounds.top, bounds.right - - bounds.left, bounds.bottom - bounds.top, SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE); - } - } + public static uint GetDpi(IntPtr handle) => + PInvoke.GetDpiForWindow((HWND)handle); - public static IntPtr Find(string windowName) => - PInvoke.FindWindow(windowName, null); + public static IntPtr GetWindowHandle(string name) => + PInvoke.FindWindow(name, null); - public static IntPtr Find(string windowName, IntPtr parentHandle) => - PInvoke.FindWindowEx(new HWND(parentHandle), new HWND(), windowName, null); + public static bool HideWindow(IntPtr hWnd) => + PInvoke.ShowWindow(new HWND(hWnd), SHOW_WINDOW_CMD.SW_HIDE); - public static void MoveAndResize(HWND handle, int x, int y, int width, int height) => + public static void MoveAndResizeWindow(HWND handle, int x, int y, int width, int height) => PInvoke.SetWindowPos(handle, new HWND(), x, y, width, height, 0); - public static bool TryGetBounds(IntPtr handle, + public static bool SetForegroundWindow(HWND handle) + { + if (TryGetWindowBoundsUnsafe(handle, out RECT bounds)) + { + return PInvoke.SetWindowPos(handle, new HWND(), bounds.left, bounds.top, bounds.right - + bounds.left, bounds.bottom - bounds.top, SET_WINDOW_POS_FLAGS.SWP_SHOWWINDOW | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE) == 1; + } + + return false; + } + + public static bool ShowWindow(IntPtr hWnd) => + PInvoke.ShowWindow(new HWND(hWnd), SHOW_WINDOW_CMD.SW_SHOW); + + public static bool TryGetWindowBounds(IntPtr handle, [MaybeNullWhen(false)] out Rect rect) { - if (TryGetBoundsUnsafe(handle, out RECT unsafeRect)) + if (TryGetWindowBoundsUnsafe(handle, out RECT unsafeRect)) { rect = new Rect(unsafeRect.left, unsafeRect.top, unsafeRect.right - unsafeRect.left, unsafeRect.bottom - unsafeRect.top); - return true; } @@ -43,7 +52,7 @@ public class WindowHelper return false; } - private static unsafe bool TryGetBoundsUnsafe(IntPtr handle, out RECT rect) + private static unsafe bool TryGetWindowBoundsUnsafe(IntPtr handle, out RECT rect) { fixed (RECT* lpRectLocal = &rect) { diff --git a/Toolkit.Windows/WndProc.cs b/Toolkit.Windows/WndProc.cs index 5b917a9..ae0076f 100644 --- a/Toolkit.Windows/WndProc.cs +++ b/Toolkit.Windows/WndProc.cs @@ -1,7 +1,7 @@ using CommunityToolkit.Mvvm.Messaging; +using Toolkit.Foundation; using Windows.Win32; using Windows.Win32.Foundation; -using Windows.Win32.Graphics.Gdi; using Windows.Win32.UI.WindowsAndMessaging; namespace Toolkit.Windows; @@ -10,56 +10,61 @@ public class WndProc(IMessenger messenger) : IWndProc { private WNDPROC? handler; + private bool isDisposed; + + ~WndProc() + { + Dispose(false); + } public IntPtr Handle { get; private set; } public void Dispose() { + Dispose(true); GC.SuppressFinalize(this); + } + + public void Initialize() => InitializeWndProc(); + + protected virtual void Dispose(bool disposing) + { + if (isDisposed) + { + return; + } + + isDisposed = true; + PInvoke.DestroyWindow((HWND)Handle); } + private LRESULT HandleWndProc(HWND hWnd, uint msg, + WPARAM wParam, LPARAM lParam) + { + messenger.Send(new WndProcEventArgs(msg, + (uint)wParam.Value, + (uint)lParam.Value)); + + return PInvoke.DefWindowProc(hWnd, msg, wParam, lParam); + } private unsafe void InitializeWndProc() { - string windowName = Guid.NewGuid().ToString(); - handler = Wndproc; + string windowId = $"WndProc_Handler_{Guid.NewGuid()}"; + handler = HandleWndProc; - WNDCLASSW wndProcWindow; - - wndProcWindow.style = 0; - wndProcWindow.lpfnWndProc = handler; - wndProcWindow.cbClsExtra = 0; - wndProcWindow.cbWndExtra = 0; - wndProcWindow.hInstance = new HINSTANCE(); - wndProcWindow.hIcon = new HICON(); - wndProcWindow.hCursor = new HCURSOR(); - wndProcWindow.hbrBackground = new HBRUSH(); - - fixed (char* menuName = "") + fixed (char* className = windowId) { - wndProcWindow.lpszMenuName = new PCWSTR(menuName); + WNDCLASSW wndCLass = new() + { + lpfnWndProc = handler, + lpszClassName = className, + }; + + _ = PInvoke.RegisterClass(wndCLass); } - fixed (char* className = windowName) - { - wndProcWindow.lpszClassName = new PCWSTR(className); - } - - PInvoke.RegisterClass(wndProcWindow); - Handle = PInvoke.CreateWindowEx(0, wndProcWindow.lpszClassName, - new PCWSTR(), 0, 0, 0, 0, 0, new HWND(), - new HMENU(), - new HINSTANCE()); - } - - private LRESULT Wndproc(HWND param0, uint param1, WPARAM param2, LPARAM param3) - { - messenger.Send(new WndProcEventArgs(param1, (uint)param2.Value, (uint)param3.Value)); - return PInvoke.DefWindowProc(param0, param1, param2, param3); - } - - public void Initialize() - { - InitializeWndProc(); + Handle = PInvoke.CreateWindowEx(0, windowId, windowId, 0, 0, 0, 0, 0, + new HWND(IntPtr.Zero), null, null, null); } }