相當多的WPF程序都有着豐富的頁面和功能,如何使程序在不同頁面間轉換並降低資源占用,選擇適合自己的導航框架就很重要了。最近花了一點時間做了一個簡單的導航框架,並在這個過程中對Window、Page、UserControl有了更多的認識。
1.“簡單粗暴”的TabControl
如果你的應用程序很簡單,各個頁面間沒有直接的聯系,那么TabControl就完全可以滿足要求。剛開始學WPF的時候,頁面導航我只會用TabControl(其他不懂),自帶Tab切換效果。
<Window>
<TabControl>
<TabItem Header="頁面A">
<Frame Source="PageA.xaml"></Frame>
</TabItem>
<TabItem Header="頁面B">
<Frame Source="PageB.xaml"></Frame>
</TabItem>
</TabControl>
</Window>
效果如上圖(設置Frame的屬性NavigationUIVisibility=”Hidden”可以隱藏導航圖標)。如果是再多一級子頁面呢?那就再加一層TabControl。但使用TabControl做頁面導航的問題是,繪制窗口時,所有子頁面都將被實例化一遍,尤其是頁面較多時加載速度會變慢,占用資源也相對較高。另外在樣式上將TabItem的Header和Content分離也需要費很大一番功夫。
2.“專注導航”的Frame
WPF中提到頁面導航切換就絕對繞不開Frame,它的導航特性使得其連接Window和Page更加自由。簡單的Frame導航是幾個按鈕加上一個Frame,通過按鈕事件控制Frame的Source屬性。
<Window>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<WrapPanel VerticalAlignment="Center">
<Button Name="btnA" Height="30" Width="60" Margin="5" Click="btnA_Click">頁面A</Button>
<Button Name="btnB" Height="30" Width="60" Click="btnB_Click">頁面B</Button>
</WrapPanel>
<Frame Name="frmMain" NavigationUIVisibility="Hidden"></Frame>
</Grid>
</Window>
cs代碼
private void btnA_Click(object sender, RoutedEventArgs e)
{
//注意:這里使用Navigate,不用Source,具體區別自己可以試試
this.frmMain.Navigate(new Uri("PageA.xaml", UriKind.Relative));
}
private void btnB_Click(object sender, RoutedEventArgs e)
{
this.frmMain.Navigate(new Uri("PageA.xaml", UriKind.Relative));
}
這樣一個簡單的Frame導航框架就完成了。但是仔細想一想,如果后期增加更多頁面,后台代碼豈不是要加很多Click事件,能不能把這些Click事件合在一起呢?答案是可以的。關鍵就在於執行Click事件時要知道是由哪個導航按鈕觸發的,可以利用控件的Tag屬性實現這一點。代碼修改如下:
<Window>
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="40"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<WrapPanel VerticalAlignment="Center">
<Button Tag="PageA" Name="btnA" Height="30" Width="60" Margin="5" Click="btnNav_Click">頁面A</Button>
<Button Tag="PageB" Name="btnB" Height="30" Width="60" Click="btnNav_Click">頁面B</Button>
</WrapPanel>
<Frame Name="frmMain" NavigationUIVisibility="Hidden"></Frame>
</Grid>
</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中有一個公共方法:
public void CallFromChild(string name)
{
MessageBox.Show("Hello," + name + "!");
}
在PageA.xam.cs中為其添加一個屬性,使其在實例化后能訪問MainWindow。
private MainWindow _parentWin;
public MainWindow ParentWindow
{
get { return _parentWin; }
set { _parentWin = value; }
}
當頁面切換到PageAxaml,即PageA實例化后,使得ParentWindow=MainWindow;
private void btnA_Click(object sender, RoutedEventArgs e)
{
PageA a = new PageA();
this.frmMain.Content = a;
a.ParentWindow = this;
}
注意這里頁面導航的方法由this.frmMain.Navigate換成了this.frmMain.Content。然后在PageA就可以添加方法來調用MainWindow中的CallFromChild()方法了。
private void btnCall_Click(object sender, RoutedEventArgs e)
{
ParentWindow.CallFromChild("PageA");
}
4.進階的導航框架
上面我們已經實現了簡單的導航框架,也實現了在Page中調用MainWindow中的方法,但問題也是顯而易見的:每新增一個頁面都要為其添加ParentWindow屬性,而且只有在頁面實例化后為其ParenWindow屬性賦值,才能調用MainWindow中的CallFromChild方法;通用的導航事件btnNav_Click中拿到的只是頁面的Uri字符串,必須將其實例化后作為frmMain的Content。
上述兩個問題從兩個方面解決:創建繼承於Page類的BasePage類,使所有頁面都繼承於BasePage,同時在BasePage中添加屬性ParentWindow;使用反射將頁面的Uri字符串轉為Page實例,同時查找其ParentWindow屬性並賦值為MainWindow。
進階后的全部代碼如下:
MainWindow.xaml
<Window x:Class="WPFClient.App.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:WPFClient.App"
mc:Ignorable="d"
Title="MainWindow" Height="480" Width="800">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="3*"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<WrapPanel Grid.Row="0" Grid.Column="1" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Tag="Home" Width="40" Height="40" Margin="5" Click="btnNav_Click">首頁</Button>
<Button Tag="SimpleChat" Width="40" Height="40" Margin="0,0,5,0" Click="btnNav_Click">內容</Button>
</WrapPanel>
<Grid Grid.Row="1" Grid.ColumnSpan="3">
<Frame Name="frmMain" NavigationUIVisibility="Hidden"></Frame>
</Grid>
</Grid>
</Window>
MainWindow.xaml.cs
namespace WPFClient.App
{
/// <summary>
/// MainWindow.xaml 的交互邏輯
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
Navigate("Home");
}
#region 頁面導航
private void btnNav_Click(object sender, RoutedEventArgs e)
{
Button btn = sender as Button;
Navigate(btn.Tag.ToString());
}
private void Navigate(string path)
{
string uri = "WPFClient.App.Views." + path;
Type type = Type.GetType(uri);
if (type != null)
{
//實例化Page頁
object obj = type.Assembly.CreateInstance(uri);
UserControl control = obj as UserControl;
this.frmMain.Content = control;
PropertyInfo[] infos = type.GetProperties();
foreach (PropertyInfo info in infos)
{
//將MainWindow設為page頁的ParentWin
if (info.Name == "ParentWindow")
{
info.SetValue(control, this, null);
break;
}
}
}
}
#endregion
//公共方法
public void CallFromChild(string name)
{
MessageBox.Show("Hello," + name + "!");
}
}
}
BasePage.cs
namespace WPFClient.App
{
public class BasePage : Page
{
#region 父窗體
private MainWindow _parentWin;
public MainWindow ParentWindow
{
get { return _parentWin; }
set { _parentWin = value; }
}
#endregion
}
}
Home.xaml
<base:BasePage x:Class="WPFClient.App.Views.Home"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:local="clr-namespace:WPFClient.App.Views"
xmlns:base="clr-namespace:WPFClient.App"
mc:Ignorable="d" >
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="50"></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<WrapPanel Grid.Row="0" Grid.Column="0" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBox Name="txtParam" Width="120" Height="30"></TextBox>
<Button Name="btnCall" Width="90" Height="30" Margin="5" Click="btnCall_Click">CallApiByGet</Button>
</WrapPanel>
<Grid Grid.Row="1">
</Grid>
</Grid>
</base:BasePage>
Home.xaml.cs
namespace WPFClient.App.Views
{
/// <summary>
/// Home.xaml 的交互邏輯
/// </summary>
public partial class Home : BasePage
{
public Home()
{
InitializeComponent();
}
private void btnCall_Click(object sender, RoutedEventArgs e)
{
string param = txtParam.Text;
ParentWindow.CallFromChild(param);
}
}
}
通過實驗發現,使用這種方案使得Page頁訪問MainWindow中的公共屬性、控件元素或公共變量也是可行的。此外將BasePage的基類從Page改成UserControl也是可以的,畢竟Page就是繼承於UserControl,關於Page和UserControl的區別就不再贅述了。
————————————————
版權聲明:本文為CSDN博主「ludewig」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/lordwish/article/details/52667150