views:

26

answers:

2

Hi,

I'm currently struggling with one of the bindings I'm trying to add to my WPF project. In the app I have a model with a bool property that cannot be used for databinding. Behind that property is a .NET remoting object that does some validation and writes the new value into the DB.

The requirement ist that the property should be displayed as checkbox, and as the user changes the value the new value should be immediatly provided to the .NET remoting object.

My approach so far: I've created in my ViewModel with a DependencyProperty that is bound to my checkbox. In the propertychanged handler of the DP, I'm writting the value to the property of the remoting object.

The problems I have with this approach: if the validation within the .net remoting object raises an exception, this exception is swallowed. In addition the checkbox state and what's in the DB is not in sync. I tried to reset the value of the DP in case of an exception, but the checkbox doesn't reflect that. What makes the situation even worse is the fact, that this WPF controls is integrated into an existing WinForms app. So I would like to have the same behavior for these exceptions as I have implemented in my Application.ThreadException handler.

any ideas how to approach this? The problem is that I heard only solutions for .NET 4.0 so far, but I'm working with 3.5SP1.

tia Martin

Short demo code:

class TestVM : DependencyObject
{
private Model _m;
public TestVM()
{
  _m = new Model();
}

public bool Value
{
  get { return (bool)GetValue(ValueProperty); }
  set { SetValue(ValueProperty, value); }
}

// Using a DependencyProperty as the backing store for Value. This enables animation, styling, binding, etc...
public static readonly DependencyProperty ValueProperty =
  DependencyProperty.Register("Value", 
                typeof(bool), 
                typeof(TestVM), 
                new FrameworkPropertyMetadata(
                   false,
                   FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
                   ((sender, e) => ((TestVM)sender).Apply(e))));

private bool _suppress = false;
private void Apply(DependencyPropertyChangedEventArgs e)
{
  if (_suppress) return;
  try
  {
    _m.Value = this.Value;
  }
  catch
  {
    _suppress = true;
    this.Value = _m.Value;
    this.OnPropertyChanged(e);
  }
  finally
  {
    _suppress = false;
  }
}

}

A: 

You don't need to use a DependencyObject as your ViewModel. You just need to implement INotifyPropertyChanged to get data binding support:

class TestVM
    : INotifyPropertyChanged
{
    private Model _m;
    public TestVM()
    {
        _m = new Model();
    }

    public bool Value
    {
        get { return _m.Value; }
        set 
        {
            _m.Value = this.Value;
            OnPropertyChanged("Value");
        }
    }

    protected void OnPropertyChanged(string propertyName)
    {
        if (PropertyChanged != null)
        {
            PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }
    }

    public event PropertyChangedEventHandler PropertyChanged;
}

Note that if you expect the setter to throw exceptions, you may want to use an ExceptionValidationRule on the binding in your view.


Update: It sounds like your problem is that the Binding won't respond to PropertyChanged events within the call to set the source. One way to get around this is to use an asynchronous binding by setting IsAsync=True in the XAML for your binding. WPF will process the PropertyChanged event after it has finished updating the source value and won't think it is a reentrant call.

You can also get around this by using a Converter and turning off updates on PropertyChanged by doing UpdateSourceTrigger=LostFocus, but I don't think you would want that behavior.

Quartermeister
sorry but this doesn't work, even if I get the exception, and call the OnPropertyChanged in the finally, the checkbox doesn't reflect the not updated value.
Martin Moser
A: 

I found a solution for my problem. I'm now deriving my own binding class that does the job.

public class ExceptionBinding : Binding
{
    public ExceptionBinding(string name)
        : base(name)
    {
        Construct();
    }

    public ExceptionBinding()
        : base()
    {
        Construct();
    }

    private void Construct()
    {
        this.ValidatesOnExceptions = true;
        this.UpdateSourceExceptionFilter = new UpdateSourceExceptionFilterCallback(OnException);
    }

    private object OnException(object bindExpression, Exception exception)
    {
        // ... custom error display ...
        var exp = (BindingExpressionBase)bindExpression;
        exp.UpdateTarget();
        return null; // null needed to avoid display of the default error template
    }
}
Martin Moser