StackPanel Dynamic Binding of Visibility and IsEnabled Properties via Converters - wpf

My small objective is to implement a dynamic on-the-go generation of controls and associate their respective properties, such as Visibility and IsEnabled.
The controls must be inserted into the StackPanel. And based on some conditions, the children nodes properties would change. I've used the following nasted StackPanel structure:
The Parent StackPanel with Vertical orientation of Children (collection of Labels ad input Fields to simulate a form)
The Child node StackPanel with Horizontal orientation of Children (Label and an input field)
The main idea is that there is only one input field per each label: Date Picker, Combo, Text or any other. As result, I created a Label and a Grid with multiple Controls. I manipulate Visibility and IsEnabled properties of the input controls via Converters.
Here is the question: is it possible to implement all these by other means (more efficient/aesthetic)? Constructive criticism and suggestions are more than welcome :)
Thank you in advance.
XAML:
<StackPanel Grid.Row="1" Orientation="Vertical">
<ItemsControl ItemsSource="{Binding DataClass}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0, 5, 0, 0" >
<Label Content="{Binding KeyName}" Width="150"/>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="190"/>
</Grid.ColumnDefinitions>
<DatePicker Text="{Binding Value}"
Visibility="{Binding Type, Converter={StaticResource TypeVisiblity}, ConverterParameter='DateTime'}"
IsEnabled="{Binding RelativeSource={x:Static RelativeSource.Self}, Converter={StaticResource VisibilityEnabled}}"/>
<ComboBox Text="{Binding Value}"
Visibility="{Binding Type, Converter={StaticResource TypeVisiblity}, ConverterParameter='Lookup'}"
IsEnabled="{Binding RelativeSource={x:Static RelativeSource.Self}, Converter={StaticResource VisibilityEnabled}}"/>
<TextBox Text="{Binding Value}"
Visibility="{Binding Type, Converter={StaticResource TypeVisiblity}, ConverterParameter='Number'}"
IsEnabled="{Binding RelativeSource={x:Static RelativeSource.Self}, Converter={StaticResource VisibilityEnabled}}"/>
<TextBox Text="{Binding Value}"
Visibility="{Binding Type, Converter={StaticResource TypeVisiblity}, ConverterParameter='Text'}"
IsEnabled="{Binding RelativeSource={x:Static RelativeSource.Self}, Converter={StaticResource VisibilityEnabled}}"/>
</Grid>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
Visibility Converter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
bool isMatched;
string type,
controlType;
// Safe Convert.
type = System.Convert.ToString(value);
controlType = System.Convert.ToString(parameter);
if (string.IsNullOrEmpty(type) || string.IsNullOrEmpty(controlType))
{
return Visibility.Hidden;
}
// Check matching.
isMatched = string.Equals(type, controlType, StringComparison.CurrentCultureIgnoreCase);
return isMatched ? Visibility.Visible : Visibility.Hidden;
}
IsEnabled Converter:
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Control control;
control = value as Control;
if (control == null)
{
return false;
}
return control.Visibility == Visibility.Visible;
}

I would use a ContentControl and set the ContentTemplate in a style
<ContentControl>
<ContentControl.Style>
<Style TargetType="{x:Type ContentControl}">
<!-- Default Template -->
<Setter Property="ContentTemplate"
Value="{DynamicResource TextBoxTemplate}" />
<Style.Triggers>
<DataTrigger Binding="{Binding Type}" Value="DateTime">
<Setter Property="ContentTemplate"
Value="{DynamicResource DateTimeTemplate}" />
</DataTrigger>
<DataTrigger Binding="{Binding Type}" Value="Lookup">
<Setter Property="ContentTemplate"
Value="{DynamicResource ComboBoxTemplate}" />
</DataTrigger>
</Style.Triggers>
</Style>
</ContentControl.Style>
</ContentControl>

Create a user control that has your DatePicker, ComboBox, TextBox etc as user interface and implement all necessary logic in code behind of this user control. You can add dependency properties to implement your logic without converters.
You can add your user control into StackPanel easily by adding it into its Children property.

Related

WPF - ToolTip in TextBlock that use the Text Property from the TextBlock

