这一节演示一下wpf, CommunityToolkit.Mvvm, Autofac 技术栈下的多页面操作。
解决方案:

INavigationService.cs
namespace WpfApp1.Services
{
public interface INavigationService
{
void Initialize(object mainWindowViewModel);
void NavigateToPage1();
void NavigateToPage2();
void NavigateToPage3();
}
}NavigationService.cs
using WpfApp1.ViewModels;
using WpfApp1.Views;
namespace WpfApp1.Services
{
public class NavigationService : INavigationService
{
private MainWindowViewModel _mainWindowViewModel;
private readonly Page1ViewModel _page1ViewModel;
private readonly Page2ViewModel _page2ViewModel;
private readonly Page3ViewModel _page3ViewModel;
public NavigationService(Page1ViewModel page1ViewModel, Page2ViewModel page2ViewModel, Page3ViewModel page3ViewModel)
{
_page1ViewModel = page1ViewModel;
_page2ViewModel = page2ViewModel;
_page3ViewModel = page3ViewModel;
}
public void Initialize(object mainWindowViewModel)
{
_mainWindowViewModel = (MainWindowViewModel)mainWindowViewModel;
}
public void NavigateToPage1()
{
_mainWindowViewModel.CurrentPage = new Views.Page1 { DataContext = _page1ViewModel };
}
public void NavigateToPage2()
{
_mainWindowViewModel.CurrentPage = new Views.Page2 { DataContext = _page2ViewModel };
}
public void NavigateToPage3()
{
_mainWindowViewModel.CurrentPage = new Views.Page3 { DataContext = _page3ViewModel };
}
}
}MainWindowViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
using CommunityToolkit.Mvvm.Input;
using WpfApp1.Services;
namespace WpfApp1.ViewModels
{
public partial class MainWindowViewModel : ObservableObject
{
private readonly INavigationService _navigationService;
[ObservableProperty]
private object _currentPage;
public MainWindowViewModel(INavigationService navigationService)
{
_navigationService = navigationService;
// 初始化NavigationService,传入自身
_navigationService.Initialize(this);
// 默认导航到第一个页面
_navigationService.NavigateToPage1();
}
[RelayCommand]
private void NavigateToPage1()
{
_navigationService.NavigateToPage1();
}
[RelayCommand]
private void NavigateToPage2()
{
_navigationService.NavigateToPage2();
}
[RelayCommand]
private void NavigateToPage3()
{
_navigationService.NavigateToPage3();
}
}
}Page1ViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
namespace WpfApp1.ViewModels
{
public partial class Page1ViewModel : ObservableObject
{
[ObservableProperty]
private string _pageTitle = "页面 1";
[ObservableProperty]
private string _content = "这是第一个页面的内容";
}
}Page2ViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
namespace WpfApp1.ViewModels
{
public partial class Page2ViewModel : ObservableObject
{
[ObservableProperty]
private string _pageTitle = "页面 2";
[ObservableProperty]
private string _content = "这是第二个页面的内容";
}
}Page3ViewModel.cs
using CommunityToolkit.Mvvm.ComponentModel;
namespace WpfApp1.ViewModels
{
public partial class Page3ViewModel : ObservableObject
{
[ObservableProperty]
private string _pageTitle = "页面 3";
[ObservableProperty]
private string _content = "这是第三个页面的内容";
}
}MainWindow.xaml
<Window x:Class="WpfApp1.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:WpfApp1"
mc:Ignorable="d"
Title="MainWindow" Height="450" Width="800">
<DockPanel>
<!-- 工具栏 -->
<ToolBar DockPanel.Dock="Top">
<Button Content="页面 1" Command="{Binding NavigateToPage1Command}" Margin="0,0,10,0"/>
<Button Content="页面 2" Command="{Binding NavigateToPage2Command}" Margin="0,0,10,0"/>
<Button Content="页面 3" Command="{Binding NavigateToPage3Command}"/>
</ToolBar>
<!-- 页面容器 -->
<ContentControl Content="{Binding CurrentPage}"/>
</DockPanel>
</Window>MainWindow.xaml.cs
using System.Windows;
using WpfApp1.ViewModels;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow(MainWindowViewModel viewModel)
{
InitializeComponent();
DataContext = viewModel;
}
}
}Page1.xaml
<UserControl x:Class="WpfApp1.Views.Page1"
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:WpfApp1.Views"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800">
<Grid Background="AliceBlue">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="{Binding PageTitle}" FontSize="24" FontWeight="Bold" Margin="0,0,0,20"/>
<TextBlock Text="{Binding Content}" FontSize="16"/>
</StackPanel>
</Grid>
</UserControl>Page2.xaml
<UserControl x:Class="WpfApp1.Views.Page2"
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:WpfApp1.Views"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800">
<Grid Background="LightCyan">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="{Binding PageTitle}" FontSize="24" FontWeight="Bold" Margin="0,0,0,20"/>
<TextBlock Text="{Binding Content}" FontSize="16"/>
</StackPanel>
</Grid>
</UserControl>Page3.xaml
<UserControl x:Class="WpfApp1.Views.Page3"
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:WpfApp1.Views"
mc:Ignorable="d"
d:DesignHeight="450"
d:DesignWidth="800">
<Grid Background="LightBlue">
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="{Binding PageTitle}" FontSize="24" FontWeight="Bold" Margin="0,0,0,20"/>
<TextBlock Text="{Binding Content}" FontSize="16"/>
</StackPanel>
</Grid>
</UserControl>App.xaml
<Application x:Class="WpfApp1.App" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:local="clr-namespace:WpfApp1"> <Application.Resources> </Application.Resources> </Application>
App.xaml.cs
using System.Windows;
using Autofac;
using WpfApp1.Services;
using WpfApp1.ViewModels;
namespace WpfApp1
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
private IContainer _container;
protected override void OnStartup(StartupEventArgs e)
{
base.OnStartup(e);
ConfigureContainer();
var mainWindow = _container.Resolve<MainWindow>();
mainWindow.Show();
}
private void ConfigureContainer()
{
var builder = new ContainerBuilder();
// 注册服务
builder.RegisterType<NavigationService>().As<INavigationService>();
// 注册视图模型
builder.RegisterType<MainWindowViewModel>();
builder.RegisterType<Page1ViewModel>();
builder.RegisterType<Page2ViewModel>();
builder.RegisterType<Page3ViewModel>();
// 注册主窗口
builder.RegisterType<MainWindow>();
_container = builder.Build();
}
}
}导航页面的关键点
1. 导航服务设计
- 接口定义 :创建了 INavigationService 接口,定义了导航到不同页面的方法。
- 实现逻辑 : NavigationService 类实现了 INavigationService 接口,负责创建页面实例并更新主窗口的当前页面。
- 避免循环依赖 :通过 Initialize 方法延迟初始化 MainWindowViewModel 引用,
解决了导航服务与主窗口视图模型之间的循环依赖问题。
2. 依赖注入配置
- 容器初始化 :在 App.xaml.cs 中使用 Autofac 配置依赖注入容器。
- 服务注册 :注册了导航服务和所有视图模型。
- 窗口解析 :通过容器解析 MainWindow 实例,实现了构造函数注入。
3. MVVM 模式应用
- 视图模型基类 :所有视图模型继承自 ObservableObject ,实现了属性变更通知。
- 命令实现 :使用 [RelayCommand] 属性自动生成命令,简化了命令绑定代码。
- 数据绑定 :页面内容通过数据绑定显示视图模型的属性值。
4. 页面容器与导航
- 容器控件 :在 MainWindow.xaml 中使用 ContentControl 作为页面容器,绑定到 CurrentPage 属性。
- 页面切换 :通过修改 CurrentPage 属性的值实现页面切换。
- 工具栏按钮 :顶部工具栏的按钮通过命令绑定触发导航操作。
5. 页面结构
- 用户控件 :每个页面都实现为 UserControl ,便于在 ContentControl 中显示。
- 视图模型关联 :每个页面都有对应的视图模型,通过构造函数注入或代码设置 DataContext 。
技术亮点
- 松耦合设计 :通过导航服务和依赖注入,实现了视图、视图模型和服务之间的解耦。
- 简化命令绑定 :使用 CommunityToolkit.Mvvm 的 [RelayCommand] 属性,自动生成命令属性,减少了样板代码。
- 灵活的导航逻辑 :导航服务集中管理导航逻辑,便于后续扩展和维护。
- 响应式页面切换 :通过属性变更通知,实现了页面切换的响应式更新。
运行效果
应用启动后,默认显示第一个页面。点击工具栏上的按钮,可以切换到对应的页面,
每个页面都有不同的背景色和内容,展示了导航功能的实现。

