自定义WPF 窗口样式

简介: 原文:自定义WPF 窗口样式 自定义 Window 在客户端程序中,经常需要用到自定义一个 Window ,大部分是为了好看吧。
原文: 自定义WPF 窗口样式

自定义 Window

在客户端程序中,经常需要用到自定义一个 Window ,大部分是为了好看吧。做了很多研究和实践之后,觉得需要把这个过程写下来,以供查阅。

WPF 提供的丰富的功能使得自定义 Window 变得简单,但是也不是一个简单的 Style 就能做到的事情。虽然 WPF 中的控件是 Lookless 的,但是 Window 类有他自己的特殊之处,做个简单的实验就能看出,对于普通的 WPF 控件,用 XamlWriter.Write 方法就能将某个类型对象的模板输出出来,这样就可以看到该控件的内部构造。但是如果输出 Window 对象的默认模板,就会发现模板非常简单,其中并没有包含标题栏以及最大化最小化按钮的定义,具体的实现不得而知,但是至少说明 Window 的默认 Style 不是按照 WPF 的规范来实现的。

 

为了实现任意风格的 Window 就需要重写 Window 的默认模板,第一步要做的就是创建 Window 的一个派生类,并创建自定义 Style ,然后重写 DefaultStyleKey 属性让 WPF 引擎来将样式和 Window 的派生类装载到一起。具体做法如下:

1.         创建一个 WPF Application 项目

2.         在项目中添加 Themes 文件夹,并在该文件夹下添加名称为 Generic.xaml ResourceDictionary 。该文件夹和文件的名称和位置都是固定的,也就是说必须这么做才能让 WPF 引擎让自定义控件和默认 Style 协同工作,因为微软对此进行了硬编码。

3.         创建自定义窗口类为 HeaderedWindow ,为什么定义为 HeaderedWindow 呢?因为我觉得 Window 对象更像是一个 HeaderedContentControl 而不是一个 ContentControl ,因为它的标题栏更像是一个 Header ,而不仅仅是为了显示图标和标题! HeaderedWindow 类的定义如下:

 

public class HeaderedWindow : Window

{

static HeaderedWindow()

    {

      DefaultStyleKeyProperty.OverrideMetadata(typeof (HeaderedWindow ), new FrameworkPropertyMetadata (typeof (HeaderedWindow )));     

    }

}

 

4.         Generic.xaml 文件中添加 HeaderedWindow 的默认样式,如下:

< Style TargetType ="{ x : Type l : HeaderedWindow }">

    < Setter Property ="WindowStyle" Value ="None"/>

    < Setter Property ="ResizeMode" Value ="NoResize"/>

    < Setter Property ="Background" Value ="Gray"/>

    < Setter Property ="BorderBrush" Value ="#FF5A3D1C"/>

    < Setter Property ="BorderThickness" Value ="1"/>

    < Setter Property ="MinWidth" Value ="90"/>

    < Setter Property ="MinHeight" Value ="33"/>

    < Setter Property ="VerticalContentAlignment" Value ="Stretch"/>

    < Setter Property ="HorizontalContentAlignment" Value ="Stretch"/>

  </ Style >

最重要的两个属性是 WindowStyle ResizeMode ,将 WindowStyle 属性设置为 None 将会去除默认的标题栏,将 ResizeMode 设置为 NoResize 将会去除 Window 边框,该边框在 Window 7 比较难看,不过却有他的作用, 就是用来调整窗口大小,我们把它去掉了,就意味着我们需要自己来编写调整窗口大小的代码了。

5.         测试一下这个 HeaderedWindow 吧,编写如下测试代码:

 

< local : HeaderedWindow x : Class ="CustomWindowDemo.MainWindow"

         xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

         xmlns : x ="http://schemas.microsoft.com/winfx/2006/xaml"

         xmlns : local ="clr-namespace:CustomWindowDemo"

         Title ="MainWindow" Height ="350" Width ="525">

    < Grid >

       

    </ Grid >

</ local : HeaderedWindow >

后置代码类必须要从 HeaderedWindow 继承才行。运行程序发现窗口黑乎乎一片。这是因为我们还没有重写 Window ControlTemplate 的缘故。

6.         一个窗口应该有如下基本要素

a)         图标,就是 Icon 属性,自定义窗口应该有将其显示出来的能力

b)         标题,就是 Title 属性,自定窗口应该将其显示在标题栏中

c)         最大化,最小化,关闭按钮

d)         为了美观, Window 的边框应该定义出来。但是当窗口最大化的时候应该隐藏边框,扩大窗口的使用面积,这也是 Windows 下窗口的标准行为。

e)         八个方向的 Resizer 应该定义出来,这些 Resize 其实就是一些透明的 Thumb 控件,用来支持拖动事件,并改变窗口的定位和高宽。

对于 HeaderedWindow 还有几个扩展要素需要提供:

a)         自定义 Header 属性,用户可以重新定义 Window Header ,在这里我们将 Window Icon Title 定义为他的默认 Header ,所以有一个 ShowDefaultHeader 属性用来控制默认 Header 的可见性。 Header 属性是一个 Object 对象,其用法和 HeaderedContentControl 控件的 Header 几乎一模一样。

