0

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.

2 Answers 2

2

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>
Sign up to request clarification or add additional context in comments.

5 Comments

Thanks for the help, but I ended up figuring out how to get RaisePropertyChanged working using the code I already have. Basically it came down to .NET not wanting to allow a RaisePropertyChanged from the same thread that is updating the binding. This makes sense because why would you look for an updated property when you are the one changing the property to begin with. Anyway I circumvented this by calling Dispatcher.BeginInvoke(() => _viewmodel.RaisePropertyChanged(null)); and thus seperating out the threads
There I go again taking the time to provide an orthodox solution only to find it does not get marked as answered and an unorthodox/"quick-fix" route is still seen as somehow the right path to go down. Oh well...
@blins here goes my +1 at least... Don't worry he will regret his decision later ;)
Well the reason you didn't get marked as answered is because my question did not ask for an orthodox solution. I asked for an explanation of the behavior, and possible work arounds. The app this is implemented in has this same technique used on over 1000 pages with hundreds of bindings each, and management could care less about the bug. Sometimes it is a quickfix that is required.
But if it means that much I'll give it to you because you did provide a good explanation of how to correctly implement this type of validation.
0

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:

  • Use data validation
  • Let the property be set not by binding but through a Command, in which you can do such value swap.

Do yourself a favor and don't abuse properties' get/set

6 Comments

binding directly to App.PropertyStorageContainer.Property does nothing to fix the issue. It still calls SET without calling GET afterwards which is the core of the problem.
and what do you mean use data validation? That's not very descriptive. Also setting the property through a Command is undesired as that is no longer MVVM.
@Josh I meant that using getters/setters this way is bad design and will lead to more design issues down the road. And using commands for that is perfectly MVVMish, as long as you're not just setting it but acting upon the data as well ( if(value meets some condition) )
Surely you realize calling GET after SET is odd, and you wouldn't expect a software written by someone else to do that.
I don't want to call GET and SET, it should happen automatically when the bound property is accessed! Anyway with the help of another used I've figured it out. I ended up invoking an event at the end of SET which calls to the view and then Dispatcher.BeginInvoke(() => _viewmodel.RaisePropertyChanged(null)); The dispatcher is necessary because .NET will try everything to prevent the UI from calling GET when it doesn't have to, so it's crucial to remove this call from the same thread that was updating the binding to begin with.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.