I have a DataGrid style template that I wish to add double click behaviour to. The binding should be correct but I cannot seem to get the xaml compiling / working.
All objects added to an IDictionary must have a Key attribute
or some other type of key associated with them.
What is wrong with the code below?
<Style TargetType="{x:Type DataGridRow}">
<EventSetter Event="MouseDoubleClick" Handler="{Binding Connect}"/>
Update per Viktor's comment (gives exact same error):
<Style x:Key="dataGridRowStyle" TargetType="{x:Type DataGridRow}">
<EventSetter Event="PreviewMouseDoubleClick" Handler="{Binding Connect}"/>
One can use DataGrid InputBindings to achieve goal:
<DataGrid.InputBindings>
<MouseBinding MouseAction="LeftDoubleClick" Command="{Binding SomeCommand}" />
</DataGrid.InputBindings>
You can apply the following behavior on data grid row and follow the usage for implementation.
Double Click Behavior
public class DoubleClickBehavior
{
#region DoubleClick
public static DependencyProperty OnDoubleClickProperty = DependencyProperty.RegisterAttached(
"OnDoubleClick",
typeof(ICommand),
typeof(DoubleClickBehavior),
new UIPropertyMetadata(DoubleClickBehavior.OnDoubleClick));
public static void SetOnDoubleClick(DependencyObject target, ICommand value)
{
target.SetValue(OnDoubleClickProperty, value);
}
private static void OnDoubleClick(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
var element = target as Control;
if (element == null)
{
throw new InvalidOperationException("This behavior can be attached to a Control item only.");
}
if ((e.NewValue != null) && (e.OldValue == null))
{
element.MouseDoubleClick += MouseDoubleClick;
}
else if ((e.NewValue == null) && (e.OldValue != null))
{
element.MouseDoubleClick -= MouseDoubleClick;
}
}
private static void MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
UIElement element = (UIElement)sender;
ICommand command = (ICommand)element.GetValue(OnDoubleClickProperty);
command.Execute(null);
}
#endregion DoubleClick
}
Usage
<Style BasedOn="{StaticResource {x:Type DataGridRow}}"
TargetType="{x:Type DataGridRow}">
<Setter Property="Helpers:DoubleClickBehavior.OnDoubleClick" Value="{Binding Path=DataContext.MyCommandInVM, RelativeSource={RelativeSource Mode=FindAncestor, AncestorType={x:Type ViewLayer:MyUserControl}}}" />
</Style>
Not sure if you're going the MVVM route, but I've achieved this functionality using an Attached Command Behavior to wire up the double click event to a command in my viewmodel (where "command" is a reference to my attachedCommandBehavior assembly/class):
<DataGrid.RowStyle>
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="command:CommandBehavior.Event" Value="MouseDoubleClick"/>
<Setter Property="command:CommandBehavior.Command" Value="{Binding SelectItemCmd}"/>
<Setter Property="command:CommandBehavior.CommandParameter" Value="{Binding }"/>
</Style>
</DataGrid.RowStyle>
Related
I'm looking for a solution where I double click on a DataGridRow that calls a method in my ViewModel with an ICommand.
I have this code for my DataGrid's DataGridRow style:
<DataGrid.Resources>
<Style TargetType="{x:Type DataGridRow}">
<EventSetter Event="MouseDoubleClick"
Handler="DataGridRow_MouseDoubleClick" />
</Style>
</DataGrid.Resources>
This works, but...
I need to have the method DataGridRow_MouseDoubleClick in the XAML's code-behind. Then in that method I need to call the method in my ViewModel.
I would like to bypass the code-behind and directly call the method in the ViewModel with an ICommand.
I found this code which was elegant but, calls the method whereever I (left) double click on the DataGrid.
<DataGrid>
<DataGrid.InputBindings>
<MouseBinding Gesture="LeftDoubleClick"
Command="{Binding MyCallback}" />
</DataGrid.InputBindings>-->
</DataGrid>
I can only allow double click on a DataGridRow.
Any suggestions?
/BR
Steffe
You could replace the event handler with an attached behaviour that executes the command:
public static class DataGridRowExtensions
{
public static readonly DependencyProperty MouseDoubleClickCommandProperty =
DependencyProperty.RegisterAttached(
"MouseDoubleClickCommand",
typeof(ICommand),
typeof(DataGridRowExtensions),
new FrameworkPropertyMetadata(default(ICommand), new PropertyChangedCallback(OnSet))
);
public static ICommand GetMouseDoubleClickCommand(DataGridRow target) =>
(ICommand)target.GetValue(MouseDoubleClickCommandProperty);
public static void SetMouseDoubleClickCommand(DataGridRow target, ICommand value) =>
target.SetValue(MouseDoubleClickCommandProperty, value);
private static void OnSet(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
DataGridRow row = (DataGridRow)d;
row.MouseDoubleClick += Row_MouseDoubleClick;
}
private static void Row_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
DataGridRow row = (DataGridRow)sender;
ICommand command = GetMouseDoubleClickCommand(row);
if (command != null)
command.Execute(default);
}
}
XAML:
<Style TargetType="{x:Type DataGridRow}">
<Setter Property="local:DataGridRowExtensions.MouseDoubleClickCommand"
Value="{Binding DataContext.MyCallback,
RelativeSource={RelativeSource AncestorType=DataGrid}}" />
</Style>
First, install the Nuget package mentioned below in your project.
Microsoft.Xaml.Behaviors.Wpf
Then add the following reference to the relevant xaml field.
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
As the next step, you can apply the double click function as follows.
<i:Interaction.Triggers>
<i:EventTrigger EventName="MouseDoubleClick">
<i:InvokeCommandAction Command="{Binding MyCallback}"/>
</i:EventTrigger>
</i:Interaction.Triggers>
Previously I had the template of the UserControl directly set, not through a style, and everything worked nicely: the root of the content could be accessed using this.Template.LoadContent() or through this.Template.FindName("MyControlName", this), both calls being done in OnApplyTemplate, after the base.OnApplyTemplate() call. Now I need a style because I use two DataTriggers to display a type of Control or another in function of a Binding value.
By debugging with the XAML below, I see that after the base.OnApplyTemplate call this.Template.LoadContent() returns a Border with its Child set to an empty ContentPresenter. I wish to get the wpf:TimeSpanPicker element.
I have read this answer and it does not help me because of the result of debugging presented above. The same with this answer.
Before, my UserControl had this directly inside (in its XAML file):
<UserControl.Template>
<ControlTemplate>
<wpf:TimeSpanPicker
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Name="MyTimeSpanPicker"
Margin="0,0,7,0"/>
</ControlTemplate>
</UserControl.Template>
Now I have this:
<UserControl.Style>
<Style TargetType="UserControl">
<Style.Triggers>
<DataTrigger Binding="{Binding Mode=OneWay, Converter={StaticResource ClockToType}}"
Value="TimerData">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="UserControl">
<wpf:TimeSpanPicker
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Name="MyTimeSpanPicker"
Margin="0,0,7,0"/>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
<DataTrigger Binding="{Binding Mode=OneWay, Converter={StaticResource ClockToType}}"
Value="AlarmData">
<Setter Property="Template">
<Setter.Value>
<ControlTemplate TargetType="UserControl">
<Button>Not yet implemented</Button>
</ControlTemplate>
</Setter.Value>
</Setter>
</DataTrigger>
</Style.Triggers>
</Style>
</UserControl.Style>
The code-behind includes:
internal wpf_timespanpicker.TimeSpanPicker GetMyTimeSpanPicker()
{
return (wpf_timespanpicker.TimeSpanPicker)Template.LoadContent();
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate();
GetMyTimeSpanPicker().TimeSpanValueChanged += MyTimeSpanPicker_TimeSpanValueChanged;
}
private void MyTimeSpanPicker_TimeSpanValueChanged(object sender, EventArgs e)
{
CurrentValue = GetMyTimeSpanPicker().TimeSpan;
}
The ClockToType value converter simply transforms one of my Clock classes' instances to their type name.
Update
Now it partially works because of the answer but I need to set the TimeSpan dependency property of the TimeSpanPicker when the CurrentValue dependency property of the UserControl is changed, and the CurrentValue dependency property can be changed when the time span picker is not yet Loaded. What is the best way to postpone this setting? Placing an ApplyTemplate call before the setting does not seem to work because the class variable in which I keep a reference to the TimeSpanPicker is null:
private static void OnCurrentValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var o = d as ClockValueScreen;
o.ApplyTemplate();
o.MyTimeSpanPicker.TimeSpan = (TimeSpan)e.NewValue; // but here o.MyTimeSpanPicker is null
}
public override void OnApplyTemplate()
{
base.OnApplyTemplate(); // execution reaches this point from the ApplyTemplate call above
}
private void MyTimeSpanPicker_Loaded(object sender, RoutedEventArgs e)
{
MyTimeSpanPicker = (wpf_timespanpicker.TimeSpanPicker)sender;
MyTimeSpanPicker.TimeSpanValueChanged += MyTimeSpanPicker_TimeSpanValueChanged;
}
You can't use OnApplyTemplate() because there is no TimeSpanPicker element available until the binding has been resolved and the converter has returned a value of "TimerData".
What you could do instead is to hook up the event handler in the XAML:
<ControlTemplate TargetType="UserControl">
<wpf:TimeSpanPicker
Loaded="OnLoaded"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"
VerticalContentAlignment="Stretch"
Name="MyTimeSpanPicker"
Margin="0,0,7,0"/>
</ControlTemplate>
...and then handle it in your code-behind;
private void OnLoaded(object sender, RoutedEventArgs e)
{
wpf_timespanpicker.TimeSpanPicker timeSpanPicker = ( wpf_timespanpicker.TimeSpanPicker)sender;
}
Edit: If you want to do something with the TimeSpanPicker when the CurrentValue property changes, you could use the VisualTreeHelper class to find it in the visual tree:
private static void OnCurrentValueChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var o = (ClockValueScreen)d;
var timeSpanPicker = FindVisualChild<wpf_timespanpicker.TimeSpanPicker>(o);
//...
}
private static T FindVisualChild<T>(Visual visual) where T : Visual
{
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(visual); i++)
{
Visual child = (Visual)VisualTreeHelper.GetChild(visual, i);
if (child != null)
{
T correctlyTyped = child as T;
if (correctlyTyped != null)
{
return correctlyTyped;
}
T descendent = FindVisualChild<T>(child);
if (descendent != null)
{
return descendent;
}
}
}
return null;
What I tried to do is create a Style to apply a WordWrap on all DataGridTextColumns in a Datagrid without explicitly setting it like this.
<DataGrid ItemsSource="{Binding Lines}">
<DataGrid.Columns>
<DataGridTextColumn Header="Column1" Binding="{Binding Path=Result1}">
<DataGridTextColumn.ElementStyle>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</DataGridTextColumn.ElementStyle>
</DataGridTextColumn>
</DataGrid.Columns>
</DataGrid>
Unfortunately it is not possible to use some Style as below directly, because DataGridTextColumn isn't a FrameworkElement.
<Style TargetType="{x:Type TextBlock}" x:Key="WrapText">
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
I found this workaround https://stackoverflow.com/a/2640862/5381620 by RayBurns and was trying to figure out how it's working. However, I'm new to attached properties and therefore don't understand why it is not working.
The c# code seems to be ok.
public class MyDataGridHelper : DependencyObject
{
private static readonly DependencyProperty TextColumnStyleProperty = DependencyProperty.RegisterAttached("TextColumnStyle", typeof(Style), typeof(MyDataGridHelper), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
var grid = (DataGrid)obj;
if (e.OldValue == null && e.NewValue != null)
grid.Columns.CollectionChanged += (obj2, e2) =>
{
UpdateColumnStyles(grid);
};
}
});
public static void SetTextColumnStyle(DependencyObject element, Style value)
{
element.SetValue(TextColumnStyleProperty, value);
}
public static Style GetTextColumnStyle(DependencyObject element)
{
return (Style)element.GetValue(TextColumnStyleProperty);
}
private static void UpdateColumnStyles(DataGrid grid)
{
var style = GetTextColumnStyle(grid);
foreach (var column in grid.Columns.OfType<DataGridTextColumn>())
foreach (var setter in style.Setters.OfType<Setter>())
if (setter.Value is BindingBase)
BindingOperations.SetBinding(column, setter.Property, (BindingBase)setter.Value);
else
column.SetValue(setter.Property, setter.Value);
}
}
I got totally confused is when we get towards figuring out the style setter.
Currently I'm trying it this way, which is obviously not working, but actually I don't have a clue what this targettype should really look like.
<local:MyDataGridHelper.TextColumnStyle>
<Style TargetType="FrameworkElement">
<Setter Property="TextBlock.TextWrapping" Value="Wrap"/>
</Style>
</local:MyDataGridHelper.TextColumnStyle>
You should set the ElementStyle of the columns to the value of the attached property:
public class MyDataGridHelper : DependencyObject
{
private static readonly DependencyProperty TextColumnStyleProperty =
DependencyProperty.RegisterAttached("TextColumnStyle", typeof(Style), typeof(MyDataGridHelper), new PropertyMetadata
{
PropertyChangedCallback = (obj, e) =>
{
var grid = (DataGrid)obj;
if (e.OldValue == null && e.NewValue != null)
grid.Columns.CollectionChanged += (obj2, e2) =>
{
UpdateColumnStyles(grid);
};
}
});
public static void SetTextColumnStyle(DependencyObject element, Style value)
{
element.SetValue(TextColumnStyleProperty, value);
}
public static Style GetTextColumnStyle(DependencyObject element)
{
return (Style)element.GetValue(TextColumnStyleProperty);
}
private static void UpdateColumnStyles(DataGrid grid)
{
var style = GetTextColumnStyle(grid);
foreach (var column in grid.Columns.OfType<DataGridTextColumn>())
column.ElementStyle = style;
}
}
Usage:
<DataGrid>
<local:MyDataGridHelper.TextColumnStyle>
<Style TargetType="TextBlock">
<Setter Property="Foreground" Value="Red"/>
<Setter Property="TextWrapping" Value="Wrap"/>
</Style>
</local:MyDataGridHelper.TextColumnStyle>
...
</DataGrid>
I have created an instance of a TextBox that implements ICommandSource, I would like to control the IsEnabled property via the DataContext. This portion of my code works, on top of this I would like to control the Text property via this same method or by extension the IsEnabled property.
Basically when the TextBox transitions from IsEnabled="False" to IsEnabled="True" I would like to reset the Text field to an empty string or preferably null.
I have attempted to do this in a handful of ways without success.
Attempt 1
<ctrl:CommandTextBox x:Name="txtSerialNumber"
Command="{Binding VMFactory.CreateViewModelCommand, RelativeSource={RelativeSource AncestorType={x:Type UserControl}}}"
CommandParameter="{Binding Text, RelativeSource={RelativeSource Self}}" DecoderPrefix="S">
<ctrl:CommandTextBox.Style>
<Style TargetType="{x:Type ctrl:CommandTextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding}" Value="{x:Null}">
<Setter Property="IsEnabled" Value="True" />
<Setter Property="Text" Value="{x:Null}" />
</DataTrigger>
</Style.Triggers>
<Setter Property="IsEnabled" Value="False" />
<Setter Property="Text" Value="{Binding SerialNumber, Mode=OneWay}" />
</Style>
</ctrl:CommandTextBox.Style>
</ctrl:CommandTextBox>
This does work but only when the CommandParameter does not need to be "Decoded". It seems as though when my text property is changed via the override it breaks the trigger until the application is restarted.
CommandTextBox.cs
public class CommandTextBox : DecoderTextBox, ICommandSource
{
// Additional Fields, Properties, and Methods removed for the sake of brevity.
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
if (e.Key == Key.Enter && Command != null)
{
RoutedCommand command = Command as RoutedCommand;
if (command != null)
command.Execute(CommandParameter, CommandTarget);
else
Command.Execute(CommandParameter);
if (CommandResetsText)
this.Text = String.Empty;
e.Handled = true;
}
}
}
DecoderTextBox.cs
public class DecoderTextBox : TextBox
{
public static DependencyProperty DecoderPrefixProperty = DependencyProperty.Register("DecoderPrefix", typeof(string), typeof(DecoderTextBox), new PropertyMetadata(String.Empty));
public string DecoderPrefix
{
get { return (string)GetValue(DecoderPrefixProperty); }
set { SetValue(DecoderPrefixProperty, value); }
}
protected override void OnKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
string text = this.Text;
// If the if statement returns true the trigger will break.
if (text.Substring(0, Math.Min(DecoderPrefix.Length, text.Length)) == DecoderPrefix)
this.Text = text.Remove(0, DecoderPrefix.Length);
}
base.OnKeyDown(e);
}
}
Is there something specific to my implementation of OnKeyDown that is breaking this trigger?
There is an issue related to setting the value of a DependencyProperty locally. It appears as though you have to use SetCurrentValue to maintain the binding.
DecoderTextBox.cs
protected override void OnPreviewKeyDown(KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
if (Text.StartsWith(DecoderPrefix))
SetCurrentValue(TextProperty, Text.Remove(0, DecoderPrefix.Length));
}
base.OnPreviewKeyDown(e);
}
I've created a WPF UserControl which contains a Button and a ComboBox. I'd like to change the style of both, depending on the position of the mouse, so the UIElement with the mouse over is coloured Black and the other is coloured Red. If neither are styled then the default styling will apply.
Don't worry, this nightmarish colour scheme is just to illustrate the concept!
Thanks in advance for your help.
XAML
<UserControl x:Class="WpfUserControlSample.ToolbarButtonCombo"
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:local="clr-namespace:WpfUserControlSample"
x:Name="Control"
mc:Ignorable="d"
d:DesignHeight="30">
<UserControl.Resources>
<Style TargetType="{x:Type local:ToolbarButtonCombo}">
<Style.Triggers>
<DataTrigger Binding="{Binding IsButtonMouseOver}" Value="True">
<Setter Property="ButtonStyle" Value="Black"/>
<Setter Property="ComboStyle" Value="Red"/>
</DataTrigger>
<!--
<DataTrigger Binding="{Binding IsComboMouseOver}" Value="True">
<Setter Property="ButtonStyle" Value="Red"/>
<Setter Property="ComboStyle" Value="Black"/>
</DataTrigger>
-->
</Style.Triggers>
</Style>
</UserControl.Resources>
<StackPanel Orientation="Horizontal" Height="30">
<Button Name="btn" Background="{Binding ButtonStyle,ElementName=Control,Mode=OneWay}">
Test
</Button>
<ComboBox Name="cmb" Background="{Binding ComboStyle,ElementName=Control,Mode=OneWay}"></ComboBox>
</StackPanel>
</UserControl>
Codebehind:
namespace WpfUserControlSample
{
public partial class ToolbarButtonCombo : UserControl, INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
public ToolbarButtonCombo()
{
InitializeComponent();
btn.MouseEnter += new MouseEventHandler(btn_MouseChanged);
btn.MouseLeave += new MouseEventHandler(btn_MouseChanged);
}
void btn_MouseChanged(object sender, MouseEventArgs e)
{
OnPropertyChanged("IsButtonMouseOver");
}
public bool IsButtonMouseOver
{
get { return btn.IsMouseOver; }
}
public static readonly DependencyProperty IsButtonMouseOverProperty =
DependencyProperty.Register("IsButtonMouseOver", typeof(string), typeof(ToolbarButtonCombo), new PropertyMetadata("false"));
public string ButtonStyle { get; set; }
public static readonly DependencyProperty ButtonStyleProperty =
DependencyProperty.Register("ButtonStyle", typeof(string), typeof(ToolbarButtonCombo));
public string ComboStyle { get; set; }
public static readonly DependencyProperty ComboStyleProperty =
DependencyProperty.Register("ComboStyle", typeof(string), typeof(ToolbarButtonCombo));
}
}
There are a two problems.
First your DataTrigger bindings do not look correct. They are looking for the IsButtonMouseOver on the DataContext, not the associated control. You'd need to use:
<DataTrigger Binding="{Binding IsButtonMouseOver, RelativeSource={RelativeSource Self}}" Value="True">
<Setter Property="ButtonStyle" Value="Black"/>
<Setter Property="ComboStyle" Value="Red"/>
</DataTrigger>
Or:
<Trigger Property="IsButtonMouseOver" Value="True">
<Setter Property="ButtonStyle" Value="Black"/>
<Setter Property="ComboStyle" Value="Red"/>
</Trigger>
The other is your IsButtonMouseOver is not implemented correctly. You should do something like:
public static readonly DependencyProperty IsButtonMouseOverProperty = DependencyProperty.Register("IsButtonMouseOver",
typeof(bool), typeof(ToolbarButtonCombo), new PropertyMetadata(false));
public bool IsButtonMouseOver
{
get { return (bool)this.GetValue(IsButtonMouseOverProperty); }
set { this.SetValue(IsButtonMouseOverProperty, value); }
}
void btn_MouseChanged(object sender, MouseEventArgs e)
{
this.IsButtonMouseOver = this.btn.IsMouseOver;
}
Or even more correctly, make the IsButtonMouseOver a read-only dependency property like so:
private static readonly DependencyPropertyKey IsButtonMouseOverPropertyKey = DependencyProperty.RegisterReadOnly("IsButtonMouseOver",
typeof(bool), typeof(ToolbarButtonCombo), new FrameworkPropertyMetadata(false));
public static readonly DependencyProperty IsButtonMouseOverProperty = ToolbarButtonCombo.IsButtonMouseOverPropertyKey.DependencyProperty;
public bool IsButtonMouseOver {
get { return (bool)this.GetValue(IsButtonMouseOverProperty); }
private set { this.SetValue(IsButtonMouseOverPropertyKey, value); }
}
Your other properties (ButtonStyle and ComboStyle) would need to be properly implemented also, and their get/set methods are not backed by the dependency property.