「WindowHeader」与「WindowBorder」完成

This commit is contained in:
zengwenjie
2025-09-30 12:57:51 +08:00
parent 5768be69f4
commit 314e3e3ebd
8 changed files with 153 additions and 25 deletions

View File

@@ -19,6 +19,7 @@
<Button Content="打开抽屉" Click="Button_Click"/> <Button Content="打开抽屉" Click="Button_Click"/>
<Button Content="自定义窗口标题测试" Click="Button_Click_1"/> <Button Content="自定义窗口标题测试" Click="Button_Click_1"/>
<Button Content="自定义窗口标题栏" Click="Button_Click_2"/> <Button Content="自定义窗口标题栏" Click="Button_Click_2"/>
<Button Content="自定义窗口边框" Click="Button_Click_3"/>
</UniformGrid> </UniformGrid>
</AdornerDecorator> </AdornerDecorator>
</DockPanel> </DockPanel>

View File

@@ -42,5 +42,10 @@ namespace Deedy
{ {
new WindowHeaderTest().ShowDialog(); new WindowHeaderTest().ShowDialog();
} }
private void Button_Click_3(object sender, RoutedEventArgs e)
{
new WindowBorderTest().ShowDialog();
}
} }
} }

View File

@@ -0,0 +1,25 @@
<Window x:Class="Deedy.Testing.WindowBorderTest"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Deedy.Testing"
xmlns:deedy="clr-namespace:Deedy;assembly=Deedy.Wpf"
mc:Ignorable="d"
Title="WindowHeaderTest" Height="450" Width="800" Foreground="White" FontSize="16" d:WindowStyle="None" Icon="/Icons/Icon.png">
<deedy:WindowBorder ButtonBase.Click="WindowBorder_Click" MenuItem.Click="WindowBorder_Click_1">
<deedy:WindowBorder.Menu>
<MenuItem Header="CustomMenu">
<MenuItem Header="Menu_1"/>
</MenuItem>
</deedy:WindowBorder.Menu>
<deedy:WindowBorder.Header>
<UniformGrid Rows="1">
<Button Content="测试的标题"/>
<Button Content="测试的标题"/>
<Button Content="测试的标题"/>
</UniformGrid>
</deedy:WindowBorder.Header>
<Border BorderBrush="Red" BorderThickness="1"/>
</deedy:WindowBorder>
</Window>

View File

@@ -0,0 +1,37 @@
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.Shapes;
namespace Deedy.Testing
{
/// <summary>
/// WindowBorderTest.xaml 的交互逻辑
/// </summary>
public partial class WindowBorderTest : Window
{
public WindowBorderTest()
{
InitializeComponent();
}
private void WindowBorder_Click(object sender, RoutedEventArgs e)
{
}
private void WindowBorder_Click_1(object sender, RoutedEventArgs e)
{
}
}
}

View File

@@ -24,6 +24,6 @@
<Button Content="按钮"/> <Button Content="按钮"/>
</UniformGrid> </UniformGrid>
</deedy:WindowHeader> </deedy:WindowHeader>
<Grid/> <Border BorderBrush="Red" BorderThickness="1" Background="LightBlue"/>
</DockPanel> </DockPanel>
</Window> </Window>

View File

