项目目标是展示如何在 WPF 应用中动态管理标签页,包括新增、关闭和切换标签页。
效果
该demo使用CommunityToolkit.Mvvm实现MVVM数据绑定与命令,通过Microsoft.Extensions.DependencyInjection配置 DI 容器,如要复刻需安装这两个包或自己实现INotifyPropertyChanged和ICommand
github地址:https://github.com/Edicius-Lia/WFPTabControl
MainWindow.xaml
<DockPanel> <Menu DockPanel.Dock="Top"> <MenuItem Header="文件"> <MenuItem Header="Page1" Command="{Binding OpenPage1Command}" /> <MenuItem Header="Page2" Command="{Binding OpenPage2Command}" /> </MenuItem> </Menu> <TabControl ItemsSource="{Binding Tabs}" SelectedItem="{Binding SelectedTab}" BorderThickness="0" Background="Transparent"> <TabControl.ItemTemplate> <DataTemplate> <DockPanel> <TextBlock Text="{Binding Header}" Margin="0,0,5,0"/> <Button Content="×" Command="{Binding CloseCommand}" DockPanel.Dock="Right" Background="Transparent" BorderThickness="0"/> </DockPanel> </DataTemplate> </TabControl.ItemTemplate> <TabControl.ContentTemplate> <DataTemplate DataType="domain:TabItemViewModel"> <ContentPresenter Content="{Binding Content, Mode=TwoWay}" /> </DataTemplate> </TabControl.ContentTemplate> </TabControl> </DockPanel>需要在MainWindow.cs赋值DataContext,需要把ViewModel注册为单例
public MainWindow() { InitializeComponent(); this.DataContext= App.Current.Services.GetService<MainViewModel>(); }ViewModel,Page1、Page2为需要展示的Page页面
[ObservableProperty] ObservableCollection<TabItemViewModel> _tabs = []; [ObservableProperty] private TabItemViewModel? _selectedTab=new(); private readonly Dictionary<string, Page> _pageCache = new(); [RelayCommand] void OpenPage1() => NewTab("Page1", new Page1()); [RelayCommand] void OpenPage2() => NewTab("Page2", new Page2()); private void NewTab(string header, Page page) { var existing = Tabs.FirstOrDefault(t => t.Header == header); if (existing != null) { SelectedTab = existing; return; } Page loadedPage; if (_pageCache.ContainsKey(header)) { loadedPage = _pageCache[header]; } else { loadedPage = page; _pageCache[header] = loadedPage; } var newTab = new TabItemViewModel ( header,new Frame() {Content=loadedPage }); Tabs.Add(newTab); SelectedTab = newTab; }TabItemViewModel.cs,获取的parentViewModel 需要是单例,确保移除的是当前窗口的标签
public partial class TabItemViewModel : ObservableObject { [ObservableProperty] private string _Header = "New Tab"; [ObservableProperty] private object _Content = new object(); public TabItemViewModel(string header,Frame frame ) { Header = header; Content = frame; } public TabItemViewModel() { } [RelayCommand] private void Close() { var parentViewModel = App.Current.Services.GetService<MainViewModel>(); if (parentViewModel != null) { parentViewModel.Tabs.Remove(this); } } }App.xaml.cs 依赖注入,注册单例
/// <summary> /// Ioc服务 /// </summary> public IServiceProvider Services { get; } public new static App Current => (App)Application.Current; public App() { Services = ConfigureServices(); } private static IServiceProvider ConfigureServices() { var services = new ServiceCollection(); services.AddSingleton<MainViewModel>(); services.AddSingleton(sp => new MainWindow { DataContext = sp.GetService<MainViewModel>() }); return services.BuildServiceProvider(); }