I still consider myself a beginner when it comes to MVVM and I am having an issue with a binding in a TabControl. My application allows the user to create nations and states, input some information and then save them to a database. Below is a descriptions of how my application is structured:
The base ViewModel/View is called ApplicationViewModel/ApplicationView. The ApplicationViewModel has an ObservableCollection called Tabs consisting of one AllNationsViewModel and one AllStatesViewModel. This Tabs property is bound to the TabControl's ItemsSource in the ApplicationView.
AllNationsViewModel/AllNationsView are used to display all the nations that have been entered by the user. It also allows the user to create new nations and select a particular nation for closer inspection. The AllStatesViewModel/AllStatesView do the same but for states.
Finally I have the NationViewModel/NationsView that deals with a particular nation; also here there are StateViewModel/StateView that do the same for a state.
For a nation you can at the moment only input a name but for a state you can input a name as well as the nation it is part of. The nation is selected using a ComboBox where all the nations created in the nations tab show up.
I use a static class called DataFacade as an interface to my data store. It is possible to add, remove and retrieve a list of nations/states using this interface; also it triggers events when something is added or removed.
The problem I am having is that when there is a state selected in the AllStatesViewModel (CurrentStateViewModel property) and I go to the nations tab and then back to the states tab the currently selected state has lost its nation. All the other states are still ok tough.
I will try to show the relevant code below (I have removed irrelevant code from some methods):
State class:
class State
{
public string Name { get; set; }
public Nation Nation { get; set; }
}
TabControl in the ApplicationView:
<TabControl ItemsSource="{Binding Tabs}" Margin="6">
<TabControl.ItemTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding DisplayName}" />
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<ContentPresenter Content="{Binding }" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
When the user creates a nation the AddCommand of the AllNationsViewModel is triggered:
private void Add(NationViewModel vm)
{
DataFacade.AddNation(vm.Nation);
}
The AllStatesViewModel gets notified when a nation gets added to the data store:
private void OnDataStoreNationsChanged(object sender, DataFacadeEventArgs e)
{
Nations.Add(new NationViewModel(e.Context as Nation));
}
The Nations property above is an ObservableCollection of NationViewModels. Now, this property is used by the ComboBox in the StateView to populate its items so a nation can be selected when creating/editing a state:
<ComboBox Grid.Row="1" Grid.Column="1" SelectedValue="{Binding Nation}" SelectedValuePath="Nation" ItemsSource="{Binding DataContext.Nations, RelativeSource={RelativeSource AncestorType={x:Type local:AllStatesView}}}">
<ComboBox.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Name}" />
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
I believe the problem has to do with the binding above. Because if I don't bind to the Nations property in AllStatesViewModel but instead bind to a temporary property in the ApplicationViewModel everything works. Can it be that the AllStatesView is thrown away by the TabControl when I go to another tab therefor the binding above sets the Nation property of my StateViewModel to null? When I debug I see that I get a null when exiting the states tab.
How would someone who is not a beginner like me solve this situation? I find my temporary solution rather ugly. I am not entirely sure how I should handle the data store access since all the MVVM examples I have found don't focus on this part.
EDIT: Addded some pictures as requested:
Atm I just have the simplest testing GUI set-up:
Here you see the AllNationsView, atm the NationView is only the "Name" TextBlock and the TextBox at the top.
Here is the AllStatesView, at the top is the currently selected state (displayed using StateView). Where you now see that USA is selected as nation for Montana, if I go to the Nations tab and then back to the States tab the nation for Montana is now blank. If I select Florida it still has USA as its nation.
WPF only keeps the UI for the active tab in memory. When you change tabs that UI is destroyed and the UI of the new tab is created and rebound.
There are a few ways around the problem you are having. You can use the Repository Pattern to store and access your data sources separate from the view models. Basically, an outside object holds your data sources, such as the lists of states and nations. That way they don't get destroyed when the active tab changes.
The other option is to store the data sources on your ApplicationViewModel
and access them via a references to the ApplicationViewModel
on each individual tab's view model. You shouldn't have to use a RelativeSource
binding anywhere in that.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With