少有人走的路

勇哥的工业自动化技术网站

wpf, CommunityToolkit.Mvvm, Autofac 技术栈下的多页面操作

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



解决方案:

image.png



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] 属性,自动生成命令属性,减少了样板代码。

- 灵活的导航逻辑 :导航服务集中管理导航逻辑,便于后续扩展和维护。

- 响应式页面切换 :通过属性变更通知,实现了页面切换的响应式更新。


运行效果

应用启动后,默认显示第一个页面。点击工具栏上的按钮,可以切换到对应的页面,

每个页面都有不同的背景色和内容,展示了导航功能的实现。


act107.gif


上面的页面导航还有一个问题,就是因为页面是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 方案

- 适用场景 :内容较少、需要在标签间快速切换的场景。

- 劣势 :

  - 所有标签页内容会同时初始化,内存占用较高。

  - 布局固定为标签页形式,不如自定义左侧菜单灵活。

  - 不适合内容复杂或数量较多的场景。


发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

«    2026年1月    »
1234
567891011
12131415161718
19202122232425
262728293031
控制面板
您好,欢迎到访网站!
  查看权限
网站分类
搜索
最新留言
文章归档
网站收藏
友情链接

Powered By Z-BlogPHP 1.7.3

Copyright www.skcircle.com Rights Reserved.

鄂ICP备18008319号


站长QQ:496103864 微信:abc496103864