Binding ContentControl to an ObservableCollection if Count == 1 - wpf

how can I bind the Content of a ContentControl to an ObservableCollection.
The control should show an object as content only if the ObservableColelction contains exactly one object (the object to be shown).
Thanks,
Walter

This is easy. Just use this DataTemplate:
<DataTemplate x:Key="ShowItemIfExactlyOneItem">
<ItemsControl x:Name="ic">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate><Grid/></ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
</ItemsControl>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Count}" Value="1">
<Setter TargetName="ic" Property="ItemsSource" Value="{Binding}" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
This is used as the ContentTemplate of your ContentControl. For example:
<Button Content="{Binding observableCollection}"
ContentTemplate="{StaticResource ShowItemIfExactlyOneItem}" />
That's all you need to do.
How it works: The template normally contains an ItemsControl with no items, which is invisible and has no size. But if the ObservableCollection that is set as Content ever has exactly one item in it (Count==1), the trigger fires and sets the ItemsSource of the ItmesControl, causing the single item to display using a Grid for a panel. The Grid template is required because the default panel (StackPanel) does not allow its content to expand to fill the available space.
Note: If you also want to specify a DataTemplate for the item itself rather than using the default template, set the "ItemTemplate" property of the ItemsControl.

+1, Good question :)
You can bind the ContentControl to an ObservableCollection<T> and WPF is smart enough to know that you are only interested in rendering one item from the collection (the 'current' item)
(Aside: this is the basis of master-detail collections in WPF, bind an ItemsControl and a ContentControl to the same collection, and set the IsSynchronizedWithCurrentItem=True on the ItemsControl)
Your question, though, asks how to render the content only if the collection contains a single item... for this, we need to utilize the fact that ObservableCollection<T> contains a public Count property, and some judicious use of DataTriggers...
Try this...
First, here's my trivial Model object, 'Customer'
public class Customer
{
public string Name { get; set; }
}
Now, a ViewModel that exposes a collection of these objects...
public class ViewModel
{
public ViewModel()
{
MyCollection = new ObservableCollection<Customer>();
// Add and remove items to check that the DataTrigger fires correctly...
MyCollection.Add(new Customer { Name = "John Smith" });
//MyCollection.Add(new Customer { Name = "Mary Smith" });
}
public ObservableCollection<Customer> MyCollection { get; private set; }
}
Set the DataContext in the Window to be an instance of the VM...
public Window1()
{
InitializeComponent();
this.DataContext = new ViewModel();
}
and here's the fun bit: the XAML to template a Customer object, and set a DataTrigger to remove the 'Invalid Count' part if (and only if) the Count is equal to 1.
<Window x:Class="WpfApplication1.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:WpfApplication1"
Title="Window1" Height="300" Width="300">
<Window.Resources>
<Style TargetType="{x:Type ContentControl}">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate x:Name="template">
<Grid>
<Grid Background="AliceBlue">
<TextBlock Text="{Binding Name}" />
</Grid>
<Grid x:Name="invalidCountGrid" Background="LightGray" Visibility="Visible">
<TextBlock
VerticalAlignment="Center" HorizontalAlignment="Center"
Text="Invalid Count" />
</Grid>
</Grid>
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Count}" Value="1">
<Setter TargetName="invalidCountGrid" Property="Visibility" Value="Collapsed" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<ContentControl
Margin="30"
Content="{Binding MyCollection}" />
</Window>
UPDATE
To get this dynamic behaviour working, there is another class that will help us... the CollectionViewSource
Update your VM to expose an ICollectionView, like:
public class ViewModel
{
public ViewModel()
{
MyCollection = new ObservableCollection<Customer>();
CollectionView = CollectionViewSource.GetDefaultView(MyCollection);
}
public ObservableCollection<Customer> MyCollection { get; private set; }
public ICollectionView CollectionView { get; private set; }
internal void Add(Customer customer)
{
MyCollection.Add(customer);
CollectionView.MoveCurrentTo(customer);
}
}
And in the Window wire a button Click event up to the new 'Add' method (You can use Commanding if you prefer, this is just as effective for now)
private void Button_Click(object sender, RoutedEventArgs e)
{
_viewModel.Add(new Customer { Name = "John Smith" });
}
Then in the XAML, without changing the Resource at all - make this the body of your Window:
<StackPanel>
<TextBlock Height="20">
<TextBlock.Text>
<MultiBinding StringFormat="{}Count: {0}">
<Binding Path="MyCollection.Count" />
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<Button Click="Button_Click" Width="80">Add</Button>
<ContentControl
Margin="30" Height="120"
Content="{Binding CollectionView}" />
</StackPanel>
So now, the Content of your ContentControl is the ICollectionView, and you can tell WPF what the current item is, using the MoveCurrentTo() method.
Note that, even though ICollectionView does not itself contain properties called 'Count' or 'Name', the platform is smart enough to use the underlying data source from the CollectionView in our Bindings...

