StringFormat, TextBox Validation and Caret Position - silverlight

I'm having an issue I need to work around. The caret position of my TextBox is reset to the first character when certain events happen. Here's the XAML for my TextBox, my application is using MVVM:
<TextBox x:Name="txtAmount" Text="{Binding CurrentClientObject.Amount, Mode=TwoWay, StringFormat='###,###,##0.00'}"></TextBox>
Is bound to model property:
private System.Nullable<decimal> _Amount;
[Display(ResourceType = typeof(MatchModelResx), Name = "LabelAmount", Description = "ToolTipAmount")]
public System.Nullable<decimal> Amount
{
get
{
return _Amount;
}
set
{
_Amount = value;
NotifyChanged("Amount");
}
}
The StringFormat is causing the issue here, since whenever the string I input in the TextBox triggers a refresh of the property in the model, the StringFormat is applied and the caret inside the TextBox moves
Now this wouldn't be a problem, since the model is refreshed on the lostfocus (UpdateSourceTrigger = Default), but there is a case when the TextBox is in Error mode. If the user either enters a value that breaks a custom validation rule, or is in the invalid format (i.e: enters 64.5x5 in a field bound to a decimal), every single keychange triggers a refresh of the property in the model. And since a model refresh may cause the StringFormat to be applied, this means the caret will move to the left while a user is trying to correct an invalid value in the TextBox. This is an irritating behaviour for our customers, and we really need to find a way around it.
I've tried using a custom converter on the binding instead of a stringformat, but that's not fixing the issue. And since the exception is sometimes raised before the code reaches the property Set, I have no way to handle this before the caret moves.
Does Silverlight 5 offer a way to work around this?
Thanks!

Not sure if you have set the UpdateSourceTrigger=PropertyChanged, for textbox this causes the refresh everytime you press the Key and StringFormat is applied, to fix this it should be set to "Default" which is LostFocus
http://msdn.microsoft.com/en-us/library/system.windows.data.binding.updatesourcetrigger(v=vs.110).aspx

Related

WPF DataGrid - how to stop user proceeding if there are invalid cells? (MVVM)

I'm implementing edit functionality in my DataGrid. The CellEditingTemplate of a typical cell looks something like this:-
<DataGridTemplateColumn.CellEditingTemplate>
<DataTemplate>
<TextBox Grid.Column="0"
Text="{Binding Concentration, ValidatesOnDataErrors=True, NotifyOnValidationError=True, UpdateSourceTrigger=LostFocus}"
Validation.ErrorTemplate="{StaticResource errorTemplate}" />
</DataTemplate>
</DataGridTemplateColumn.CellEditingTemplate>
In this example there is a TextBox bound to a property called "Concentration", which is a property of type double, with validation to ensure that it falls within a given range. The models that are being bound to the grid implement IDataErrorInfo by the way.
The problem is, although the error template highlights the cell when an invalid value is entered, there is nothing to stop me from moving focus away from that cell (or row). What's the preferred approach for handling validation with data grid editing? I wondered if I could prevent the user from leaving the cell until a valid value was entered, but that could be dangerous - they wouldn't be able to do anything (even close the app) until a valid value had been entered.
Another option might be to prevent the user from proceeding if the datagrid contains any invalid rows. But what's the best way to test this? IDataErrorInfo doesn't provide an "IsValid" property that I can examine on each of the rows' models.
Lastly I could just revert the model's property back to its previous "good" value (or a default value) if the user enters something invalid.
Any suggestions? I'm using MVVM by the way.
I use this to see if IDataerrorInfo has any errors for the object, a small snippet of the implementation:
protected Dictionary<string, string> _propertyErrors = new Dictionary<string, string>();
public bool HasErrors {
get { return (_propertyErrors.Count) > 0; }
}
Then I can handle the logic for what to do after evaluating this property. Do you want to prevent navigation, closing app, etc. Then you need to evaluate for errors from that code and then cancel that action.
I've used this method in the past to determine if a datagrid has errors:
private bool HasError(DataGrid dg,)
{
bool errors = (from c in
(from object i in dg.ItemsSource
select dg.ItemContainerGenerator.ContainerFromItem(i))
where c != null
select Validation.GetHasError(c)
).FirstOrDefault(x => x);
return errors;
}
Then it's only a matter of preventing the next action if the method returns true.

Updating WPF TextBox properties after setting Text

