I am creating a custom control that is supposed to auto-complete stuff. For this, a Popup is getting created where the user can pick the wanted item.
This all works perfectly fine already, with a tiny problem: I want those items (presented inside of a ListView) to also auto-complete using a double click.
This however, presents a problem to me as i cannot add an EventSetter onto the style for the corresponding ListViewItem.
I want to avoid having to add a dependency property with some ICommand to relay the whole thing via attached properties.
XAML code (broken down):
<Style x:Key="{x:Type local:AutoCompleteTextBox}" TargetType="{x:Type local:AutoCompleteTextBox}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:AutoCompleteTextBox}">
<!-- ... -->
<Popup x:Name="PART_Popup" PlacementTarget="{Binding ...}" IsOpen="{Binding ...}">
<ListView ItemTemplate="{Binding ...}" ItemsSource="{Binding ...}">
<ListView.ItemContainerStyle>
<Style TargetType="{x:Type ListViewItem}">
<!-- 'ResourceDictionary' root element requires a x:Class attribute to support event handlers in the XAML file. Either remove the event handler for the MouseDoubleClick event, or add a x:Class attribute to the root element. -->
<EventSetter Event="MouseDoubleClick" Handler="ListViewItem_MouseDoubleClick"/>
</Style>
</ListView.ItemContainerStyle>
</ListView>
</Popup>
<!-- ... -->
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
The problem is that the KeyDown event is triggered twice, the first one comes from the CustomTextBox named textSource inside the Style; the second, from control in the MainWindow named "CTbox".The linked question provides a solution where you filter on the EventHandler OnKeyDown the source e.Source != "textSource" which is working fine:
if (e.Source is CustomTextBox sourceTextBox && sourceTextBox.Name.Equals("textSource"))
{
return;
}
Basically I would like to know if there is any better solution to this and if someone can explain the reason why is this happening and how can be avoid it.
The style is mean to create a Hint Text or WaterMark in the CustomTextBox without relaying in the Focus events
Thanks in advance.
Following the code to create a Minimal, Complete, and Verifiable example of this behaviour
CustomTextBox Class:
public class CustomTextBox : TextBox
{
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
}
}
MainWindow:
<Window x:Class="WpfApp2.MainWindow"
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:WpfApp2"
mc:Ignorable="d"
Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<Style x:Key="CustomTextBoxStyle"
TargetType="{x:Type local:CustomTextBox}">
<Setter Property="Foreground"
Value="#FF414042" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomTextBox}">
<Border Name="Border"
BorderBrush="#FF348781"
BorderThickness="0,0,0,4"
CornerRadius="2">
<ScrollViewer x:Name="PART_ContentHost"
Margin="0" />
<VisualStateManager.VisualStateGroups>
<VisualStateGroup x:Name="CommonStates">
<VisualState x:Name="Normal" />
<VisualState x:Name="Disabled" />
<VisualState x:Name="ReadOnly" />
<VisualState x:Name="MouseOver" />
</VisualStateGroup>
</VisualStateManager.VisualStateGroups>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
<Style TargetType="{x:Type local:CustomTextBox}"
BasedOn="{StaticResource CustomTextBoxStyle}">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type local:CustomTextBox}">
<Grid>
<local:CustomTextBox
Text="{TemplateBinding Text}"
x:Name="textSource"
Background="Transparent"
Panel.ZIndex="2"
Style="{StaticResource CustomTextBoxStyle}"/>
<TextBlock Text="{TemplateBinding Tag}">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Foreground"
Value="Transparent" />
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Text, Source={x:Reference textSource}}"
Value="">
<Setter Property="Foreground"
Value="Gray" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<local:CustomTextBox x:Name="CTbox" Tag="Hint Text Example" Height="25" Width="258"/>
</Grid>
</Window>
So you're getting two executions of your OnKeyDown handler because your XAML essentially builds a CustomTextBox within a CustomTextBox. With the routing strategy of WPF events and your visual tree looking like this:
the KeyDown event naturally will fire in both places (textsource, then bubble up to CustomTextBox). Side note, if you overrode OnPreviewKeyDown, you should get the reverse order - CTbox tunneling down to textSource.
To answer the second part of your question - how to avoid this, I think perhaps you should rethink your implementation here. My questions would be:
Should you even use an editable control (your CustomTextBox in this case) for your hinttext overlay in your control template? Since the hinttext should only be readable, perhaps a TextBlock would suffice.
Should you override the ControlTemplate for customtextbox to get this HintText functionality? Perhaps the better way is just to create a UserControl that provides this functionality. i.e. Grid containing your custom textbox and a textblock overlaying it. This will prevent you from having a visual tree with nested CustomTextBox's.
If you need a ControlTemplate so you can reuse it, why not make it the default control template of your CustomTextBox? If your "Tag" property is not bound, the hinttext will just naturally not show. This way you won't have nested CustomTextBoxes causing duplicate execution of OnKeyDown as well!
Why do you even need a CustomTextBox? Do you have other override code and behavior that you're not showing that requires this? I'm guessing this is because you showed a minimal sample and there's more - but just thought I'd ask :)
EDIT for comment
Given your clarifications/questions, I would've approached solving this via a custom control. I know what you have is technically a custom control, but I'm talking about the kind that comes with a themes\generic.xaml file :). If you're not familiar, I recommend creating a new VS project and making it of the "WPF custom control library" template. Then you should be able to add a new class of the template "Custom Control (WPF)". You'll see that VS has generated a themes\generic.xaml file for you - this is where you would hold the controltemplate for your CustomTextBox. I would get the default control template of a TextBox, and add in a TextBlock that's not hit test visible (so a user can click through to your editable area) and set a TemplateBinding on it for the HintText. You'll then be able to reuse this custom control everywhere(as it's compiled in a separate dll... you can also opt to keep it within your current project too), get the default behaviors of textbox that you didn't override, and won't have nested CustomTextBoxes.
In an attempt to use a Click event on a label I have found an example of using a button but applying a template to it so that it looks like a label. As shown below:
<Button Name="LooksLikeALabel" Canvas.Left="279" Canvas.Top="-37" Height="26" Width="48" Content="Words" Click="answer1Label_MouseUp" MouseEnter="answer1Label_MouseEnter" MouseLeave="answer1Label_MouseLeave">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Label x:Name="buttonLabel" Content="{TemplateBinding Content}"/>
</ControlTemplate>
</Button.Template>
</Button>
Now I want to be able to change the colour of the text on mouse over. Changing the foreground on the button does nothing bot in the code behind and through the XAML. Changing the colour of the label foreground works through the XAML but for whatever reason I am unable to access the label through the code behind meaning I can't access any of the label controls through my C#.
Is there something I am missing to get control over the label in the code behind? Alternatively, is there a better way to have a click event on a label?
Put Trigger inside ControlTemplate of button:
<Button Name="LooksLikeALabel" Canvas.Left="279" Canvas.Top="-37" Height="26"
Width="48" Content="Words">
<Button.Template>
<ControlTemplate TargetType="{x:Type Button}">
<Label x:Name="buttonLabel" Content="{TemplateBinding Content}"/>
<ControlTemplate.Triggers>
<Trigger Property="Button.IsMouseOver" Value="True">
<Setter TargetName="buttonLabel" Property="Foreground" Value="Red"/>
</Trigger>
</ControlTemplate.Triggers>
</ControlTemplate>
</Button.Template>
</Button>
I have the following style which removes data point and randomly generates a line color for my line series plots
<Style x:Key="LineDataPointStyle"
TargetType="ChartingToolkit:LineDataPoint">
<Setter Property="Foreground" Value="DarkGreen"/>
<Setter Property="IsTabStop" Value="False"/>
<Setter Property="Width" Value="NaN"/>
<Setter Property="Height" Value="NaN"/>
<Setter Property="Background"
Value="{Binding RelativeSource={RelativeSource Self},
Converter={StaticResource ColorBrushConverter}}"/>
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="ChartingToolkit:LineDataPoint">
<Grid x:Name="Root" Opacity="0"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
where the converter is:
public class ColorToBrushConverter : IValueConverter
{
public object Convert(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
return new SolidColorBrush(Utils.GenerateRandomColor());
}
public object ConvertBack(object value, Type targetType,
object parameter, System.Globalization.CultureInfo culture)
{
throw new NotImplementedException();
}
}
This generate the line in a random color, but the legend is a different color; either being auto-generated by the library itself or it is also calling my converter through the style template.
How can I get the legend to print the correct color?
Note: is the answer to the question of Killercam, that has been asked here. Answer to this question is particularly suitable for his bounty, so at his request I publish it here.
In this answer, the Button control is used to demonstrate working with templates.
Part I. Binding in ControlTemplate
If you want to use Binding in a ControlTemplate, you should use following construction:
<ControlTemplate TargetType="{x:Type SomeControl}">
<Rectangle Fill="{TemplateBinding Background}" />
Quoted from MSDN:
A TemplateBinding is an optimized form of a Binding for template scenarios, analogous to a Binding constructed with {Binding RelativeSource={RelativeSource TemplatedParent}}.
Notes about using TemplateBinding
TemplateBinding doesn’t work outside a template or outside its VisualTree property, so you can’t even use TemplateBinding inside a template’s trigger. Furthermore, TemplateBinding doesn’t work when applied to a Freezable (for mostly artificial reasons), for example - VisualBrush. In such cases it is possible to use Binding like this:
<FreezableControl Property="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=Background}" />
Also, you can always use an alternative for TemplateBinding:
<Rectangle Fill="{Binding RelativeSource={RelativeSource TemplatedParent},
Path=Background}" />
As another possibility, you can also try the following:
<Rectangle Fill="{Binding Background,
RelativeSource={RelativeSource AncestorType={x:Type SomeControl}},
Path=Background}" />
Part II. Notes about your version
In your case, this may cause a conflict of names in the ControlTemplate, because you already are using Binding background is for Border. Therefore, remove it this Binding for a Border, or use another property, such as Tag or attached dependency property for binding Background color.
Example of using
Instead ChartingToolkit controls, took as a basis Button control, because it's easier to demonstrate the idea of this styles.
Solution 1: using Tag
<Window.Resources>
<Style x:Key="TestButtonStyle" TargetType="{x:Type Button}">
<Setter Property="BorderThickness" Value="0" />
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<!-- Here we are set Tag for Border Background -->
<Border Background="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Tag}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Rectangle Width="24"
Height="24"
Fill="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=Background}"
Stroke="{TemplateBinding BorderBrush}" />
<ContentPresenter Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Button Name="TestButton"
Style="{StaticResource TestButtonStyle}"
Content="Test"
HorizontalContentAlignment="Center"
VerticalContentAlignment="Center"
Tag="Green"
Background="Aquamarine"
Width="100"
Height="100" />
</Grid>
Output
Here for Rectangle, set two colors: default for Rectangle, in Tag for Border. I do not find this a good solution, and here's why:
If a Border and Rectangle need to set different values, such as: Background, BorderThickness, BorderBrush, etc. one Tag is not enough.
With one name property must be clearly its purpose, one name "Tag" us to nothing says.
Of these disadvantages can be concluded that we should find an alternative, as an alternative I use a extender-class with the attached dependency properties.
Extender class ButtonExt.cs
public static class ButtonExt
{
#region RectangleBackground Property
public static readonly DependencyProperty RectangleBackgroundProperty;
public static void SetRectangleBackground(DependencyObject DepObject, Brush value)
{
DepObject.SetValue(RectangleBackgroundProperty, value);
}
public static Brush GetRectangleBackground(DependencyObject DepObject)
{
return (Brush)DepObject.GetValue(RectangleBackgroundProperty);
}
#endregion
#region RectangleBorderBrush Property
public static readonly DependencyProperty RectangleBorderBrushProperty;
public static void SetRectangleBorderBrush(DependencyObject DepObject, Brush value)
{
DepObject.SetValue(RectangleBorderBrushProperty, value);
}
public static Brush GetRectangleBorderBrush(DependencyObject DepObject)
{
return (Brush)DepObject.GetValue(RectangleBorderBrushProperty);
}
#endregion
#region Button Constructor
static ButtonExt()
{
#region RectangleBackground
PropertyMetadata BrushPropertyMetadata = new PropertyMetadata(Brushes.Transparent);
RectangleBackgroundProperty = DependencyProperty.RegisterAttached("RectangleBackground",
typeof(Brush),
typeof(ButtonExt),
BrushPropertyMetadata);
#endregion
#region RectangleBorderBrush
RectangleBorderBrushProperty = DependencyProperty.RegisterAttached("RectangleBorderBrush",
typeof(Brush),
typeof(ButtonExt),
BrushPropertyMetadata);
#endregion
}
#endregion
}
MainWindow.xaml
<Window.Resources>
<Style x:Key="TestButtonExtensionStyle" TargetType="{x:Type Button}">
<Setter Property="Width" Value="80" />
<Setter Property="Height" Value="80" />
<Setter Property="Background" Value="Green" />
<Setter Property="BorderBrush" Value="Pink" />
<Setter Property="BorderThickness" Value="4" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="{x:Type Button}">
<Border Background="{TemplateBinding Background}"
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}">
<Grid>
<Rectangle Fill="{TemplateBinding PropertiesExtension:ButtonExt.RectangleBackground}"
Stroke="{TemplateBinding PropertiesExtension:ButtonExt.RectangleBorderBrush}"
Width="30"
Height="30" />
<ContentPresenter Content="{TemplateBinding Content}"
HorizontalAlignment="{TemplateBinding HorizontalContentAlignment}"
VerticalAlignment="{TemplateBinding VerticalContentAlignment}" />
</Grid>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Window.Resources>
<Grid>
<Button Style="{StaticResource TestButtonExtensionStyle}"
PropertiesExtension:ButtonExt.RectangleBackground="Aquamarine"
PropertiesExtension:ButtonExt.RectangleBorderBrush="Black"
Content="Test" />
</Grid>
Output
Part III. Setting values for dependency properties
When you create and register your attached dependency property, you must declare the Set and Get methods to work with him:
public static void SetRectangleBackground(DependencyObject DepObject, Brush value)
{
DepObject.SetValue(RectangleBackgroundProperty, value);
}
public static Brush GetRectangleBackground(DependencyObject DepObject)
{
return (Brush)DepObject.GetValue(RectangleBackgroundProperty);
}
Then work with them will be as follows:
Set
ButtonExt.SetRectangleBackground(MyButton, Brushes.Red);
Get
Brush MyBrush = ButtonExt.GetRectangleBackground(MyButton);
But in our case, it's not so simple. When I used the attached dependency property problems in updating values were not. But in our case, the property is in the template, and in my case there was no update Button. I tried to set Mode=TwoWay, UpdateSourceTrigger=PropertyChanged, in Binding and in the property declaration, GetBindingExpression().UpdateTarget(), but it was useless.
Note that for the property setting a new value, and notification from the template is not, that the property has been updated. Maybe I'm wrong, and you have will work, or maybe it's made specifically, for example to avoid memory leaks.
In any case, it is better not to update the dependency property directly, and bind to it the property of the Model and in the ViewModel to set the value.
Example:
<Button Style="{StaticResource TestButtonExtensionStyle}"
adp:ButtonExt.RectangleBackground="{Binding Path=Model.RectBackground,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}"
adp:ButtonExt.RectangleBorderBrush="{Binding Path=Model.RectBorderBrush,
Mode=TwoWay,
UpdateSourceTrigger=PropertyChanged}" />
where RectBackground and RectBorderBrush implement the INotifyPropertyChanged interface.
As an alternative in this case, do not use dependency properties and use the DataTemplate for the control. DataTemplate ideal for MVVM, very flexible and dynamic.
For example, work with DataTemplate, you can see my answers:
Make (create) reusable dynamic Views
One ViewModel for UserControl and Window or separate ViewModels
I do something similar, where I generate plots with changing colors, but these colors are randomly selected from a preferred list (I have a black background and some colors just don't work very well on top of black) . I set the color from code behind and I am not sure this is something you can do.
In your case I would try something like this:
//If you declare your style in a resource dictionary, get that resource first
ResourceDictionary resD = (ResourceDictionary)Application.LoadComponent(new Uri("ResourcesPlot\\ResourceDictionaryPlot.xaml", UriKind.Relative));
//The actual style
Style lineDataPointStyle= (Style)resD["LineDataPointStyle"];
//Set the color
lineDataPointStyle.Setters.Add(new Setter(BackgroundProperty, Utils.GenerateRandomColor()));
Hope this works.
Edit:
For the legend I use this (I have an extra checkbox for showing/hiding a certain plot)
<Style x:Key="CustomLegendItemStyle" TargetType="{x:Type chartingToolkit:LegendItem}">
<Setter Property="IsTabStop" Value="False" />
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="chartingToolkit:LegendItem">
<StackPanel Orientation="Horizontal">
<CheckBox VerticalAlignment="Center" Margin="3" IsChecked="true" Checked="DisplaySeries_Checked" Unchecked="DisplaySeries_Unchecked"/>
<!--<Rectangle VerticalAlignment="Center" Width="8" Height="8" Fill="{DynamicResource MyBackgroundDiode1}" Stroke="{Binding BorderBrush}" StrokeThickness="1" Margin="5,5,5,5" />-->
<chartingToolkit:LegendItem VerticalAlignment="Center" Content="{TemplateBinding Content}" />
</StackPanel>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
I have the following XAML/Control Template for a ListViewItem:
<Window.Resources>
<ControlTemplate x:Key="ItemTemplate" TargetType="ListViewItem">
<Border BorderThickness="{TemplateBinding Border.BorderThickness}" Padding="0,5,0,5" BorderBrush="{TemplateBinding Border.BorderBrush}"
Background="{TemplateBinding Panel.Background}" SnapsToDevicePixels="True">
<ContentPresenter Content="{TemplateBinding ContentControl.Content}" ContentTemplate="{TemplateBinding ContentControl.ContentTemplate}"
HorizontalAlignment="{TemplateBinding Control.HorizontalContentAlignment}" VerticalAlignment="{TemplateBinding Control.VerticalContentAlignment}"
SnapsToDevicePixels="{TemplateBinding UIElement.SnapsToDevicePixels}" />
</Border>
</ControlTemplate>
<Style TargetType="ListViewItem">
<Setter Property="Template" Value="{StaticResource ItemTemplate}" />
</Style>
<DataTemplate x:Key="ItemDataTemplate">
<RadioButton
x:Name="radiobutton" GroupName="LeGroup"
Content="{Binding Path=Name}" Checked="radiobutton_Checked" />
</DataTemplate>
</Window.Resources>
AND when I try to set the SelectionChanged to fire, it ONLY fires when a radio button is checked with a Mouse RIGHT Click. I need it to fire when it is a NORMAL Left Click.
<ListView x:Name="radioListView" SelectionMode="Single" ItemsSource="{Binding}" ItemTemplate="{StaticResource ItemDataTemplate}" SelectionChanged="radioListView_SelectionChanged" Loaded="radioListView_Loaded"></ListView>
Can't seem to find anything referring to forcing a selectionchanged event on a right click. Standard is a left click.
Thank you in advance :)
EDIT:
So I noticed that the SelectionChanged event fires when I Click OUTSIDE of the radio button (and its associated text) on Left Click. It's as if the radio button is working independently from the listview. The SelectionChanged event fires on a Right Click INSIDE the radio button element. Weird, still trying to figure it out.
I'm not sure why the Left mouse button doesn't work, but here is the style I use for displaying a ListBox with RadioButtons. It works fine with both Right and Left mouse clicks
<Style x:Key="ListBoxAsRadioButtonsStyle" 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 IsHitTestVisible="False" Focusable="false"
Content="{TemplateBinding ContentPresenter.Content}"
IsChecked="{Binding Path=IsSelected,RelativeSource={RelativeSource TemplatedParent},Mode=TwoWay}"/>
</Border>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>
</Setter.Value>
</Setter>
</Style>
It is used like
<ListBox ItemsSource="{Binding MyItems}"
Style="{StaticResource ListBoxAsRadioButtonsStyle}" />
If I had to guess at your problem, I would guess that the regular ListView Selection behavior is marking the LeftMouseClick event as Handled when you click on an item
Well thats strange! Selection takes place on Right click???
All I can suggest you is to take an eventless approach ...
You could use the MVVM pattern by binding SelectedItem / SelectedValue properties etc.
Also you could harness single selection mode of ListView to automatically "group" radio buttons so that ONLY one is selected at time
e.g. radiobutton's IsChecked bound to ancestor ListViewItem's IsSelected property, two way. This way when a radio button is checked it would automatically select that list view row and SelectedItem\Value binding would fire automatically. And being in single selection mode of ListView, next radio button click would automatically de-select previous row.