Related

How do I make ItemsControl to use different editors for different rows in the same column (depending on a data type)?

I need to allow user specify filters applied to a search. I want UI to look something like this:
(in the picture above I manually typed "Test" and chose "Both" in ComboBox, actual binding doesn't work)
So, user could select which filters to apply and specify value using a corresponding editor (TextBox for strings, ComboBox for enums, etc).
To create this one I used DataGrid with TemplateColumn, DataTriggers and DataTemplates (it doesn't work completely as I need, that's why I'm writing this question):
<DataGridTemplateColumn Header="Value">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<ContentControl>
<ContentControl.Style>
<Style TargetType="ContentControl">
<Style.Triggers>
<DataTrigger Binding="{Binding SearchFilter.Type}" Value="string">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBox Text="{Binding SearchFilter.Value}"/>
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding SearchFilter.Type}" Value="enum">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<ComboBox ItemsSource="{wpf:EnumMembers dataModel:MyEnumType}" SelectedItem="{Binding SearchFilter.Value}" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
The DataGrid is bound to a ViewModel containing a list of these filter objects:
public class PositionSearchFilter
{
public string DisplayName { get; set; }
public object Value { get; set; }
public string Type { get; set; }
...
}
The problem with this approach is data binding doesn't work inside DataTemplate (at least in my code, maybe I'm doing something wrong), I mean this part:
<TextBox Text="{Binding SearchFilter.Value}"/>
Of course, I could manually create a bunch of controls (i.e. not using ItemsControl), but I want a generic solution, so I could simply take a list of filter objects and get a completely working UI.
Please help me solve my task.
You can create your own DataTemplateSelector that will decide which DataTemplate to use for which particular data type.
Here is an example of a DataTemplateSelector:
class ValueCellTemplateSelector : DataTemplateSelector
{
public DataTemplate StringTemplate { get; set; }
public DataTemplate EnumTemplate { get; set; }
public override DataTemplate SelectTemplate(object item, DependencyObject container)
{
if (item is PositionSearchFilter)
{
PositionSearchFilter element = (PositionSearchFilter)item;
if (element.Value is string)
{
return this.StringTemplate;
}
else if (element.Value is MyEnumType)
{
return this.EnumTemplate;
}
}
return null;
}
}
You can instantiate this selector in your resources, providing the corresponding DataTemplates at the same time:
<DataGrid.Resources>
<local:ValueCellTemplateSelector x:Key="ValueCellTemplateSelector">
<local:ValueCellTemplateSelector.StringTemplate>
<DataTemplate>
<TextBox Text="{Binding Value}"/>
</DataTemplate>
</local:ValueCellTemplateSelector.StringTemplate>
<local:ValueCellTemplateSelector.EnumTemplate>
<DataTemplate>
<ComboBox ItemsSource="{wpf:EnumMembers dataModel:MyEnumType}" SelectedItem="{Binding Value}"/>
</DataTemplate>
</local:ValueCellTemplateSelector.EnumTemplate>
</local:ValueCellTemplateSelector>
</DataGrid.Resources>
Finally, just set this selector in your column:
<DataGridTemplateColumn CellTemplateSelector="{StaticResource ValueCellTemplateSelector}"/>
Note that in my example, the DataTemplate will be choosen according to the actual object type contained in the Value property. So we don't need the additional Type property in the filter class. If it's important to you, just change the selector accordingly.
If you're inside a DataTemplate you'll need to tunnel your way out a little. Try:
<TextBox Text="{Binding SearchFilter.Value, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type DataGrid}}}" />
This should force the binding to work its way up until it gets to something of type DataGrid, then use that control's DataContext as the context for the binding.
DataGridTemplateColumn isn't part of DataGrid's logical or visual tree. To get around this and set a DataContext for binding within it you can use a binding proxy as described here: http://www.thomaslevesque.com/2011/03/21/wpf-how-to-bind-to-data-when-the-datacontext-is-not-inherited/
public class BindingProxy : Freezable
{
#region Overrides of Freezable
protected override Freezable CreateInstanceCore()
{
return new BindingProxy();
}
#endregion
public object Data
{
get { return (object)GetValue(DataProperty); }
set { SetValue(DataProperty, value); }
}
// Using a DependencyProperty as the backing store for Data. This enables animation, styling, binding, etc...
public static readonly DependencyProperty DataProperty =
DependencyProperty.Register("Data", typeof(object), typeof(BindingProxy), new UIPropertyMetadata(null));
}
In your Control/Window Resources you then define an instance of the proxy:
<UserControl.Resources>
<local:BindingProxy x:Key="proxy" Data="{Binding}" />
</UserControl.Resources>
And from your DataGridTemplateColumn you can reference the proxy for your binding:
<TextBox Text="{Binding SearchFilter.Value, Source={StaticResource proxy}}"/>

