How to use IsKeyboardFocusWithin and IsSelected together? - wpf

I have a style defined for my ListBoxItems with a trigger to set a background color when IsSelected is True:
<Style x:Key="StepItemStyle" TargetType="{x:Type ListBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="Border" Padding="0" SnapsToDevicePixels="true">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="#40a0f5ff"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
This style maintains the selected item even when the ListBox and ListBoxItem loses focus, which in my case is an absolute must.
The problem is that I also want the ListBoxItem to be selected when one of its TextBox's child gets focused. To achieve this I add a trigger that sets IsSelected to true when IsKeyboardFocusWithin is true:
<Trigger Property="IsKeyboardFocusWithin" Value="True">
<Setter Property="IsSelected" Value="True" />
</Trigger>
When I add this trigger the Item is selected when the focus is on a child TextBox, but the first behaviour disappears. Now when I click outside the ListBox, the item is de-selected.
How can I keep both behaviours?

When your listbox looses focus, it will set selected item to null because of your trigger. You can select on focus using some code behind that will not unselect when you loose focus.
XAML:
<Window x:Class="SelectedTest.Window1"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Height="300" Width="300">
<StackPanel>
<TextBox Text="Loose focus here" />
<ListBox Name="_listBox" ItemsSource="{Binding Path=Items}">
<ListBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" GotFocus="OnChildGotFocus">
<TextBox Text="{Binding .}" Margin="10" />
<TextBox Text="{Binding .}" Margin="10" />
</StackPanel>
</DataTemplate>
</ListBox.ItemTemplate>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="SnapsToDevicePixels" Value="true"/>
<Setter Property="OverridesDefaultStyle" Value="true"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="Border" SnapsToDevicePixels="true" Background="Transparent">
<ContentPresenter />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter TargetName="Border" Property="Background" Value="Red"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
</StackPanel>
</Window>
Code behind:
private void OnChildGotFocus(object sender, RoutedEventArgs e)
{
_listBox.SelectedItem = (sender as StackPanel).DataContext;
}

"When I add this trigger the Item is selected when the focus is on a child TextBox, but the first behaviour disappears. Now when I click outside the ListBox, the item is de-selected."
Actually, I don't think it has lost that original behavior. What I suspect is happening is you're clicking directly in the textbox from somewhere else so the underlying ListBoxItem never actually became selected. If it did however, you'd see the selection would still remain after you left as you want.
You can test this by forcing the ListBoxItem to be selected by clicking directly on it (side-note: you should always give it a background, even if just 'transparent' so it can receive mouse clicks, which it won't if it's null) or even just hitting 'Shift-Tab' to set the focus there, back from the textbox.
However, that doesn't solve your issue, which is that the TextBox gets the focus but doesn't let the underlying ListBoxItem know about it.
The two approaches you can use for that are an event trigger or an attached behavior.
The first is an event trigger on the IsKeyboardFocusWithinChanged event where you set 'IsSelected' to true if the keyboard focus changed to true. (Note: Sheridan's answer does a faux-change-notification but it should not be used in cases where you can multi-select in the list because everything becomes selected.) But even an event trigger causes issues because you lose the multi-select behaviors such as toggling or range-clicking, etc.
The other (and my preferred approach) is to write an attached behavior which you set on the ListBoxItem, either directly, or via a style if you prefer.
Here's the attached behavior. Note: You again would need to handle the multi-select stuff if you want to implement that. Also note that although I'm attaching the behavior to a ListBoxItem, inside I cast to UIElement. This way you can also use it in ComboBoxItem, TreeViewItem, etc. Basically any ContainerItem in a Selector-based control.
public class AutoSelectWhenAnyChildGetsFocus
{
public static readonly DependencyProperty EnabledProperty = DependencyProperty.RegisterAttached(
"Enabled",
typeof(bool),
typeof(AutoSelectWhenAnyChildGetsFocus),
new UIPropertyMetadata(false, Enabled_Changed));
public static bool GetEnabled(DependencyObject obj){ return (bool)obj.GetValue(EnabledProperty); }
public static void SetEnabled(DependencyObject obj, bool value){ obj.SetValue(EnabledProperty, value); }
private static void Enabled_Changed(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var attachEvents = (bool)e.NewValue;
var targetUiElement = (UIElement)sender;
if(attachEvents)
targetUiElement.IsKeyboardFocusWithinChanged += TargetUiElement_IsKeyboardFocusWithinChanged;
else
targetUiElement.IsKeyboardFocusWithinChanged -= TargetUiElement_IsKeyboardFocusWithinChanged;
}
static void TargetUiElement_IsKeyboardFocusWithinChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var targetUiElement = (UIElement)sender;
if(targetUiElement.IsKeyboardFocusWithin)
Selector.SetIsSelected(targetUiElement, true);
}
}
...and you simply add this as a property setter in your ListBoxItem's style
<Setter Property="behaviors:AutoSelectWhenAnyChildGetsFocus.Enabled" Value="True" />
This of course assumes you've imported an XML namespace called 'behaviors' that points to the namespace where the class is contained. You can put the class itself in a shared 'Helper' library, which is what we do. That way, everywhere we want it, its a simple property set in the XAML and the behavior takes care of everything else.

I figured out that IsKeyboardFocusWithin is not the best solution.
What I did in this case was to set the style on all of the controls used as DataTemplate to send the GotFocus-event to be handled in code behind. Then, in code behind, I searched up the visual tree (using VisualTreeHelper) to find the ListViewItem and set IsSelected to true. This way it does not "touch" the DataContext and works just with the View elements.
<Style TargetType="{x:Type Control}" x:Key="GridCellControlStyle">
...
<EventSetter Event="GotFocus" Handler="SelectListViewItemOnControlGotFocus"/>
...
private void SelectListViewItemOnControlGotFocus(object sender, RoutedEventArgs e)
{
var control = (Control)sender;
FocusParentListViewItem(control);
}
private void FocusParentListViewItem(Control control)
{
var listViewItem = FindVisualParent<ListViewItem>(control);
if (listViewItem != null)
listViewItem.IsSelected = true;
}
public static T FindVisualParent<T>(UIElement element) where T : UIElement
{
UIElement parent = element;
while (parent != null)
{
var correctlyTyped = parent as T;
if (correctlyTyped != null)
{
return correctlyTyped;
}
parent = VisualTreeHelper.GetParent(parent) as UIElement;
}
return null;
}

Related

How to use depenceny property in the style of tooltip to set the text?

In a button, I am using a dependency property to pass information from the view model to the style of the button, so I can set the color of the color according to some conditions.
The code for the button is this:
The style in my xaml file:
<Style x:Key="BotonesColorEstadosTemplate" TargetType="{x:Type Button}">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<ControlTemplate.Triggers>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="dp:BotonesEstadosAttachedProperty.CodigoEstado" Value="0"/>
<Condition Property="dp:BotonesEstadosAttachedProperty.AccionHabilitada" Value="true"/>
</MultiTrigger.Conditions>
</MultiTrigger>
<ContentTemplate>
</ContentTemplate>
</Style>
The dependency properties:
public static class BotonesEstadosAttachedProperty
{
public static readonly DependencyProperty CodigoEstadoProperty =
DependencyProperty.RegisterAttached(
"CodigoEstado",
typeof(short),
typeof(BotonesEstadosAttachedProperty));
public static short GetCodigoEstado(DependencyObject obj)
{
return (short)obj.GetValue(CodigoEstadoProperty);
}
public static void SetCodigoEstado(DependencyObject obj, short value)
{
obj.SetValue(CodigoEstadoProperty, value);
}
public static readonly DependencyProperty AccionHabilitadaProperty =
DependencyProperty.RegisterAttached(
"AccionHabilitada",
typeof(bool),
typeof(BotonesEstadosAttachedProperty));
public static bool GetAccionhabilitada(DependencyObject obj)
{
return (bool)obj.GetValue(AccionHabilitadaProperty);
}
public static void SetAccionHabilitada(DependencyObject obj, bool value)
{
obj.SetValue(AccionHabilitadaProperty, value);
}
}
How to use in the button:
<Button Name="btnAlmacenesActualizar" Content="..." Height="23" Margin="3,0,0,0" Width="23"
ap:BotonesEstadosDependencyProperty.CodigoEstado="{Binding CodigoEstadoActualizarAlmacenes}"
ap:BotonesEstadosDependencyProperty.AccionHabilitada="{Binding EsAccionActualizarAlmacenesHabilitada}">
With this, I can use a property of my view model and pass to my style, that use the information in the trigger to set the color of the button.
Now, I would like to have a style for the tooltip, to have the default configuration for all the tooltips, and I would like to can pass the text of the tooltip at first, but later I would like to pass another variables. By the moment, to test, I would like to try with the text.
I have this style:
<Style TargetType="ToolTip" x:Key="ToolTipDefaultStyle">
<Setter Property="ContentTemplate">
<Setter.Value>
<DataTemplate>
<StackPanel>
<TextBlock>
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Text" Value="dp:ToolTipAttachedProperty.Texto"/>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</DataTemplate>
</Setter.Value>
</Setter>
</Style>
My StackPanel with the tooltip:
<StackPanel Name="spTiposIva" Orientation="Vertical" Margin="5,0,0,0"
ap:ToolTipDependencyProperty.Texto="{Binding TiposIvaTooltip}">
<StackPanel.ToolTip>
<ToolTip Style="{StaticResource ToolTipDefaultStyle}"/>
</StackPanel.ToolTip>
</StackPanel>
But in this case the text that is shown is "dp:ToolTipAttachedProperty.Texto". So I would like if it is possible to do the same than with the button, use a dependency propety to pass information from view model to the style.
Thanks.
You are currently not binding the attached property, you are assigning a string as Value. For binding to attached properties, you need to use the binding markup extension and parentheses, e.g.:
<Setter Property="Text" Value="{Binding (dp:ToolTipAttachedProperty.Texto)"/>
However, in your case you need to refer to the parent control of ToolTip, which your property is attached to. Normally, you would do this with a RelativeSource binding and AncestorType, but this does not work here, because ToolTip is not within the same visual tree as the parent control.
Instead, you can access the control via the PlacementTarget property on the parent ToolTip.
<Setter Property="Text" Value="{Binding PlacementTarget.(dp:ToolTipAttachedProperty.Texto), RelativeSource={RelativeSource AncestorType={x:Type ToolTip}}}"/>
Please also check your XAML for typos. The attached properties type does not match on the StackPanel and in the ToolTip style: ToolTipDependencyProperty or ToolTipAttachedProperty?

