wpf 的window基类

今天想做个wpf 的window基类,却发现问题重重。下面是两篇很好的参考文章:

http://www.cnblogs.com/zhouyinhui/archive/2008/03/16/1108561.html [WPF疑难] 继承自定义窗口--周银辉

http://blog.csdn.net/gentle_wolf/article/details/5908340  自定义WPF 窗口样式--月影

项目中有不少的弹出窗口,按照美工的设计其外边框(包括最大化,最小化,关闭等按钮)自然不同于Window自身的,但每个弹出框的外边框都是一样的。对其中一个窗口而言,我们要取消其Window边框,并在右上角摆上三个按钮并编写其点击事件等,但若每个弹出窗口都按照这种方式做一遍就太土了。我们想避免重复劳动,最自然的联想到了“继承”。但WPF给我们找了若干麻烦,被挫败了几次。今天经过2小时的奋战,终于搞定了,分享一下。

挫败1,继承时编译错误

假设我们写好的父窗口类为BaseWindow,对应BaseWindow.cs和BaseWindow.xaml, 要继承它的窗口为Window1,对应Window1.cs和Window1.xaml,我们常常进行的动作是将VS为我们自动生成的代码中的如下语句:

public partial class Window1 : Window

修改成:

public partial class Window1 : BaseWindow

但编译后,你会得到一个错误:Window1有着不同的基类。

这是因为在window1.xaml中

<Window

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

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

   x:Class="InheritWindowDemo.Window1"

   Width="300" Height="300">

   <Grid x:Name="LayoutRoot"/>

</Window>

