Files
Shrlalgo.RvKits/WPFluent/Controls/TitleBar/TitleBar.cs
ShrlAlgo 4d35cadb56 更新
2025-07-11 09:20:23 +08:00

599 lines
20 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.Windows.Input;
using WPFluent.Extensions;
using WPFluent.Input;
using WPFluent.Interop;
namespace WPFluent.Controls;
/// <summary>
/// HCWhite navigation buttons for the window.
/// </summary>
[TemplatePart(Name = ElementMainGrid, Type = typeof(System.Windows.Controls.Grid))]
[TemplatePart(Name = ElementIcon, Type = typeof(System.Windows.Controls.Image))]
[TemplatePart(Name = ElementHelpButton, Type = typeof(TitleBarButton))]
[TemplatePart(Name = ElementMinimizeButton, Type = typeof(TitleBarButton))]
[TemplatePart(Name = ElementMaximizeButton, Type = typeof(TitleBarButton))]
[TemplatePart(Name = ElementRestoreButton, Type = typeof(TitleBarButton))]
[TemplatePart(Name = ElementCloseButton, Type = typeof(TitleBarButton))]
public class TitleBar : System.Windows.Controls.Control
{
private const string ElementCloseButton = "PART_CloseButton";
private const string ElementHelpButton = "PART_HelpButton";
private const string ElementIcon = "PART_Icon";
private const string ElementMainGrid = "PART_MainGrid";
private const string ElementMaximizeButton = "PART_MaximizeButton";
private const string ElementMinimizeButton = "PART_MinimizeButton";
private const string ElementRestoreButton = "PART_RestoreButton";
private static DpiScale? dpiScale;
/// <summary>
/// Identifies the <see cref="ButtonsBackground"/> dependency property.
/// </summary>
public static readonly DependencyProperty ButtonsBackgroundProperty = DependencyProperty.Register(
nameof(ButtonsBackground),
typeof(Brush),
typeof(TitleBar),
new FrameworkPropertyMetadata(SystemColors.ControlBrush, FrameworkPropertyMetadataOptions.Inherits));
/// <summary>
/// Identifies the <see cref="ButtonsForeground"/> dependency property.
/// </summary>
public static readonly DependencyProperty ButtonsForegroundProperty = DependencyProperty.Register(
nameof(ButtonsForeground),
typeof(Brush),
typeof(TitleBar),
new FrameworkPropertyMetadata(SystemColors.ControlTextBrush, FrameworkPropertyMetadataOptions.Inherits));
/// <summary>
/// Identifies the <see cref="CanMaximize"/> dependency property.
/// </summary>
public static readonly DependencyProperty CanMaximizeProperty = DependencyProperty.Register(
nameof(CanMaximize),
typeof(bool),
typeof(TitleBar),
new PropertyMetadata(true));
/// <summary>
/// Identifies the <see cref="CloseClicked"/> routed event.
/// </summary>
public static readonly RoutedEvent CloseClickedEvent = EventManager.RegisterRoutedEvent(
nameof(CloseClicked),
RoutingStrategy.Bubble,
typeof(TypedEventHandler<TitleBar, RoutedEventArgs>),
typeof(TitleBar));
/// <summary>
/// Identifies the <see cref="CloseWindowByDoubleClickOnIcon"/> dependency property.
/// </summary>
public static readonly DependencyProperty CloseWindowByDoubleClickOnIconProperty =
DependencyProperty.Register(
nameof(CloseWindowByDoubleClickOnIcon),
typeof(bool),
typeof(TitleBar),
new PropertyMetadata(false));
/// <summary>
/// Identifies the <see cref="HelpClicked"/> routed event.
/// </summary>
public static readonly RoutedEvent HelpClickedEvent = EventManager.RegisterRoutedEvent(
nameof(HelpClicked),
RoutingStrategy.Bubble,
typeof(TypedEventHandler<TitleBar, RoutedEventArgs>),
typeof(TitleBar));
/// <summary>
/// Identifies the <see cref="Icon"/> dependency property.
/// </summary>
public static readonly DependencyProperty IconProperty = DependencyProperty.Register(
nameof(Icon),
typeof(IconElement),
typeof(TitleBar),
new PropertyMetadata(null));
/// <summary>
/// Identifies the <see cref="IsMaximized"/> dependency property.
/// </summary>
public static readonly DependencyProperty IsMaximizedProperty = DependencyProperty.Register(
nameof(IsMaximized),
typeof(bool),
typeof(TitleBar),
new PropertyMetadata(false));
/// <summary>
/// Identifies the <see cref="MaximizeClicked"/> routed event.
/// </summary>
public static readonly RoutedEvent MaximizeClickedEvent = EventManager.RegisterRoutedEvent(
nameof(MaximizeClicked),
RoutingStrategy.Bubble,
typeof(TypedEventHandler<TitleBar, RoutedEventArgs>),
typeof(TitleBar));
/// <summary>
/// Identifies the <see cref="MinimizeClicked"/> routed event.
/// </summary>
public static readonly RoutedEvent MinimizeClickedEvent = EventManager.RegisterRoutedEvent(
nameof(MinimizeClicked),
RoutingStrategy.Bubble,
typeof(TypedEventHandler<TitleBar, RoutedEventArgs>),
typeof(TitleBar));
/// <summary>
/// Identifies the <see cref="ShowClose"/> dependency property.
/// </summary>
public static readonly DependencyProperty ShowCloseProperty = DependencyProperty.Register(
nameof(ShowClose),
typeof(bool),
typeof(TitleBar),
new PropertyMetadata(true));
/// <summary>
/// Identifies the <see cref="ShowHelp"/> dependency property.
/// </summary>
public static readonly DependencyProperty ShowHelpProperty = DependencyProperty.Register(
nameof(ShowHelp),
typeof(bool),
typeof(TitleBar),
new PropertyMetadata(false));
/// <summary>
/// Identifies the <see cref="ShowMaximize"/> dependency property.
/// </summary>
public static readonly DependencyProperty ShowMaximizeProperty = DependencyProperty.Register(
nameof(ShowMaximize),
typeof(bool),
typeof(TitleBar),
new PropertyMetadata(true));
/// <summary>
/// Identifies the <see cref="ShowMinimize"/> dependency property.
/// </summary>
public static readonly DependencyProperty ShowMinimizeProperty = DependencyProperty.Register(
nameof(ShowMinimize),
typeof(bool),
typeof(TitleBar),
new PropertyMetadata(true));
/// <summary>
/// Identifies the <see cref="TemplateButtonCommand"/> dependency property.
/// </summary>
public static readonly DependencyProperty TemplateButtonCommandProperty = DependencyProperty.Register(
nameof(TemplateButtonCommand),
typeof(IRelayCommand),
typeof(TitleBar),
new PropertyMetadata(null));
/// <summary>
/// Identifies the <see cref="Title"/> dependency property.
/// </summary>
public static readonly DependencyProperty TitleProperty = DependencyProperty.Register(
nameof(Title),
typeof(string),
typeof(TitleBar),
new PropertyMetadata(null));
/// <summary>
/// Property for <see cref="TrailingContent"/>.
/// </summary>
public static readonly DependencyProperty TrailingContentProperty = DependencyProperty.Register(
nameof(TrailingContent),
typeof(object),
typeof(TitleBar),
new PropertyMetadata(null));
private readonly TitleBarButton[] buttons = new TitleBarButton[4];
private System.Windows.Window currentWindow;
private System.Windows.Controls.ContentPresenter icon;
private DependencyObject _parentWindow;
/// <summary>
/// Initializes a new instance of the <see cref="TitleBar"/> class and sets the default <see
/// cref="FrameworkElement.Loaded"/> event.
/// </summary>
public TitleBar()
{
SetValue(TemplateButtonCommandProperty, new RelayCommand<TitleBarButtonType>(OnTemplateButtonClick));
dpiScale ??= VisualTreeHelper.GetDpi(this);
Loaded += OnLoaded;
Unloaded += OnUnloaded;
}
/// <summary>
/// Event triggered after clicking close button.
/// </summary>
public event TypedEventHandler<TitleBar, RoutedEventArgs> CloseClicked
{
add => AddHandler(CloseClickedEvent, value);
remove => RemoveHandler(CloseClickedEvent, value);
}
/// <summary>
/// Event triggered after clicking help button
/// </summary>
public event TypedEventHandler<TitleBar, RoutedEventArgs> HelpClicked
{
add => AddHandler(HelpClickedEvent, value);
remove => RemoveHandler(HelpClickedEvent, value);
}
/// <summary>
/// Event triggered after clicking maximize or restore button.
/// </summary>
public event TypedEventHandler<TitleBar, RoutedEventArgs> MaximizeClicked
{
add => AddHandler(MaximizeClickedEvent, value);
remove => RemoveHandler(MaximizeClickedEvent, value);
}
/// <summary>
/// Event triggered after clicking minimize button.
/// </summary>
public event TypedEventHandler<TitleBar, RoutedEventArgs> MinimizeClicked
{
add => AddHandler(MinimizeClickedEvent, value);
remove => RemoveHandler(MinimizeClickedEvent, value);
}
private void CloseWindow() { currentWindow.Close(); }
private T GetTemplateChild<T>(string name) where T : DependencyObject
{
var element = GetTemplateChild(name);
if(element is not T tElement)
{
throw new InvalidOperationException($"Template part '{name}' is not found or is not of type {typeof(T)}");
}
return tElement;
}
private IntPtr HwndSourceHook(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
var message = (User32.WM)msg;
if(message
is not (
User32.WM.NCHITTEST
or User32.WM.NCMOUSELEAVE
or User32.WM.NCLBUTTONDOWN
or User32.WM.NCLBUTTONUP
))
{
return IntPtr.Zero;
}
foreach(TitleBarButton button in buttons)
{
if(!button.ReactToHwndHook(message, lParam, out IntPtr returnIntPtr))
{
continue;
}
// 修复了有时未正确清除按钮悬停背景,导致多个按钮看起来好像悬停的问题。
foreach(TitleBarButton anotherButton in buttons)
{
if(anotherButton == button)
{
continue;
}
if(anotherButton.IsHovered && button.IsHovered)
{
anotherButton.RemoveHover();
}
}
handled = true;
return returnIntPtr;
}
var isMouseOverHeaderContent = false;
//if (message == User32.WM.NCHITTEST && (TrailingContent is UIElement))
//{
// var headerRightUiElement = TrailingContent as UIElement;
// if (headerLeftUIElement is not null)
// {
// isMouseOverHeaderContent =
// (headerRightUiElement?.IsMouseOverElement(lParam) ?? false);
// }
// else
// {
// isMouseOverHeaderContent = headerRightUiElement?.IsMouseOverElement(lParam) ?? false;
// }
//}
switch(message)
{
case User32.WM.NCHITTEST when CloseWindowByDoubleClickOnIcon && icon.IsMouseOverElement(lParam):
// Ideally, clicking on the icon should open the system menu, but when the system menu is opened manually, double-clicking on the icon does not close the window
handled = true;
return (IntPtr)User32.WM_NCHITTEST.HTSYSMENU;
case User32.WM.NCHITTEST when this.IsMouseOverElement(lParam) && !isMouseOverHeaderContent:
handled = true;
return (IntPtr)User32.WM_NCHITTEST.HTCAPTION;
default:
return IntPtr.Zero;
}
}
private void MaximizeWindow()
{
if(!CanMaximize)
{
return;
}
if(MaximizeActionOverride is not null)
{
MaximizeActionOverride(this, currentWindow);
return;
}
if(currentWindow.WindowState == WindowState.Normal)
{
SetCurrentValue(IsMaximizedProperty, true);
currentWindow.SetCurrentValue(Window.WindowStateProperty, WindowState.Maximized);
}
else
{
SetCurrentValue(IsMaximizedProperty, false);
currentWindow.SetCurrentValue(Window.WindowStateProperty, WindowState.Normal);
}
}
private void MinimizeWindow()
{
if(MinimizeActionOverride is not null)
{
MinimizeActionOverride(this, currentWindow);
return;
}
currentWindow.SetCurrentValue(Window.WindowStateProperty, WindowState.Minimized);
}
private void OnParentWindowStateChanged(object sender, EventArgs e)
{
if(IsMaximized != (currentWindow.WindowState == WindowState.Maximized))
{
SetCurrentValue(IsMaximizedProperty, currentWindow.WindowState == WindowState.Maximized);
}
}
/// <summary>
/// 设置窗口状态
/// </summary>
/// <param name="buttonType"></param>
private void OnTemplateButtonClick(TitleBarButtonType buttonType)
{
switch(buttonType)
{
case TitleBarButtonType.Maximize
or TitleBarButtonType.Restore:
RaiseEvent(new RoutedEventArgs(MaximizeClickedEvent, this));
MaximizeWindow();
break;
case TitleBarButtonType.Close:
RaiseEvent(new RoutedEventArgs(CloseClickedEvent, this));
CloseWindow();
break;
case TitleBarButtonType.Minimize:
RaiseEvent(new RoutedEventArgs(MinimizeClickedEvent, this));
MinimizeWindow();
break;
case TitleBarButtonType.Help:
RaiseEvent(new RoutedEventArgs(HelpClickedEvent, this));
break;
}
}
private void OnUnloaded(object sender, RoutedEventArgs e)
{
Loaded -= OnLoaded;
Unloaded -= OnUnloaded;
}
/// <summary>
/// Listening window hooks after rendering window content to SizeToContent support
/// </summary>
private void OnWindowContentRendered(object sender, EventArgs e)
{
if(sender is not Window window)
{
return;
}
window.ContentRendered -= OnWindowContentRendered;
var handle = new WindowInteropHelper(window).Handle;
var windowSource =
HwndSource.FromHwnd(handle) ?? throw new InvalidOperationException("Window source is null");
windowSource.AddHook(HwndSourceHook);
}
/// <summary>
/// 在鼠标右键时显示 'SystemMenu'。
/// </summary>
private void TitleBar_MouseRightButtonUp(object sender, MouseButtonEventArgs e)
{
var point = PointToScreen(e.GetPosition(this));
if(dpiScale is null)
{
throw new InvalidOperationException("dpiScale is not initialized.");
}
SystemCommands.ShowSystemMenu(
_parentWindow as Window,
new Point(point.X / dpiScale.Value.DpiScaleX, point.Y / dpiScale.Value.DpiScaleY));
}
protected virtual void OnLoaded(object sender, RoutedEventArgs e)
{
// 添加设计时检查
if (DesignerProperties.GetIsInDesignMode(this))
return;
currentWindow = System.Windows.Window.GetWindow(this) ?? throw new InvalidOperationException("Window is null");
currentWindow.StateChanged += OnParentWindowStateChanged;
currentWindow.ContentRendered += OnWindowContentRendered;
}
/// <summary>
/// 获取单击标题栏按钮时触发的命令。
/// </summary>
internal IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty);
/// <summary>
/// 每当应用程序代码或内部进程如重建布局传递调用OnApplyTemplate方法。
/// </summary>
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
_parentWindow = VisualTreeHelper.GetParent(this);
while(_parentWindow is not null and not Window)
{
_parentWindow = VisualTreeHelper.GetParent(_parentWindow);
}
MouseRightButtonUp += TitleBar_MouseRightButtonUp;
/*_mainGrid = GetTemplateChild<System.Windows.Controls.Grid>(ElementMainGrid);*/
icon = GetTemplateChild<System.Windows.Controls.ContentPresenter>(ElementIcon);
var helpButton = GetTemplateChild<TitleBarButton>(ElementHelpButton);
var minimizeButton = GetTemplateChild<TitleBarButton>(ElementMinimizeButton);
var maximizeButton = GetTemplateChild<TitleBarButton>(ElementMaximizeButton);
var closeButton = GetTemplateChild<TitleBarButton>(ElementCloseButton);
buttons[0] = maximizeButton;
buttons[1] = minimizeButton;
buttons[2] = closeButton;
buttons[3] = helpButton;
}
///// <inheritdoc/>
//public Appearance.ThemeType ApplicationTheme
//{
// get => (Appearance.ThemeType)GetValue(ApplicationThemeProperty);
// set => SetValue(ApplicationThemeProperty, value);
//}
/// <summary>
/// 获取或设置导航按钮悬停时的背景。
/// </summary>
[Bindable(true)]
[Category("Appearance")]
public Brush ButtonsBackground
{
get => (Brush)GetValue(ButtonsBackgroundProperty);
set => SetValue(ButtonsBackgroundProperty, value);
}
/// <summary>
/// Gets or sets the foreground of the navigation buttons.
/// </summary>
[Bindable(true)]
[Category("Appearance")]
public Brush ButtonsForeground
{
get => (Brush)GetValue(ButtonsForegroundProperty);
set => SetValue(ButtonsForegroundProperty, value);
}
/// <summary>
/// Gets or sets a value indicating whether the maximize functionality is enabled. If disabled the
/// MaximizeActionOverride action won't be called
/// </summary>
public bool CanMaximize { get => (bool)GetValue(CanMaximizeProperty); set => SetValue(CanMaximizeProperty, value); }
/// <summary>
/// Gets or sets a value indicating whether the window can be closed by double clicking on the icon
/// </summary>
public bool CloseWindowByDoubleClickOnIcon
{
get => (bool)GetValue(CloseWindowByDoubleClickOnIconProperty);
set => SetValue(CloseWindowByDoubleClickOnIconProperty, value);
}
/// <summary>
/// Gets or sets the titlebar icon.
/// </summary>
public IconElement Icon { get => (IconElement)GetValue(IconProperty); set => SetValue(IconProperty, value); }
/// <summary>
/// 获取一个值,该值指示当前窗口是否最大化。
/// </summary>
public bool IsMaximized
{
get => (bool)GetValue(IsMaximizedProperty);
internal set => SetValue(IsMaximizedProperty, value);
}
/// <summary>
/// 获取或设置单击“最大化”按钮时应执行的 <see cref=“Action”/>。
/// </summary>
public Action<TitleBar, System.Windows.Window> MaximizeActionOverride { get; set; }
/// <summary>
/// 获取或设置在单击 Minimize 按钮时应执行的 <see cref=“Action”/> 内容。
/// </summary>
public Action<TitleBar, System.Windows.Window> MinimizeActionOverride { get; set; }
/// <summary>
/// 获取或设置一个值,该值指示是否显示关闭按钮。
/// </summary>
public bool ShowClose { get => (bool)GetValue(ShowCloseProperty); set => SetValue(ShowCloseProperty, value); }
/// <summary>
/// 获取或设置一个值,该值指示是否显示 help 按钮
/// </summary>
public bool ShowHelp { get => (bool)GetValue(ShowHelpProperty); set => SetValue(ShowHelpProperty, value); }
/// <summary>
/// 获取或设置一个值,该值指示是否显示 maximize (最大化) 按钮。
/// </summary>
public bool ShowMaximize
{
get => (bool)GetValue(ShowMaximizeProperty);
set => SetValue(ShowMaximizeProperty, value);
}
/// <summary>
/// 获取或设置一个值,该值指示是否显示 minimize (最小化) 按钮。
/// </summary>
public bool ShowMinimize
{
get => (bool)GetValue(ShowMinimizeProperty);
set => SetValue(ShowMinimizeProperty, value);
}
/// <summary>
/// 获取或设置显示在左侧的标题。
/// </summary>
public string Title { get => (string)GetValue(TitleProperty); set => SetValue(TitleProperty, value); }
/// <summary>
/// 获取或设置在右侧显示的内容 <see cref="TitleBar"/>.
/// </summary>
public object TrailingContent
{
get => GetValue(TrailingContentProperty);
set => SetValue(TrailingContentProperty, value);
}
}