I have some code to set the focused property of a text box, but what i'm actually after is finding out if the text box currently has the keyboard focus, I need to determine this from my view model
public static class FocusExtension
{
public static bool GetIsFocused(DependencyObject obj)
{
return (bool)obj.GetValue(IsFocusedProperty);
}
public static void SetIsFocused(DependencyObject obj, bool value)
{
obj.SetValue(IsFocusedProperty, value);
}
public static readonly DependencyProperty IsFocusedProperty = DependencyProperty.RegisterAttached
(
"IsFocused",
typeof(bool),
typeof(FocusExtension),
new UIPropertyMetadata(false, OnIsFocusedPropertyChanged)
);
public static void OnIsFocusedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var uie = (UIElement)d;
if ((bool)e.NewValue)
{
uie.Focus();
}
}
}
And the xaml is
<TextBox Text="{Binding Path=ClientCode}" c:FocusExtension.IsFocused="{Binding IsClientCodeFocused}" />
source of code
have you seen the FocusManager? you can get/set focus using this object.
Edit
Based on the comments below, here's an example of an attached property that hooks up an event and updates the source of a binding. I'll add comments where I know you'll need to make modifications. Hopefully it will point you in the right direction
public class TextBoxHelper
{
// I excluded the generic stuff, but the property is called
// EnterUpdatesSource and it makes a TextBox update it's source
// whenever the Enter key is pressed
// Property Changed Event - You have this in your class above
private static void EnterUpdatesTextSourcePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
UIElement sender = obj as UIElement;
if (obj != null)
{
// In my case, the True/False value just determined a behavior,
// so toggling true/false added/removed an event.
// Since you want your events to be on at all times, you'll either
// want to have two AttachedProperties (one to tell the control
// that it should be tracking the current focused state, and
// another for binding the actual focused state), or you'll want
// to find a way to only add the EventHandler when the
// AttachedProperty is first added and not toggle it on/off as focus
// changes or add it repeatedly whenever this value is set to true
// You can use the GotFocus and LostFocus Events
if ((bool)e.NewValue == true)
{
sender.PreviewKeyDown += new KeyEventHandler(OnPreviewKeyDownUpdateSourceIfEnter);
}
else
{
sender.PreviewKeyDown -= OnPreviewKeyDownUpdateSourceIfEnter;
}
}
}
// This is the EventHandler
static void OnPreviewKeyDownUpdateSourceIfEnter(object sender, KeyEventArgs e)
{
// You won't need this
if (e.Key == Key.Enter)
{
// or this
if (GetEnterUpdatesTextSource((DependencyObject)sender))
{
// But you'll want to take this bit and modify it so it actually
// provides a value to the Source based on UIElement.IsFocused
UIElement obj = sender as UIElement;
// If you go with two AttachedProperties, this binding should
// point to the property that contains the IsFocused value
BindingExpression textBinding = BindingOperations.GetBindingExpression(
obj, TextBox.TextProperty);
// I know you can specify a value for a binding source, but
// I can't remember the exact syntax for it right now
if (textBinding != null)
textBinding.UpdateSource();
}
}
}
There might be a better way of accomplishing what you're trying to do, but if not then I hope this provides a good starting point :)
in your OnIsFocusedPropertyChanged handler, you need to get a reference to the control that it is being set on and subscribe to its FocusChanged event, where you can re-set the dependency pproperty. Make sure in your XAML you set the binding mode to TwoWay
Related
I have a class that contains 2 data:
public class Settings
{
EnumType Mode;
float Rate;
}
The value of the attribute Rate depends on the value of the attribute Mode.
I use the CoerceValue function to update ensure that the Rate value is correct at all time.
The property are binded to the UI in read only mode (one way) because I want to do some additional process on write.
So I created an event on my UI control to know when the Rate property changed.
In my window the UI is binded to a static variable SelectedSettings.
My problem is the following:
When I change the value of SelectedSettings (with another settings class), instead of loading the new settings in the UI, it does the following operations:
Set the new Mode value in the UI
The previous operation start the coerceValue process and modify the Rate value.
The modifiation of the rate value trig the event.
The trigged event write the new value of rate in the SelectedSettings.
Set the new Rate value (that is now incorrect).
What did I do wrong ? is my utilisation of dependency properties and coerce system invalid ?
Edit:
Here more information about my actual state
I created a user control to display the two settings options
public partial class EncodingQualitySliderControl : UserControl
{
public static readonly DependencyProperty BitrateProperty = DependencyProperty.Register(
"Bitrate",
typeof(double),
typeof(EncodingQualitySliderControl),
new FrameworkPropertyMetadata(new PropertyChangedCallback(EncodingQualitySliderControl.OnBitrateValueChanged), new CoerceValueCallback(EncodingQualitySliderControl.CoerceBitrateValue)));
public static readonly DependencyProperty EncodingModeProperty = DependencyProperty.Register(
"EncodingMode",
typeof(EncodingMode),
typeof(EncodingQualitySliderControl),
new PropertyMetadata(new PropertyChangedCallback(EncodingQualitySliderControl.OnEncodingModeValueChanged)));
public event EventHandler<double> BitrateValueChanged;
public EncodingQualitySliderControl()
{
this.InitializeComponent();
this.CoerceValue(EncodingQualitySliderControl.BitrateProperty);
Debug.Assert(this.slider != null);
this.slider.ValueChanged += this.Slider_ValueChanged;
}
public EncodingMode EncodingMode
{
get
{
return (EncodingMode)this.GetValue(EncodingQualitySliderControl.EncodingModeProperty);
}
set
{
this.SetCurrentValue(EncodingQualitySliderControl.EncodingModeProperty, value);
}
}
public double Bitrate
{
get
{
return (double)this.GetValue(EncodingQualitySliderControl.BitrateProperty);
}
set
{
this.SetCurrentValue(EncodingQualitySliderControl.BitrateProperty, this.GetNearestTickValue(value));
}
}
private static void OnBitrateValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs eventArgs)
{
EncodingQualitySliderControl encodingQualitySliderControl = sender as EncodingQualitySliderControl;
encodingQualitySliderControl.slider.Value = (double)eventArgs.NewValue;
}
private static object CoerceBitrateValue(DependencyObject sender, object basevalue)
{
EncodingQualitySliderControl encodingQualitySliderControl = sender as EncodingQualitySliderControl;
return encodingQualitySliderControl.GetNearestTickValue((double)basevalue);
}
private static void OnEncodingModeValueChanged(DependencyObject sender, DependencyPropertyChangedEventArgs eventArgs)
{
EncodingQualitySliderControl encodingQualitySliderControl = sender as EncodingQualitySliderControl;
Slider sliderControl = encodingQualitySliderControl.slider;
// ... Some code that change the user control depending on the new mode.
encodingQualitySliderControl.CoerceValue(EncodingQualitySliderControl.BitrateProperty);
// Send ValueChanged in case of bitrate value change from coerce value.
encodingQualitySliderControl.BitrateValueChanged?.Invoke(encodingQualitySliderControl, encodingQualitySliderControl.Bitrate);
}
private void Slider_ValueChanged(object sender, RoutedPropertyChangedEventArgs<double> e)
{
this.SetCurrentValue(EncodingQualitySliderControl.BitrateProperty, e.NewValue);
// Only send the bitrate value changed event if the value change come from the slider.
this.BitrateValueChanged?.Invoke(this, e.NewValue);
}
}
Then I have a class with my settings data:
public class ConversionPreset : INotifyPropertyChanged
{
private EncodingMode mode;
private double bitrate;
public event PropertyChangedEventHandler PropertyChanged;
[XmlAttribute]
public EncodingMode Mode
{
get
{
return this.mode;
}
set
{
this.mode = value;
this.OnPropertyChanged();
}
}
[XmlAttribute]
public double Bitrate
{
get
{
return this.bitrate;
}
set
{
this.bitrate= value;
this.OnPropertyChanged();
}
}
}
Then i have a data binding between my settings class and my user control.
<controls:EncodingQualitySliderControl x:Name="EncodingQualitySlider" BitrateValueChanged="EncodingQualitySlider_ValueChanged"
EncodingMode="{Binding SelectedPreset.Mode, ElementName=window, Mode=OneWay}"
Bitrate="{Binding SelectedPreset.Bitrate, ElementName=window, Mode=OneWay}" />
And some code in the main window to apply the modifications
private void EncodingQualitySlider_ValueChanged(object sender, double bitrateValue)
{
this.SelectedPreset?.Bitrate = bitrateValue;
}
Edit 2:
Here is a minimal project that reproduce my problem:
Linked dependency property test project
The wanted behavior is: when I start the application I want to see the preset1 (bitrate 32). Then if I check the preset2, I want to see the a bitrate value of 225.
Thanks a lot.
I finally found how to make my project work, thanks to the sample project.
I did not totally understood the concept of dependency properties and tried to avoid it, using events fired by controls.
I just remove all the code that set the values of my settings from events and then put the property binding in the mode "TwoWay". I didn't do that in the first place because I didn't know how to set my settings directly from binding. To make it work, I used (like Peter Duniho said, Thanks!) value converters (IValueConverter).
Thank you all for your comments!
Requirement
I want to have ComboBox where user may enter some text or choose text from drop-down list. Binding source should be updated when user press Enter after typing or when item is simply selected from drop-down list (best View behavior in my case).
Problem
when UpdateSourceTrigger=PropertyChange (default) is set, then source update will trigger after every character, which is not good, because property setter call is expensive;
when UpdateSourceTrigger=LostFocus is set, then selecting item from drop-down list will require one more action to actually lose focus, which is not very user-friendly (required additional click after click to select item).
I tried to use UpdateSourceTrigger=Explicit, but it doesn't go well:
<ComboBox IsEditable="True" VerticalAlignment="Top" ItemsSource="{Binding List}"
Text="{Binding Text, UpdateSourceTrigger=LostFocus}"
SelectionChanged="ComboBox_SelectionChanged"
PreviewKeyDown="ComboBox_PreviewKeyDown" LostFocus="ComboBox_LostFocus"/>
public partial class MainWindow : Window
{
private string _text = "Test";
public string Text
{
get { return _text; }
set
{
if (_text != value)
{
_text = value;
MessageBox.Show(value);
}
}
}
public string[] List
{
get { return new[] { "Test", "AnotherTest" }; }
}
public MainWindow()
{
InitializeComponent();
DataContext = this;
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
((ComboBox)sender).GetBindingExpression(ComboBox.TextProperty).UpdateSource();
}
private void ComboBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if(e.Key == Key.Enter)
((ComboBox)sender).GetBindingExpression(ComboBox.TextProperty).UpdateSource();
}
private void ComboBox_LostFocus(object sender, RoutedEventArgs e)
{
((ComboBox)sender).GetBindingExpression(ComboBox.TextProperty).UpdateSource();
}
}
This code has 2 issues:
when item is selected from drop-down menu, then source is updated with previously selected value, why?
when user start typing something and then click drop-down button to choose something from the list - source is updated again (due to focus lost?), how to avoid that?
I am a bit afraid to fall under XY problem that's why I posted original requirement (perhaps I went wrong direction?) instead of asking to help me to fix one of above problems.
Your approach of updating the source in response to specific events is on the right track, but there is more you have to take into account with the way ComboBox updates things. Also, you probably want to leave UpdateSourceTrigger set to LostFocus so that you do not have as many update cases to deal with.
You should also consider moving the code to a reusable attached property so you can apply it to combo boxes elsewhere in the future. It so happens that I have created such a property in the past.
/// <summary>
/// Attached properties for use with combo boxes
/// </summary>
public static class ComboBoxBehaviors
{
private static bool sInSelectionChange;
/// <summary>
/// Whether the combo box should commit changes to its Text property when the Enter key is pressed
/// </summary>
public static readonly DependencyProperty CommitOnEnterProperty = DependencyProperty.RegisterAttached("CommitOnEnter", typeof(bool), typeof(ComboBoxBehaviors),
new PropertyMetadata(false, OnCommitOnEnterChanged));
/// <summary>
/// Returns the value of the CommitOnEnter property for the specified ComboBox
/// </summary>
public static bool GetCommitOnEnter(ComboBox control)
{
return (bool)control.GetValue(CommitOnEnterProperty);
}
/// <summary>
/// Sets the value of the CommitOnEnterProperty for the specified ComboBox
/// </summary>
public static void SetCommitOnEnter(ComboBox control, bool value)
{
control.SetValue(CommitOnEnterProperty, value);
}
/// <summary>
/// Called when the value of the CommitOnEnter property changes for a given ComboBox
/// </summary>
private static void OnCommitOnEnterChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
ComboBox control = sender as ComboBox;
if (control != null)
{
if ((bool)e.OldValue)
{
control.KeyUp -= ComboBox_KeyUp;
control.SelectionChanged -= ComboBox_SelectionChanged;
}
if ((bool)e.NewValue)
{
control.KeyUp += ComboBox_KeyUp;
control.SelectionChanged += ComboBox_SelectionChanged;
}
}
}
/// <summary>
/// Handler for the KeyUp event attached to a ComboBox that has CommitOnEnter set to true
/// </summary>
private static void ComboBox_KeyUp(object sender, KeyEventArgs e)
{
ComboBox control = sender as ComboBox;
if (control != null && e.Key == Key.Enter)
{
BindingExpression expression = control.GetBindingExpression(ComboBox.TextProperty);
if (expression != null)
{
expression.UpdateSource();
}
e.Handled = true;
}
}
/// <summary>
/// Handler for the SelectionChanged event attached to a ComboBox that has CommitOnEnter set to true
/// </summary>
private static void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (!sInSelectionChange)
{
var descriptor = DependencyPropertyDescriptor.FromProperty(ComboBox.TextProperty, typeof(ComboBox));
descriptor.AddValueChanged(sender, ComboBox_TextChanged);
sInSelectionChange = true;
}
}
/// <summary>
/// Handler for the Text property changing as a result of selection changing in a ComboBox that has CommitOnEnter set to true
/// </summary>
private static void ComboBox_TextChanged(object sender, EventArgs e)
{
var descriptor = DependencyPropertyDescriptor.FromProperty(ComboBox.TextProperty, typeof(ComboBox));
descriptor.RemoveValueChanged(sender, ComboBox_TextChanged);
ComboBox control = sender as ComboBox;
if (control != null && sInSelectionChange)
{
sInSelectionChange = false;
if (control.IsDropDownOpen)
{
BindingExpression expression = control.GetBindingExpression(ComboBox.TextProperty);
if (expression != null)
{
expression.UpdateSource();
}
}
}
}
}
Here is an example of setting the property in xaml:
<ComboBox IsEditable="True" ItemsSource="{Binding Items}" Text="{Binding SelectedItem, UpdateSourceTrigger=LostFocus}" local:ComboBoxBehaviors.CommitOnEnter="true" />
I think this will give you the behavior you are looking for. Feel free to use it as-is or modify it to your liking.
There is one issue with the behavior implementation where if you start to type an existing value (and don't press enter), then choose that same value from the dropdown, the source does not get updated in that case until you press enter, change focus, or choose a different value. I am sure that could be worked out, but it was not enough of an issue for me to spend time on it since it is not a normal workflow.
I would recommend keeping UpdateSourceTrigger=PropertyChanged, and also putting a delay on the combobox to help mitigate your expensive setter/update problem. The delay will cause the PropertyChanged event to wait the amount of milliseconds you specify before firing.
More on Delay here: http://www.jonathanantoine.com/2011/09/21/wpf-4-5-part-4-the-new-bindings-delay-property/
Hopefully somebody will come up with a better solution for you, but this should at least get you moving along for now.
I had a similar issue, I handled it in the .cs code. It isn't the XAML way, but it gets it done. First I broke the binding, then manually propagated the value both ways.
<ComboBox x:Name="Combo_MyValue"
ItemsSource="{Binding Source={StaticResource ListData}, XPath=MyContextType/MyValueType}"
DisplayMemberPath="#Description"
SelectedValuePath="#Value"
IsEditable="True"
Loaded="Combo_MyValue_Loaded"
SelectionChanged = "Combo_MyValue_SelectionChanged"
LostFocus="Combo_MyValue_LostFocus"
/>
private void Combo_MyValue_Loaded(object sender, RoutedEventArgs e)
{
if (DataContext != null)
{
Combo_MyValue.SelectedValue = ((MyContextType)DataContext).MyValue;
}
}
private void Combo_MyValue_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if( e.AddedItems.Count == 0)
{
// this is a custom value, we'll set it in the lost focus event
return;
}
// this is a picklist value, get the value from itemsource
XmlElement selectedItem = (XmlElement)e.AddedItems[0];
string selectedValue = selectedItem.GetAttribute("Value");
((PumpParameters)DataContext).MyValue = selectedValue;
}
private void Combo_MyValue_LostFocus(object sender, RoutedEventArgs e)
{
if( Combo_MyValue.IsDropDownOpen || Combo_MyValue.SelectedIndex != -1)
{
// not a custom value
return;
}
// custom value
((MyContextType)DataContext).MyValue = Combo_MyValue.Text;
}
I had this same problem. I had a binding on the ComboBox.Text property, including ValidationRules. It seems like a better user experience if the source is updated immediately when something is selected from the list, but if something is being typed into the box, then I didn't want validation to occur until the entry is complete.
I got a satisfying solution by letting the binding's UpdateSourceTrigger="LostFocus". I created an attached behavior that forces update to binding source when the SelectionChanged event is published (it is not published when typing into the TextBox). If you prefer, you can put this event handler into the code-behind instead of an attached behavior or attached property class.
protected void ComboBox_SelectionChanged(Object sender, SelectionChangedEventArgs e)
{
// Get the BindingExpression object for the ComboBox.Text property.
// We'll use this to force the value of ComboBox.Text to update to the binding source
var be = BindingOperations.GetBindingExpression(comboBox, ComboBox.TextProperty);
if (be == null) return;
// Unfortunately, the code of the ComboBox class publishes the SelectionChanged event
// immediately *before* it transfers the value of the SelectedItem to its Text property.
// Therefore, the ComboBox.Text property does not yet have the value
// that we want to transfer to the binding source. We use reflection to invoke method
// ComboBox.SelectedItemUpdated to force the update to the Text property just a bit early.
// Method SelectedItemUpdated encapsulates everything that we need--it is exactly what
// happens from method ComboBox.OnSelectionChanged.
var method = typeof(ComboBox).GetMethod("SelectedItemUpdated",
BindingFlags.NonPublic | BindingFlags.Instance);
if (method == null) return;
method.Invoke(comboBox, new Object[] { });
// Now that ComboBox.Text has the proper value, we let the binding object update
// its source.
be.UpdateSource();
}
I need to be able to access the binding expression for the Text property of a TextBox from within a DependencyProperty on a TextBox. the value of my DependencyProperty is set in XAML. I'm calling GetBindingExpression in the PropertyChangedCallback method of my DependencyProperty, but I'm too early at this point because GetBindingExpression always returns null here, yet after the window fully loads it definitely returns a value (I tested using a button on screen to change the value of my DependencyProperty).
Clearly I have a load order issue here where my DependencyProperty's value is set before the Text property is bound to my view model. My question is, is there some event I can hook into to identify when the binding of the Text property is complete? Preferably without modifying the XAML of my TextBox as I have hundreds of them in the solution.
public class Foobar
{
public static readonly DependencyProperty TestProperty =
DependencyProperty.RegisterAttached(
"Test", typeof(bool), typeof(Foobar),
new UIPropertyMetadata(false, Foobar.TestChanged));
private static void TestChanged(DependencyObject o, DependencyPropertyChangedEventArgs args)
{
var textBox = (TextBox)o;
var expr = textBox.GetBindingExpression(TextBox.TextProperty);
//expr is always null here, but after the window loads it has a value
}
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
public static bool GetTest(DependencyObject obj)
{
return (bool)obj.GetValue(Foobar.TestProperty);
}
[AttachedPropertyBrowsableForType(typeof(TextBoxBase))]
public static void SetTest(DependencyObject obj, bool value)
{
obj.SetValue(Foobar.TestProperty, value);
}
}
Try listen to LayoutUpdated event. I think its called layout updated event. Else google it. Its a crazy little event which will be fired everytime no matter what you do ex. when loading, drawing, even when you move your mouse!
Take a look at this pseudo code:
private static void TestChanged(DependencyObject o, DependencyPropertyChangedEventArgs args)
{
var textBox = (TextBox)o;
// start listening
textBox.LayoutUpdated += SomeMethod();
}
private static void SomeMethod(...)
{
// this will be called very very often
var expr = textBox.GetBindingExpression(TextBox.TextProperty);
if(expr != null)
{
// finally you got the value so stop listening
textBox.LayoutUpdated -= SomeMethod();
I want to add to my application, that when I use the mousewheel in any place of my application it should case a scrolling in the last active or last used combobox.
Can you help me? How should I proceed.
Thank you very much.
Here are two ways I attempted to solve the problem
Approach 1: Attached properties
I have created a class MouseExtension with an Attached Property ScrollAnywhere which will enable the bahavior on any element of your application. You may choose to have different scrollable region as well, so different combobox can be scrolled when mouse is in their respective regions. you may have a sub region which have this behavior for it's child control only. possibilities are unlimited.
Also this approach makes it injectable
example
<Window x:Class="CSharpWPF.View"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:l="clr-namespace:CSharpWPF"
Title="MainWindow"
l:MouseExtension.ScrollAnywhere="true">
...
</Window>
I have set the property l:MouseExtension.ScrollAnywhere="true" in order to enable the behavior
code
namespace CSharpWPF
{
class MouseExtension : DependencyObject
{
public static bool GetScrollAnywhere(DependencyObject obj)
{
return (bool)obj.GetValue(ScrollAnywhereProperty);
}
public static void SetScrollAnywhere(DependencyObject obj, bool value)
{
obj.SetValue(ScrollAnywhereProperty, value);
}
// Using a DependencyProperty as the backing store for ScrollAnywhere. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ScrollAnywhereProperty =
DependencyProperty.RegisterAttached("ScrollAnywhere", typeof(bool), typeof(MouseExtension), new PropertyMetadata(false, OnScrollAnywhere));
private static void OnScrollAnywhere(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
UIElement element = d as UIElement;
if ((bool)e.NewValue)
element.PreviewMouseWheel += element_PreviewMouseWheel;
else
element.PreviewMouseWheel -= element_PreviewMouseWheel;
}
static void element_PreviewMouseWheel(object sender, System.Windows.Input.MouseWheelEventArgs e)
{
IInputElement element = FocusManager.GetFocusedElement(sender as DependencyObject);
if (element != null && e.Source != element)
{
MouseWheelEventArgs args = new MouseWheelEventArgs(Mouse.PrimaryDevice, e.Timestamp, e.Delta) { RoutedEvent = UIElement.MouseWheelEvent };
element.RaiseEvent(args);
e.Handled = true;
}
}
}
}
this property is also settable via styles or programmatically
eg
<Style x:key="MyScrollStyle">
<Setter Property="l:MouseExtension.ScrollAnywhere" Value="true"/>
</Style>
or
MouseExtension.SetScrollAnywhere(element, true);
or
element.SetValue(MouseExtension.ScrollAnywhereProperty, true);
Approach 2: Override method
this is rather simple approach if you can access the code behind of window or user control.
simply paste the code below in the class and rest will be handled
protected override void OnMouseWheel(MouseWheelEventArgs e)
{
IInputElement element = FocusManager.GetFocusedElement(this);
if (element != null && e.Source != element)
{
MouseWheelEventArgs args = new MouseWheelEventArgs(Mouse.PrimaryDevice, e.Timestamp, e.Delta) { RoutedEvent = UIElement.MouseWheelEvent };
element.RaiseEvent(args);
e.Handled = true;
}
base.OnMouseWheel(e);
}
Conclusion
You may choose the method which you prefer. I would suggest to go for attached property approach as that make this behavior plug and play. E.g. You can turn on or off the behavior via a check box. You can store the user preference in settings and apply to the property.
Attached properties allows you to extend the behavior, may you need another behavior after this.
Not really sure how to tackle this issue:
I have a "Save" button that has access keys attached to it... but, if I type something into a textbox and press the access keys to save, the textbox doesn't update my viewmodel because it never lost focus. Any way to solve this outside of changing the UpdateSourceTrigger to PropertyChanged?
Your problem is the UpdateSourceTrigger="LostFocus"
This is default for TextBoxes, and means that the TextBox will only update its bound value when it loses focus
One way to force it to update without setting UpdateSourceTrigger="PropertyChanged" is to hook into the KeyPress event and if the key combination is something that would trigger a save, call UpdateSource() first
Here's an Attached Property I like using when the Enter key should update the source.
It is used like this:
<TextBox Text="{Binding Name}"
local:TextBoxProperties.EnterUpdatesTextSource="True" />
and the Attached Property definition looks like this:
public class TextBoxProperties
{
public static readonly DependencyProperty EnterUpdatesTextSourceProperty =
DependencyProperty.RegisterAttached("EnterUpdatesTextSource", typeof(bool), typeof(TextBoxProperties),
new PropertyMetadata(false, EnterUpdatesTextSourcePropertyChanged));
// Get
public static bool GetEnterUpdatesTextSource(DependencyObject obj)
{
return (bool)obj.GetValue(EnterUpdatesTextSourceProperty);
}
// Set
public static void SetEnterUpdatesTextSource(DependencyObject obj, bool value)
{
obj.SetValue(EnterUpdatesTextSourceProperty, value);
}
// Changed Event - Attach PreviewKeyDown handler
private static void EnterUpdatesTextSourcePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
var sender = obj as UIElement;
if (obj != null)
{
if ((bool)e.NewValue)
{
sender.PreviewKeyDown += OnPreviewKeyDown_UpdateSourceIfEnter;
}
else
{
sender.PreviewKeyDown -= OnPreviewKeyDown_UpdateSourceIfEnter;
}
}
}
// If key being pressed is the Enter key, and EnterUpdatesTextSource is set to true, then update source for Text property
private static void OnPreviewKeyDown_UpdateSourceIfEnter(object sender, KeyEventArgs e)
{
if (e.Key == Key.Enter)
{
if (GetEnterUpdatesTextSource((DependencyObject)sender))
{
var obj = sender as UIElement;
BindingExpression textBinding = BindingOperations.GetBindingExpression(
obj, TextBox.TextProperty);
if (textBinding != null)
textBinding.UpdateSource();
}
}
}
}