使用C# MVVM将视图绑定到带有标题的TabControl

4

我有一个WPF程序,它有一个主视图(Window),其中包含一个TabControl,用于显示不同的UserControl视图(子视图,每个标签页中都有一个)。 每个视图都有一个关联的ViewModel。

我希望绑定TabControl,这样我只需要将新的子视图加载到ApplicationViewModel中,它就会出现在TabControl上。

我已经成功地将子视图绑定到了内容,但无法在标题中得到任何东西。 我希望将标题绑定到子视图的ViewModel中的属性,具体来说是TabTitle

应用程序视图(DataTemplate绑定不起作用):

<Window ...>
    <DockPanel>
        <TabControl ItemsSource="{Binding PageViews}" SelectedIndex="0"> <!--Working-->
            <TabControl.ItemTemplate>
                <DataTemplate>
                    <TextBlock Text="{Binding DataContext.TabTitle}, Path=DataContext.TabTitle}" /> <!--Not Working-->
                </DataTemplate>    
            </TabControl.ItemTemplate>
        </TabControl>
    </DockPanel>
</Window>

应用程序视图模型(ObservableObject 实际上实现了 INotifyPropertyChanged):

class ApplicationViewModel : ObservableObject
{
    private DataManager Data;
    private ObservableCollection<UserControl> _pageViews;

    internal ApplicationViewModel()
    {
        Data = new DataManager();
        PageViews.Add(new Views.MembersView(new MembersViewModel(Data.DataSet)));
    }

    public ObservableCollection<UserControl> PageViews
    {
        get
        {
            if (_pageViews == null)
            {
                _pageViews = new ObservableCollection<UserControl>();
            }
            return _pageViews;
        }
    }

MembersView代码后台:

public partial class MembersView : UserControl
{
    public MembersView(MembersViewModel ViewModel)
    {
        InitializeComponent();
        DataContext = ViewModel;
    }
}

MembersViewModel(截断):

public class MembersViewModel : INotifyPropertyChanged
{
    public TabTitle { get; protected set; }

    public MembersViewModel(DataSet BBDataSet)
    {
        TabTitle = "Members";
    }

    //All view properties
}

我相信这只是一个简单的问题...


1
你不应该创建 ObservableCollection<UserControl>,而是应该创建 ObservableCollection<MembersViewModel>,并且 ItemTemplate/ContentTemplate 应该是 MembersView - dkozl
我该如何告诉程序需要显示一个视图? - Jonathan Twite
如果你绑定到ObservableCollection <MembersViewModel>,那么每个标头和内容的DataContext都将是MembersViewModel的实例,并且它将转换为带有DataTemplate(标头的ItemTemplate和内容的ContentTemplate)的UI元素,所以您在标头中的TextBlock将如下所示:<TextBlock ... Text="{Binding TabTitle}"/>,并且ContentTemplate-我假设-将显示MembersView,以与标头中的相同方式显示_All视图属性_。 - dkozl
1个回答

1
您正在将TabControl绑定到UserControl类型的集合。这意味着每个项的数据上下文将是UserControl类型。在UserControl中没有名为“TabTitle”的属性,因此绑定不起作用。
我认为您想要做的可以通过以下更改来实现:
  1. Have ApplicationViewModel expose a collection of type MembersViewModel, instead of UserControl, and populate it appropriately.
  2. Setup a ContentTemplate to create views for your items in the TabControl:

    <TabControl.ContentTemplate>
        <DataTemplate DataType="{x:Type namespace:MembersViewModel}">
            <namespace:MembersView />
        </DataTemplate>
    </TabControl.ContentTemplate>
    

    (Replace "namespace:" with your xaml imported namespace containing your controls.)

  3. Update the ItemTemplate in your TabControl so it binds properly to the view model:

    <TabControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding TabTitle}}" />
        </DataTemplate>    
    </TabControl.ItemTemplate>
    
  4. Update MembersView to have a parameterless constructor. The DataContext on the view will be set for you by the TabControl. If you need to access the view model from your code-behind, it should be available through the DataContext property after the InitializeComponent() call.

每当您使用ItemsControl(以及其扩展,如ListBox、TreeView、TabControl等)时,都不应该实例化自己的项视图。您始终希望设置一个模板,该模板根据数据(或视图模型)实例化视图,并直接绑定到ItemsSource属性中的数据(或视图模型)。这样可以为所有项的数据上下文设置项,以便您可以绑定它们。
编辑:由于您有多个视图/视图模型配对,因此您需要稍微不同地定义模板:
<TabControl ItemsSource="{Binding PageViews}" SelectedIndex="0">
    <TabControl.Resources>
        <DataTemplate DataType="{x:Type namespace:MembersViewModel}">
            <namespace:MembersView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type namespace:ClassesViewModel}">
            <namespace:ClassesView />
        </DataTemplate>
        <DataTemplate DataType="{x:Type namespace:SessionsViewModel}">
            <namespace:SessionsView />
        </DataTemplate>
    </TabControl.Resources>
    <TabControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding TabTitle}}" />
        </DataTemplate>    
    </TabControl.ItemTemplate>
</TabControl>

区别在于你希望为每种类型定义多个数据模板,放在资源中。这意味着每当遇到那些类型时它会使用这些模板。你仍然想设置ItemTemplate来强制制表符标题使用特定的模板。但是,不要设置ContentTemplate,允许内容使用在资源中定义的数据模板。
我希望这有意义。
P.S. 如果你想让它适用于你使用这些视图模型的所有内容呈现者而不仅仅是在这个TabControl中,则还可以将这些数据模板定义在更高级别的资源字典中,比如在你的主窗口或应用程序中。

好的,现在我已经在ApplicationView中得到了以下内容: 这显示了第一个选项卡的视图(MemberView),如何设置每个选项卡的视图而不必逐个定义它们?(PageViewModels返回一个VM集合) - Jonathan Twite
你的类设置肯定有些地方我没有理解。在内容模板中,你指定的 TabbedViewModelBase 类型是什么?我原以为类型是 MembersViewModel。只有对你正在做的事情有部分了解,很难回答你的问题。 - Xavier
ApplicationViewModelApplicationView 是主应用程序。目前,ApplicationView 只包含一个 TabControl。 我希望每个选项卡都包含一个子视图 (MemberView, ClassesView, SessionsView),每个子视图都有一个相应的 ViewModel (MemberViewModel 等)。 为了拥有这些子视图模型的集合,每个模型都扩展了 TabbedViewModelBase (它只实现了 INotifyPropertyChanged 并定义了一个 TabTitle 属性)。ApplicationViewModel 现在提供一个 ObservableCollection<TabbedViewModelBase> PageViews,供 TabControl 绑定。 - Jonathan Twite
好的,现在我明白了。所以你有三个不同的视图/视图模型配对。因此,我们需要稍微不同地定义内容模板。我更新了我的答案。 - Xavier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接