c# mvvm bind views to tabcontrol with header

对着背影说爱祢 提交于 2019-12-23 15:55:02

问题


I have a wpf programme with a main View (Window)which contains a TabControl to show several different UserControl Views (the sub-views, one in each tab). Every View has an associated ViewModel.

I wish to bind the TabControl so that I just need to load a new sub-view into the ApplicationViewModel and it will appear on the TabControl.

I have successfully bound the sub-views to the content, but cannot seem to get anything in the header. I wish to bind the header to a property in the sub-view's ViewModel, specifically TabTitle.

Application View (DataTemplate binding not working):

<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 basically implements 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;
        }
    }

The MembersView Code behind:

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

MembersViewModel (truncated):

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

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

    //All view properties
}

I'm sure that it is something simple...


回答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.



来源:https://stackoverflow.com/questions/30427706/c-sharp-mvvm-bind-views-to-tabcontrol-with-header

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!