When setting the Text property of a WPF TextBox control, other properties that should also change (as a side effect) do not change. In particular, I would like to check the value of the ExtentWidth property after setting Text, but it does not change. I've tried calling UpdateLayout() to no avail. In Windows.Forms, I would call DoEvents().
OK, here's some code. I put this in the Window_Loaded() event handler. The problem is that textBox.ExtentWidth doesn't change when textBox.Text changes. That doesn't really surprise me. I figure I need to call something like textBox.UpdateLayout() to make it recalculate ExtentWidth, but that didn't help. ExtentWidth does vary depending on what I initialize textBox.Text to in the Window's constructor, but that doesn't help me. I need to set several different Text values and get the corresponding ExtentWidth for each.
string initText = textBox.Text; // "textBox"
double extentWidth = textBox.ExtentWidth; // 39.3
textBox.Text = "short text";
extentWidth = textBox.ExtentWidth; // 39.3
textBox.Text = "Long enough to make a difference, eh?";
extentWidth = textBox.ExtentWidth; // 39.3
I found a solution to the specific problem of getting TextBox.ExtentWidth to change after setting Text. Setting Text will raise the LayoutUpdated event, and you can get the new value of ExtentWidth in a handler for LayoutUpdated.
I used this fact to create a subclass of WPF TextBox that displays an ellipsis when the text is too long for the visible area. I wrote a CodeProject article about it here.

Ignoring text/value changes due to databinding

How does one ignore changes to a control when databinding occurs? I tried hooking various events like gotfocus,textchanged,and leavefocus, but if the control already has focus and the user "cancels" their changes, when I reload the record and data binding takes over, textchanged thinks the user still made the change since the focus is on that control. The call stack is empty. Are there any global data binding events like databinding starting and databinding ending? I see where I fire my OnProperyChanged but within that call, databinding does not occur. Looks like it's getting "queued" up and runs at some other point.
At one point, I was going to hook the property change events in our view model , but this means I won't detect and can't VISUALLY display the form is modified till the user leaves the control. I know, I know, I can change all my bindings so that binding occurs immediately on every character change but then this messes with some validation cases as the user hasn't finished typing in their value.
I'd really love some kind of event like TextChangedByUser that would fire whether the user used a key, clipboard, mouse clipboard, anything triggered by the user.
I just can't figure out how to distinguish between user changes and databinding changes.
I'd really love some kind of event like TextChangedByUser that would
fire whether the user used a key, clipboard, mouse clipboard, anything
triggered by the user.
I just can't figure out how to distinguish between user changes and
databinding changes.
Don't use the Text.TextChanged event to detect user input,
use the Binding.SourceUpdated event instead.
Or more general:
Don't use the DPs of your visual elements to detect user updates, use the Binding.SourceUpdated event instead.
This is a RoutedEvent.
At your binding, you have to set NotifyOnSourceUpdated = true. With help of UpdateSourceTrigger you are even able to finetune when you want to be informed.
Your xaml could be sth like this:
<Grid x:Name="LayoutRoot" Binding.SourceUpdated="LayoutRoot_SourceUpdated">
...
<TextBox>
<TextBox.Text>
<Binding NotifyOnSourceUpdated="True" Path="path" UpdateSourceTrigger="PropertyChanged" >
</Binding>
</TextBox.Text>
</Grid>
Your event could be like this:
private void LayoutRoot_SourceUpdated(object sender, DataTransferEventArgs e)
{
// called every time s.th. changed by user
}
(edited due to comment)
Why is this a valid way to detect if an input is triggered in any way by the user?
In the given example, the TextBox's DataContext 'path' property is the source, while the 'TextBox.Text' property is the target.
[Data Binding Overview]http://msdn.microsoft.com/en-us/library/ms752347.aspx
The TextBox.Text property is changed for the first time when the binding initializes and the source-value is written to the 'TextBox.Text' property. Because you do not know when the binding takes place exactly you cannot use the TextBox.Text property or any of its events (e.g. TextChanged) to detect a user input. Hence:
Don't use the Text.TextChanged event to detect user input!!! more general: Don't use the DPs of your visual elements to detect user updates!!!
If the user changes the content of the visual text field by which means whatsoever, the 'TextBox.Text' property changes (your target).After that, the binding updates the source at a time defined by UpdateSourceTrigger.That's when the SourceUpdated event is fired.
I admit not to know the effect of changes to the binding source from outside the binding.
But I have a complete Editor-like Desktop-Application detecting changes by the user that way and it is working very nicely.
You should update your Binding Code to set the following
{Binding Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}
EDIT: Sorry, I have overseen the fact that you already know this...In that case, I can't help :(
You can use the UIElement.TextInput event to detect user input.
Note that the event is probably already handled by the input control itself so you might have to use the UIElement.PreviewTextInput event.

WPF CheckBox's IsChecked property doesn't match binding source's value

