Обернутый WPF Control - PullRequest
       13

Обернутый WPF Control

1 голос
/ 12 мая 2010

Я пытаюсь создать библиотеку GUI (WPF), в которой каждый (пользовательский) элемент управления в основном обертывает внутренний (сторонний) элемент управления. Затем я вручную выставляю каждое свойство (не все, но почти все). В XAML результирующий элемент управления довольно прост:

<my:CustomButton Content="ClickMe" />

И код довольно прост:

public class CustomButton : Control
{    
    private MyThirdPartyButton _button = null;

    static CustomButton()
    {        
        DefaultStyleKeyProperty.OverrideMetadata(typeof(CustomButton), new FrameworkPropertyMetadata(typeof(CustomButton)));
    }

    public CustomButton()
    {
        _button = new MyThirdPartyButton();
        this.AddVisualChild(_button);        
    }

    protected override int VisualChildrenCount
    {    
        get
            { return _button == null ? 0 : 1; }
    }

    protected override Visual GetVisualChild(int index)
    {
        if (_button == null)
        {
            throw new ArgumentOutOfRangeException();
        }
        return _button;
    }

    #region Property: Content
    public Object Content
    {
        get { return GetValue(ContentProperty); }
        set { SetValue(ContentProperty, value); }
    }

    public static readonly DependencyProperty ContentProperty = DependencyProperty.Register(
                        "Content", typeof(Object),
                    typeof(CustomButton), 
                        new FrameworkPropertyMetadata(new PropertyChangedCallback(ChangeContent))
    );

    private static void ChangeContent(DependencyObject source, DependencyPropertyChangedEventArgs e)
    {
        (source as CustomButton).UpdateContent(e.NewValue);
    }

    private void UpdateContent(Object sel)
    {
        _button.Content = sel;
    }
    #endregion
}

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

public MyThirdPartyButton InternalControl
{
    get { return _button; }
    set
    {
        if (_button != value)
        {
            this.RemoveVisualChild(_button);
            _button = value;
            this.AddVisualChild(_button);
        }
    }
}

Полученный XAML будет выглядеть так:

<my:CustomButton>
<my:CustomButton.InternalControl>
    <thirdparty:MyThirdPartyButton Content="ClickMe" />
</my:CustomButton.InternalControl>

И то, что я ищу, выглядит примерно так:

<my:CustomButton>
<my:CustomButton.InternalControl Content="ClickMe" />

Но (с кодом, который у меня есть) невозможно добавить атрибуты к InternalControl ...

Есть идеи / предложения?

Большое спасибо,

- Роберт

1 Ответ

2 голосов
/ 13 мая 2010

Система анимации WPF имеет возможность устанавливать под-свойства объектов, а синтаксический анализатор XAML - нет.

Два обходных пути:

  1. В установщике свойств InternalControl возьмите переданное значение и выполните итерацию по его свойствам DependencyProperties, копируя их в ваш фактический InternalControl.
  2. Использование события сборки для программного создания вложенных свойств для всех свойств внутреннего контроля.

Я объясню каждый из них по очереди.

Установка свойств с помощью установщика свойств

Это решение не приведет к упрощенному синтаксису, который вы желаете, но оно простое в реализации и, вероятно, решит основную проблему, состоящую в том, как объединить значения, установленные в вашем контроле контейнера, со значениями, установленными во внутреннем контроле.

Для этого решения вы продолжаете использовать XAML, который вам не понравился:

<my:CustomButton Something="Abc">
  <my:CustomButton.InternalControl> 
    <thirdparty:MyThirdPartyButton Content="ClickMe" /> 
  </my:CustomButton.InternalControl> 

но на самом деле вы не заменяете свой InternalControl.

Для этого установщик вашего InternalControl будет:

public InternalControl InternalControl
{
  get { return _internalControl; }
  set
  {
    var enumerator = value.GetLocalValueEnumerator();
    while(enumerator.MoveNext())
    {
      var entry = enumerator.Current as LocalValueEntry;
      _internalControl.SetValue(entry.Property, entry.Value);
    }
  }
}

Вам может потребоваться дополнительная логика для исключения DP, которые не видны публично или которые установлены по умолчанию. На самом деле это легко сделать, создав фиктивный объект в статическом конструкторе и составив список DP, которые по умолчанию имеют локальные значения.

Использование события сборки для создания вложенных свойств

Это решение позволяет писать очень симпатичный XAML:

<my:CustomButton Something="Abc"
                 my:ThirdPartyButtonProperty.Content="ClickMe" />

Реализация заключается в автоматическом создании класса ThirdPartyButtonProperty в событии сборки. Событие сборки будет использовать CodeDOM для создания вложенных свойств для каждого свойства, объявленного в ThirdPartyButton, которое еще не отражено в CustomButton. В каждом случае PropertyChangedCallback для присоединенного свойства будет копировать значение в соответствующее свойство InternalControl:

 public class ThirdPartyButtonProperty
 {
   public static object GetContent(...
   public static void SetContent(...
   public static readonly DependencyProperty ContentProperty = DependencyProperty.RegisterAttached("Content", typeof(object), typeof(ThirdPartyButtonProperty), new PropertyMetadata
   {
     PropertyChangedCallback = (obj, e) =>
     {
       ((CustomButton)obj).InternalControl.Content = (object)e.NewValue;
     }
   });
 }

Эта часть реализации проста: сложная часть создает задачу MSBuild, ссылается на нее из вашего .csproj и упорядочивает ее так, чтобы она выполнялась после прекомпиляции my: CustomButton, чтобы она могла видеть, какие дополнительные свойства необходимы добавить.

...