We have many TextBlocks in our application and many of them must have ToolTips.
Often the ToolTip should display the same as the TextBlock.Text property.
Also the ToolTip should not be displayed if the TextBlock.Text = "" or null.
So we have this solution in many many places:
<TextBlock Text="{Binding SomeTextProperty}">
<TextBlock.ToolTip>
<ToolTip Visibility="{Binding SomeTextProperty}, Converter={StaticResource StringToVisibilityConverter}">
<TextBlock Text="{Binding SomeTextProperty}" />
</TextBlock.ToolTip>
</TextBlock>
Notice:
I have to specify the SomeTextProperty three times on each TextBlock that need a TooLTip with this functionality.This seems very redundant!
The ToolTip.Content is a TextBlock itself.This is because I need to have a Style on the TextBlock.I have omitted that style to keep this post as simple as possible.
So I have tried to invent a Style for TextBlocks that use Bindings with RelativeSource to get the TextBlock.Text property for the ToolTip. I came up with this solution:
MainWindow (Just copy-paste more or less)
<Window x:Class="Main.Views.ToolTips"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:local="clr-namespace:Main.Views"
xmlns:converters="clr-namespace:Main.Converters"
mc:Ignorable="d"
Title="ToolTips" Height="450" Width="800">
<Window.Resources>
<Style TargetType="TextBlock" x:Key="TextBlockWithToolTipStyle">
<Style.Resources>
<converters:StringToVisibilityConverter x:Key="StringToVisibilityConverter" />
</Style.Resources>
<Setter Property="ToolTip">
<Setter.Value>
<ToolTip Visibility="{Binding Text, RelativeSource={RelativeSource AncestorType={x:Type TextBlock}}, Converter={StaticResource StringToVisibilityConverter}}">
<ToolTip.Content>
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType={x:Type TextBlock}}}" />
</ToolTip.Content>
</ToolTip>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<StackPanel Orientation="Vertical" HorizontalAlignment="Center" VerticalAlignment="Center">
<TextBlock Text="This should display a tooltip" />
<TextBlock Text="asdf"
Background="Gray"
Style="{StaticResource TextBlockWithToolTipStyle}" />
<Separator Height="50" Visibility="Hidden" />
<TextBlock Text="This should not display a tooltip" />
<TextBlock Text=""
Background="LightGray"
Style="{StaticResource TextBlockWithToolTipStyle}" />
</StackPanel>
</Window>
StringToVisibilityConverter
public class StringToVisibilityConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
var input = value is null ? string.Empty : value.ToString();
return string.IsNullOrWhiteSpace(input) ? Visibility.Collapsed : Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
This, of course, don't work!
I put a breakpoint in the StringToVisibilityConverter and that breakpoint is not hit when I hover both TextBlocks in the Window.
In VS's XAML Binding Failures view I see two binding errors that regards ToolTip.Visibility and TextBlock.Text:
ToolTip.Visibility: Cannot find source: RelativeSource FindAncestor, AncestorType='System.Windows.Controls.TextBlock', AncestorLevel='1'
TextBlock.Text: Cannot find source: RelativeSource FindAncestor, AncestorType='System.Windows.Controls.TextBlock', AncestorLevel='1'
Is this possible to solve?
If so, how?
/BR,
Steffe
The ToolTip is not part of the visual tree. That's why your Binding.RelativeSource does not resolve.
To reference the element that is decorated by the ToolTip you must reference the ToolTip.PlacementTarget property:
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=ToolTip}, Path=PlacementTarget.Text}" />
Furthermore, you should not try to toggle the ToolTip.Visibility. It won't work.
Instead use a Trigger to set the ToolTip.
The correct solution would be as followed:
<Style TargetType="TextBlock"
x:Key="TextBlockWithToolTipStyle">
<Style.Resources>
<ToolTip x:Key="ToolTip">
<TextBlock Text="{Binding RelativeSource={RelativeSource AncestorType=ToolTip}, Path=PlacementTarget.Text}" />
</ToolTip>
</Style.Resources>
<Setter Property="ToolTip"
Value="{StaticResource ToolTip}" />
<Style.Triggers>
<Trigger Property="Text" Value="">
<Setter Property="ToolTip" Value="{x:Null}" />
</Trigger>
</Style.Triggers>
</Style>

Binding a DataTrigger value to this instance of DataTemplate

