如何将TabControl绑定到viewmodel集合

本文关键字:viewmodel 集合 绑定 TabControl | 更新日期: 2023-09-27 17:50:18

基本上我在我的MainViewModel.cs:

ObservableCollection<TabItem> MyTabs { get; private set; }

然而,我需要以某种方式不仅能够创建选项卡,而且有选项卡的内容被加载并链接到他们适当的视图模型,同时维护MVVM。

基本上,我如何获得一个用户控件作为一个表项的内容加载,并有该用户控件连接到一个适当的视图模型。困难的是ViewModel不应该构造实际的视图项,对吧?或者可以吗?

基本上,这是MVVM合适吗?

UserControl address = new AddressControl();
NotificationObject vm = new AddressViewModel();
address.DataContext = vm;
MyTabs[0] = new TabItem()
{
    Content = address;
}

我只问,因为好吧,我正在构建一个视图(AddressControl)从一个ViewModel,这对我来说听起来像一个MVVM的no-no.

如何将TabControl绑定到viewmodel集合

这不是MVVM。你不应该在视图模型中创建UI元素。

您应该将选项卡的ItemsSource绑定到ObservableCollection上,并且它应该包含关于应该创建的选项卡的信息的模型。

下面是代表标签页的VM和模型:

public sealed class ViewModel
{
    public ObservableCollection<TabItem> Tabs {get;set;}
    public ViewModel()
    {
        Tabs = new ObservableCollection<TabItem>();
        Tabs.Add(new TabItem { Header = "One", Content = "One's content" });
        Tabs.Add(new TabItem { Header = "Two", Content = "Two's content" });
    }
}
public sealed class TabItem
{
    public string Header { get; set; }
    public string Content { get; set; }
}

下面是窗口中绑定的样子:

<Window x:Class="WpfApplication12.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Window.DataContext>
        <ViewModel
            xmlns="clr-namespace:WpfApplication12" />
    </Window.DataContext>
    <TabControl
        ItemsSource="{Binding Tabs}">
        <TabControl.ItemTemplate>
            <!-- this is the header template-->
            <DataTemplate>
                <TextBlock
                    Text="{Binding Header}" />
            </DataTemplate>
        </TabControl.ItemTemplate>
        <TabControl.ContentTemplate>
            <!-- this is the body of the TabItem template-->
            <DataTemplate>
                <TextBlock
                    Text="{Binding Content}" />
            </DataTemplate>
        </TabControl.ContentTemplate>
    </TabControl>
</Window>

(注意,如果您希望在不同的选项卡中显示不同的内容,请使用DataTemplates。每个选项卡的视图模型应该是它自己的类,或者创建一个自定义的DataTemplateSelector来选择正确的模板。

数据模板中的UserControl:

<TabControl
    ItemsSource="{Binding Tabs}">
    <TabControl.ItemTemplate>
        <!-- this is the header template-->
        <DataTemplate>
            <TextBlock
                Text="{Binding Header}" />
        </DataTemplate>
    </TabControl.ItemTemplate>
    <TabControl.ContentTemplate>
        <!-- this is the body of the TabItem template-->
        <DataTemplate>
            <MyUserControl xmlns="clr-namespace:WpfApplication12" />
        </DataTemplate>
    </TabControl.ContentTemplate>
</TabControl>

在Prism中,您通常使选项卡控制一个区域,这样您就不必控制绑定的选项卡页面集合。

<TabControl 
    x:Name="MainRegionHost"
    Regions:RegionManager.RegionName="MainRegion" 
    />

现在视图可以通过将自身注册到MainRegion:

区域来添加。
RegionManager.RegisterViewWithRegion( "MainRegion", 
    ( ) => Container.Resolve<IMyViewModel>( ).View );

在这里你可以看到Prism的一个特点。View由ViewModel实例化。在我的例子中,我通过控制反转容器(例如Unity或MEF)来解决ViewModel。ViewModel通过构造函数注入获得View注入,并将自己设置为View的数据上下文。

另一种方法是将视图的类型注册到区域控制器中:
RegionManager.RegisterViewWithRegion( "MainRegion", typeof( MyView ) );

使用这种方法可以让你稍后在运行时创建视图,例如通过控制器:

IRegion region = this._regionManager.Regions["MainRegion"];
object mainView = region.GetView( MainViewName );
if ( mainView == null )
{
    var view = _container.ResolveSessionRelatedView<MainView>( );
    region.Add( view, MainViewName );
}

由于您已经注册了视图的类型,因此视图被放置在正确的区域中。

我有一个转换器来解耦UI和ViewModel,这就是下面的要点:

<TabControl.ContentTemplate>
    <DataTemplate>
        <ContentPresenter Content="{Binding Tab,Converter={StaticResource TabItemConverter}"/>
    </DataTemplate>
</TabControl.ContentTemplate>

Tab是TabItemViewModel中的枚举,TabItemConverter将其转换为真实的UI。

在TabItemConverter中,只需获取值并返回您需要的usercontrol。

我的解决方案直接使用ViewModels,所以我认为它可能对某人有用:

首先,我将view绑定到App.xaml文件中的ViewModels:

<Application.Resources>
        <DataTemplate DataType="{x:Type local:ViewModel1}">
            <local:View1/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:ViewModel2}">
            <local:View2/>
        </DataTemplate>
        <DataTemplate DataType="{x:Type local:ViewModel3}">
            <local:View3/>
        </DataTemplate>
