开发者

Force update of view on validation error using MVVM pattern

开发者 https://www.devze.com 2023-01-07 23:49 出处:网络
I have the problem: I have some controls on a form (a check-box, a combo-box, a slider, a text-box). Their values are bound to different properties of the view model.

I have the problem: I have some controls on a form (a check-box, a combo-box, a slider, a text-box). Their values are bound to different properties of the view model.

When a property of the view model has a certain value, I want these control to be "fixed" (a error message is displayed and they are set to some fixed value (e.g: the check-box is unchecked when the user tries to check-it, the slider is set to a certain value, the combo's selected item is the second item in the list). I did this this way (a simplified example for text-box): In the view:

            <TextBox 
                Text="{Binding ViewModelProperty, 
                               NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged, 
                               ValidatesOnDataErrors=True, NotifyOnValidationError=True}"
                />

In the view model: The property is defined like this:

String _ViewModelProperty;
public String ViewModelProperty
{
    get
    {
        return _ViewModelProperty;
    }
    set
    {

        _ViewModelProperty = value;
        OnPropertyChanged("ViewModelProperty");
    }
}

and the implementation of IDataErrorInfo:

String IDataErrorInfo.this[String propertyName]
{
    get
    {
        String error = null;
        if (propertyName == "ViewModelProperty")
        {
            if (ViewModelProperty != "FixedValue")
            {
                error = DisplayMessage("You ca开发者_高级运维n only set a fixed value here");
                ViewModelProperty= "FixedValue";
            }
        }
        return error;
    }
}

This works well for the check-box, but for all other controls, it functions like this: the user sets the "wrong" value, the error message is displayed and then, instead of updating the control with the fixed value, the wrong value is still displayed (it is not synchronized with the view model anymore).

I can't figure out how to force an update of the control's value.

Thank you in advance.


You can bind the IsEnabled properties of the controls you want to fix to a set of properties on the ViewModel, such as CanUserChangeSlider.

<Slider IsEnabled={Binding CanUserChangeSlider} ... />

So in the setter for the Comboboxes bound property:

set 
{
  // store value in backing store
  // RaisePropertyChanged

  if (value == true)
  {
    this.CanUserChangeSlider = false;
    // the bound property for slider set to certain value
    // the bound property for combobox changed to be a certain value
  }
}


The way to force the re-bind is to call BindingExpression.UpdateTarget() or BindingExpression.UpdateSource(), as appropriate.

Of course, you don't want this in your view-model.

Since I inject view-models into my views, and set them as the DataContext in the view constructor, I would do something like the following:

  • declare a delegate or event on the view-model that will be called or raised from property setters when data is coerced
  • in the view constructor, attach a handler to the delegate that will call the appropriate update methods as described above (also, override Dispose() and remove the handler)

Basically, just raise an event whenever data is coerced, and pass out the data that the view will need to re-bind to the view-model.

This is off the top of my head, so I haven't worked out the exact parameters you might need in the delegate signature, nor the extent to which the handler can be made to work with all your fields. It is perhaps a jumping-off point, though.


I don't use the IsEnabled property, because these are the requirements: nothing disabled, only message displayed if "invalid" values are set.
Other requirement is not to inject view-models into view... We have some 'workspace' classes that link a view with a view model, so no view is aware of the view model instance attached to it. Of course, the injection can be made from the workspace... I tried this and still does not seem to work.
But isn't any other elegant and MVVM-ish method of doing this? I prepared a very simple example that illustrates my problem.
I have 2 text-boxes, bound to the same property in the view model:

<StackPanel>
    <Label>First text box</Label>
    <TextBox 
        Text="{Binding Path=Property,
                 NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged, 
                 ValidatesOnDataErrors=True, NotifyOnValidationError=True}"
    />
    <Label>Second text box</Label>
    <TextBox
        Text="{Binding Path=Property, 
                 NotifyOnSourceUpdated=True, UpdateSourceTrigger=PropertyChanged, 
                 ValidatesOnDataErrors=True, NotifyOnValidationError=True}"
        />
</StackPanel>

The view model has the IDataErrorInfo implementation:

public String this[String propertyName]
{
    get 
    {
        String error = null;
        if (propertyName == "Property")
        {
            if (Property != "1000")
            {
                error ="Only value '1000' is accepted here.";
                MessageBox.Show(error, "Error", MessageBoxButton.OK, MessageBoxImage.Error);
                Property = "1000";
            }
        }
        return error;

    }
}

Initially, the value is "1000".
If I add a "0" in one text-box, the message is displayed, the other text-box is correctly updated to "1000", but the focused text-box's value remains "10000".
I feel that I miss here something essential.

Lucia

0

精彩评论

暂无评论...
验证码 换一张
取 消