WPF: combining TextBox input and validation rules - wpf

I am learning about properly implementing input validation. I have reviewed dozens of similar questions about different aspects of it, but for my case, combining all these different solutions is confusing. In the essence, I have a bunch of textboxes in my application that need to be validated by these requirements:
All textboxes must allow numeric values. Some textboxes must allow positive/negative Doubles, some only positive/negative Integers, some only positive variants of each. Meaning that user can enter 0-9, . and -, but only once. Regex filter ("[^0-9.-]+") can filter such inputs, but it doesn't prevent the user from entering something like 9.1.2 or -5-5, so it doesn't work.
Some textboxes must allow entering multiple valid numbers separated by a space, like 1 5.61 -7.2. So the user can enter a space symbol now as well, but only after a valid number, and only once (so that they don't place five spaces between numbers). Meaning that if the user types one space, he should not be allowed to type another in sequence.
If the user enters an invalid value (or multiple values) that cannot be parsed into integer/double, the textbox should fix it automatically. For example, if user enters 123., it should automatically be corrected into 123. If the user enters nothing, or just one symbol, like -, textbox should automatically restore previous valid value.
So right now, the textbox (for single values, I haven't solved multiple values yet) is defined as:
<TextBox x:Name="numericValuesTextBox" PreviewTextInput="PreviewTextBoxNumeric">
<TextBox.Text>
<Binding Path="UserValues" Converter="{StaticResource StringToDoublesArrayConverter}">
<Binding.ValidationRules>
<local:DoublePositiveOrNegative/>
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
PreviewTextBoxNumeric - this bit handles that only valid characters can be entered:
Private Sub PreviewTextBoxNumeric(ByVal sender As Object, ByVal e As TextCompositionEventArgs)
Dim regex As Regex = New Regex("[^0-9.-]+")
e.Handled = regex.IsMatch(e.Text)
End Sub
However, like I said, it still doesn't prevent the user from entering values that later cannot be converted to an integer or double, causing an exception. PreviewTextInput event only checks a single character only, so it is not very useful.
And the DoublePositiveOrNegative rule looks like this:
Public Class DoublePositiveOrNegative
Inherits ValidationRule
Public Overrides Function Validate(value As Object, cultureInfo As Globalization.CultureInfo) As ValidationResult
Dim result As Double
If Double.TryParse(value, result) Then
Return ValidationResult.ValidResult
Else
Return New ValidationResult(False, $"Invalid number")
End If
End Function
End Class
However, all this does right now is set the border of the textbox in red - it doesn't restore the original value. And I can't see how to store that value here, since this class is instantized only during the binding attempt.
I am unsure on how to combine all these requirements into neat, compact and reusable solution that I could use anywhere on my application. I do not know how to make input check rule (currently that Regex bit) that checks not only if valid character is being entered, but also if it can be logically entered to make a valid number (not allowing several commas, etc.). And also, how to restore the original value if the input is still invalid (ideally, this shouldn't be needed if the first check works right).
Any suggestions?

Related

DataGridView navigate cell on KeyUp with wrapped text

I would expect to navigate to the other cell only when reaching the very top.
What actually happens is that it navigates on hitting the 'up' key at the fourth line (which would the first line, if not for the text wrap).
Do you know if it is 'by design' or can be avoided?
Handling of the keyboard input is a responsability shared by the editing controls (classes implementing IDataGridViewEditingControl) inside the DataGridView and the DataGridView itself.
If you look at the source code inside DataGridView, you will see that the decision of where to handle a key press is demanded to the editing controls. Basically, the DataGridView asks to the IDataGridViewEditingControl (in your case, a DataGridViewTextBoxEditingControl) "Do you want to handle it?" by calling the
EditingControlWantsInputKey method.
This method contains a bug, or better an over-semplifications. The intent of the code seems to be "if I am already on the first line, a KeyUp means that the user wants to move to the cell above me". Unfortunately, "first line" is, in the code, anything before the first \r\n, disregarding word wrap. Which is what you are observing.
Fortunately EditingControlWantsInputKey is public virtual, so you can derive from DataGridViewTextBoxEditingControl and adjust the behaviour corresponding to your needs.
For example:
public class MyDataGridViewTextBoxEditingControl : DataGridViewTextBoxEditingControl {
public override bool EditingControlWantsInputKey(Keys keyData, bool dataGridViewWantsInputKey) {
switch (keyData & Keys.KeyCode)
{
case Keys.Up:
return false; // never pass KeyUp to the DataGrid
...
// call base.EditingControlWantsInputKey when
// you want to retain the standard behaviour
This is oversimplified (it just disables the "move to the cell above" behaviour; you might want to act differently, maybe measuring the text lenght fitting in the "real" first line inside the cell by using Graphics.MeasureString. This is up to you and what you want to obtain.
Then, it is relatively easy to replace the standard controls for DataGridView cells with your own.

How to implement TextBox.InputScope in WPF

I'm searching for a way to implement the InputScope of a Textbox like it is done in Wp7, but using classic WPF.
What I want to achieve is, that the input is restricted to only use decimal numbers.
How can I achieve that?
<TextBox Text="{Binding Amount, Mode=TwoWay}" InputScope="Number"/>
InputScope is a valid attribute for classic WPF, but sadly it does not seem to work.
InputScope doesn't force any kind of validation or restriction on the input. It is a hint to input processors (eg. on-screen keyboards, speech recognition) of the kind of data that can be entered in a control.
This value is used only if an IME (like an on-screen keyboard) is activated.
Even in WP7, InputScope doesn't restrict the values that can be entered in a text box. You could still enter unwanted characters if you could install an input processor that ignored InputScope.
If you want to restrict text input to specific characters you will have to use a MaskedTextBox or intercept keystroke events. The code will also be easier to understand.
It may be possible to use InputManager's PreProcessInput event to filter input events using the InputScope but it probably isn't worth the effort.
There is no built in way to do it.
You'll need to write a bit of code to achieve what you're looking for.
Here is an example of what you need to do: WPF Maskable TextBox for Numeric Values
I haven't used InputScope, however, from the MSDN documentation (here, here, here and here) it appears that WPF's input scope requires a more complicated input e.g.
xmlns:swi should be mapped to System.Windows.Input.
<TextBox>
<TextBox.InputScope>
<swi:InputScope RegularExpression="^(0|(-(((0|[1-9]\d*)\.\d+)|([1-9]\d*))))$" />
</TextBox.InputScope>
</TextBox>
(Regex string found on Google, I haven't checked it.)
Or perhaps:
<TextBox>
<TextBox.InputScope>
<swi:InputScopePhrase>
<swi:InputScopePhrase.Names>Number</swi:InputScopePhrase>
</swi:InputScopePhrase>
</TextBox.InputScope>
</TextBox>
string txt = MessageWrite.Text;
if (txt != "")
{
MessageWrite.Text = Regex.Replace(MessageWrite.Text, "[^a-f ^0-9]", "");
if (txt != MessageWrite.Text)
{
MessageWrite.Select(MessageWrite.Text.Length, 0);
}
}
messageWrite is a textbox name. It restricts from A-F and 0-9. User can modify it :)

TextBox - Issue with validation using IDataErrorInfo with integer Value more than int.MaxValue (2147483647)

In my WPF text box i have bound the integer entity variable and set the IntegerValidationAttribute.
when i input text IDataErrorInfo is triggered with proper error info.
The issue is when i input the integer value more then int.MaxValue(2147483647) say i input
2147483649 the IDataErrorInfo error is not triggered.
I need to disable/Enable button depends on proper integer value validation.
Please suggest for the same.
Regards,
Vivek
when you put somthing like "cclks" in your textbox, your validation will also not happen or?
if not then you have some sort of "numeric only textbox" and if you have such a textbox you can go further and create a "integer numeric only textbox"
i always use string properties in my viewmodels so i can easily validate all input, but of course i have to cast to the real type for my models

TextBox max/min numeric value

I have a little problem - in my Windows Forms program I have a lot of text boxes. They only can get numeric values between 1 - 1024. "Protecting" the text box form non numeric inputs is no problem. But how can I assure that the value doesn't get higher than 1024? Is there any function or any event I could try to catch and then Handle it on my own? I thought about catching the "TextChanged" Event and then check for the value. But how can I know then which Button was the last one pressed?
Besides I wouldn't like to exchange my Textboxes with any other Controls since they're all implemented right now and it would be a lot of work to exchange them all.
Best Regards
Quendras
You should use the NumericUpDown control and set the Maximum property.
You could try using OnLostFocus on each text box. Then verify the input was numeric, and it's value is greater/equal to 0, and less/equal to 1024.
You could check when that textbox loses focus, and then check its value:
public sub Textbox1_lostFocus() handles textbox1.onLostFocus
If cint(textbox1.text) > 1024 then
'whatever you need to do here
End if
end sub

How to use the previous value when validating a WPF DataGrid cell entry?

I have a WPF DataGrid. I would like to restrict the values a user may enter in a particular column, such as the following:
Column A values may only increase (new value > old value).
Column B values may only decrease (new value < old value).
If the user-entered value is invalid, I want to revert to the previous valid value.
I thought that I might be able to do this in a custom ValidationRule, but I don't see a way to access the previous value of the cell from the Validate() function.
I would appreciate any help!
You need to do this logic in the business layer. Make sure you dont have UpdateSourceTrigger set to be PropertyChanged as this will update your values on every keystroke. if the value was 9 and the user types 10 the update will happen when the user types 1 which will be less than 9 which will reset the value to 9.
A better solution is to flag the field as invalid (easy if you are binding to an object) and show the user the entry is invalid, rather than just changing what they have just entered. Sometimes its only out by one letter and they might just want to change that to make the input valid.

Resources