</Application.Resources>

MainViewModel看起来像这样:

    public class MainViewModel : ObservableObject
        {
            private ObservableCollection<ViewModelBase> _viewModels = new ObservableCollection<ViewModelBase>();
            
            public ObservableCollection<ViewModelBase> ViewModels
            {
                get { return _viewModels; }
                set
                {
                    _viewModels = value;
                    OnPropertyChanged();
                }
            }
    
            private ViewModelBase _currentViewModel;
 
           public ViewModelBase CurrentViewModel
            {
                get { return _currentViewModel; }
                set
                {
                    _currentViewModel = value;
                    OnPropertyChanged();
                }
            }
    
            private ICommand _closeTabCommand;
    
            public ICommand CloseTabCommand => _closeTabCommand ?? (_closeTabCommand = new RelayCommand(p => closeTab()));
            
private void closeTab()
            {
                ViewModels.Remove(CurrentViewModel);
                CurrentViewModel = ViewModels.LastOrDefault();
            }
    
    
            private ICommand _openTabCommand;
    
            public ICommand OpenTabCommand => _openTabCommand ?? (_openTabCommand = new RelayCommand(p => openTab(p)));
            
private void openTab(object selectedItem)
            {
                Type viewModelType;
    
                switch (selectedItem)
                {
                    case "1":
                        {
                            viewModelType = typeof(ViewModel1);
                            break;
                        }
                    case "2":
                        {
                            viewModelType = typeof(ViewModel2);
                            break;
                        }
                    default:
                        throw new Exception("Item " + selectedItem + " not set.");
                }
    
                displayVM(viewModelType);
            }
    
            private void displayVM(Type viewModelType)
            {
                if (!_viewModels.Where(vm => vm.GetType() == viewModelType).Any())
                {
                    ViewModels.Add((ViewModelBase)Activator.CreateInstance(viewModelType));
                }
                CurrentViewModel = ViewModels.Single(vm => vm.GetType() == viewModelType);
            }
    
        }
    }

主窗口。XAML:

<Window.DataContext>
        <local:MainWindowViewModel x:Name="vm"/>
    </Window.DataContext>
    <Grid>
        <Grid.RowDefinitions>
            <RowDefinition Height="Auto" />
            <RowDefinition Height="*" />
        </Grid.RowDefinitions>
        <Menu Grid.Row="0">
            <MenuItem Header="1" Command="{Binding OpenTabCommand}" CommandParameter="1"/>
            <MenuItem Header="2" Command="{Binding OpenTabCommand}" CommandParameter="2"/>
            <MenuItem Header="3" Command="{Binding OpenTabCommand}" CommandParameter="3"/>
        </Menu>
        <TabControl Grid.Row="1" ItemsSource="{Binding ViewModels}" SelectedItem="{Binding CurrentViewModel}">
            <TabControl.ItemTemplate>
                <DataTemplate DataType="{x:Type MVVMLib:ViewModelBase}">
                    <TextBlock Text="{Binding Title}">
                    <Hyperlink Command="{Binding RelativeSource={RelativeSource AncestorType=Window, Mode=FindAncestor}, Path=DataContext.CloseWindowCommand}">X</Hyperlink>    
                    </TextBlock>                    
                </DataTemplate>
            </TabControl.ItemTemplate>                        
        </TabControl>
    </Grid>

为了更容易理解,我翻译了一些部分,可能有一些错别字。