In my WPF application I have a CheckBox whose IsChecked value is bound to a property in my viewmodel. Notice that I have commented out the actual line which sets the value in my viewmodel. It's the standard pattern:
View.xaml
<CheckBox IsChecked="{Binding Path=SomeProperty}" />
ViewModel.cs
public bool SomeProperty
{
get { return this.mSomeProperty; }
set
{
if (value != this.mSomeProperty)
{
//this.mSomeProperty = value;
NotifyPropertyChanged(new PropertyChangedEventArgs("SomeProperty"));
}
}
}
When I click the CheckBox I expect nothing to happen, since the value of this.mSomeProperty does not get set. However the observed behavior is that the CheckBox is being checked and unchecked regardless of the value of this.mSomeProperty.
What is going on? Why isn't my binding forcing the CheckBox to show what the underlying data model is set to?
Because WPF does not automatically reload from the binding source after updating the source. This is probably partly for performance reasons, but mostly to handle binding failures. For example, consider a TextBox bound to an integer property. Suppose the user types 123A. WPF wants to continue showing what the user typed so that they can correct it, rather than suddenly resetting the TextBox contents to the old value of the property.
So when you click the CheckBox, WPF assumes that it should continue to display the control state, not to re-check the bound property.
The only way I've found around this, which is not very elegant, is to raise PropertyChanged after WPF has returned from calling the property setter. This can be done using Dispatcher.BeginInvoke:
set
{
// ...actual real setter logic...
Action notify = () => NotifyPropertyChanged(...);
Dispatcher.CurrentDispatcher.BeginInvoke(DispatcherPriority.ApplicationIdle, notify);
}
This could be made a bit less horrible by consolidating it into the NotifyPropertyChanged implementation so that you wouldn't have to pollute individual properties with this implementation concern. You might also be able to use NotifyOnSourceUpdated and the SourceUpdated attached event, but I haven't explored this possibility.

How to stop a WPF binding from ignoring the PropertyChanged event that it caused?

I have a TextBox bound to a ViewModel's Text property with the following setup:
Xaml
<TextBox Text="{Binding Text}"/>
C#
public class ViewModel : INotifyPropertyChanged
{
public string Text
{
get
{
return m_Text;
}
set
{
if (String.Equals(m_Text, value))
{
return;
}
m_Text = value.ToLower();
RaisePropertyChanged("Text");
}
}
// Snip
}
When I type some stuff in to the TextBox it successfully sets the Text property on the ViewModel. The problem is that WPF ignores the property changed event that is raised by it's own update. This results in the user not seeing the text they typed converted to lowercase.
How can I change this behaviour so that the TextBox updates with lowercase text?
Note: this is just an example I have used to illustrate the problem of WPF ignoring events. I'm not really interested in converting strings to lowercase or any issues with String.Equals(string, string).
You can achieve this by raising the event in a seperate dispatcher call using Dispatcher.BeginInvoke
Define a delegate:
private delegate void RaisePropertyChangedDelegate(string property);
Then use the following to raise the event
Dispatcher.CurrentDispatcher.BeginInvoke(
DispatcherPriority.Normal,
new RaisePropertyChangedDelegate(RaisePropertyChanged),
"Text");
Apparently this is fixed in WPF 4.0 in .NET Framework 4.0 released in 2010: Karl Shifflett's blog
I remember Rocky Lhotka complaining about this very behaviour on an old episode of .NET Rocks. (Searches for half an hour ...) ah, here we are. Episode 169 from March, 2006:
So if you have got a detailed Form in
Windows Forms then you bind all of
your properties to those different
text boxes and the user is typing
things into a text box and tabs off,
of course that value gets put into
your object but your object then might
change the value, may be there is some
sort of a manipulation that says all
letters must be upper case. It’s a
business rule so it goes in your
object. If you do that it won’t show
up in the UI. In other words the user
can type a, b, c, type in lower case,
tab off. The lower case a, b, c will
stay in the text box. Then later when
they change some other field, then
they will keep in mind the object to
upper case the value, right? So the
object has an uppercase a, b, c the UI
is incorrectly showing a lowercase,
the user then changes some other
control and tabs off that control, all
of a sudden the a, b, c in uppercase
shows up in the text box that they
weren’t on.
Rocky doesn't actually suggest a solution to the problem, and I'd hazard that if he hasn't worked it out maybe there's no good answer. Perhaps you need to subscribe to the PropertyChanged event on your object from the code-behind and manually refresh the binding when the property in question has changed.
ps. This isn't directly answering your question, but in the example you've given you could set the CharacterCasing on the TextBox so that it only accepts lower-case characters.
You need to force an update of the binding target (see this post on MSDN). However, this was changed/broken in WPF4, so you have to force an update on the source instead (see this post).

Resources