I've messed around with PreviewLostKeyboardFocus
which almost gets you there. I've seen a couple of implementations using LostFocus
, but that just forces focus back on the TextBox
after it's lost focus and you can easily see this shifting on the screen. Basically, I'm just looking for the same type of behavior you could get with using OnValidating
in WinForms.
In my opinion, the best way is generally not to do it. It is almost always better to just disable the other controls or prevent saving until the value is valid.
But if your design really needs this ability, here is what you should do:
Intercept the Preview
version of keyboard and mouse events at your window level, or whatever scope you want to prevent focus changes within (eg maybe not your menu bar).
When the Tab KeyDown or Return KeyDown is detected in the text box, or when a MouseDown is detected outside the text box while it has the focus, call UpdateSource() on the binding expression, then if the validation has failed set Handled=true to prevent the KeyDown or MouseDown event from being processed further.
Also continue handling PreviewLostKeyboardFocus
to catch any causes of focus change that aren't from the keyboard or mouse, or that your other code didn't recognize.
To add onto Ray's answer:
UpdateSource is called like so:
BindingExpression be = userTextbox.GetBindingExpression(TextBox.TextProperty);
be.UpdateSource();
Also, as an alternative you can set the text box binding to:
UpdateSourceTrigger = "PropertyChanged";
The latter will cause a continuous check, whereas the former will check when needed (performant).
If you attempt to focus an element inside its own LostFocus handler you will face a StackOverflowException, I'm not sure about the root cause (I suspect the focus kind of bounces around) but there is an easy workaround: dispatch it.
private void TextBox_LostFocus(object sender, RoutedEventArgs e)
{
var element = (sender as TextBox);
if (!theTextBoxWasValidated())
{
// doing this would cause a StackOverflowException
// element.Focus();
var restoreFocus = (System.Threading.ThreadStart)delegate { element.Focus(); };
Dispatcher.BeginInvoke(restoreFocus);
}
}
Through Dispatcher.BeginInvoke you make sure that restoring the focus doesn't get in the way of the in-progress loss of focus (and avoid the nasty exception you'd face otherwise)
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With