• [WPF疑难] 继承自定义窗口


     

                                                     [WPF疑难] 继承自定义窗口

    周银辉

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

    挫败1,继承时编译错误

    假设我们写好的父窗口类为BaseWindow,对应BaseWindow.csBaseWindow.xaml, 要继承它的窗口为Window1,对应Window1.csWindow1.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自动生成的一个中间文件,可以在Window1InitializeComponent()方法上“转到定义”来跳转到该文件,也可以在Obj"Debug目录下找到)。这就使得我们的Window1同时继承WindowBaseWindow类,多继承是不被允许的。

    那么自然地,需要修改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中的GridTextBlock会覆盖BaseWindow的内容而仅仅看到“hiI am window1”的文本块而没有最小化最大化以及关闭按钮了。

    事实上,我们应该反过来想,Window也是一个控件,与其他控件一样其外观及其外观中的视觉元素仍然是由其StyleControlTemplate来定义的。想到这里,一切就变得简单了,我们应该将窗口外边框(包括最小化,最大化和关闭按钮)定义在其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;

                };

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

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

  • 相关阅读:
    L1-050 倒数第N个字符串 (15分)
    Oracle存储过程的疑难问题
    Linux的细节
    Linux字符设备和块设备的区别
    Shell变量
    游标的常用属性
    Oracle中Execute Immediate用法
    Oracle中的sqlerrm和sqlcode
    Oracle把一个表的数据复制到另一个表中
    Oracle的差异增量和累积增量
  • 原文地址:https://www.cnblogs.com/zhouyinhui/p/1108561.html
Copyright © 2020-2023  润新知