本篇筆記主要記錄,如何在 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 控件。