I'm pretty sure this has been answered somewhere, but I can't seem to find it for the life of me.
I'm trying to use a TabControl to switch between UserControls (each tab is different, so not using Items)
Here's the breakdown: I have my mainview, and 3 usercontrols. Mainview has a tab control - each tab should display a different user control.
I could easily just set the tabcontrol contect to the usercontrol using But then it isn't bound to the viewmodel, only the view.
So I'm using Conductor in my VM, and ActivateItem. Here's where it starts to get weird / frustrating. Application starts with Tab0 selected, but Tab2 (last tab) content. Click on any other tab, loads the correct ViewModel for that tab. Click back to Tab0, loads the correct content there as well.
How do I get this to stop? Also, I'd really like it if switching tabs doesn't re-initialize the viewmodel again, clearing out fields that have already been entered.
Anyways, here's some of my source, I'm going to just drop this here and work on something else before I break my mouse.
View:
<TabControl HorizontalAlignment="Stretch" VerticalAlignment="Stretch" Grid.Row ="1">
<TabItem Header="PC Information">
<Grid>
<ContentControl x:Name="LoadRemoteInfo" cal:View.Model="{Binding ActiveItem}"/>
</Grid>
</TabItem>
<TabItem Header="Remote Tools">
<Grid>
<ContentControl x:Name="LoadRemoteTools" cal:View.Model="{Binding ActiveItem}"/>
</Grid>
</TabItem>
<TabItem Header="CHRemote">
<Grid>
<ContentControl x:Name="LoadCHRemote" cal:View.Model="{Binding ActiveItem}"/>
</Grid>
</TabItem>
</TabControl>
and the ViewModel:
class MainViewModel : Conductor<object>
{
RemoteInfoViewModel remoteInfo = new RemoteInfoViewModel();
RemoteToolsViewModel remoteTools = new RemoteToolsViewModel();
CHRemoteViewModel chRemote = new CHRemoteViewModel();
public MainViewModel()
{
ActivateItem(remoteInfo);
}
public void LoadRemoteInfo()
{
ActivateItem(remoteInfo);
}
public void LoadRemoteTools()
{
ActivateItem(remoteTools);
}
public void LoadCHRemote()
{
ActivateItem(chRemote);
}
}
May I suggest a tad different route?
It's something that I have been successfully doing in master-details scenarios. Let's say you have a collection of child view models. I'll prepare a marker interface for all those items, of course you can add properties/methods you see fit if there are such methods that span all child view models:
You can be quite sure that you want all your child models to be
Screen
s (or, in case of nested scenarios,Conductor
s). It makes them have the full initialization/activation/deactivation cycle available.Then, the child view models:
DisplayName
will be displayed as a header text. It's a good practice to make those classes sealed, becauseDisplayName
is a virtual property, and it's a big no-no to call virtual methods in a constructor of a class that's not sealed.Then, you can add corresponding views and set your IoC container of choice registrations - you have to register your all child view models as classes implementing the
IMainScreenTabItem
and then:Where the
MainView.xaml
is just:And it just works. It's also very nice and convenient solution if your child view models take multiple dependencies (e.g. database access, logger, validation mechanism etc), now you can have the IoC do all the heavy lifting instead of instantiating them by hand.
One thing here though: the tabs will be placed in the same order the classes are injected. If you want to have a control over the ordering, you can order them in
MainViewModel
constructor by either passing a customIComparer<IMainScreenTabItem>
or adding some property you canOrderBy
or select to theIMainScreenTabItem
interface. The default selected item will be the first one in theItems
list.Other option is to make the
MainViewModel
take three parameters:Although when you have more than 2 - 3 child view models (and you can easily get more), it's going to get messy quick.
About the 'clearing' part. The view models created by IoC confrom to the regular life-cycle: they're initialized at most once (
OnInitialize
), then deactivated each time they are navigated away fromOnDeactivate(bool)
and activated when they're navigated to (OnActivate
). Thebool
parameter inOnDeactivate
indicates whether the view model is just deactivated or completely 'closed' (e.g. when you close the dialog window and navigate away). If you completely close the view model, it will be re-initialized next time it's shown.That means that any bound data will be retained between
OnActivate
calls and you'd have to explicitly clear it inOnDeactivate
. What's more, if you keep the strong reference to your child view models, then even after you callOnDeactivate(true)
, the data will still be there on next initialization - that's because IoC injected view models are created once (unless you inject the factory function in a form ofFunc<YourViewModel>
), and then initialized/activated/deactivated on demand.EDIT
About the bootstrapper, I'm not quite sure what kind of IoC container you're using. My sample uses SimpleInjector, but you can do the same just as easily with e.g. Autofac:
Note the
viewModels
registration inConfigure
.