Перенесенное управление WPF

Я пытаюсь создать 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...

Какие-либо идеи/предложения?

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

- Robert

1
задан Yannick Motton 18 May 2010 в 14:37
поделиться

1 ответ

Система анимации 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, чтобы он мог видеть, какие дополнительные свойства ему нужно добавить.

2
ответ дан 3 September 2019 в 00:36
поделиться
Другие вопросы по тегам:

Похожие вопросы: