程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> 關於.NET >> [WPF疑難] 繼承自定義窗口

[WPF疑難] 繼承自定義窗口

編輯:關於.NET

項目中有不少的彈出窗口,按照美工的設計其外邊框(包括最大化,最小化,關閉等按鈕)自然不同於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。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved