How can the property grid handle async errors?

Grids for WPF Forum

Posted 9 years ago by Craig - Varigence, Inc.
Version: 10.2.0532
Avatar
We have an application where validation occurs on a separate thread from the UI thread. When new validation errors are identified, or old issues are fixed, we notify the appropriate object. Each object has a backing store that tracks the errors for each property. Thus, we can still query for an error string via the 'this indexer' that's defined in IDataErrorInfo. The problem is the property grid seems to do this immediately when the property changes, but the VM's backing store won't have been updated yet. Is there a way to handle async errors in the property grid?

1. Note that I've looked into modifying the templates. While I was able to get the TextBox template to work, I had issues getting the ComboBox template to show the changes to the borders via triggers. Additionally, I'd prefer to not make template changes if possible (or at least keep them simple) to keep maintenence simple as new releases come along, and since we use custom DataTemplates in a bunch of situations.

2. For our DataGrids, we handle this by modifying the cell style with triggers, which can cause an error border to appear around a cell when the property indexer fires:

<DataTrigger 
    Binding="{Binding Path=[Length], UpdateSourceTrigger=PropertyChanged}"
    Value="Error"
    >
    <Setter Property="BorderBrush" Value="{DynamicResource errorBrush}"/>
    <Setter Property="BorderThickness" Value="{DynamicResource validationBorder}"/>
</DataTrigger>
One challenge with adapting this approach to the property grid is that I don't see a way to create the {Binding Path=[Length]} on the fly for any property.

3. I've noticed that Silverlight has INotifyDataErrorInfo, which I suspect is exactly what we want. Does the WPF PropertyGrid support this?

4. Is there be a way to tell the property grid to recheck for an error on a property?

Thanks for any help you can give,

-Craig

[Modified at 12/13/2010 04:47 PM]

Comments (8)

Posted 9 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Craig,

The PropertyGrid simply acts as a proxy for IDataErrorInfo requests, so it's ultimately called by the WPF control used to present the value. So if you have a TextBox presenting a value, then it would call into our wrapper object (which implements IDataErrorInfo), which forwards the calls to the underlying object (if it implements IDataErrorInfo). So it's not a matter of getting the PropertyGrid to "recheck for an error on a property", as this a feature of the WPF binding system and control used to present the value.

The INotifyDataErrorInfo interface is currently only support in Silverlight, and it is not available to WPF controls. But again, even if we added a similar interface, it's up to the Binding to run the validation (which we have no control over).

The bindings in our default property editors set ValidatesOnDataErrors to true. This effectively adds an instance of DataErrorValidationRule to the Binding.ValidationRules. I believe this is where you would need to add support for asychronous IDataErrorInfo. One article about an asynchronous ValidationRule implementation is here. You can probably combine the async support from that blog post with DataErrorValidationRule (which is sealed). You'd need to create an instance of DataErrorValidationRule and simply call it to validate from your custom ValidationRule. Once you have that, you'd need to create custom property editors (probably based on our default ones) that use your custom ValidationRule.

Alternatively, you may be able to fire a PropertyChanged event after validating a given property, and ensure that Binding.ValidatesOnTargetUpdated is true. But again, here you'd need custom property editors to update the Bindings of the default property editors.


Actipro Software Support

Posted 9 years ago by Craig - Varigence, Inc.
Avatar
Thanks for the prompt reply, along with the article on async ValidationRules. If I can get that to work, I think that'll work great for both the PropertyGrid and my DataGrids, especially as it'll allow me to return to using the built in Validation.ErrorTemplate.

For creating custom property editors to support additional validation rules, do you have a documentation page or sample that discusses best practices? Currently, I copy the base editors from Actipro Styles\PropertyGrid\Themes\PropertyGrid\Controls\PropertyGrid\PropertyEditors.xaml into the ResourceDictionary of the xaml file where I declare my property grid. I then use the PropertyEditorsModifiers to add support. For example:

<DataTemplate x:Key="{x:Static appropgrid:BuiltinEditors.TextBoxValueTemplateKey}">
    <TextBox 
        x:Name="textBox" 
        Margin="0" 
        Padding="0"
... (Altered DataTemplate)
</DataTemplate>


<appropgrid:PropertyEditorsModifier 
    x:Key="{x:Static appropgrid:BuiltinEditors.PropertyEditorsModifierKey}"
    >
    <appropgrid:PropertyEditorsModifierActionAdd>
        <appropgrid:TextBoxPropertyEditor             
            ValueTemplate="{StaticResource {x:Static    appropgrid:BuiltinEditors.TextBoxValueTemplateKey}}"
            >
            <appropgrid:TextBoxPropertyEditor.ValueStyles>
                <appropgrid:PropertyEditorStyle
                    Key="{x:Type TextBox}"
                    >
                    <appropgrid:PropertyEditorStyle.Style>
                        <Style 
                            TargetType="{x:Type TextBox}"
                            />
                    </appropgrid:PropertyEditorStyle.Style>
            </appropgrid:PropertyEditorStyle>
        </appropgrid:TextBoxPropertyEditor.ValueStyles>
    </appropgrid:TextBoxPropertyEditor>
</appropgrid:PropertyEditorsModifierActionAdd>
Is this the right way to do this or is there a better way?

Thanks again,

-Craig
Posted 9 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Craig,

If you just want to override the default property editors with your custom ones, then you can simply redefine the DataTemplates from PropertyEditors.xaml as-is (possibly updating the xmlns entries). The PropertyGrid will look for say the TextBoxValueTemplateKey DataTemplate using BuiltinEditors.TextBoxValueTemplateKey. So if you define a new DataTemplate with the same key lower in the search tree, then yours will effectively override ours.

This is definitely the easiest way, since you don't have to update BuiltinEditors.PropertyEditors or PropertyGrid.PropertyEditors.