Load controls on runtime based on selection

I'm new to XAML and I have a case where I need to change controls based on a selection on a combobox with templates.
For example, let's say that a user selects a template that requires a day of week and a time range that something will be available. I would like that, on the moment of the selection, the control with the information needed get build on the screen and that the bindings get to work as well.
Can someone give me a hint or indicate an article with an elegant way to do so?
Thanks in advance.
The solution you are looking for is a ContentControl and DataTemplates. You use the selected item of the ComboBox to change ContentTemplate of the Content Control.
You question mentions binding so I will assume you understand the MVVM pattern.
As an example, lets use MyModel1 as the Model
public class MyModel1
{
private Collection<string> values;
public Collection<string> Values { get { return values ?? (values = new Collection<string> { "One", "Two" }); } }
public string Field1 { get; set; }
public string Field2 { get; set; }
}
And MyViewModel as the ViewModel
public class MyViewModel
{
public MyViewModel()
{
Model = new MyModel1();
}
public MyModel1 Model { get; set; }
}
And the code behind does nothing but instantiate the ViewModel.
public partial class MainWindow : Window
{
public MainWindow()
{
ViewModel = new MyViewModel();
InitializeComponent();
}
public MyViewModel ViewModel { get; set; }
}
All three are very simple classes. The fun comes in the Xaml which is
<Window x:Class="StackOverflow._20893945.MainWindow" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:system="clr-namespace:System;assembly=mscorlib"
xmlns:this="clr-namespace:StackOverflow._20893945"
DataContext="{Binding RelativeSource={RelativeSource Self}, Path=ViewModel}"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="MyModel1Template1" DataType="{x:Type this:MyModel1}">
<StackPanel>
<TextBlock Text="Template 1"></TextBlock>
<ComboBox ItemsSource="{Binding Path=Values}" SelectedItem="{Binding Path=Field1}" />
</StackPanel>
</DataTemplate>
<DataTemplate x:Key="MyModel1Template2" DataType="{x:Type this:MyModel1}">
<StackPanel>
<TextBlock Text="Template 2"></TextBlock>
<TextBox Text="{Binding Path=Field2}" />
</StackPanel>
</DataTemplate>
</Window.Resources>
<DockPanel>
<StackPanel Orientation="Horizontal" DockPanel.Dock="Top" Margin="2">
<ComboBox x:Name="TypeSelector">
<system:String>Template 1</system:String>
<system:String>Template 2</system:String>
</ComboBox>
</StackPanel>
<ContentControl Content="{Binding Path=Model}">
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=TypeSelector, Path=SelectedItem}" Value="Template 2">
<Setter Property="ContentTemplate" Value="{StaticResource MyModel1Template2}" />
</DataTrigger>
</Style.Triggers>
<Setter Property="ContentTemplate" Value="{StaticResource MyModel1Template1}" />
</Style>
</ContentControl.Style>
</ContentControl>
</DockPanel>
</Window>
The notable points of the view are
The DataContext is initialised on the Window element, allowing for auto-complete on our binding expressions
The definition of 2 template to display 2 different views of the data.
The ComboBox is populated with a list of strings and has a default selection of the first element.
The ContentControl has its content bound to the Model exposed via the ViewModel
The default DataTemplate is the first template with a ComboBox.
The Trigger in the ContentControl's style will change the ContentTemplate if the SelectedItem of the ComboBox is changed to 'Template 2'
Implied facts are
If the SelectedItem changes back to 'Template 1', the style will revert the the ContentTemplate back to the default, ie MyModel1Template1
If there were a need for 3 separate displays, create another DataTemplate, add a string to the ComboBox and add another DataTrigger.
NOTE: This is the complete source to my example. Create a new C#/WPF project with the same classes and past the code in. It should work.
I hope this helps.

RadioButton IsChecked property gets overridden when changing tabs

I'm sure this behavior is known, but I'm unable to google it. I have following code:
<Window x:Class="ContentControlListDataTemplateKacke.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<DockPanel>
<TabControl ItemsSource="{Binding Items}">
<TabControl.ContentTemplate>
<DataTemplate>
<StackPanel>
<Label Content="{Binding Name}" />
<RadioButton Content="Option1" IsChecked="{Binding Option1}" />
<RadioButton Content="Option2" IsChecked="{Binding Option2}" />
</StackPanel>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
</DockPanel>
</Window>
The code-behind is simple:
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
DataContext = new ViewModel();
}
}
The ViewModel looks like this:
public class ViewModel : NotificationObject
{
public ViewModel()
{
Items = new ObservableCollection<Item>
{
new Item {Name = "1", Option1 = true},
new Item {Name = "2", Option2 = true}
};
}
public ObservableCollection<Item> Items { get; set; }
}
And an Item like this:
public class Item : NotificationObject
{
public string Name { get; set; }
private bool _option1;
public bool Option1
{
get { return _option1; }
set
{
_option1 = value;
RaisePropertyChanged(() => Option1);
}
}
private bool _option2;
public bool Option2
{
get { return _option2; }
set
{
_option2 = value;
RaisePropertyChanged(() => Option2);
}
}
}
I'm using Prism, so the RaisePropertyChanged raises an PropertyChanged-event. Select the second tab, then the first tab, then the second tab again and voilá, the RadioButtons on the second tab are deselected.
Why?
Another solution apart from Rachels
A colleague of mine just had the idea to bind the GroupName property of the RadioButtons to a unique string of each item. Just change the declaration of the RadioButtons into this:
<RadioButton GroupName="{Binding Name}" Content="Option1" IsChecked="{Binding Option1}" />
<RadioButton GroupName="{Binding Name}" Content="Option2" IsChecked="{Binding Option2}" />
And it works if the Name-property is unique for all items (as its the case for my problem).
WPF is reading all the RadioButtons as part of the same Group, and in a radio button group only one item can be selected at a time.
The load order goes:
Load Tab1
Load Tab1.Radio1. IsChecked = True
Load Tab1.Radio2. IsChecked = True, so set Tab1.Radio2.IsChecked = False
Click Tab 2
Load Tab2
Load Tab2.Radio1. IsChecked = True, so set Tab1.Radio2.IsChecked = False
Load Tab2.Radio2. IsChecked = True, so set Tab2.Radio1.IsChecked = False
By now, Tab2.Radio2 is the only one checked, and all the other Radios have been loaded and Unchecked, so their DataBound values have been updated to false.
Click Tab 1
Load Tab1.Radio1. IsChecked = False
Load Tab1.Radio2. IsChecked = False
If you Radio buttons are unrelated and can both be checked at once, I would suggest switching to CheckBoxes
If they're meant to be grouped and only one item can be selected at a time, I'd suggest switching to a ListBox drawn with RadioButtons, and only storing the SelectedOption in your ViewModel
Here's the style I typically use for that:
<Style x:Key="RadioButtonListBoxStyle" TargetType="{x:Type ListBox}">
<Setter Property="BorderBrush" Value="Transparent"/>
<Setter Property="KeyboardNavigation.DirectionalNavigation" Value="Cycle" />
<Setter Property="ItemContainerStyle">
<Setter.Value>
<Style TargetType="{x:Type ListBoxItem}" >
<Setter Property="Margin" Value="2, 2, 2, 0" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate>
<Border Background="Transparent">
<RadioButton
Content="{TemplateBinding ContentPresenter.Content}" VerticalAlignment="Center"
IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
It's used like this:
<ListBox ItemsSource="{Binding Options}"
SelectedValue="{Binding SelectedValue}"
Style="{StaticResource RadioButtonListBoxStyle}" />
I used to have a problem very similar to this one where text was getting cleared when I was switching between views. If I recall correctly the following solution was what worked.
Bind OneWay to the properties and mark these bound properties with an attribute.
Every time you load the view (and hence viewmodel), use reflection on the aforementioned attribute to find the bound properties.
Fire off a PropertyChanged event for each of the properties to update the view correctly.
I think this results from the view loading with default settings and not querying the properties on load since nothing is raising a PropertyChanged event.
Also, it's not part of your question, but you can set the data context in XAML (via the DataContext property in Window) directly so that Visual Studio doesn't have to have an explicit codebehind file.

