From 5768be69f46600c0a886e54972d73d228f67e12e Mon Sep 17 00:00:00 2001
From: zengwenjie <1663900244@qq.com>
Date: Tue, 30 Sep 2025 11:08:16 +0800
Subject: [PATCH] =?UTF-8?q?=E5=87=86=E5=A4=87=E7=BC=96=E5=86=99=E3=80=8CWi?=
=?UTF-8?q?ndowBorder=E3=80=8D=E6=8E=A7=E4=BB=B6?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
DeedyDesigner/Deedy.Wpf/Themes/Generic.xaml | 13 +
DeedyDesigner/Deedy.Wpf/WindowBorder.cs | 258 ++++++++++++++++++++
DeedyDesigner/Deedy.Wpf/WindowHeader.cs | 3 +
3 files changed, 274 insertions(+)
create mode 100644 DeedyDesigner/Deedy.Wpf/WindowBorder.cs
diff --git a/DeedyDesigner/Deedy.Wpf/Themes/Generic.xaml b/DeedyDesigner/Deedy.Wpf/Themes/Generic.xaml
index e692577..3c9fe18 100644
--- a/DeedyDesigner/Deedy.Wpf/Themes/Generic.xaml
+++ b/DeedyDesigner/Deedy.Wpf/Themes/Generic.xaml
@@ -58,4 +58,17 @@
+
+
diff --git a/DeedyDesigner/Deedy.Wpf/WindowBorder.cs b/DeedyDesigner/Deedy.Wpf/WindowBorder.cs
new file mode 100644
index 0000000..95511d5
--- /dev/null
+++ b/DeedyDesigner/Deedy.Wpf/WindowBorder.cs
@@ -0,0 +1,258 @@
+using System.ComponentModel;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Markup;
+using System.Windows.Media;
+using Shell = System.Windows.Shell;
+
+namespace Deedy
+{
+ ///
+ /// 为自定义窗口提供标题栏与内容容器;可以作为一个窗口的直接子节点
+ ///
+ [ContentProperty("Content")]
+ [TemplatePart(Name = "MainMenu", Type = typeof(Menu))]
+ [TemplatePart(Name = "Minimize", Type = typeof(Button))]
+ [TemplatePart(Name = "Maximize", Type = typeof(Button))]
+ [TemplatePart(Name = "CloseWin", Type = typeof(Button))]
+ [TemplatePart(Name = "TitleBar", Type = typeof(Control))]
+ [TemplatePart(Name = "Controller", Type = typeof(Panel))]
+ [TemplatePart(Name = "Container", Type = typeof(Decorator))]
+ public class WindowBorder : HeaderedContentControl
+ {
+ static WindowBorder()
+ {
+ DefaultStyleKeyProperty.OverrideMetadata(typeof(WindowBorder), new FrameworkPropertyMetadata(typeof(WindowBorder)));
+ BackgroundProperty.OverrideMetadata(typeof(WindowBorder), new FrameworkPropertyMetadata(Brushes.Gray));
+ }
+ ///
+ /// 悬停画刷
+ ///
+ public Brush HoverBrush
+ {
+ get { return (Brush)GetValue(HoverBrushProperty); }
+ set { SetValue(HoverBrushProperty, value); }
+ }
+ public static readonly DependencyProperty HoverBrushProperty =
+ DependencyProperty.Register("HoverBrush", typeof(Brush), typeof(WindowBorder), new PropertyMetadata(Brushes.Silver));
+ ///
+ /// 标题画刷
+ ///
+ public Brush Headground
+ {
+ get { return (Brush)GetValue(HeadgroundProperty); }
+ set { SetValue(HeadgroundProperty, value); }
+ }
+ public static readonly DependencyProperty HeadgroundProperty =
+ DependencyProperty.Register("Headground", typeof(Brush), typeof(WindowBorder), new PropertyMetadata(Brushes.DimGray));
+ ///
+ /// 标题高度
+ ///
+ public double HeaderHeight
+ {
+ get { return (double)GetValue(HeaderHeightProperty); }
+ set { SetValue(HeaderHeightProperty, value); }
+ }
+ public static readonly DependencyProperty HeaderHeightProperty =
+ DependencyProperty.Register("HeaderHeight", typeof(double), typeof(WindowBorder), new PropertyMetadata(40.0,
+ (d, e) => (d as WindowBorder)?.HeaderHeight_PropertyChangedCallback(e)));
+ ///
+ /// 处理「WindowBorder.HeaderHeight」属性变更
+ ///
+ protected virtual void HeaderHeight_PropertyChangedCallback(DependencyPropertyChangedEventArgs e)
+ {
+ if ((double)e.NewValue < 32) this.HeaderHeight = 32;
+ }
+ ///
+ /// 菜单
+ ///
+ public MenuItem Menu
+ {
+ get { return (MenuItem)GetValue(MenuProperty); }
+ set { SetValue(MenuProperty, value); }
+ }
+ public static readonly DependencyProperty MenuProperty =
+ DependencyProperty.Register("Menu", typeof(MenuItem), typeof(WindowBorder), new PropertyMetadata(null,
+ (d, e) => (d as WindowBorder)?.Menu_PropertyChangedCallback(e)));
+ ///
+ /// 处理「WindowBorder.Menu」属性变更
+ ///
+ protected virtual void Menu_PropertyChangedCallback(DependencyPropertyChangedEventArgs e)
+ {
+ if (this.MainMenu != null)
+ {
+ this.MainMenu.Items.Clear();
+ if (e.NewValue is MenuItem menu)
+ {
+ menu.Height = this.HeaderHeight;
+ menu.Background = Brushes.Transparent;
+ this.MainMenu.Items.Add(menu);
+ }
+ }
+ }
+
+ private Panel? Controller;
+ private Decorator? Container;
+ private Button? Minimize;
+ private Button? Maximize;
+ private Button? CloseWin;
+ private Menu? MainMenu;
+ private Control? TitleBar;
+
+ private Window? Target;
+ private Shell.WindowChrome? Chrome;
+
+ private static bool IsNeedOverrideMetadata = true;
+ public WindowBorder() { }
+ public void OnAttached()
+ {
+ FrameworkElement? parent = this.Parent as FrameworkElement;
+ Window? target = parent as Window;
+ while (parent != null && target == null)
+ {
+ parent = parent.Parent as FrameworkElement;
+ target = parent as Window;
+ }
+ //这是第【1】步
+ if (target == null && !DesignerProperties.GetIsInDesignMode(this)) throw new ArgumentNullException("窗体装饰器[WindowDecorator]对象不可以附加到一个空的Window对象上!");
+ this.Target = target;
+ if (this.Target == null) return;
+ this.Target.StateChanged += Target_StateChanged;
+ this.Target.Loaded += Target_Loaded;
+ if (IsNeedOverrideMetadata)
+ {
+ Window.WindowStyleProperty.OverrideMetadata(this.Target.GetType(), new FrameworkPropertyMetadata((d, e) => this.AdjustVisual()));
+ Window.ResizeModeProperty.OverrideMetadata(this.Target.GetType(), new FrameworkPropertyMetadata((d, e) => this.AdjustVisual()));
+ IsNeedOverrideMetadata = false;
+ }
+ }
+ public override void OnApplyTemplate()
+ {
+ this.OnDetaching();
+ base.OnApplyTemplate();
+ this.OnAttached();
+ //这是第【2】步
+ this.Controller = GetTemplateChild("Controller") as Panel;
+ this.Container = GetTemplateChild("Container") as Decorator;
+ this.Minimize = GetTemplateChild("Minimize") as Button;
+ this.Maximize = GetTemplateChild("Maximize") as Button;
+ this.CloseWin = GetTemplateChild("CloseWin") as Button;
+ this.TitleBar = GetTemplateChild("TitleBar") as Control;
+ this.MainMenu = GetTemplateChild("MainMenu") as Menu;
+
+ if (this.MainMenu != null)
+ {
+ this.MainMenu.Items.Clear();
+ if (this.Menu != null)
+ {
+ this.Menu.Height = this.Height;
+ this.Menu.Background = Brushes.Transparent;
+ this.MainMenu.Items.Add(this.Menu);
+ }
+ }
+ }
+ private void Target_Loaded(object sender, RoutedEventArgs e)
+ {
+ if (this.Background == BackgroundProperty.DefaultMetadata.DefaultValue) this.SetBinding(BackgroundProperty, new Binding() { Source = this.Target, Path = new PropertyPath(BackgroundProperty.Name) });
+ if (this.BorderBrush == BorderBrushProperty.DefaultMetadata.DefaultValue) this.SetBinding(BorderBrushProperty, new Binding() { Source = this.Target, Path = new PropertyPath(BorderBrushProperty.Name) });
+ //这是第【3】步
+ this.Chrome = new Shell.WindowChrome() { CaptionHeight = this.HeaderHeight };
+ Shell.WindowChrome.SetWindowChrome(this.Target, this.Chrome);
+ Shell.WindowChrome.SetIsHitTestVisibleInChrome(this.Container, true);
+ Shell.WindowChrome.SetIsHitTestVisibleInChrome(this.Controller, true);
+
+ if (this.Controller != null)
+ {
+ foreach (var c in this.Controller.Children)
+ {
+ if (c is Button button) button.AddHandler(Button.ClickEvent, new RoutedEventHandler(this.CommandButton_Click));
+ }
+ }
+ if (this.Menu != null) this.Menu.Height = this.HeaderHeight;
+ this.AdjustVisual();
+ this.AdjustMargin();
+ }
+ public void OnDetaching()
+ {
+ if (this.Target != null)
+ {
+ this.Target.StateChanged -= this.Target_StateChanged;
+ this.Target.Loaded -= this.Target_Loaded;
+ }
+ }
+ private void AdjustVisual()
+ {
+ if (this.Target != null)
+ {
+ this.Visibility = Visibility.Visible;
+ if (this.Controller != null) this.Controller.Visibility = Visibility.Visible;
+ if (this.Minimize != null) this.Minimize.Visibility = Visibility.Visible;
+ if (this.Maximize != null) this.Maximize.Visibility = Visibility.Visible;
+ if (this.CloseWin != null) this.CloseWin.Visibility = Visibility.Visible;
+ if (this.TitleBar != null) this.TitleBar.Visibility = Visibility.Visible;
+
+ if (this.Target.ResizeMode == ResizeMode.NoResize)
+ {
+ if (this.Minimize != null) this.Minimize.Visibility = Visibility.Collapsed;
+ if (this.Maximize != null) this.Maximize.Visibility = Visibility.Collapsed;
+ }
+ if (this.Target.ResizeMode == ResizeMode.CanMinimize)
+ {
+ if (this.Maximize != null) this.Maximize.Visibility = Visibility.Collapsed;
+ }
+ if (this.Target.WindowStyle == WindowStyle.None)
+ {
+ if (this.TitleBar != null) this.TitleBar.Visibility = Visibility.Collapsed;
+ }
+ if (this.Target.WindowStyle == WindowStyle.ToolWindow)
+ {
+ if (this.Controller != null) this.Controller.Visibility = Visibility.Collapsed;
+ }
+ }
+ }
+ private void AdjustMargin()
+ {
+ if (this.Target != null)
+ {
+ var winContent = this.Target.Content as FrameworkElement;
+ if (winContent == null) return;
+ if (this.Target.WindowState == WindowState.Maximized)
+ winContent.Margin = new Thickness(8);
+ else winContent.Margin = new Thickness(0);
+ }
+ }
+ private void Target_StateChanged(object? sender, EventArgs e)
+ {
+ this.AdjustMargin();
+ }
+ private void CommandButton_Click(object sender, RoutedEventArgs e)
+ {
+ Button? button = sender as Button;
+ if (button != null)
+ {
+ switch (button.Name)
+ {
+ case "Minimize":
+ if (this.Target != null) this.Target.WindowState = WindowState.Minimized;
+ break;
+ case "Maximize":
+ {
+ if (this.Target != null)
+ {
+ if (this.Target.WindowState == WindowState.Maximized || this.Target.WindowState == WindowState.Minimized)
+ this.Target.WindowState = WindowState.Normal;
+ else this.Target.WindowState = WindowState.Maximized;
+ }
+ }
+ break;
+ case "CloseWin":
+ this.Target?.Close();
+ break;
+ default:
+ break;
+ }
+ }
+ }
+ }
+}
diff --git a/DeedyDesigner/Deedy.Wpf/WindowHeader.cs b/DeedyDesigner/Deedy.Wpf/WindowHeader.cs
index 97683cd..05ff5b3 100644
--- a/DeedyDesigner/Deedy.Wpf/WindowHeader.cs
+++ b/DeedyDesigner/Deedy.Wpf/WindowHeader.cs
@@ -8,6 +8,9 @@ using Shell = System.Windows.Shell;
namespace Deedy
{
+ ///
+ /// 为自定义窗口提供标题栏
+ ///
[ContentProperty("Header")]
[TemplatePart(Name = "MainMenu", Type = typeof(Menu))]
[TemplatePart(Name = "Minimize", Type = typeof(Button))]