WPF简单导航框架(Window与Page互相调用)

2023-10-27

相当多的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的区别就不再赘述了。


源代码例子在这里,能够运行才上传的。

源码示例,点我下载

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

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

  • iTextSharp 从 WPF 固定文档生成 PDF

    我有一个简单的 WPF 应用程序 可以显示和打印一些内容 使用固定文档进行报告 如何使用免费且开放的解决方案从中生成 PDF 比如iTextSharp WPF 固定文档 也称为 XPS 文档 是对 PDF 的明显改进 它具有 PDF 所缺乏
  • 如何在 WPF 数据网格中一起选择所有复选框

    我的 wpf 数据网格是
  • 如何按 z-index 对 Windows 进行排序?

    如果我枚举窗口Application Current Windows 对于任意两个窗口 我如何判断哪一个 更接近 即具有更大的 z index 或者 换句话说 我如何按 z 索引对这些窗口进行排序 您无法从 WPF 获取 Window 的
  • 如何从注册表获取安装的软件路径?

    我需要替换安装在任何驱动器 如 C D E 中的文件 我想从注册表中找到安装的文件路径并将该文件替换为其他文件 软件将安装在任何驱动器中 我想替换文件 我正在使用这段代码 如何使用注册表查找安装的文件路径并替换为 C 中的其他文件 stri
  • 为基于架构的 XML 文件创建 WPF 编辑器

    这是场景 我们的服务器产品之一使用大型 XML 配置文件 该文件的布局相当好 并且针对 XSD 文件进行了验证 现在是时候构建一个配置 GUI 来维护这个文件了 我想深入研究 WPF 来完成它 我可以为每个配置部分布置一个单独的表单 每次向
  • 优化 WPF 中由单元格组成的网格以获得最短路径

    我目前正在尝试在 WPF 中制作一个由 Cell 对象组成的网格 我需要将单元格绑定到对象 该对象需要位于二维数组中 我需要它很大 可扩展 并改变单元格的颜色并将数据存储在对象中 我已经实现了 但是绘制网格似乎很慢 100x100 网格需要
  • 嵌套控件结构 - 使用 XAML 还是 C#?

    我想创建一个由相当多的元素组成的结构 它的基本布局如下
  • 需要帮助处理 Application.xaml 文件中的 DataTemplate 事件

    我的应用程序中有一个包含几个按钮的数据模板 我希望这些按钮的偶数处理程序在当前页面 我在许多页面中使用此模板 而不是在 Application xaml vb cs 文件中触发 因为我希望在每个页面上执行不同的操作 我希望我说清楚了 您可以
  • 如何禁用基于 ValidationRule 类的按钮?

    如何禁用基于 ValidationRule 类的 WPF 按钮 下面的代码可以很好地突出显示 TextBox
  • 我对 MVVM 模式有一些疑问

    我叫 Jes s 来自西班牙 是一名 NET 开发人员 几天前我刚刚发现了这个伟大的网络 我有一些关于 MVVM 模式的问题 如果您能回答我 我将很高兴 我三个月前开始使用 WPF 并且学习了 MVP 模式 MVP 非常好 因为您可以很好地
  • 当绑定值为 null 时出现 WPF 日期选择器验证错误

    我有一个 WPF 应用程序 其中使用绑定到实体框架 带有 SQL Server 实体的日期字段的日期选择器 我将其绑定如下
  • 为什么我的 WPF XAML Grid TranslateTransform.X 不会?

    我可以使用它来更改网格的宽度 高度 那么为什么当我这样使用 Grid RenderTransform TranslateTransform X 时它不起作用
  • Prism / MEF:如何在不硬编码区域名称的情况下注册ViewWithRegion

    我们正在构建一个 WPF Prism 应用程序 我们有不同的开发人员从事不同的模块项目 并且多个模块被注入到主应用程序外壳中 主应用程序也是一个单独的项目 我们还希望能够在不同的应用程序中使用这些模块 我们不希望在每个应用程序中都使用相同的
  • Wpf ICollectionView 绑定项无法解析类型对象的属性

    我已经绑定了一个GridView与ICollectionView在 XAML 设计器中 属性是未知的 因为CollectionView已转化为类型Object并且无法访问实体属性 它运行良好 没有错误 但设计器将其显示为错误 如果我绑定到集
  • WPF MVVM将DataTable绑定到DataGrid不显示数据

    我有一个简单的控件 其中包含一个 DataGrid 其中 ItemsSource 绑定到 DataTable 当我填充 DataTable 时 我可以看到 DataGrid 中添加了行 但没有显示任何数据 我没有为此 DataGrid 使用
  • 如何在 WPF 中从原始帧渲染视频?

    我有一个特殊的摄像机 使用 GigEVision 协议 我使用提供的库进行控制 我可以订阅帧接收事件 然后通过 IntPtr 访问帧数据 在我的旧 WinForms 应用程序中 我可以通过从数据创建 Bitmap 对象并将其设置为 Pict
  • 如何创建自动滚动文本框

    我有一个 WPF 应用程序 其中包含一个多行文本框 用于显示调试文本输出 如何设置文本框 以便将文本附加到框中时 它会自动滚动到文本框的底部 我正在使用 MVVM 模式 理想情况下 纯 XAML 方法会很好 TextBox 本身不一定是焦点
  • wpf 的 prism 与 mvvm light

    我们正在启动一个带有 MVVM 的 WPF 项目 并且必须决定使用 PRISM 还是 MVVM Light 我对这两个框架都是新手 我已经阅读了一些帖子 但仍然有一些问题 有人可以阐明以下几个方面吗 两个框架 性能 无论出于何种原因 其中一
  • 在 wpf 中隐藏或禁用输入手势文本

    假设我们使用 Ctrl S 输入手势来保存项目 在 文件 菜单 保存 Ctrl S 中显示这样的文本 现在 在 SaveCommand 的 canexecute 上 我检查项目是否需要保存 如果不需要 则禁用 保存 在这种情况下 我会看到类
  • 将集合绑定到自定义控件属性

    我没有运气尝试将数据集合绑定到我的自定义控件的属性 我已经实现了该控件的字符串属性的机制 在此处提供了一些帮助 并期望集合类型同样简单 但是我无法让它再次工作 这是我的自定义控件视图

