Reputation: 1610
I am attempting to implement some simple validation on a textbox in MVVM
public string Property
{
get
{
if (App.PropertyStorageContainer != null)
{
return App.PropertyStorageContainer.Property;
}
else
{
return null;
}
}
set
{
App.PropertyStorageContainer.Property = value;
RaisePropertyChanged("Property");
}
}
Then in my PropertyStorageContainer class I have
private string _property;
public string Property
{
get
{
return App.PropertyStorageContainer.Property;
}
set
{
if(value meets some condition)
{
_property = value;
}
else
{
_property = someothervalue;
}
}
}
.
<TextBox Width="50" TextAlignment="Center" Text="{Binding Property, Mode=TwoWay, NotifyOnValidationError=True}" MaxLength="3"></TextBox>
The point of this is to validate what goes in the box. Now if I set this value directly from my code then everything works as I would expect. It attempts to SET the value, then calls RaiseProperyChanged, then GET the value (which because of the validation may not be the same value that was entered originally). The final value retrieved does show up on the view, so I know TwoWay binding is working.
The problem I have is when the input for SET comes from the bound XAML property / directy from user. In this case the SET method is called, the validation performed, but the GET never happens. This results in the unvalidated value remaining in the textbox on screen.
My first question would be is this a bug or expected behavior? I can see how maybe they tried to save performance by removing that last GET when the input came straight from the user since there should be nothing new to GET. But if not then maybe the way I have it all setup is interfering with the GET being called.
Second question is of course any suggestions for getting around this one. I've read a few suggestions for other methods of doing validation, but my program is already live on PROD and most of the changes being suggested involve a lot of rework for me so I am hoping to find a way to make it call GET any time the property is SET.
Upvotes: 0
Views: 1260
Reputation: 2535
I have made a couple of assumptions since I am not sure I understand you code completely but I think you could consider possibly implementing a custom validation rule. First off, since your custom ValidationRule will take care of the validation you could get the logic out of your model class's property definition and "dumb down" your poco:
class PropertyStorageContainer
{
public string Property { get; set; }
}
It seems you desire your view model to act as a basic wrapper around your model class. Again, I will assume this is valid based on the description of your scenario:
class PropertyStorageContainerViewModel : INotifyPropertyChanged
{
private PropertyStorageContainer model;
public PropertyStorageContainerViewModel(PropertyStorageContainer model)
{
this.model = model;
}
public string Property
{
get
{
if (model != null)
{
return model.Property;
}
else
{
return null;
}
}
set
{
if (model.Property != value)
{
model.Property = value;
RaisePropertyChanged("Property");
}
}
}
// INotifyPropertyChanged implementation...
}
Now create a new class that extends System.Windows.Controls.ValidationRule and override the abstract Validate method in order implement your validation logic. For the example, I created a rule that just checks if the string is null or empty (assuming that would be an invalid scenario):
class IsNullOrEmptyValidationRule : ValidationRule
{
public override ValidationResult Validate(object value, System.Globalization.CultureInfo cultureInfo)
{
string s = (value ?? string.Empty).ToString();
if (string.IsNullOrEmpty(s))
{
// Invalid...
return new ValidationResult(false, "Please enter a value.");
}
else
{
// Valid...
return new ValidationResult(true, null);
}
}
}
Now for the XAML... Here is an example of a TextBox that adds the validation rule to its binding validation rules (can be multiple rules).
<TextBox Name="textBox1" Width="50" FontSize="12"
Validation.ErrorTemplate="{StaticResource validationTemplate}"
Style="{StaticResource textBoxInError}">
<TextBox.Text>
<Binding Path="Property" UpdateSourceTrigger="PropertyChanged" >
<Binding.ValidationRules>
<local:IsNullOrEmptyValidationRule />
</Binding.ValidationRules>
</Binding>
</TextBox.Text>
</TextBox>
Then define the following resources (referenced above) somewhere (e.g., Window.Resources). First a ControlTemplate to define how the TextBox should look when in invalid state:
<ControlTemplate x:Key="validationTemplate">
<DockPanel>
<TextBlock Foreground="Red" FontSize="15" Text="!!!" />
<AdornedElementPlaceholder/>
</DockPanel>
</ControlTemplate>
Additionally you could define a style trigger to display the error message. Here I just bind it to the ToolTip property of the TextBox:
<Style x:Key="textBoxInError" TargetType="{x:Type TextBox}">
<Style.Triggers>
<Trigger Property="Validation.HasError" Value="true">
<Setter Property="ToolTip"
Value="{Binding RelativeSource={x:Static RelativeSource.Self},
Path=(Validation.Errors)[0].ErrorContent}"/>
</Trigger>
</Style.Triggers>
</Style>
Upvotes: 2
Reputation: 16628
You're going into INPC hell right now. I've been there and it's not fun.
That's a big no-no, especially since if any mapping is done on such classes, those getters and setters will be called outside of their WPF binding context and hell breaks lose.
Keep it simple: bind straight to App.PropertyStorageContainer.Property
For the second case, either:
Do yourself a favor and don't abuse properties' get/set
Upvotes: 0