In WPF with MVVM it's easy to fire some code when the user changes the tab.
<TabControl Margin="0 5 5 5" Background="#66F9F9F9" SelectedIndex="{Binding TabIndex}">
And then in the ViewModel:
private int _tabIndex;
public int TabIndex
{
get { return _tabIndex; }
set
{
if(_tabIndex != value)
{
_tabIndex = value;
OnPropertyChanged("TabIndex");
if(value == 1)
{
//do something
}
}
}
}
But I'm vaguely uncomfortable with this. What if another developer happens along later and adds another tab in the "1" position. If this is application-critical code (which it is), things will break spectacularly.
Danger can be minimized with unit tests, of course. But it made me wonder: is this seen as bad practice? And is there a way of doing this that allows you to refer to the Tab with a string, rather than an int? I tried noodling with binding to the SelectedValue
property, but nothing seemed to happen when the tabs were changed.
As with all collection controls, the best way to maintain the selected item is to use the SelectedItem
property. If you data bind a property of the relevant data type to the TabControl.SelectedItem
property, then you'll still be able to tell which tab is selected and select a different one from the view model.
The only problem with this method is that you'll also need to use the TabControl.ItemsSource
property to set up the TabItem
s:
<TabControl ItemsSource="{Binding YourDataItems}" SelectedItem="{Binding YourItem}" />
If you want to try this, then you should know that defining the TabItem
s can be a little bit confusing. Please refer to the answer from the How to bind items of a TabControl to an observable collection in wpf? question for help with that.
You could make a behavior for TabItem, listening for changes to the IsSelected dependency property, and raises a Command when the tab is selected. This can be extended to any number of tabs, each which invokes different commands in the viewmodel. You could also supply a command parameter for any optional context:
class TabSelectedBehavior : Behavior<TabItem>
{
public static readonly DependencyProperty SelectedCommandProperty = DependencyProperty.Register("SelectedCommand", typeof(ICommand), typeof(TabSelectedBehavior));
public ICommand SelectedCommand
{
get { return (ICommand)GetValue(SelectedCommandProperty); }
set { SetValue(SelectedCommandProperty, value); }
}
private EventHandler _selectedHandler;
protected override void OnAttached()
{
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(TabItem.IsSelectedProperty, typeof(TabItem));
if (dpd != null)
{
_selectedHandler = new EventHandler(AssociatedObject_SelectedChanged);
dpd.AddValueChanged(AssociatedObject, _selectedHandler);
}
base.OnAttached();
}
protected override void OnDetaching()
{
DependencyPropertyDescriptor dpd = DependencyPropertyDescriptor.FromProperty(TabItem.IsSelectedProperty, typeof(TabItem));
if (dpd != null && _selectedHandler != null)
{
dpd.RemoveValueChanged(AssociatedObject, _selectedHandler);
}
base.OnDetaching();
}
void AssociatedObject_SelectedChanged(object sender, EventArgs e)
{
if (AssociatedObject.IsSelected)
{
if (SelectedCommand != null)
{
SelectedCommand.Execute(null);
}
}
}
}
XAML
<TabControl>
<TabItem Header="TabItem1">
<i:Interaction.Behaviors>
<local:TabSelectedBehavior SelectedCommand="{Binding TabSelectedCommand}"/>
</i:Interaction.Behaviors>
</TabItem>
<TabItem Header="TabItem2">
</TabItem>
</TabControl>
In a similar fashion you could also make a behavior for the TabControl, which turns the SelectionChanged event into a command, and pass the Tag object of the selected TabItem as command parameter.