WPF簡單導航框架(Window與Page互相調用)


 相當多的WPF程序都有着豐富的頁面和功能,如何使程序在不同頁面間轉換並降低資源占用,選擇適合自己的導航框架就很重要了。最近花了一點時間做了一個簡單的導航框架,並在這個過程中對Window、Page、UserControl有了更多的認識。


1.“簡單粗暴”的TabControl

如果你的應用程序很簡單,各個頁面間沒有直接的聯系,那么TabControl就完全可以滿足要求。剛開始學WPF的時候,頁面導航我只會用TabControl(其他不懂),自帶Tab切換效果。

 
 1      <Window>
 2          <TabControl>
 3             <TabItem Header="頁面A">
 4                 <Frame Source="PageA.xaml"></Frame>
 5             </TabItem>
 6             <TabItem Header="頁面B">
 7                 <Frame Source="PageB.xaml"></Frame>
 8             </TabItem>
 9         </TabControl>
10     </Window>

 

  這里寫圖片描述

效果如上圖(設置Frame的屬性NavigationUIVisibility=”Hidden”可以隱藏導航圖標)。如果是再多一級子頁面呢?那就再加一層TabControl。但使用TabControl做頁面導航的問題是,繪制窗口時,所有子頁面都將被實例化一遍,尤其是頁面較多時加載速度會變慢,占用資源也相對較高。另外在樣式上將TabItem的Header和Content分離也需要費很大一番功夫。

2.“專注導航”的Frame

WPF中提到頁面導航切換就絕對繞不開Frame,它的導航特性使得其連接Window和Page更加自由。簡單的Frame導航是幾個按鈕加上一個Frame,通過按鈕事件控制Frame的Source屬性。

 1 <Window>
 2     <Grid>
 3         <Grid.RowDefinitions>
 4             <RowDefinition Height="40"></RowDefinition>
 5             <RowDefinition></RowDefinition>
 6         </Grid.RowDefinitions>
 7         <WrapPanel VerticalAlignment="Center">
 8             <Button Name="btnA" Height="30" Width="60" Margin="5" Click="btnA_Click">頁面A</Button>
 9             <Button Name="btnB" Height="30" Width="60" Click="btnB_Click">頁面B</Button>
10         </WrapPanel>
11         <Frame Name="frmMain" NavigationUIVisibility="Hidden"></Frame>
12     </Grid>
13 </Window>
 1   private void btnA_Click(object sender, RoutedEventArgs e)
 2         {
 3             //注意:這里使用Navigate,不用Source,具體區別自己可以試試
 4             this.frmMain.Navigate(new Uri("PageA.xaml", UriKind.Relative));
 5         }
 6 
 7         private void btnB_Click(object sender, RoutedEventArgs e)
 8         {
 9             this.frmMain.Navigate(new Uri("PageA.xaml", UriKind.Relative));
10         }

這樣一個簡單的Frame導航框架就完成了。但是仔細想一想,如果后期增加更多頁面,后台代碼豈不是要加很多Click事件,能不能把這些Click事件合在一起呢?答案是可以的。關鍵就在於執行Click事件時要知道是由哪個導航按鈕觸發的,可以利用控件的Tag屬性實現這一點。代碼修改如下:

 1  <Window>
 2      <Grid>
 3         <Grid.RowDefinitions>
 4             <RowDefinition Height="40"></RowDefinition>
 5             <RowDefinition></RowDefinition>
 6         </Grid.RowDefinitions>
 7         <WrapPanel VerticalAlignment="Center">
 8             <Button Tag="PageA" Name="btnA" Height="30" Width="60" Margin="5" Click="btnNav_Click">頁面A</Button>
 9             <Button Tag="PageB" Name="btnB" Height="30" Width="60" Click="btnNav_Click">頁面B</Button>
10         </WrapPanel>
11         <Frame Name="frmMain" NavigationUIVisibility="Hidden"></Frame>
12     </Grid>
13 </Window>

cs代碼改為

 private void btnNav_Click(object sender, RoutedEventArgs e)
        {
            Button btn = sender as Button;
            this.frmMain.Navigate(new Uri(btn.Tag.ToString()+".xaml",UriKind.Relative));
        }

這樣無論添加多少頁面,不需要修改后台方法,只需為導航按鈕添加相應的Tag就可以了。(使用Name屬性或其他屬性也是可以的,有興趣的可以自己試試)

3.互相調用的Window和Page

在復雜一點的WPF程序里,我們往往不僅需要頁面間切換瀏覽,有時也需要相互調用方法,比如說在PageA中調用MainWindow的方法,代碼如下: 
在MainWindow.xaml.cs中有一個公共方法:

1         public void CallFromChild(string name)
2         {
3             MessageBox.Show("Hello," + name + "!");
4         }

在PageA.xam.cs中為其添加一個屬性,使其在實例化后能訪問MainWindow。

1         private MainWindow _parentWin;
2         public MainWindow ParentWindow
3         {
4             get { return _parentWin; }
5             set { _parentWin = value; }
6         }

當頁面切換到PageAxaml,即PageA實例化后,使得ParentWindow=MainWindow;

1     private void btnA_Click(object sender, RoutedEventArgs e)
2         {
3             PageA a = new PageA();
4             this.frmMain.Content = a;
5             a.ParentWindow = this;
6         }

注意這里頁面導航的方法由this.frmMain.Navigate換成了this.frmMain.Content。然后在PageA就可以添加方法來調用MainWindow中的CallFromChild()方法了。

1         private void btnCall_Click(object sender, RoutedEventArgs e)
2         {
3             ParentWindow.CallFromChild("PageA");
4         }

4.進階的導航框架