b)         ShowResizeGrip 属性,用来控制是否在右下角显示一个抓手模样的图形,用来指示用此处可以拖动,当然不显示也可以调整窗口大小,不过显示这个抓手增加了易用性。

c)         CanResize 属性,由于我们在样式中将窗口设置 ResizeMode NoResize ,所以需要提供一个额外的属性用来设置当前窗口是否可以调整大小。当 CanResize False 的时候,所有可以调整窗口大小的控件将被隐藏。

d)         最后一个比较有意思的属性就是 IsFullScreenMaximize ,当该属性设置为 True 的时候,那么当最大化的时候,窗口将把任务栏覆盖住。是真正意义上的“最大化”了。

7.           如果想制作不规则形状的窗口,需要将 AllowTransparency 属性设置为 true ,不过当此属性为 true 的时候,该 WPF 窗口将不能作为 WinForm 控件或者 ActiveX 控件的宿主了。所以需要慎重考虑。

最后附上全部代码以及使用方式,以供查阅!

Generic.xaml 的内容:

< ResourceDictionary xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

     xmlns : x ="http://schemas.microsoft.com/winfx/2006/xaml"

     xmlns : sys ="clr-namespace:System;assembly=mscorlib"

     xmlns : l ="clr-namespace:CustomWindowDemo">

 

    < l : BoolToVisibilityConverter x : Key ="BoolToVisibilityConverter"></ l : BoolToVisibilityConverter >

 

    < Style x : Key ="styleWindowButtonMinimize" BasedOn ="{ x : Null }" TargetType ="{ x : Type Button }">

        < Setter Property ="Template">

            < Setter.Value >

                < ControlTemplate TargetType ="{ x : Type Button }">

                    < Grid x : Name ="buttonClose">

                        < Ellipse Stroke ="{ x : Null }" StrokeThickness ="1" x : Name ="btnEllipse" >

                            < Ellipse.Fill >

                                < RadialGradientBrush >

                                    < GradientStop Color ="#BFABA7A4" Offset ="0.777"/>

                                     < GradientStop Color ="#FF897F77" Offset ="1"/>

                                </ RadialGradientBrush >

                            </ Ellipse.Fill >

                        </ Ellipse >

 

                        < Path x : Name ="iconMin" Width ="11.1641" Height ="2.06641" Canvas.Left ="3.97266" Canvas.Top ="8.51962" Stretch ="Fill" Fill ="#FFFFFFFF" Data ="F1 M 5.00659,8.51962C 4.43613,8.51831 3.97266,8.98184 3.97266,9.55347C 3.97266,10.1238 4.435,10.586 5.00659,10.586L 14.1028,10.586C 14.6733,10.586 15.1353,10.1238 15.1367,9.55215C 15.1367,8.98056 14.6733,8.51962 14.1028,8.51962L 5.00659,8.51962 Z "/>

                         < ContentPresenter SnapsToDevicePixels ="{ TemplateBinding SnapsToDevicePixels }" HorizontalAlignment ="{ TemplateBinding HorizontalContentAlignment }" VerticalAlignment ="{ TemplateBinding VerticalContentAlignment }" RecognizesAccessKey ="True"/>

                    </ Grid >

                    < ControlTemplate.Triggers >

                        < Trigger Property ="IsFocused" Value ="True"/>

                        < Trigger Property ="IsDefaulted" Value ="True"/>

                        < Trigger Property ="IsMouseOver" Value ="True">

                            < Setter Property ="Fill" Value ="#FF5D4E3E" TargetName ="btnEllipse"/>

                        </ Trigger >

                        < Trigger Property ="IsPressed" Value ="True"/>

                        < Trigger Property ="IsEnabled" Value ="False"/>

                    </ ControlTemplate.Triggers >

                </ ControlTemplate >

            </ Setter.Value >

        </ Setter >

    </ Style >

 

    < Style x : Key ="styleWindowButtonMaximize" BasedOn ="{ x : Null }" TargetType ="{ x : Type ToggleButton }">

        < Setter Property ="Template">

            < Setter.Value >

                < ControlTemplate TargetType ="{ x : Type ToggleButton }">

                    < Grid >

                        < Ellipse x : Name ="btnEllipse" Stroke ="{ x : Null }" StrokeThickness ="1">

                            < Ellipse.Fill >

                                < RadialGradientBrush >

                                    < GradientStop Color ="#BFABA7A4" Offset ="0.777"/>

                                    < GradientStop Color ="#FF897F77" Offset ="1"/>

                                </ RadialGradientBrush >

                            </ Ellipse.Fill >

                        </ Ellipse >

 

                        < Path x : Name ="iconMax" Width ="12.4011" Height ="9.93359" Stretch ="Fill" Fill ="#FFFFFFFF" Data ="F1 M 390.641,289.034C 389.073,289.034 387.8,290.308 387.8,291.875L 387.8,296.125C 387.8,297.692 389.073,298.967 390.641,298.967L 397.362,298.967C 398.927,298.967 400.201,297.692 400.201,296.125L 400.201,291.875C 400.201,290.308 398.927,289.034 397.362,289.034L 390.641,289.034 Z M 389.399,296.125L 389.399,291.875C 389.399,291.19 389.956,290.634 390.641,290.634L 397.362,290.634C 398.045,290.634 398.602,291.19 398.602,291.875L 398.602,296.125C 398.602,296.81 398.045,297.367 397.362,297.367L 390.641,297.367C 389.956,297.367 389.399,296.81 389.399,296.125 Z "/>

 

                        < Path x : Name ="iconRestore" Visibility ="Collapsed" Width ="11.6719" Height ="9.48242" Stretch ="Fill" Fill ="#FFFFFFFF" Data ="F1 M 411.826,302.421C 411.802,301.775 411.28,301.259 410.629,301.259L 404.483,301.259C 403.836,301.257 403.312,301.775 403.286,302.421L 403.277,302.421C 403.277,302.421 403.277,303.254 403.277,304.177L 407.126,304.177C 407.984,304.177 408.679,304.853 408.722,305.728L 408.722,307.822L 410.629,307.822C 411.294,307.822 411.834,307.283 411.836,306.615C 411.836,306.507 411.836,302.421 411.836,302.421L 411.826,302.421 Z M 407.126,304.956L 400.979,304.956C 400.545,304.955 400.19,305.301 400.164,305.728L 400.164,309.925C 400.164,310.374 400.531,310.741 400.981,310.741L 407.126,310.741C 407.575,310.741 407.942,310.374 407.944,309.923L 407.934,305.742C 407.918,305.301 407.565,304.956 407.126,304.956 Z "/>

 

                        < ContentPresenter SnapsToDevicePixels ="{ TemplateBinding SnapsToDevicePixels }" HorizontalAlignment ="{ TemplateBinding HorizontalContentAlignment }" VerticalAlignment ="{ TemplateBinding VerticalContentAlignment }" RecognizesAccessKey ="True"/>

                    </ Grid >

                    < ControlTemplate.Triggers >

                        < Trigger Property ="IsFocused" Value ="True"/>

                        < Trigger Property ="IsMouseOver" Value ="True">

                            < Setter Property ="Fill" Value ="#FF6E8F9A" TargetName ="btnEllipse"/>

                         </ Trigger >

                        < Trigger Property ="IsPressed" Value ="True"/>

                        < Trigger Property ="IsEnabled" Value ="False"/>

                        < Trigger Property ="IsChecked" Value ="True">

                            < Setter TargetName ="iconMax" Property ="Visibility" Value ="Collapsed" />

                            < Setter TargetName ="iconRestore" Property ="Visibility" Value ="Visible" />

                        </ Trigger >

                    </ ControlTemplate.Triggers >

                </ ControlTemplate >

            </ Setter.Value >

        </ Setter >

    </ Style >

 

    < Style x : Key ="styleMainWindowButtonClose" BasedOn ="{ x : Null }" TargetType ="{ x : Type Button }">

        < Setter Property ="Template">

            < Setter.Value >

                < ControlTemplate TargetType ="{ x : Type Button }">

                    < Grid x : Name ="buttonClose">

                        < Ellipse Stroke ="{ x : Null }" StrokeThickness ="1" x : Name ="btnEllipse" >

                            < Ellipse.Fill >

                                 < RadialGradientBrush >

                                    < GradientStop Color ="#BFABA7A4" Offset ="0.777"/>

                                    < GradientStop Color ="#FF897F77" Offset ="1"/>

                                </ RadialGradientBrush >

                            </ Ellipse.Fill >

                        </ Ellipse >

 

                        < Path x : Name ="iconX" Width ="8.50003" Height ="8.50006" Stretch ="Fill" Fill ="#FF8D1200" Data ="F1 M 401.281,299.818L 398.795,297.333L 401.281,294.847C 401.684,294.445 401.684,293.79 401.281,293.386C 400.877,292.982 400.222,292.982 399.82,293.386L 397.334,295.872L 394.848,293.386C 394.445,292.982 393.79,292.982 393.386,293.386C 392.982,293.79 392.982,294.445 393.386,294.848L 395.872,297.334L 393.387,299.818C 392.984,300.222 392.982,300.876 393.387,301.281C 393.791,301.684 394.445,301.683 394.848,301.279L 397.333,298.795L 399.818,301.281C 400.222,301.684 400.877,301.684 401.281,301.281C 401.684,300.877 401.684,300.222 401.281,299.818 Z "/>

                        < ContentPresenter SnapsToDevicePixels ="{ TemplateBinding SnapsToDevicePixels }" HorizontalAlignment ="{ TemplateBinding HorizontalContentAlignment }" VerticalAlignment ="{ TemplateBinding VerticalContentAlignment }" RecognizesAccessKey ="True"/>

                    </ Grid >

                    < ControlTemplate.Triggers >

                        < Trigger Property ="IsFocused" Value ="True"/>

                        < Trigger Property ="IsDefaulted" Value ="True"/>

                        < Trigger Property ="IsMouseOver" Value ="True">

                            < Setter Property ="Fill" Value ="#FF333332" TargetName ="btnEllipse"/>

                            < Setter Property ="Fill" Value ="#FFFFFFFF" TargetName ="iconX"/>

                        </ Trigger >

                        < Trigger Property ="IsPressed" Value ="True"/>

                        < Trigger Property ="IsEnabled" Value ="False"/>

                    </ ControlTemplate.Triggers >

                </ ControlTemplate >

            </ Setter.Value >

        </ Setter >

    </ Style >

 

    <!--Window Style-->

  < Style TargetType ="{ x : Type l : HeaderedWindow }">

    < Setter Property ="WindowStyle" Value ="None"/>

    < Setter Property ="ResizeMode" Value ="NoResize"/>

        < Setter Property ="Background">

            < Setter.Value >

                < LinearGradientBrush MappingMode ="Absolute" StartPoint ="327.657227,-92.486328" EndPoint ="495.033722,694.958313">

                    < LinearGradientBrush.Transform >

                        < MatrixTransform Matrix ="2.000000,0.000000,-0.000000,-1.000000,-4.095703,626.486816" />

                    </ LinearGradientBrush.Transform >

                    < GradientStop Offset ="0.743034" Color ="LightPink" />

                    < GradientStop Offset ="0.835913" Color ="Red" />

                    < GradientStop Offset ="0.840783" Color ="DarkRed" />

                    < GradientStop Offset ="0.842105" Color ="Red" />

                    < GradientStop Offset ="0.990868" Color ="LightPink" />

                </ LinearGradientBrush >

             </ Setter.Value >

        </ Setter >

        < Setter Property ="BorderBrush" Value ="#FF5A3D1C"/>

    < Setter Property ="BorderThickness" Value ="1"/>

    < Setter Property ="MinWidth" Value ="90"/>

    < Setter Property ="MinHeight" Value ="33"/>

    < Setter Property ="VerticalContentAlignment" Value ="Stretch"/>

    < Setter Property ="HorizontalContentAlignment" Value ="Stretch"/>

        < Setter Property ="Template">

            < Setter.Value >

                < ControlTemplate TargetType ="{ x : Type l : HeaderedWindow }">

                    < Border BorderBrush ="{ TemplateBinding BorderBrush }"

                   BorderThickness ="{ TemplateBinding BorderThickness }">

 

                        < Grid SnapsToDevicePixels ="True" Background ="{ TemplateBinding Background }">

                             < Grid.RowDefinitions >

                                < RowDefinition Height ="33" />

                                < RowDefinition />

                            </ Grid.RowDefinitions >

 

                            <!-- Window header-->

                            < Grid Name ="PART_HeaderContainer" Background ="Transparent">

 

                                < StackPanel Orientation ="Horizontal" Margin ="4,0" Visibility ="{ Binding RelativeSource ={ x : Static RelativeSource .TemplatedParent}, Path =ShowDefaultHeader, Converter ={ StaticResource BoolToVisibilityConverter }}">

                                    < Image Height ="24" Width ="24" HorizontalAlignment ="Left" VerticalAlignment ="Center" Source ="{ TemplateBinding Icon }"/>

 

                                     < TextBlock Text ="{ TemplateBinding Title }"

                          Margin ="4,0"                              

                          VerticalAlignment ="Center"

                          FontFamily ="Arial"

                          FontSize ="15"

                          Foreground ="#FFFFFFFF"

                          TextWrapping ="NoWrap"/>

 

                                </ StackPanel >

 

                                < StackPanel HorizontalAlignment ="Right" Orientation ="Horizontal" Margin ="0,0,10,0">

                                    < Button x : Name ="PART_MinimizeButton" IsTabStop ="False" Cursor ="Hand" Style ="{ StaticResource styleWindowButtonMinimize }" Width ="19" Height ="19" Margin ="0,0,4,0" ToolTip ="Minimize"

                           Visibility ="{ Binding RelativeSource ={ x : Static RelativeSource .TemplatedParent}, Path =CanResize, Converter ={ StaticResource BoolToVisibilityConverter }}"/>

                                    < ToggleButton x : Name ="PART_RestoreButton" IsTabStop ="False" Cursor ="Hand" Style ="{ StaticResource styleWindowButtonMaximize }" Width ="19" Height ="19" Margin ="0,0,4,0" ToolTip ="Maximize"

                           Visibility ="{ Binding RelativeSource ={ x : Static RelativeSource .TemplatedParent}, Path =CanResize, Converter ={ StaticResource BoolToVisibilityConverter }}"/>

                                    < Button x : Name ="PART_CloseButton" IsTabStop ="False" Cursor ="Hand" Style ="{ StaticResource styleMainWindowButtonClose }" Width ="19" Height ="19" Margin ="0,0,0,0" ToolTip ="Close"/>

                                 </ StackPanel >

 

                                < ContentPresenter ContentSource ="Header" VerticalAlignment ="Stretch" HorizontalAlignment ="Stretch"/>

                            </ Grid >

 

                            < Grid Grid.RowSpan ="2" x : Name ="PART_ResizerContainers" Visibility ="Hidden">

                                < Grid.Resources >

                                    < sys : Double x : Key ="StraightResizerSize"> 8 </ sys : Double >

                                    < sys : Double x : Key ="SlantResizerSize"> 16 </ sys : Double >

                                    < Style TargetType ="{ x : Type Thumb }">

                                        < Setter Property ="Template">

                                            < Setter.Value >

                                                < ControlTemplate >

                                                    < Rectangle Fill ="Transparent"/>

                                                </ ControlTemplate >

                                             </ Setter.Value >

                                        </ Setter >

                                    </ Style >

                                </ Grid.Resources >

                                < Thumb Width ="{ StaticResource StraightResizerSize }" VerticalAlignment ="Stretch" HorizontalAlignment ="Left" Cursor ="SizeWE" x : Name ="PART_LeftResizer"/>

                                < Thumb Height ="{ StaticResource StraightResizerSize }" VerticalAlignment ="Top" HorizontalAlignment ="Stretch" Cursor ="SizeNS" x : Name ="PART_TopResizer"/>

                                < Thumb Width ="{ StaticResource StraightResizerSize }" VerticalAlignment ="Stretch" HorizontalAlignment ="Right" Cursor ="SizeWE" x : Name ="PART_RightResizer"/>

                                < Thumb Height ="{ StaticResource StraightResizerSize }" VerticalAlignment ="Bottom" HorizontalAlignment ="Stretch" Cursor ="SizeNS" x : Name ="PART_BottomResizer"/>

 

                                < ResizeGrip Width ="{ StaticResource SlantResizerSize }" Height ="{ StaticResource SlantResizerSize }" HorizontalAlignment ="Right" VerticalAlignment ="Bottom" Visibility ="{ Binding RelativeSource ={ x : Static RelativeSource .TemplatedParent}, Path =ShowResizeGrip, Converter ={ StaticResource BoolToVisibilityConverter }}"/>

                                < Thumb Width ="{ StaticResource SlantResizerSize }" Height ="{ StaticResource SlantResizerSize }" HorizontalAlignment ="Right" VerticalAlignment ="Bottom" Cursor ="SizeNWSE" x : Name ="PART_BottomRightResizer"/>

                                < Thumb Width ="{ StaticResource SlantResizerSize }" Height ="{ StaticResource SlantResizerSize }" HorizontalAlignment ="Right" VerticalAlignment ="Top" Cursor ="SizeNESW" x : Name ="PART_TopRightResizer"/>

                                < Thumb Width ="{ StaticResource SlantResizerSize }" Height ="{ StaticResource SlantResizerSize }" HorizontalAlignment ="Left" VerticalAlignment ="Top" Cursor ="SizeNWSE" x : Name ="PART_TopLeftResizer"/>

                                < Thumb Width ="{ StaticResource SlantResizerSize }" Height ="{ StaticResource SlantResizerSize }" HorizontalAlignment ="Left" VerticalAlignment ="Bottom" Cursor ="SizeNESW" x : Name ="PART_BottomLeftResizer"/>

                            </ Grid >

 

                            < Border x : Name ="PART_ContentBorder" Grid.Row ="1"

                     BorderBrush ="#FF5A3D1C"

                     BorderThickness ="2"

                     Margin ="8,0,8,8"

                     ClipToBounds ="True">

                                < AdornerDecorator >

                                    < ContentPresenter Margin ="{ TemplateBinding Padding }"

                                   VerticalAlignment ="{ TemplateBinding VerticalContentAlignment }"

                                   HorizontalAlignment ="{ TemplateBinding HorizontalContentAlignment }"/>

                                 </ AdornerDecorator >

                            </ Border >

                        </ Grid >

                    </ Border >

                    < ControlTemplate.Triggers >

                        < MultiTrigger >

                            < MultiTrigger.Conditions >

                                < Condition Property ="CanResize" Value ="True"/>

                                < Condition Property ="WindowState" Value ="Normal"/>

                            </ MultiTrigger.Conditions >

                             < Setter TargetName ="PART_ResizerContainers" Property ="Visibility" Value ="Visible"/>

                        </ MultiTrigger >

                        < Trigger Property ="WindowState" Value ="Maximized">

                            < Setter TargetName ="PART_ContentBorder" Property ="Margin" Value ="0"/>

                            < Setter TargetName ="PART_ContentBorder" Property ="BorderThickness" Value ="0,2,0,0"/>

                        </ Trigger >

                    </ ControlTemplate.Triggers >

                </ ControlTemplate >

            </ Setter.Value >

        </ Setter >

    </ Style >

</ ResourceDictionary >

以上样式中使用了一个Converter类,代码如下:

namespace CustomWindowDemo

{

  public class BoolToVisibilityConverter : IValueConverter

  {

    #region IValueConverter Members

 

    public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

    {

      return (bool )value ? Visibility .Visible : Visibility .Hidden;

    }

 

    public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)

     {

      throw new NotImplementedException ();

    }

 

    #endregion

  }

}

 

HeaderedWindow 的代码:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Windows;

using System.Windows.Input;

using System.Windows.Markup;

using System.Reflection;

using System.Windows.Media.Animation;

using System.Windows.Controls;

using System.Windows.Controls.Primitives;

using System.Diagnostics;

 

namespace CustomWindowDemo

{

  [TemplatePart (Name = HeaderContainerName, Type = typeof (FrameworkElement ))]

  [TemplatePart (Name = MinimizeButtonName, Type = typeof (Button ))]

  [TemplatePart (Name = RestoreButtonName, Type = typeof (ToggleButton ))]

  [TemplatePart (Name = CloseButtonName, Type = typeof (Button ))]

  [TemplatePart (Name = TopResizerName, Type = typeof (Thumb ))]

  [TemplatePart (Name = LeftResizerName, Type = typeof (Thumb ))]

  [TemplatePart (Name = RightResizerName, Type = typeof (Thumb ))]

