472 lines
17 KiB
C#
472 lines
17 KiB
C#
using System;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.InteropServices;
|
|
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Interop;
|
|
using System.Windows.Media;
|
|
using System.Windows.Threading;
|
|
using HandyControl.Data;
|
|
using HandyControl.Expression.Drawing;
|
|
using HandyControl.Tools;
|
|
using HandyControl.Tools.Interop;
|
|
|
|
namespace HandyControl.Controls
|
|
{
|
|
public class GlowWindow : Window
|
|
{
|
|
internal int DeferGlowChangesCount;
|
|
|
|
private readonly GlowEdge[] _glowEdges = new GlowEdge[4];
|
|
|
|
private DispatcherTimer _makeGlowVisibleTimer;
|
|
|
|
private bool _isGlowVisible;
|
|
|
|
private bool _useLogicalSizeForRestore;
|
|
|
|
private bool _updatingZOrder;
|
|
|
|
private Rect _logicalSizeForRestore = Rect.Empty;
|
|
|
|
public static readonly DependencyProperty ActiveGlowColorProperty = DependencyProperty.Register(
|
|
"ActiveGlowColor", typeof(Color), typeof(GlowWindow), new PropertyMetadata(default(Color), OnGlowColorChanged));
|
|
|
|
public Color ActiveGlowColor
|
|
{
|
|
get => (Color)GetValue(ActiveGlowColorProperty);
|
|
set => SetValue(ActiveGlowColorProperty, value);
|
|
}
|
|
|
|
public static readonly DependencyProperty InactiveGlowColorProperty = DependencyProperty.Register(
|
|
"InactiveGlowColor", typeof(Color), typeof(GlowWindow), new PropertyMetadata(default(Color), OnGlowColorChanged));
|
|
|
|
public Color InactiveGlowColor
|
|
{
|
|
get => (Color)GetValue(InactiveGlowColorProperty);
|
|
set => SetValue(InactiveGlowColorProperty, value);
|
|
}
|
|
|
|
static GlowWindow()
|
|
{
|
|
StyleProperty.OverrideMetadata(typeof(GlowWindow), new FrameworkPropertyMetadata(Application.Current.FindResource(ResourceToken.WindowGlow)));
|
|
ResizeModeProperty.OverrideMetadata(typeof(GlowWindow), new FrameworkPropertyMetadata(OnResizeModeChanged));
|
|
}
|
|
|
|
#region internal
|
|
|
|
internal void EndDeferGlowChanges()
|
|
{
|
|
foreach (var current in LoadedGlowWindows) current.CommitChanges();
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region protected
|
|
|
|
protected virtual bool ShouldShowGlow
|
|
{
|
|
get
|
|
{
|
|
var handle = this.GetHandle();
|
|
return InteropMethods.IsWindowVisible(handle) && !InteropMethods.IsIconic(handle) &&
|
|
!InteropMethods.IsZoomed(handle) && ResizeMode != ResizeMode.NoResize;
|
|
}
|
|
}
|
|
|
|
protected virtual IntPtr HwndSourceHook(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
|
|
{
|
|
if (msg <= InteropValues.WM_WINDOWPOSCHANGED)
|
|
{
|
|
if (msg == InteropValues.WM_ACTIVATE)
|
|
{
|
|
return IntPtr.Zero;
|
|
}
|
|
|
|
if (msg != InteropValues.WM_QUIT)
|
|
{
|
|
switch (msg)
|
|
{
|
|
case InteropValues.WM_WINDOWPOSCHANGING:
|
|
WmWindowPosChanging(lParam);
|
|
return IntPtr.Zero;
|
|
case InteropValues.WM_WINDOWPOSCHANGED:
|
|
WmWindowPosChanged(lParam);
|
|
return IntPtr.Zero;
|
|
default:
|
|
return IntPtr.Zero;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (msg <= InteropValues.WM_NCRBUTTONDBLCLK)
|
|
{
|
|
switch (msg)
|
|
{
|
|
case InteropValues.WM_SETICON:
|
|
break;
|
|
case InteropValues.WM_NCCREATE:
|
|
case InteropValues.WM_NCDESTROY:
|
|
return IntPtr.Zero;
|
|
case InteropValues.WM_NCACTIVATE:
|
|
handled = true;
|
|
return WmNcActivate(hWnd, wParam);
|
|
default:
|
|
switch (msg)
|
|
{
|
|
case InteropValues.WM_NCRBUTTONDOWN:
|
|
case InteropValues.WM_NCRBUTTONUP:
|
|
case InteropValues.WM_NCRBUTTONDBLCLK:
|
|
handled = true;
|
|
return IntPtr.Zero;
|
|
default:
|
|
return IntPtr.Zero;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
switch (msg)
|
|
{
|
|
case InteropValues.WM_NCUAHDRAWCAPTION:
|
|
case InteropValues.WM_NCUAHDRAWFRAME:
|
|
handled = true;
|
|
return IntPtr.Zero;
|
|
default:
|
|
if (msg != InteropValues.WM_SYSCOMMAND) return IntPtr.Zero;
|
|
WmSysCommand(hWnd, wParam);
|
|
return IntPtr.Zero;
|
|
}
|
|
}
|
|
}
|
|
|
|
handled = true;
|
|
return CallDefWindowProcWithoutVisibleStyle(hWnd, msg, wParam, lParam);
|
|
}
|
|
|
|
protected override void OnActivated(EventArgs e)
|
|
{
|
|
UpdateGlowActiveState();
|
|
base.OnActivated(e);
|
|
}
|
|
|
|
protected override void OnDeactivated(EventArgs e)
|
|
{
|
|
UpdateGlowActiveState();
|
|
base.OnDeactivated(e);
|
|
}
|
|
|
|
protected override void OnClosed(EventArgs e)
|
|
{
|
|
StopTimer();
|
|
DestroyGlowWindows();
|
|
base.OnClosed(e);
|
|
}
|
|
|
|
protected override void OnSourceInitialized(EventArgs e)
|
|
{
|
|
var hwndSource = HwndSource.FromHwnd(new WindowInteropHelper(this).Handle);
|
|
if (hwndSource != null)
|
|
{
|
|
hwndSource.AddHook(HwndSourceHook);
|
|
CreateGlowWindowHandles();
|
|
}
|
|
|
|
base.OnSourceInitialized(e);
|
|
}
|
|
|
|
#endregion
|
|
|
|
#region private
|
|
|
|
private IEnumerable<GlowEdge> LoadedGlowWindows => from w in _glowEdges where w != null select w;
|
|
|
|
private bool IsGlowVisible
|
|
{
|
|
get => _isGlowVisible;
|
|
set
|
|
{
|
|
if (_isGlowVisible != value)
|
|
{
|
|
_isGlowVisible = value;
|
|
for (var i = 0; i < _glowEdges.Length; i++)
|
|
{
|
|
GetOrCreateGlowWindow(i).IsVisible = value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private GlowEdge GetOrCreateGlowWindow(int direction)
|
|
{
|
|
return _glowEdges[direction] ?? (_glowEdges[direction] = new GlowEdge(this, (Dock)direction)
|
|
{
|
|
ActiveGlowColor = ActiveGlowColor,
|
|
InactiveGlowColor = InactiveGlowColor,
|
|
IsActive = IsActive
|
|
});
|
|
}
|
|
|
|
private static void OnResizeModeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
var customChromeWindow = (GlowWindow)obj;
|
|
customChromeWindow.UpdateGlowVisibility(false);
|
|
}
|
|
|
|
private static void OnGlowColorChanged(DependencyObject obj, DependencyPropertyChangedEventArgs args) => ((GlowWindow)obj).UpdateGlowColors();
|
|
|
|
private void UpdateGlowColors()
|
|
{
|
|
using (DeferGlowChanges())
|
|
{
|
|
foreach (var current in LoadedGlowWindows)
|
|
{
|
|
current.ActiveGlowColor = ActiveGlowColor;
|
|
current.InactiveGlowColor = InactiveGlowColor;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void UpdateGlowVisibility(bool delayIfNecessary)
|
|
{
|
|
var shouldShowGlow = ShouldShowGlow;
|
|
if (shouldShowGlow != IsGlowVisible)
|
|
{
|
|
if (SystemParameters.MinimizeAnimation && shouldShowGlow && delayIfNecessary)
|
|
{
|
|
if (_makeGlowVisibleTimer != null)
|
|
{
|
|
_makeGlowVisibleTimer.Stop();
|
|
}
|
|
else
|
|
{
|
|
_makeGlowVisibleTimer = new DispatcherTimer
|
|
{
|
|
Interval = TimeSpan.FromMilliseconds(200.0)
|
|
};
|
|
_makeGlowVisibleTimer.Tick += OnDelayedVisibilityTimerTick;
|
|
}
|
|
|
|
_makeGlowVisibleTimer.Start();
|
|
return;
|
|
}
|
|
|
|
StopTimer();
|
|
IsGlowVisible = shouldShowGlow;
|
|
}
|
|
}
|
|
|
|
private void StopTimer()
|
|
{
|
|
if (_makeGlowVisibleTimer != null)
|
|
{
|
|
_makeGlowVisibleTimer.Stop();
|
|
_makeGlowVisibleTimer.Tick -= OnDelayedVisibilityTimerTick;
|
|
_makeGlowVisibleTimer = null;
|
|
}
|
|
}
|
|
|
|
private void OnDelayedVisibilityTimerTick(object sender, EventArgs e)
|
|
{
|
|
StopTimer();
|
|
UpdateGlowWindowPositions(false);
|
|
}
|
|
|
|
private void UpdateGlowWindowPositions(bool delayIfNecessary)
|
|
{
|
|
using (DeferGlowChanges())
|
|
{
|
|
UpdateGlowVisibility(delayIfNecessary);
|
|
foreach (var current in LoadedGlowWindows) current.UpdateWindowPos();
|
|
}
|
|
}
|
|
|
|
private IDisposable DeferGlowChanges() => new ChangeScope(this);
|
|
|
|
private void UpdateGlowActiveState()
|
|
{
|
|
using (DeferGlowChanges())
|
|
{
|
|
foreach (var current in LoadedGlowWindows)
|
|
{
|
|
current.IsActive = IsActive;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void DestroyGlowWindows()
|
|
{
|
|
for (var i = 0; i < _glowEdges.Length; i++)
|
|
{
|
|
using (_glowEdges[i])
|
|
{
|
|
_glowEdges[i] = null;
|
|
}
|
|
}
|
|
}
|
|
|
|
private void WmWindowPosChanging(IntPtr lParam)
|
|
{
|
|
var windowpos = (InteropValues.WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(InteropValues.WINDOWPOS));
|
|
if ((windowpos.flags & 2u) == 0u && (windowpos.flags & 1u) == 0u && windowpos.cx > 0 && windowpos.cy > 0)
|
|
{
|
|
var rect = new Rect(windowpos.x, windowpos.y, windowpos.cx, windowpos.cy);
|
|
rect = rect.DeviceToLogicalUnits();
|
|
if (_useLogicalSizeForRestore)
|
|
{
|
|
rect = _logicalSizeForRestore;
|
|
_logicalSizeForRestore = Rect.Empty;
|
|
_useLogicalSizeForRestore = false;
|
|
}
|
|
|
|
var logicalRect = GetOnScreenPosition(rect);
|
|
logicalRect = logicalRect.LogicalToDeviceUnits();
|
|
windowpos.x = (int)logicalRect.X;
|
|
windowpos.y = (int)logicalRect.Y;
|
|
Marshal.StructureToPtr(windowpos, lParam, true);
|
|
}
|
|
}
|
|
|
|
private void UpdateZOrderOfOwner(IntPtr hwndOwner)
|
|
{
|
|
var lastOwnedWindow = IntPtr.Zero;
|
|
InteropMethods.EnumThreadWindows(InteropMethods.GetCurrentThreadId(), delegate (IntPtr hwnd, IntPtr lParam)
|
|
{
|
|
if (InteropMethods.GetWindow(hwnd, 4) == hwndOwner) lastOwnedWindow = hwnd;
|
|
return true;
|
|
}, IntPtr.Zero);
|
|
|
|
if (lastOwnedWindow != IntPtr.Zero && InteropMethods.GetWindow(hwndOwner, 3) != lastOwnedWindow)
|
|
InteropMethods.SetWindowPos(hwndOwner, lastOwnedWindow, 0, 0, 0, 0, 19);
|
|
}
|
|
|
|
private void UpdateZOrderOfThisAndOwner()
|
|
{
|
|
if (_updatingZOrder) return;
|
|
try
|
|
{
|
|
_updatingZOrder = true;
|
|
var windowInteropHelper = new WindowInteropHelper(this);
|
|
var handle = windowInteropHelper.Handle;
|
|
foreach (var current in LoadedGlowWindows)
|
|
{
|
|
var window = InteropMethods.GetWindow(current.Handle, 3);
|
|
if (window != handle) InteropMethods.SetWindowPos(current.Handle, handle, 0, 0, 0, 0, 19);
|
|
handle = current.Handle;
|
|
}
|
|
|
|
var owner = windowInteropHelper.Owner;
|
|
if (owner != IntPtr.Zero) UpdateZOrderOfOwner(owner);
|
|
}
|
|
finally
|
|
{
|
|
_updatingZOrder = false;
|
|
}
|
|
}
|
|
|
|
private void WmWindowPosChanged(IntPtr lParam)
|
|
{
|
|
try
|
|
{
|
|
var windowpos = (InteropValues.WINDOWPOS)Marshal.PtrToStructure(lParam, typeof(InteropValues.WINDOWPOS));
|
|
|
|
UpdateGlowWindowPositions((windowpos.flags & 64u) == 0u);
|
|
UpdateZOrderOfThisAndOwner();
|
|
}
|
|
catch
|
|
{
|
|
// ignored
|
|
}
|
|
}
|
|
|
|
private Rect GetOnScreenPosition(Rect floatRect)
|
|
{
|
|
var result = floatRect;
|
|
floatRect = floatRect.LogicalToDeviceUnits();
|
|
ScreenHelper.FindMaximumSingleMonitorRectangle(floatRect, out _, out var rect2);
|
|
if (!floatRect.IntersectsWith(rect2))
|
|
{
|
|
ScreenHelper.FindMonitorRectsFromPoint(InteropMethods.GetCursorPos(), out _, out rect2);
|
|
rect2 = rect2.DeviceToLogicalUnits();
|
|
if (result.Width > rect2.Width) result.Width = rect2.Width;
|
|
if (result.Height > rect2.Height) result.Height = rect2.Height;
|
|
if (rect2.Right <= result.X) result.X = rect2.Right - result.Width;
|
|
if (rect2.Left > result.X + result.Width) result.X = rect2.Left;
|
|
if (rect2.Bottom <= result.Y) result.Y = rect2.Bottom - result.Height;
|
|
if (rect2.Top > result.Y + result.Height) result.Y = rect2.Top;
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private static InteropValues.MONITORINFO MonitorInfoFromWindow(IntPtr hWnd)
|
|
{
|
|
var hMonitor = InteropMethods.MonitorFromWindow(hWnd, 2);
|
|
var result = default(InteropValues.MONITORINFO);
|
|
result.cbSize = (uint)Marshal.SizeOf(typeof(InteropValues.MONITORINFO));
|
|
InteropMethods.GetMonitorInfo(hMonitor, ref result);
|
|
return result;
|
|
}
|
|
|
|
private IntPtr WmNcActivate(IntPtr hWnd, IntPtr wParam) => InteropMethods.DefWindowProc(hWnd, InteropValues.WM_NCACTIVATE, wParam, InteropMethods.HRGN_NONE);
|
|
|
|
private bool IsAeroSnappedToMonitor(IntPtr hWnd)
|
|
{
|
|
var monitorinfo = MonitorInfoFromWindow(hWnd);
|
|
var logicalRect = new Rect(Left, Top, Width, Height);
|
|
logicalRect = logicalRect.LogicalToDeviceUnits();
|
|
return MathHelper.AreClose(monitorinfo.rcWork.Height, logicalRect.Height) && MathHelper.AreClose(monitorinfo.rcWork.Top, logicalRect.Top);
|
|
}
|
|
|
|
private void WmSysCommand(IntPtr hWnd, IntPtr wParam)
|
|
{
|
|
var num = InteropMethods.GET_SC_WPARAM(wParam);
|
|
|
|
if (num == InteropValues.SC_MOVE)
|
|
{
|
|
InteropMethods.RedrawWindow(hWnd, IntPtr.Zero, IntPtr.Zero,
|
|
InteropValues.RedrawWindowFlags.Invalidate | InteropValues.RedrawWindowFlags.NoChildren |
|
|
InteropValues.RedrawWindowFlags.UpdateNow | InteropValues.RedrawWindowFlags.Frame);
|
|
}
|
|
|
|
if ((num == InteropValues.SC_MAXIMIZE || num == InteropValues.SC_MINIMIZE || num == InteropValues.SC_MOVE ||
|
|
num == InteropValues.SC_SIZE) && WindowState == WindowState.Normal && !IsAeroSnappedToMonitor(hWnd))
|
|
{
|
|
_logicalSizeForRestore = new Rect(Left, Top, Width, Height);
|
|
}
|
|
|
|
if (num == InteropValues.SC_MOVE && WindowState == WindowState.Maximized && _logicalSizeForRestore == Rect.Empty)
|
|
{
|
|
_logicalSizeForRestore = new Rect(Left, Top, Width, Height);
|
|
}
|
|
|
|
if (num == InteropValues.SC_RESTORE && WindowState != WindowState.Minimized &&
|
|
_logicalSizeForRestore.Width > 0.0 && _logicalSizeForRestore.Height > 0.0)
|
|
{
|
|
Left = _logicalSizeForRestore.Left;
|
|
Top = _logicalSizeForRestore.Top;
|
|
Width = _logicalSizeForRestore.Width;
|
|
Height = _logicalSizeForRestore.Height;
|
|
_useLogicalSizeForRestore = true;
|
|
}
|
|
}
|
|
|
|
private IntPtr CallDefWindowProcWithoutVisibleStyle(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam)
|
|
{
|
|
var flag = VisualHelper.ModifyStyle(hWnd, InteropValues.WS_VISIBLE, 0);
|
|
var result = InteropMethods.DefWindowProc(hWnd, msg, wParam, lParam);
|
|
if (flag) VisualHelper.ModifyStyle(hWnd, 0, InteropValues.WS_VISIBLE);
|
|
return result;
|
|
}
|
|
|
|
private void CreateGlowWindowHandles()
|
|
{
|
|
for (var i = 0; i < _glowEdges.Length; i++) GetOrCreateGlowWindow(i).EnsureHandle();
|
|
}
|
|
|
|
#endregion
|
|
|
|
}
|
|
} |