Files
ShrlAlgoToolkit/Melskin/Controls/MelWindow.xaml.cs
2026-02-17 22:17:13 +08:00

357 lines
12 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Runtime.InteropServices;
using System.Windows.Automation.Peers;
using System.Windows.Automation.Provider;
using System.Windows.Input;
using Melskin.Assists;
namespace Melskin.Controls;
/// <summary>
/// MelWindow 类继承自 WPF 的 Window 类,用于创建具有特定样式和功能的窗口。
/// 该类通过设置默认样式资源引用来自定义窗口外观,并提供属性来控制窗口的行为和内容布局。
/// </summary>
public class MelWindow : Window
{
private const string VbMinimizeButtonName = "minimizeButton";
private const string VbMaximizeRestoreButtonName = "maximizeRestoreButton";
/// <summary>
/// 用于在XAML模板中标识关闭按钮的名称。
/// 此常量字符串值为 "closeButton"用于在窗口样式应用时通过GetTemplateChild方法查找对应的关闭按钮控件。
/// </summary>
private const string VbCloseButtonName = "closeButton";
/// <summary>
///
/// </summary>
public MelWindow()
{
SetResourceReference(StyleProperty, typeof(MelWindow)); //设置默认的样式资源引用
}
#region
/// <inheritdoc />
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);
/// <summary>
/// 跟踪鼠标事件的结构体。
/// </summary>
[StructLayout(LayoutKind.Sequential)]
public struct TRACKMOUSEEVENT
{
/// <summary>
/// 结构体大小
/// </summary>
public int cbSize;
/// <summary>
/// 控制跟踪行为的标志
/// </summary>
public uint dwFlags;
/// <summary>
/// 要为其跟踪鼠标事件的窗口句柄
/// </summary>
public IntPtr hwndTrack;
/// <summary>
/// 可选的悬停时间(毫秒)
/// </summary>
public uint dwHoverTime;
/// <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>
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;
private const int HTMAXBUTTON = 9;
private const uint TME_LEAVE = 0x00000002;
private const uint TME_NONCLIENT = 0x00000010;
#endregion
private bool isMouseOverMaximizeButton = false;
private bool isMaximizeButtonPressed = false;
private IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
switch (msg)
{
case WM_NCHITTEST:
if (IsMouseOverMaximizeButton(lParam))
{
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;
UpdateMaximizeButtonVisualState();
handled = true;
}
break;
case WM_NCLBUTTONUP:
if (isMaximizeButtonPressed)
{
isMaximizeButtonPressed = false;
UpdateMaximizeButtonVisualState();
if (isMouseOverMaximizeButton)
{
maximizeRestoreButton?.RaiseEvent(new RoutedEventArgs(Button.ClickEvent));
}
handled = true;
}
break;
}
return IntPtr.Zero;
}
private bool IsMouseOverMaximizeButton(IntPtr lParam)
{
var x = (short)(lParam.ToInt32() & 0xFFFF);
var y = (short)(lParam.ToInt32() >> 16);
var mousePos = new Point(x, y);
var windowPos = this.PointFromScreen(mousePos);
if (maximizeRestoreButton is Visual maximizeButtonVisual)
{
var bounds = VisualTreeHelper.GetDescendantBounds(maximizeButtonVisual);
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);
maximizeRestoreButton?.Background = stateName switch
{
"Pressed" =>
//VisualStateManager.GoToState(maximizeRestoreButton, "Pressed", true);
new SolidColorBrush((Color)ColorConverter.ConvertFromString("#33000000")),
"MouseOver" =>
//VisualStateManager.GoToState(maximizeRestoreButton, "MouseOver", true);
new SolidColorBrush((Color)ColorConverter.ConvertFromString("#1A000000")),
"Normal" => Brushes.Transparent,
//VisualStateManager.GoToState(maximizeRestoreButton, "Normal", true);
_ => maximizeRestoreButton?.Background
};
}
#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
static MelWindow()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(MelWindow), new FrameworkPropertyMetadata(typeof(MelWindow)));
}
/// <summary>
/// 如果窗口不活动,请让窗口的内容淡出。
/// 默认值为真(已启用)。
/// </summary>
public static readonly DependencyProperty FadeContentIfInactiveProperty = DependencyProperty.Register(
nameof(FadeContentIfInactive), typeof(bool), typeof(MelWindow), new FrameworkPropertyMetadata(true));
/// <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 =
DependencyProperty.Register(nameof(TitleBarLeftContent), typeof(object), typeof(MelWindow));
/// <summary>
/// 标题栏右侧内容模板
/// </summary>
public object TitleBarRightContent
{
get => (object)GetValue(TitleBarRightContentProperty);
set => SetValue(TitleBarRightContentProperty, value);
}
/// <summary>
/// 用于定义窗口右侧内容区域的数据模板。
/// 此依赖属性允许开发者自定义右侧内容的布局和显示方式通过设置不同的DataTemplate来改变该区域的内容呈现形式。
/// </summary>
public static readonly DependencyProperty TitleBarRightContentProperty =
DependencyProperty.Register(nameof(TitleBarRightContent), typeof(object), typeof(MelWindow));
private Button? minimizeButton;
private Button? maximizeRestoreButton;
private Button? closeButton;
/// <inheritdoc />
public override void OnApplyTemplate()
{
if (minimizeButton != null)
{
minimizeButton.Click -= MinimizeButtonClickHandler;
}
minimizeButton = GetTemplateChild(VbMinimizeButtonName) as Button;
if (minimizeButton != null)
{
minimizeButton.Click += MinimizeButtonClickHandler;
}
if (maximizeRestoreButton != null)
{
maximizeRestoreButton.Click -= MaximizeRestoreButtonClickHandler;
}
maximizeRestoreButton = GetTemplateChild(VbMaximizeRestoreButtonName) as Button;
if (maximizeRestoreButton != null)
{
maximizeRestoreButton.Click += MaximizeRestoreButtonClickHandler;
}
if (closeButton != null)
{
closeButton.Click -= CloseButtonClickHandler;
}
closeButton = GetTemplateChild(VbCloseButtonName) as Button;
if (closeButton != null)
{
closeButton.Click += CloseButtonClickHandler;
}
base.OnApplyTemplate();
}
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;
}
}