  [TemplatePart (Name = BottomResizerName, Type = typeof (Thumb ))]

  [TemplatePart (Name = BottomRightResizerName, Type = typeof (Thumb ))]

  [TemplatePart (Name = TopRightResizerName, Type = typeof (Thumb ))]

  [TemplatePart (Name = TopLeftResizerName, Type = typeof (Thumb ))]

  [TemplatePart (Name = BottomLeftResizerName, Type = typeof (Thumb ))]

  public class HeaderedWindow : Window

  {

    #region Template Part Name

 

    private const string HeaderContainerName = "PART_HeaderContainer" ;

    private const string MinimizeButtonName = "PART_MinimizeButton" ;

    private const string RestoreButtonName = "PART_RestoreButton" ;

    private const string CloseButtonName = "PART_CloseButton" ;

    private const string TopResizerName = "PART_TopResizer" ;

    private const string LeftResizerName = "PART_LeftResizer" ;

    private const string RightResizerName = "PART_RightResizer" ;

    private const string BottomResizerName = "PART_BottomResizer" ;

    private const string BottomRightResizerName = "PART_BottomRightResizer" ;

    private const string TopRightResizerName = "PART_TopRightResizer" ;

    private const string TopLeftResizerName = "PART_TopLeftResizer" ;

    private const string BottomLeftResizerName = "PART_BottomLeftResizer" ;

 

