using System.Windows.Input; using WPFluent.Extensions; using WPFluent.Input; using WPFluent.Interop; namespace WPFluent.Controls; /// /// HCWhite navigation buttons for the window. /// [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; /// /// Identifies the dependency property. /// public static readonly DependencyProperty ButtonsBackgroundProperty = DependencyProperty.Register( nameof(ButtonsBackground), typeof(Brush), typeof(TitleBar), new FrameworkPropertyMetadata(SystemColors.ControlBrush, FrameworkPropertyMetadataOptions.Inherits)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty ButtonsForegroundProperty = DependencyProperty.Register( nameof(ButtonsForeground), typeof(Brush), typeof(TitleBar), new FrameworkPropertyMetadata(SystemColors.ControlTextBrush, FrameworkPropertyMetadataOptions.Inherits)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty CanMaximizeProperty = DependencyProperty.Register( nameof(CanMaximize), typeof(bool), typeof(TitleBar), new PropertyMetadata(true)); /// /// Identifies the routed event. /// public static readonly RoutedEvent CloseClickedEvent = EventManager.RegisterRoutedEvent( nameof(CloseClicked), RoutingStrategy.Bubble, typeof(TypedEventHandler), typeof(TitleBar)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty CloseWindowByDoubleClickOnIconProperty = DependencyProperty.Register( nameof(CloseWindowByDoubleClickOnIcon), typeof(bool), typeof(TitleBar), new PropertyMetadata(false)); /// /// Identifies the routed event. /// public static readonly RoutedEvent HelpClickedEvent = EventManager.RegisterRoutedEvent( nameof(HelpClicked), RoutingStrategy.Bubble, typeof(TypedEventHandler), typeof(TitleBar)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty IconProperty = DependencyProperty.Register( nameof(Icon), typeof(IconElement), typeof(TitleBar), new PropertyMetadata(null)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty IsMaximizedProperty = DependencyProperty.Register( nameof(IsMaximized), typeof(bool), typeof(TitleBar), new PropertyMetadata(false)); /// /// Identifies the routed event. /// public static readonly RoutedEvent MaximizeClickedEvent = EventManager.RegisterRoutedEvent( nameof(MaximizeClicked), RoutingStrategy.Bubble, typeof(TypedEventHandler), typeof(TitleBar)); /// /// Identifies the routed event. /// public static readonly RoutedEvent MinimizeClickedEvent = EventManager.RegisterRoutedEvent( nameof(MinimizeClicked), RoutingStrategy.Bubble, typeof(TypedEventHandler), typeof(TitleBar)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty ShowCloseProperty = DependencyProperty.Register( nameof(ShowClose), typeof(bool), typeof(TitleBar), new PropertyMetadata(true)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty ShowHelpProperty = DependencyProperty.Register( nameof(ShowHelp), typeof(bool), typeof(TitleBar), new PropertyMetadata(false)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty ShowMaximizeProperty = DependencyProperty.Register( nameof(ShowMaximize), typeof(bool), typeof(TitleBar), new PropertyMetadata(true)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty ShowMinimizeProperty = DependencyProperty.Register( nameof(ShowMinimize), typeof(bool), typeof(TitleBar), new PropertyMetadata(true)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty TemplateButtonCommandProperty = DependencyProperty.Register( nameof(TemplateButtonCommand), typeof(IRelayCommand), typeof(TitleBar), new PropertyMetadata(null)); /// /// Identifies the dependency property. /// public static readonly DependencyProperty TitleProperty = DependencyProperty.Register( nameof(Title), typeof(string), typeof(TitleBar), new PropertyMetadata(null)); /// /// Property for . /// 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; /// /// Initializes a new instance of the class and sets the default event. /// public TitleBar() { SetValue(TemplateButtonCommandProperty, new RelayCommand(OnTemplateButtonClick)); dpiScale ??= VisualTreeHelper.GetDpi(this); Loaded += OnLoaded; Unloaded += OnUnloaded; } /// /// Event triggered after clicking close button. /// public event TypedEventHandler CloseClicked { add => AddHandler(CloseClickedEvent, value); remove => RemoveHandler(CloseClickedEvent, value); } /// /// Event triggered after clicking help button /// public event TypedEventHandler HelpClicked { add => AddHandler(HelpClickedEvent, value); remove => RemoveHandler(HelpClickedEvent, value); } /// /// Event triggered after clicking maximize or restore button. /// public event TypedEventHandler MaximizeClicked { add => AddHandler(MaximizeClickedEvent, value); remove => RemoveHandler(MaximizeClickedEvent, value); } /// /// Event triggered after clicking minimize button. /// public event TypedEventHandler MinimizeClicked { add => AddHandler(MinimizeClickedEvent, value); remove => RemoveHandler(MinimizeClickedEvent, value); } private void CloseWindow() { currentWindow.Close(); } private T GetTemplateChild(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); } } /// /// 设置窗口状态 /// /// 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; } /// /// Listening window hooks after rendering window content to SizeToContent support /// 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); } /// /// 在鼠标右键时显示 'SystemMenu'。 /// 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; } /// /// 获取单击标题栏按钮时触发的命令。 /// internal IRelayCommand TemplateButtonCommand => (IRelayCommand)GetValue(TemplateButtonCommandProperty); /// /// 每当应用程序代码或内部进程(如重建布局传递)调用OnApplyTemplate方法。 /// 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(ElementMainGrid);*/ icon = GetTemplateChild(ElementIcon); var helpButton = GetTemplateChild(ElementHelpButton); var minimizeButton = GetTemplateChild(ElementMinimizeButton); var maximizeButton = GetTemplateChild(ElementMaximizeButton); var closeButton = GetTemplateChild(ElementCloseButton); buttons[0] = maximizeButton; buttons[1] = minimizeButton; buttons[2] = closeButton; buttons[3] = helpButton; } ///// //public Appearance.ThemeType ApplicationTheme //{ // get => (Appearance.ThemeType)GetValue(ApplicationThemeProperty); // set => SetValue(ApplicationThemeProperty, value); //} /// /// 获取或设置导航按钮悬停时的背景。 /// [Bindable(true)] [Category("Appearance")] public Brush ButtonsBackground { get => (Brush)GetValue(ButtonsBackgroundProperty); set => SetValue(ButtonsBackgroundProperty, value); } /// /// Gets or sets the foreground of the navigation buttons. /// [Bindable(true)] [Category("Appearance")] public Brush ButtonsForeground { get => (Brush)GetValue(ButtonsForegroundProperty); set => SetValue(ButtonsForegroundProperty, value); } /// /// Gets or sets a value indicating whether the maximize functionality is enabled. If disabled the /// MaximizeActionOverride action won't be called /// public bool CanMaximize { get => (bool)GetValue(CanMaximizeProperty); set => SetValue(CanMaximizeProperty, value); } /// /// Gets or sets a value indicating whether the window can be closed by double clicking on the icon /// public bool CloseWindowByDoubleClickOnIcon { get => (bool)GetValue(CloseWindowByDoubleClickOnIconProperty); set => SetValue(CloseWindowByDoubleClickOnIconProperty, value); } /// /// Gets or sets the titlebar icon. /// public IconElement Icon { get => (IconElement)GetValue(IconProperty); set => SetValue(IconProperty, value); } /// /// 获取一个值,该值指示当前窗口是否最大化。 /// public bool IsMaximized { get => (bool)GetValue(IsMaximizedProperty); internal set => SetValue(IsMaximizedProperty, value); } /// /// 获取或设置单击“最大化”按钮时应执行的 。 /// public Action MaximizeActionOverride { get; set; } /// /// 获取或设置在单击 Minimize 按钮时应执行的 内容。 /// public Action MinimizeActionOverride { get; set; } /// /// 获取或设置一个值,该值指示是否显示关闭按钮。 /// public bool ShowClose { get => (bool)GetValue(ShowCloseProperty); set => SetValue(ShowCloseProperty, value); } /// /// 获取或设置一个值,该值指示是否显示 help 按钮 /// public bool ShowHelp { get => (bool)GetValue(ShowHelpProperty); set => SetValue(ShowHelpProperty, value); } /// /// 获取或设置一个值,该值指示是否显示 maximize (最大化) 按钮。 /// public bool ShowMaximize { get => (bool)GetValue(ShowMaximizeProperty); set => SetValue(ShowMaximizeProperty, value); } /// /// 获取或设置一个值,该值指示是否显示 minimize (最小化) 按钮。 /// public bool ShowMinimize { get => (bool)GetValue(ShowMinimizeProperty); set => SetValue(ShowMinimizeProperty, value); } /// /// 获取或设置显示在左侧的标题。 /// public string Title { get => (string)GetValue(TitleProperty); set => SetValue(TitleProperty, value); } /// /// 获取或设置在右侧显示的内容 . /// public object TrailingContent { get => GetValue(TrailingContentProperty); set => SetValue(TrailingContentProperty, value); } }