我们的Window继承了Window类,打开Window1.g.cs也可以看到这一点(这是VS自动生成的一个中间文件,可以在Window1的InitializeComponent()方法上“转到定义”来跳转到该文件,也可以在Obj"Debug目录下找到)。这就使得我们的Window1同时继承Window和BaseWindow类,多继承是不被允许的。

那么自然地,需要修改Window1.xaml,将其中的根“Window”,修改成我们的BaseWindow:

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

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

                x:Class="InheritWindowDemo.Window1"

                xmlns:src="clr-namespace:InheritWindowDemo"

                Height="300"

                Width="300">

    <Grid>

    </Grid>

</src:BaseWindow>

心想,这下可以编译通过了吧,抱歉,不行,又得到另一个编译错误:src:BaseWindow不能是Xaml文件的根,因为它是由Xaml定义的,目前我避免这个问题的办法是让BaseWindow仅仅在C#中定义(即,没有BaseWindow.xaml,只有BaseWindow.cs)。

OK,编译顺利通过,继承成功。

挫败2,外边框(包括最小化,最大化和关闭按钮)放在哪里

明显,不能作为BaseWindow的内容,这是因为继承了BaseWindow的子类窗口(比如Window1)会覆盖BaseWindow的内容。

假设BaseWindow这样编写:

        public BaseWindow()

        {

            Grid grid = new Grid();

            Button minBtn = new Button();

            Button maxBtn = new Button();

            Button closeBtn =new Button();

            //something to ini these buttons

            grid.Children.Add(minBtn);

            grid.Children.Add(maxBtn);

            grid.Children.Add(closeBtn);

            this.Content = grid;

        }

当子类Window1如下定义时:

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

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

                x:Class="InheritWindowDemo.Window1"

                xmlns:src="clr-namespace:InheritWindowDemo"

                Height="300"

                Width="300">

    <Grid>

        <TextBlock Text="hi , i am window1"/>

    </Grid>

</src:BaseWindow>

这样以来Window1中的Grid和TextBlock会覆盖BaseWindow的内容而仅仅看到“hi,I am window1”的文本块而没有最小化最大化以及关闭按钮了。

事实上,我们应该反过来想,Window也是一个控件,与其他控件一样其外观及其外观中的视觉元素仍然是由其Style和ControlTemplate来定义的。想到这里,一切就变得简单了,我们应该将窗口外边框(包括最小化,最大化和关闭按钮)定义在其Template中,其他一些属性(比如是否支持透明等)定义在Style中

其Template如下:

    <ControlTemplate x:Key="BaseWindowControlTemplate" TargetType="{x:Type Window}">

        <DockPanel LastChildFill="True">

            <!--外边框-->

            <Border Width="Auto"

                    Height="Auto"

                    DockPanel.Dock="Top"

                    Background="#FF7097D0"

                    CornerRadius="4,4,0,0"

                    x:Name="borderTitle">

                <StackPanel HorizontalAlignment="Right"

                            Orientation="Horizontal">

                    <!--最小化按钮-->

                    <Button Content="Min"

                            x:Name="btnMin" />

                    <!--最大化按钮-->

                    <Button Content="Max"

                            x:Name="btnMax" />

                    <!--关闭按钮-->

                    <Button Content="Close"

                            x:Name="btnClose" />

                </StackPanel>

            </Border>

            <Border Background="{TemplateBinding Background}"

                    BorderBrush="{TemplateBinding BorderBrush}"

                    BorderThickness="{TemplateBinding BorderThickness}"

                    Width="Auto"

                    Height="Auto"

                    DockPanel.Dock="Top"

                    CornerRadius="0,0,4,4">

                <AdornerDecorator>

                    <ContentPresenter />

                </AdornerDecorator>

            </Border>

        </DockPanel>

</ControlTemplate>

其Style如下:

    <Style x:Key="BaseWindowStyle"

           TargetType="{x:Type Window}">

        <Setter Property="Template" Value="{StaticResource BaseWindowControlTemplate}"/>

           

        <Setter Property="AllowsTransparency"

                Value="True" />

        <Setter Property="WindowStyle"

                Value="None" />

        <Setter Property="BorderBrush"

                Value="#FF7097D0" />

        <Setter Property="BorderThickness"

                Value="4,0,4,4" />

        <!—Something else-->

    </Style>

然后在BaseWindow的构造函数中指定其Style为我们定义的样式:

        private void InitializeStyle()

        {

            this.Style = (Style) App.Current.Resources["BaseWindowStyle"];

        }

这样一来,所有继承了BaseWindow的窗体,都有我们统一定义的外观了。

挫败3,让外边框(包括最小化,最大化和关闭按钮)响应事件

只有外观还不够,至少得有鼠标事件吧。那最小化事件来说,要做的事情是找到定义在ControlTemplate中的btnMin这个Button控件,然后当其被点击时该ControlTemplate被应用到的那个窗体被最小化。

FrameworkTemplate.FindName(string name, FrameworkElement templatedParent)方法可以做帮助我们找到指定的FrameworkTemplate被应用到templatedParent上后具有name名称的控件。

            ControlTemplate baseWindowTemplate = (ControlTemplate)App.Current.Resources["BaseWindowControlTemplate"];

            Button minBtn = (Button)baseWindowTemplate.FindName("btnMin", this);

            minBtn.Click += delegate

            {

                this.WindowState = WindowState.Minimized;

            };

其他事件同理:)不过值得提醒的是,上面这样的代码应该在窗体的Style和Template被应用之后,比如你可以在Loaded后编写使用上面的代码而不是直接放在构造方法中,否则FrameworkTemplate.FindName()方法将返回null。

至此,问题搞定。下载DEMO:https://files.cnblogs.com/zhouyinhui/InheritWindowDemo.zip

 

 

 

自定义 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 )));     

 

    }

 

}

 

 

 

  1. 在 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 比较难看,不过却有他的作用, 就是用来调整窗口大小,我们把它去掉了,就意味着我们需要自己来编写调整窗口大小的代码了。

 

  1. 测试一下这个 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 的缘故。

 

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

 

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 的时候,那么当最大化的时候,窗口将把任务栏覆盖住。是真正意义上的“最大化”了。

 

  1. 如果想制作不规则形状的窗口,需要将 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 ="{ TemplateBindingVerticalContentAlignment }" 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 ="{ TemplateBindingVerticalContentAlignment }" 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 ="{ TemplateBindingVerticalContentAlignment }" 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 ={ StaticResourceBoolToVisibilityConverter }}">

 

                                    < 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 外观如下所示:

 

 

 

posted on 2013-08-27 10:41  cloudfee  阅读(2404)  评论(0编辑  收藏  举报