I have a ListView, with its items represented by an ItemTemplate like so:
<ListView dependencyObjects:InterestingItem.Interesting="{Binding InterestingItem}"
ItemsSource="{Binding Quotations}" >
<ListView.ItemTemplate>
<DataTemplate>
<Border>
<Grid>
<StackPanel x:Name="NotImportant">
</StackPanel>
<Grid x:Name="HiddenGrid"
Background="Red"
Visibility="Hidden" >
<Grid.Style>
<Style TargetType="Grid">
<Style.Triggers>
<Grid.Triggers>
<DataTrigger Binding="{Binding Path=DataContext.InterestingItem,
RelativeSource={RelativeSource AncestorType={x:Type ListView }}}"
Value="*this instance here*!">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Grid.Triggers>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
The ListView has an attached property InterestingItem that is one of the items in the ListView.
What I can't hook up is when the InterestingItem is the same as one of the items, the second Grid should become visible.
I would prefer not to change and bind to the actual objects in the list - but rather have the ListView control which item is to be altered.
What is the Value in the DataTrigger that I need?
There are multiple issues in your XAML and conceptually that prevent it from working.
To bind attached properties, you have to use the correct syntax with parentheses.
Path="{Binding (local:InterestingItem.Interesting), RelativeSource={RelativeSource AncestorType={x:Type ListView}}}"
See the Binding path syntax documenation for reference.
The Triggers property does only support EventTriggers, see FrameworkElement.Triggers.
Note that the collection of triggers established on an element only supports EventTrigger, not property triggers (Trigger). If you require property triggers, you must place these within a style or template and then assign that style or template to the element either directly through the Style property, or indirectly through an implicit style reference.
You cannot bind the Value property of DataTrigger, since it is not a dependency property.
You could of course change the bound type to expose a property that indicates if it is a special object or not and bind that in XAML using a DataTrigger, similar to this (where IsSpecial is the new bool property).
<Grid x:Name="HiddenGrid"
Background="Red">
<TextBlock Text="Hidden Grid"/>
<Grid.Style>
<Style TargetType="{x:Type Grid}">
<Setter Property="Visibility" Value="Hidden"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsSpecial}" Value="True">
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
If you want to stick to your current approach, you could create a custom IMultiValueConverter that enables binding multiple properties. It would check if all of the bound values are equal and return Visibility.Visible or Visibility.Hidden otherwise. This example uses Linq to check this and supports an arbitrary number of values bound, but there are many other options.
public class EqualityToVisibilityConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
if (values is null || values.Length < 2)
return Binding.DoNothing;
return values.Distinct().Count() == 1 ? Visibility.Visible : Visibility.Hidden;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new InvalidOperationException();
}
}
Next, instantiate the converter in the resources of the ListView or any other resource dictionary in scope and bind the Visibility property of the Grid to both the current item (just <Binding/>) and the attached property local:InterestingItem.Interesting with a MultiBinding that uses the converter to convert them to a Visibility.
<ListView local:InterestingItem.Interesting="{Binding InterestingItem}"
ItemsSource="{Binding Quotations}">
<ListView.Resources>
<local:EqualityToVisibilityConverter x:Key="EqualityToVisibilityConverter"/>
</ListView.Resources>
<ListView.ItemTemplate>
<DataTemplate>
<Border>
<Grid>
<StackPanel x:Name="NotImportant">
<TextBlock Text="Not Important"/>
</StackPanel>
<Grid x:Name="HiddenGrid"
Background="Red">
<Grid.Visibility>
<MultiBinding Converter="{StaticResource EqualityToVisibilityConverter}">
<Binding/>
<Binding Path="(local:InterestingItem.Interesting)"
RelativeSource="{RelativeSource AncestorType={x:Type ListView}}"/>
</MultiBinding>
</Grid.Visibility>
<TextBlock Text="Hidden Grid"/>
</Grid>
</Grid>
</Border>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
There are two other things to note here. I have added two dummy TextBlocks, otherwise the result will not be visible, as the panels are empty. Replace them with your content. Furthermore, both the StackPanel and the Grid are overlapping in the parent Grid, I do not know if this is intentional or not, but you can change it by adding rows or columns and moving the elements there.
What is the Value in the DataTrigger that I need?
I am afraid XAML has no support for something like the this keyword in C#.
You may use a MultiBinding with an IMultiValueConverter implementation that determines whether the items are equal:
<Grid x:Name="HiddenGrid" Background="Red">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Hidden" />
<Style.Triggers>
<DataTrigger Value="True">
<DataTrigger.Binding>
<MultiBinding>
<MultiBinding.Converter>
<local:MultiConverter />
</MultiBinding.Converter>
<Binding Path="{Binding Path=DataContext.InterestingItem,
RelativeSource={RelativeSource AncestorType={x:Type ListView }}}" />
<Binding Path="{Binding}" />
</MultiBinding>
</DataTrigger.Binding>
<Setter Property="Visibility" Value="Visible" />
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
</Grid>
Converter:
public class MultiConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture) =>
values != null && values.Length == 2 && values[0] == values[1];
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture) =>
throw new NotSupportedException();
}
Note that you cannot set the Visibility property of the Grid to a local value if you want to be able to override the value using a Style setter.
<Grid x:Name="HiddenGrid" Background="Red" Visibility="Hidden">

Cannot bind to DataGrid Columns from within DataGridColumnHeader style template

I'm using a custom defined DataGrid and want to be able to have a ContextMenu when I right click on a Column header that can alter the visibility of Columns. I have a Style defined for my DataGridColumnHeader with a template inside where I have tried to define a ContextMenu which takes the DataGrid's Columns as it's ItemsSource:
<ContextMenu ItemsSource="{Binding Columns, RelativeSource={RelativeSource AncestorType={x:Type customControls:CustomDataGrid}}}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</ContextMenu.ItemTemplate>
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="IsChecked" Value="{Binding Visibility, Converter={StaticResource BooleanToVisibilityConverter}, Mode = TwoWay}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
However when I right click the Column Headers to view the ContextMenu, nothing happens (it doesn't open as expected), and when I view the Live Property Explorer in VS I can see that the ItemsSource property for the ContextMenu is empty so it's obviously not finding the Columns property of the DataGrid.
Note that in the Live Visual Tree in VS I can see my DataGridColumnHeader style defined for the control which sits under my custom DataGrid in the hierarchy.
Any ideas? Cheers.
The DataGrid is not a visual ancestor of the ContextMenu.
You could bind the Tag property of the DataGridColumnHeader to the DataGrid and then bind the ContextMenu to the DataGrid using the PlacementTarget property:
<Style TargetType="DataGridColumnHeader">
<Setter Property="Tag" Value="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}}}" />
<Setter Property="ContextMenu">
<Setter.Value>
<ContextMenu ItemsSource="{Binding PlacementTarget.Tag.Columns, RelativeSource={RelativeSource Self}}">
<ContextMenu.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding Header}"/>
</DataTemplate>
</ContextMenu.ItemTemplate>
<ContextMenu.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="IsCheckable" Value="True" />
<Setter Property="IsChecked" Value="{Binding Visibility, Converter={StaticResource VisibilityToBooleanConverter}}"/>
</Style>
</ContextMenu.ItemContainerStyle>
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
Also note that you should use a VisibilityToBooleanConverter and not a BooleanToVisibilityConverter:
class VisibilityToBooleanConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
Visibility visibility = (Visibility)value;
return visibility == Visibility.Visible;
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
bool b = (bool)value;
return b ? Visibility.Visible : Visibility.Collapsed;
}
}

Add Check Box for Treeview parent nodes only Wpf

I want to add the check box for treeview of observable collection. the following code add the check box for all the parent and children nodes. I need only for the parent node Is there any way to achieve this...
<HierarchicalDataTemplate x:Key="LogFolderExplorer" DataType="{x:Type TestAutomationClient:TestArtifact}" ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" MinWidth="200">
<CheckBox
Focusable="False"
IsChecked="{Binding IsChecked}"
VerticalAlignment="Center"
/>
<TextBlock Text="{Binding Name}" FontSize="14"/>
</StackPanel>
</HierarchicalDataTemplate>
There are tons of ways you can achieve this.
One of them:
You can go to the ViewModel for the TestAutomationClient:TestArtifact, and add the bool property here:
public bool IsEmpty { get { return !this.Children.Any() } }
You should update it when the entire collection has changed (if this is your case), i.e. you should make RaisePropertyChanged("IsEmpty"). In case you are using some kind of ICollectionView - you do not need that property on your ViewModel.
<HierarchicalDataTemplate x:Key="LogFolderExplorer"
DataType="{x:Type TestAutomationClient:TestArtifact}"
ItemsSource="{Binding Children}">
<StackPanel Orientation="Horizontal" MinWidth="200">
<CheckBox Focusable="False"
IsChecked="{Binding IsChecked}"
VerticalAlignment="Center">
<CheckBox.Style>
<Style TargetType="CheckBox">
<Setter Property="Visibility" Value="Visible" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsEmpty}" Value="True">
<Setter Property="Visibility" Value="Collapsed" />
</DataTrigger>
</Style.Triggers>
</Style>
</CheckBox.Style>
</CheckBox>
<TextBlock Text="{Binding Name}" FontSize="14"/>
</StackPanel>
</HierarchicalDataTemplate>
You need a DataTrigger with a Converter. The Trigger should look like below,
<HierarchicalDataTemplate ItemsSource="{Binding SubItems}">
<StackPanel Orientation="Horizontal">
<CheckBox x:Name="chk" Visibility="Collapsed"/>
<TextBlock Text="{Binding Name}"
VerticalAlignment="Center"
Margin="3 0" />
</StackPanel>
<HierarchicalDataTemplate.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource Self}, Converter={StaticResource TreeLevelFinder}}"
Value="True">
<Setter Property="Visibility"
TargetName="checkBox"
Value="Visible" />
</DataTrigger>
</HierarchicalDataTemplate.Triggers>
</HierarchicalDataTemplate>
The whole TreeViewItem is being send to the Converter. The Converter will look for a Visual Parent. If it finds a Parent as TreeView, it is the first level. If it finds TreeViewItem, then it is sub levels.
public class TreeLevelFinder : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value is FrameworkElement)
{
var element = value as FrameworkElement;
var treeItem = element.TemplatedParent as TreeViewItem;
var parent = treeItem.FindAncestor<ItemsControl>();
if (parent is TreeView)
{
return true;
}
}
return false;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
The extension method FindAncestor<T> is available in this link.

