zoukankan      html  css  js  c++  java
  • 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的区别就不再赘述了。

  • 相关阅读:
    Silverlight 4.0添加鼠标右键菜单和Silverlight全屏模式的进入退出
    获取天气服务
    Silverlight 数据绑定 (1):怎样实现数据绑定 &&Silverlight 数据绑定 (2):Source to Target
    调用根据IP查看城市WebService
    Silverlight中的Binding使用(一、二、三)
    使用Prism实现的WPF MVVM点餐Demo
    [Silverlight入门系列]实现局部元素全屏(Element部分全屏)
    silverlight 上下标
    动画教程(动态注册/静态注册)
    Silverlight数据验证
  • 原文地址:https://www.cnblogs.com/Jaasdsa/p/6164200.html
Copyright © 2011-2022 走看看