Creating a button that uses an image for the background, when button pressed a different image is used for the background. Inherited from button and added two dependency properties of type ImageBrush, one is PressedBackground. There will later be an enum type property, which will be used to select which set of image resources will be loaded in 'PressedImage' and 'NormalImage'.
In controltemplate, using a data trigger for "IsPressed", to set the background property to the imagebrush in 'PressedBackground'.
But when run, on press we have no background, debug tracing says "Cannot retrieve value using binding and no valid fallback value exists.
Problem is loading an imagebrush, from a dependency property into the background property, in the onpressed data trigger. I have tried all sorts to get a value into the background property, but with out success
Custom class snippet
public class QuantaImageButton : Button
{
static QuantaImageButton()
{
DefaultStyleKeyProperty.OverrideMetadata(typeof(QuantaImageButton),
new FrameworkPropertyMetadata(typeof(QuantaImageButton)));
}
public static readonly DependencyProperty PressedBackgroundProperty = DependencyProperty.Register(
"PressedBackground", typeof(ImageBrush), typeof(QuantaImageButton), new PropertyMetadata(default(ImageBrush)));
public ImageBrush PressedBackground
{
get { return (ImageBrush) GetValue(PressedBackgroundProperty); }
set { SetValue(PressedBackgroundProperty, value); }
}
// other dependency properties
}
style is:
<Style x:Key="BtnQuanta" TargetType="controls:QuantaImageButton">
<Setter Property="PressedBackground" Value="{StaticResource BtnMenuMediumDownImage}"/>
<Setter Property="Background" Value="{StaticResource BtnMenuMediumUpImage}"/>
<Setter Property= "Width" Value="500" />
<Setter Property="Height" Value="470"/>
<Setter Property="FontFamily" Value="Arial"/>
<Setter Property="FontSize" Value="52"/>
<Setter Property="Foreground" Value="White"/>
<Setter Property="BorderThickness" Value="0"/>
<Setter Property="Template" Value="{StaticResource QuantaBtnControlTemplate}"/>
</Style>
<ControlTemplate x:Key="QuantaBtnControlTemplate" TargetType="{x:Type controls:QuantaImageButton}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<ContentPresenter x:Name="contentPresenter" ContentTemplate="{TemplateBinding ContentTemplate}" Content="{TemplateBinding Content}" ContentStringFormat="{TemplateBinding ContentStringFormat}" Focusable="False" HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="{Binding PressedBackground, RelativeSource={RelativeSource TemplatedParent} }"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
I found the answer - as the debug text suggests - it cant find the property. The binding was incorrect - I have changed it to :
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" Value="{Binding PressedBackground, RelativeSource={RelativeSource Self} }"/>
</Trigger>
</ControlTemplate.Triggers>
The RelativeSource needs to be {RelativeSource Self} for the local object - not TemplateParent - which does not have the new property.
As an 'old' Winforms guy, moving over to WPF - it would be a great help if any one has a link to more explanation about this sort of binding and how you use it.
if you add TargetName="border" to apply Background to the border inside Template, then {RelativeSource TemplatedParent} works:
<ControlTemplate.Triggers>
<Trigger Property="IsPressed" Value="True">
<Setter TargetName="border" Property="Background"
Value="{Binding Path=PressedBackground, RelativeSource={RelativeSource TemplatedParent} }"/>
</Trigger>
</ControlTemplate.Triggers>
Adding TargetName changes target of binding, and apparently it affects RelativeSource resolution. However I cannot pin-point this subtle difference anywhere in the docs
Related
In order to change the visual behaviour of a control, it's necessary to extract the entire ControlTemplate into the xaml and modify the appropriate section. This is not a problem (form the properties view of the control, click on the little square to the right of the Template property and select Convert to New Resource), then edit like...
<ControlTemplate x:Key="ToggleButtonControlTemplate1" TargetType="{x:Type ToggleButton}">
<Border x:Name="border" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Background="{TemplateBinding Background}" SnapsToDevicePixels="True">
<ContentPresenter x:Name="contentPresenter"
ContentTemplate="{TemplateBinding ContentTemplate}"
Content="{TemplateBinding Content}"
ContentStringFormat="{TemplateBinding ContentStringFormat}" Focusable="False"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
Margin="{TemplateBinding Padding}" RecognizesAccessKey="True"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="Button.IsDefaulted" Value="True">
<Setter Property="BorderBrush" TargetName="border"
Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="True">
<Setter Property="Background" TargetName="border" Value="#FFBEE6FD"/>
<Setter Property="BorderBrush" TargetName="border" Value="#FF3C7FB1"/>
</Trigger>
<Trigger Property="IsPressed" Value="True">
<Setter Property="Background" TargetName="border" Value="#FFC4E5F6"/>
<Setter Property="BorderBrush" TargetName="border" Value="#FF2C628B"/>
</Trigger>
<Trigger Property="IsChecked" Value="True">
<Setter Property="Background" TargetName="border" Value="#FFBCDDEE"/>
<Setter Property="BorderBrush" TargetName="border" Value="#FF245A83"/>
<Setter Property="Background" TargetName="border" Value="Red"/> <!-- mod -->
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" TargetName="border" Value="#FFF4F4F4"/>
<Setter Property="BorderBrush" TargetName="border" Value="#FFADB2B5"/>
<Setter Property="TextElement.Foreground" TargetName="contentPresenter" Value="#FF838383"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
I can see that the Border element has been used as the root node in this case and that the ContentPresenter is in the Border Content property,
but, what is the logic behind using the Border element as the visual root and why is the background color of the button managed by setting the BackGround property of the Border?
Also, what elements are available to be used as root nodes and what are the inheritance rules? What is the visual structure of the standard controls and Where can I find some reasonable documentation about this?
I have some vague understanding about the VisualTreeRootNode and the need for a single root node for a template but... only vague.
References
There is some background here but no definitive reference.
The basic concept is documented here and you can drill down to individual controls to find examples. The link to Visual Tree looks promising but its broken. The button section gives some examples but does not give any definitive reference about the topic. I could not answer my questions based on what I read here.
The Border is like a box around your button. It has the Border and the Background properties of a control. So most Controls have Border in their Template, but you can template any Control like you want. You could even use an Ellipse to make a round Button or put every thing into a Grid. For the Functionality the important part is the ContentPresenter. It defines where the Content you set is shown.
The Microsoft documentation delivers good exsamples for every control you want to use. It contains every named Part of the Control and every VisualState it can take.
e.g. https://msdn.microsoft.com/de-de/library/ms753328(v=vs.110).aspx for a Button
For a detailed answer how Templating works in WPF see also:
https://msdn.microsoft.com/en-us/en-en/library/ee230084(v=vs.110).aspx
The Link shows how a Template is built, with the button example and how to set Visual States of a Button.
I've read a couple of posts that handles the same issue, but I don't find a solution.
What I want to achieve is, that the WPF checkbox changes its background and borderbrush colors on mouse hover. The colors shouldn't be hardcoded in XAML. Background and borderbrush properties should be bound to properties in a POCO class.
This is, what I've done:
first, I created a POCO class, that derives from WPF CheckBox. I created a dependency property called MouseOverBorderBrush:
Public Class efCheckBox
Inherits CheckBox
Implements INotifyPropertyChanged
Public Shared ReadOnly MouseOverBorderBrushProperty As DependencyProperty =
DependencyProperty.Register("MouseOverBorderBrush",
GetType(Brush), GetType(efCheckBox))
Public Property MouseOverBorderBrush As Brush
Get
Return CType(GetValue(MouseOverBorderBrushProperty), Brush)
End Get
Set(ByVal value As Brush)
PropertyHasChanged(MouseOverBorderBrushProperty, value)
End Set
End Property
Private Sub PropertyHasChanged(dp As DependencyProperty, value As Object, <System.Runtime.CompilerServices.CallerMemberName> Optional p As String = Nothing)
SetValue(dp, value)
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs(p))
End Sub
Second, I created a ResourceDictionary called "efComboBox". I did this by cloning the template of the original WPF CheckBox, so the following code is - with two exceptions - the original CheckBox definition:
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:emmbeeFramework"
xmlns:Themes="clr-namespace:Microsoft.Windows.Themes;assembly=PresentationFramework.Aero">
<SolidColorBrush x:Key="CheckBoxFillNormal" Color="#F4F4F4"/>
<SolidColorBrush x:Key="CheckBoxStroke" Color="#8E8F8F"/>
<Style x:Key="EmptyCheckBoxFocusVisual" TargetType="{x:Type local:efCheckBox}">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="1" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="CheckRadioFocusVisual">
<Setter Property="Control.Template">
<Setter.Value>
<ControlTemplate>
<Rectangle Margin="14,0,0,0" SnapsToDevicePixels="true" Stroke="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}" StrokeThickness="1" StrokeDashArray="1 2"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style x:Key="efCheckBoxStyle" TargetType="{x:Type local:efCheckBox}">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
<Setter Property="Background" Value="{StaticResource CheckBoxFillNormal}"/>
<Setter Property="BorderBrush" Value="{StaticResource CheckBoxStroke}"/>
<Setter Property="BorderThickness" Value="1"/>
<Setter Property="FocusVisualStyle" Value="{StaticResource EmptyCheckBoxFocusVisual}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:efCheckBox}">
<BulletDecorator x:Name="bullet" Background="Transparent" SnapsToDevicePixels="true">
<BulletDecorator.Bullet>
<Themes:BulletChrome BorderBrush="{TemplateBinding BorderBrush}" Background="{TemplateBinding Background}" IsChecked="{TemplateBinding IsChecked}" RenderPressed="{TemplateBinding IsPressed}"/>
</BulletDecorator.Bullet>
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" Margin="{TemplateBinding Padding}" RecognizesAccessKey="True" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</BulletDecorator>
<ControlTemplate.Triggers>
<Trigger Property="HasContent" Value="true">
<Setter Property="FocusVisualStyle" Value="{StaticResource CheckRadioFocusVisual}"/>
<Setter Property="Padding" Value="4,0,0,0"/>
</Trigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="BorderBrush" Value="{Binding local:efCheckBox.MouseOverBorderBrush}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The two exceptions:
I've chanted the "TargetType" from "CheckBox" to"efCheckBox"
I've added a trigger "IsMouseOver" near the end of the XAML code
This binding is not working:
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="BorderBrush" Value="{Binding local:efCheckBox.MouseOverBorderBrush}"/>
</Trigger>
I also tried, but it doesn't work:
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="BorderBrush" Value="{Binding RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type local:efCheckBox}}, Path=MouseOverBorderBrush}"/>
If I hardcode the brush, it works:
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="BorderBrush" Value="Red"/>
</Trigger>
<Setter Property="BorderBrush" Value="Red">
Could someone please help me?
I'm trying to work with, and understand XAML hierarchy for styles... in simple, a simple Textbox... seen all over the place for how to set the "disabled" background color based on the "IsEnabled" flag. Great, got that.
Now, I want to have another class derived from TextBox... MyTextBox. For this class, I have a property (not dependency property, so I was using DataTrigger). So, I want to keep all the normal TextBox actions that were working, but now get the new trigger to properly update the background color to some other color.. So, here is what I have. just to clarify, all my static resources for colors are SOLID BRUSHES...
<Style TargetType="TextBox" >
<Setter Property="FontFamily" Value="Courier New" />
<Setter Property="FontSize" Value="12" />
<Setter Property="Foreground" Value="{StaticResource MyForeground}" />
<Setter Property="Background" Value="{StaticResource MyBackground}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Border Name="Bd" BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="true">
<ScrollViewer Name="PART_ContentHost"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="{StaticResource MyDisBackground}" />
<Setter TargetName="PART_ContentHost" Property="Background"
Value="MyDisBackground"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<!-- Now, my derived (or so I was hoping) style that just adds additional trigger -->
<Style TargetType="local:MyTextBox" BasedOn="{StaticResource {x:Type TextBox}}" >
<Style.Triggers>
<DataTrigger Binding="{Binding Path=IsRequired}" Value="True">
<Setter Property="Background" Value="{StaticResource RequiredBackground}" />
</DataTrigger>
</Style.Triggers>
</Style>
Am I missing something simple?
First, your DataTrigger is looking at the DataContext of your MyTextBox (not the control itself). So look at the control, you'd need to do something like:
<DataTrigger Binding="{Binding Path=IsRequired, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="Background" Value="{StaticResource RequiredBackground}" />
</DataTrigger>
Now that will set the MyTextBox.Background property when MyTextBox.IsRequired is true. But dependency property values have a precedence order. So the above will visually change the background used like:
<local:MyTextBox />
In the following case your RequiredBackground brush will not be used. Instead you'll see the MyDisBackground brush:
<local:MyTextBox IsEnabled="False" />
In this case, the ScrollViewer.Background is changed to MyDisBackground and no longer binds to the MyTextBox.Background property. The MyTextBox.Background would still be RequiredBackground, but it's no longer used anywhere.
Finally, in the following case your RequiredBackground brush will not be used.
<local:MyTextBox Background="Yellow" />
Here, the local value (yellow) is at #3 in the precedence list, while the style setter is at #8.
If you make your property a dependency property that defaults to false, then you could do something like:
<Style TargetType="TextBox" >
<Setter Property="FontFamily" Value="Courier New" />
<Setter Property="FontSize" Value="12" />
<Setter Property="Foreground" Value="{StaticResource MyForeground}" />
<Setter Property="Background" Value="{StaticResource MyBackground}" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="TextBox">
<Border Name="Bd" BorderThickness="{TemplateBinding BorderThickness}"
BorderBrush="{TemplateBinding BorderBrush}"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="true">
<ScrollViewer Name="PART_ContentHost"
Background="{TemplateBinding Background}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
<ControlTemplate.Triggers>
<Trigger Property="local:MyTextBox.IsRequired" Value="False">
<Setter Property="Background" Value="{StaticResource RequiredBackground}" />
<Setter TargetName="PART_ContentHost" Property="Background"
Value="RequiredBackground"/>
</Trigger>
<Trigger Property="IsEnabled" Value="False">
<Setter Property="Background" Value="{StaticResource MyDisBackground}" />
<Setter TargetName="PART_ContentHost" Property="Background"
Value="MyDisBackground"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="local:MyTextBox" BasedOn="{StaticResource {x:Type TextBox}}" />
Eventhough the property doesn't exist for TextBox, it can still get the default value of your dependency property and trigger off it. But since it would be set for a TextBox, that trigger will never be used.
I try to change the Background property for my ListBoxItems using triggers in the ItemContainerStyle of my ListBox as follows:
<ListBox Height="100" HorizontalAlignment="Left" Margin="107,59,0,0" Name="listBox1" VerticalAlignment="Top" Width="239">
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Background" Value="Lightblue"/>
<Style.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background" Value="Red"/>
</Trigger>
<Trigger Property="IsMouseOver" Value="true">
<Setter Property="Background" Value="Yellow"/>
</Trigger>
</Style.Triggers>
</Style>
</ListBox.ItemContainerStyle>
<ListBox.Items>
<ListBoxItem Content="First Item"/>
<ListBoxItem Content="SecondItem"/>
<ListBoxItem Content="Third Item"/>
</ListBox.Items>
</ListBox>
I would expect unselected items to have a light blue background, hovered items (i.e. when the mouse cursor is over them) to be yellow and selected items to be red.
For the unselected and hovered items this is working as expected, but the selected items still have their standard background color (i.e. blue, if the listbox has focus and light gray otherwise).
Is there anything I'm missing? Is this behaviour documented somewhere?
Thanks for any hint!
EDIT
I'm aware of the solution of overriding the default system colors (as described in Change selected and unfocused Listbox style to not be grayed out, thanks anyway for everyone posting this as an answer). However this is not what I want to do. I'm more interested in why my solution doesn't work.
I'm suspecting the standard ControlTemplate of ListItem to define it's own triggers which seem to take precendence over triggers defined by the style (perhaps someone could confirm this and point me to some resource where this behaviour is defined).
My solution for the meantime is to define a ControlTemplate for my ListItems like:
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ListBoxItem">
<Border Name="Border" Padding="2" SnapsToDevicePixels="true" Background="LightBlue" Margin="0">
<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>
A little bit of reflecting on the Aero-style offers us an explanation to why this simple trigger-setting doesn't work.
The ListBoxItem has a ControlTemplate with triggers that takes precedence over our trigger. At least this seems to be true for a MultiTrigger.
I´ve managed to override the simple trigger of Selected=true but for the multitrigger I had to make my own ControlTemplate.
This is the template from the Aero style that shows the problematic MultiTrigger:
<ControlTemplate TargetType="{x:Type ListBoxItem}">
<ControlTemplate.Triggers>
<Trigger Property="IsSelected" Value="true">
<Setter TargetName="Bd" Value="{DynamicResource {x:Static HighlightBrush}}" Property="Background" />
<Setter Value="{DynamicResource {x:Static HighlightTextBrush}}" Property="Foreground" />
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true" />
<Condition Property="IsSelectionActive" Value="false" />
</MultiTrigger.Conditions>
<Setter TargetName="Bd" Value="{DynamicResource {x:Static ControlBrush}}" Property="Background" />
<Setter Value="{DynamicResource {x:Static ControlTextBrush}}" Property="Foreground" />
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Value="{DynamicResource {x:Static GrayTextBrush}}" Property="Foreground" />
</Trigger>
</ControlTemplate.Triggers>
<Border Name="Bd" Background="{TemplateBinding Background}" BorderBrush="{TemplateBinding BorderBrush}" BorderThickness="{TemplateBinding BorderThickness}" Padding="{TemplateBinding Padding}" SnapsToDevicePixels="true">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding VerticalContentAlignment}" SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}" />
</Border>
</ControlTemplate>
Hope it clears things up a little bit. I can't fathom why they´ve overcomplicated the style this much.
delete IsSelected trigger
And add to listbox:
<ListBox.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Red" />
<SolidColorBrush x:Key="{x:Static SystemColors.ControlBrushKey}"
Color="Red" />
</ListBox.Resources>
First brush for focused second for otherwise
Try adding this to your window resources -
<Window.Resources>
<SolidColorBrush x:Key="{x:Static SystemColors.HighlightBrushKey}"
Color="Red" />
</Window.Resources>
And remove the IsSelected Trigger from your code, it won't work because every system has its default highlight brush depending on your system theme.
You need to override the highlight brush in your code to make it work.
Try using Selector.IsSelected in your Trigger rather than IsSelected.
I am writing an application with a listbox allowing multi-selection (SelectionMode=Multiple); the items in the lisbox are ingredients for a recipe.
Unfortunately, clicking on a listbox item selects this item, which may not be desired. I would like the following scenario:
the user clicks on the listbox in order to select the listbox (the listbox itself, not an item)
the user scrolls to the right item and selects it
What I did is style the ListBoxItem to include a checkbox and a ContentPresenter (like in this blog).
Still, clicking on the ingredient name selects it.
So, I trap the MouseDown event on the textblock containing the ingredient name, find the underlying ListBoxItem, call Focus() on it and set the Handled property of the event to true.
Now, the Listbox item has the focus but is not selected. Using the up and down keys shows that the focus was on the right item.
My problem is that the user cannot see that he has clicked on the right item. The dotted rectangle is not shown on this item.
Here is the result:
And here is what I'd like:
I've tried calling private WPF methods, like KeyboardNavigation.ShowFocusVisual, I've tried sending keystrokes to the listbox (when done by a human, pressing the right cursor key or the Alt key makes the dotted rectangle appear).
Any idea ?
SendInput is the only way I've found which gets past this. From this link.
PInvoke to SendInput – this is the
official way to simulate input. It
pushes the input through all of the
expected code paths, and is
indistinguishable from real input.
An easy way to use this is with InputSimulator from CodePlex.
Adding a reference to InputSimulator.dll we can do something like this
private bool m_waitingForFocusVisualStyle = false;
private void ListBoxItem_GotFocus(object sender, RoutedEventArgs e)
{
if (m_waitingForFocusVisualStyle == false)
{
m_waitingForFocusVisualStyle = true;
InputSimulator.SimulateKeyDown(VirtualKeyCode.TAB);
InputSimulator.SimulateModifiedKeyStroke(VirtualKeyCode.SHIFT, VirtualKeyCode.TAB);
}
else
{
m_waitingForFocusVisualStyle = false;
}
}
But this might not be ideal for a lot of reasons (Shift+Tab to the ListBoxItem for example)
A better idea is probably to remove the FocusVisualStyle for ListBoxItem and add your own in the ControlTemplate like this. (Copied from Blend and added a "FocusVisualStyle" from standard FocusVisualStyle)
<ListBox ...>
<ListBox.ItemContainerStyle>
<Style TargetType="{x:Type ListBoxItem}">
<Setter Property="FocusVisualStyle" Value="{x:Null}"/>
<Setter Property="Template" Value="{StaticResource ListBoxItemTemplate}" />
</Style>
</ListBox.ItemContainerStyle>
</ListBox>
<ControlTemplate x:Key="ListBoxItemTemplate" TargetType="{x:Type ListBoxItem}">
<Grid>
<Rectangle Grid.ZIndex="1"
Name="focusVisualStyle"
StrokeThickness="1"
Stroke="Black"
StrokeDashArray="1 2"
SnapsToDevicePixels="true"
Visibility="Hidden"/>
<Border x:Name="Bd"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
Background="{TemplateBinding Background}"
Padding="{TemplateBinding Padding}"
SnapsToDevicePixels="true">
<ContentPresenter HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding SnapsToDevicePixels}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}"/>
</Border>
</Grid>
<ControlTemplate.Triggers>
<Trigger Property="IsFocused" Value="True">
<Setter TargetName="focusVisualStyle" Property="Visibility" Value="Visible"/>
</Trigger>
<Trigger Property="IsSelected" Value="true">
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.HighlightBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.HighlightTextBrushKey}}"/>
</Trigger>
<MultiTrigger>
<MultiTrigger.Conditions>
<Condition Property="IsSelected" Value="true"/>
<Condition Property="Selector.IsSelectionActive" Value="false"/>
</MultiTrigger.Conditions>
<Setter Property="Background" TargetName="Bd" Value="{DynamicResource {x:Static SystemColors.ControlBrushKey}}"/>
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.ControlTextBrushKey}}"/>
</MultiTrigger>
<Trigger Property="IsEnabled" Value="false">
<Setter Property="Foreground" Value="{DynamicResource {x:Static SystemColors.GrayTextBrushKey}}"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
I have found Meleak's answer very helpful, but using GotFocus does not work for me. Instead I have bind the even handler to PreviewMouseLeftButtonDown even. Now you don't need boolean property to store the state and the code is very simple:
void SlideCanvasPreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
InputSimulator.SimulateKeyDown(VirtualKeyCode.TAB);
InputSimulator.SimulateModifiedKeyStroke(VirtualKeyCode.SHIFT, VirtualKeyCode.TAB);
}
This does the job for me very good.
P.S. I use this style - it has some fine animation of moving dashed rectanle