Change control in a DataGridRow programmatically

I have a WPF DataGrid contains a list of Products with an image button in the last column to add the Product item into the Orders_Products collection. That works.
Now I'd like to change the "add" image button with a "delete" image button if the Product item is already in the Orders_Products collection.
I tried to use the LoadingRow event but it seems there's no way to access the Image object because is not ready yet in the LoadingRow event...
I tried the Load event of the Image object, but it's not fired if the row is not visible in the form (when I must scroll the datagrid to see that row). It fires when I sort a column and the row is directly visible in the form. I'm going crazy... :(
I think I'm not doing something unusual but I'm new to WPF and probably I miss something.
Can someone give me an hint?
Thanks in advance,
Joe
P.S.: I'm using Entity Framework.
The proper way of doing this is by binding the DataGrid to the collection of objects you want to display (you are probably doing this already).
Then, manually define a DataGridTemplateColumn and set its CellTemplate to an appropriate DataTemplate (this would usually be defined as a resource in your DataGrid or somewhere higher in the logical tree of elements).
You can see an example of how the above is done here.
Inside the DataTemplate, use the technique described in my answer to this question to vary what is displayed in the template by matching the value of an appropriate property in the databound Product.
All of this can be done entirely in XAML, as is the preferred way of doing things in WPF.
Update (working example)
The Product:
public class Product
{
public string Name { get; set; }
public bool Exists { get; set; }
}
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
this.Products = new List<Product>
{
new Product { Name = "Existing product", Exists = true },
new Product { Name = "This one does not exist", Exists = false },
};
InitializeComponent();
this.DataContext = this;
}
public IEnumerable<Product> Products { get; set; }
}
MainWindow.xaml:
<Window x:Class="SandboxWPF.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" Title="MainWindow" Height="350" Width="525">
<Grid>
<Grid.Resources>
<DataTemplate x:Key="ButtonColumnTemplate" >
<ContentControl x:Name="MyContentControl" Content="{Binding}" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Exists}" Value="True">
<Setter TargetName="MyContentControl" Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Your Remove product button goes here" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Exists}" Value="False">
<Setter TargetName="MyContentControl" Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<TextBlock Text="Your Add product button goes here" />
</DataTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</Grid.Resources>
<DataGrid ItemsSource="{Binding Products}" AutoGenerateColumns="False" CanUserAddRows="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Product Name" Binding="{Binding Name}" />
<DataGridTemplateColumn Header="Add/Remove" CellTemplate="{StaticResource ButtonColumnTemplate}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>

