More WIndows specific parts moved to this project

This commit is contained in:
Dan Clark
2024-11-02 20:57:13 +00:00
parent 48c63b3bb0
commit 81e39f9895
25 changed files with 528 additions and 68 deletions
@@ -19,7 +19,7 @@ public partial class ConfigurationValueViewModel<TConfiguration, TValue>(IServic
{ {
if (args.Sender is TConfiguration configuration) if (args.Sender is TConfiguration configuration)
{ {
// await Task.Run(() => Value = read(configuration)); await Task.Run(() => Value = read(configuration));
} }
} }
@@ -96,7 +96,7 @@ public partial class ConfigurationValueViewModel<TConfiguration, TValue, TItem>
{ {
if (args.Sender is TConfiguration configuration) if (args.Sender is TConfiguration configuration)
{ {
// await Task.Run(() => Value = read(configuration)); await Task.Run(() => Value = read(configuration));
} }
} }
+1 -1
View File
@@ -8,7 +8,7 @@ public class Navigation(IServiceProvider provider,
IPublisher publisher) : IPublisher publisher) :
INavigation INavigation
{ {
public async void Navigate(string route, public void Navigate(string route,
object? sender = null, object? sender = null,
object? region = null, object? region = null,
EventHandler? navigated = null, EventHandler? navigated = null,
@@ -533,8 +533,8 @@ public class ContentCropper : ContentControl
double centreX, double centreX,
double centreY) double centreY)
{ {
int width = (int)border.Width; int width = (int?)border?.Width ?? 0;
int height = (int)border.Height; int height = (int?)border?.Height ?? 0;
double x = Math.Max(centreX - width / 2, 0); double x = Math.Max(centreX - width / 2, 0);
double y = Math.Max(centreY - height / 2, 0); double y = Math.Max(centreY - height / 2, 0);
+12
View File
@@ -0,0 +1,12 @@
using Toolkit.Foundation;
namespace Toolkit.Windows;
public interface ITaskbar :
IInitialization,
IDisposable
{
TaskbarState GetCurrentState();
IntPtr GetHandle();
}
+9
View File
@@ -0,0 +1,9 @@
namespace Toolkit.Windows;
public interface ITaskbarButton :
IDisposable
{
Rect Rect { get; }
string Name { get; }
}
@@ -0,0 +1,9 @@
namespace Toolkit.Windows;
public static class PointerLocationExtensions
{
public static bool IsWithinBounds(this PointerLocation args, Rect bounds) => args.X >= bounds.X
&& args.X <= bounds.X + bounds.Width
&& args.Y >= bounds.Y
&& args.Y <= bounds.Y + bounds.Height;
}
+1 -60
View File
@@ -3,70 +3,10 @@ using System.Drawing;
using Toolkit.Foundation; using Toolkit.Foundation;
using Windows.Win32; using Windows.Win32;
using Windows.Win32.Foundation; using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.WindowsAndMessaging; using Windows.Win32.UI.WindowsAndMessaging;
namespace Toolkit.Windows; namespace Toolkit.Windows;
public class WndProcMonitor(IPublisher publisher) :
IWndProcMonitor
{
private WNDPROC? handler;
private readonly IPublisher publisher = publisher;
public IntPtr Handle { get; private set; }
public void Dispose()
{
PInvoke.DestroyWindow((HWND)Handle);
}
private unsafe void InitializeWndProc()
{
var windowName = Guid.NewGuid().ToString();
handler = Wndproc;
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 = "")
{
wndProcWindow.lpszMenuName = new PCWSTR(menuName);
}
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)
{
publisher.Publish(new WndProcEventArgs(param1, (uint)param2.Value, (uint)param3.Value));
return PInvoke.DefWindowProc(param0, param1, param2, param3);
}
public void Initialize()
{
InitializeWndProc();
}
}
public class PointerMonitor(IPublisher publisher) : public class PointerMonitor(IPublisher publisher) :
IPointerMonitor IPointerMonitor
{ {
@@ -75,6 +15,7 @@ public class PointerMonitor(IPublisher publisher) :
private bool isPointerPressed; private bool isPointerPressed;
private HOOKPROC? mouseEventDelegate; private HOOKPROC? mouseEventDelegate;
private UnhookWindowsHookExSafeHandle? mouseHandle; private UnhookWindowsHookExSafeHandle? mouseHandle;
~PointerMonitor() ~PointerMonitor()
{ {
Dispose(false); Dispose(false);
+11
View File
@@ -0,0 +1,11 @@
using Windows.Win32.Foundation;
namespace Toolkit.Windows;
internal static class RectExtensions
{
internal static Rect ToRect(this RECT rect) =>
rect.right - rect.left < 0 || rect.bottom - rect.top < 0
? new Rect(rect.left, rect.top, 0, 0)
: new Rect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
}
+113
View File
@@ -0,0 +1,113 @@
using System.Runtime.InteropServices;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
using Windows.Win32.Graphics.Gdi;
namespace Toolkit.Windows;
public class Screen
{
private const int CCHDEVICENAME = 32;
private const int PRIMARY_MONITOR = unchecked((int)0xBAADF00D);
private static readonly bool _multiMonitorSupport;
private readonly IntPtr _monitorHandle;
static Screen()
{
_multiMonitorSupport = PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CMONITORS) != 0;
}
internal Screen(IntPtr monitorHandle)
{
if (!_multiMonitorSupport || monitorHandle == (IntPtr)PRIMARY_MONITOR)
{
Bounds = SystemInformationHelper.VirtualScreen;
Primary = true;
DeviceName = "DISPLAY";
}
else
{
var monitorData = GetMonitorData(monitorHandle);
Bounds = new Rect(monitorData.MonitorRect.left, monitorData.MonitorRect.top,
monitorData.MonitorRect.right - monitorData.MonitorRect.left,
monitorData.MonitorRect.bottom - monitorData.MonitorRect.top);
Primary = (monitorData.Flags & (int)MonitorFlag.MONITOR_DEFAULTTOPRIMARY) != 0;
DeviceName = monitorData.DeviceName;
}
_monitorHandle = monitorHandle;
}
private enum MonitorFlag : uint
{
MONITOR_DEFAULTTONULL = 0,
MONITOR_DEFAULTTOPRIMARY = 1,
MONITOR_DEFAULTTONEAREST = 2
}
public Rect Bounds { get; }
public string DeviceName { get; }
public bool Primary { get; }
public Rect WorkingArea => GetWorkingArea();
public static Screen FromHandle(IntPtr handle) => _multiMonitorSupport ?
new Screen(PInvoke.MonitorFromWindow((HWND)handle,
MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST)) :
new Screen(PRIMARY_MONITOR);
public override bool Equals(object? obj)
{
if (obj is not Screen monitor) return false;
return _monitorHandle == monitor._monitorHandle;
}
public override int GetHashCode()
{
checked
{
return (int)_monitorHandle;
}
}
[DllImport("user32.dll", EntryPoint = "GetMonitorInfo", CharSet = CharSet.Auto, SetLastError = true)]
private static extern bool GetMonitorInfoEx(IntPtr hMonitor, ref MonitorData lpmi);
private MonitorData GetMonitorData(IntPtr monitorHandle)
{
MonitorData monitorData = new();
monitorData.Size = Marshal.SizeOf(monitorData);
GetMonitorInfoEx(monitorHandle, ref monitorData);
return monitorData;
}
private Rect GetWorkingArea()
{
if (!_multiMonitorSupport || _monitorHandle == PRIMARY_MONITOR)
{
return SystemInformationHelper.WorkingArea;
}
var monitorData = GetMonitorData(_monitorHandle);
return new Rect(monitorData.WorkAreaRect.left, monitorData.WorkAreaRect.top, monitorData.WorkAreaRect.right - monitorData.WorkAreaRect.left, monitorData.WorkAreaRect.bottom - monitorData.WorkAreaRect.top);
}
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
private struct MonitorData
{
public int Size;
public RECT MonitorRect;
public RECT WorkAreaRect;
public uint Flags;
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
public string DeviceName;
}
}
@@ -0,0 +1,33 @@
using System.Runtime.InteropServices;
using Windows.Foundation;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Toolkit.Windows;
internal static class SystemInformationHelper
{
private const int SPI_GETWORKAREA = 48;
public static Rect VirtualScreen => GetVirtualScreen();
public static Rect WorkingArea => GetWorkingArea();
private static Rect GetVirtualScreen()
{
Size size = new(PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN),
PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYSCREEN));
return new Rect(0, 0, (int)size.Width, (int)size.Height);
}
private static Rect GetWorkingArea()
{
var rect = new RECT();
SystemParametersInfo(SPI_GETWORKAREA, 0, ref rect, 0);
return new Rect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
}
[DllImport("user32.dll", CharSet = CharSet.Auto)]
private static extern bool SystemParametersInfo(int nAction, int nParam, ref RECT rc, int nUpdate);
}
+114
View File
@@ -0,0 +1,114 @@
using Toolkit.Foundation;
using Windows.Win32;
namespace Toolkit.Windows;
public class Taskbar(ISubscriber subscriber,
IPublisher publisher,
IDisposer disposer) :
ITaskbar,
INotificationHandler<WndProcEventArgs>,
INotificationHandler<PointerReleasedEventArgs>,
INotificationHandler<PointerMovedEventArgs>,
INotificationHandler<PointerDragEventArgs>
{
private bool isDrag;
private bool isWithinBounds;
public void Dispose()
{
disposer.Dispose(this);
GC.SuppressFinalize(this);
}
public TaskbarState GetCurrentState()
{
var handle = GetHandle();
var state = new TaskbarState
{
Screen = Screen.FromHandle(handle)
};
var appBarData = PInvoke.GetAppBarData(handle);
PInvoke.GetAppBarPosition(ref appBarData);
state.Rect = appBarData.rect.ToRect();
state.Placement = (TaskbarPlacement)appBarData.uEdge;
return state;
}
public IntPtr GetHandle() => WindowHelper.Find("Shell_TrayWnd");
public Task Handle(WndProcEventArgs args)
{
if (args.Message == PInvoke.WM_TASKBARCREATED ||
args.Message == (int)WndProcMessages.WM_SETTINGCHANGE &&
(int)args.WParam == PInvoke.SPI_SETWORKAREA)
{
publisher.Publish<TaskbarChangedEventArgs>();
}
return Task.CompletedTask;
}
public Task Handle(PointerReleasedEventArgs args)
{
if (isDrag)
{
isDrag = false;
}
return Task.CompletedTask;
}
public Task Handle(PointerMovedEventArgs args)
{
nint taskbarHandle = GetHandle();
if (WindowHelper.TryGetBounds(taskbarHandle, out var rect))
{
if (args.Location.IsWithinBounds(rect))
{
if (isWithinBounds)
{
return Task.CompletedTask;
}
isWithinBounds = true;
publisher.Publish<TaskbarEnteredEventArgs>();
}
else
{
isDrag = false;
isWithinBounds = false;
}
}
return Task.CompletedTask;
}
public Task Handle(PointerDragEventArgs args)
{
if (isWithinBounds)
{
if (isDrag)
{
publisher.Publish<TaskbarDragOverEventArgs>();
}
else
{
publisher.Publish<TaskbarDragEnterEventArgs>();
}
isDrag = true;
}
else
{
isDrag = false;
}
return Task.CompletedTask;
}
public void Initialize() => subscriber.Subscribe(this);
}
+99
View File
@@ -0,0 +1,99 @@
using Toolkit.Foundation;
namespace Toolkit.Windows;
public class TaskbarButton :
ITaskbarButton,
INotificationHandler<PointerReleasedEventArgs>,
INotificationHandler<PointerMovedEventArgs>,
INotificationHandler<PointerDragEventArgs>
{
private readonly IPublisher publisher;
private readonly IDisposer disposer;
private bool isWithinBounds;
private bool isDrag;
public TaskbarButton(string name,
Rect rect,
IPublisher publisher,
ISubscriber subscriber,
IDisposer disposer)
{
this.publisher = publisher;
this.disposer = disposer;
Name = name;
Rect = rect;
subscriber.Subscribe(this);
}
public Rect Rect { get; internal set; }
public string Name { get; internal set; }
public void Dispose()
{
disposer.Dispose(this);
GC.SuppressFinalize(this);
}
public Task Handle(PointerReleasedEventArgs args)
{
if (!isDrag && isWithinBounds)
{
publisher.Publish(new TaskbarButtonInvokedEventArgs(this));
}
if (isDrag)
{
isDrag = false;
}
return Task.CompletedTask;
}
public Task Handle(PointerDragEventArgs args)
{
if (isWithinBounds)
{
if (isDrag)
{
publisher.Publish(new TaskbarButtonDragOverEventArgs(this));
}
else
{
publisher.Publish(new TaskbarButtonDragEnterEventArgs(this));
}
isDrag = true;
}
else
{
isDrag = false;
}
return Task.CompletedTask;
}
public Task Handle(PointerMovedEventArgs args)
{
if (args.Location.IsWithinBounds(Rect))
{
if (isWithinBounds)
{
return Task.CompletedTask;
}
isWithinBounds = true;
publisher.Publish(new TaskbarButtonEnteredEventArgs(this));
}
else
{
isDrag = false;
isWithinBounds = false;
}
return Task.CompletedTask;
}
}
@@ -0,0 +1,3 @@
namespace Toolkit.Windows;
public record TaskbarButtonDragEnterEventArgs(TaskbarButton Button);
@@ -0,0 +1,3 @@
namespace Toolkit.Windows;
public record TaskbarButtonDragOverEventArgs(TaskbarButton Button);
@@ -0,0 +1,3 @@
namespace Toolkit.Windows;
public record TaskbarButtonEnteredEventArgs(TaskbarButton Button);
@@ -0,0 +1,3 @@
namespace Toolkit.Windows;
public record TaskbarButtonInvokedEventArgs(TaskbarButton Button);
@@ -0,0 +1,3 @@
namespace Toolkit.Windows;
public record TaskbarChangedEventArgs;
@@ -0,0 +1,3 @@
namespace Toolkit.Windows;
public record TaskbarDragEnterEventArgs;
@@ -0,0 +1,3 @@
namespace Toolkit.Windows;
public record TaskbarDragOverEventArgs;
@@ -0,0 +1,3 @@
namespace Toolkit.Windows;
public record TaskbarEnteredEventArgs;
+12
View File
@@ -0,0 +1,12 @@
namespace Toolkit.Windows;
public class TaskbarList(ITaskbar taskbar) :
ITaskbarList
{
public IntPtr GetHandle()
{
nint rebarHandle = WindowHelper.Find("ReBarWindow32", taskbar.GetHandle());
nint taskHandle = WindowHelper.Find("MSTaskSwWClass", rebarHandle);
return WindowHelper.Find("MSTaskListWClass", taskHandle);
}
}
+9
View File
@@ -0,0 +1,9 @@
namespace Toolkit.Windows;
public enum TaskbarPlacement
{
Left = 0,
Top = 1,
Right = 2,
Bottom = 3
}
+8
View File
@@ -0,0 +1,8 @@
namespace Toolkit.Windows;
public struct TaskbarState
{
public TaskbarPlacement Placement;
public Rect Rect;
public Screen Screen;
}
+4 -3
View File
@@ -1,12 +1,14 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net9.0-windows</TargetFramework> <TargetFramework>net9.0-windows10.0.19041.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks> <AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<ResolveComReferenceSilent>True</ResolveComReferenceSilent> <ResolveComReferenceSilent>True</ResolveComReferenceSilent>
<Platforms>x64;x86</Platforms> <Platforms>AnyCPU;x64;x86</Platforms>
<WindowsSdkPackageVersion>10.0.19041.41</WindowsSdkPackageVersion>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
@@ -14,7 +16,6 @@
<PrivateAssets>all</PrivateAssets> <PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets> <IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference> </PackageReference>
<PackageReference Include="System.Runtime.WindowsRuntime" Version="5.0.0-preview.5.20278.1" />
<PackageReference Include="WindowsShortcutFactory" Version="1.0.1" /> <PackageReference Include="WindowsShortcutFactory" Version="1.0.1" />
</ItemGroup> </ItemGroup>
+65
View File
@@ -0,0 +1,65 @@
using Toolkit.Foundation;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.WindowsAndMessaging;
namespace Toolkit.Windows;
public class WndProcMonitor(IPublisher publisher) :
IWndProcMonitor
{
private WNDPROC? handler;
private readonly IPublisher publisher = publisher;
public IntPtr Handle { get; private set; }
public void Dispose()
{
PInvoke.DestroyWindow((HWND)Handle);
}
private unsafe void InitializeWndProc()
{
var windowName = Guid.NewGuid().ToString();
handler = Wndproc;
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 = "")
{
wndProcWindow.lpszMenuName = new PCWSTR(menuName);
}
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)
{
publisher.Publish(new WndProcEventArgs(param1, (uint)param2.Value, (uint)param3.Value));
return PInvoke.DefWindowProc(param0, param1, param2, param3);
}
public void Initialize()
{
InitializeWndProc();
}
}