    #endregion

 

    #region Dependency Properties

 

    public static readonly DependencyProperty ShowDefaultHeaderProperty =

        DependencyProperty .Register("ShowDefaultHeader" , typeof (bool ), typeof (HeaderedWindow ), new FrameworkPropertyMetadata (true ));

 

    public static readonly DependencyProperty ShowResizeGripProperty =

        DependencyProperty .Register("ShowResizeGrip" , typeof (bool ), typeof (HeaderedWindow ), new FrameworkPropertyMetadata (false ));

 

    public static readonly DependencyProperty CanResizeProperty =

        DependencyProperty .Register("CanResize" , typeof (bool ), typeof (HeaderedWindow ), new FrameworkPropertyMetadata (true ));

 

    public static readonly DependencyProperty HeaderProperty =

        DependencyProperty .Register("Header" , typeof (object ), typeof (HeaderedWindow ), new FrameworkPropertyMetadata (null , OnHeaderChanged));

 

    public static readonly DependencyProperty HeaderTemplateProperty =

        DependencyProperty .Register("HeaderTemplate" , typeof (DataTemplate ), typeof (HeaderedWindow ), new FrameworkPropertyMetadata (null ));

 

    public static readonly DependencyProperty HeaderTempateSelectorProperty =

        DependencyProperty .Register("HeaderTempateSelector" , typeof (DataTemplateSelector ), typeof (HeaderedWindow ), new FrameworkPropertyMetadata (null ));

 

