In this post I'm going to bind a collection of item to a TabControl with MVVM pattern.
Prerequisites : Data binding,
1. Add a new project
2. Add new folders called "ViewModels" and "Infrastructure"
3. Create a new class called "ViewModelBase" to the "Infrastructure" folder and add the below code.
ViewModelBase is the base class for all view model. It is inherited by INotifyPropertyChanged and has implemented the NotifyPropertyChanged method. So whenever there is an update for the viewmodel, it notifies the UI and updates.
public class ViewModelBase : INotifyPropertyChanged
{
/// <summary>
/// Occurs when a property value changes.
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;
/// <summary>
/// Notifies the property changed.
/// </summary>
/// <param name="propertyName">Name of the property.</param>
protected void NotifyPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
4. Add a class called "RelayCommand" to the "Infrastructure" folder and add the code below.
This is responsible for commands for the view model.
public class RelayCommand : ICommand
{
/// <summary>
/// Initializes a new instance of the <see cref="RelayCommand"/> class.
/// </summary>
/// <param name="execute">The execute.</param>
public RelayCommand(Action<object> execute)
: this(execute, null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="RelayCommand"/> class.
/// </summary>
/// <param name="execute">The execute.</param>
/// <param name="canExecute">The can execute.</param>
public RelayCommand(Action<object> execute, Predicate<object> canExecute)
{
if (execute == null)
throw new ArgumentNullException("execute");
_execute = execute;
_canExecute = canExecute;
}
/// <summary>
/// Defines the method that determines whether the command can execute in its current state.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
/// <returns>
/// true if this command can be executed; otherwise, false.
/// </returns>
public bool CanExecute(object parameter)
{
return _canExecute == null ? true : _canExecute(parameter);
}
/// <summary>
/// Occurs when changes occur that affect whether or not the command should execute.
/// </summary>
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
/// <summary>
/// Defines the method to be called when the command is invoked.
/// </summary>
/// <param name="parameter">Data used by the command. If the command does not require data to be passed, this object can be set to null.</param>
public void Execute(object parameter)
{
_execute(parameter);
}
/// <summary>
/// Action
/// </summary>
private readonly Action<object> _execute;
/// <summary>
/// Predicate
/// </summary>
private readonly Predicate<object> _canExecute;
}
5. Add another view model called "MyTabItem" to represent a tab and add the code below. (Inherit if from the ViewModelBase class)
public class MyTabItem : ViewModelBase
{
#region Properties
public string Header { get; set; }
public string Content { get; set; }
public string closeButtonVisibility { get; set; }
public string CloseButtonVisibility
{
get
{
return closeButtonVisibility;
}
set
{
closeButtonVisibility = value;
NotifyPropertyChanged("CloseButtonVisibility");
}
}
#endregion
}
Here the CloseButtonVisibility property will be bound to the visibility of the button which will be appeared in the tab header. And you can see this property is observable. Therefore whenever the property is changed, it will affect to the visibility of the closing button of the tab.
6. Now add a view model class for the MainWindow.xaml called "MainViewModel" to the "ViewModels" folder and inherit it from the ViewModelBase class.
a. Create a MyTabItem ObservableCollection
This property will be bound to the ItemsSource property of the TabControl.
If you have three items in the collection then three tabs will be appeared.
If you add a new item to the collection, a new tab will be appeared.
If you remove an item, that tab will be disappeared.
b. Add a SelectedTab property
This property will be bound to the "SelectedItem" of the TabControl
If you wanna select a tab, then assign the tab to this property. And if you need the selected tab, you can get it from this property.
c. Now add "CloseTabCommand"
This Command will be bound to to the Command property of the close button of the tab item.
CloseTab method will be executed whenever the close button is clicked.
d. Add "SelectedChangedCommand" Command
This Command will be bound to "SelectionChanged" event of the tab.
I have implemented the ChangeSelected method so that whenever the tab is selected the close button will be displayed for that tab. (for tab1, this will not happen)
e. Add some dummy data in the constructor
Here tow tabs are created and the tab1 is selected.
OK. Now its time to bind the UI.
f. Please change your XAML to following
<Window x:Class="TabControl.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
Title="MainWindow" Height="350" Width="525">
<Grid Name="mainGrid">
<TabControl
Height="211"
HorizontalAlignment="Left"
Margin="12,20,0,0"
Name="tabControl1"
VerticalAlignment="Top"
Width="479"
ItemsSource="{Binding Tabs}"
SelectedItem="{Binding SelectedTab}">
<!--Bind the SelectionChanged event of the tab-->
<i:Interaction.Triggers>
<i:EventTrigger EventName="SelectionChanged">
<i:InvokeCommandAction Command="{Binding SelectedChangedCommand}" />
</i:EventTrigger>
</i:Interaction.Triggers>
<!--Header section-->
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock Text="{Binding Header}" Margin="0 0 20 0"/>
<Button Content="X"
Command="{Binding RelativeSource=
{RelativeSource FindAncestor,
AncestorType={x:Type TabControl}},
Path= DataContext.CloseTabCommand}"
Width="10" Height="10" FontWeight="UltraBold" Background="#FF6699 " FontSize="5"
Foreground="White"
Visibility="{Binding CloseButtonVisibility}"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<!--Content section-->
<TabControl.ContentTemplate>
<DataTemplate>
<TextBlock
Text="{Binding Content}" />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</Grid>
</Window>
you have to add
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity".
To have this you have to install a redistributable. you can download it from the following link
http://www.microsoft.com/en-us/download/details.aspx?id=10801
g. Final thing.. Add the following code inside the MainWindow constructor (which is in Code behind class)
mainGrid.DataContext = new MainViewModel();
h. Now run the program and see the results.
Well.. Here is my MainViewModel class
public class MainViewModel : ViewModelBase
{
#region Enums
/// <summary>
/// Close button visibility options for the tab control.
/// </summary>
public enum Visibility
{
/// <summary>
/// Close button is hidden.
/// </summary>
Hidden,
/// <summary>
/// Close button is visible.
/// </summary>
Visible
}
#endregion
#region Declarations
private ObservableCollection<MyTabItem> tabs;
private MyTabItem selectedTab;
private ICommand addTabCommand;
private ICommand closeTabCommand;
private ICommand selectedChangedCommand;
#endregion
#region Properties
/// <summary>
/// Gets or sets the tabs.
/// </summary>
/// <value>The tabs.</value>
public ObservableCollection<MyTabItem> Tabs
{
get
{
return tabs;
}
set
{
tabs = value;
NotifyPropertyChanged("Tabs");
}
}
/// <summary>
/// Gets or sets the selected tab.
/// </summary>
/// <value>The selected tab.</value>
public MyTabItem SelectedTab
{
get
{
return selectedTab;
}
set
{
selectedTab = value;
NotifyPropertyChanged("SelectedTab");
}
}
#endregion
#region Commands
/// <summary>
/// Gets the add tab command.
/// </summary>
/// <value>The add tab command.</value>
public ICommand AddTabCommand
{
get
{
if (addTabCommand == null)
{
addTabCommand = new RelayCommand(param => this.AddTab(),
null);
}
return addTabCommand;
}
}
/// <summary>
/// Gets the close tab command.
/// </summary>
/// <value>The close tab command.</value>
public ICommand CloseTabCommand
{
get
{
if (closeTabCommand == null)
{
closeTabCommand = new RelayCommand(param => this.CloseTab(),
null);
}
return closeTabCommand;
}
}
/// <summary>
/// Gets the selected changed command.
/// </summary>
/// <value>The selected changed command.</value>
public ICommand SelectedChangedCommand
{
get
{
if (selectedChangedCommand == null)
{
selectedChangedCommand = new RelayCommand(param => this.ChangeSelected(),
null);
}
return selectedChangedCommand;
}
}
#endregion
#region Constructors
/// <summary>
/// Initializes a new instance of the <see cref="MainViewModel"/> class.
/// </summary>
public MainViewModel()
{
this.Tabs = new ObservableCollection<MyTabItem>();
//Create sample data for the tabs
MyTabItem tab1 = new MyTabItem();
tab1.Header = "tab1";
tab1.Content = "Tab 1 content";
tab1.CloseButtonVisibility = Visibility.Hidden.ToString();
this.Tabs.Add(tab1);
MyTabItem tab2 = new MyTabItem();
tab2.Header = "tab2";
tab2.Content = "Tab 2 content";
tab2.CloseButtonVisibility = Visibility.Hidden.ToString();
this.Tabs.Add(tab2);
this.SelectedTab = tab1;
}
#endregion
#region Private Methods
/// <summary>
/// Adds the tab.
/// </summary>
private void AddTab()
{
//check whether the tab3 exists
bool doesTab2Exist = Tabs.Select(t => t.Header).Contains("tab3");
//If tab2 doesn't exist
if (!doesTab2Exist)
{
MyTabItem tab3 = new MyTabItem();
tab3.Header = "tab3";
tab3.Content = "Tab 3 content";
tab3.CloseButtonVisibility = Visibility.Visible.ToString();
this.Tabs.Add(tab3);
}
}
/// <summary>
/// Closes the tab.
/// </summary>
private void CloseTab()
{
this.Tabs.Remove(this.SelectedTab);
}
/// <summary>
/// Changes the selected.
/// </summary>
private void ChangeSelected()
{
foreach (var tab in this.Tabs)
{
tab.CloseButtonVisibility = Visibility.Hidden.ToString();
}
if (this.SelectedTab != null && !this.SelectedTab.Header.Equals("tab1"))
{
this.Tabs.Where(tab => tab.Header.Equals(this.SelectedTab.Header)).First().CloseButtonVisibility = Visibility.Visible.ToString();
}
}
#endregion
}
Beautiful isn't it?