随机推荐

  • Android禁用第三方应用

    需要权限android Manifest permission CHANGE COMPONENT ENABLED STATE 而这个权限是只有system app才能使用 所以app需要系统签名 非system app即便在Android
  • Java中使用到的异步任务总结(CompletableFuture类,@Async注解)

    文章目录 1 CompletableFuture 1 1 Completable supplyAsync 1 2 Completable runAsync 1 3 get方法 1 4 使用自定义线程池 1 5 Completable all
  • LaTex

    LaTex LaTex 前言 一 安装配置LaTex 版本安装介绍 配置使用的IDE 二 简单的论文配置问题 基本的语法 1 文档类型和开始 2 最基础的格式化命令 3 Chapters and Sections 文章的章节 4 添加图片
  • 大学物理实验:迈克尔逊干涉仪的调整与使用

    若本文对你有帮助 记得点赞 关注我哟 大学物理专栏https blog csdn net qq 41587612 category 9323622 html
  • Java图形化界面设计之容器(JFrame)详解

    Java图形化界面设计之容器 JFrame 详解 Java图形化界面设计 容器 JFrame 程序是为了方便用户使用的 因此实现图形化界面的程序编写是所有编程语言发展的必然趋势 在命令提示符下运行的程序可以让我们了解java程序的基本知识体
  • kvm的快照功能 (二、基于libvirt的快照)

    实例二 利用libvirt使用快照 virsh snapshot create domain name 一 创建虚机快照 名字自动生成 可在开机 关机 suspend等各种状态下做 virsh snapshot create test Do
  • 【TensorFlow 入门】6、eval 函数

    eval 其实就是tf Tensor的Session run 的另外一种写法 但两者有差别 eval 将字符串string对象转化为有效的表达式参与求值运算返回计算结果 eval 也是启动计算的一种方式 基于Tensorflow的基本原理
  • 算法入门之最常用的排序:快速排序算法

    回顾前面2篇文章我们提到了桶算法和冒泡算法 虽然冒泡算法解决了桶算法的空间问题 但是如果排序的基数比较大 你会发现冒泡算法的时间复杂度O N 也是惊人的 有没有一种更好的算法既能解决空间问题又能解决时间复杂度的问题呢 答案就是我们今天要说的
  • 华为机试题:【中级】报文转换

    描述 报文转换 报文中如果出现0x7E 转义成为2个字节0x7D 0x5E 如果出现0x7D 转义成为2个字节
  • leetcode 110.平衡二叉树

    110 平衡二叉树 leetcode 110 平衡二叉树 题目描述 平衡二叉树 每个节点的左右两个子树的高度差的绝对值不超过1 该二叉树不是平衡二叉树 不是 每个节点的左右子树高度差不超过1 递归解法 每次递归结束时都是当二叉树为一个根节点
  • R绘图笔记

    前面介绍过一些图形的绘制 我们有时候进行GO富集分析 需要绘制富集结果 这里介绍怎么将GO BP GO MF GO CC绘制到同一图形中 library ggplot2 library RColorBrewer display brewer
  • 如何做好技术团队review

    一 Code Review的好处 想要做好Code Review 必须让参与的工程师充分认识到Code Review的好处 1 互相学习 彼此成就 无论是高手云集的架构师团队 还是以CURD为主的业务开发团队 大家的技术能力 经验都是有差异
  • numpy库笔记

    一 ndarray类常用的属性 我用的是jupyter编程 将就看一下 import numpy as np a np random rand 3 4 生成3行4列的随机数组 a reshape 4 3 修改a为4行3列 a reshape
  • 为什么8位数据范围是-128到127,而不是-127到128?

    很表面很浅薄的问题 简单说爱怎么规定就怎么规定 甚至 1到254都行 无非是显示时通过编码表做个转换的问题而已 不过 当初选择 补码 这种编码形式 却并不像表面看起来那么浅薄 背后的道道可多着呢 首先 8位二进制一共可以提供256个 码点
  • web前端技术笔记(七)CSS3动画、选择器和权重

    CSS3动画 圆角 效果图 html 透明 rgba 新的颜色值表示法 效果 html transition动画 html 综合练习 transform变换 transform origin 旋转中心点 三维旋转 animation动画 方
  • MDK外部Flash烧录算法文件制作

    MDK外部Flash烧录算法文件制作 硬件平台 算法制作工程配置 Flash算法驱动 修改硬件初始化代码 修改外部Flash的描述信息 完善Flash的驱动接口 屏蔽无效代码 使用Flash算法 测试验证 分散加载文件的修改 执行编译 常见
  • 深度学习------tensorflow张量创建

    1 数值类型张量 创建标量 向量 矩阵 a 1 2 python 语言方式创建标量 aa tf constant 1 2 tf方式创建标量 b tf constant 1 2 创建一个元素的向量 c tf constant
  • 贝叶斯定理

    贝叶斯定理 通常 事件A在事件B的条件下的概率 与事件B在事件A的条件下的概率是不一样的 然而 这两者是有确定的关系 贝叶斯法则就是这种关系的陈述 贝叶斯法则又被称为贝叶斯定理 贝叶斯规则 是指概率统计中的应用所观察到的现象对有关概率分布的
  • window 安装/连接 MySQL8

    前言 开发服务器崩溃两次了 感觉老不靠谱了 所以自己在本地搞一个环境 正文 1 下载 MySQL8官方下载地址 2 下载后执行安装命令 初始化 留意末尾打印的初始密码 mysqld initialize console 安装mysql服务
  • WPF简单导航框架(Window与Page互相调用)

    相当多的WPF程序都有着丰富的页面和功能 如何使程序在不同页面间转换并降低资源占用 选择适合自己的导航框架就很重要了 最近花了一点时间做了一个简单的导航框架 并在这个过程中对Window Page UserControl有了更多的认识 1