    public static readonly DependencyProperty IsFullScreenMaximizeProperty =

        DependencyProperty .Register("IsFullScreenMaximize" , typeof (bool ), typeof (HeaderedWindow ), new FrameworkPropertyMetadata (false ));

 

    private static void OnHeaderChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)

    {

      HeaderedWindow win = sender as HeaderedWindow ;

      win.RemoveLogicalChild(e.OldValue);

      win.AddLogicalChild(e.NewValue);

    }  

 

    public bool ShowDefaultHeader

    {

      get { return (bool )GetValue(ShowDefaultHeaderProperty); }

      set { SetValue(ShowDefaultHeaderProperty, value ); }

    }

 

    public bool CanResize

    {

      get { return (bool )GetValue(CanResizeProperty); }

      set { SetValue(CanResizeProperty, value ); }

    }

 

    public bool ShowResizeGrip

    {

      get { return (bool )GetValue(ShowResizeGripProperty); }

      set { SetValue(ShowResizeGripProperty, value ); }

    }

 

    public object Header

    {

      get { return (object )GetValue(HeaderProperty); }

      set { SetValue(HeaderProperty, value ); }

    }   

 

    public DataTemplate HeaderTemplate

    {

      get { return (DataTemplate )GetValue(HeaderTemplateProperty); }

      set { SetValue(HeaderTemplateProperty, value ); }

    }

 

    public DataTemplateSelector HeaderTempateSelector

    {

      get { return (DataTemplateSelector )GetValue(HeaderTempateSelectorProperty); }

      set { SetValue(HeaderTempateSelectorProperty, value ); }

    }

 

    public bool IsFullScreenMaximize

    {

      get { return (bool )GetValue(IsFullScreenMaximizeProperty); }

      set { SetValue(IsFullScreenMaximizeProperty, value ); }

    }

 

    #endregion

 