Show popup with binding on DataGrid row mouse over

When a user hovers a row in a DataGrid I want to show a popup with some information about this row.
I've stuck how to bind DataTrigger to every row in dynamically populated DataGrid table.
I've found solutions only for tooltips but tooltips don't suit me because I need to have more control on popup (don't hide it immediately when a user moved a mouse cursor to another control, ability to click in popup with mouse etc.)
Here is XAML code where I'm trying to bind popup DataTrigger to every DataGrid row (I've put comments with questions in the code below)
<Window x:Class="VKPN.UI.Windows.TestWindow"
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:VKPN.UI.Windows"
mc:Ignorable="d"
Title="TestWindow" SizeToContent="WidthAndHeight">
<Grid>
<Popup Name="UserPopup" Placement="RelativePoint" HorizontalOffset="-5" VerticalOffset="0"
PlacementTarget="{Binding ElementName=ThisUserControl}">
<Popup.Style>
<Style TargetType="Popup">
<Style.Triggers>
<!--How to specify binding to every DataGridTable row below?-->
<DataTrigger Binding="{Binding ElementName=DataGridTable, Path=???}" Property="IsMouseOver" Value="True">
<Setter Property="IsOpen" Value="True"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Popup.Style>
<Label>
<Label.Style>
<Style TargetType="Label">
<Style.Triggers>
<!--How to specify binding to every DataGridTable row below?-->
<DataTrigger Binding="{Binding ElementName=???}" Property="IsMouseOver" Value="True">
<!--DataGrid row has a column "id" which I want to show in the label. Did I do it correct below?-->
<Setter Property="Content" Value="{Binding RelativeSource={RelativeSource Mode=Self}, Path=DataContext.id}"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Label.Style>
</Label>
</Popup>
<DataGrid Name="DataGridTable" ItemsSource="{Binding}" IsReadOnly="True" ScrollViewer.CanContentScroll="True" ScrollViewer.VerticalScrollBarVisibility="Auto" ScrollViewer.HorizontalScrollBarVisibility="Auto">
</DataGrid>
</Grid>
</Window>
Please help me to figure out how to do it.
Create 2 AttachedProperty called RowPopup and ShowPopup.
RowPopup will hold a reference to Popup control, and ShowPopup will show/hide this Popup based on DataGridRow.IsMouseOver property. These are extremely easy to implement.
Create a Style with TargetType DataGridRow.
Example,
xmlns:local="clr-namespace:MyNamespace"
<Style TargetType="DataGridRow" x:Key="RowStyleKey">
<Setter Property="local:CustomADP.RowPopup" Value="{Binding ElementName=Popup1}"/>
<Setter Property="local:CustomADP.ShowPopup" Value="{Binding IsMouseOver, Mode=OneWay, RelativeSource={RelativeSource Self}}"/>
</Style>
<DataGrid RowStyle="{StaticResource RowStyleKey}" ... />
AttachedProperties Code :
public class CustomADP
{
/********* Set Popup to show ****************/
public static Popup GetRowPopup(DependencyObject obj)
{
return (Popup)obj.GetValue(RowPopupProperty);
}
public static void SetRowPopup(DependencyObject obj, Popup value)
{
obj.SetValue(RowPopupProperty, value);
}
public static readonly DependencyProperty RowPopupProperty =
DependencyProperty.RegisterAttached("RowPopup", typeof(Popup), typeof(CustomADP), new PropertyMetadata(null));
/************* Show Hide using IsOpen property ************/
public static bool GetShowPopup(DependencyObject obj)
{
return (bool) obj.GetValue(ShowPopupProperty);
}
public static void SetShowPopup(DependencyObject obj, bool value)
{
obj.SetValue(ShowPopupProperty, value);
}
// Using a DependencyProperty as the backing store for ShowPopup. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ShowPopupProperty =
DependencyProperty.RegisterAttached("ShowPopup", typeof(bool), typeof(CustomADP), new PropertyMetadata(false, new PropertyChangedCallback(ShowPopupCallback)));
private static void ShowPopupCallback(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is DataGridRow))
return;
if (((DataGridRow)d).IsFocused == true)
{
Popup p = CustomADP.GetRowPopup(d);
p.IsOpen = Convert.ToBoolean(e.NewValue);
}
else
{
Popup p = CustomADP.GetRowPopup(d);
p.IsOpen = Convert.ToBoolean(e.NewValue);
}
}
}

