I dealing with a problem that I am hoping one of you has solved elegantly already and can point me to a solution.
I have a tab control in a parent view with a set of child views in the tabs. It is being done with MVVM, with the parent view model containing a collection of child view models which the tab control is bound to:
class MainWindowViewModel
{
public MainWindowViewModel()
{
ChildViewModels = new ObservableCollection<ViewModelBase>
{
new FooViewModel(), new BarViewModel()
};
}
public ObservableCollection<ViewModelBase> ChildViewModels { get; set; }
}
<Window.DataContext>
<my:MainWindowViewModel />
</Window.DataContext>
<Grid>
<TabControl ItemsSource="{Binding ChildViewModels}"/>
</Grid>
The child view models derive from the ViewModelBase class that the collection contains.
The child views are being married to their view models through implicit data templates:
<Window.Resources>
<DataTemplate DataType="{x:Type my:FooViewModel}">
<my:FooView />
</DataTemplate>
<DataTemplate DataType="{x:Type my:BarViewModel}">
<my:BarView />
</DataTemplate>
<Style TargetType="TabItem">
<Setter Property="Header"
Value="{Binding DisplayName}" />
</Style>
</Window.Resources>
This is all pretty standard parent-child MVVM set up for dynamic sets of child views that I’m sure most of you are familiar with.
The challenge has to do with data bindings in the child views. If you use the default binding Mode of LostFocus, then if you have an edit in progress in a TextBox or other control and you click on a different tab, the tab control never takes the focus away from the control before it releases the instance of the view (since it constructs a new instance of the data template each time it presents a tab item). This means the edit in progress in the focused control is lost when you select another tab. This problem does not happen if you statically wire up the view and view model and add the views as the items to the tab control instead of adding the view models as the items because then there is no dynamic data template instantiation that is constructing the views on each tab item change. We need to stick to the data template approach because we are dynamically injecting the views and view models with MEF for extensibility reasons, and the parent view model has to push some data into the child view models.
Obviously the quick answer to this is to use UpdateSourceTrigger=PropertyChanged on the bindings. But with the validation scheme we need for some of the controls, this is undesirable because we don’t want to annoy the user with invalid input indications until they have completed their input.
Is there some way to force the TabControl to change focus on the child view before it switches tabs when using data templates to render the view?
Thanks
Brian
-----------------------------------------
Brian Noyes
Chief Architect, IDesign Inc
Microsoft Regional Director / MVP
http://www.idesign.net
+1 703-447-3712
-----------------------------------------
Actually just did some more digging. The problem is not that it is not changing focus on the form, the problem is that it is setting the DataContext on the view to null before it changes focus, so the binding can no longer write the current value to the view model.
Awesome, thanks. That does appear to be a straightforward solution to this problem.
You guys rock.
Brian
So can anyone point me to a completed sample or something that shows the whole thing, not just pasted code in a forum thread? I’ve got it working, but had to find another sample that showed that I need a control template modification to the TabControl to create the PART_ItemsHolder it is referring to, but the sample I found then has a crappy appearance to the tabs with the simple control template it uses.
Is there a link to a ready to go TabControlEx class that is considered up to date along with whatever control template I need that doesn’t modify the appearance of the tabs at all (or at least noticeably)?
Thanks
Brian
Actually got there myself in similar time, just cracked open the default control template with Blend, found where to insert the grid in place of the content presenter, and now am up and running.
Thanks all again for the help on this one!
Brian