    static HeaderedWindow()

    {

      DefaultStyleKeyProperty.OverrideMetadata(typeof (HeaderedWindow ), new FrameworkPropertyMetadata (typeof (HeaderedWindow )));     

    }

  

    #region Private Fields

 

    private FrameworkElement headerContainer;

    private Button minimizeButton;

    private ToggleButton restoreButton;

    private Button closeButton;

    private Thumb topResizer;

    private Thumb leftResizer;

    private Thumb rightResizer;

    private Thumb bottomResizer;

    private Thumb bottomRightResizer;

    private Thumb topRightResizer;

    private Thumb topLeftResizer;

    private Thumb bottomLeftResizer;

 

    #endregion

 

    public override void OnApplyTemplate()

    {

      base .OnApplyTemplate();

 

      headerContainer = GetTemplateChild<FrameworkElement >(HeaderContainerName);

      headerContainer.MouseLeftButtonDown += HeaderContainerMouseLeftButtonDown;     

      closeButton = GetTemplateChild<Button >(CloseButtonName);

      closeButton.Click += delegate { Close(); };

      restoreButton = GetTemplateChild<ToggleButton >(RestoreButtonName);

      restoreButton.Checked += delegate { ChangeWindowState(WindowState .Maximized); };

      restoreButton.Unchecked += delegate { ChangeWindowState(WindowState .Normal); };

      StateChanged += new EventHandler (HeaderedWindowStateChanged);

      minimizeButton = GetTemplateChild<Button >(MinimizeButtonName);

      minimizeButton.Click += delegate { ChangeWindowState(WindowState .Minimized); };

 

      topResizer = GetTemplateChild<Thumb >(TopResizerName);

      topResizer.DragDelta += new DragDeltaEventHandler (ResizeTop);

      leftResizer = GetTemplateChild<Thumb >(LeftResizerName);

      leftResizer.DragDelta += new DragDeltaEventHandler (ResizeLeft);

      rightResizer = GetTemplateChild<Thumb >(RightResizerName);

      rightResizer.DragDelta += new DragDeltaEventHandler (ResizeRight);

      bottomResizer = GetTemplateChild<Thumb >(BottomResizerName);

      bottomResizer.DragDelta += new DragDeltaEventHandler (ResizeBottom);

      bottomRightResizer = GetTemplateChild<Thumb >(BottomRightResizerName);

      bottomRightResizer.DragDelta += new DragDeltaEventHandler (ResizeBottomRight);

      topRightResizer = GetTemplateChild<Thumb >(TopRightResizerName);

      topRightResizer.DragDelta += new DragDeltaEventHandler (ResizeTopRight);

      topLeftResizer = GetTemplateChild<Thumb >(TopLeftResizerName);

      topLeftResizer.DragDelta += new DragDeltaEventHandler (ResizeTopLeft);

      bottomLeftResizer = GetTemplateChild<Thumb >(BottomLeftResizerName);

      bottomLeftResizer.DragDelta += new DragDeltaEventHandler (ResizeBottomLeft);

    }

   

    private T GetTemplateChild<T>(string childName) where T : FrameworkElement , new ()

    {

      return (GetTemplateChild(childName) as T) ?? new T();

    }   

 

    private void HeaderContainerMouseLeftButtonDown(object sender, MouseButtonEventArgs e)

    {

      if (e.ClickCount == 1)

      {

        DragMove();

      }

      else

      {

        ChangeWindowState(WindowState == WindowState .Maximized ? WindowState .Normal : WindowState .Maximized);

      }

    }

 

    private void ChangeWindowState(WindowState state)

    {

      if (state == WindowState .Maximized)

      {

        if (!IsFullScreenMaximize && IsLocationOnPrimaryScreen())

        {

          MaxHeight = SystemParameters .WorkArea.Height;

          MaxWidth = SystemParameters .WorkArea.Width;

        }

        else

        {

          MaxHeight = double .PositiveInfinity;

          MaxWidth = double .PositiveInfinity;

        }

      }

     

      WindowState = state;

    }

   

    private void HeaderedWindowStateChanged(object sender, EventArgs e)

    {

      if (WindowState == WindowState .Minimized)

      {

        restoreButton.IsChecked = null ;

      }

      else

      {

        restoreButton.IsChecked = WindowState == WindowState .Maximized;

      }

    }

 

    private bool IsLocationOnPrimaryScreen()

    {

      return Left < SystemParameters .PrimaryScreenWidth && Top < SystemParameters .PrimaryScreenHeight;

    }

 

    #region Resize

 

    private void ResizeBottomLeft(object sender, DragDeltaEventArgs e)

    {

      ResizeLeft(sender, e);

      ResizeBottom(sender, e);

    }

 

    private void ResizeTopLeft(object sender, DragDeltaEventArgs e)

    {

      ResizeTop(sender, e);

      ResizeLeft(sender, e);

    }

 

    private void ResizeTopRight(object sender, DragDeltaEventArgs e)

    {

      ResizeRight(sender, e);

      ResizeTop(sender, e);

    }

 

    private void ResizeBottomRight(object sender, DragDeltaEventArgs e)

    {

      ResizeBottom(sender, e);

      ResizeRight(sender, e);

    }

 