Actipro Software Support

Posted 9 years ago by Craig - Varigence, Inc.
Avatar
I looked over the async validation blog link you provided but unfortunately, the approach is tied to a custom validation framework that the author was working on but never released. After doing some more reading, I'm not optimistic about writing a custom validation rule that runs asynchronously as I don't see how to notify the UI that the property is ready to be checked. If I block the validation function to wait for the property's validation state to change, which may not even happen, then I'd be blocking the UI Thread. If I don't block the validation function, then it will return and I won't have a way to notify the built-in validation system that the property's error state changed afterwards.

This brings me back to bindings, however, as I'd need to author custom property editors anyway. Is there a way to create a binding from IPropertyDataAccessor that would be bound to the property name surrounded by brackets. For example, providing the equivalent of:

Binding="{Binding Path=[Length], Mode=OneWay, UpdateSourceTrigger=PropertyChanged}"
for a property named Length? If that's possible, then I could at least have custom DataTriggers that fired when the property changed, and I wouldn't have to worry about knowing the name of the property.

Thanks again,

-Craig
Posted 9 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Craig,

Sorry about that, I didn't read into the article enough. Did you try setting ValidatesOnTargetUpdated to true on the bindings and raising a PropertyChanged event for the appropriate property? My suggestion previous referenced Binding, but this is actually on ValidationRule. So you'd need to add an instance of DataErrorValidationRule and set ValidatesOnTargetUpdated to true. Be sure to set Binding.ValidatesOnDataErrors to false, or remove it, as that would add another instance of DataErrorValidationRule.

The "[Length]" binding is actually accessing your object's implementation of "this[string name]", which is part of the IDataErrorInfo interface. When we implement IDataErrorInfo in our abstraction layer, it needs to return errors for the "Value" or "ValueAsString", as those are the properties that are bound to by the property editors. So when the WPF binding/validation system calls into our IDataErrorInfo implementation it passes "Value" or "ValueAsString", then we call into the underlying object for the associated property (i.e. "Length").

A similar process happens when we receive a PropertyChanged event from the underlying object. If we receive a changed event for "Length", then the associated IPropertyDataAccessor (i.e. the abstraction layer) will raise it's own PropertyChanged events passing "Value" and "ValueAsString".

So you may be able to bind to "[Value]" or "[ValueAsString]", depending on which you use. The IDataErrorInfo interface is implemented explicitly, so that may be a problem for the Binding. But you would also need to raise some sort of change event to notify the Binding that the value of "[Value]" or "[ValueAsString]" has changed. Currently, there's nothing built in to do that.

One idea is you could create custom PropertyGrid and PropertyGridDataAccessorItem classes. For your MyPropertyGrid you'd watch for changes to SelectedObject(s) and for MyPropertyGridDataAccessorItem you'd watch for changes to the DataContext. In those change handlers you'd need to see if the object(s) implement a custom interface, which has an event of say ValidationChanged. Your objects would need to implement this interface and raise the event appropriately. Your MyPropertyGrid and MyPropertyGridDataAccessorItem would hook into this event, and when raised they would raise a PropertyChanged event for all it's "items" passing "[Value]" and/or "[ValueAsString]". You'd need to be sure to dispatch that call, if it's coming from another thread.

You can access the underlying object via a binding ("{Binding DataContext.Target[Length], RelativeSource={RelativeSource AncestorType={x:Type propgrid:PropertyGridDataAccessorItem}}}"), but there isn't a generic way to pass the property name.


Actipro Software Support

Posted 9 years ago by Craig - Varigence, Inc.
Avatar
I re-examined your suggestion of raising the PropertyChanged event and that seems to be working.

First, I changed my underlying object to implement IDataErrorInfo again. Additionally, when we add an error for a property, we now fire an OnPropertyChanged event for that property.

Then, I implemented the Validation.Error event on the PropertyGrid as follows:

        private void propertyGrid_Error(object sender, ValidationErrorEventArgs e)
        {
            if (e.Action == ValidationErrorEventAction.Added)
            {
                Severity errorSeverity;
                var dependencyObject = e.OriginalSource as DependencyObject;

                if (dependencyObject != null && Enum.TryParse(e.Error.ErrorContent as string, out errorSeverity))
                {
                    // if (errorSeverity == Severity.Warning)
                    {
                        Validation.SetErrorTemplate(dependencyObject, "CustomValidationTemplate" as ControlTemplate);
                    }
                }
            }
        }
So when an error is added, I access the actual control it came from (the TextBox, ComboBox, etc...) and set its error template. Since the strings we return from the this[] property represent error severity values (Error, Warning, etc...), I can also use this approach to choose which validation template I want, depending on the severity type.

Also, it seems like I don't need to alter the property editor templates to support this approach; presumably since I'm setting the ErrorTemplate directly.

Do you see any holes or potential problems here? I need to look at a couple corner cases regarding properties that have multiple errors of different severity types, but assuming no issues there, I think this may be a good approach.

Thanks again,

-Craig
Posted 9 years ago by Actipro Software Support - Cleveland, OH, USA
Avatar
Hi Craig,

The concern would be if you were calling the PropertyChanged from a separate thread, as that would generally need to be dispatched to the UI thread. But it may be ok in this case.


Actipro Software Support

Posted 9 years ago by Craig - Varigence, Inc.
Avatar
I just wanted to say that we have this working now and it's quite nice. I used OnPropertyChanged to trigger the re-evaluation, and I ended up changing the templates to use a custom validation error template, as opposed to using the PropertyGrid error event handler approach.

Thanks again for your help,

-Craig
The latest build of this product (v2019.1 build 0683) was released 2 months ago, which was after the last post in this thread.

Add Comment

Please log in to a validated account to post comments.