Files
ShrlAlgoToolkit/Melskin/Controls/MelWindow.xaml.cs

361 lines
13 KiB
C#
Raw Normal View History

2026-03-01 10:42:42 +08:00
using System.Runtime.InteropServices;
2025-10-10 11:19:58 +08:00
using System.Windows.Input;
2026-01-02 17:30:30 +08:00
namespace Melskin.Controls;
2025-10-10 11:19:58 +08:00
/// <summary>
2026-01-02 17:30:41 +08:00
/// MelWindow 类继承自 WPF 的 Window 类,用于创建具有特定样式和功能的窗口。
2025-10-10 11:19:58 +08:00
/// 该类通过设置默认样式资源引用来自定义窗口外观,并提供属性来控制窗口的行为和内容布局。
/// </summary>
2026-01-02 17:30:41 +08:00
public class MelWindow : Window
2025-10-10 11:19:58 +08:00
{
private const string VbMinimizeButtonName = "minimizeButton";
private const string VbMaximizeRestoreButtonName = "maximizeRestoreButton";
/// <summary>
/// 用于在XAML模板中标识关闭按钮的名称。
/// 此常量字符串值为 "closeButton"用于在窗口样式应用时通过GetTemplateChild方法查找对应的关闭按钮控件。
/// </summary>
private const string VbCloseButtonName = "closeButton";
/// <summary>
///
/// </summary>
2026-01-02 17:30:41 +08:00
public MelWindow()
2025-10-10 11:19:58 +08:00
{
2026-01-02 17:30:41 +08:00
SetResourceReference(StyleProperty, typeof(MelWindow)); //设置默认的样式资源引用
2025-10-10 11:19:58 +08:00
}
2025-12-23 21:35:54 +08:00
2025-10-10 11:19:58 +08:00
#region
2025-12-23 21:35:54 +08:00
/// <inheritdoc />
2025-10-10 11:19:58 +08:00
protected override void OnSourceInitialized(EventArgs e)
{
base.OnSourceInitialized(e);
var helper = new WindowInteropHelper(this);
var hwndSource = HwndSource.FromHwnd(helper.Handle);
hwndSource?.AddHook(HwndSourceHook);
}
[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
private static extern bool TrackMouseEvent(ref TRACKMOUSEEVENT lpEventTrack);
2025-12-23 21:35:54 +08:00
/// <summary>
/// 跟踪鼠标事件的结构体。
/// </summary>
2025-10-10 11:19:58 +08:00
[StructLayout(LayoutKind.Sequential)]
public struct TRACKMOUSEEVENT
{
2025-12-23 21:35:54 +08:00
/// <summary>
/// 结构体大小
/// </summary>
2025-10-10 11:19:58 +08:00
public int cbSize;
2025-12-23 21:35:54 +08:00
/// <summary>
/// 控制跟踪行为的标志
/// </summary>
2025-10-10 11:19:58 +08:00
public uint dwFlags;
2025-12-23 21:35:54 +08:00
/// <summary>
/// 要为其跟踪鼠标事件的窗口句柄
/// </summary>
2025-10-10 11:19:58 +08:00
public IntPtr hwndTrack;
2025-12-23 21:35:54 +08:00
/// <summary>
/// 可选的悬停时间(毫秒)
/// </summary>
2025-10-10 11:19:58 +08:00
public uint dwHoverTime;
2025-12-23 21:35:54 +08:00
/// <summary>
/// 初始化 TRACKMOUSEEVENT 结构体的新实例。
/// </summary>
/// <param name="dwFlags">A combination of flags that specify the services requested. The value should correspond to the TME_*
/// constants defined by the Windows API.</param>
/// <param name="hwndTrack">窗口句柄</param>
2025-10-10 11:19:58 +08:00
public TRACKMOUSEEVENT(uint dwFlags, IntPtr hwndTrack)
{
this.cbSize = Marshal.SizeOf(typeof(TRACKMOUSEEVENT));
this.dwFlags = dwFlags;
this.hwndTrack = hwndTrack;
this.dwHoverTime = 0;
}
}
private const int WM_NCHITTEST = 0x0084;
private const int WM_NCMOUSEMOVE = 0x00A0;
private const int WM_NCMOUSELEAVE = 0x02A2;
private const int WM_NCLBUTTONDOWN = 0x00A1;
private const int WM_NCLBUTTONUP = 0x00A2;
2026-02-17 22:17:23 +08:00
private const int WM_SYSCOMMAND = 0x0112;
2025-10-10 11:19:58 +08:00
private const int HTMAXBUTTON = 9;
2026-02-17 22:17:23 +08:00
private const int SC_MAXIMIZE = 0xF030;
private const int SC_RESTORE = 0xF120;
2025-10-10 11:19:58 +08:00
private const uint TME_LEAVE = 0x00000002;
private const uint TME_NONCLIENT = 0x00000010;
#endregion
private bool isMouseOverMaximizeButton = false;
private bool isMaximizeButtonPressed = false;
2026-02-17 22:17:23 +08:00
/// <summary>
/// 记录点击前的窗口状态,用于判断系统是否已通过 Snap Layout 处理了窗口状态变更。
/// </summary>
private WindowState windowStateBeforeClick;
2025-10-10 11:19:58 +08:00
private IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case WM_NCHITTEST:
if (IsMouseOverMaximizeButton(lParam))
{
2026-02-17 22:17:23 +08:00
// 返回 HTMAXBUTTON 让 Windows 11 Shell 知道鼠标在最大化按钮上,
// 从而触发 Snap Layout 弹出菜单。
2025-10-10 11:19:58 +08:00
handled = true;
return new IntPtr(HTMAXBUTTON);
}
else if (isMouseOverMaximizeButton || isMaximizeButtonPressed)
{
// 如果鼠标移出按钮区域,重置状态
isMouseOverMaximizeButton = false;
isMaximizeButtonPressed = false;
UpdateMaximizeButtonVisualState();
}
break;
case WM_NCMOUSEMOVE:
bool isCurrentlyOver = IsMouseOverMaximizeButton(lParam);
if (isCurrentlyOver != isMouseOverMaximizeButton)
{
isMouseOverMaximizeButton = isCurrentlyOver;
UpdateMaximizeButtonVisualState();
if (isMouseOverMaximizeButton)
{
var tme = new TRACKMOUSEEVENT(TME_LEAVE | TME_NONCLIENT, hwnd);
TrackMouseEvent(ref tme);
}
}
break;
case WM_NCMOUSELEAVE:
isMouseOverMaximizeButton = false;
isMaximizeButtonPressed = false;
UpdateMaximizeButtonVisualState();
break;
case WM_NCLBUTTONDOWN:
if (isMouseOverMaximizeButton)
{
isMaximizeButtonPressed = true;
2026-02-17 22:17:23 +08:00
windowStateBeforeClick = this.WindowState;
2025-10-10 11:19:58 +08:00
UpdateMaximizeButtonVisualState();
handled = true;
}
break;
case WM_NCLBUTTONUP:
if (isMaximizeButtonPressed)
{
isMaximizeButtonPressed = false;
2026-02-17 22:17:23 +08:00
isMouseOverMaximizeButton = false;
2025-10-10 11:19:58 +08:00
UpdateMaximizeButtonVisualState();
2026-02-17 22:17:23 +08:00
// 检查系统是否已经通过 Snap Layout 处理了窗口状态变更
// 如果窗口状态未被系统改变,说明用户只是普通点击最大化按钮
if (this.WindowState == windowStateBeforeClick)
2025-10-10 11:19:58 +08:00
{
2026-02-17 22:17:23 +08:00
ToggleWindowState();
2025-10-10 11:19:58 +08:00
}
handled = true;
}
break;
2026-02-17 22:17:23 +08:00
case WM_SYSCOMMAND:
var command = wParam.ToInt32() & 0xFFF0;
if (command == SC_MAXIMIZE || command == SC_RESTORE)
{
// 让系统正常处理 Snap Layout 发出的最大化/还原命令
// 不要阻止这些消息,以确保 Snap Layout 选择的布局能正确应用
}
break;
2025-10-10 11:19:58 +08:00
}
return IntPtr.Zero;
}
private bool IsMouseOverMaximizeButton(IntPtr lParam)
{
2026-02-17 22:17:23 +08:00
// 使用 ToInt64() 确保在 64 位系统和多显示器环境下坐标解析正确
var val = lParam.ToInt64();
var x = (short)(val & 0xFFFF);
var y = (short)((val >> 16) & 0xFFFF);
2025-10-10 11:19:58 +08:00
var mousePos = new Point(x, y);
var windowPos = this.PointFromScreen(mousePos);
2025-12-23 21:35:54 +08:00
if (maximizeRestoreButton is Visual maximizeButtonVisual)
2025-10-10 11:19:58 +08:00
{
var bounds = VisualTreeHelper.GetDescendantBounds(maximizeButtonVisual);
2026-03-01 10:42:42 +08:00
//Debug.WriteLine(bounds.ToString());
2025-10-10 11:19:58 +08:00
var buttonTransform = maximizeButtonVisual.TransformToAncestor(this);
var buttonRect = buttonTransform.TransformBounds(bounds);
return buttonRect.Contains(windowPos);
}
return false;
}
private void UpdateMaximizeButtonVisualState()
{
string stateName;
if (isMaximizeButtonPressed && isMouseOverMaximizeButton)
{
stateName = "Pressed";
}
else if (isMouseOverMaximizeButton)
{
stateName = "MouseOver";
}
else
{
stateName = "Normal";
}
//VisualStateManager.GoToState(maximizeRestoreButton, stateName, true);
2026-01-02 16:37:37 +08:00
maximizeRestoreButton?.Background = stateName switch
2025-10-10 11:19:58 +08:00
{
2026-01-02 16:37:37 +08:00
"Pressed" =>
2025-10-10 11:19:58 +08:00
//VisualStateManager.GoToState(maximizeRestoreButton, "Pressed", true);
2026-01-02 16:37:37 +08:00
new SolidColorBrush((Color)ColorConverter.ConvertFromString("#33000000")),
"MouseOver" =>
2025-10-10 11:19:58 +08:00
//VisualStateManager.GoToState(maximizeRestoreButton, "MouseOver", true);
2026-01-02 16:37:37 +08:00
new SolidColorBrush((Color)ColorConverter.ConvertFromString("#1A000000")),
"Normal" => Brushes.Transparent,
2026-03-01 10:42:42 +08:00
//VisualStateManager.GoToState(maximizeRestoreButton, "Normal", true);
2026-01-02 16:37:37 +08:00
_ => maximizeRestoreButton?.Background
};
2025-10-10 11:19:58 +08:00
}
#region Window Control Methods
private void TitleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 2)
{
ToggleWindowState();
}
else
{
this.DragMove();
}
}
private void maximizeRestoreButton_Click(object sender, RoutedEventArgs e)
{
ToggleWindowState();
}
private void ToggleWindowState()
{
this.WindowState = (this.WindowState == WindowState.Normal) ? WindowState.Maximized : WindowState.Normal;
}
private void CloseButton_Click(object sender, RoutedEventArgs e)
{
this.Close();
}
#endregion
2026-01-02 17:30:41 +08:00
static MelWindow()
2025-10-10 11:19:58 +08:00
{
2026-01-02 17:30:41 +08:00
DefaultStyleKeyProperty.OverrideMetadata(typeof(MelWindow), new FrameworkPropertyMetadata(typeof(MelWindow)));
2025-10-10 11:19:58 +08:00
}
/// <summary>
/// 如果窗口不活动,请让窗口的内容淡出。
/// 默认值为真(已启用)。
/// </summary>
public static readonly DependencyProperty FadeContentIfInactiveProperty = DependencyProperty.Register(
2026-01-02 17:30:41 +08:00
nameof(FadeContentIfInactive), typeof(bool), typeof(MelWindow), new FrameworkPropertyMetadata(true));
2025-10-10 11:19:58 +08:00
/// <summary>
/// 如果窗口不活动,请让窗口的内容淡出。
/// 默认值为真(已启用)。
/// </summary>
public bool FadeContentIfInactive
{
get => (bool)GetValue(FadeContentIfInactiveProperty);
set => SetValue(FadeContentIfInactiveProperty, value);
}
/// <summary>
/// 标题栏左侧内容模板
/// </summary>
public object TitleBarLeftContent
{
get => (object)GetValue(TitleBarLeftContentProperty);
set => SetValue(TitleBarLeftContentProperty, value);
}
/// <summary>
/// 用于定义窗口左侧内容区域的数据模板。
/// 此依赖属性允许开发者自定义左侧内容的布局和显示方式通过设置不同的DataTemplate来改变该区域的内容呈现形式。
/// </summary>
public static readonly DependencyProperty TitleBarLeftContentProperty =
2026-01-02 17:30:41 +08:00
DependencyProperty.Register(nameof(TitleBarLeftContent), typeof(object), typeof(MelWindow));
2025-10-10 11:19:58 +08:00
/// <summary>
/// 标题栏右侧内容模板
/// </summary>
public object TitleBarRightContent
{
get => (object)GetValue(TitleBarRightContentProperty);
set => SetValue(TitleBarRightContentProperty, value);
}
/// <summary>
/// 用于定义窗口右侧内容区域的数据模板。
/// 此依赖属性允许开发者自定义右侧内容的布局和显示方式通过设置不同的DataTemplate来改变该区域的内容呈现形式。
/// </summary>
public static readonly DependencyProperty TitleBarRightContentProperty =
2026-01-02 17:30:41 +08:00
DependencyProperty.Register(nameof(TitleBarRightContent), typeof(object), typeof(MelWindow));
2025-10-10 11:19:58 +08:00
private Button? minimizeButton;
private Button? maximizeRestoreButton;
private Button? closeButton;
/// <inheritdoc />
public override void OnApplyTemplate()
{
2026-03-01 10:42:42 +08:00
base.OnApplyTemplate();
2026-02-17 22:17:23 +08:00
minimizeButton?.Click -= MinimizeButtonClickHandler;
2025-10-10 11:19:58 +08:00
minimizeButton = GetTemplateChild(VbMinimizeButtonName) as Button;
2026-02-17 22:17:23 +08:00
minimizeButton?.Click += MinimizeButtonClickHandler;
2025-10-10 11:19:58 +08:00
2026-02-17 22:17:23 +08:00
maximizeRestoreButton?.Click -= MaximizeRestoreButtonClickHandler;
2025-10-10 11:19:58 +08:00
maximizeRestoreButton = GetTemplateChild(VbMaximizeRestoreButtonName) as Button;
2026-02-17 22:17:23 +08:00
maximizeRestoreButton?.Click += MaximizeRestoreButtonClickHandler;
2025-10-10 11:19:58 +08:00
2026-02-17 22:17:23 +08:00
closeButton?.Click -= CloseButtonClickHandler;
2025-10-10 11:19:58 +08:00
closeButton = GetTemplateChild(VbCloseButtonName) as Button;
2026-02-17 22:17:23 +08:00
closeButton?.Click += CloseButtonClickHandler;
2025-10-10 11:19:58 +08:00
}
private void CloseButtonClickHandler(object sender, RoutedEventArgs args)
{
Close();
}
private void MaximizeRestoreButtonClickHandler(object sender, RoutedEventArgs args)
{
WindowState = (WindowState == WindowState.Normal) ? WindowState.Maximized : WindowState.Normal;
}
private void MinimizeButtonClickHandler(object sender, RoutedEventArgs args)
{
WindowState = WindowState.Minimized;
}
}