【​​​​1.42 自定义窗体的几个关键问题】WPF案例代码解析

【​​​​1.42 自定义窗体的几个关键问题】WPF案例代码解析

WPF自定义窗体的几个关键问题

  • 自定义标题栏内容
  • 标题栏背景色
  • 最大化、最小化、关闭按钮样式
  • 最大化后窗体尺寸覆盖任务栏问题
  • 窗体拖拽问题
  • 标题栏双击还原、最大化问题

自定义标题栏内容

xaml 里设置 WindowStyle 为 None,同时设置最大宽度和最大高度(后台设置也可)

WindowStyle="None"
Topmost="{Binding ElementName=MyTopMost,Path=IsChecked}"
MaxWidth="{StaticResource {x:Static SystemParameters.MaximizedPrimaryScreenWidthKey}}"
MaxHeight="{StaticResource {x:Static SystemParameters.MaximizedPrimaryScreenHeightKey}}"

xaml 里自定义标题栏内容

从左往右是图标、标题、自定义的几个按钮(置顶,最小化,最大化、向下还原,关闭)

<!-- 设计标题栏 -->
<Grid MouseMove="Move_MouseMove" MouseDown="Grid_MouseDown" Background="#FF63BBD0">
	<Grid.ColumnDefinitions>
		<ColumnDefinition Width="auto"/>
		<ColumnDefinition/>
		<ColumnDefinition Width="auto"/>
	</Grid.ColumnDefinitions>
	<Path x:Name="MyResizeIcon" Data="{DynamicResource IconWindowMax}"/>
	<Image Source="icon.ico" Height="32" Margin="5,0"/>
	<TextBlock Grid.Column="1" Text="GeometryX" FontSize="24" VerticalAlignment="Center" Margin="5"/>
	<StackPanel Grid.Column="2" Orientation="Horizontal" HorizontalAlignment="Right" VerticalAlignment="Top">
		<ToggleButton x:Name="MyTopMost" hc:IconElement.Geometry="{DynamicResource IconPin}" ToolTip="置顶" Style="{DynamicResource MyToggleButtonIconFont}"
					  hc:IconElement.Width="8" Width="32"/>
		<Button hc:IconElement.Geometry="{DynamicResource IconWindowMin}" Style="{DynamicResource MyWindowButton}"
				hc:IconElement.Width="10" ToolTip="最小化" Width="32" Click="ButtonMin_Click"/>
		<Button x:Name="MyButtonResize" hc:IconElement.Geometry="{Binding ElementName=MyResizeIcon,Path=Data}" Style="{DynamicResource MyWindowButton}"
				hc:IconElement.Width="10" ToolTip="最大化" Width="32" Click="ButtonResize_Click"/>
		<Button hc:IconElement.Geometry="{DynamicResource IconWindowClose}" Style="{DynamicResource MyWindowButton}"
				hc:IconElement.Width="10" ToolTip="关闭" Width="32" Click="ButtonClose_Click">
			<Button.Resources>
				<SolidColorBrush x:Key="MouseEnterBrush" Color="{DynamicResource WindowClosedMouseEnterColor}"/>
			</Button.Resources>
		</Button>
	</StackPanel>
</Grid>

后台代码

/// <summary>
/// 设置窗体尺寸
/// </summary>
private void SetWindowSize()
	if (WindowState == WindowState.Maximized)
		WindowState = WindowState.Normal;
		MyResizeIcon.Data = (Geometry)FindResource("IconWindowMax");
		MyButtonResize.ToolTip = "最大化";
		WindowState = WindowState.Maximized;
		MyResizeIcon.Data = (Geometry)FindResource("IconWindowRestore");
		MyButtonResize.ToolTip = "向下还原";
private void ButtonMin_Click(object sender, RoutedEventArgs e)
	WindowState = WindowState.Minimized;
private void ButtonClose_Click(object sender, RoutedEventArgs e)
	Close();
private void ButtonResize_Click(object sender, RoutedEventArgs e)
	SetWindowSize();
private void Move_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
	if (e.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
		DragMove();
		// 拖动后窗体变成默认的
		WindowState = WindowState.Normal;
		MyResizeIcon.Data = (Geometry)FindResource("IconWindowMax");
		MyButtonResize.ToolTip = "最大化";
private void Grid_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
	if (e.ChangedButton == System.Windows.Input.MouseButton.Left && e.ClickCount == 2)
		SetWindowSize();

标题栏背景色

直接设置 Background 即可

最大化、最小化、关闭按钮样式

先到 Iconfont 官网下载一些图标,放到资源文件 Geometry.xaml

定义一个资源文件 Brush.xaml 保存颜色资源

<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
    <Color x:Key="PrimaryColor">#FF326CF3</Color>
    <Color x:Key="DarkPrimaryColor">#FF326CF3</Color>
    <Color x:Key="AccentColor">#FFF8491E</Color>
    <Color x:Key="DarkAccentColor">#FFF8491E</Color>
    <Color x:Key="DangerColor">#FFDB3340</Color>
    <Color x:Key="DarkDangerColor">#FFDB3340</Color>
    <Color x:Key="ErrorColor">#FFDB3340</Color>
    <Color x:Key="DarkErrorColor">#FFDB3340</Color>
    <Color x:Key="WarningColor">#FFE9AF20</Color>
    <Color x:Key="DarkWarningColor">#FFE9AF20</Color>
    <Color x:Key="InfoColor">#FF00BCD4</Color>
    <Color x:Key="DarkInfoColor">#FF00BCD4</Color>
    <Color x:Key="SuccessColor">#FF2DB84d</Color>
    <Color x:Key="DarkSuccessColor">#FF2DB84d</Color>
    <Color x:Key="MouseEnterColor">#FFDCDCDC</Color>
    <Color x:Key="DarkMouseEnterColor">#FFDCDCDC</Color>
    <Color x:Key="MousePressedColor">#FFD3D3D3</Color>
    <Color x:Key="DarkMousePressedColor">#FFD3D3D3</Color>
    <Color x:Key="WindowClosedMouseEnterColor">#FFF07C82</Color>
    <LinearGradientBrush x:Key="PrimaryBrush" EndPoint="1,0" StartPoint="0,0">
        <GradientStop Color="{DynamicResource PrimaryColor}" Offset="0"/>
        <GradientStop Color="{DynamicResource DarkPrimaryColor}" Offset="1"/>
    </LinearGradientBrush>
    <SolidColorBrush x:Key="DarkPrimaryBrush" Color="{DynamicResource DarkPrimaryColor}"/>
    <LinearGradientBrush x:Key="AccentBrush" EndPoint="1,0" StartPoint="0,0">
        <GradientStop Color="{DynamicResource AccentColor}" Offset="0"/>
        <GradientStop Color="{DynamicResource DarkAccentColor}" Offset="1"/>
    </LinearGradientBrush>
    <SolidColorBrush x:Key="DarkAccentBrush" Color="{DynamicResource DarkAccentColor}"/>
    <LinearGradientBrush x:Key="DangerBrush" EndPoint="1,0" StartPoint="0,0">
        <GradientStop Color="{DynamicResource DangerColor}" Offset="0"/>
        <GradientStop Color="{DynamicResource DarkDangerColor}" Offset="1"/>
    </LinearGradientBrush>
    <SolidColorBrush x:Key="DarkDangerBrush" Color="{DynamicResource DarkDangerColor}"/>
    <LinearGradientBrush x:Key="ErrorBrush" EndPoint="1,0" StartPoint="0,0">
        <GradientStop Color="{DynamicResource ErrorColor}" Offset="0"/>
        <GradientStop Color="{DynamicResource DarkErrorColor}" Offset="1"/>
    </LinearGradientBrush>
    <SolidColorBrush x:Key="DarkErrorBrush" Color="{DynamicResource DarkErrorColor}"/>
    <LinearGradientBrush x:Key="WarningBrush" EndPoint="1,0" StartPoint="0,0">
        <GradientStop Color="{DynamicResource WarningColor}" Offset="0"/>
        <GradientStop Color="{DynamicResource DarkWarningColor}" Offset="1"/>
    </LinearGradientBrush>
    <SolidColorBrush x:Key="DarkWarningBrush" Color="{DynamicResource DarkWarningColor}"/>
    <LinearGradientBrush x:Key="InfoBrush" EndPoint="1,0" StartPoint="0,0">
        <GradientStop Color="{DynamicResource InfoColor}" Offset="0"/>
        <GradientStop Color="{DynamicResource DarkInfoColor}" Offset="1"/>
    </LinearGradientBrush>
    <SolidColorBrush x:Key="DarkInfoBrush" Color="{DynamicResource DarkInfoColor}"/>
    <LinearGradientBrush x:Key="SuccessBrush" EndPoint="1,0" StartPoint="0,0">
        <GradientStop Color="{DynamicResource SuccessColor}" Offset="0"/>
        <GradientStop Color="{DynamicResource DarkSuccessColor}" Offset="1"/>
    </LinearGradientBrush>
    <SolidColorBrush x:Key="DarkSuccessBrush" Color="{DynamicResource DarkSuccessColor}"/>
    <LinearGradientBrush x:Key="MouseEnterBrush" EndPoint="1,0" StartPoint="0,0">
        <GradientStop Color="{DynamicResource MouseEnterColor}" Offset="0"/>
        <GradientStop Color="{DynamicResource DarkMouseEnterColor}" Offset="1"/>
    </LinearGradientBrush>
    <SolidColorBrush x:Key="DarkMouseEnterBrush" Color="{DynamicResource DarkMouseEnterColor}"/>
    <LinearGradientBrush x:Key="MousePressedBrush" EndPoint="1,0" StartPoint="0,0">
        <GradientStop Color="{DynamicResource MousePressedColor}" Offset="0"/>
        <GradientStop Color="{DynamicResource DarkMousePressedColor}" Offset="1"/>
    </LinearGradientBrush>
    <SolidColorBrush x:Key="DarkMousePressedBrush" Color="{DynamicResource DarkMousePressedColor}"/>
</ResourceDictionary>

在 App.xaml 里添加对资源的引用

<Application x:Class="GeometryX.App"
             xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
             StartupUri="MainWindow.xaml">
    <Application.Resources>
        <ResourceDictionary>
            <!-- 主题样式 -->
            <ResourceDictionary.MergedDictionaries>
                <ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/SkinDefault.xaml"/>
                <ResourceDictionary Source="pack://application:,,,/HandyControl;component/Themes/Theme.xaml"/>
                <ResourceDictionary Source="/Resources/Geometry.xaml"/>
                <ResourceDictionary Source="/Resources/Brush.xaml"/>
                <ResourceDictionary Source="/Resources/Style.xaml"/>
            </ResourceDictionary.MergedDictionaries>
        </ResourceDictionary>
    </Application.Resources>
</Application>

置顶按钮是一个 ToggleButton,样式如下

<Style x:Key="MyToggleButtonIconFont" BasedOn="{StaticResource ToggleButtonBaseStyle}" TargetType="ToggleButton">
	<Setter Property="Background" Value="Transparent"/>
	<Setter Property="BorderThickness" Value="0"/>
	<Setter Property="Foreground" Value="{DynamicResource PrimaryTextBrush}"/>
	<Setter Property="hc:BorderElement.CornerRadius" Value="0"/>
	<Setter Property="Cursor" Value="Hand"/>
	<Setter Property="Template">
		<Setter.Value>
			<ControlTemplate TargetType="ToggleButton">
				<Border CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
					<ContentControl HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
							<Path x:Name="MyPath" Fill="{TemplateBinding Foreground}" SnapsToDevicePixels="True" Stretch="Uniform" Width="{TemplateBinding hc:IconElement.Width}" 
								  Height="{TemplateBinding hc:IconElement.Height}" Data="{TemplateBinding hc:IconElement.Geometry}"/>
						</Grid>
					</ContentControl>
				</Border>
				<ControlTemplate.Triggers>
					<!-- 鼠标停留时 -->
					<Trigger Property="IsMouseOver" Value="True">
						<Setter Property="Background" Value="Gainsboro"/>
					</Trigger>
					<!-- 选中时 -->
					<Trigger Property="IsChecked" Value="True">
						<Setter TargetName="MyPath" Property="Fill" Value="{DynamicResource PrimaryBrush}"/>
						<Setter Property="Background" Value="Gainsboro"/>
					</Trigger>
					<!-- 鼠标按下时 -->
					<Trigger Property="IsPressed" Value="True">
						<Setter Property="Background" Value="LightGray"/>
					</Trigger>
				</ControlTemplate.Triggers>
			</ControlTemplate>
		</Setter.Value>
	</Setter>
</Style>

最小化,最大化、向下还原,关闭按钮样式

关闭按钮在鼠标停留时背景色为红色(和 Windows 类似),使用时修改引用的资源颜色值即可

<Style x:Key="MyWindowButton" BasedOn="{StaticResource ButtonBaseStyle}" TargetType="Button">
    <Setter Property="Background" Value="Transparent"/>
    <Setter Property="BorderThickness" Value="0"/>
    <Setter Property="Foreground" Value="{DynamicResource PrimaryTextBrush}"/>
    <Setter Property="hc:BorderElement.CornerRadius" Value="0"/>
    <Setter Property="Cursor" Value="Hand"/>
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="Button">
                <Border CornerRadius="{Binding Path=(hc:BorderElement.CornerRadius),RelativeSource={RelativeSource TemplatedParent}}" Background="{TemplateBinding Background}" BorderThickness="{TemplateBinding BorderThickness}" BorderBrush="{TemplateBinding BorderBrush}">
                    <ContentControl HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" Margin="{TemplateBinding Padding}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}">
                            <Path x:Name="MyPath" Fill="{TemplateBinding Foreground}" SnapsToDevicePixels="True" Stretch="Uniform" Width="{TemplateBinding hc:IconElement.Width}" 
                                  Height="{TemplateBinding hc:IconElement.Height}" Data="{TemplateBinding hc:IconElement.Geometry}"/>
                        </Grid>
                    </ContentControl>
                </Border>
                <ControlTemplate.Triggers>
                    <!-- 鼠标停留时 -->
                    <Trigger Property="IsMouseOver" Value="True">
                        <Setter Property="Background" Value="{DynamicResource MouseEnterBrush}"/>
                    </Trigger>
                    <!-- 鼠标按下时 -->
                    <Trigger Property="IsPressed" Value="True">
                        <Setter Property="Background" Value="{DynamicResource MousePressedBrush}"/>
                    </Trigger>
                </ControlTemplate.Triggers>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

最大化后窗体尺寸覆盖任务栏问题

设置 WindowState = WindowState.Maximized 有一个问题就是,最大化后会覆盖任务栏

只需要设置最大高度和最大宽度即可

窗体拖 拽问题

在标题栏的 MouseMove 事件里设置窗体拖拽

private void Move_MouseMove(object sender, System.Windows.Input.MouseEventArgs e)
    if (e.LeftButton == System.Windows.Input.MouseButtonState.Pressed)
        DragMove();
        // 拖动后窗体变成默认的
        WindowState = WindowState.Normal;
        MyResizeIcon.Data = (Geometry)FindResource("IconWindowMax");
        MyButtonResize.ToolTip = "最大化";

标题栏双 击还原、最大化问题

在标题栏的 MouseDown 事件里判断

private void Grid_MouseDown(object sender, System.Windows.Input.MouseButtonEventArgs e)