csharpc-sharpdotnetxamlavaloniauicross-platformcross-platform-xamlavaloniaguimulti-platformuser-interfacedotnetcore
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
424 lines
15 KiB
424 lines
15 KiB
// -----------------------------------------------------------------------
|
|
// <copyright file="WindowImpl.cs" company="Steven Kirk">
|
|
// Copyright 2014 MIT Licence. See licence.md for more information.
|
|
// </copyright>
|
|
// -----------------------------------------------------------------------
|
|
|
|
using Perspex.Input;
|
|
|
|
namespace Perspex.Win32
|
|
{
|
|
using System;
|
|
using System.Collections.Generic;
|
|
using System.ComponentModel;
|
|
using System.Diagnostics.CodeAnalysis;
|
|
using System.Linq;
|
|
using System.Reactive.Disposables;
|
|
using System.Reactive.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using Perspex.Controls;
|
|
using Perspex.Input.Raw;
|
|
using Perspex.Platform;
|
|
using Perspex.Win32.Input;
|
|
using Perspex.Win32.Interop;
|
|
|
|
public class WindowImpl : IWindowImpl
|
|
{
|
|
private static List<WindowImpl> instances = new List<WindowImpl>();
|
|
|
|
private static readonly IntPtr DefaultCursor = UnmanagedMethods.LoadCursor(
|
|
IntPtr.Zero, new IntPtr((int) UnmanagedMethods.Cursor.IDC_ARROW));
|
|
|
|
private UnmanagedMethods.WndProc wndProcDelegate;
|
|
|
|
private string className;
|
|
|
|
private IntPtr hwnd;
|
|
|
|
private TopLevel owner;
|
|
|
|
private bool trackingMouse;
|
|
|
|
public WindowImpl()
|
|
{
|
|
this.CreateWindow();
|
|
instances.Add(this);
|
|
}
|
|
|
|
public Action Activated { get; set; }
|
|
|
|
public Action Closed { get; set; }
|
|
|
|
public Action Deactivated { get; set; }
|
|
|
|
public Action<RawInputEventArgs> Input { get; set; }
|
|
|
|
public Action<Rect, IPlatformHandle> Paint { get; set; }
|
|
|
|
public Action<Size> Resized { get; set; }
|
|
|
|
public Size ClientSize
|
|
{
|
|
get
|
|
{
|
|
UnmanagedMethods.RECT rect;
|
|
UnmanagedMethods.GetClientRect(this.hwnd, out rect);
|
|
return new Size(rect.right, rect.bottom);
|
|
}
|
|
|
|
set
|
|
{
|
|
if (value != this.ClientSize)
|
|
{
|
|
var style = UnmanagedMethods.GetWindowLong(this.hwnd, -16);
|
|
var exStyle = UnmanagedMethods.GetWindowLong(this.hwnd, -20);
|
|
var padding = new UnmanagedMethods.RECT();
|
|
|
|
if (UnmanagedMethods.AdjustWindowRectEx(ref padding, style, false, exStyle))
|
|
{
|
|
UnmanagedMethods.SetWindowPos(
|
|
this.hwnd,
|
|
IntPtr.Zero,
|
|
0,
|
|
0,
|
|
-padding.left + padding.right + (int)value.Width,
|
|
-padding.top + padding.bottom + (int)value.Height,
|
|
UnmanagedMethods.SetWindowPosFlags.SWP_RESIZE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
public IPlatformHandle Handle
|
|
{
|
|
get;
|
|
private set;
|
|
}
|
|
|
|
public bool IsEnabled
|
|
{
|
|
get { return UnmanagedMethods.IsWindowEnabled(this.hwnd); }
|
|
set { UnmanagedMethods.EnableWindow(this.hwnd, value); }
|
|
}
|
|
|
|
public void Activate()
|
|
{
|
|
UnmanagedMethods.SetActiveWindow(this.hwnd);
|
|
}
|
|
|
|
public IPopupImpl CreatePopup()
|
|
{
|
|
return new PopupImpl();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
instances.Remove(this);
|
|
UnmanagedMethods.DestroyWindow(this.hwnd);
|
|
}
|
|
|
|
public void Hide()
|
|
{
|
|
UnmanagedMethods.ShowWindow(this.hwnd, UnmanagedMethods.ShowWindowCommand.Hide);
|
|
}
|
|
|
|
public void Invalidate(Rect rect)
|
|
{
|
|
var r = new UnmanagedMethods.RECT
|
|
{
|
|
left = (int)rect.X,
|
|
top = (int)rect.Y,
|
|
right = (int)rect.Right,
|
|
bottom = (int)rect.Bottom,
|
|
};
|
|
|
|
UnmanagedMethods.InvalidateRect(this.hwnd, ref r, false);
|
|
}
|
|
|
|
public Point PointToScreen(Point point)
|
|
{
|
|
var p = new UnmanagedMethods.POINT { X = (int)point.X, Y = (int)point.Y };
|
|
UnmanagedMethods.ClientToScreen(this.hwnd, ref p);
|
|
return new Point(p.X, p.Y);
|
|
}
|
|
|
|
public void SetOwner(TopLevel owner)
|
|
{
|
|
this.owner = owner;
|
|
}
|
|
|
|
public void SetTitle(string title)
|
|
{
|
|
UnmanagedMethods.SetWindowText(this.hwnd, title);
|
|
}
|
|
|
|
public virtual void Show()
|
|
{
|
|
UnmanagedMethods.ShowWindow(this.hwnd, UnmanagedMethods.ShowWindowCommand.Normal);
|
|
}
|
|
|
|
public virtual IDisposable ShowDialog()
|
|
{
|
|
var disabled = instances.Where(x => x != this && x.IsEnabled).ToList();
|
|
TopLevel activated = null;
|
|
|
|
foreach (var window in disabled)
|
|
{
|
|
if (window.owner.IsActive)
|
|
{
|
|
activated = window.owner;
|
|
}
|
|
|
|
window.IsEnabled = false;
|
|
}
|
|
|
|
this.Show();
|
|
|
|
return Disposable.Create(() =>
|
|
{
|
|
foreach (var window in disabled)
|
|
{
|
|
window.IsEnabled = true;
|
|
}
|
|
|
|
if (activated != null)
|
|
{
|
|
activated.Activate();
|
|
}
|
|
});
|
|
}
|
|
|
|
public void SetCursor(IPlatformHandle cursor)
|
|
{
|
|
UnmanagedMethods.SetClassLong(this.hwnd, UnmanagedMethods.ClassLongIndex.GCL_HCURSOR,
|
|
cursor?.Handle ?? DefaultCursor);
|
|
}
|
|
|
|
protected virtual IntPtr CreateWindowOverride(ushort atom)
|
|
{
|
|
return UnmanagedMethods.CreateWindowEx(
|
|
0,
|
|
atom,
|
|
null,
|
|
(int)UnmanagedMethods.WindowStyles.WS_OVERLAPPEDWINDOW,
|
|
UnmanagedMethods.CW_USEDEFAULT,
|
|
UnmanagedMethods.CW_USEDEFAULT,
|
|
UnmanagedMethods.CW_USEDEFAULT,
|
|
UnmanagedMethods.CW_USEDEFAULT,
|
|
IntPtr.Zero,
|
|
IntPtr.Zero,
|
|
IntPtr.Zero,
|
|
IntPtr.Zero);
|
|
}
|
|
|
|
[SuppressMessage("Microsoft.StyleCop.CSharp.NamingRules", "SA1305:FieldNamesMustNotUseHungarianNotation", Justification = "Using Win32 naming for consistency.")]
|
|
protected virtual IntPtr WndProc(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam)
|
|
{
|
|
bool unicode = UnmanagedMethods.IsWindowUnicode(hWnd);
|
|
|
|
const double WheelDelta = 120.0;
|
|
uint timestamp = unchecked((uint)UnmanagedMethods.GetMessageTime());
|
|
|
|
RawInputEventArgs e = null;
|
|
|
|
WindowsMouseDevice.Instance.CurrentWindow = this;
|
|
|
|
switch ((UnmanagedMethods.WindowsMessage)msg)
|
|
{
|
|
case UnmanagedMethods.WindowsMessage.WM_ACTIVATE:
|
|
var wa = (UnmanagedMethods.WindowActivate)((int)wParam & 0xffff);
|
|
|
|
switch (wa)
|
|
{
|
|
case UnmanagedMethods.WindowActivate.WA_ACTIVE:
|
|
case UnmanagedMethods.WindowActivate.WA_CLICKACTIVE:
|
|
if (this.Activated != null)
|
|
{
|
|
this.Activated();
|
|
}
|
|
|
|
break;
|
|
|
|
case UnmanagedMethods.WindowActivate.WA_INACTIVE:
|
|
if (this.Deactivated != null)
|
|
{
|
|
this.Deactivated();
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
return IntPtr.Zero;
|
|
|
|
case UnmanagedMethods.WindowsMessage.WM_DESTROY:
|
|
if (this.Closed != null)
|
|
{
|
|
UnmanagedMethods.UnregisterClass(this.className, Marshal.GetHINSTANCE(this.GetType().Module));
|
|
this.Closed();
|
|
}
|
|
|
|
return IntPtr.Zero;
|
|
|
|
case UnmanagedMethods.WindowsMessage.WM_KEYDOWN:
|
|
case UnmanagedMethods.WindowsMessage.WM_SYSKEYDOWN:
|
|
e = new RawKeyEventArgs(
|
|
WindowsKeyboardDevice.Instance,
|
|
timestamp,
|
|
RawKeyEventType.KeyDown,
|
|
KeyInterop.KeyFromVirtualKey((int)wParam), WindowsKeyboardDevice.Instance.Modifiers);
|
|
break;
|
|
|
|
case UnmanagedMethods.WindowsMessage.WM_KEYUP:
|
|
case UnmanagedMethods.WindowsMessage.WM_SYSKEYUP:
|
|
e = new RawKeyEventArgs(
|
|
WindowsKeyboardDevice.Instance,
|
|
timestamp,
|
|
RawKeyEventType.KeyUp,
|
|
KeyInterop.KeyFromVirtualKey((int)wParam), WindowsKeyboardDevice.Instance.Modifiers);
|
|
break;
|
|
case UnmanagedMethods.WindowsMessage.WM_CHAR:
|
|
// Ignore control chars
|
|
if (wParam.ToInt32() > 32)
|
|
{
|
|
e = new RawTextInputEventArgs(WindowsKeyboardDevice.Instance, timestamp,
|
|
new string((char) wParam.ToInt32(), 1));
|
|
}
|
|
|
|
break;
|
|
case UnmanagedMethods.WindowsMessage.WM_LBUTTONDOWN:
|
|
e = new RawMouseEventArgs(
|
|
WindowsMouseDevice.Instance,
|
|
timestamp,
|
|
this.owner,
|
|
RawMouseEventType.LeftButtonDown,
|
|
new Point((uint)lParam & 0xffff, (uint)lParam >> 16), WindowsKeyboardDevice.Instance.Modifiers);
|
|
break;
|
|
|
|
case UnmanagedMethods.WindowsMessage.WM_LBUTTONUP:
|
|
e = new RawMouseEventArgs(
|
|
WindowsMouseDevice.Instance,
|
|
timestamp,
|
|
this.owner,
|
|
RawMouseEventType.LeftButtonUp,
|
|
new Point((uint)lParam & 0xffff, (uint)lParam >> 16), WindowsKeyboardDevice.Instance.Modifiers);
|
|
break;
|
|
|
|
case UnmanagedMethods.WindowsMessage.WM_MOUSEMOVE:
|
|
if (!this.trackingMouse)
|
|
{
|
|
var tm = new UnmanagedMethods.TRACKMOUSEEVENT
|
|
{
|
|
cbSize = Marshal.SizeOf(typeof(UnmanagedMethods.TRACKMOUSEEVENT)),
|
|
dwFlags = 2,
|
|
hwndTrack = this.hwnd,
|
|
dwHoverTime = 0,
|
|
};
|
|
|
|
UnmanagedMethods.TrackMouseEvent(ref tm);
|
|
}
|
|
|
|
e = new RawMouseEventArgs(
|
|
WindowsMouseDevice.Instance,
|
|
timestamp,
|
|
this.owner,
|
|
RawMouseEventType.Move,
|
|
new Point((uint)lParam & 0xffff, (uint)lParam >> 16), WindowsKeyboardDevice.Instance.Modifiers);
|
|
break;
|
|
|
|
case UnmanagedMethods.WindowsMessage.WM_MOUSEWHEEL:
|
|
e = new RawMouseWheelEventArgs(
|
|
WindowsMouseDevice.Instance,
|
|
timestamp,
|
|
this.owner,
|
|
this.ScreenToClient((uint)lParam & 0xffff, (uint)lParam >> 16),
|
|
new Vector(0, ((int)wParam >> 16) / WheelDelta), WindowsKeyboardDevice.Instance.Modifiers);
|
|
break;
|
|
|
|
case UnmanagedMethods.WindowsMessage.WM_MOUSELEAVE:
|
|
this.trackingMouse = false;
|
|
e = new RawMouseEventArgs(
|
|
WindowsMouseDevice.Instance,
|
|
timestamp,
|
|
this.owner,
|
|
RawMouseEventType.LeaveWindow,
|
|
new Point(), WindowsKeyboardDevice.Instance.Modifiers);
|
|
break;
|
|
|
|
case UnmanagedMethods.WindowsMessage.WM_PAINT:
|
|
if (this.Paint != null)
|
|
{
|
|
UnmanagedMethods.PAINTSTRUCT ps;
|
|
|
|
if (UnmanagedMethods.BeginPaint(this.hwnd, out ps) != IntPtr.Zero)
|
|
{
|
|
UnmanagedMethods.RECT r;
|
|
UnmanagedMethods.GetUpdateRect(this.hwnd, out r, false);
|
|
this.Paint(new Rect(r.left, r.top, r.right - r.left, r.bottom - r.top), this.Handle);
|
|
UnmanagedMethods.EndPaint(this.hwnd, ref ps);
|
|
}
|
|
}
|
|
|
|
return IntPtr.Zero;
|
|
|
|
case UnmanagedMethods.WindowsMessage.WM_SIZE:
|
|
if (this.Resized != null)
|
|
{
|
|
var clientSize = new Size((int)lParam & 0xffff, (int)lParam >> 16);
|
|
this.Resized(clientSize);
|
|
}
|
|
|
|
return IntPtr.Zero;
|
|
}
|
|
|
|
if (e != null && this.Input != null)
|
|
{
|
|
this.Input(e);
|
|
return IntPtr.Zero;
|
|
}
|
|
|
|
return UnmanagedMethods.DefWindowProc(hWnd, msg, wParam, lParam);
|
|
}
|
|
|
|
private void CreateWindow()
|
|
{
|
|
// Ensure that the delegate doesn't get garbage collected by storing it as a field.
|
|
this.wndProcDelegate = new UnmanagedMethods.WndProc(this.WndProc);
|
|
|
|
this.className = Guid.NewGuid().ToString();
|
|
|
|
UnmanagedMethods.WNDCLASSEX wndClassEx = new UnmanagedMethods.WNDCLASSEX
|
|
{
|
|
cbSize = Marshal.SizeOf(typeof(UnmanagedMethods.WNDCLASSEX)),
|
|
style = 0,
|
|
lpfnWndProc = this.wndProcDelegate,
|
|
hInstance = Marshal.GetHINSTANCE(this.GetType().Module),
|
|
hCursor = DefaultCursor,
|
|
hbrBackground = (IntPtr)5,
|
|
lpszClassName = this.className,
|
|
};
|
|
|
|
ushort atom = UnmanagedMethods.RegisterClassEx(ref wndClassEx);
|
|
|
|
if (atom == 0)
|
|
{
|
|
throw new Win32Exception();
|
|
}
|
|
|
|
this.hwnd = this.CreateWindowOverride(atom);
|
|
|
|
if (this.hwnd == IntPtr.Zero)
|
|
{
|
|
throw new Win32Exception();
|
|
}
|
|
|
|
this.Handle = new PlatformHandle(this.hwnd, PlatformConstants.WindowHandleType);
|
|
}
|
|
|
|
private Point ScreenToClient(uint x, uint y)
|
|
{
|
|
var p = new UnmanagedMethods.POINT { X = (int)x, Y = (int)y };
|
|
UnmanagedMethods.ScreenToClient(this.hwnd, ref p);
|
|
return new Point(p.X, p.Y);
|
|
}
|
|
}
|
|
}
|
|
|