准备编写「WindowBorder」控件

This commit is contained in:
zengwenjie
2025-09-30 11:08:16 +08:00
parent e8d6c06089
commit 5768be69f4
3 changed files with 274 additions and 0 deletions

View File

@@ -58,4 +58,17 @@
</Setter.Value> </Setter.Value>
</Setter> </Setter>
</Style> </Style>
<Style TargetType="{x:Type local:WindowBorder}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:WindowBorder}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ResourceDictionary> </ResourceDictionary>

View File

@@ -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
{
/// <summary>
/// 为自定义窗口提供标题栏与内容容器;可以作为一个窗口的直接子节点
/// </summary>
[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));
}
/// <summary>
/// 悬停画刷
/// </summary>
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));
/// <summary>
/// 标题画刷
/// </summary>
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));
/// <summary>
/// 标题高度
/// </summary>
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)));
/// <summary>
/// 处理「WindowBorder.HeaderHeight」属性变更
/// </summary>
protected virtual void HeaderHeight_PropertyChangedCallback(DependencyPropertyChangedEventArgs e)
{
if ((double)e.NewValue < 32) this.HeaderHeight = 32;
}
/// <summary>
/// 菜单
/// </summary>
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)));
/// <summary>
/// 处理「WindowBorder.Menu」属性变更
/// </summary>
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;
}
}
}
}
}

View File

@@ -8,6 +8,9 @@ using Shell = System.Windows.Shell;
namespace Deedy namespace Deedy
{ {
/// <summary>
/// 为自定义窗口提供标题栏
/// </summary>
[ContentProperty("Header")] [ContentProperty("Header")]
[TemplatePart(Name = "MainMenu", Type = typeof(Menu))] [TemplatePart(Name = "MainMenu", Type = typeof(Menu))]
[TemplatePart(Name = "Minimize", Type = typeof(Button))] [TemplatePart(Name = "Minimize", Type = typeof(Button))]