Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a visualtree off of a control template in code

This is a follow up question to a previous question, wich didn't really get me anywhere: deterministic and asynchronous field validation in WPF

Since WPF doesn't support INotifyDataErrorInfo it seems, that I need to implement something like that myself (please correct me if I am wrong here). I need this because I want the ViewModel to trigger when to display special ErrorTemplates for certain fields (e.g. after the click of a button or after the end of a long running async validation operation or when the internal state changes in a way that certain fields suddenly become invalid).

I am considering to write a custom markup extension or behavior for this. It listens to my version of INotifyDataErrorInfo implemented by the ViewModel and creates a VisualTree from a special wellknown ErrorTemplate defined in XAML once the ErrorsChanged event was raised.

Once I have defined that template in XAML, how do I find it from my behavior/expression, materialize an actual visual tree out of it and then display it (probably somehow on an adorner layer) at the right field entry on my form?

like image 689
bitbonk Avatar asked Sep 03 '11 13:09

bitbonk


People also ask

What is the difference between control template and data template?

A ControlTemplate will generally only contain TemplateBinding expressions, binding back to the properties on the control itself, while a DataTemplate will contain standard Binding expressions, binding to the properties of its DataContext (the business/domain object or view model).

What is a control template?

The ControlTemplate allows you to specify the visual structure of a control. The control author can define the default ControlTemplate and the application author can override the ControlTemplate to reconstruct the visual structure of the control.

What is a visual tree?

The visual tree describes the structure of visual objects, as represented by the Visual base class.

Where do you put ControlTemplate?

The most common way to declare a ControlTemplate is as a resource in the Resources section in a XAML file. Because templates are resources, they obey the same scoping rules that apply to all resources.


1 Answers

You don't need a markup extension. I recently found myself wishing for the same behavior, so I created a solution that works for my needs. Hopefully this helps you as well.

The IDataErrorInfo interface actually contains everything we need in order to do asynchronous signaling. What it lacks is an event system to trigger notifications automatically. There is a relationship between that interface and the INotifyPropertyChanged interface. The combination of the two actually allows you to signal a change, somewhat indirectly.

First the control:

<TextBox
    Grid.Column="1"
    Width="100"
    Text="{Binding UpdateSourceTrigger=LostFocus,
        Path=Id,
        ValidatesOnDataErrors=true}" />

Pretty straightforward. The value of UpdateSourceTrigger is not important, and NotifyOnValidationError is not required, but won't hurt anything if you add it.

Next, the view model, which is just a contrived example. The important part is in the IDataErrorInfo indexer.

public class WindowViewModel : INotifyPropertyChanged, IDataErrorInfo
{
    public event PropertyChangedEventHandler PropertyChanged;

    private int _id;
    public int Id
    {
        get{ return _id; }
        set
        {
            _id = value;
            this.PropertyChanged( this, new PropertyChangedEventArgs( "Id" ) );
        }
    }

    public string this[ string columnName ]
    {
        get
        {
            object result = AsynchValidationCoordinator.GetError( columnName );
            if ( result != null )
            {
                return result.ToString();
            }
            return null;
        }
    }

AsynchValidationCoordinator is a class that tracks properties and any associated error information. For illustrative purposes, the key being used is just the property name, but you could as easily create a compound key to differentiate potential property collisions in scenarios with multiple view models.

public static class AsynchValidationCoordinator
{
    private static readonly ConcurrentDictionary<string, object> ErrorList = 
        new ConcurrentDictionary<string, object>();

    public static void CancelError( string propertyName, object error )
    {
        object value;
        ErrorList.TryRemove( propertyName, out value );
    }

    public static object GetError( string propertyName )
    {
        object error = null;
        if ( ErrorList.ContainsKey( propertyName ) )
        {
            ErrorList.TryRemove( propertyName, out error );
        }
        return error;
    }

    public static void RegisterError( string propertyName, object error )
    {
        ErrorList[propertyName] = error;
    }
}

Tracking property names is necessary, but you could create an entirely different way of tracking them, including tracking the names within the view model. This was just an easy way for me to apply a structured form quickly to an existing project.

So tying this all together, I added the following ICommand property to the test view model and bound it to a Button. (RelayCommand is from Josh Smith's MSDN MVVM article.)

public ICommand ValidateCommand
{
    get
    {
        return new RelayCommand( Validate );
    }
}

private void Validate( object value )
{
    Thread thread = new Thread( RaiseChanged );
    thread.Start();
}

private void RaiseChanged()
{
    Thread.Sleep( 3000 );
    AsynchValidationCoordinator.RegisterError( "Id", "Error Message Goes Here" );
    this.PropertyChanged( this, new PropertyChangedEventArgs( "Id" ) );
}

The source of the call is irrelevant. The important point that ties all of this together is the fact that once PropertyChanged is called, the IDataErrorInfo indexer follows in its tracks. Returning the error information that was registered in AsynchValidationCoordinator triggers the Validation.ErrorTemplate of the control with the relevant error message.

like image 107
Paul Walls Avatar answered Sep 23 '22 13:09

Paul Walls