It is convenient to have an "Accept Button" (in WPF: IsDefault="True") on a Form.
In the Windows Forms world, I used to read the data from the UI to the object(s) in the corresponding Click event of the button.
But with WPF, data binding ought to be used. In the constructor of the Window, I set this.DataContext = test;
And here comes the problem: the user entered some text in TextBox2, and hits the Enter key. Now, the command bound to the OK button gets executed, the data are saved.
But it is not the correct data! Why? TextBox2 has not yet lost focus, and consequently the ViewModel has not yet been updated.
Changing the UpdateSourceTrigger to PropertyChanged is not always appropriate (e.g. formatted numbers), I am looking for a general solution.
How do you overcome such a problem?
Typically I use a custom Attached Property to tell WPF to update the binding source when the Enter key is pressed
It is used in the XAML like this:
<TextBox Text="{Binding SomeProperty}"
local:TextBoxProperties.EnterUpdatesTextSource="True" />
And the code for the attached property is below:
public class TextBoxProperties
{
// When set to True, Enter Key will update Source
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 += OnPreviewKeyDownUpdateSourceIfEnter;
}
else
{
sender.PreviewKeyDown -= OnPreviewKeyDownUpdateSourceIfEnter;
}
}
}
// If key being pressed is the Enter key, and EnterUpdatesTextSource is set to true, then update source for Text property
private static void OnPreviewKeyDownUpdateSourceIfEnter(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();
}
}
}
}
Related
I have a TextBox whose text can be changed programmatically via binding from its Text property to a viewmodel property. This may for example happen as a result of a keypress (e.g. ↑ or ↓), but can also happen without any user input whatsoever. When this happens, it seems that any existing selection in the text box is removed. The behavior I desire is: If a text box has focus and all of the text is selected before the programmatic change (or if the text is empty), I want all of the text to be selected after the change. The text should however not be selected after a change caused by the user typing, since that would mean the user would just be replacing one character over and over again.
I have not found a way to accomplish this. Is it possible?
To be specific: I have set up a global event handler to select all text when a TextBox is focused, in order to allow users to more easily edit existing text in the TextBox if desired:
EventManager.RegisterClassHandler(
typeof(TextBox),
UIElement.GotFocusEvent,
new RoutedEventHandler((s, _) => (s as TextBox)?.SelectAll()));
However, in one of my views, tabbing out of TextBox A triggers an asynchronous action that changes the text in TextBox B (which is next in the tab order). This happens very quickly, but TextBox B gets focus before the text change happens, and thus the text is not selected. I would like the text that arrives in TextBox B to be selected so the user can more easily change it if desired.
I prefer implementing this kind of functionality in a Behavior that can be added in XAML; this requires the System.Windows.Interactivity.WPF NuGet Package.
I haven't tested this fully because I'm not exactly sure how to replicate your "asynchronous action", but it seems to work for the "normal" programmatic value changes that I've tried.
If you you really don't want the Behavior aspect of it, it should be fairly trivial to extract the event handling logic from it to use in whatever method you prefer.
Here is a short gif of it in action:
public class KeepSelectionBehavior : Behavior<TextBox>
{
private bool _wasAllTextSelected = false;
private int inputKeysDown = 0;
protected override void OnAttached()
{
base.OnAttached();
CheckSelection();
AssociatedObject.TextChanged += TextBox_TextChanged;
AssociatedObject.SelectionChanged += TextBox_SelectionChanged;
AssociatedObject.PreviewKeyDown += TextBox_PreviewKeyDown;
AssociatedObject.KeyUp += TextBox_KeyUp;
}
protected override void OnDetaching()
{
base.OnDetaching();
AssociatedObject.TextChanged -= TextBox_TextChanged;
AssociatedObject.SelectionChanged -= TextBox_SelectionChanged;
AssociatedObject.PreviewKeyDown -= TextBox_PreviewKeyDown;
AssociatedObject.KeyUp -= TextBox_KeyUp;
}
private void TextBox_TextChanged(object sender, TextChangedEventArgs e)
{
if (_wasAllTextSelected && inputKeysDown == 0)
{
AssociatedObject.SelectAll();
}
CheckSelection();
}
private void TextBox_SelectionChanged(object sender, RoutedEventArgs e)
{
CheckSelection();
}
private void TextBox_PreviewKeyDown(object sender, KeyEventArgs e)
{
if (IsInputKey(e.Key))
{
inputKeysDown++;
}
}
private void TextBox_KeyUp(object sender, KeyEventArgs e)
{
if (IsInputKey(e.Key))
{
inputKeysDown--;
}
}
private bool IsInputKey(Key key)
{
return
key == Key.Space ||
key == Key.Delete ||
key == Key.Back ||
(key >= Key.D0 && key <= Key.Z) ||
(key >= Key.Multiply && key <= Key.Divide) ||
(key >= Key.Oem1 && key <= Key.OemBackslash);
}
private void CheckSelection()
{
_wasAllTextSelected = AssociatedObject.SelectionLength == AssociatedObject.Text.Length;
}
}
You can use it like this:
<Window
x:Class="ScriptyBot.Client.TestWindow"
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:i="http://schemas.microsoft.com/expression/2010/interactivity"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="TestWindow"
Width="800"
Height="450"
mc:Ignorable="d">
<StackPanel>
<TextBox Name="TextBox1" Margin="20">
<i:Interaction.Behaviors>
<behaviors:KeepSelectionBehavior />
</i:Interaction.Behaviors>
</TextBox>
</StackPanel>
</Window>
I'm testing it with a simple DispatchTimer that updates the text every second:
public partial class TestWindow : Window
{
private DispatcherTimer timer;
public TestWindow()
{
InitializeComponent();
timer = new DispatcherTimer(DispatcherPriority.Normal);
timer.Interval = TimeSpan.FromSeconds(1);
timer.Tick += (sender, e) => { TextBox1.Text = DateTime.Now.ToString(); };
timer.Start();
}
}
By default, a Behavior has to be applied to every control manually in XAML, which can be very annoying. If you instead use this base class for your Behavior, you will be able to add it using a Style. This also works with implicit Styles too, so you can set it once in app.xaml, instead of manually for every control.
public class AttachableForStyleBehavior<TComponent, TBehavior> : Behavior<TComponent>
where TComponent : System.Windows.DependencyObject
where TBehavior : AttachableForStyleBehavior<TComponent, TBehavior>, new()
{
public static readonly DependencyProperty IsEnabledForStyleProperty =
DependencyProperty.RegisterAttached(name: "IsEnabledForStyle",
propertyType: typeof(bool),
ownerType: typeof(AttachableForStyleBehavior<TComponent, TBehavior>),
defaultMetadata: new FrameworkPropertyMetadata(false, OnIsEnabledForStyleChanged));
public bool IsEnabledForStyle
{
get => (bool)GetValue(IsEnabledForStyleProperty);
set => SetValue(IsEnabledForStyleProperty, value);
}
private static void OnIsEnabledForStyleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is UIElement uiElement)
{
var behaviors = Interaction.GetBehaviors(uiElement);
var existingBehavior = behaviors.FirstOrDefault(b => b.GetType() == typeof(TBehavior)) as TBehavior;
if ((bool)e.NewValue == false && existingBehavior != null)
{
behaviors.Remove(existingBehavior);
}
else if ((bool)e.NewValue == true && existingBehavior == null)
{
behaviors.Add(new TBehavior());
}
}
}
}
The declaration of the Behavior class changes to look like this:
public class KeepSelectionBehavior : AttachableForStyleBehavior<TextBox, KeepSelectionBehavior>
And is applied like this (It can even be bound to a bool and dynamically turned on and off!):
<Style TargetType="TextBox">
<Setter Property="KeepSelectionBehavior.IsEnabledForStyle" Value="True" />
</Style>
Personally, I prefer using the Style based method anyway, even when adding it to a single, one-off, control. It is significantly less typing, and I don't have to remember how to define the xmlns for the Interactions or Behaviors namespaces.
I would like the text that arrives in TextBox B to be selected so the user can more easily change it if desired.
Handle the TextChanged event then. This event is raised whenever the Text property is changed. Yoy may want to add a delay so the user can type without the text being selected on each key stroke:
private DateTime _last;
private void txt2_TextChanged(object sender, TextChangedEventArgs e)
{
if (DateTime.Now.Subtract(_last) > TimeSpan.FromSeconds(3))
{
TextBox tb = (TextBox)sender;
if (Keyboard.FocusedElement == tb)
tb.SelectAll();
}
_last = DateTime.Now;
}
I am having a master window in which there are plenty of user control. and using navigation i am able to access the user controls. But by question is how to set focus on the first text box when ever the user control is opened.
I tried with dependency property and boolean flags, i was able to succeeded a bit. When i first render the UserControl i was able to focus but when i open for the second time i was not able to set focus on the TextBox.
And one more thing, i have validation for TextBoxes, if the validation fails then the textbox should be emptied and the focus should be on the respective text box.
How can i achieve this using MVVM in WPF (CLR 3.5, VS2008)
thanks in advance.
If you have a UserControl then you also have CodeBehind.
Place this inside your codebehind and you will do fine.
this.Loaded += (o, e) => { Keyboard.Focus(textBox1) }
Place this inside your UserControl XAML if you wish to listen to validation errors.
<UserControl>
<Grid Validation.Error="OnValidationError">
<TextBox Text{Binding ..., NotifyOnValidationError=true } />
</Grid>
<UserControl>
Inside the CodeBehind of your UserControl you will have something like this:
public void OnValidationError(o , args)
{
if(o is TextBox)
{
(TextBox)o).Text = string.Empty;
}
}
You should use AttachedProperty to stick to MVVM pattern it'll keep your view model independent of UI code and fully unit testable. Following attached property binds a boolean property to focus and highlight the TextBox, if you do not want the highlighting then you can remove the highlighting code and just work with focus code.
public class TextBoxBehaviors
{
#region HighlightTextOnFocus Property
public static readonly DependencyProperty HighlightTextOnFocusProperty =
DependencyProperty.RegisterAttached("HighlightTextOnFocus", typeof (bool), typeof (TextBoxBehaviors),
new PropertyMetadata(false, HighlightTextOnFocusPropertyChanged));
public static bool GetHighlightTextOnFocus(DependencyObject obj)
{
return (bool) obj.GetValue(HighlightTextOnFocusProperty);
}
public static void SetHighlightTextOnFocus(DependencyObject obj, bool value)
{
obj.SetValue(HighlightTextOnFocusProperty, value);
}
private static void HighlightTextOnFocusPropertyChanged(DependencyObject sender,
DependencyPropertyChangedEventArgs e)
{
var uie = sender as UIElement;
if (uie == null) return;
if ((bool) e.NewValue)
{
uie.GotKeyboardFocus += OnKeyboardFocusSelectText;
uie.PreviewMouseLeftButtonDown += OnMouseLeftButtonDownSetFocus;
}
else
{
uie.GotKeyboardFocus -= OnKeyboardFocusSelectText;
uie.PreviewMouseLeftButtonDown -= OnMouseLeftButtonDownSetFocus;
}
}
private static void OnKeyboardFocusSelectText(object sender, KeyboardFocusChangedEventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null) return;
textBox.SelectAll();
}
private static void OnMouseLeftButtonDownSetFocus(object sender, MouseButtonEventArgs e)
{
var textBox = sender as TextBox;
if (textBox == null) return;
if (!textBox.IsKeyboardFocusWithin)
{
textBox.Focus();
e.Handled = true;
}
}
#endregion
}
You can use this attached property in on your TextBox which you want to focus/highlight...
<TextBox ... local:TextBoxBehaviors.HighlightTextOnFocus="{Binding IsScrolledToEnd}" ... />
You can also try using FocusManager
<UserControl>
<Grid FocusManager.FocusedElement="{Binding Path=FocusedTextBox, ElementName=UserControlName}">
<TextBox x:Name="FocusedTextBox" />
</Grid>
<UserControl>
I have a textbox on my wpfgrid that I need to perform some tekst inserting and such on. To do this, the textbox is referenced into the presentationmodel from the view, ReferenceToTextBox (we do MVP with Prism). Also, the textbox in the view TextDescription is bound to the Description-property on the model.
We also have a dropdown-list containing some predefined text-blobs (adresses, VAT-numbers and such). When you choose one of these, they should be inserted into the textbox at the carets current position. Since you can't bind on CaretIndex, the above mentioned workaround is made. The dropdown-list is bound on SelectedItem to a property on the model, so when the SelectedItem changes, the property changes, and in the setter on the property a method is called to insert the text of the selected-item into the ReferenceToTextBox "virtual" textbox in the model (which should be just a reference to the textbox in the view).
However, if I delete all the text from the textbox in the view and add a new predefined text-blob. The ReferenceToTextBox.Text property still contains all the text that I deleted. It seems like the ReferenceToTextBox is no longer just a reference, but a whole own textbox. Which makes it even weirder when updates to ReferenceToTextBox.Text actually updates the "visual" textbox on the view.
What is actually happening here?
Not a direct answer to your question, but what about a derived TextBox class that actually allows binding to its CaretIndex property:
public class TextBoxEx : TextBox
{
public static readonly DependencyProperty CaretIndexProperty = DependencyProperty.Register(
"CaretIndex", typeof(int), typeof(TextBoxEx),
new FrameworkPropertyMetadata(0, FrameworkPropertyMetadataOptions.BindsTwoWayByDefault, CaretIndexChanged));
public new int CaretIndex
{
get { return (int)GetValue(CaretIndexProperty); }
set { SetValue(CaretIndexProperty, value); }
}
protected override void OnTextChanged(TextChangedEventArgs e)
{
base.OnTextChanged(e);
CaretIndex = base.CaretIndex;
}
protected override void OnKeyDown(KeyEventArgs e)
{
base.OnKeyDown(e);
CaretIndex = base.CaretIndex;
}
protected override void OnKeyUp(KeyEventArgs e)
{
base.OnKeyUp(e);
CaretIndex = base.CaretIndex;
}
protected override void OnMouseDown(MouseButtonEventArgs e)
{
base.OnMouseDown(e);
CaretIndex = base.CaretIndex;
}
protected override void OnMouseUp(MouseButtonEventArgs e)
{
base.OnMouseUp(e);
CaretIndex = base.CaretIndex;
}
private static void CaretIndexChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e)
{
if (obj is TextBox)
{
((TextBox)obj).CaretIndex = (int)e.NewValue;
}
}
}
all. I have a usercontrol "NumericTextBox" that only allows numeric entries. I need to exhibit another specialized behaviour, that is, I need it to be able to bind it to a VM value OneWayToSource and only have the VM value update when I press enter while focusing the textbox. I already have the an EnterPressed event that fires when I press the key, I'm just having a hard time figuring out a way to cause that action to update the binding...
In your binding expression, set the UpdateSourceTrigger to Explicit.
Text="{Binding ..., UpdateSourceTrigger=Explicit}"
Then, when handling the EnterPressed event, call UpdateSource on the binding expression, this will push the value from the textbox to the actual bound property.
BindingExpression exp = textBox.GetBindingExpression(TextBox.TextProperty);
exp.UpdateSource();
Here is a complete version of the idea provided by Anderson Imes:
public static readonly DependencyProperty UpdateSourceOnKeyProperty =
DependencyProperty.RegisterAttached("UpdateSourceOnKey",
typeof(Key), typeof(TextBox), new FrameworkPropertyMetadata(Key.None));
public static void SetUpdateSourceOnKey(UIElement element, Key value) {
element.PreviewKeyUp += TextBoxKeyUp;
element.SetValue(UpdateSourceOnKeyProperty, value);
}
static void TextBoxKeyUp(object sender, KeyEventArgs e) {
var textBox = sender as TextBox;
if (textBox == null) return;
var propertyValue = (Key)textBox.GetValue(UpdateSourceOnKeyProperty);
if (e.Key != propertyValue) return;
var bindingExpression = textBox.GetBindingExpression(TextBox.TextProperty);
if (bindingExpression != null) bindingExpression.UpdateSource();
}
public static Key GetUpdateSourceOnKey(UIElement element) {
return (Key)element.GetValue(UpdateSourceOnKeyProperty);
}
If you are using MVVM you can use a combination of decastelijau's approach along with a custom attached property that calls UpdateSource on the textbox when PreviewKeyUp.
public static readonly DependencyProperty UpdateSourceOnKey = DependencyProperty.RegisterAttached(
"UpdateSourceOnKey",
typeof(Key),
typeof(TextBox),
new FrameworkPropertyMetadata(false)
);
public static void SetUpdateSourceOnKey(UIElement element, Key value)
{
//TODO: wire up specified key down event handler here
element.SetValue(UpdateSourceOnKey, value);
}
public static Boolean GetUpdateSourceOnKey(UIElement element)
{
return (Key)element.GetValue(UpdateSourceOnKey);
}
Then you can do:
<TextBox myprops:UpdaterProps.UpdateSourceOnKey="Enter" ... />
I'm new to WPF.
I have like 15 grids on my Window and I have a small menu on which I can click and choose which grid to show up or hide. One grid at a time only. I would like that grid to hode (fade out) when I hit Esc. I have all the animations already, I just need to know what grid is visible (active) at the moment.
I don't know how to get current topmost control of my Window.
My solution is when KeyDown event is triggered on my Window to:
private void Window_KeyDown(object sender, System.Windows.Input.KeyEventArgs e)
{
if (e.Key == System.Windows.Input.Key.Escape)
{
//check all grids for IsVisible and on the one that is true make
BeginStoryboard((Storyboard)this.FindResource("theVisibleOne_Hide"));
}
}
By active, I assume that means the one that has keyboard focus. If so, the following will return the control that currently has keyboard input focus:
System.Windows.Input.Keyboard.FocusedElement
You could use it like this:
if (e.Key == System.Windows.Input.Key.Escape)
{
//check all grids for IsVisible and on the one that is true make
var selected = Keyboard.FocusedElement as Grid;
if (selected == null) return;
selected.BeginStoryboard((Storyboard)this.FindResource("HideGrid"));
}
An approach that would be more decoupled would be to create a static attached dependency property. It could be used like this (untested):
<Grid local:Extensions.HideOnEscape="True" .... />
A very rough implementation would look like:
public class Extensions
{
public static readonly DependencyProperty HideOnEscapeProperty =
DependencyProperty.RegisterAttached(
"HideOnEscape",
typeof(bool),
typeof(Extensions),
new UIPropertyMetadata(false, HideOnExtensions_Set));
public static void SetHideOnEscape(DependencyObject obj, bool value)
{
obj.SetValue(HideOnEscapeProperty, value);
}
public static bool GetHideOnEscape(DependencyObject obj)
{
return (bool)obj.GetValue(HideOnEscapeProperty);
}
private static void HideOnExtensions_Set(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var grid = d as Grid;
if (grid != null)
{
grid.KeyUp += Grid_KeyUp;
}
}
private static void Grid_KeyUp(object sender, KeyEventArgs e)
{
// Check for escape key...
var grid = sender as Grid;
// Build animation in code, or assume a resource exists (grid.FindResource())
// Apply animation to grid
}
}
This would remove the need to have code in codebehind.