多页面程序是一种很常见的设计, 一个程序中有多个页面, 然后直接切换页面而不需要创建新的窗口, WPF 中使用
Frame
来做页面跳转, 但是如何优雅的设计一个多页面程序, 这是个问题.
最基本的页面跳转
编写一个最基本的页面跳转, 首先我们需要在主窗口中放一个
Frame
控件, 然后再编写两个
Page
. 下面我们以两个页面 MainPage 和 Configuration 作为示例讲解.
<!--窗口 UI--><Windowx:Class="WpfNavigationTutorial.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:WpfNavigationTutorial"xmlns:pages="clr-namespace:WpfTutorial.Views.Pages"mc:Ignorable="d"Title="MainWindow"Height="450"Width="800"><Grid><Grid.ColumnDefinitions><ColumnDefinitionWidth="3*"/><ColumnDefinitionWidth="13*"/></Grid.ColumnDefinitions><ListBoxName="navMenu"SelectionChanged="ListBox_SelectionChanged"><ListBoxItemContent="Main"Tag="{x:Type pages:MainPage}"/><ListBoxItemContent="Configuration"Tag="{x:Type pages:ConfigurationPage}"/></ListBox><FrameGrid.Column="1"Name="appFrame"NavigationUIVisibility="Hidden"/></Grid></Window>
在上面的代码中, 我们使用了一个左右两栏布局, 左侧放了一个
ListBox
, 右侧放了一个
Frame
, 在
ListBox
中, 我们放了两个
ListBoxItem
, 使用
Content
做内容, 并且给加了
Tag
标识这个按钮具体要跳转到哪个页面.
然后在两个页面中分别这么简单写一下内容:
<!--这里是主页面--><Pagex:Class="WpfTutorial.Views.Pages.MainPage"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:WpfTutorial.Views.Pages"mc:Ignorable="d"d:DesignHeight="450"d:DesignWidth="800"Title="MainPage"><Grid><GridMaxWidth="400"Margin="20"><StackPanelHorizontalAlignment="Stretch"><TextBlock>This is main page</TextBlock></StackPanel></Grid></Grid></Page>
<!--这里是里一个页面页面 (配置页面)--><Pagex:Class="WpfTutorial.Views.Pages.ConfigurationPage"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:WpfTutorial.Views.Pages"mc:Ignorable="d"d:DesignHeight="450"d:DesignWidth="800"Title="ConfigurationPage"><Grid><GridMaxWidth="400"Margin="20"><StackPanelHorizontalAlignment="Stretch"><TextBlock>This is configuration page</TextBlock></StackPanel></Grid></Grid></Page>
在后台代码中, 我们这么写, 当选择项变更的时候, 也使用
Frame
进行页面跳转.
publicpartialclassMainWindow:Window{publicMainWindow(){InitializeComponent();}// 页面实例的缓存privatestaticreadonlyDictionary<Type, Page> bufferedPages =newDictionary<Type, Page>();// 当privatevoidListBox_SelectionChanged(object sender,SelectionChangedEventArgs e){// 如果选择项不是 ListBoxItem, 则返回if(navMenu.SelectedItem isnotListBoxItem item)return;// 如果 Tag 不是一个类型, 则返回if(item.Tag isnotType type)return;// 如果页面缓存中找不到页面, 则创建一个新的页面并存入if(!bufferedPages.TryGetValue(type,outPage? page))
page = bufferedPages[type]=
Activator.CreateInstance(type)asPage ??thrownewException("this would never happen");// 使用 Frame 进行导航.
appFrame.Navigate(page);}}
最后的效果就是这样, 点击左侧项目的时候, 右侧页面会直接进行跳转:
加一点美化
用 ListBox 做侧边页面栏就太丑了, 我们可以使用
ListBox
自己给它套一个样式.
这里不使用
ItemsControl
是因为,
ListBox
的项目是 “可选中” 的, 而
ItemsControl
是没有的,
ItemsControl
只适合界面控件的批量绑定生成.
创建一个
NavigationItem
用来存放导航数据:
publicclassNavigationItem{/// <summary>/// 导航标题/// </summary>publicstring Title {get;set;}=string.Empty;/// <summary>/// 导航描述/// </summary>publicstring Description {get;set;}=string.Empty;/// <summary>/// 目标页面的类型/// </summary>publicType TargetPageType {get;set;}=typeof(Page);}
为
MainWindow
创建一个
MainWindowModel
用来存放页面数据:
publicclassMainWndowModel{publicObservableCollection<NavigationItem> NavigationItems {get;}=newObservableCollection<NavigationItem>(){newNavigationItem(){
Title ="Main",
Description ="The main page of this application",
TargetPageType =typeof(MainPage)},newNavigationItem(){
Title ="Configuration",
Description ="Configure something you want",
TargetPageType =typeof(ConfigurationPage)}};}
然后将主页面的代码改成这样:
<Windowx:Class="WpfNavigationTutorial.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:WpfNavigationTutorial"xmlns:pages="clr-namespace:WpfTutorial.Views.Pages"xmlns:vm="clr-namespace:WpfNavigationTutorial.ViewModels"mc:Ignorable="d"Title="MainWindow"Height="450"Width="800"><Window.DataContext><!--直接在 XAML 中给 Window 的 DataContext 赋值--><vm:MainWndowModel/></Window.DataContext><Grid><Grid.ColumnDefinitions><ColumnDefinitionWidth="3*"/><ColumnDefinitionWidth="13*"/></Grid.ColumnDefinitions><!--使用 ListBox, 并且将 ItemsSource 绑定到我们的数据--><!--记得要使用禁用它的水平滚动条--><ListBoxName="navMenu"ItemsSource="{Binding NavigationItems}"BorderThickness="0 0 1 0"ScrollViewer.HorizontalScrollBarVisibility="Disabled"SelectionChanged="navMenu_SelectionChanged"><ListBox.ItemTemplate><DataTemplate><BorderPadding="5"><StackPanel><TextBlockText="{Binding Title}"/><!--描述信息用灰色字体, 字号也小一点, 多余的部分剪切掉--><TextBlockText="{Binding Description}"Foreground="Gray"FontSize="10"TextTrimming="CharacterEllipsis"/></StackPanel></Border></DataTemplate></ListBox.ItemTemplate></ListBox><FrameGrid.Column="1"Name="appFrame"NavigationUIVisibility="Hidden"/></Grid></Window>
后台代码也相应的改一下, 只需要判断数据是否是
NavigationItem
就好:
publicpartialclassMainWindow:Window{publicMainWindow(){InitializeComponent();}privatestaticreadonlyDictionary<Type, Page> bufferedPages =newDictionary<Type, Page>();privatevoidnavMenu_SelectionChanged(object sender,SelectionChangedEventArgs e){// 如果选择项不是 FrameworkElement, 则返回if(navMenu.SelectedItem isnotNavigationItem item)return;Type type =
item.TargetPageType;// 如果页面缓存中找不到页面, 则创建一个新的页面并存入if(!bufferedPages.TryGetValue(type,outPage? page))
page = bufferedPages[type]=
Activator.CreateInstance(type)asPage ??thrownewException("this would never happen");
appFrame.Navigate(page);}}
最后的效果:
当然, 这样的效果还是很简单, 不过, 更复杂的设计, 就任由你自己发挥啦. 例如改掉
ListBox
元素被选中时的丑陋颜色, 以及去掉聚集焦点时的虚线, 或者加一些动画, 都可以.
版权归原作者 SlimeNull 所有, 如有侵权,请联系我们删除。