上面的页面导航还有一个问题,就是因为页面是new出来的,你反复点击“页面1”就会不断创建页面,
这样用户先前的输入就会保持不了。
因此可以像下面这样修改一下NavigationService .cs
以实现页面缓存:
NavigationService .cs
using WpfApp1.ViewModels;
using WpfApp1.Views;
namespace WpfApp1.Services
{
public class NavigationService : INavigationService
{
private MainWindowViewModel _mainWindowViewModel;
private readonly Page1ViewModel _page1ViewModel;
private readonly Page2ViewModel _page2ViewModel;
private readonly Page3ViewModel _page3ViewModel;
// 页面缓存
private Page1 _page1;
private Page2 _page2;
private Page3 _page3;
public NavigationService(Page1ViewModel page1ViewModel, Page2ViewModel page2ViewModel, Page3ViewModel page3ViewModel)
{
_page1ViewModel = page1ViewModel;
_page2ViewModel = page2ViewModel;
_page3ViewModel = page3ViewModel;
// 初始化页面实例并缓存
_page1 = new Page1 { DataContext = _page1ViewModel };
_page2 = new Page2 { DataContext = _page2ViewModel };
_page3 = new Page3 { DataContext = _page3ViewModel };
}
public void Initialize(object mainWindowViewModel)
{
_mainWindowViewModel = (MainWindowViewModel)mainWindowViewModel;
}
public void NavigateToPage1()
{
_mainWindowViewModel.CurrentPage = _page1;
}
public void NavigateToPage2()
{
_mainWindowViewModel.CurrentPage = _page2;
}
public void NavigateToPage3()
{
_mainWindowViewModel.CurrentPage = _page3;
}
}
/*
private MainWindowViewModel _mainWindowViewModel;
private readonly Page1ViewModel _page1ViewModel;
private readonly Page2ViewModel _page2ViewModel;
private readonly Page3ViewModel _page3ViewModel;
public NavigationService(Page1ViewModel page1ViewModel, Page2ViewModel page2ViewModel, Page3ViewModel page3ViewModel)
{
_page1ViewModel = page1ViewModel;
_page2ViewModel = page2ViewModel;
_page3ViewModel = page3ViewModel;
}
public void Initialize(object mainWindowViewModel)
{
_mainWindowViewModel = (MainWindowViewModel)mainWindowViewModel;
}
public void NavigateToPage1()
{
_mainWindowViewModel.CurrentPage = new Views.Page1 { DataContext = _page1ViewModel };
}
public void NavigateToPage2()
{
_mainWindowViewModel.CurrentPage = new Views.Page2 { DataContext = _page2ViewModel };
}
public void NavigateToPage3()
{
_mainWindowViewModel.CurrentPage = new Views.Page3 { DataContext = _page3ViewModel };
}
*/
}常见的导航有两类:
一是需要完整导航栈、后退/前进功能的场景(如多步骤向导、文档浏览)
可以使用 Frame + Page 方案
- 劣势 :
- 性能开销较大, Page 初始化比 UserControl 慢。
- 导航历史管理可能与左侧菜单的选择逻辑产生冲突。
- 代码复杂度增加,需要处理 Frame 的导航事件和状态。
二是页面之间没有关的
可以使用UserControl + ContentControl 方案
轻量高效
- UserControl :作为轻量级控件容器,加载速度快,性能开销小。
- ContentControl :简洁的内容展示容器,适合直接替换内容。
三是TabControl 方案
- 适用场景 :内容较少、需要在标签间快速切换的场景。
- 劣势 :
- 所有标签页内容会同时初始化,内存占用较高。
- 布局固定为标签页形式,不如自定义左侧菜单灵活。
- 不适合内容复杂或数量较多的场景。