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

时间:2015-05-24 19:53:46

标签: c# wpf mvvm tabcontrol

我有一个主视图(Window)的wpf程序,其中包含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>

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
}

我确信这很简单...

1 个答案:

答案 0 :(得分:1)

You are binding the TabControl to a collection of type UserControl. That means the data context for each item will be of type UserControl. There is no property named "TabTitle" in UserControl, so the binding will not work.

I think what you are trying to do can be accomplished with the following changes:

  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.

Anytime you are working with ItemsControl (and its extensions such as ListBox, TreeView, TabControl, etc.), you should never be instantiating your own item views. You always want to setup a template that instantiates the view based on the data (or view model) and bind directly to the data (or view model) in the ItemsSource property. This allows all of the item's data contexts to be setup for you so you can bind to them.

Edit: Since you have multiple view / viewmodel pairings, you will want to define your templates slightly differently:

<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>

The difference is that you want to define multiple data templates, one for each type, in your resources. That means it will use those templates each time it encounters those types. You still want to set ItemTemplate to force the tab headers to use a specific template. However, do not set ContentTemplate, allowing the content to use the data templates defined in resources.

I hope that makes sense.

P.S. You can also define these data templates in a higher level resource dictionary, such as in your main window or your application, if you want them to apply to content presenters every place you use those view models, rather than only in this one TabControl.

相关问题