Я пытаюсь создать 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
Система анимации WPF имеет возможность устанавливать подсвойства объектов, но синтаксический анализатор XAML этого не делает.
Два обходных пути:
Я объясню каждый из них по очереди.
Установка свойств с помощью средства задания свойств
Это решение не приведет к желаемому упрощенному синтаксису, но его легко реализовать и, вероятно, решит основную проблему: как объединить значения, установленные в элементе управления контейнера, с значения, установленные на внутреннем контроле.
Для этого решения вы продолжаете использовать 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, чтобы он мог видеть, какие дополнительные свойства ему нужно добавить.