    private void ResizeBottom(object sender, DragDeltaEventArgs e)

    {

      if (ActualHeight <= MinHeight && e.VerticalChange < 0)

      {

        return ;

      }

 

      if (double .IsNaN(Height))

      {

        Height = ActualHeight;

      }

 

      Height += e.VerticalChange;

    }

 

    private void ResizeRight(object sender, DragDeltaEventArgs e)

    {

      if (ActualWidth <= MinWidth && e.HorizontalChange < 0)

      {

        return ;

      }

 

      if (double .IsNaN(Width))

      {

        Width = ActualWidth;

      }

 

      Width += e.HorizontalChange;

    }

 

    private void ResizeLeft(object sender, DragDeltaEventArgs e)

    {

      if (ActualWidth <= MinWidth && e.HorizontalChange > 0)

      {

        return ;

      }

 

      if (double .IsNaN(Width))

      {

        Width = ActualWidth;

      }

 

      Width -= e.HorizontalChange;

      Left += e.HorizontalChange;

    }

 

    private void ResizeTop(object sender, DragDeltaEventArgs e)

    {

      if (ActualHeight <= MinHeight && e.VerticalChange > 0)

      {

        return ;

      }

 

      if (double .IsNaN(Height))

      {

        Height = ActualHeight;

      }

 

      Height -= e.VerticalChange;

      Top += e.VerticalChange;

    }

 

    #endregion

  }

}

 

使用 HeaderedWindow 的代码:

XAML

< local : HeaderedWindow x : Class ="CustomWindowDemo.MainWindow"

         xmlns ="http://schemas.microsoft.com/winfx/2006/xaml/presentation"

         xmlns : x ="http://schemas.microsoft.com/winfx/2006/xaml"

         xmlns : local ="clr-namespace:CustomWindowDemo"

         Title ="MainWindow" Height ="350" Width ="525">

    < Grid >

       

    </ Grid >

</ local : HeaderedWindow >

.cs

public partial class MainWindow : HeaderedWindow

    {

        public MainWindow()

        {

            InitializeComponent();

        }

    }

最后的 Window 外观如下所示:


 

目录
相关文章
WPF疑难问题之Treeview中HierarchicalDataTemplate多级样式
WPF疑难问题之Treeview中HierarchicalDataTemplate多级样式
348 0
|
3月前
|
开发框架 缓存 前端开发
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(11) -- 下拉列表的数据绑定以及自定义系统字典列表控件
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(11) -- 下拉列表的数据绑定以及自定义系统字典列表控件
|
3月前
|
开发者 C# UED
WPF多窗口应用程序开发秘籍:掌握窗口创建、通信与管理技巧,轻松实现高效多窗口协作!
【8月更文挑战第31天】在WPF应用开发中,多窗口设计能显著提升用户体验与工作效率。本文详述了创建新窗口的多种方法,包括直接实例化`Window`类、利用`Application.Current.MainWindow`及自定义方法。针对窗口间通信,介绍了`Messenger`类、`DataContext`共享及`Application`类的应用。此外,还探讨了布局控件与窗口管理技术,如`StackPanel`与`DockPanel`的使用,并提供了示例代码展示如何结合`Messenger`类实现窗口间的消息传递。总结了多窗口应用的设计要点,为开发者提供了实用指南。
221 0
|
3月前
|
开发者 C# 存储
WPF开发者必读:资源字典应用秘籍,轻松实现样式与模板共享,让你的WPF应用更上一层楼!
【8月更文挑战第31天】在WPF开发中,资源字典是一种强大的工具,用于共享样式、模板、图像等资源,提高了应用的可维护性和可扩展性。本文介绍了资源字典的基础知识、创建方法及最佳实践,并通过示例展示了如何在项目中有效利用资源字典,实现资源的重用和动态绑定。
81 0
|
3月前
|
开发者 C# 存储
WPF开发者必读:样式与模板的艺术,轻松定制UI外观,让你的应用程序更上一层楼!
【8月更文挑战第31天】在WPF应用开发中,样式与模板是实现美观界面与一致性的关键工具。样式定义了控件如字体、颜色等属性,而模板则允许自定义控件布局与子控件,两者均可存储于`.xaml`文件中。本文介绍了样式与模板的基础知识,通过示例展示了如何创建并应用它们来改变按钮的外观,从而提升用户体验。
80 0
|
3月前
|
开发框架 前端开发 JavaScript
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(3)--自定义用户控件
循序渐进介绍基于CommunityToolkit.Mvvm 和HandyControl的WPF应用端开发(3)--自定义用户控件
|
3月前
|
C#
WPF 自定义可拖动标题栏
WPF 自定义可拖动标题栏
50 0
|
3月前
|
存储 前端开发 C#
WPF/C#:更改界面的样式
WPF/C#:更改界面的样式
43 0
|
3月前
|
开发框架 前端开发 C#
使用WPF开发自定义用户控件,以及实现相关自定义事件的处理
使用WPF开发自定义用户控件,以及实现相关自定义事件的处理
WPF-布局样式练习-Day02-聊天气泡
WPF-布局样式练习-Day02-聊天气泡
237 1