相当多的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=""></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<WrapPanel VerticalAlignment="Center">
<Button Name="btnA" Height="" Width="" Margin="" Click="btnA_Click">页面A</Button>
<Button Name="btnB" Height="" Width="" Click="btnB_Click">页面B</Button>
</WrapPanel>
<Frame Name="frmMain" NavigationUIVisibility="Hidden"></Frame>
</Grid>
</Window>
   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=""></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<WrapPanel VerticalAlignment="Center">
<Button Tag="PageA" Name="btnA" Height="" Width="" Margin="" Click="btnNav_Click">页面A</Button>
<Button Tag="PageB" Name="btnB" Height="" Width="" 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="" Width="">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height=""></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="1*"></ColumnDefinition>
<ColumnDefinition Width="3*"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<WrapPanel Grid.Row="" Grid.Column="" HorizontalAlignment="Center" VerticalAlignment="Center">
<Button Tag="Home" Width="" Height="" Margin="" Click="btnNav_Click">首页</Button>
<Button Tag="SimpleChat" Width="" Height="" Margin="0,0,5,0" Click="btnNav_Click">内容</Button>
</WrapPanel>
<Grid Grid.Row="" Grid.ColumnSpan="">
<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=""></RowDefinition>
<RowDefinition></RowDefinition>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="3*"></ColumnDefinition>
<ColumnDefinition Width="1*"></ColumnDefinition>
</Grid.ColumnDefinitions>
<WrapPanel Grid.Row="" Grid.Column="" VerticalAlignment="Center" HorizontalAlignment="Center">
<TextBox Name="txtParam" Width="" Height=""></TextBox>
<Button Name="btnCall" Width="" Height="" Margin="" Click="btnCall_Click">CallApiByGet</Button>
</WrapPanel>
<Grid Grid.Row=""> </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的区别就不再赘述了。

WPF简单导航框架(Window与Page互相调用)的更多相关文章

  1. WPF中的导航框架(一)——概述

    有的时候,我们需要一个支持页面跳转的UI,例如文件浏览器,开始向导等.对于这样的界面,简单的可以使用ContentControl + ContentTemplateSelector的方式来实现,但是有 ...

  2. [转]WPF中的导航框架

    有的时候,我们需要一个支持页面跳转的UI,例如文件浏览器,开始向导等.对于这样的界面,简单的可以使用ContentControl + ContentTemplateSelector的方式来实现,但是有 ...

  3. Wpf Page间跳转传参数 And Window To Page

    这段时间用到Wpf,页面间的跳转网上有不少的示例,但是有些已经不能用了,尤其是页面间的传参问题更是一大堆,但正确的解决方案却没有几个,或者说写的不清楚,让人走了很多弯路,查看官方文档后发现了正确的姿势 ...

  4. 【源码分享】WPF漂亮界面框架实现原理分析及源码分享

    1 源码下载 2 OSGi.NET插件应用架构概述 3 漂亮界面框架原理概述 4 漂亮界面框架实现  4.1 主程序  4.2 主程序与插件的通讯   4.2.1 主程序获取插件注册的服务   4.2 ...

  5. .NET CORE(C#) WPF简单菜单MVVM绑定

    微信公众号:Dotnet9,网站:Dotnet9,问题或建议:请网站留言, 如果对您有所帮助:欢迎赞赏. .NET CORE(C#) WPF简单菜单MVVM绑定 阅读导航 本文背景 代码实现 本文参考 ...

  6. ModernUI教程:如何使用你自己的导航框架

         Modern UI for WPF带有一个内置的页面导航框架,易于使用和可扩展的.但这并不是必须的,你也可以自己来自定义一个导航框架.      默认的ModernWindow控件模板包括标 ...

  7. 搭建一个简单struts2框架的登陆

    第一步:下载struts2对应的jar包,可以到struts官网下载:http://struts.apache.org/download.cgi#struts252 出于学习的目的,可以把整个完整的压 ...

  8. WPF简单入门总结

    WPF简单总结 最近看了点关于WPF的东西,总结了点点入门的东西. XAML语法基础 1.  定义样式 <Window.Resources><!--窗体资源的定义--> < ...

  9. .NET 跨平台RPC框架DotNettyRPC Web后台快速开发框架(.NET Core) EasyWcf------无需配置,无需引用,动态绑定,轻松使用 C# .NET 0配置使用Wcf(半成品) C# .NET Socket 简单实用框架 C# .NET 0命令行安装Windows服务程序

    .NET 跨平台RPC框架DotNettyRPC   DotNettyRPC 1.简介 DotNettyRPC是一个基于DotNetty的跨平台RPC框架,支持.NET45以及.NET Standar ...

随机推荐

  1. Visual Studio最好用的快捷键

    当然每个人常用的一般都会有些不一样,欢迎大家评论说出自己常用或最常用的快捷键吧,比比看谁用的巧~~~ ctrl+-(shift+ctrl+-):移动光标到上次位置或相反,比如定位一个函数,转到函数定义 ...

  2. Python基础篇【第8篇】: Socket编程(二)SocketServer

    SocketServer 在上一篇文章中我们学习了利用socket模块创建socket通信服务,但细心学习后就会发现利用socket模块创建的服务无法进行多进程的处理,当需要进行大量请求处理时,请求就 ...

  3. windows下CMake使用图文手册 Part 4

    例子4:链接静态库(.lib) 例子3里面我们构建了date.lib, 这个例子里我们调用这个库. 前提: date.h的头文件在 E:\Playground\CMakeExamples\DateLi ...

  4. WCF配置

    服务端 <system.serviceModel> <services> <service name="WCF.Homedo.Service.Cache.Ser ...

  5. Java 之ThreadLocal 对应C#之ThreadStatic

    java: public class JForumExecutionContext { private static ThreadLocal userData = new ThreadLocal(); ...

  6. android开发--okhttp

    一.okhttp简单实用: 一般的get请求 一般的post请求 基于Http的文件上传 文件下载 加载图片 支持请求回调,直接返回对象.对象集合 支持session的保持 二.实用教程 1.添加an ...

  7. Rasterizer Stage(读书笔记3 --- Real-Time rendering)

    rasterizer stage的目标:计算和设置每个像素的颜色.将屏幕空间的二维顶点和每个顶点的shading信息转换为屏幕上的像素. rasterizer stage可以分为几个阶段:triang ...

  8. arcgis基于地形数据的坡度分析

    高程.坡度和坡向是小班中非常重要的因子,坡度对水土保持规划设计具有决定性的作用,是土地利用规划和治理措施配置首先要考虑的因素.如何利用地形数据对坡度进行分析呢,本文即将揭晓. 软件准备: locasp ...

  9. sql评估期已过如何解决该问题

    点击开始-所有程序-Microsoft SQL Server 2008-配置工具-SQL Server 安装中心然后点击左侧的维护,在点击右侧的版本升级,接着按照提示一直点下一步,到产品密钥的时候输入 ...

  10. [CSS]多浏览器兼容的垂直居中,兼容多个IE

    相信你都是在兼容垂直居中而烦恼,翻阅多个网站总是找不到理想的方法而苦恼,来到这里你的问题解决了!如果对你有帮助请点个赞,谢谢. 多兼容垂直居中,在IE6-9亲自测试并通过 <!doctype h ...