prevent WPF style to be applied to parent during mouseover

I have the following user control which gets highlighted on mouse over:
<UserControl x:Class="P.WebEnt.Designer.CanvasControls.Container"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ig="http://schemas.infragistics.com/xaml"
mc:Ignorable="d"
d:DesignHeight="300" d:DesignWidth="300">
<Border.Style>
<Style>
<Setter Property="Border.Background" Value="White"/>
<Style.Triggers>
<Trigger Property="Border.IsMouseOver" Value="True">
<Setter Property="Border.Background" Value="Beige" />
</Trigger>
</Style.Triggers>
</Style>
</Border.Style>
<StackPanel Orientation="Vertical">
<ig:DragDropManager.DropTarget>
<ig:DropTarget IsDropTarget="True">
</ig:DropTarget>
</ig:DragDropManager.DropTarget>
<Label Content="" />
</StackPanel>
</Border>
At run-time, based on our scenario, same control could be added as a child to this control (nesting the same user control for 'n' number of levels).
The requirement is that if the same control is nested inside another, the mouseover on the child should highlight only child and not any of its ancestors. Currently, if I hover the mouse on inner-most control, it highlights all the ancestors.
Any help is appreciated.
here you go, use IsMouseDirectlyOver instead of IsMouseOver
<Trigger Property="Border.IsMouseDirectlyOver"
Value="True">
<Setter Property="Border.Background"
Value="Beige" />
</Trigger>
assuming that the concerned border is the only element nested inside other border, if there is other child elements in the inner most border it may not work as expected. in this case converters or attached behavior may help achieve the same.
EDIT
I attempted to solve the issue in a different way. as you have access to class itself, then we can perhaps do within the class
so change the trigger in style to DataTrigger
<Style>
<Setter Property="Border.Background"
Value="White" />
<Style.Triggers>
<DataTrigger Binding="{Binding IsCurrent,RelativeSource={RelativeSource FindAncestor,AncestorType=UserControl}}"
Value="True">
<Setter Property="Border.Background"
Value="Beige" />
</DataTrigger>
</Style.Triggers>
</Style>
and add the following code in the code behind of Container class
protected override void OnMouseMove(MouseEventArgs e)
{
Debug.Print(e.Source.GetHashCode().ToString());
if (CurrentControl != e.Source)
CurrentControl = e.Source as Container;
e.Handled = true;
}
static Container _CurrentControl;
static Container CurrentControl
{
get
{
return _CurrentControl;
}
set
{
if (_CurrentControl != null)
_CurrentControl.IsCurrent = false;
if (value != null)
value.IsCurrent = true;
_CurrentControl = value;
}
}
public bool IsCurrent
{
get { return (bool)GetValue(IsCurrentProperty); }
set { SetValue(IsCurrentProperty, value); }
}
// Using a DependencyProperty as the backing store for IsCurrent. This enables animation, styling, binding, etc...
public static readonly DependencyProperty IsCurrentProperty =
DependencyProperty.Register("IsCurrent", typeof(bool), typeof(Container), new PropertyMetadata(false));
give it a try and see if that is what you are looking for.

