Loading

笔记:在 WPF 中通过侧边导航栏实现内容切换

本篇笔记主要记录,如何在 WPF 中利用 ListBox+ContentControl+UserControl 三个控件,以侧边导航栏的形式实现内容切换。

效果图如下:

┌─────────┬────────────────────────────┐
│  Title  │                            │
├─────────┤                            │
│  Title  │                            │
├─────────┤                            │
│  Title  │                            │
├─────────┤           Content          │
│         │                            │
│         │                            │
│         │                            │
│         │                            │
│         │                            │
└─────────┴────────────────────────────┘

所使用的开发环境如下:

  • 语言版本:.Net 10
  • MVVM 框架:CommunityToolkit.Mvvm 8.4.0

以事件驱动的实现方案

具体实现思路为,利用 ListBox 控件搭建导航栏,然后创建选中事件,如果事件被触发,则将对应的 UserControl 控件赋值给 ContentControl 控件的 Content 属性,至此实现内容切换。

  • XAML 代码
<DockPanel>
    <!-- 导航栏列表框,用于页面导航切换,包含首页、设置、关于三个选项 -->
    <ListBox x:Name="NavBar" SelectionChanged="NavBar_OnSelectionChanged">
        <ListBoxItem>首页</ListBoxItem>
        <ListBoxItem>设置</ListBoxItem>
        <ListBoxItem>关于</ListBoxItem>
    </ListBox>
    <!-- 内容控件,用于显示与导航选项对应的页面内容 -->
    <ContentControl x:Name="ContCtrl"></ContentControl>
</DockPanel>
  • 后置代码
public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        // 初始化导航栏,默认选中第一项,并设置内容控件显示首页视图
        NavBar.SelectedIndex = 0;
        ContCtrl.Content = new HomeView();
    }

    // 当导航栏选择项发生改变时,触发的事件处理函数
    private void NavBar_OnSelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        // 获取当前选中的列表项
        var item = NavBar.SelectedItem as ListBoxItem;

        // 根据选中项的内容切换到对应的视图页面
        ContCtrl.Content = item?.Content switch
        {
            "首页" => new HomeView(),
            "设置" => new SettingView(),
            "关于" => new AboutView(),
            _ => ContCtrl
        };
    }
}
  • 自定义 UserControl 控件内容。由于现阶段实现的很简单,三个用户控件只有文本不同,所以在这里只给出了其中一个 UserControl 控件的内容
<UserControl x:Class="TryDemo.HomeView"
             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:TryDemo"
             mc:Ignorable="d"
             d:DesignHeight="300" d:DesignWidth="300">
    
    <Grid Background="LightBlue">
        <TextBlock Text="首页"
                   HorizontalAlignment="Center" 
                   VerticalAlignment="Center" 
                   FontSize="20"/>
    </Grid>
</UserControl>

MVVM 模式的实现方案

与事件驱动的实现思路,并没有多大的区别,依旧是检测 ListBox 选择项是否更改,如果发生变化,则修改 ContentControl 的 Content 属性,实现动态切换。

在这里,将事件触发的逻辑改为了数据绑定,当被绑定的属性值发生变化,会调用更改方法。

同时也进行了部分升级,比如:

  • 自定义 ListBox 模板,显示个性化内容,同时导航项也变为从后台添加,依此可以实现动态更新。
  • 关于导航项的数据,则变为了自定义的记录结构,其中定义了要显示的内容,同时保存了对应要切换的视图类型,以便简化视图切换,直接使用反射获取对应的视图实例。
  • 使用 CommunityToolkit.Mvvm 框架,简化 MVVM 模式的实现。

下面就是代码实现了:

  • XAML 代码
<DockPanel>
    <!-- 导航栏列表框,绑定到 ViewModel 的 NavItems 集合和 SelectedItem 属性,实现导航项的数据驱动显示 -->
    <ListBox x:Name="NavBar" ItemsSource="{Binding NavItems}"
             SelectedItem="{Binding SelectedItem}">
        <ListBox.ItemTemplate>
            <!-- 自定义列表项模板,定义每个导航项的显示布局 -->
            <DataTemplate>
                <DockPanel>
                    <!-- 显示导航项的图标 -->
                    <TextBlock Text="{Binding Icon}"/>
                    <!-- 显示导航项的标题文本 -->
                    <TextBlock Text="{Binding Title}"/>
                </DockPanel>
            </DataTemplate>
        </ListBox.ItemTemplate>
    </ListBox>
    <!-- 内容控制器,绑定到 ViewModel 的 CurrentView 属性,动态显示当前选中的视图 -->
    <ContentControl Content="{Binding CurrentView}"></ContentControl>
</DockPanel>
  • 自定义的视图模型
public partial class MainWindowViewModel : ObservableObject
{
    // 被选中的导航项属性
    [ObservableProperty]
    private NavItem _selectedItem;
    
    // 当前显示的视图对象属性
    [ObservableProperty]
    private object _currentView;
    
    // 导航栏项目数组,包含所有可用的导航选项
    public NavItem[] NavItems { get; init; }
    
    public MainWindowViewModel()
    {
        // 初始化三个导航项:首页、设置、关于,分别关联对应的视图类型
        NavItems =
        [
            new NavItem("首页", "🏠️", typeof(HomeView)),
            new NavItem("设置", "⚙️", typeof(SettingView)),
            new NavItem("关于", "ℹ️", typeof(AboutView))
        ];
        // 默认选中第一个导航项(首页)
        SelectedItem = NavItems[0];
    }

    /// <summary>
    /// SelectedItem 属性改变时的方法实现,用于切换当前显示的视图
    /// </summary>
    /// <param name="value">新选中的导航项,包含目标视图的类型信息</param>
    partial void OnSelectedItemChanged(NavItem value)
    {
        // 通过反射创建对应视图类型的实例
        var view = Activator.CreateInstance(value.View);
        // 如果创建成功则更新当前视图,否则保持原视图不变
        CurrentView = view ?? CurrentView;
    }
}
  • 导航项类型定义
/// <summary>
/// 导航项记录类型,封装导航栏项目的显示信息和关联的视图类型
/// </summary>
/// <param name="Title">导航项显示的标题文本</param>
/// <param name="Icon">导航项显示的图标字符</param>
/// <param name="View">导航项关联的视图类型,用于创建对应的页面</param>
public record NavItem(string Title, string Icon, Type View);
  • UserControl 控件保持不变

小结

在这里选择 ListBox 作为导航栏的原因是,其具有单选功能,可以轻易地实现选项排他性,保证只有一个选项被选中。

一开始想的是用 Button + 布局控件实现,但发现排他性的实现比较麻烦,转而使用 ListBox 控件。

posted @ 2026-03-08 17:31  Abaiami  阅读(30)  评论(0)    收藏  举报