Jeremy D Jeremy D - 2 months ago 17
C# Question

How can I lazy load my view model properties when bound to a tab control?

I'm using MVVM in my WPF application in a master/detail type scenario where I have a list of search results (the master). When a user selects a result I call Refresh and pass the database key to load the details. The details is a view model with some complex properties. Each of these properties has a list of items (representing data in a database) along with some other ICommands and properties.

My view has a tab control where each tab displays the data for one of these complex properties. I only want to load the list of the complex properties (call the LoadItems() method) if the tab item used to view the data is selected (visible?).

Here is an abbreviated example:

public class MyItem{
public string Id{get;set;}
public DateTime Timestamp{get;set;}
}

public class ComplexClass:INotifyPropertyChanged
{
private Guid _key;
private List<MyItem> _items;

public string Name{get;set;}
public List<MyItem> Items
{
get{
if (_items == null) LoadItems(_key);
return _items;
}
set{
if(Equals(value, _items) return;
_item = value;
OnPropertyChanged("Items");
}
}
public void LoadItems(Guid key){
... //code to load Items from database based on the key.
}
public ComplexClass(string name, Guid key){
Name = name;
_key = key;
}
}

public class MyViewModel:INotifyPropertyChanged
{
private ComplexClass _currentItems;
public ComplexClass CurrentItems
{
get{return _currentItems;}
set{
if(Equals(value, _currentItems) return;
_currentItem = value;
OnPropertyChanged("CurrentItems");
}
}
private ComplexClass _historyItems;
public ComplexClass HistoryItems
{
get{return _historyItems;}
set{
if(Equals(value, _historyItems) return;
_historyItems= value;
OnPropertyChanged("HistoryItems");
}
}

public void Refresh(Guid key){
CurrentItems = new ComplexClass("Foo", key);
HistoryItems = new ComplexClass("Bar", key);
}
}


And here is some example XAML (assume the TabControl's DataContex is an instance of the ViewModel).

...
<TabControl>
<TabItem Header="Current Items"
DataContext={Binding CurrentItems}>
...
<ItemsControl ItemsSource={Binding Items}>
....
</ItemsControl>
</TabItem>
<TabItem Header="History Items"
DataContext={Binding HistoryItems}>
...
<ItemsControl ItemsSource={Binding Items}>
....
</ItemsControl>
</TabItem>
</TabControl>


This works the first time. The HistoryItems.Items Getter doesn't get called until the "History Items" tab is selected.

But if I call Refresh on the ViewModel instance it calls the Items getter on both CurrentItems and HistoryItems regardless of which tab is selected.

Basically I want to improve performance by only loading the data needed. In most cases users will either scroll through search results (calling ViewModel.Refresh) looking at the current items or the history. I only want to load both if the user changes tabs.

Is there any way that after I call Refresh I can only call LoadItems for either CurrentItems or HistoryItems based on which tab is selected?

Answer

There's a IsSelected property on TabItem element.

Create two bool properties in your view model. Bind the same property to both TabItem element's IsSelected property.

Call the LoadItems() in respective set method for the selected tab.

Hope that solves your problem :-)