Using Generics on Classes with Static DependencyProperty

133 views Asked by At

We are looking at creating a helper class for validation on a DevExpress WPF application single view model.

In our xaml, we want to add a reference to our ValidationServiceHelper class:

<dxlc:DataLayoutControl x:Name="layoutControlMyObject" Style="{StaticResource EntityView.DataLayoutControl}"
    viewmodel:ValidationServiceHelper.HasErrors="{Binding RelativeSource={RelativeSource Self}, Path=(dxe:ValidationService.HasValidationError)}">

The ValidationServiceHelper class looks like this:

namespace MyApplication.ViewModels
{
    public partial class MyObjectViewModel : 
        SingleObjectViewModel<MyObject, int, IMyEntityUnitOfWork>
    {
        // ...
    }

    public class ValidationServiceHelper
    {
        public static bool GetHasErrors(DependencyObject obj)
        {
            return (bool)obj.GetValue(HasErrorsProperty);
        }

        public static void SetHasErrors(DependencyObject obj, bool value)
        {
            obj.SetValue(HasErrorsProperty, value);
        }

        public static readonly DependencyProperty HasErrorsProperty = 
            DependencyProperty.RegisterAttached("HasErrors", typeof(bool), 
            typeof(ValidationServiceHelper), new PropertyMetadata(false, OnHasErrorsChanged));

        private static void OnHasErrorsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            try
            {
                FrameworkElement element = (FrameworkElement)d;
                element.Dispatcher.BeginInvoke(new Action(() => 
                    ((MyObjectViewModel)element.DataContext).ViewHasErrors = (bool)e.NewValue));
                var err = ValidationService.GetValidationErrors(d);
                if (err != null)
                    element.Dispatcher.BeginInvoke(new Action(() => 
                        ((MyObjectViewModel)element.DataContext).ViewErrors = 
                    err.Select(p => p.ErrorContent).Distinct().Aggregate(
                    (j, i) => string.Format("{0}{1}{2}", i, Environment.NewLine, j)).ToString()));
            }
            catch (Exception ex)
            {
                throw new Exception(ex.Message);
            }
        }
    }
}

Looking at the two Dispatcher.BeginInvoke calls in OnHasErrorsChanged, you will see I hard-coded a cast to MyObjectViewModel.

element.Dispatcher.BeginInvoke(new Action(() =>
    ((MyObjectViewModel)element.DataContext).ViewHasErrors = (bool)e.NewValue));

Written like this, I would need to create a different helper class. Is there a way to make this generic, so I can only use one class for all my view models?

1

There are 1 answers

7
mm8 On

You could derive your view model classes from a common base class (or implement an interface) where the ViewErrors property is defined and cast to this type:

var vm = element.DataContext as BaseViewModel;
if  (vm != null)
    element.Dispatcher.BeginInvoke(new Action(() => vm.ViewHasErrors = (bool)e.NewValue));

But you will never be able to cast the DataContext to any other type than the actual type of the object in memory. Using generics one way or another doesn't change this fact.

And there is no way to cast a SingleObjectViewModel<A, int, IMyEntityUnitOfWork> to a SingleObjectViewModel<B, int, IMyEntityUnitOfWork> because these are two totally different types.