Ищем ярлык DependencyProperty.Register

Определение свойств WPF слишком длинное:

public static readonly DependencyProperty FooProperty = 
  DependencyProperty.Register("Foo", typeof(string), typeof(FooClass), new PropertyMetadata("Foooooo"));

У меня есть вспомогательный метод, который немного короче:

public static readonly DependencyProperty FooProperty =
  WpfUtils.Property<string, FooControl>("Foo", "Foooooo");

Код:

public partial class WpfUtils 
{
    public static DependencyProperty Property<T, TClass>(string name) 
    {
        return Property<T, TClass>(name, default(T));
    }

    public static DependencyProperty Property<T, TClass>(string name, T defaultValue) 
    {
        return DependencyProperty.Register(name, typeof(T), typeof(TClass), new PropertyMetadata(defaultValue));
    }
}

Есть ли помощники получше?

Ответов (4)

Решение

Вот код для этого. Этот код злой, но я хотел показать, как это сделать без использования Cecil или создания проекта sourceforge :-)

Чтобы воспользоваться им, позвоните:

public static readonly DependencyProperty FooProperty = D.P();

И код такой:

public class D
{
    [MethodImpl(MethodImplOptions.NoInlining)]
    public static DependencyProperty P()
    {
        StackTrace stackTrace = new StackTrace();
        StackFrame oneUp = stackTrace.GetFrame(1);
        MethodBase callingMethod = oneUp.GetMethod();
        if (!(callingMethod is ConstructorInfo))
        {
            throw new InvalidOperationException("This method must be called from a static constructor/initializer");
        }
        byte[] staticConstructorCode = callingMethod.GetMethodBody().GetILAsByteArray();
        int offsetAfterThisCall = oneUp.GetILOffset() + 5;

        while (staticConstructorCode[offsetAfterThisCall] == OpCodes.Nop.Value)
        {
            offsetAfterThisCall++;
        }

        if (staticConstructorCode[offsetAfterThisCall] != OpCodes.Stsfld.Value)
        {
            throw new InvalidOperationException("Unexpected IL");
        }

        int token = BitConverter.ToInt32(staticConstructorCode, offsetAfterThisCall + 1);

        FieldInfo field = callingMethod.Module.ResolveField(token);

        if (!field.Name.EndsWith("Property") || field.FieldType != typeof(DependencyProperty))
        {
            throw new NotSupportedException("The field the return value of this method will be stored in must be named xxxProperty and be of type DependencyProperty");
        }

        string name = field.Name.Substring(0, field.Name.Length - "Property".Length);
        return DependencyProperty.Register(name, callingMethod.DeclaringType.GetProperty(name).PropertyType, callingMethod.DeclaringType);
    }
}

Я согласен, что очень неприятно, что свойства зависимостей так длинны. Я не использую помощник, но в Visual Studio есть отличный встроенный фрагмент, который можно использовать, набрав wpfdp.

Вот так я быстро заполняю кучу свойств зависимостей.

Для тех, кто использует resharper, я использую следующий шаблон

//DependencyProperty $PropertyName$
public static readonly DependencyProperty $PropertyName$Property =
      DependencyProperty.Register("$PropertyName$", typeof($PropertyType$), typeof($SelfType$),
      new FrameworkPropertyMetadata($DefaultValue$, $PropertyName$ChangedCallback, $PropertyName$CoerceValue));

public $PropertyType$ $PropertyName${
       set { SetValue($PropertyName$Property, value); }
       get { return ($PropertyType$)GetValue($PropertyName$Property); }
      }

private static void $PropertyName$ChangedCallback(DependencyObject d, DependencyPropertyChangedEventArgs e){

      $SelfType$ owner = d as $SelfType$;
      if(owner!=null){}

      }

 private static object $PropertyName$CoerceValue(DependencyObject d, object value)   {

            $PropertyType$ val = ($PropertyType$)value;
        return value;
      }

У меня он показывает только там, где можно объявить член, я также установил $ SelfType $ для расширения до родительского типа, который здесь является именем класса

Вот моя попытка. Это безопаснее, чем метод считывания IL Аланом Харфордом.

Этот помощник имеет следующие особенности:

  • Поддержка свойств зависимостей и прикрепленных свойств
  • Предоставление имени свойства безопасным для типов способом, без (магических) строк (с использованием деревьев выражений)
  • Поддержка типобезопасного обратного вызова с универсальными аргументами

Сначала я покажу использование:

public class Tester : DependencyObject
{
    public int Foo
    {
        get { return (int)GetValue(FooProperty); }
        set { SetValue(FooProperty, value); }
    }
    public static readonly DependencyProperty FooProperty = 
        For<Tester>.Register(o => o.Foo, 0, onFooChanged);

    private static void onFooChanged(Tester obj, DependencyPropertyChangedEventArgs<int> e)
    {
    }

    public string Attached
    {
        get { return (string)GetValue(AttachedProperty); }
        set { SetValue(AttachedProperty, value); }
    }
    public static readonly DependencyProperty AttachedProperty = 
        For<Tester>.RegisterAttached(o => o.Attached, "default", onAttachedChanged);

    private static void onAttachedChanged(DependencyObject obj, DependencyPropertyChangedEventArgs<string> e)
    {
    }
}

А вот и реализация:

public static class For<TOwner>
    where TOwner : DependencyObject
{
    public static DependencyProperty Register<TProperty>(
        Expression<Func<TOwner,TProperty>> property, 
        TProperty defaultValue, 
        Action<TOwner, DependencyPropertyChangedEventArgs<TProperty>> callback)
    {
        return DependencyProperty.Register(
            getName(property),
            typeof(TProperty),
            typeof(TOwner),
            new FrameworkPropertyMetadata(
                defaultValue,
                (o, args) => callback((TOwner)o, new DependencyPropertyChangedEventArgs<TProperty>(args))));
    }

    public static DependencyProperty RegisterAttached<TProperty>(
        Expression<Func<TOwner,TProperty>> property, 
        TProperty defaultValue,
        Action<DependencyObject, DependencyPropertyChangedEventArgs<TProperty>> callback)
    {
        return DependencyProperty.RegisterAttached(
            getName(property),
            typeof(TProperty),
            typeof(TOwner),
            new FrameworkPropertyMetadata(
                defaultValue,
                (o, args) => callback(o, new DependencyPropertyChangedEventArgs<TProperty>(args))));
    }

    private static string getName<T>(Expression<Func<TOwner,T>> property)
    {
        var name = ((MemberExpression)property.Body).Member.Name;
        return name;
    }
}

public struct DependencyPropertyChangedEventArgs<T>
{
    public DependencyPropertyChangedEventArgs(DependencyPropertyChangedEventArgs source)
    {
        m_property = source.Property;
        m_oldValue = (T)source.OldValue;
        m_newValue = (T)source.NewValue;
    }

    private readonly DependencyProperty m_property;
    public DependencyProperty Property { get { return m_property; } }

    private readonly T m_oldValue;
    public T OldValue { get { return m_oldValue; } }

    private readonly T m_newValue;
    public T NewValue { get { return m_newValue; } }
}