@@ -5,8 +5,6 @@
<Style TargetType="{x:Type local:WindowHeader}"> <Style TargetType="{x:Type local:WindowHeader}">
<Setter Property="Height" Value="40"/> <Setter Property="Height" Value="40"/>
<Setter Property="MinHeight" Value="32"/> <Setter Property="MinHeight" Value="32"/>
<Setter Property="HorizontalAlignment" Value="Stretch"/>
<Setter Property="HorizontalContentAlignment" Value="Left"/>
<Setter Property="DockPanel.Dock" Value="Top"/> <Setter Property="DockPanel.Dock" Value="Top"/>
<Setter Property="Padding" Value="6"/> <Setter Property="Padding" Value="6"/>
<Setter Property="Template"> <Setter Property="Template">
@@ -60,13 +58,56 @@
</Style> </Style>
<Style TargetType="{x:Type local:WindowBorder}"> <Style TargetType="{x:Type local:WindowBorder}">
<Setter Property="Padding" Value="6"/>
<Setter Property="Template"> <Setter Property="Template">
<Setter.Value> <Setter.Value>
<ControlTemplate TargetType="{x:Type local:WindowBorder}"> <ControlTemplate TargetType="{x:Type local:WindowBorder}">
<Border Background="{TemplateBinding Background}" <ControlTemplate.Resources>
BorderBrush="{TemplateBinding BorderBrush}" <Style x:Key="ControlButtonStyle" TargetType="{x:Type Button}">
BorderThickness="{TemplateBinding BorderThickness}"> <Setter Property="Background" Value="Transparent"/>
</Border> <Setter Property="Cursor" Value="Hand"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}">
<ContentPresenter Margin="{TemplateBinding Padding}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
<Style.Triggers>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" Value="{Binding HoverBrush,RelativeSource={RelativeSource Mode=TemplatedParent}}"/>
</Trigger>
</Style.Triggers>
</Style>
</ControlTemplate.Resources>
<DockPanel Background="{TemplateBinding Background}">
<Border x:Name="TitleBar" Background="{TemplateBinding Headground}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}"
DockPanel.Dock="Top" MinHeight="32" Height="{TemplateBinding HeaderHeight}">
<DockPanel>
<Image Margin="{TemplateBinding Padding}" Stretch="Uniform" Source="{Binding Icon, RelativeSource={RelativeSource AncestorType=Window}}"/>
<TextBlock VerticalAlignment="Center" Margin="0,0,6,0" Text="{Binding Title, RelativeSource={RelativeSource AncestorType=Window}}"/>
<DockPanel x:Name="Controller" DockPanel.Dock="Right" MinWidth="147" Background="#01FFFFFF" VerticalAlignment="Stretch">
<Button x:Name="CloseWin" DockPanel.Dock="Right" Style="{DynamicResource ControlButtonStyle}" Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}">
<Path Stretch="Uniform" Margin="{TemplateBinding Padding}" Stroke="Transparent" Fill="{TemplateBinding Foreground}"
Data="M 1 0 L 0 1 L 23 24 L 24 23 Z M 1 24 L 0 23 L 11 12 L 12 13 Z M 12 11 L 23 0 L 24 1 L 13 12 Z"/>
</Button>
<Button x:Name="Maximize" DockPanel.Dock="Right" Style="{DynamicResource ControlButtonStyle}" Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}">
<Path Stretch="Uniform" Margin="{TemplateBinding Padding}" Stroke="Transparent" Fill="{TemplateBinding Foreground}"
Data="M 0 6 V 24 H 18 V 6 H 16.5 V 22.5 H 1.5 V 7.5 H 16.5 V 6 Z M 6 0 H 24 V 18 H 22.5 V 1.5 H 6 Z"/>
</Button>
<Button x:Name="Minimize" DockPanel.Dock="Right" Style="{DynamicResource ControlButtonStyle}" Width="{Binding ActualHeight, RelativeSource={RelativeSource Self}}">
<Path Stretch="Uniform" Margin="{TemplateBinding Padding}" Stroke="Transparent" Fill="{TemplateBinding Foreground}"
Data="M 0 0 V 24 M 0 11.25 V 12.75 H 24 V 11.25 Z"/>
</Button>
<Menu x:Name="MainMenu" Background="#01FFFFFF" Foreground="{TemplateBinding HoverBrush}"/>
</DockPanel>
<Decorator x:Name="Container" VerticalAlignment="Stretch" HorizontalAlignment="Stretch"/>
</DockPanel>
</Border>
<ContentPresenter/>
</DockPanel>
</ControlTemplate> </ControlTemplate>
</Setter.Value> </Setter.Value>
</Setter> </Setter>

View File