上面我們已經實現了簡單的導航框架,也實現了在Page中調用MainWindow中的方法,但問題也是顯而易見的:每新增一個頁面都要為其添加ParentWindow屬性,而且只有在頁面實例化后為其ParenWindow屬性賦值,才能調用MainWindow中的CallFromChild方法;通用的導航事件btnNav_Click中拿到的只是頁面的Uri字符串,必須將其實例化后作為frmMain的Content。 
上述兩個問題從兩個方面解決:創建繼承於Page類的BasePage類,使所有頁面都繼承於BasePage,同時在BasePage中添加屬性ParentWindow;使用反射將頁面的Uri字符串轉為Page實例,同時查找其ParentWindow屬性並賦值為MainWindow。 
進階后的全部代碼如下: 
MainWindow.xaml

 1 <Window x:Class="WPFClient.App.MainWindow"
 2         xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3         xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4         xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
 5         xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
 6         xmlns:local="clr-namespace:WPFClient.App"
 7         mc:Ignorable="d"
 8         Title="MainWindow" Height="480" Width="800">
 9     <Grid>
10         <Grid.RowDefinitions>
11             <RowDefinition Height="50"></RowDefinition>
12             <RowDefinition></RowDefinition>
13         </Grid.RowDefinitions>
14         <Grid.ColumnDefinitions>
15             <ColumnDefinition Width="1*"></ColumnDefinition>
16             <ColumnDefinition Width="3*"></ColumnDefinition>
17             <ColumnDefinition Width="1*"></ColumnDefinition>
18         </Grid.ColumnDefinitions>
19         <WrapPanel Grid.Row="0" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center">
20             <Button Tag="Home" Width="40" Height="40" Margin="5" Click="btnNav_Click">首頁</Button>
21             <Button Tag="SimpleChat" Width="40" Height="40" Margin="0,0,5,0" Click="btnNav_Click">內容</Button>
22         </WrapPanel>
23         <Grid Grid.Row="1" Grid.ColumnSpan="3">
24             <Frame Name="frmMain" NavigationUIVisibility="Hidden"></Frame>
25         </Grid>
26     </Grid>
27 </Window>

MainWindow.xaml.cs

 1 namespace WPFClient.App
 2 {
 3     /// <summary>
 4     /// MainWindow.xaml 的交互邏輯
 5     /// </summary>
 6     public partial class MainWindow : Window
 7     {
 8         public MainWindow()
 9         {
10             InitializeComponent();
11             Navigate("Home");
12         }
13 
14 
15         #region 頁面導航
16         private void btnNav_Click(object sender, RoutedEventArgs e)
17         {
18             Button btn = sender as Button;
19             Navigate(btn.Tag.ToString());
20         }
21         private void Navigate(string path)
22         {
23             string uri = "WPFClient.App.Views." + path;
24             Type type = Type.GetType(uri);
25             if (type != null)
26             {
27                 //實例化Page頁
28                 object obj = type.Assembly.CreateInstance(uri);
29                 UserControl control = obj as UserControl;
30                 this.frmMain.Content = control;
31                 PropertyInfo[] infos = type.GetProperties();
32                 foreach (PropertyInfo info in infos)
33                 {
34                     //將MainWindow設為page頁的ParentWin
35                     if (info.Name == "ParentWindow")
36                     {
37                         info.SetValue(control, this, null);
38                         break;
39                     }
40                 }
41             }
42         }
43 
44         #endregion
45 
46         //公共方法
47         public void CallFromChild(string name)
48         {
49             MessageBox.Show("Hello," + name + "!");
50         }
51 
52     }
53 }

BasePage.cs

 1 namespace WPFClient.App
 2 {
 3     public class BasePage : Page
 4     {
 5         #region 父窗體
 6         private MainWindow _parentWin;
 7         public MainWindow ParentWindow
 8         {
 9             get { return _parentWin; }
10             set { _parentWin = value; }
11         }
12         #endregion
13 
14     }
15 }

Home.xaml

 1 <base:BasePage x:Class="WPFClient.App.Views.Home"
 2              xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
 3              xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
 4              xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
 5              xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
 6              xmlns:local="clr-namespace:WPFClient.App.Views"
 7              xmlns:base="clr-namespace:WPFClient.App"
 8              mc:Ignorable="d" >
 9     <Grid>
10         <Grid.RowDefinitions>
11             <RowDefinition Height="50"></RowDefinition>
12             <RowDefinition></RowDefinition>
13         </Grid.RowDefinitions>
14         <Grid.ColumnDefinitions>
15             <ColumnDefinition Width="3*"></ColumnDefinition>
16             <ColumnDefinition Width="1*"></ColumnDefinition>
17         </Grid.ColumnDefinitions>
18         <WrapPanel Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center">
19             <TextBox Name="txtParam" Width="120" Height="30"></TextBox>
20             <Button Name="btnCall" Width="90" Height="30" Margin="5" Click="btnCall_Click">CallApiByGet</Button>
21         </WrapPanel>
22         <Grid Grid.Row="1">
23 
24         </Grid>
25     </Grid>
26 </base:BasePage>

Home.xaml.cs

 1 namespace WPFClient.App.Views
 2 {
 3     /// <summary>
 4     /// Home.xaml 的交互邏輯
 5     /// </summary>
 6     public partial class Home : BasePage
 7     {
 8         public Home()
 9         {
10             InitializeComponent();
11         }
12 
13         private void btnCall_Click(object sender, RoutedEventArgs e)
14         {
15             string param = txtParam.Text;
16             ParentWindow.CallFromChild(param);
17         }
18 
19     }
20 }

通過實驗發現,使用這種方案使得Page頁訪問MainWindow中的公共屬性、控件元素或公共變量也是可行的。此外將BasePage的基類從Page改成UserControl也是可以的,畢竟Page就是繼承於UserControl,關於Page和UserControl的區別就不再贅述了。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM