XAML Binding.UpdateSourceTrigger when a Button.Click is fired? - wpf

I want to set the UpdateSourceTrigger to an event of a control:
<TextBox Text="{Binding Field, UpdateSourceMode=btnOK.Click}">
<Button Name="btnOK">
<Button.Triggers>
<Trigger>
<!-- Update source -->
</Trigger>
</Button.Triggers>
</Button>
I thought about two ways:
Set UpdateSourceMode or some other stuff in the binding.
Set an EventTrigger that updates source on button click.
Possible, or I have to do it with code?

You'll have to use code. Specifically:
Set UpdateSourceTrigger=Explicit on the TextBox.
Call UpdateSource when the user clicks the Button.
However, you can put the code in the code behind or in an attached behavior.

I know it's been a while, but I came across the same issue and want to share my solution. Hope it will be helpful for somebody.
public class UpdateSourceBehavior : Behavior<System.Windows.Interactivity.TriggerBase>
{
internal const string TargetElementPropertyLabel = "TargetElement";
static UpdateSourceBehavior()
{
TargetElementProperty = DependencyProperty.Register
(
TargetElementPropertyLabel,
typeof(FrameworkElement),
typeof(UpdateSourceBehavior),
new PropertyMetadata(null)
);
}
public static readonly DependencyProperty TargetElementProperty;
[Bindable(true)]
public FrameworkElement TargetElement
{
get { return (FrameworkElement)base.GetValue(TargetElementProperty); }
set { base.SetValue(TargetElementProperty, value); }
}
public PropertyPath TargetProperty { get; set; }
protected override void OnAttached()
{
base.OnAttached();
this.InitializeMembers();
base.AssociatedObject.PreviewInvoke += this.AssociatedObject_PreviewInvoke;
}
protected override void OnDetaching()
{
base.AssociatedObject.PreviewInvoke -= this.AssociatedObject_PreviewInvoke;
base.OnDetaching();
}
private void AssociatedObject_PreviewInvoke(object sender, PreviewInvokeEventArgs e)
{
this.m_bindingExpression.UpdateSource();
}
private void InitializeMembers()
{
if (this.TargetElement != null)
{
var targetType = this.TargetElement.GetType();
var fieldInfo = targetType.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy)
.FirstOrDefault(fi => fi.Name == this.TargetProperty.Path + "Property");
if (fieldInfo != null)
this.m_bindingExpression = this.TargetElement.GetBindingExpression((DependencyProperty)fieldInfo.GetValue(null));
else
throw new ArgumentException(string.Format("{0} doesn't contain a DependencyProperty named {1}.", targetType, this.TargetProperty.Path));
}
else
throw new InvalidOperationException("TargetElement must be assigned to in order to resolve the TargetProperty.");
}
private BindingExpression m_bindingExpression;
}

Here is my solution:
XAML:
<StackPanel>
<i:Interaction.Triggers>
<i:EventTrigger SourceName="submit" EventName="Click">
<behaviours:TextBoxUpdateSourceAction TargetName="searchBox"></behaviours:TextBoxUpdateSourceAction>
</i:EventTrigger>
</i:Interaction.Triggers>
<TextBox x:Name="searchBox">
<TextBox.Text>
<Binding Path="SomeProperty" UpdateSourceTrigger="Explicit" NotifyOnValidationError="True">
<Binding.ValidationRules>
<DataErrorValidationRule ValidatesOnTargetUpdated="False"/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<Button x:Name="submit"></Button>
</StackPanel>
Behaviour definition (inherited from TargetedTriggerAction):
public class TextBoxUpdateSourceAction : TargetedTriggerAction<TextBox>
{
protected override void Invoke(object parameter)
{
BindingExpression be = Target.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
}
}
Please note that it's important to attach TextBoxUpdateSourceAction to parent container (StackPanel in example code).

Related

Invoke command on a DataGridcolumn with single-click

I would like to create a hyperlink-column in wpf but rather with a command-binding instead of an uri.
<DataGridTemplateColumn Header="{lex:Loc newmaterialnumber}" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<TextBlock TextDecorations="Underline" Text="{Binding NewMaterialnumber}" Cursor="Hand" VerticalAlignment="Center" HorizontalAlignment="Left">
<TextBlock.InputBindings>
<MouseBinding MouseAction="LeftClick" Command="{Binding DataContext.OpenNewMaterialnumberCommand, RelativeSource={RelativeSource AncestorType={x:Type local:ArticleInfoSearchWindow}}}" />
</TextBlock.InputBindings>
</TextBlock>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
This works great but I have to select the row first before I can click the link. It would be achievable with a datatrigger which sets the row to selected on hover, but I don't really want do select the row on hover.
Any better ideas?
I came up with this solution. First of all create a new DataGridCommandColumn inherited by DataGridBoundColumn.
public class DataGridCommandColumn : DataGridBoundColumn
{
[Bindable(true)]
public ICommand Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
public static readonly DependencyProperty CommandProperty = DependencyProperty.Register(
nameof(Command),
typeof(ICommand),
typeof(DataGridCommandColumn));
[Bindable(true)]
public int FontSize
{
get => (int)GetValue(FontSizeProperty);
set => SetValue(FontSizeProperty, value);
}
public static readonly DependencyProperty FontSizeProperty = DependencyProperty.Register(
nameof(FontSize),
typeof(int),
typeof(DataGridCommandColumn));
protected override FrameworkElement GenerateEditingElement(DataGridCell cell, object dataItem)
{
throw new NotImplementedException();
}
protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
{
var button = new Button();
button.SetResourceReference(FrameworkElement.StyleProperty, "ToolButonWithDisabledRecognizesAccessKey");
button.SetResourceReference(Control.ForegroundProperty, "PrimaryHueMidBrush");
button.Height = 20;
button.VerticalContentAlignment = VerticalAlignment.Top;
button.HorizontalContentAlignment = HorizontalAlignment.Left;
button.HorizontalAlignment = HorizontalAlignment.Left;
button.Padding = new Thickness(0);
button.FontWeight = FontWeights.Normal;
if (FontSize != 0)
{
button.FontSize = FontSize;
}
button.Click += Button_Click;
BindingOperations.SetBinding(button, ContentControl.ContentProperty, Binding);
return button;
}
private void Button_Click(object sender, RoutedEventArgs e)
{
if (Command == null)
{
return;
}
if (Command.CanExecute(null))
{
Command.Execute(null);
}
}
}
Then create a proxy:
public class ViewModelProxy : Freezable
{
protected override Freezable CreateInstanceCore()
{
return new ViewModelProxy();
}
public ViewModelBase ViewModel
{
get => (ViewModelBase)GetValue(ViewModelProperty);
set => SetValue(ViewModelProperty, value);
}
public static readonly DependencyProperty ViewModelProperty =
DependencyProperty.Register("ViewModel", typeof(ViewModelBase), typeof(ViewModelProxy), new UIPropertyMetadata(null));
}
Later you can use it:
<utility:ViewModelProxy x:Key="ViewModelProxy" ViewModel="{Binding}" />
<controls:DataGridCommandColumn Header="{lex:Loc submissionnumber}" Binding="{Binding SubmissionNumber}" Command="{Binding ViewModel.OpenReceivableSubmissionNumberCommand, Source={StaticResource ViewModelProxy}}" Width="Auto" />

How to connect MouseDoubleClick to ViewModel in an Attached Behavior?

I am trying to implement Attached Behaviors functionality in the MVVM pattern. I have a Calendar control and would like to handle the MouseDoubleClick event. I was doing that using System.Windows.Interactivity and Interaction.Triggers. However, I am also using BlackoutDates in the Calendar and double-clicking on a Blackout Date results in the last valid selected date being passed to the MouseDoubleClick method, not the date clicked on.
So I am now targeting the CalendarDayButton, which will get me the date clicked on, but CDB doesn't have Commands, so I need to use an Attached Behavior. But I'm still not understanding how to get the MouseDoubleClick handler info into the ViewModel. My current code:
View
<Calendar HorizontalAlignment="Left" VerticalAlignment="Top" Margin="20,48,0,0"
SelectedDate="{Binding ReportDate, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
DisplayDateStart="{Binding ReportDateStart, Mode=OneTime}"
DisplayDateEnd="{Binding ReportDateEnd, Mode=OneTime}"
local:AttachedProperties.RegisterBlackoutDates="{Binding NoProdDates, Mode=OneWay}">
<Calendar.CalendarDayButtonStyle>
<Style TargetType="CalendarDayButton">
<Setter Property="local:AttachedBehaviors.IsValidDateSelected"
Value="{Binding ValidDateSelected, Mode=TwoWay}"/>
</Style>
</Calendar.CalendarDayButtonStyle>
</Calendar>
ViewModel
...
private bool validDateSelected;
public bool ValidDateSelected
{
get { return validDateSelected; }
set
{
if (validDateSelected != value)
{
validDateSelected = value;
RaisePropertyChanged("ValidDateSelected");
}
}
}
...
Attached Behaviors class
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Input;
namespace MDOD
{
public class AttachedBehaviors : DependencyObject
{
public static readonly DependencyProperty IsValidDateSelectedProperty =
DependencyProperty.RegisterAttached("IsValidDateSelected", typeof(bool), typeof(AttachedBehaviors),
new UIPropertyMetadata(false, OnIsValidDateSelectedChanged));
public static bool GetIsValidDateSelected(DependencyObject obj)
{
return (bool)obj.GetValue(IsValidDateSelectedProperty);
}
public static void SetIsValidDateSelected(DependencyObject obj, bool value)
{
obj.SetValue(IsValidDateSelectedProperty, value);
}
private static void OnIsValidDateSelectedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
CalendarDayButton cdb = d as CalendarDayButton;
if (cdb != null)
{
if ((bool)e.NewValue)
{
cdb.MouseDoubleClick += cdb_MouseDoubleClick;
}
else
{
cdb.MouseDoubleClick -= cdb_MouseDoubleClick;
}
}
}
private static void cdb_MouseDoubleClick(object sender, MouseButtonEventArgs e)
{
// How do I get this info to the ViewModel?
}
}
}

Set focus on another control when a key pressed without code-behind

I'm implementing something like an autosuggestion control: I have a user control that contains a TextBox and a ListBox. When the user enters text I'm handing it with System.Windows.Interactivity behaviors and filling the ListBox with some values...
Everything works fine... but I want to enable the user to select items in the ListBox (i.e. to set Focus on the ListBox) when the down arrow key is pressed.
I know that it is possible to handle the KeyPressDown event of the TextBox in the code-behind .cs file but how can I avoid this?
If you already use Interactivity that should not be much of an issue, just implement your own TriggerAction that has the properties Key & TargetName to indentify when and what to focus. Set it in an EventTrigger for PreviewKeyDown.
Sample implementation & use:
<TextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewKeyDown">
<t:KeyDownFocusAction Key="Down"
Target="{Binding ElementName=lbx}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<ListBox Name="lbx" ItemsSource="{Binding Data}" />
class KeyDownFocusAction : TriggerAction<UIElement>
{
public static readonly DependencyProperty KeyProperty =
DependencyProperty.Register("Key", typeof(Key), typeof(KeyDownFocusAction));
public Key Key
{
get { return (Key)GetValue(KeyProperty); }
set { SetValue(KeyProperty, value); }
}
public static readonly DependencyProperty TargetProperty =
DependencyProperty.Register("Target", typeof(UIElement), typeof(KeyDownFocusAction), new UIPropertyMetadata(null));
public UIElement Target
{
get { return (UIElement)GetValue(TargetProperty); }
set { SetValue(TargetProperty, value); }
}
protected override void Invoke(object parameter)
{
if (Keyboard.IsKeyDown(Key))
{
Target.Focus();
}
}
}
Tested it and it works, note that KeyDown does not because the arrow keys are intercepted and marked as handled by the TextBox.
I don't think you can avoid it
What's wrong with capturing the KeyDown event of the TextBox and if it's an Up or Down arrow key, just trigger the ListBox.KeyDown event in the code behind?
I see no reason not to use code-behind in MVVM if it is to provide view-specific functionality such as focus
This answer is based on the one from H.B., and adds support for checking to see if the Ctrl key is pressed. This means it can handle keycombinations such as Ctrl-F for find.
XAML
<TextBox>
<i:Interaction.Triggers>
<i:EventTrigger EventName="PreviewKeyDown">
<t:KeyDownFocusAction Key="Down" Ctrl="True"
Target="{Binding ElementName=lbx}" />
</i:EventTrigger>
</i:Interaction.Triggers>
</TextBox>
<ListBox Name="lbx" ItemsSource="{Binding Data}" />
Namespaces
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
See help on adding System.Windows.Interactivity.
DependencyProperty
public class KeyDownFocusAction : TriggerAction<UIElement>
{
public static readonly DependencyProperty KeyProperty =
DependencyProperty.Register("Key", typeof(Key), typeof(KeyDownFocusAction));
public Key Key
{
get { return (Key)GetValue(KeyProperty); }
set { SetValue(KeyProperty, value); }
}
public static readonly DependencyProperty CtrlProperty =
DependencyProperty.Register("Ctrl", typeof(bool), typeof(KeyDownFocusAction));
public bool Ctrl
{
get { return (bool)GetValue(CtrlProperty); }
set { SetValue(CtrlProperty, value); }
}
public static readonly DependencyProperty TargetProperty =
DependencyProperty.Register("Target", typeof(UIElement), typeof(KeyDownFocusAction), new UIPropertyMetadata(null));
public UIElement Target
{
get { return (UIElement)GetValue(TargetProperty); }
set { SetValue(TargetProperty, value); }
}
protected override void Invoke(object parameter)
{
if (Keyboard.IsKeyDown(Key))
{
if (Ctrl == true)
{
if (Keyboard.IsKeyDown(Key.LeftCtrl) || Keyboard.IsKeyDown(Key.RightCtrl))
{
Target.Focus();
}
}
}
}
}

Executing viewmodels command on enter in TextBox

I want to execute a command in my viewmodel when the user presses enter in a TextBox.
The command works when bound to a button.
<Button Content="Add" Command="{Binding Path=AddCommand}" />
But I can't bring it to work from the TextBox.
I tried an Inputbinding, but it didn't work.
<TextBox.InputBindings>
<KeyBinding Command="{Binding Path=AddCommand}" Key="Enter"/>
</TextBox.InputBindings>
I also tried to set the working button as default, but it doesn't get executed when enter is pressed.
Thanks for your help.
I know I am late to the party, but I got this to work for me. Try using Key="Return" instead of Key="Enter"
Here is the full example
<TextBox Text="{Binding FieldThatIAmBindingToo, UpdateSourceTrigger=PropertyChanged}">
<TextBox.InputBindings>
<KeyBinding Command="{Binding AddCommand}" Key="Return" />
</TextBox.InputBindings>
</TextBox>
Make sure to use UpdateSourceTrigger=PropertyChanged in your binding, otherwise the property will not be updated until focus is lost, and pressing enter will not lose focus...
Hope this was helpful!
You have probably not made the command a property, but a field. It only works to bind to properties. Change your AddCommand to a property and it will work. (Your XAML works fine for me with a property instead of a field for the command -> no need for any code behind!)
Here's an attached dependency property I created for this. It has the advantage of ensuring that your text binding is updated back to the ViewModel before the command fires (useful for silverlight which doesn't support the property changed update source trigger).
public static class EnterKeyHelpers
{
public static ICommand GetEnterKeyCommand(DependencyObject target)
{
return (ICommand)target.GetValue(EnterKeyCommandProperty);
}
public static void SetEnterKeyCommand(DependencyObject target, ICommand value)
{
target.SetValue(EnterKeyCommandProperty, value);
}
public static readonly DependencyProperty EnterKeyCommandProperty =
DependencyProperty.RegisterAttached(
"EnterKeyCommand",
typeof(ICommand),
typeof(EnterKeyHelpers),
new PropertyMetadata(null, OnEnterKeyCommandChanged));
static void OnEnterKeyCommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
ICommand command = (ICommand)e.NewValue;
FrameworkElement fe = (FrameworkElement)target;
Control control = (Control)target;
control.KeyDown += (s, args) =>
{
if (args.Key == Key.Enter)
{
// make sure the textbox binding updates its source first
BindingExpression b = control.GetBindingExpression(TextBox.TextProperty);
if (b != null)
{
b.UpdateSource();
}
command.Execute(null);
}
};
}
}
You use it like this:
<TextBox
Text="{Binding Answer, Mode=TwoWay}"
my:EnterKeyHelpers.EnterKeyCommand="{Binding SubmitAnswerCommand}"/>
You need to define Gesture instead of Key property of the KeyBinding:
<TextBox.InputBindings>
<KeyBinding Gesture="Enter" Command="{Binding AddCommand}"/>
</TextBox.InputBindings>
In addition to Mark Heath's answer, I took the class one step further by implementing Command Parameter attached property in this way;
public static class EnterKeyHelpers
{
public static ICommand GetEnterKeyCommand(DependencyObject target)
{
return (ICommand)target.GetValue(EnterKeyCommandProperty);
}
public static void SetEnterKeyCommand(DependencyObject target, ICommand value)
{
target.SetValue(EnterKeyCommandProperty, value);
}
public static readonly DependencyProperty EnterKeyCommandProperty =
DependencyProperty.RegisterAttached(
"EnterKeyCommand",
typeof(ICommand),
typeof(EnterKeyHelpers),
new PropertyMetadata(null, OnEnterKeyCommandChanged));
public static object GetEnterKeyCommandParam(DependencyObject target)
{
return (object)target.GetValue(EnterKeyCommandParamProperty);
}
public static void SetEnterKeyCommandParam(DependencyObject target, object value)
{
target.SetValue(EnterKeyCommandParamProperty, value);
}
public static readonly DependencyProperty EnterKeyCommandParamProperty =
DependencyProperty.RegisterAttached(
"EnterKeyCommandParam",
typeof(object),
typeof(EnterKeyHelpers),
new PropertyMetadata(null));
static void OnEnterKeyCommandChanged(DependencyObject target, DependencyPropertyChangedEventArgs e)
{
ICommand command = (ICommand)e.NewValue;
Control control = (Control)target;
control.KeyDown += (s, args) =>
{
if (args.Key == Key.Enter)
{
// make sure the textbox binding updates its source first
BindingExpression b = control.GetBindingExpression(TextBox.TextProperty);
if (b != null)
{
b.UpdateSource();
}
object commandParameter = GetEnterKeyCommandParam(target);
command.Execute(commandParameter);
}
};
}
}
Usage:
<TextBox Text="{Binding Answer, Mode=TwoWay}"
my:EnterKeyHelpers.EnterKeyCommand="{Binding SubmitAnswerCommand}"
my:EnterKeyHelpers.EnterKeyCommandParam="your parameter"/>

DataStateBehavior for Enum instead of bool? String?

Is there an easy way in WPF to bind VisualStates to enum values? Kinda like DataStateBehavior, but for an Enum?
The best way is to just go ahead and implement a Behavior that does just that -
public class EnumStateBehavior : Behavior<FrameworkElement>
{
public object EnumProperty
{
get { return (object)GetValue(EnumPropertyProperty); }
set { SetValue(EnumPropertyProperty, value); }
}
// Using a DependencyProperty as the backing store for EnumProperty. This enables animation, styling, binding, etc...
public static readonly DependencyProperty EnumPropertyProperty =
DependencyProperty.Register("EnumProperty", typeof(object), typeof(EnumStateBehavior), new UIPropertyMetadata(null, EnumPropertyChanged));
static void EnumPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
if (e.NewValue == null) return;
EnumStateBehavior eb = sender as EnumStateBehavior;
VisualStateManager.GoToElementState(eb.AssociatedObject, e.NewValue.ToString(), true);
}
}
The usage is extremely simple - use as follows:
<i:Interaction.Behaviors>
<local:EnumStateBehavior EnumProperty="{Binding MyEnumProperty}" />
</i:Interaction.Behaviors>
You can do it in pure xaml by using a DataTrigger per possible enum value with each trigger calling GoToStateAction with a different state. See the example below. For more details take a look at
Enum driving a Visual State change via the ViewModel.
<i:Interaction.Triggers>
<ei:DataTrigger Binding="{Binding ConfirmedAnswerStatus}" Value="Unanswered">
<ei:GoToStateAction StateName="UnansweredState" UseTransitions="False" />
</ei:DataTrigger>
<ei:DataTrigger Binding="{Binding ConfirmedAnswerStatus}" Value="Correct">
<ei:GoToStateAction StateName="CorrectlyAnsweredState" UseTransitions="True" />
</ei:DataTrigger>
<ei:DataTrigger Binding="{Binding ConfirmedAnswerStatus}" Value="Incorrect">
<ei:GoToStateAction StateName="IncorrectlyAnsweredState" UseTransitions="True" />
</ei:DataTrigger>
</i:Interaction.Triggers>
There is a DataStateSwitchBehavior in SL that could be ported to WPF: Anyone have a DataStateSwitchBehavior for WPF4?
the syntax is pretty straightforward:
<is:DataStateSwitchBehavior Binding="{Binding Orientation}">
<is:DataStateSwitchCase Value="Left" State="LeftState"/>
<is:DataStateSwitchCase Value="Right" State="RightState"/>
<is:DataStateSwitchCase Value="Down" State="DownState"/>
<is:DataStateSwitchCase Value="Up" State="UpState"/>
<is:DataStateSwitchCase/>
I was having issues with the above EnumStateBehavior answer.
The PropertyChanged handler will first trigger when the AssociatedObject is null (since the binding has been set up but the Behavior hasn't been attached yet). Also, even when the behavior is first attached, the target elements of the VisualState animation may not yet exist since the behavior may have been attached before other child visual trees.
The solution was to use the Loaded event on the associated object to ensure the binding's initial state is set.
public class EnumStateBehavior : Behavior<FrameworkElement>
{
public static readonly DependencyProperty BindingProperty =
DependencyProperty.Register(nameof(Binding), typeof(object), typeof(EnumStateBehavior), new UIPropertyMetadata(null, BindingPropertyChanged));
public object Binding
{
get { return (object)GetValue(BindingProperty); }
set { SetValue(BindingProperty, value); }
}
protected override void OnAttached()
{
base.OnAttached();
this.AssociatedObject.Loaded += AssociatedObject_Loaded;
}
protected override void OnDetaching()
{
this.AssociatedObject.Loaded -= AssociatedObject_Loaded;
base.OnDetaching();
}
private void AssociatedObject_Loaded(object sender, RoutedEventArgs e)
{
if (Binding != null)
GoToState();
}
private void GoToState()
{
VisualStateManager.GoToElementState(this.AssociatedObject, Binding.ToString(), true);
}
private static void BindingPropertyChanged(object sender, DependencyPropertyChangedEventArgs e)
{
var eb = (EnumStateBehavior)sender;
if (e.NewValue == null || eb.AssociatedObject == null || !eb.AssociatedObject.IsLoaded)
return;
eb.GoToState();
}
}

Resources