From 437c2b1252b8c037ec5bc12d0325e92d582112eb Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E4=BA=8E=E6=99=BA=E7=BA=AF?= <于智纯@LODESTAR>
Date: Sat, 20 Sep 2025 13:52:13 +0800
Subject: [PATCH] =?UTF-8?q?=E5=BC=95=E5=85=A5=E8=87=AA=E5=AE=9A=E4=B9=89?=
 =?UTF-8?q?=E7=AA=97=E5=8F=A3?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
 DeedyDesigner/Deedy.Testing/ElegantWindow.cs  |  61 +++++
 DeedyDesigner/Deedy.Testing/ExMarkup.cs       |  86 +++++++
 .../Deedy.Testing/Themes/Generic.xaml         |  83 ++++++
 DeedyDesigner/Deedy.Testing/Waiting.xaml      |  46 ++++
 DeedyDesigner/Deedy.Testing/Waiting.xaml.cs   | 104 ++++++++
 .../Deedy.Testing/WindowDecorator.cs          | 239 ++++++++++++++++++
 6 files changed, 619 insertions(+)
 create mode 100644 DeedyDesigner/Deedy.Testing/ElegantWindow.cs
 create mode 100644 DeedyDesigner/Deedy.Testing/ExMarkup.cs
 create mode 100644 DeedyDesigner/Deedy.Testing/Themes/Generic.xaml
 create mode 100644 DeedyDesigner/Deedy.Testing/Waiting.xaml
 create mode 100644 DeedyDesigner/Deedy.Testing/Waiting.xaml.cs
 create mode 100644 DeedyDesigner/Deedy.Testing/WindowDecorator.cs