How to add new user control in TabControl.ContentTemplate?

I am little stuck with adding new instances of a usercontrol in a TabControl.ContentTemplate?
My Xaml is here:
<TabControl ItemsSource="{Binding Tables}">
<TabControl.ItemTemplate>
<DataTemplate>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate DataType="{x:Type uc:mytest1}">
<uc:mytest1>
</uc:mytest1>
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>
I am binding TabControl.ItemsSource property to an ObservableCollection and in the content template I am adding a user control, but when this app runs I am getting new items as TabItems but the content page is holding same user control, but I want new user controls to be added for each new TabItem.
I am very new to the WPF and may be I am doing a very basic mistake, kindly guide me.
The ControlTemplate determines the appearance of the elements of the tab control that are not part of the individual tab items. The ItemTemplate handles the content of the individual tab items. Additionally, a TabItem is a headered content control, which means it has two content type properties Content and Header with two separate templates ContentTemplate and HeaderTemplate. In order to be able to populate the tab items using binding, you need to style the TabItem using the above properties.
Example:
<Window x:Class="Example.Window2"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Name="Window"
Title="Window2" Height="300" Width="300">
<Window.DataContext>
<Binding ElementName="Window" Path="VM"/>
</Window.DataContext>
<Window.Resources>
<DataTemplate x:Key="TabItemHeaderTemplate">
<Grid>
<TextBlock Text="{Binding Header}"/>
<Ellipse Fill="Red" Width="40" Height="40" Margin="0,20,0,0"/>
</Grid>
</DataTemplate>
<DataTemplate x:Key="TabItemContentTemplate">
<Ellipse Fill="Green"/>
</DataTemplate>
<Style x:Key="TabItemContainerStyle" TargetType="TabItem">
<Setter Property="Header" Value="{Binding}"/>
<Setter Property="HeaderTemplate"
Value="{StaticResource TabItemHeaderTemplate}"/>
<Setter Property="Content" Value="{Binding}"/>
<Setter Property="ContentTemplate"
Value="{StaticResource TabItemContentTemplate}"/>
</Style>
</Window.Resources>
<Grid>
<TabControl ItemsSource="{Binding Items}"
ItemContainerStyle="{StaticResource TabItemContainerStyle}"/>
</Grid>
</Window>
The code behind:
public partial class Window2 : Window
{
public TabControlVM VM { get; set; }
public Window2()
{
VM = new TabControlVM();
InitializeComponent();
}
}
And the view model classes:
public class TabControlVM
{
public ObservableCollection<TabItemVM> Items { get; set; }
public TabControlVM()
{
Items = new ObservableCollection<TabItemVM>();
Items.Add(new TabItemVM("tabitem1"));
Items.Add(new TabItemVM("tabitem2"));
Items.Add(new TabItemVM("tabitem3"));
Items.Add(new TabItemVM("tabitem4"));
}
}
public class TabItemVM
{
public string Header { get; set; }
public TabItemVM(string header)
{
Header = header;
}
}
Saurabh, When you set Template, usually DataTemplate, ControlTemplate etc, the visual elements inside these templates are reused in WPF with concept of UI Virtualization. TabControl typically displays only one item at a time, so it does not create new Visual Item for every tab item, instead it only changes that DataContext and refreshes bindings of "Selected Visual Item". Its loaded/unloaded events are fired, but the object is same always.
You can use loaded/unload events and write your code accordingly that your "Visual Element" which is your usercontrol, so that control should be stateless and is not dependent on old data. When new DataContext has applied you should refresh everything.
DataContextChanged, Loaded and Unloaded events can help you remove all dependencies on old data.
Otherwise, you an create a new TabItem manually with your UserControl as its Child and add it in TabControl instead of adding Data Items.
Adding TabItems manually will create new control for every item and in selected area different elements will appear based on selection.

Resources