Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF: How to accept both string and FrameworkElement in dependency property (like wpf Label does)?

I am creating a custom WPF control that should have several content slots. I'd like the user to be able to use either string, or a FrameworkElement as a value of property, for example:

<!-- MyHeading is a string -->
<MyControl MyHeading="Hello World" />

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

I know that WPF ContentControl does this (accepts both strings and other elements), and I know that it has something to do with TypeConverter attribute (partially explained here), but I tried to look at ContentControl, Label, TextBlock and other controls in Reflector, and didn't find any TypeConverter atrribute there, and googling didn't help.

I first tried to implemet it like this, but it obviously doesn't know about how to convert string to FrameworkElement, and throws exception during control's initialization:

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()));

Then I tried to hack it like this:

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));

but it is pretty ugly and still doesn't instantiate :(.

Anyone knows how to do it? Thanks for your time!

like image 257
Tomáš Kafka Avatar asked Dec 29 '25 02:12

Tomáš Kafka


2 Answers

The DependencyProperty should be of type Object. The magic happens in the when you bind the property as the Content for a ContentPresenter. You should also look into the ContentSource property if you want to handle Templating and StringFormatting properly.

like image 200
Bryan Anderson Avatar answered Dec 31 '25 18:12

Bryan Anderson


As Bryan said just use object as your type. When WPF encounters a non-frameworkelement it will (assuming there is no DataTemplate to apply) call the object's ToString() method and use the text as the content. So not only can you use string, but also DateTime, Enum's, whatever.

Also, you should consider deriving from HeaderedContentControl if your control has both a header and main content. Then you don't need to implement either of those two content properties and you'll get all the bells and whistles for free such as data templating.

like image 23
Josh Avatar answered Dec 31 '25 18:12

Josh