4

I have an entity class. This entity has lots of properties and entity's data is shown to the user in several TabItems of a TabControl. I also implement MVVM approach.

When the screen is shown to the user first, I want to bind only the active tab page controls and as the user navigates through tab pages additional separate bindings will be incurred as-needed. How can I achieve that?

Ruben Bartelink
  • 59,778
  • 26
  • 187
  • 249
mkus
  • 3,357
  • 6
  • 37
  • 45

3 Answers3

17

You don't have anything to do, that's the default behavior. The DataTemplate for a TabItem content won't be instantiated until this TabItem is selected


EDIT: here's an example:

<Window.Resources>
    <DataTemplate DataType="{x:Type vm:Page1ViewModel}">
        <v:Page1View />
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:Page3ViewModel}">
        <v:Page3View />
    </DataTemplate>
    <DataTemplate DataType="{x:Type vm:Page3ViewModel}">
        <v:Page3View />
    </DataTemplate>
</Window.Resources>

...

<TabControl ItemsSource="{Binding Pages}"
            DisplayMemberPath="Title">
</TabControl>

In the code above, the TabControl will pick the appropriate DataTemplate based on the item type, and will render it only when that item is selected.


EDIT 2: apparently you want to display the data of a single ViewModel on several pages. If you want the controls of each TabItem to lazily instantiated, you need to use the ContentTemplate property of each TabItem:

<TabControl>
    <TabItem Header="Page 1">
        <TabItem.ContentTemplate>
            <DataTemplate>
                <v:Page1View />
            </DataTemplate>
        </TabItem.ContentTemplate>
    </TabItem>
    <TabItem Header="Page 2">
        <TabItem.ContentTemplate>
            <DataTemplate>
                <v:Page2View />
            </DataTemplate>
        </TabItem.ContentTemplate>
    </TabItem>
    <TabItem Header="Page 3">
        <TabItem.ContentTemplate>
            <DataTemplate>
                <v:Page3View />
            </DataTemplate>
        </TabItem.ContentTemplate>
    </TabItem>
</TabControl>
Thomas Levesque
  • 286,951
  • 70
  • 623
  • 758
  • @Thomas : Thanks so much four your response. Can you give me some useful links about this behavior? – mkus Aug 17 '10 at 06:45
  • Unfortunately I don't have any link about it... It doesn't seem to be documented – Thomas Levesque Aug 17 '10 at 07:37
  • @Thomas : If I don't use DataTemplates for a tab item, is the same default lazy binding behaviour is effective? – mkus Aug 17 '10 at 10:28
  • No, it works only with templates. See my updated answer for an example – Thomas Levesque Aug 17 '10 at 12:34
  • @Thomas : Thanks for your detailed response. But I have only one viewmodel.How can I achieve lazy binding of the tab items? Thanks so much. – mkus Aug 17 '10 at 13:04
  • Oh, I see... you display the data of a single ViewModel on several pages, right ? In that case, rather than defining direct content in each TabItem, write a ContentTemplate for each TabItem. Sample code coming soon... – Thomas Levesque Aug 17 '10 at 13:12
  • @Thomas: I implemented as you said. But getters of all viewmodel properties are executed, although I don' t navigate through tab pages. This means that all bindings occurred and lazy binding is not occurred. – mkus Aug 18 '10 at 10:53
  • Not necessarily... If you put breakpoints in the constructors of your UserControls, you will see they are not instantiated until the tab is selected. Anyway, I don't think it deserves a downvote... – Thomas Levesque Aug 18 '10 at 11:43
  • @Thomas : My other question was about ContentTemplate usage. http://stackoverflow.com/questions/3510114/databinding-problem-in-contenttemplate-of-a-tabcontrol. I implemented your solution as a sample project. But I was not successful. Unfortunately, all getters are executed. – mkus Aug 18 '10 at 12:55
  • So the tabcontrol itemsource (Pages) is just a collection of ViewModels? – Peter Goras Feb 15 '11 at 08:05
  • One up vote is no enough. By using this technique the time WPF took to open some of our more complex forms went from 1+ second to almost instantaneous. – Hrvoje Prgeša Jun 30 '12 at 13:24
1

Marked as answer approach has one drawback - content of TabItem will be always re-rendered when selected. If it's critical - you can try this.

Community
  • 1
  • 1
1

I created this solution which works for our third party tabcontrol.

The idea is to "intercept" the datacontext when it is beeing set, save it for later and set the datacontext back to null. When the tabitem gets focus we then set the datacontext and the data will populate in the tab.

Implemented as a dependency property. Then simply set the property on the tabs that need to (do not set it on the tab that comes up as a default)

    #region SavedDataContext

    private static object GetSavedDataContext(TabItemEx tabItem)
    {
        return tabItem.GetValue(SavedDataContextProperty);
    }

    private static void SetSavedDataContext(TabItemEx tabItem, object value)
    {
        tabItem.SetValue(SavedDataContextProperty, value);
    }

    public static readonly DependencyProperty SavedDataContextProperty =
        DependencyProperty.RegisterAttached("SavedDataContext", typeof(object),
                                            typeof(Attach), new UIPropertyMetadata(null));

    #endregion

    #region LazyLoad

    public static bool GetLazyLoad(TabItemEx tabItem)
    {
        return (bool)tabItem.GetValue(LazyLoadProperty);
    }

    public static void SetLazyLoad(TabItemEx tabItem, bool value)
    {
        tabItem.SetValue(LazyLoadProperty, value);
    }

    private static readonly DependencyProperty LazyLoadProperty =
        DependencyProperty.RegisterAttached("LazyLoad", typeof(bool),
                                            typeof(Attach), new UIPropertyMetadata(false, LazyLoadPropertyChanged));

    private static void LazyLoadPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs eventArgs)
    {
        if ((bool)eventArgs.NewValue)
        {
            var tabItemEx = sender as TabItemEx;
            if (tabItemEx == null)
                return;

            tabItemEx.DataContextChanged += DataContextChanged;
            tabItemEx.GotFocus += TabGotFocus;
        }
    }


    #endregion

    private static void TabGotFocus(object sender, RoutedEventArgs e)
    {
        var tabItemEx = sender as TabItemEx;

        if (tabItemEx == null)
            return;

        tabItemEx.GotFocus -= TabGotFocus;
        tabItemEx.DataContext = GetSavedDataContext(tabItemEx);
        tabItemEx.IsSelected = true;
    }

    private static void DataContextChanged(object sender, DependencyPropertyChangedEventArgs eventArgs)
    {
        var tabItemEx = sender as TabItemEx;

        if (tabItemEx == null)
            return;

        SetSavedDataContext(tabItemEx, eventArgs.NewValue);

        tabItemEx.DataContextChanged -= DataContextChanged;
        tabItemEx.DataContext = null;
    }