diff --git a/DeedyDesigner/Deedy.Testing/ElegantWindow.cs b/DeedyDesigner/Deedy.Testing/ElegantWindow.cs
new file mode 100644
index 0000000..be84e57
--- /dev/null
+++ b/DeedyDesigner/Deedy.Testing/ElegantWindow.cs
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+
+namespace Deedy.Testing
+{
+    /// 
+    /// 按照步骤 1a 或 1b 操作,然后执行步骤 2 以在 XAML 文件中使用此自定义控件。
+    ///
+    /// 步骤 1a) 在当前项目中存在的 XAML 文件中使用该自定义控件。
+    /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根
+    /// 元素中:
+    ///
+    ///     xmlns:MyNamespace="clr-namespace:Deedy.Testing"
+    ///
+    ///
+    /// 步骤 1b) 在其他项目中存在的 XAML 文件中使用该自定义控件。
+    /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根
+    /// 元素中:
+    ///
+    ///     xmlns:MyNamespace="clr-namespace:Deedy.Testing;assembly=Deedy.Testing"
+    ///
+    /// 您还需要添加一个从 XAML 文件所在的项目到此项目的项目引用,
+    /// 并重新生成以避免编译错误:
+    ///
+    ///     在解决方案资源管理器中右击目标项目,然后依次单击
+    ///     “添加引用”->“项目”->[浏览查找并选择此项目]
+    ///
+    ///
+    /// 步骤 2)
+    /// 继续操作并在 XAML 文件中使用控件。
+    ///
+    ///     
+    ///
+    /// 
+    public class ElegantWindow : Window
+    {
+        static ElegantWindow()
+        {
+            DefaultStyleKeyProperty.OverrideMetadata(typeof(ElegantWindow), new FrameworkPropertyMetadata(typeof(ElegantWindow)));
+        }
+        public ElegantWindow() : base()
+        {
+        }
+        public override void OnApplyTemplate()
+        {
+            base.OnApplyTemplate();
+        }
+    }
+}
diff --git a/DeedyDesigner/Deedy.Testing/ExMarkup.cs b/DeedyDesigner/Deedy.Testing/ExMarkup.cs
new file mode 100644
index 0000000..9dccdca
--- /dev/null
+++ b/DeedyDesigner/Deedy.Testing/ExMarkup.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+
+namespace Deedy
+{
+    public class ExMarkup : DependencyObject
+    {
+
+        public static WindowDecorator GetWindowChrome(DependencyObject obj)
+        {
+            return (WindowDecorator)obj.GetValue(WindowChromeProperty);
+        }
+
+        public static void SetWindowChrome(DependencyObject obj, WindowDecorator value)
+        {
+            obj.SetValue(WindowChromeProperty, value);
+        }
+
+        // 用于自定义窗体的标题栏
+        public static readonly DependencyProperty WindowChromeProperty =
+            DependencyProperty.RegisterAttached("WindowChrome", typeof(WindowDecorator), typeof(ExMarkup), new PropertyMetadata(null, (d, e) =>
+            {
+                if (e.NewValue == null)
+                {
+                    if (e.OldValue != null) ((WindowDecorator)e.OldValue).OnDetaching();
+                }
+                else
+                {
+                    if (d is Decorator)
+                    {
+                        Decorator header = (Decorator)d;
+                        FrameworkElement? parent = header.Parent as FrameworkElement;
+                        Window? target = parent as Window;
+                        while (parent != null && target == null)
+                        {
+                            parent = parent.Parent as FrameworkElement;
+                            target = parent as Window;
+                        }
+
+                        if (target != null) ((WindowDecorator)e.NewValue).OnAttached(target, header);
+                        else
+                        {
+                            if (!DesignerProperties.GetIsInDesignMode(d))
+                                throw new InvalidOperationException("窗体装饰器[WindowDecorator]对象在附加到作为标题栏容器的[Decorator]类型派生对象(例如[Border]对象)前,该容器必须已经挂接到一个窗体对象的视觉树上!");
+                        }
+                    }
+                    else throw new InvalidOperationException("窗体装饰器[WindowDecorator]对象只能附加到作为标题栏容器的[Decorator]类型派生对象(例如[Border]对象)上!");
+                }
+            }));
+
+
+
+        public static string GetTextBoxRegexMatch(DependencyObject obj)
+        {
+            return (string)obj.GetValue(TextBoxRegexMatchProperty);
+        }
+
+        public static void SetTextBoxRegexMatch(DependencyObject obj, string value)
+        {
+            obj.SetValue(TextBoxRegexMatchProperty, value);
+        }
+
+        // Using a DependencyProperty as the backing store for TextBoxRegexMatch.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty TextBoxRegexMatchProperty =
+            DependencyProperty.RegisterAttached("TextBoxRegexMatch", typeof(string), typeof(ExMarkup), new PropertyMetadata("", (d, e) =>
+            {
+                if (d is TextBox textBox)
+                {
+                    if (!string.IsNullOrEmpty(e.NewValue as string) && string.IsNullOrEmpty(e.OldValue as string))
+                    {
+                        //加事件监听逻辑
+                    }
+                    if (string.IsNullOrEmpty(e.NewValue as string) && !string.IsNullOrEmpty(e.OldValue as string))
+                    {
+                        //取消事件监听逻辑
+                    }
+                }
+            }));
+    }
+}
diff --git a/DeedyDesigner/Deedy.Testing/Themes/Generic.xaml b/DeedyDesigner/Deedy.Testing/Themes/Generic.xaml
new file mode 100644
index 0000000..56134d0
--- /dev/null
+++ b/DeedyDesigner/Deedy.Testing/Themes/Generic.xaml
@@ -0,0 +1,83 @@
+
+
+    
+                    
+                    
+                        
+                            
+                                
+                                
+                                
+                                
+                            
+                            
+                                
+                            
+                            
+                            
+                            
+                                
+                                
+                                
+                            
+                        
+                    
+                
+            
+        
+    
+    
+    
+
diff --git a/DeedyDesigner/Deedy.Testing/Waiting.xaml b/DeedyDesigner/Deedy.Testing/Waiting.xaml
new file mode 100644
index 0000000..a4048d4
--- /dev/null
+++ b/DeedyDesigner/Deedy.Testing/Waiting.xaml
@@ -0,0 +1,46 @@
+
+    
+        
+        
+            
+                
+                    
+                
+                
+                    
+                    
+                        
+                        
+                        
+                    
+                    
+                        
+                            
+                                
+                            
+                        
+                        
+                            
+                                
+                            
+                        
+                    
+                
+                
+                    
+                
+            
+        
+    
+
diff --git a/DeedyDesigner/Deedy.Testing/Waiting.xaml.cs b/DeedyDesigner/Deedy.Testing/Waiting.xaml.cs
new file mode 100644
index 0000000..4a6553f
--- /dev/null
+++ b/DeedyDesigner/Deedy.Testing/Waiting.xaml.cs
@@ -0,0 +1,104 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Shapes;
+
+namespace Deedy
+{
+    /// 
+    /// Waiting.xaml 的交互逻辑
+    /// 
+    public partial class Waiting : Window
+    {
+        private Func? CancelHandler;
+        public ObservableCollection Options { get; private set; } = new ObservableCollection();
+        public Waiting()
+        {
+            InitializeComponent();
+            Options.Add("确定");
+            if (Debugger.IsAttached) this.view.Background = null;
+        }
+        public Waiting(string? hintMsg = null, int process = -1, Func? cancelHandler = null) : this()
+        {
+            this.HintMessage = hintMsg ?? "系统正忙,请稍等...";
+            if (process >= 0) this.SetProgress(process);
+            this.OptionsVisibility = Visibility.Collapsed;
+            CancelHandler = cancelHandler;
+            if (CancelHandler != null) this.cancelBar.Visibility = Visibility.Visible;
+        }
+        public Waiting(string? hintMsg = null, params string[] options) : this(hintMsg, -1)
+        {
+            if (options != null && options.Length > 0)
+            {
+                this.Options.Clear();
+                foreach (string option in options)
+                    this.Options.Add(option);
+            }
+            this.OptionsVisibility = Visibility.Visible;
+        }
+        public string HintMessage
+        {
+            get { return (string)GetValue(HintMessageProperty); }
+            set { SetValue(HintMessageProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for HintMessage.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty HintMessageProperty =
+            DependencyProperty.Register("HintMessage", typeof(string), typeof(Waiting), new PropertyMetadata("系统正忙,请稍等..."));
+
+        public bool ShowProgress
+        {
+            get { return (bool)GetValue(ShowProgressProperty); }
+            set { SetValue(ShowProgressProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for ShowProgress.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty ShowProgressProperty =
+            DependencyProperty.Register("ShowProgress", typeof(bool), typeof(Waiting), new PropertyMetadata(false));
+        public string Option { get; private set; } = "";
+
+
+        public Visibility OptionsVisibility
+        {
+            get { return (Visibility)GetValue(OptionsVisibilityProperty); }
+            set { SetValue(OptionsVisibilityProperty, value); }
+        }
+
+        // Using a DependencyProperty as the backing store for OptionsVisibility.  This enables animation, styling, binding, etc...
+        public static readonly DependencyProperty OptionsVisibilityProperty =
+            DependencyProperty.Register("OptionsVisibility", typeof(Visibility), typeof(Waiting), new PropertyMetadata(Visibility.Collapsed));
+
+
+        public void SetProgress(double progress, string hintMsg = "")
+        {
+            this.ShowProgress = true;
+            if (!string.IsNullOrEmpty(hintMsg)) this.HintMessage = hintMsg;
+            this.OptionsVisibility = Visibility.Collapsed;
+            this.pbProgressBar.Value = progress;
+        }
+
+        private void Button_Click(object sender, RoutedEventArgs e)
+        {
+            Button btn = (Button)sender;
+            this.Option = (btn.Content as string) ?? "";
+            this.DialogResult = true;
+            this.Close();
+        }
+
+        private void Button_取消按钮被点击_Click(object sender, RoutedEventArgs e)
+        {
+            if (this.CancelHandler?.Invoke() == true) this.Close();
+        }
+    }
+}
diff --git a/DeedyDesigner/Deedy.Testing/WindowDecorator.cs b/DeedyDesigner/Deedy.Testing/WindowDecorator.cs
new file mode 100644
index 0000000..30fb074
--- /dev/null
+++ b/DeedyDesigner/Deedy.Testing/WindowDecorator.cs
@@ -0,0 +1,239 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Threading.Tasks;
+using System.Windows;
+using System.Windows.Controls;
+using System.Windows.Data;
+using System.Windows.Documents;
+using System.Windows.Input;
+using System.Windows.Markup;
+using System.Windows.Media;
+using System.Windows.Media.Imaging;
+using System.Windows.Navigation;
+using System.Windows.Shapes;
+using System.Windows.Shell;
+
+namespace Deedy
+{
+    /// 
+    /// 按照步骤 1a 或 1b 操作,然后执行步骤 2 以在 XAML 文件中使用此自定义控件。
+    ///
+    /// 步骤 1a) 在当前项目中存在的 XAML 文件中使用该自定义控件。
+    /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根
+    /// 元素中:
+    ///
+    ///     xmlns:MyNamespace="clr-namespace:ReoKM"
+    ///
+    ///
+    /// 步骤 1b) 在其他项目中存在的 XAML 文件中使用该自定义控件。
+    /// 将此 XmlNamespace 特性添加到要使用该特性的标记文件的根
+    /// 元素中:
+    ///
+    ///     xmlns:MyNamespace="clr-namespace:ReoKM;assembly=ReoKM"
+    ///
+    /// 您还需要添加一个从 XAML 文件所在的项目到此项目的项目引用,
+    /// 并重新生成以避免编译错误:
+    ///
+    ///     在解决方案资源管理器中右击目标项目,然后依次单击
+    ///     “添加引用”->“项目”->[浏览查找并选择此项目]
+    ///
+    ///
+    /// 步骤 2)
+    /// 继续操作并在 XAML 文件中使用控件。
+    ///
+    ///     
+    ///
+    /// 
+    [ContentProperty("Child")]
+    [TemplatePart(Name = "IconView", Type = typeof(Border))]
+    [TemplatePart(Name = "TitleBar", Type = typeof(TextBlock))]
+    [TemplatePart(Name = "Minimize", Type = typeof(Button))]
+    [TemplatePart(Name = "Maximize", Type = typeof(Button))]
+    [TemplatePart(Name = "CloseWin", Type = typeof(Button))]
+    [TemplatePart(Name = "Controller", Type = typeof(Panel))]
+    [TemplatePart(Name = "Container", Type = typeof(Decorator))]
+    public class WindowDecorator : Control
+    {
+        static WindowDecorator()
+        {
+            DefaultStyleKeyProperty.OverrideMetadata(typeof(WindowDecorator), new FrameworkPropertyMetadata(typeof(WindowDecorator)));
+        }
+
+        public Brush HoverBrush
+        {
+            get { return (Brush)GetValue(HoverBrushProperty); }
+            set { SetValue(HoverBrushProperty, value); }
+        }
+
+        public static readonly DependencyProperty HoverBrushProperty =
+            DependencyProperty.Register("HoverBrush", typeof(Brush), typeof(WindowDecorator), new PropertyMetadata(null));
+
+
+        public UIElement Child
+        {
+            get { return (UIElement)GetValue(ChildProperty); }
+            set { SetValue(ChildProperty, value); }
+        }
+
+        public static readonly DependencyProperty ChildProperty =
+            DependencyProperty.Register("Child", typeof(UIElement), typeof(WindowDecorator), new PropertyMetadata(null, (d, e) =>
+            {
+                WindowDecorator windowDecorator = (WindowDecorator)d;
+                if (e.NewValue != null)
+                {
+                    if (windowDecorator.Container != null)
+                        windowDecorator.Container.Child = e.NewValue as UIElement;
+                }
+            }));
+
+        private Panel? Controller;
+        private Decorator? Container;
+        private Button? Minimize;
+        private Button? Maximize;
+        private Button? CloseWin;
+        private TextBlock? TitleBar;
+        private Border? IconView;
+
+        private Window? Target;
+        private WindowChrome? Chrome;
+        private Decorator? Decorator;
+
+        private static bool IsNeedOverrideMetadata = true;
+        public WindowDecorator() { }
+        public void OnAttached(Window target, Decorator decorator)
+        {
+            //这是第【1】步
+            if (target == null) throw new ArgumentNullException("窗体装饰器[WindowDecorator]对象不可以附加到一个空的Window对象上!");
+            if (decorator == null) throw new ArgumentNullException("窗体装饰器[WindowDecorator]对象需要一个用于放置的容器!");
+            this.Target = target;
+            this.Decorator = decorator;
+            this.Decorator.Child = this;
+            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()
+        {
+            base.OnApplyTemplate();
+            //这是第【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.IconView = GetTemplateChild("IconView") as Border;
+            this.TitleBar = GetTemplateChild("TitleBar") as TextBlock;
+        }
+        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 WindowChrome() { CaptionHeight = this.ActualHeight };
+            WindowChrome.SetWindowChrome(this.Target, this.Chrome);
+            WindowChrome.SetIsHitTestVisibleInChrome(this.Container, true);
+            WindowChrome.SetIsHitTestVisibleInChrome(this.Controller, true);
+            if (this.Container != null) this.Container.Child = this.Child;
+            if (this.Controller != null)
+            {
+                foreach (var c in this.Controller.Children)
+                {
+                    Button? button = c as Button;
+                    button?.AddHandler(Button.ClickEvent, new RoutedEventHandler(this.CommandButton_Click));
+                    button?.AddHandler(Button.MouseEnterEvent, new RoutedEventHandler((s, e) => ((Button)e.Source).Background = this.HoverBrush));
+                    button?.AddHandler(Button.MouseLeaveEvent, new RoutedEventHandler((s, e) => ((Button)e.Source).Background = Brushes.Transparent));
+                }
+            }
+            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)
+            {
+                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.IconView != null) this.IconView.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.IconView != null) this.IconView.Visibility = Visibility.Collapsed;
+                    if (this.TitleBar != null) this.TitleBar.Visibility = Visibility.Collapsed;
+                    if (this.Controller != null) this.Controller.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;
+                    default:
+                        this.Target?.Close();
+                        break;
+                }
+            }
+        }
+    }
+}