@@ -1,4 +1,5 @@
using System.ComponentModel; using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Windows; using System.Windows;
using System.Windows.Controls; using System.Windows.Controls;
using System.Windows.Data; using System.Windows.Data;
@@ -19,12 +20,11 @@ namespace Deedy
[TemplatePart(Name = "TitleBar", Type = typeof(Control))] [TemplatePart(Name = "TitleBar", Type = typeof(Control))]
[TemplatePart(Name = "Controller", Type = typeof(Panel))] [TemplatePart(Name = "Controller", Type = typeof(Panel))]
[TemplatePart(Name = "Container", Type = typeof(Decorator))] [TemplatePart(Name = "Container", Type = typeof(Decorator))]
public class WindowBorder : HeaderedContentControl public class WindowBorder : ContentControl
{ {
static WindowBorder() static WindowBorder()
{ {
DefaultStyleKeyProperty.OverrideMetadata(typeof(WindowBorder), new FrameworkPropertyMetadata(typeof(WindowBorder))); DefaultStyleKeyProperty.OverrideMetadata(typeof(WindowBorder), new FrameworkPropertyMetadata(typeof(WindowBorder)));
BackgroundProperty.OverrideMetadata(typeof(WindowBorder), new FrameworkPropertyMetadata(Brushes.Gray));
} }
/// <summary> /// <summary>
/// 悬停画刷 /// 悬停画刷
@@ -55,14 +55,24 @@ namespace Deedy
set { SetValue(HeaderHeightProperty, value); } set { SetValue(HeaderHeightProperty, value); }
} }
public static readonly DependencyProperty HeaderHeightProperty = public static readonly DependencyProperty HeaderHeightProperty =
DependencyProperty.Register("HeaderHeight", typeof(double), typeof(WindowBorder), new PropertyMetadata(40.0, DependencyProperty.Register("HeaderHeight", typeof(double), typeof(WindowBorder), new PropertyMetadata(40.0));
(d, e) => (d as WindowBorder)?.HeaderHeight_PropertyChangedCallback(e)));
/// <summary> /// <summary>
/// 处理「WindowBorder.HeaderHeight」属性变更 /// 标题
/// </summary> /// </summary>
protected virtual void HeaderHeight_PropertyChangedCallback(DependencyPropertyChangedEventArgs e) public UIElement Header
{ {
if ((double)e.NewValue < 32) this.HeaderHeight = 32; get { return (UIElement)GetValue(HeaderProperty); }
set { SetValue(HeaderProperty, value); }
}
public static readonly DependencyProperty HeaderProperty =
DependencyProperty.Register("Header", typeof(UIElement), typeof(WindowBorder), new PropertyMetadata(null,
(d, e) => (d as WindowBorder)?.Header_PropertyChangedCallback(e)));
/// <summary>
/// 处理「WindowBorder.Header」属性变更
/// </summary>
protected virtual void Header_PropertyChangedCallback(DependencyPropertyChangedEventArgs e)
{
if (e.NewValue is UIElement header && this.Container != null) this.Container.Child = header;
} }
/// <summary> /// <summary>
/// 菜单 /// 菜单
@@ -141,12 +151,13 @@ namespace Deedy
this.TitleBar = GetTemplateChild("TitleBar") as Control; this.TitleBar = GetTemplateChild("TitleBar") as Control;
this.MainMenu = GetTemplateChild("MainMenu") as Menu; this.MainMenu = GetTemplateChild("MainMenu") as Menu;
if (this.Container != null) this.Container.Child = this.Header;
if (this.MainMenu != null) if (this.MainMenu != null)
{ {
this.MainMenu.Items.Clear(); this.MainMenu.Items.Clear();
if (this.Menu != null) if (this.Menu != null)
{ {
this.Menu.Height = this.Height; this.Menu.Height = this.HeaderHeight;
this.Menu.Background = Brushes.Transparent; this.Menu.Background = Brushes.Transparent;
this.MainMenu.Items.Add(this.Menu); this.MainMenu.Items.Add(this.Menu);
} }
@@ -215,11 +226,15 @@ namespace Deedy
{ {
if (this.Target != null) if (this.Target != null)
{ {
var winContent = this.Target.Content as FrameworkElement; if (this.Target.Content is not FrameworkElement winContent) return;
if (winContent == null) return;
if (this.Target.WindowState == WindowState.Maximized) if (this.Target.WindowState == WindowState.Maximized)
{
winContent.Margin = new Thickness(8); winContent.Margin = new Thickness(8);
else winContent.Margin = new Thickness(0); }
else
{
winContent.Margin = new Thickness(1, 0, 1, 1);
}
} }
} }
private void Target_StateChanged(object? sender, EventArgs e) private void Target_StateChanged(object? sender, EventArgs e)
@@ -228,9 +243,9 @@ namespace Deedy
} }
private void CommandButton_Click(object sender, RoutedEventArgs e) private void CommandButton_Click(object sender, RoutedEventArgs e)
{ {
Button? button = sender as Button; if (sender is Button button)
if (button != null)
{ {
e.Handled = true;
switch (button.Name) switch (button.Name)
{ {
case "Minimize": case "Minimize":

View File

@@ -204,11 +204,15 @@ namespace Deedy
{ {
if (this.Target != null) if (this.Target != null)
{ {
var winContent = this.Target.Content as FrameworkElement; if (this.Target.Content is not FrameworkElement winContent) return;
if (winContent == null) return;
if (this.Target.WindowState == WindowState.Maximized) if (this.Target.WindowState == WindowState.Maximized)
{
winContent.Margin = new Thickness(8); winContent.Margin = new Thickness(8);
else winContent.Margin = new Thickness(0); }
else
{
winContent.Margin = new Thickness(1, 0, 1, 1);
}
} }
} }
private void Target_StateChanged(object? sender, EventArgs e) private void Target_StateChanged(object? sender, EventArgs e)
@@ -217,9 +221,9 @@ namespace Deedy
} }
private void CommandButton_Click(object sender, RoutedEventArgs e) private void CommandButton_Click(object sender, RoutedEventArgs e)
{ {
Button? button = sender as Button; if (sender is Button button)
if (button != null)
{ {
e.Handled = true;
switch (button.Name) switch (button.Name)
{ {
case "Minimize": case "Minimize":