WPF setting keyboard-focus without code behind at runtime

I searched a lot on that topic but couldnt really find a solution for this using no code behind. I know some would say using code-behind for this view related things is totally ok, but nevertheless i would like to avoid it.
I have a usercontrol which shows a "dialog" with a single textbox and an OK button. That dialog is a simple usercontrol that is placed on top of all others. By default the usercontrols visibility is set to collapsed. I would like to set the keyboardfocus to the textbox on the dialog usercontrol if the usercontrol gets visible. Is there any way to do this completely in xaml? Since my dialog-control is not visible at the time when the control is loaded, simply setting
FocusManager.FocusedElement="{Binding ElementName=tbID}"
will not work. I tried to use some kind of visibility trigger:
<TextBox Grid.Column="3"
Grid.Row="5"
Name="tbID"
VerticalAlignment="Center">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Visibility" Value="Visible">
<Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=tbID}" />
</Trigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
but this doesnt work either. The trigger gets fired but the textbox doesnt get the focus. I would really appreciate any suggestions on that. Thanks in advance!
You could try using an attached behavior to set the focus. Here's some sample code:
public static class Focus
{
public static readonly DependencyProperty ShouldFocusWhenVisibleProperty =
DependencyProperty.RegisterAttached("ShouldFocusWhenVisible", typeof (bool), typeof (Focus), new PropertyMetadata(default(bool), ShouldFocusWhenVisibleChanged));
private static void ShouldFocusWhenVisibleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
var uiElement = sender as UIElement;
if (uiElement == null) return;
var shouldFocus = GetShouldFocusWhenVisible(uiElement);
if (shouldFocus)
{
UpdateFocus(uiElement);
uiElement.IsVisibleChanged += UiElementOnIsVisibleChanged;
}
else
uiElement.IsVisibleChanged -= UiElementOnIsVisibleChanged;
}
private static void UiElementOnIsVisibleChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var uiElement = sender as UIElement;
if (uiElement == null) return;
UpdateFocus(uiElement);
}
private static void UpdateFocus(UIElement uiElement)
{
if (!uiElement.IsVisible) return;
Keyboard.PrimaryDevice.Focus(uiElement);
}
public static void SetShouldFocusWhenVisible(UIElement uiElement, bool value)
{
uiElement.SetValue(ShouldFocusWhenVisibleProperty, value);
}
public static bool GetShouldFocusWhenVisible(UIElement uiElement)
{
return (bool)uiElement.GetValue(ShouldFocusWhenVisibleProperty);
}
}
Then, you apply the following code to the TextBox in your dialog: <TextBox local:Focus.ShouldFocusWhenVisible="True" />. Note that local: will need to be a reference to the namespace of the Focus class above.
I think you want to bind to the UserControl Visibility property not the TextBox
Example
<UserControl x:Class="WpfApplication7.IconButton"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
mc:Ignorable="d"
d:DesignHeight="100" d:DesignWidth="200" Name="_this">
<Grid>
<TextBox Name="tbID" Margin="0,12,0,0" VerticalAlignment="Top">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding ElementName=_this, Path=Visibility}" Value="Visible">
<Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=tbID}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>
</Grid>
</UserControl>

