【问题标题】:Wrapped WPF Control包装的 WPF 控件
【发布时间】:2010-05-18 22:37:28
【问题描述】:

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

有什么想法/建议吗?

非常感谢,

-- 罗伯特

【问题讨论】:

    标签: c# wpf custom-controls


    【解决方案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 的预编译之后运行,这样它就可以看到它需要哪些附加属性添加。

    【讨论】:

    • 谢谢雷。第二个解决方法就像魅力一样。是的,生成所有这些属性会很棘手......第一个解决方法不能直接在 setter 中工作。这可能是因为此时对象尚未初始化。无论如何,如果您在 GetLocalValueEnumerator() 初始化 (value.Initialized += HANDLER) 之后迭代它,它就可以工作。如果我错了,请纠正我。我仍然需要检查“InternalControl”属性上的绑定是否正常工作。实际上,我必须检查: -Bindings -Styles -Targeted Styles -ControlTemplates -ItemTemplates -Validations -???
    猜你喜欢
    • 1970-01-01
    • 2012-04-28
    • 2010-12-07
    • 1970-01-01
    • 1970-01-01
    • 2014-10-20
    • 1970-01-01
    • 1970-01-01
    • 2010-11-21
    相关资源
    最近更新 更多