WPF:如何在依赖属性中接受字符串和FrameworkElement(就像WPF标签一样)?

3
我正在创建一个自定义的WPF控件,它应该有几个内容插槽。 我希望用户能够使用字符串或FrameworkElement作为属性值,例如:
<!-- MyHeading is a string -->
<MyControl MyHeading="Hello World" />

<MyControl>
    <!-- MyHeading is a FrameworkElement -->
    <MyControl.MyHeading>
        <Expander Header="Hello">
            World
        </Expander>
    </MyControl.MyHeading>
</MyControl>

我知道WPF ContentControl可以实现这一点(接受字符串和其他元素),我知道它与TypeConverter属性有关(部分解释在这里),但是我尝试查看Reflector中的ContentControl、Label、TextBlock和其他控件,没有找到任何TypeConverter属性,并且谷歌搜索也没有帮助。
我最初尝试按照以下方式实现,但很明显它不知道如何将字符串转换为FrameworkElement,在控件初始化期间抛出异常:
public FrameworkElement Heading
{
    get { return (FrameworkElement)GetValue(HeadingProperty); }
    set { SetValue(HeadingProperty, value); }
}

// Using a DependencyProperty as the backing store for Heading.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty HeadingProperty =
    DependencyProperty.Register("Heading", typeof(object), typeof(DialogControl), new UIPropertyMetadata(new FrameworkElement()));

然后我尝试这样黑它:
public object Heading
{
    get { return (object)GetValue(HeadingProperty); }
    set
    {
        if (value is string)
        {
            var tb = new TextBlock();
            tb.Text = (string) value;
            tb.FontSize = 20;
            SetValue(HeadingProperty, tb);
        }
        else if (value is FrameworkElement)
        {
            SetValue(HeadingProperty, value);
        } else 
            throw new ArgumentOutOfRangeException("Heading can take only string or FrameworkElement.");
    }
}

// Using a DependencyProperty as the backing store for Heading.  This enables animation, styling, binding, etc...
public static readonly DependencyProperty HeadingProperty =
    DependencyProperty.Register("Heading", typeof(object), typeof(DialogControl), new UIPropertyMetadata(null));

但它相当丑陋,仍无法实例化 :(。

有人知道如何做吗?感谢您的时间!

3个回答

3

DependencyProperty 应该是 Object 类型。当您将属性绑定为 ContentPresenter 的 Content 时,会出现神奇的效果。如果您想正确处理模板和字符串格式化,还应该查看 ContentSource 属性。


谢谢您澄清转换发生在ContentPresenter中,这对我非常有价值。ContentSource的链接也帮助我正确地完成了它,谢谢! - Tomáš Kafka
@Bryan Anderson,你知道如何在设计器中做相同的事情吗?我可以在XAML中将内容设置为字符串,但如果我想在设计器中这样做,它会变灰。 - Stefan Vasiljevic
@StefanVasiljevic 你是想把名为“Content”的属性设置为字符串类型吗?这是一个非常糟糕的想法,也是一个常见的错误,因为它违反了XAML约定和框架的其他部分。如果是这种情况,我猜设计师正在防止你犯错。尝试将属性名称改为“Text”。 - Bryan Anderson
@BryanAnderson 不,我不是在尝试那样做。 :) 我只想让它的行为像标签一样。但内容可以是任何东西,而不仅仅是一个字符串。 - Stefan Vasiljevic

1

正如Bryan所说,只需将对象用作您的类型即可。当WPF遇到非框架元素时(假设没有要应用的DataTemplate),它将调用对象的ToString()方法并使用文本作为内容。因此,您不仅可以使用字符串,还可以使用DateTime、Enum等。

此外,如果您的控件既有标题又有主要内容,则应考虑从HeaderedContentControl派生。然后,您无需实现这两个内容属性,而且您将免费获得所有铃铛和口哨,例如数据模板。


感谢您的澄清,是的,在我的情况下,从HeaderedControl派生就足够了,但我想知道如何自己做(例如当向HeaderedControl添加第三个“内容插槽”时)。 - Tomáš Kafka

0

就像其他人所说的那样,将类型设置为对象。要进行类型检查,请在依赖属性上使用验证回调。然后检查有效类型。

public object Header
    {
        get { return (object)GetValue(HeadingProperty); }
        set { SetValue(HeadingProperty, value); }
    }

    // Using a DependencyProperty as the backing store for Value.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty HeadingProperty = DependencyProperty.Register(
        "Heading",
        typeof(object),
        typeof(DialogControl),
        new UIPropertyMetadata(null),
        new ValidateValueCallback(Heading_Validation)
        );

    private static bool Heading_Validation(object source)
    {
        return source is string||
            source is FrameworkElement ||
            source == null;
    }

在赋值之前,这将检查传递的对象是否为String类型、FrameworkElement类型或者为空。

尽情享受吧!


但是这样你就失去了支持数据模板或仍可打印的非字符串类型的能力。 - Josh
确实,我从我的项目中复制了这段代码,其中需要绘制特定的对象。不过你是正确的,对于一个标题来说,这可能不是最好的实现方式。 - Alastair Pitts

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接