"Tag" ... Special functionality in WPF?

MSDN says "Gets or sets an arbitrary object value that can be used to store custom information about this element." which means I can store anything I want in this property.
But if you bind to this property (with property of type String having a value say "XYZ") and use it in Trigger conditions it doesn't work!
<Trigger Property="Tag" Value="XYZ">
<Setter Property="Background" Value="Red" />
</Trigger>
It does not set the background red. You can try and assume myElement to be a TextBlock! Why is it like this?
Tag has no special functionality in WPF.
This works for me:
<TextBlock Tag="{Binding Data}"
x:Name="tb">
<TextBlock.Style>
<Style>
<Style.Triggers>
<Trigger Property="TextBlock.Tag"
Value="XYZ">
<Setter Property="TextBlock.Background"
Value="Lime" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
And setting the Data object property to "XYZ" in an event.
The Tag is a construct held over from Winforms days (and possibly there from before that!). It was used as a convenient place to associate an object with a UI element, such as a FileInfo with a Button, so in the Button's event handler you could simply take the event sender, cast it to a Button, then cast the Tag value to a FileInfo and you have everything you need about the file you want to open.
There is one situation, however, where I've found the Tag is useful in WPF. I've used it as a holding spot that can be accessed by a ContextMenu MenuItem, which can't use the normal RelativeSource bindings you'd use to traverse the visual tree.
<ListBox.ItemContainerStyle>
<Style
TargetType="ListBoxItem">
<Setter
Property="Tag"
Value="{Binding ElementName=TheUserControlRootElement}" />
<Setter
Property="ContextMenu">
<Setter.Value>
<ContextMenu>
<MenuItem
Header="_Remove"
ToolTip="Remove this from this list"
CommandParameter="{Binding DataContext, RelativeSource={RelativeSource AncestorType=ContextMenu}}"
Command="{Binding PlacementTarget.Tag.Remove, RelativeSource={RelativeSource AncestorType=ContextMenu}}" />
</ContextMenu>
</Setter.Value>
</Setter>
</Style>
</ListBox.ItemContainerStyle>
From the ContextMenu, I cannot access the Remove command which is defined in the UserControl class where this snippet is defined. But I can bind the root to the Tag of the ListBoxItem, which I can access via the ContextMenu.PlacementTarget property. The same trick can be used when binding within a ToolTip, as the same limitations apply.
MainWindow.xaml:
<Window x:Class="wpftest.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>
<TextBlock x:Name="test" MouseDown="test_MouseDown"
Tag="{Binding TestProperty}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Style.Triggers>
<Trigger Property="Tag" Value="XYZ">
<Setter Property="Background" Value="Red" />
</Trigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</Window>
MainWindow.xaml.cs:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new TestViewModel();
}
private void test_MouseDown(object sender, MouseButtonEventArgs e)
{
((TestViewModel)DataContext).TestProperty = "XYZ";
}
private sealed class TestViewModel : INotifyPropertyChanged
{
private string _testPropertyValue;
public string TestProperty
{
get { return _testPropertyValue; }
set
{
_testPropertyValue = value;
var handler = PropertyChanged;
if(handler != null)
handler(this, new PropertyChangedEventArgs("TestProperty"));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
}
Updated: Tag property now is bound to TestProperty.

Resources