包装的WPF控件

时间:2010-05-12 15:39:33

标签: c# wpf custom-controls

我正在尝试创建一个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 个答案:

答案 0 :(得分:2)

WPF的动画系统能够设置对象的子属性,但XAML解析器不能。

两个解决方法:

  1. 在InternalControl属性setter中,获取传入的值并遍历其DependencyProperties,将它们复制到实际的InternalControl。
  2. 使用构建事件以编程方式为所有内部控件属性创建附加属性。
  3. 我将依次解释其中的每一个。

    使用属性设置器设置属性

    此解决方案不会产生您希望的简化语法,但它实现起来很简单,并且可能解决主要问题,即如何将容器控件上设置的值与内部控件上设置的值合并。

    对于此解决方案,您继续使用您不喜欢的XAML:

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

    但实际上你最终没有更换你的InternalControl。

    为此,您的InternalControl的setter将是:

    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。实际上,这可以通过在静态构造函数中创建虚拟对象并生成默认情况下具有本地值的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的预编译之后运行,以便它可以看到它需要的其他属性添加。