This commit is contained in:
dan_clark@outlook.com
2022-03-23 15:44:32 +00:00
commit 2ac0e3ed26
129 changed files with 4197 additions and 0 deletions
@@ -0,0 +1,15 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public class DataTemplateBuilder : IDataTemplateBuilder
{
private readonly Dictionary<Type, Type> items = new();
public IDataTemplateCollection DataTemplates => new DataTemplateCollection(items);
public IDataTemplateBuilder Map<TViewModel, TView>()
{
items.Add(typeof(TViewModel), typeof(TView));
return this;
}
}
}
@@ -0,0 +1,12 @@
using System.Collections.ObjectModel;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class DataTemplateCollection : ReadOnlyDictionary<Type, Type>, IDataTemplateCollection
{
public DataTemplateCollection(IDictionary<Type, Type> dictionary) : base(dictionary)
{
}
}
}
+78
View File
@@ -0,0 +1,78 @@
using System.Reactive.Linq;
using System.Reactive.Disposables;
using System.Runtime.CompilerServices;
using System.Collections;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class Disposer : IDisposer
{
private readonly ConditionalWeakTable<object, CompositeDisposable> subjects = new();
public void Add(object subject, params object[] objects)
{
var disposables = subjects.GetOrCreateValue(subject);
foreach (var disposable in objects.OfType<IDisposable>())
{
disposables.Add(disposable);
}
foreach (var notDisposable in objects.Where(x => x is not IDisposable))
{
disposables.Add(Disposable.Create(() => MakeNotDisposable(notDisposable)));
}
}
public void Dispose(object subject)
{
if (subjects.TryGetValue(subject, out CompositeDisposable disposables))
{
disposables.Dispose();
}
}
public void Remove(object subject, IDisposable disposer)
{
var disposables = subjects.GetOrCreateValue(subject);
if (disposer != null)
{
disposables.Remove(disposer);
}
}
public TDisposable Replace<TDisposable>(object subject, IDisposable disposer, TDisposable replacement) where TDisposable : IDisposable
{
var disposables = subjects.GetOrCreateValue(subject);
if (disposer is not null)
{
disposables.Remove(disposer);
}
disposables.Add(replacement);
return replacement;
}
private void MakeNotDisposable(object target)
{
if (target is IEnumerable enumerableTarget)
{
foreach (var item in enumerableTarget)
{
MakeNotDisposable(item);
}
}
if (target is IDisposable disposableTarget)
{
disposableTarget.Dispose();
}
if (target is not IDisposable)
{
Dispose(target);
}
}
}
}
@@ -0,0 +1,14 @@
using System.Reflection;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class EventAggregatorInvoker : IEventAggregatorInvoker
{
public void Invoke<TMessage>(object target, TMessage message, MethodInfo methodInfo)
{
if (message is null) throw new ArgumentNullException(nameof(message));
methodInfo.Invoke(target, new object[] { message });
}
}
}
@@ -0,0 +1,12 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IAsyncMessageHandler<TMessage>
{
Task Handle(TMessage message, CancellationToken canellationToken = default);
}
public interface IAsyncMessageHandler<TReturn, TMessage>
{
Task<TReturn> Handle(TMessage message, CancellationToken cancellationToken = default);
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IBindViewModel<TViewModel> where TViewModel : class
{
public TViewModel ViewModel { get; set; }
}
}
@@ -0,0 +1,9 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IDataTemplateBuilder
{
IDataTemplateCollection DataTemplates { get; }
IDataTemplateBuilder Map<TViewModel, TView>();
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IDataTemplateCollection : IReadOnlyDictionary<Type, Type>
{
}
}
@@ -0,0 +1,9 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IDispatcherTimer
{
void Start();
void Stop();
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IDispatcherTimerFactory
{
IDispatcherTimer Create(Action actionDelegate, TimeSpan interval);
}
}
+13
View File
@@ -0,0 +1,13 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IDisposer
{
void Add(object subject, params object[] objects);
void Dispose(object subject);
void Remove(object subject, IDisposable disposer);
TDisposable Replace<TDisposable>(object subject, IDisposable disposer, TDisposable replacement) where TDisposable : IDisposable;
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IDropTarget<TTarget>
{
void Register(TTarget target);
}
}
@@ -0,0 +1,9 @@
using System.Reflection;
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IEventAggregatorInvoker
{
void Invoke<TMessage>(object target, TMessage message, MethodInfo methodInfo);
}
}
@@ -0,0 +1,21 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
namespace TheXamlGuy.TaskbarGroup.Core
{
public static class IHostingExtensions
{
public static IHostBuilder ConfigureDataTemplates(this IHostBuilder hostBuilder, Action<IDataTemplateBuilder> builderDelegate)
{
hostBuilder.ConfigureServices((hostBuilderContext, serviceCollection) =>
{
var builder = new DataTemplateBuilder();
builderDelegate?.Invoke(builder);
serviceCollection.AddSingleton(builder.DataTemplates);
});
return hostBuilder;
}
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IInitializable
{
void Initialize();
}
}
+17
View File
@@ -0,0 +1,17 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IMediator
{
void Handle(object request, params object[] parameters);
void Handle<TEvent>() where TEvent : new();
TResponse Handle<TResponse, TRequest>(params object[] parameters) where TRequest : new();
TResponse Handle<TResponse>(object request, params object[] parameters);
Task HandleAsync(object request, CancellationToken cancellationToken, params object[] parameters);
Task HandleAsync(object request, params object[] parameters);
Task HandleAsync<TEvent>() where TEvent : new();
Task<TResponse> HandleAsync<TResponse, TRequest>(params object[] parameters) where TRequest : new();
Task<TResponse> HandleAsync<TResponse>(object request, CancellationToken cancellationToken, params object[] parameters);
Task<TResponse> HandleAsync<TResponse>(object request, params object[] parameters);
}
}
@@ -0,0 +1,12 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IMessageHandler<TMessage>
{
void Handle(TMessage message);
}
public interface IMessageHandler<TReturn, TMessage>
{
TReturn Handle(TMessage message);
}
}
@@ -0,0 +1,14 @@
using System.Reactive.Concurrency;
using System.Reactive.Subjects;
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IMessenger
{
void Send<TMessage>() where TMessage : new();
void Send<TMessage>(TMessage message);
IDisposable Subscribe<TMessage>(Action<TMessage> actionDelegate, IScheduler? scheduler = null, Func<TMessage, bool>? where = null);
}
}
@@ -0,0 +1,31 @@
using System.Reactive.Linq;
namespace TheXamlGuy.TaskbarGroup.Core
{
public record FileDropped();
public static class IObservableExtensions
{
public static IDisposable WeakSubscribe<TMessage>(this IObservable<TMessage> observable, IEventAggregatorInvoker invoker, Action<TMessage> actionDelegate)
{
var methodInfo = actionDelegate.Method;
var weakReference = new WeakReference(actionDelegate.Target);
IDisposable? subscription = null;
subscription = observable.Subscribe(item =>
{
if (weakReference.Target is object target)
{
invoker.Invoke(target, item, methodInfo);
}
else
{
subscription?.Dispose();
}
});
return subscription;
}
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IPointerMonitor : IInitializable, IDisposable
{
}
}
@@ -0,0 +1,22 @@
using Microsoft.Extensions.DependencyInjection;
namespace TheXamlGuy.TaskbarGroup.Core
{
public static class IServiceCollectionExtensions
{
public static IServiceCollection AddRequiredCore(this IServiceCollection serviceCollection)
{
return serviceCollection
.AddSingleton<IDisposer, Disposer>()
.AddSingleton<IServiceFactory>(provider => new ServiceFactory(provider.GetService, (type, parameter) => ActivatorUtilities.CreateInstance(provider, type, parameter)))
.AddSingleton<IEventAggregatorInvoker, EventAggregatorInvoker>()
.AddSingleton<IMessenger, Messenger>()
.AddSingleton<IMediator, Mediator>()
.AddSingleton<IInitializable, WndProcMonitor>()
.AddSingleton<ITaskbar, Taskbar>()
.AddSingleton<IInitializable, TaskbarMonitor>()
.AddSingleton<IInitializable, PointerMonitor>()
.AddSingleton<IInitializable, TaskbarButtonMonitor>();
}
}
}
@@ -0,0 +1,10 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IServiceFactory
{
T Create<T>(params object[] parameters);
T Create<T>(Type type);
T Create<T>();
}
}
+8
View File
@@ -0,0 +1,8 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface ITaskbar
{
TaskbarState GetCurrentState();
}
}
@@ -0,0 +1,9 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface ITaskbarButton : IDisposable
{
TaskbarButtonBounds Bounds { get; }
string Name { get; }
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface ITaskbarButtonMonitor : IInitializable
{
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface ITaskbarMonitor : IInitializable
{
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface ITemplateSelector
{
}
}
@@ -0,0 +1,7 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public interface IWndProcMonitor : IInitializable, IDisposable
{
}
}
@@ -0,0 +1,13 @@
namespace System.Runtime.CompilerServices
{
using System.ComponentModel;
/// <summary>
/// Reserved to be used by the compiler for tracking metadata.
/// This class should not be used by developers in source code.
/// </summary>
[EditorBrowsable(EditorBrowsableState.Never)]
public static class IsExternalInit
{
}
}
@@ -0,0 +1,24 @@
namespace System.Diagnostics.CodeAnalysis
{
/// <summary>Specifies that an output will not be null even if the corresponding type allows it. Specifies that an input argument was not null when the call returns.</summary>
[AttributeUsage(AttributeTargets.Field | AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.ReturnValue, Inherited = false)]
public sealed class NotNullAttribute : Attribute { }
/// <summary>Specifies that when a method returns <see cref="ReturnValue"/>, the parameter may be null even if the corresponding type disallows it.</summary>
[AttributeUsage(AttributeTargets.Parameter, Inherited = false)]
public sealed class MaybeNullWhenAttribute : Attribute
{
/// <summary>Initializes the attribute with the specified return value condition.</summary>
/// <param name="returnValue">
/// The return value condition. If the method returns this value, the associated parameter may be null.
/// </param>
public MaybeNullWhenAttribute(bool returnValue) => ReturnValue = returnValue;
/// <summary>Gets the return value condition.</summary>
public bool ReturnValue { get; }
}
// NOTE: you can find the full list of attributes in this gist:
// https://gist.github.com/Sergio0694/eb988b243dd4a720a66fe369b63e5b08.
// Keeping this one shorter so that the Medium embed doesn't take up too much space.
}
+70
View File
@@ -0,0 +1,70 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public class Mediator : IMediator
{
private readonly IServiceFactory serviceFactory;
public Mediator(IServiceFactory serviceFactory)
{
this.serviceFactory = serviceFactory;
}
public void Handle<TEvent>() where TEvent : new()
{
Handle(new TEvent());
}
public TResponse Handle<TResponse, TRequest>(params object[] parameters) where TRequest : new()
{
return Handle<TResponse>(new TRequest(), parameters);
}
public void Handle(object request, params object[] parameters)
{
GetHandler(typeof(IMessageHandler<>).MakeGenericType(request.GetType()), parameters)
.Handle((dynamic)request);
}
public TResponse Handle<TResponse>(object request, params object[] parameters)
{
return GetHandler(typeof(IMessageHandler<,>).MakeGenericType(typeof(TResponse), request.GetType()), parameters)
.Handle((dynamic)request);
}
public Task HandleAsync<TEvent>() where TEvent : new()
{
return HandleAsync(new TEvent());
}
public Task<TResponse> HandleAsync<TResponse, TRequest>(params object[] parameters) where TRequest : new()
{
return HandleAsync<TResponse>(new TRequest(), parameters);
}
public Task HandleAsync(object request, CancellationToken cancellationToken, params object[] parameters)
{
return GetHandler(typeof(IAsyncMessageHandler<>).MakeGenericType(request.GetType()), parameters)
.Handle((dynamic)request, cancellationToken);
}
public Task HandleAsync(object request, params object[] parameters)
{
return HandleAsync(request, CancellationToken.None, parameters);
}
public Task<TResponse> HandleAsync<TResponse>(object request, CancellationToken cancellationToken, params object[] parameters)
{
return GetHandler(typeof(IAsyncMessageHandler<,>).MakeGenericType(typeof(TResponse), request.GetType()), parameters)
.Handle((dynamic)request, cancellationToken);
}
public Task<TResponse> HandleAsync<TResponse>(object request, params object[] parameters)
{
return HandleAsync<TResponse>(request, CancellationToken.None, parameters);
}
private dynamic GetHandler(Type type, params object[] parameters)
{
return parameters.Length == 0 ? serviceFactory.Create<object>(type) : serviceFactory.Create<object>(type, parameters);
}
}
}
+54
View File
@@ -0,0 +1,54 @@
using System.Reactive.Concurrency;
using System.Collections.Concurrent;
using System.Reactive.Linq;
using System.Reactive.Subjects;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class Messenger : IMessenger
{
public IEventAggregatorInvoker invoker;
private readonly ConcurrentDictionary<Type, object> subjects = new();
private IScheduler dispatcher;
public Messenger(IEventAggregatorInvoker invoker)
{
var synchronizationContext = SynchronizationContext.Current;
if (synchronizationContext is null) throw new NullReferenceException(nameof(synchronizationContext));
this.invoker = invoker;
dispatcher = new SynchronizationContextScheduler(synchronizationContext);
}
public ISubject<TMessage> GetSubject<TMessage>()
{
return (ISubject<TMessage>)subjects.GetOrAdd(typeof(TMessage), type => new BehaviorSubject<TMessage>(default));
}
public void Send<TMessage>() where TMessage : new()
{
Send(new TMessage());
}
public void Send<TMessage>(TMessage message)
{
GetSubject<TMessage>().OnNext(message);
}
public IDisposable Subscribe<TMessage>(Action<TMessage> actionDelegate, IScheduler? scheduler = null, Func<TMessage, bool>? where = null)
{
if (scheduler is null)
{
scheduler = Scheduler.Default;
}
if (where == null)
{
where = x => true;
}
return GetSubject<TMessage>().AsObservable().Skip(1).Where(where).ObserveOn(scheduler).WeakSubscribe(invoker, actionDelegate);
}
}
}
@@ -0,0 +1,4 @@
{
"$schema": "https://aka.ms/CsWin32.schema.json",
"public": true
}
@@ -0,0 +1,17 @@
SetWindowsHookEx
GetModuleHandle
CallNextHookEx
GetPhysicalCursorPos
FindWindowEx
FindWindow
GetWindowRect
DestroyWindow
DefWindowProcW
CreateWindowExW
RegisterClassW
GetSystemMetrics
MonitorFromWindow
RegisterWindowMessage
GetDpiForWindow
SetWindowPos
SHCreateShellItemArrayFromDataObject
@@ -0,0 +1,132 @@
using System.Collections.ObjectModel;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class ObservableCollectionViewModel<TItemViewModel> :
ObservableCollection<TItemViewModel>, IDisposable
where TItemViewModel : class
{
private readonly IDisposer disposer;
private readonly IMessenger messenger;
private readonly IServiceFactory serviceFactory;
public ObservableCollectionViewModel(IMessenger messenger,
IServiceFactory serviceFactory,
IDisposer disposer)
{
this.messenger = messenger;
this.serviceFactory = serviceFactory;
this.disposer = disposer;
}
public bool IsInitialized { get; protected set; }
public void Add()
{
var item = serviceFactory.Create<TItemViewModel>();
disposer.Add(this, item);
base.Add(item);
}
public void Add(object parameter, params object[] parameters)
{
var item = serviceFactory.Create<TItemViewModel>(new[] { parameter }.Concat(parameters));
disposer.Add(this, item);
base.Add(item);
}
public new void Clear()
{
foreach (var item in this)
{
disposer.Dispose(item);
}
base.Clear();
}
public void Dispose()
{
OnDisposing();
disposer.Dispose(this);
GC.SuppressFinalize(this);
}
public void Initialize()
{
if (IsInitialized)
{
return;
}
IsInitialized = true;
OnInitialize();
}
public void Insert<TItem>(params object[] parameters) where TItem : TItemViewModel
{
var item = serviceFactory.Create<TItem>(parameters);
disposer.Add(this, item);
base.Add(item);
}
public new void Insert(int index, TItemViewModel item)
{
disposer.Add(this, item);
base.Insert(index, item);
}
public void Insert<TItem>(int index, params object[] parameters) where TItem : TItemViewModel
{
var item = serviceFactory.Create<TItem>(parameters);
disposer.Add(this, item);
base.Insert(index, item);
}
public void Insert(TItemViewModel item)
{
base.Insert(0, item);
disposer.Add(item);
}
public new void Remove(TItemViewModel item)
{
disposer.Dispose(item);
base.Remove(item);
}
protected virtual void OnDisposing()
{
}
protected virtual void OnInitialize()
{
}
protected void Publish<TEvent>(TEvent @event)
{
messenger.Send(@event);
}
protected void Publish<TEvent>() where TEvent : new()
{
messenger.Send<TEvent>();
}
protected void Register<TEvent>(Action<TEvent> action)
{
disposer.Add(this, messenger.Subscribe(action));
}
protected void Register<TEvent>(Action<TEvent> action, Func<TEvent, bool> condition)
{
disposer.Add(this, messenger.Subscribe(action, null, condition));
}
}
}
@@ -0,0 +1,68 @@
using CommunityToolkit.Mvvm.ComponentModel;
namespace TheXamlGuy.TaskbarGroup.Core
{
[INotifyPropertyChanged]
public partial class ObservableViewModel : IDisposable
{
private readonly IDisposer disposer;
private readonly IMessenger messenger;
public ObservableViewModel(IMessenger messenger,
IServiceFactory serviceFactory,
IDisposer disposer)
{
this.messenger = messenger;
this.disposer = disposer;
}
public bool IsInitialized { get; protected set; }
public void Dispose()
{
OnDisposing();
disposer.Dispose(this);
GC.SuppressFinalize(this);
}
public void Initialize()
{
if (IsInitialized)
{
return;
}
IsInitialized = true;
OnInitialize();
}
protected virtual void OnDisposing()
{
}
protected virtual void OnInitialize()
{
}
protected void Publish<TEvent>(TEvent @event)
{
messenger.Send(@event);
}
protected void Publish<TEvent>() where TEvent : new()
{
messenger.Send<TEvent>();
}
protected void Register<TEvent>(Action<TEvent> action)
{
disposer.Add(this, messenger.Subscribe(action));
}
protected void Register<TEvent>(Action<TEvent> action, Func<TEvent, bool> condition)
{
disposer.Add(this, messenger.Subscribe(action, null, condition));
}
}
}
@@ -0,0 +1,9 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public enum PointerButton
{
Left,
Middle,
Right
}
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record PointerDrag(PointerLocation Location);
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record PointerLocation(int X, int Y);
}
@@ -0,0 +1,155 @@
using Windows.Win32;
using Windows.Win32.UI.WindowsAndMessaging;
using Windows.Win32.Foundation;
using System.Diagnostics.CodeAnalysis;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class PointerMonitor : IPointerMonitor
{
private readonly IMessenger messenger;
private bool isDisposed;
private bool isPointerPressed;
private HOOKPROC? mouseEventDelegate;
private UnhookWindowsHookExSafeHandle? mouseHandle;
private bool isPointerDrag;
public PointerMonitor(IMessenger messenger)
{
this.messenger = messenger;
}
~PointerMonitor()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
public unsafe void Initialize()
{
InitializeHook();
}
protected virtual void Dispose(bool disposing)
{
if (!isDisposed)
{
RemoveHook();
isDisposed = true;
}
}
private unsafe void InitializeHook()
{
mouseEventDelegate = new HOOKPROC(MouseProc);
mouseHandle = PInvoke.SetWindowsHookEx(WINDOWS_HOOK_ID.WH_MOUSE_LL, mouseEventDelegate, PInvoke.GetModuleHandle("user32.dll"), 0);
}
private unsafe bool TryGetPointer(out POINT point)
{
fixed (POINT* lpPointLocal = &point)
{
return PInvoke.GetPhysicalCursorPos(lpPointLocal);
}
}
private bool TryGetPointerLocation([MaybeNullWhen(false)]out PointerLocation location)
{
if (TryGetPointer(out POINT point))
{
location = new PointerLocation(point.x, point.y);
return true;
}
location = null;
return false;
}
private LRESULT MouseProc(int nCode, WPARAM wParam, LPARAM lParam)
{
if (nCode >= 0)
{
if (TryGetPointerLocation(out var location))
{
switch ((uint)wParam.Value)
{
case (uint)WndProcMessages.WM_MOUSEMOVE:
SendPointerMoved(location);
break;
case (uint)WndProcMessages.WM_LBUTTONUP:
SendPointerReleased(location, PointerButton.Left);
break;
case (uint)WndProcMessages.WM_MBUTTONUP:
SendPointerReleased(location, PointerButton.Middle);
break;
case (uint)WndProcMessages.WM_RBUTTONUP:
SendPointerReleased(location, PointerButton.Right);
break;
case (uint)WndProcMessages.WM_LBUTTONDOWN:
SendPointerPressed(location, PointerButton.Left);
break;
case (uint)WndProcMessages.WM_MBUTTONDOWN:
SendPointerPressed(location, PointerButton.Middle);
break;
case (uint)WndProcMessages.WM_RBUTTONDOWN:
SendPointerPressed(location, PointerButton.Right);
break;
}
}
}
return PInvoke.CallNextHookEx(mouseHandle, nCode, wParam, lParam);
}
private unsafe void RemoveHook()
{
if (mouseHandle is not null && mouseHandle.DangerousGetHandle() != IntPtr.Zero)
{
PInvoke.UnhookWindowsHookEx((HHOOK)mouseHandle.DangerousGetHandle());
}
}
private void SendPointerMoved(PointerLocation location)
{
if (isPointerPressed)
{
if (!isPointerDrag)
{
isPointerDrag = true;
}
messenger.Send(new PointerDrag(location));
}
messenger.Send(new PointerMoved(location));
}
private void SendPointerPressed(PointerLocation location, PointerButton button)
{
isPointerPressed = true;
messenger.Send(new PointerPressed(location, button));
}
private void SendPointerReleased(PointerLocation location, PointerButton button)
{
if (isPointerPressed)
{
if (isPointerDrag)
{
isPointerDrag = false;
messenger.Send(new PointerDragReleased(location, button));
}
isPointerPressed = false;
messenger.Send(new PointerReleased(location, button));
}
}
}
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record PointerMoved(PointerLocation Location);
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record PointerPressed(PointerLocation Location, PointerButton Button = PointerButton.Left);
}
@@ -0,0 +1,6 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record PointerDragReleased(PointerLocation Location, PointerButton Button = PointerButton.Left);
public record PointerReleased(PointerLocation Location, PointerButton Button = PointerButton.Left);
}
@@ -0,0 +1,14 @@
using Windows.Foundation;
using Windows.Win32.Foundation;
namespace TheXamlGuy.TaskbarGroup.Core
{
internal static class RECTExtensions
{
internal static Rect ToRect(this RECT rect)
{
if (rect.right - rect.left < 0 || rect.bottom - rect.top < 0) return new Rect(rect.left, rect.top, 0, 0);
return new Rect(rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top);
}
}
}
+109
View File
@@ -0,0 +1,109 @@
using System.Runtime.InteropServices;
using Windows.Foundation;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
using Windows.Win32.Graphics.Gdi;
namespace TheXamlGuy.TaskbarGroup.Core
{
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)
{
return _multiMonitorSupport ? new Screen(PInvoke.MonitorFromWindow((HWND)handle, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST)) : new Screen((IntPtr)PRIMARY_MONITOR);
}
public override bool Equals(object? obj)
{
if (obj is not Screen monitor) return false;
return _monitorHandle == monitor._monitorHandle;
}
public override int GetHashCode()
{
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)
{
var monitorData = new MonitorData();
monitorData.Size = Marshal.SizeOf(monitorData);
GetMonitorInfoEx(monitorHandle, ref monitorData);
return monitorData;
}
private Rect GetWorkingArea()
{
if (!_multiMonitorSupport || _monitorHandle == (IntPtr)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,30 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public class ServiceFactory : IServiceFactory
{
private readonly Func<Type, object> factory;
private readonly Func<Type, object[], object> factoryWithParameters;
public ServiceFactory(Func<Type, object> factory, Func<Type, object[], object> factoryWithParameters)
{
this.factory = factory;
this.factoryWithParameters = factoryWithParameters;
}
public T Create<T>(params object[] parameters)
{
return (T)factoryWithParameters(typeof(T), parameters);
}
public T Create<T>(Type type)
{
return (T)factory(type);
}
public T Create<T>()
{
return (T)factory(typeof(T));
}
}
}
@@ -0,0 +1,33 @@
using System.Runtime.InteropServices;
using Windows.Foundation;
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
namespace TheXamlGuy.TaskbarGroup.Core
{
internal static class SystemInformationHelper
{
private const int SPI_GETWORKAREA = 48;
public static Rect VirtualScreen => GetVirtualScreen();
public static Rect WorkingArea => GetWorkingArea();
private static Rect GetVirtualScreen()
{
var size = new Size(PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CXSCREEN), PInvoke.GetSystemMetrics(SYSTEM_METRICS_INDEX.SM_CYSCREEN));
return new Rect(0, 0, size.Width, 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);
}
}
+80
View File
@@ -0,0 +1,80 @@
using System.Runtime.InteropServices;
using Windows.Win32.Foundation;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class Taskbar : ITaskbar
{
private const string ShellTrayHandleName = "Shell_TrayWnd";
private enum AppBarEdge : uint
{
Left = 0,
Top = 1,
Right = 2,
Bottom = 3
}
private enum AppBarMessage : uint
{
New = 0x00000000,
Remove = 0x00000001,
QueryPos = 0x00000002,
SetPos = 0x00000003,
GetState = 0x00000004,
GetTaskbarPos = 0x00000005,
Activate = 0x00000006,
GetAutoHideBar = 0x00000007,
SetAutoHideBar = 0x00000008,
WindowPosChanged = 0x00000009,
SetState = 0x0000000A,
}
public TaskbarState GetCurrentState()
{
var handle = GetSystemTrayHandle();
var state = new TaskbarState
{
Screen = Screen.FromHandle(handle)
};
var appBarData = GetAppBarData(handle);
GetAppBarPosition(ref appBarData);
state.Rect = appBarData.rect.ToRect();
state.Placement = (TaskbarPlacement)appBarData.uEdge;
return state;
}
[DllImport("user32.dll", SetLastError = true)]
private static extern IntPtr DefWindowProcW(IntPtr handle, uint msg, IntPtr wParam, IntPtr lParam);
private static IntPtr GetSystemTrayHandle() => WindowHelper.Find(ShellTrayHandleName);
[DllImport("shell32.dll", SetLastError = true)]
private static extern IntPtr SHAppBarMessage(AppBarMessage dwMessage, ref AppBarData pData);
private AppBarData GetAppBarData(IntPtr handle)
{
return new AppBarData
{
cbSize = (uint)Marshal.SizeOf(typeof(AppBarData)),
hWnd = handle
};
}
private void GetAppBarPosition(ref AppBarData appBarData) => SHAppBarMessage(AppBarMessage.GetTaskbarPos, ref appBarData);
[StructLayout(LayoutKind.Sequential)]
private struct AppBarData
{
public uint cbSize;
public IntPtr hWnd;
public uint uCallbackMessage;
public AppBarEdge uEdge;
public RECT rect;
public int lParam;
}
}
}
@@ -0,0 +1,103 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public class TaskbarButton : ITaskbarButton
{
private readonly IMessenger messenger;
private readonly IDisposer disposer;
private bool isWithinBounds;
private bool isDrag;
public TaskbarButton(IMessenger messenger,
IDisposer disposer,
string name,
TaskbarButtonBounds bounds)
{
this.messenger = messenger;
this.disposer = disposer;
Name = name;
Bounds = bounds;
disposer.Add(this, messenger.Subscribe<PointerReleased>(OnPointerReleased));
disposer.Add(this, messenger.Subscribe<PointerMoved>(OnPointerMoved));
disposer.Add(this, messenger.Subscribe<PointerDrag>(OnPointerDrag));
}
public TaskbarButtonBounds Bounds { get; internal set; }
public string Name { get; internal set; }
public void Dispose()
{
disposer.Dispose(this);
GC.SuppressFinalize(this);
}
private bool IsWithinBounds(PointerLocation args)
{
if (args.X >= Bounds.X
&& args.X <= Bounds.X + Bounds.Width
&& args.Y >= Bounds.Y
&& args.Y <= Bounds.Y + Bounds.Height)
{
return true;
}
else
{
return false;
}
}
private void OnPointerDrag(PointerDrag args)
{
if (isWithinBounds)
{
if (isDrag)
{
messenger.Send(new TaskbarButtonDragOver(this));
}
else
{
messenger.Send(new TaskbarButtonDragEnter(this));
}
isDrag = true;
}
else
{
isDrag = false;
}
}
private void OnPointerMoved(PointerMoved args)
{
if (IsWithinBounds(args.Location))
{
if (isWithinBounds)
{
return;
}
isWithinBounds = true;
messenger.Send(new TaskbarButtonEntered(this));
}
else
{
isDrag = false;
isWithinBounds = false;
}
}
private void OnPointerReleased(PointerReleased args)
{
if (!isDrag && isWithinBounds)
{
messenger.Send(new TaskbarButtonInvoked(this));
}
if (isDrag)
{
isDrag = false;
}
}
}
}
@@ -0,0 +1,23 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record TaskbarButtonBounds
{
public TaskbarButtonBounds()
{
}
public TaskbarButtonBounds(int x, int y, int width, int height)
{
X = x;
Y = y;
Width = width;
Height = height;
}
public int X { get; }
public int Y { get; }
public int Width { get; }
public int Height { get; }
}
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record TaskbarButtonCreated(TaskbarButton Button);
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record TaskbarButtonDragEnter(TaskbarButton Button);
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record TaskbarButtonDragOver(TaskbarButton Button);
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record TaskbarButtonEntered(TaskbarButton Button);
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record TaskbarButtonInvoked(TaskbarButton Button);
}
@@ -0,0 +1,153 @@
using Windows.Win32.Foundation;
using UIAutomationClient;
using System.Diagnostics;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class TaskbarButtonMonitor : ITaskbarButtonMonitor
{
private readonly IDispatcherTimer dispatcherTimer;
private readonly IDispatcherTimerFactory dispatcherTimerFactory;
private readonly IServiceFactory serviceFactory;
private readonly IMessenger messenger;
private readonly Dictionary<string, TaskbarButton> taskbarButtons = new();
private RECT taskbarBoundsCache;
private IUIAutomationCondition? taskListCondition;
private IUIAutomationElement? taskListElement;
private HWND taskListHandle;
public TaskbarButtonMonitor(IMessenger messenger,
IDispatcherTimerFactory dispatcherTimerFactory,
IServiceFactory serviceFactory)
{
this.messenger = messenger;
this.dispatcherTimerFactory = dispatcherTimerFactory;
this.serviceFactory = serviceFactory;
dispatcherTimer = dispatcherTimerFactory.Create(OnDispatcher, TimeSpan.FromMilliseconds(500));
}
public void Initialize()
{
var clientUIAutomation = new CUIAutomation();
taskListCondition = clientUIAutomation.CreateTrueCondition();
var trayHandle = WindowHelper.Find("Shell_TrayWnd");
var rebarHandle = WindowHelper.Find("ReBarWindow32", trayHandle);
var taskHandle = WindowHelper.Find("MSTaskSwWClass", rebarHandle);
taskListHandle = WindowHelper.Find("MSTaskListWClass", taskHandle);
taskListElement = clientUIAutomation.ElementFromHandle(taskListHandle);
if (WindowHelper.TryGetBounds(taskListHandle, out var bounds))
{
taskbarBoundsCache = bounds;
}
dispatcherTimer.Start();
UpdateTaskbarButtons();
}
private bool CheckDirtyTaskbarRegion()
{
if (WindowHelper.TryGetBounds(taskListHandle, out var bounds))
{
var width = taskbarBoundsCache.right - taskbarBoundsCache.left;
var height = taskbarBoundsCache.bottom - taskbarBoundsCache.top;
var deltaWidth = bounds.right - bounds.left;
var deltaHeight = bounds.bottom - bounds.top;
if (width != deltaWidth || height != deltaHeight)
{
taskbarBoundsCache = bounds;
return true;
}
}
return false;
}
private Dictionary<string, tagRECT> FindTaskbarButtons()
{
var taskElements = taskListElement?.FindAll(TreeScope.TreeScope_Descendants | TreeScope.TreeScope_Children, taskListCondition);
var buttons = new Dictionary<string, tagRECT>();
if (taskElements is not null)
{
for (int index = 0; index <= taskElements.Length - 1; index++)
{
var taskUIElement = taskElements.GetElement(index);
var name = taskUIElement.CurrentName;
var rect = taskUIElement.CurrentBoundingRectangle;
buttons.Add(name, rect);
}
}
return buttons;
}
private void OnDispatcher()
{
dispatcherTimer.Stop();
if (CheckDirtyTaskbarRegion())
{
UpdateTaskbarButtons();
}
dispatcherTimer.Start();
}
private void UpdateTaskbarButtons()
{
if (taskListElement is null)
{
return;
}
var buttons = FindTaskbarButtons();
foreach (var buttonToRemove in taskbarButtons.Where(taskbarButton => !buttons.ContainsKey(taskbarButton.Key)))
{
var key = buttonToRemove.Key;
var button = buttonToRemove.Value;
Debug.WriteLine($"{key} button removed");
taskbarButtons.Remove(key);
messenger.Send(new TaskbarButtonRemoved(button));
button.Dispose();
}
foreach (var button in buttons)
{
var name = button.Key;
var bounds = button.Value;
var buttonBounds = new TaskbarButtonBounds(bounds.left,
bounds.top,
bounds.right - bounds.left,
bounds.bottom - bounds.top);
if (taskbarButtons.TryGetValue(name, out var taskbarButton))
{
Debug.WriteLine($"{name} button updated");
taskbarButtons[name].Bounds = buttonBounds;
messenger.Send(new TaskbarButtonUpdated(taskbarButtons[name]));
}
else
{
Debug.WriteLine($"{name} button added");
taskbarButtons.Add(name, serviceFactory.Create<TaskbarButton>(name, buttonBounds));
messenger.Send(new TaskbarButtonCreated(taskbarButtons[name]));
}
}
}
}
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record TaskbarButtonRemoved(TaskbarButton Button);
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record TaskbarButtonUpdated(TaskbarButton Button);
}
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record TaskbarChanged;
}
@@ -0,0 +1,30 @@
using Windows.Win32;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class TaskbarMonitor : ITaskbarMonitor
{
private const int SPI_SETWORKAREA = 0x002F;
private readonly uint WM_TASKBARCREATED = PInvoke.RegisterWindowMessage("TaskbarCreated");
private readonly IMessenger messenger;
public TaskbarMonitor(IMessenger messenger)
{
this.messenger = messenger;
}
public void Initialize()
{
messenger.Subscribe<WndProc>(OnWndProc);
}
private void OnWndProc(WndProc args)
{
if (args.Message == WM_TASKBARCREATED || args.Message == (int)WndProcMessages.WM_SETTINGCHANGE && (int)args.WParam == SPI_SETWORKAREA)
{
messenger.Send<TaskbarChanged>();
}
}
}
}
@@ -0,0 +1,10 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public enum TaskbarPlacement
{
Left = 0,
Top = 1,
Right = 2,
Bottom = 3
}
}
@@ -0,0 +1,11 @@
using Windows.Foundation;
namespace TheXamlGuy.TaskbarGroup.Core
{
public struct TaskbarState
{
public TaskbarPlacement Placement;
public Rect Rect;
public Screen Screen;
}
}
@@ -0,0 +1,42 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<ResolveComReferenceSilent>True</ResolveComReferenceSilent>
<LangVersion>10.0</LangVersion>
<Platforms>x64;x86</Platforms>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommunityToolkit.Mvvm" Version="8.0.0-preview2" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="6.0.0" />
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.1.635-beta">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="System.Reactive" Version="5.0.0" />
<PackageReference Include="System.Runtime.WindowsRuntime" Version="5.0.0-preview.5.20278.1" />
</ItemGroup>
<ItemGroup>
<COMReference Include="UIA">
<WrapperTool>tlbimp</WrapperTool>
<VersionMinor>0</VersionMinor>
<VersionMajor>1</VersionMajor>
<Guid>930299ce-9965-4dec-b0f4-a54848d4b667</Guid>
<Lcid>0</Lcid>
<Isolated>false</Isolated>
<EmbedInteropTypes>true</EmbedInteropTypes>
</COMReference>
<COMReference Include="UIAutomationClient">
<VersionMinor>0</VersionMinor>
<VersionMajor>1</VersionMajor>
<Guid>944de083-8fb8-45cf-bcb7-c477acb2f897</Guid>
<Lcid>0</Lcid>
<WrapperTool>tlbimp</WrapperTool>
<Isolated>false</Isolated>
<EmbedInteropTypes>true</EmbedInteropTypes>
</COMReference>
</ItemGroup>
</Project>
@@ -0,0 +1,37 @@
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.UI.WindowsAndMessaging;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class WindowHelper
{
public static void MoveAndResize(HWND handle, int x, int y, int width, int height)
{
PInvoke.SetWindowPos(handle, new HWND(), x, y, width, height, 0);
}
public static void BringToForeground(HWND handle)
{
if (TryGetBounds(handle, out var 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 HWND Find(string windowName) => PInvoke.FindWindow(windowName, null);
public static HWND Find(string windowName, HWND parentHandle)
{
return PInvoke.FindWindowEx(parentHandle, new HWND(), windowName, null);
}
public static unsafe bool TryGetBounds(IntPtr handle, out RECT rect)
{
fixed (RECT* lpRectLocal = &rect)
{
return PInvoke.GetWindowRect(new HWND(handle), lpRectLocal);
}
}
}
}
+4
View File
@@ -0,0 +1,4 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
public record WndProc(uint Message, uint WParam, uint LParam);
}
@@ -0,0 +1,14 @@
namespace TheXamlGuy.TaskbarGroup.Core
{
internal enum WndProcMessages
{
WM_LBUTTONUP = 0x0202,
WM_MBUTTONUP = 0x0208,
WM_RBUTTONUP = 0x0205,
WM_MOUSEMOVE = 0x0200,
WM_SETTINGCHANGE = 0x001A,
WM_MBUTTONDOWN = 0x0207,
WM_LBUTTONDOWN = 0x0201,
WM_RBUTTONDOWN = 0x0204
}
}
@@ -0,0 +1,68 @@
using Windows.Win32;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Gdi;
using Windows.Win32.UI.WindowsAndMessaging;
namespace TheXamlGuy.TaskbarGroup.Core
{
public class WndProcMonitor : IWndProcMonitor
{
private WNDPROC? handler;
private readonly IMessenger messenger;
public WndProcMonitor(IMessenger messenger)
{
this.messenger = messenger;
}
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)
{
messenger.Send(new WndProc(param1, (uint)param2.Value, (uint)param3.Value));
return PInvoke.DefWindowProc(param0, param1, param2, param3);
}
public void Initialize()
{
InitializeWndProc();
}
}
}