Page indicator using ItemsControl ignores data template

I'm trying to create a page indicator using ItemsControl. The idea is to bind to the tabs of a TabControl and display a circle for each tab, with the color determined by a trigger that checks whether the tab is selected or not:
<ItemsControl ItemsSource="{Binding Path=Items, ElementName=ATabControl}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate DataType="{x:Type TabItem}">
<Ellipse x:Name="PageIndicator" Width="6" Height="6" Margin="2,0" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="False">
<Setter TargetName="PageIndicator" Property="Fill" Value="White" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsSelected}" Value="True">
<Setter TargetName="PageIndicator" Property="Fill" Value="Blue" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
It compiles without error but instead of the circles, I get the names of the tabs listed. Practically, it ignores the ItemTemplate/DataTemplate completely. Actually, if I remove the latter, the display remains the same all right.
There is always an issue while binding UI elements in two different containers. as one UI element can have only 1 parent so the last parent will have the actual element and hence the same will be removed from the former parent containers.
in the issue you've mentioned you attempted to bind the UI element TabItems to the items control which effectively pulled the original elements from the tab control and placed them as child of the items control.
in order to solve this issue I propose a solution to wrap such UI elements in a class and wire the properties needed.
I attempted a solution using converter
xaml
<StackPanel xmlns:l="clr-namespace:CSharpWPF">
<StackPanel.Resources>
<l:TabItemsConverter x:Key="TabItemsConverter" />
</StackPanel.Resources>
<TabControl x:Name="ATabControl">
<TabItem Header="item 1" />
<TabItem Header="item 2" />
<TabItem Header="item 3" />
</TabControl>
<ItemsControl ItemsSource="{Binding Path=Items, ElementName=ATabControl,Converter={StaticResource TabItemsConverter}}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"
HorizontalAlignment="Center" />
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Ellipse x:Name="PageIndicator"
Width="6"
Height="6"
Margin="2,0"
Fill="Gray" />
<DataTemplate.Triggers>
<DataTrigger Binding="{Binding Path=IsSelected}"
Value="False">
<Setter TargetName="PageIndicator"
Property="Fill"
Value="Gray" />
</DataTrigger>
<DataTrigger Binding="{Binding Path=IsSelected}"
Value="True">
<Setter TargetName="PageIndicator"
Property="Fill"
Value="Blue" />
</DataTrigger>
</DataTemplate.Triggers>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
converter class
namespace CSharpWPF
{
public class TabItemsConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
List<TabItemWrapper> result = new List<TabItemWrapper>();
foreach (TabItem item in (ItemCollection)value)
{
result.Add(new TabItemWrapper(item));
}
return result;
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
class TabItemWrapper : DependencyObject
{
public bool IsSelected
{
get { return (bool)GetValue(IsSelectedProperty); }
set { SetValue(IsSelectedProperty, value); }
}
// Using a DependencyProperty as the backing store for IsSelected. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsSelectedProperty =
DependencyProperty.Register("IsSelected", typeof(bool), typeof(TabItemWrapper), new PropertyMetadata(false));
public TabItemWrapper(TabItem source)
{
Binding b = new Binding("IsSelected");
b.Source = this;
b.Mode = BindingMode.TwoWay;
source.SetBinding(TabItem.IsSelectedProperty, b);
}
}
}
}
in this converter I binded the tab item's IsSelected to the property of a wrapper class and used it to bind in the view
Note: this converter work for static tab items only, if you intend to add or remove the tab items during runtime then perhaps you may need to handle CollectionChanged events to keep the result in sync.
result

Resources