c# mvvm 使用 header 将视图绑定到 tabcontrol

c# mvvm bind views to tabcontrol with header

我有一个带有主视图 (Window) 的 wpf 程序,其中包含 TabControl 以显示几个不同的 UserControl 视图(sub-views,每个选项卡一个)。每个视图都有一个关联的 ViewModel。

我希望绑定 TabControl 这样我只需要将新的 sub-view 加载到 ApplicationViewModel 中它就会出现在 TabControl.

我已成功将sub-views绑定到内容,但似乎无法在header中获取任何内容。我希望将 header 绑定到 sub-view 的 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>

Application ViewModel(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
}

我确定这很简单...

您正在将 TabControl 绑定到类型为 UserControl 的 collection。这意味着每个项目的数据上下文都属于 UserControl 类型。 UserControl 中没有名为 "TabTitle" 的 属性,因此绑定将不起作用。

我认为您可以通过以下更改来完成您正在尝试做的事情:

  1. 让 ApplicationViewModel 公开一个 collection 类型的 MembersViewModel,而不是 UserControl,并适当地填充它。
  2. 设置 ContentTemplate 以在 TabControl 中为您的项目创建视图:

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

    (将 "namespace:" 替换为包含您的控件的 xaml 导入命名空间。)

  3. 更新 TabControl 中的 ItemTemplate,使其正确绑定到视图模型:

    <TabControl.ItemTemplate>
        <DataTemplate>
            <TextBlock Text="{Binding TabTitle}}" />
        </DataTemplate>    
    </TabControl.ItemTemplate>
    
  4. 更新 MembersView 以具有无参数构造函数。视图上的 DataContext 将由 TabControl 为您设置。如果您需要从 code-behind 访问视图模型,它应该在 InitializeComponent() 调用后通过 DataContext 属性 可用。

无论何时使用 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 以强制选项卡 headers 使用特定模板。但是,不要设置ContentTemplate,允许内容使用资源中定义的数据模板。

我希望这是有道理的。

P.S。您还可以在更高级别的资源字典中定义这些数据模板,例如在您的主 window 或您的应用程序中,如果您希望它们应用于您使用这些视图模型的每个地方的内容呈现器,而不是仅在此一个 TabControl.