如何将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.
这不是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>
为了更容易理解,我翻译了一些部分,可能有一些错别字。