How to validate a UI in Silverlight? - c#

My UI is simple. In a Silverlight 5.0 application, I'm using MVVM and I let the user adds many textboxes as he wants to add in a ObservableCollection<Model> and a Button.
Model just have one property and its datatype is an integer.
The data template for this model is just a simply textbox.
<TextBox Text="{Binding Number}" />
So the idea is, when all the textboxes does not have any error, the command is enabled, but if any model has an error, the command should be disabled.
How can I implement this validation?
Thanks in advance.

You can simply throw an exception in appropriate property`s setter:
public int Number
{
get {//...}
set {
if(value >= 10)
throw new Exception("Number should be less than 10");
_number = number;
}
}
And your binding should be:
<TextBox Text="{Binding Number, Mode="TwoWay" ValidateOnExceptions="True"}" />
FrameworkElement has BindingValidationErrorEvent, which can be used for implement enable/disable command logic. Remember to set NotifyOnValidationError to True for your binding.
p.s.Also, i suggest you read about INotifyDataErrorInfo

Related

C# - RaisePropertyChanged called multiple times and throwing stackoverflow exception

I'm fairly new to WPF. I have the following radio button in my application
<Viewbox Height="30">
<RadioButton Content="B1" GroupName="InputBelt" IsChecked="{Binding RBChecked, Mode=TwoWay, FallbackValue=True}" VerticalAlignment="Center"/>
</Viewbox>
<Viewbox Height="30">
<RadioButton Content="B2" GroupName="InputBelt" IsChecked="{Binding RBChecked, Converter={StaticResource boolconverter}, Mode=TwoWay}" VerticalAlignment="Center"/>
</Viewbox>
I have defined datacontext in xaml file
<Window.DataContext>
<vm:TestViewModel />
</Window.DataContext>
The issue is when the page is loaded for the 1st time, everything is fine. But when I go to some other page in the application and comes back to this page, the application crashes due to stackoverflow exception.
I even tried adding datacontext locally in radiobutton tag but it isn't working either.
Property structure given below.
private bool _bRBChecked;
public bool RBChecked
{
get { return _bRBChecked; }
set
{
_bRBChecked = value;
RaisePropertyChanged("RBChecked");
}
}
Upon investigating further, I found out that the RaisePropertyChanged of the binded property is being called too many times. This issue occurs only with the property binded to radio button. I have other controls which has two-way bind with other properties in the same page and it seems to work fine.
Now I have tried the below fix suggested in another stackoverflow question and it seems to be working.
set
{
if (_bRBChecked != value)
{
_bRBChecked = value;
RaisePropertyChanged("RBChecked");
}
}
But I would like to know the root cause of this issue and why the property is being set so many times and find any alternate fix if possible. Please let me know if I am missing anything.
Any insight is highly appreciable.
Your change notification is not protected from recursion. Property A changing Property B, whose change changes Property A...
A simple solution is this:
set
{
if(value != _bRBChecked){
_bRBChecked = value;
RaisePropertyChanged("RBChecked");
}
}
Simply check if the value is actually a change, before you go tell everyone about it. This pattern is explicitly used in the Examples. I am 90% sure the Depdency Properties have a similar recursion protection, but it would not be the first time I was wrong.
I think it is fairly easy to figure this out, based on the fix you shared.
What happens in steps:
You set the new value in one of the radio buttons
The event is raised
Since it's two way binding, the binding of the second radio button sets the value again to the other radio button
The event is raised again due to 3
Go back to 1 as now the value is set again for the first radio button.
With your fix the value is not set (the setter it's not called) so the event is not triggered again.

Raise Validation.Error without NotifyOnValidationError. Is posible?

Is posible to force a control to raise the Validation.Error event, even when the binding doesn't specify the NotifyOnValidationError?
Or
Is there another event that raises always when the red validation border is shown or hidden?
Explanation:
I have an attached behaviour that makes a binding between the Validation.GetHasError and a property of my ViewModel. This with the purpose of knowing on the view model side, when a View side validation has happened.
Example: A view model with an int property
class ViewModel
{
int Value { get; set; }
}
A text box:
<TextBox Text="{Binding Value}" Validation.Error="Validation_Handler" />
Example I want to know when the user enter a value that it is not an integer.
Modifying all view model properties to have string type and have all validation done on the ViewModel is not feasible because some ViewModel properties are generated dynamically on run time, and I want to preserve the default converter behavior.
As seen on this question
The easiest solution was to inherit the Binding class and set the NotifyOnValidationError to true on the constructor. This way the Validation.Error event is always triggered.
public class Binding : System.Data.Binding
{
public Binding(string path) : base(path)
{
NotifyOnValidationError = true;
}
}
And the XAML:
<TextBox Text="{ui:Binding Value}" Validation.Error="Validation_Handler" />
Instead of:
<TextBox Text="{Binding Value, NotifyOnValidationError='True'}" Validation.Error="Validation_Handler" />

MVVM Distribute Objects

Maybe it is just a small step to the solution but I can't get it so far.
I did some WPF tutorials for DataContext and Binding, but I can't get how I could share the context and/or binding between (e.g.) two pages.
For example when you look at this one: https://msdn.microsoft.com/en-us/library/ms754356%28v=vs.110%29.aspx
<Label>Enter a Name:</Label>
<TextBox>
<TextBox.Text>
<Binding Source="{StaticResource myDataSource}" Path="Name" UpdateSourceTrigger="PropertyChanged"/>
</TextBox.Text>
</TextBox>
<Label>The name you entered:</Label>
<TextBlock Text="{Binding Source={StaticResource myDataSource}, Path=Name}"/>
This will be the result:
It's an easy example and there is no problem running and understanding this, but what I want is:
Fill the TextBox and the Label from code behind. I tried to name the TextBox tb and then just call tb.Text = "some text" - it works. I also tried to assign a DataContext for both the TextBox and the Label, then create a object and fill the DataContext with the object - this also worked.
Placing the Label on another page.
Problem 2 is the one that is really hard for me, especially in combination with problem 1.
For example: When I create the object in page 1 constructor and assign it the DataContext (ofcourse) only the TextBox on page 1 will contain the value.
I simply don't know how to share this one object I declared in page 1 with page 2 to set it also to the DataContext.
Maybe I just didn't find the perfect tutorial or explanation for me to understand how DataContext and Binding really works and how I can share objects between pages and windows.
Can you guys help me out?
If you need more informations, feel free to ask ;)
public class MySharedDataContext : INotifyPropertyChanged
{
private string _name;
public string Name
{
get { return _name; }
set
{
if (value == _name) return;
_name = value;
OnPropertyChanged();
}
}
}
In app.xaml create shared resource:
<Application x:Class="WpfApplication1.App"
xmlns:my="clr-namespace:WpfApplication1">
<Application.Resources>
<my:MySharedDataContext x:Key="MySharedDataContext" />
now you can use shared resource in both pages:
<TextBox Text="{Binding Name, Source={StaticResource MySharedDataContext}" />
if you want to sent Name value, better don't access TextBox directly, but set it in you MySharedDataContext class:
var dataContext = (MySharedDataContext)FindResource("MySharedDataContext");
dataContext.Name = "John Smith";

Binding multiple textbox values to single property

I was wondering if there is way to bind multiple textboxes to a single property in VM and a single validation logic. Perhaps a good example of that would be a typical Serial Number in any product activation process. Usually, when asked to enter serial number, the end user has, say, 5 text boxes with 5 max symbols.
Now lets imagine that in my VM I have only one property called SerialNumber. So my XAML would look like this:
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding SerialNumber}"/>
<TextBox Text="{Binding SerialNumber}"/>
<TextBox Text="{Binding SerialNumber}"/>
<TextBox Text="{Binding SerialNumber}"/>
<TextBox Text="{Binding SerialNumber}"/>
<StackPanel>
And my code will be like this:
class ViewModel
{
public string SerialNumber{get;set;}
}
Is there a way to bind these textboxes so that they each point ot the same property on VM and that property is updated only when the validation on all 5 textboxes passes?
EDIT: As some posters pointed out, yes, I could go with 5 separate properties for each textbox, the problem with that is that the actual situation is much more complex than in the example that I've provided. One of the reasons that the suggested approach is unfavorable is because this view will be reused in multiple places with different VM classes and going with the 5 properties approach I would have to copy them in each and every VM class that will use this View. If it were only as simple as taking five string properties and concatenating them, it would be tolerable. But in the real world scenario there is a very complicated verification, validation and combination logic behind these properties, that it makes it impractical to rewrite the same logic in each and every VM, which is why I'm looking for something reusable, something that could be done in XAML as much as possible. I was wondering if BindingGroup with some sort of ValidationRule and ValueConverter could be used in this case.
You should try to bind it into separate properties and then use the + operator to link them togeather, in a separate property and use that after in the model.
You cannot data bind different UI controls to one property and have them reflect different values. Instead, you will need to define additional properties to data bind to the other TextBoxes. It's stil a bit unclear as to exactly what you want, but if you want several TextBoxes that each show a few characters of a pass code, or something similar, then you'll need to do it more like this:
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding SerialNumber1}" />
<TextBox Text="{Binding SerialNumber2}" />
<TextBox Text="{Binding SerialNumber3}" />
<TextBox Text="{Binding SerialNumber4}" />
<TextBox Text="{Binding SerialNumber5}" />
<StackPanel>
...
string serialNumber = string.Concat(SerialNumber1, SerialNumber2, SerialNumber3,
SerialNumber4, SerialNumber5);
Alternatively, if you want to compare the supposedly identical values of two TextBoxes, as in a typical password entry field, you could do something like this:
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding SerialNumber1}" />
<TextBox Text="{Binding SerialNumber2}" />
<StackPanel>
...
bool isValid = SerialNumber1 == SerialNumber2;
In all cases, you will need to add further properties.
Based on the edit of the question, it is impractical for the O/P to change the viewmodel(s) to add additional string properties for the serial number parts.
A custom IValueConverter
In this situation, a custom IValueConverter can provide the required functionality. Let's call this custom converter SerialNumberConverter.
As already hinted at by IL_Agent's very brief answer, you would use the converter in XAML simliar to the following:
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<My:SerialNumberConverter x:Key="SerialNumberConverter" />
</StackPanel.Resources>
<TextBox Text="{Binding SerialNumber, ConverterParameter=0, Converter={StaticResource SerialNumberConverter}}"/>
<TextBox Text="{Binding SerialNumber, ConverterParameter=1, Converter={StaticResource SerialNumberConverter}}"/>
<TextBox Text="{Binding SerialNumber, ConverterParameter=2, Converter={StaticResource SerialNumberConverter}}"/>
<TextBox Text="{Binding SerialNumber, ConverterParameter=3, Converter={StaticResource SerialNumberConverter}}"/>
<TextBox Text="{Binding SerialNumber, ConverterParameter=4, Converter={StaticResource SerialNumberConverter}}"/>
</StackPanel>
The implementation of the SerialNumberConverter looks somewhat unconventional:
public class SerialNumberConverter : IValueConverter
{
private readonly string[] _serialNumberParts = new string[5];
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
{
int serialPartIndex;
if (!int.TryParse(parameter.ToString(), out serialPartIndex)
|| serialPartIndex < 0
|| serialPartIndex >= _serialNumberParts.Length
)
return Binding.DoNothing;
string completeSerialNumber = (string) value;
if (string.IsNullOrEmpty(completeSerialNumber))
{
for (int i = 0; i < _serialNumberParts.Length; ++i)
_serialNumberParts[i] = null;
return "";
}
_serialNumberParts[serialPartIndex] = completeSerialNumber.Substring(serialPartIndex * 6, 5);
return _serialNumberParts[serialPartIndex];
}
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
{
int serialPartIndex;
if (!int.TryParse(parameter.ToString(), out serialPartIndex)
|| serialPartIndex < 0
|| serialPartIndex >= _serialNumberParts.Length
)
return Binding.DoNothing;
_serialNumberParts[serialPartIndex] = (string)value;
return (_serialNumberParts.Any(string.IsNullOrEmpty)) ?
Binding.DoNothing :
string.Join("-", _serialNumberParts);
}
}
How does it work? For the following explanation, the reader is required to have a basic understanding of how the binding mechanism of WPF utilizes IValueConverters.
Convert method
First, let's take a look at the Convert method. The value passed to that method is obviously coming from the view models SerialNumber property and thus is a complete serial number.
Based on the ConverterParameter - which specifies the serial number part to be used for a particular binding - the appropriate portion of the serial number string will be extracted. In the example converter given here, i assumed a serial number format of five parts with 5 characters each, and each part being separated from another by a hyphen - character (i.e., a serial number would look like "11111-22222-33333-44444-55555").
Obviously the Convert method will return this serial number part, but before doing so it will memorize it in a private string array _serialNumberParts. The reason for doing this becomes clear when looking at the ConvertBack method.
Another responsibility of the Convert method is erasing the _serialNumberParts array in case the bound SerialNumber property provides an empty string or null.
ConvertBack method
The ConvertBack method essentially converts the data from the text box before it is being assigned to the SerialNumber property of the view model. However, the text box will only provide one part of the serial number -- but the SerialNumber property needs to receive a complete serial number.
To create a complete serial number, ConvertBack relies on the serial number parts memorized in the _serialNumberParts array. However, before composing the complete serial number the _serialNumberParts array will be updated with the new data provided by the text box.
In case your UI starts with empty text boxes, the ConvertBack method will not return a serial number until all text boxes have provided their data (i.e., until the user has typed something into all text boxes). Instead, the method will return Binding.DoNothing in case a complete serial number cannot be composed yet. (Binding.DoNothing instructs the binding to do (erm...) nothing.)
Considerations regarding SerialNumberConverter
For this converter to work without troubles, the following considerations need to be taken into account:
The bindings of each text box belonging to the same serial number need to use the same converter instance (so that the _serialNumberParts array will be able to keep track of the complete serial number)
If the UI provides several text box groups for entering multiple serial numbers, then each of these text box groups need to use a separate converter instance (otherwise, serial number parts of different serial numbers could mix in the same _serialNumberParts array). In my XAML example above, i ensured this by placing the converter instance in the resource dictionary of the parent StackPanel of the text boxes (which makes the converter instance local to this StackPanel and its descendent elements).
It is required to use data bindings for all serial number parts. Otherwise, the _serialNumberParts array will never be populated fully, which in turn will prevent ConvertBack from returning any complete serial number.
A ValidationRule for the text boxes
If validation of input should be handled for each text box individually, a custom ValidationRule is required:
public class SerialNumberValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, CultureInfo cultureInfo)
{
string serialNumberPart = value.ToString();
return (serialNumberPart.All(c => '0' <= c && c <= '9')) ?
(serialNumberPart.Length == 5) ?
ValidationResult.ValidResult :
new ValidationResult(false, "Serial number part must be 5 numbers") :
new ValidationResult(false, "Invalid characters in serial number part");
}
}
In the example SerialNumberValidationRule given here i assume that only number characters are valid characters for a serial number (you would of course implement the ValidationRule differently depending on the specification of your serial number format...)
While implementing such a ValidationRule is rather easy and straightforward, attaching it to the data bindings in XAML is unfortunately not as elegant:
<StackPanel Orientation="Horizontal">
<StackPanel.Resources>
<My:SerialNumberConverter x:Key="SerialNumberConverter" />
</StackPanel.Resources>
<TextBox>
<TextBox.Text>
<Binding Path="SerialNumber" ConverterParameter="0" Converter="{StaticResource SerialNumberConverter}">
<Binding.ValidationRules>
<My:SerialNumberValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
<TextBox>
<TextBox.Text>
<Binding Path="SerialNumber" ConverterParameter="1" Converter="{StaticResource SerialNumberConverter}">
<Binding.ValidationRules>
<My:SerialNumberValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
...here follow the remaining text boxes...
</StackPanel>
The reason for this convoluted XAML is Binding.ValidationRules being a read-only property. Unfortunately that means we cannot simply write something like
<Binding ValidationRules="{StaticResource MyValidator}" ... />
but instead need to resort to this kind of verbose XAML shown above to add our SerialNumberValidationRule to the Binding.ValidationRules collection.
Final notes
For the sake of readability, i omitted any sanity checks in my example converter code which are not required to get an understanding of how the code works. Depending on your requirements and application scenario you might need to add sanity checks to the converter code to prevent it from going haywire if the view model's SerialNumber property could possibly provide improper data.
The validation as depicted above will just show a slim red rectangle around a text box if the ValidationRule fails (this is default behavior for a text box). If your UI should present a more elaborate validation error response, most certainly you will need to do much more than just only adding the ValidationRule to the bindings...
I think the best way is using of 5 separate properties, but if you want only one you could use Converter and pass order number of each part as parameter.
<StackPanel Orientation="Horizontal">
<TextBox Text="{Binding SerialNumber, Converter={StaticResource SerialNumberConverter}, ConverterParameter=0}"/>
<TextBox Text="{Binding SerialNumber, Converter={StaticResource SerialNumberConverter}, ConverterParameter=1}"/>
<TextBox Text="{Binding SerialNumber, Converter={StaticResource SerialNumberConverter}, ConverterParameter=2}"/>
<TextBox Text="{Binding SerialNumber, Converter={StaticResource SerialNumberConverter}, ConverterParameter=3}"/>
<TextBox Text="{Binding SerialNumber, Converter={StaticResource SerialNumberConverter}, ConverterParameter=4}"/>
<StackPanel>

Databinding with WPF

I want to bind a button's width to some textbox's text value, although I want to always have a button width's that's twice what is written on the textbox. This is:
textBox1.Text = 10
will set
button1.Width = 20
Can I only do this through ValueConverters or is there other way to do it?
Thanks
Using IValueConverter is the easy solution but if you do not wish to do so, then you can try binding textbox1 and button1 with a single variable. For example, let say you have created two controls as seen in below and have binded into a single variable called ButtonText. For simplicity, Content of the button will be modified instead of Width of the button.
In xaml:
<TextBox Text="{Binding ButtonText, Mode=OneWayToSource, UpdateSourceTrigger=PropertyChanged}"/>
<Button Content="{Binding ButtonText, Mode=OneWay}"/>
In ViewModel:
public string ButtonText
{
get { return _buttonText; }
set
{
int result;
if (int.TryParse(value, out result))
_buttonText = (result * 2).ToString();
else
_buttonText = value;
OnPropertyChanged("ButtonText");
}
}
private string _buttonText;
Unfortunately, this solution does not work in .NET 4.0 because the way .NET 4.0 handles OneWayToSource, as stated in this article. Basically, the issue is that the Textbox will be updated with the value from ButtonText after it is set by the Textbox although its Mode was configured as "OneWayToSource". This solution will work for .NET 3.5.
To get around this OneWayToSource issue in .NET 4.0, you can use BlockingConverter (type of IValueConverter) to separate each time that the resource is used and set x:Shared="False", as stated in this article. Then again, you are using the IValueConverter but at least you are not using it to modify the value.
Bindings that are not simple assignments, that is what value converters are for.
(No other way to do it.)

Categories

Resources