0

I am trying to create a very flexible custom control. The flexibility that I'm trying to achieve to be able to bind UserControl to the ExpanderContent Dependency property, code behind snippet:

public partial class ChartBar : UserControl
{
    public UIElement ExpanderContent
    {
        get { return (UIElement)GetValue(ExpanderContentProperty); }
        set { SetValue(ExpanderContentProperty, value); }
    }

    // Using a DependencyProperty as the backing store for ExpanderContent.  This enables animation, styling, binding, etc...
    public static readonly DependencyProperty ExpanderContentProperty =
        DependencyProperty.Register("ExpanderContent", typeof(UIElement), typeof(ChartBar), new PropertyMetadata(null, OnExpanderContentChanged));

    private static void OnExpanderContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        //throw new NotImplementedException();
    }
    .
    .
    .

I have tried using a ContentPresenter in the XAML but it doesn't work. I obviously can fill with buttons and it works but this defeats the dynamic content via binding.

<Expander x:Name="expander" Header="" VerticalAlignment="Top" d:LayoutOverrides="Width" Style="{DynamicResource ExpanderStyle1}">
    <ContentPresenter Content="{Binding ExpanderContent, ElementName=TestControlWithContent}" />
    <!--<WrapPanel HorizontalAlignment="Center" >
        <Button Content="A" Style="{DynamicResource ButtonStyle1}" />
        <Button Content="B" Style="{DynamicResource ButtonStyle1}" />
        <Button Content="C" Style="{DynamicResource ButtonStyle1}" />
        <Button Content="D" Style="{DynamicResource ButtonStyle1}" />
        <Button Content="E" Style="{DynamicResource ButtonStyle1}" />
        <Button Content="F" Style="{DynamicResource ButtonStyle1}" />
    </WrapPanel>-->
</Expander>

What's even more confusing is I can do

// ChartBarParent is the name of the custom control set in XAML
ChartBarParent.Content = new TestControlWithContent();

and it works as well as fires the callback.

Ultimately, is UIElement in a dependency property and using a ContentPresenter the right way to do this?

Guru Stron
  • 102,774
  • 10
  • 95
  • 132
Jason
  • 3
  • 5
  • 1
    [How to add content to user control](http://stackoverflow.com/q/10427133/1997232). – Sinatr Aug 31 '16 at 13:20
  • I've looked at that link but the solution there was to use typeof(object) which is not what I wanted to achieve. I wanted to use UIElement or FrameworkElement. Am I missing something (very possible)? – Jason Aug 31 '16 at 13:44
  • `ContentPresenter` is meant to be used in a `ControlTemplate`, and you populate its content not by binding its `Content` property, but by setting its `ContentSource` property to the name of a property on the templated parent (`ContentSource` has a default value of `"Content"`, which is why you can often drop one in with no attributes and it magically Does What You Mean). If that specific usage doesn't apply to what you're doing, do as @ibebbs suggests and use `ContentControl` instead. – 15ee8f99-57ff-4f92-890c-b56153 Aug 31 '16 at 13:44
  • Also, `ExpanderContent` should be of type `Object`, same as `ContentControl.Content`, `ContentPresenter.Content`. or whatever. – 15ee8f99-57ff-4f92-890c-b56153 Aug 31 '16 at 13:51
  • Ok, if I change it's type to object, when I bind a UserControl to it, will WPF cast it correctly for me? – Jason Aug 31 '16 at 13:54
  • *Bind* a UserControl to it? If it's the original parent of the UserControl, you don't need binding (and it'll work fine; WPF is all about the actual runtime type of control content); if it's not the original parent, I think you're going to get into trouble trying to reparent the UserControl. – 15ee8f99-57ff-4f92-890c-b56153 Aug 31 '16 at 13:58
  • By bind to it I mean this is a repurposed expander where I want to be able to put custom content into the expanded area. So depending on what it resides on top of, it's expanded content will need to be changed. I.E. on a OHLC chart, it will need drawing tool buttons, on a RSI indicator window, it'll need period settings. So I'll use this custom expander multiple times in an app to host different content. – Jason Aug 31 '16 at 14:07
  • Can you show me the code, C# or XAML, that puts content in one instance of this control? – 15ee8f99-57ff-4f92-890c-b56153 Aug 31 '16 at 14:08
  • Let me get on my dev machine this evening and I'll post up some more stuff which will help illustrate. – Jason Aug 31 '16 at 14:27
  • See if this link works for you to join a conversation: http://chat.stackoverflow.com/rooms/122315/how-to-bind-usercontrol-to-content-of-custom-control – 15ee8f99-57ff-4f92-890c-b56153 Aug 31 '16 at 14:27
  • No, minimum 20 reps required to join a chat room. I will post more this evening. – Jason Aug 31 '16 at 14:36

2 Answers2

1

Try changing the ContentPresenter to a ContentControl.

Furthermore, you could wrap the UserControl in a DataTemplate and set it as the ContentControl.ContentTemplate allowing you to flow data context through the ContentControl.Content property.

ibebbs
  • 1,963
  • 2
  • 13
  • 20
  • I'll give that a try. Thanks. – Jason Aug 31 '16 at 13:40
  • It didn't matter if I used `ContentPresenter` or `ContentControl` I could get the binding to work with either but neither would display the bound content. – Jason Aug 31 '16 at 18:58
1

Here's how I would do this. It relies on the SecondaryContent being either UI stuff (like the second example in "Usage" below), or else a viewmodel with an implicit DataTemplate. I could easily add a SecondaryDataTemplateSelector property to give the consumer more explicit control over how that templating happens.

ChartBar.cs

public class ChartBar : ContentControl
{
    static ChartBar()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(ChartBar), 
            new FrameworkPropertyMetadata(typeof(ChartBar)));
    }

    //  Rather than ExpanderContent, we're inheriting ContentControl.Content for the 
    //  main control content. 

    #region SecondaryContent Property
    public Object SecondaryContent
    {
        get { return (Object)GetValue(SecondaryContentProperty); }
        set { SetValue(SecondaryContentProperty, value); }
    }

    public static readonly DependencyProperty SecondaryContentProperty =
        DependencyProperty.Register("SecondaryContent", typeof(Object), typeof(ChartBar),
            new PropertyMetadata(null));
    #endregion SecondaryContent Property

    #region IsExpanded Property
    //  This is optional. I just know I'd end up wanting it. 
    public bool IsExpanded
    {
        get { return (bool)GetValue(IsExpandedProperty); }
        set { SetValue(IsExpandedProperty, value); }
    }

    public static readonly DependencyProperty IsExpandedProperty =
        DependencyProperty.Register("IsExpanded", typeof(bool), typeof(ChartBar),
            new PropertyMetadata(false));
    #endregion IsExpanded Property
}

Themes/Generic.xaml, or else App.xaml, within <Application.Resources>, or some other .xaml resource dictionary included in one or the other.

<ControlTemplate x:Key="ChartBarDefaultTemplate" TargetType="local:ChartBar">
    <!-- 
    Use Binding/RelativeSource TemplatedParent on IsExpanded so it updates both ways, 
    or remove that attribute/binding if you're not bothering with the IsExpanded DP.
    -->
    <Expander 
        x:Name="expander" 
        Header="" 
        VerticalAlignment="Top" 
        Style="{DynamicResource ExpanderStyle1}"
        IsExpanded="{Binding IsExpanded, RelativeSource={RelativeSource TemplatedParent}}"
        >
        <StackPanel Orientation="Vertical">
            <ContentPresenter />
            <ContentControl 
                Content="{TemplateBinding SecondaryContent}" 
                />
        </StackPanel>
    </Expander>
</ControlTemplate>

<Style TargetType="local:ChartBar">
    <Setter 
        Property="Template" 
        Value="{StaticResource ChartBarDefaultTemplate}" 
        />
</Style>

Usage:

<StackPanel Orientation="Vertical" >
    <!-- Control content -->
    <local:ChartBar SecondaryContent="Secondary Content One">
        <StackPanel Orientation="Vertical">
            <Label>Chart Bar</Label>
            <Ellipse Height="30" Width="60" Fill="GreenYellow" Opacity="0.2" />
            <Label>Other stuff, etc. etc.</Label>
        </StackPanel>
    </local:ChartBar>

    <!-- Templated viewmodel content -->
    <local:ChartBar Content="{Binding RandomViewModelProperty}" IsExpanded="True">
        <local:ChartBar.ContentTemplate>
            <DataTemplate>
                <Label Background="Beige" Content="{Binding}" Margin="20" />
            </DataTemplate>
        </local:ChartBar.ContentTemplate>
        <local:ChartBar.SecondaryContent>
            <ComboBox>
                <TextBlock Text="One" />
                <TextBlock Text="Two" />
                <TextBlock Text="Three" />
            </ComboBox>
        </local:ChartBar.SecondaryContent>
    </local:ChartBar>
</StackPanel>
  • Ok. Option A is much more of what I'm trying to achieve. The buttons A-F were simply placeholders to ensure I had the expander working correctly and those placeholders will be replaced in your "usage" case with a `UserControl` (a view with a corresponding viewmodel). The empty `` is my concern. – Jason Aug 31 '16 at 14:46
  • @Jason Is that secondary view/viewmodel content the same every time, or would you parameterize that as well? – 15ee8f99-57ff-4f92-890c-b56153 Aug 31 '16 at 14:51
  • The view model will be resolved dynamically via IoC based on what this custom expander is attached to (OHLC chart, indicator pane, etc). This is why I wanted the expander content to be as flexible as possible since it will host different user controls with varying content – Jason Aug 31 '16 at 14:54
  • @Jason OK, so that part is a ContentControl in the template with its Content bound to a separate DependencyProperty of ChartBar. I would want if at all possible to leave the view there up to implicit templating, or maybe a DataTemplateSelector. How are you resolving that viewmodel, internally to the control or externally? – 15ee8f99-57ff-4f92-890c-b56153 Aug 31 '16 at 14:57
  • Yes, now I think we're talking the same and understand each other. Everything will be handled externally to this custom chart bar control. It simply needs to host whatever I send to it which will be UI stuff that will have its business logic in its own view model. – Jason Aug 31 '16 at 15:00
  • This didn't quite work. In the expander, `` does bind correctly. Then in usage walking up the tree with find ancestor with `ExpanderContent="{Binding Mode=OneWay, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type view:TestUserControl}}}"` the binding doesn't work. However, setting it directly with ` ` does. However, even when the binding is working correctly, the content is never shown. – Jason Aug 31 '16 at 18:54
  • I've verified that the expander is working properly by hard coding content into it (buttons A-F) and it will expand and contract correctly. So the `UserControl` is getting populated but isn't able to be shown....??? – Jason Aug 31 '16 at 18:57
  • @Jason My code works perfectly. I tested it. I have no idea what changes you made to it, so I can't help you with those. – 15ee8f99-57ff-4f92-890c-b56153 Aug 31 '16 at 19:00
  • Ok, I'll go back and make sure I didn't miss something. Thanks for the help @Ed Plunkett – Jason Aug 31 '16 at 19:01
  • Yes, there were some subtleties that I missed. Thank you for your help. – Jason Aug 31 '16 at 20:12
  • I was looking for placing a custom content inside a usercontrol and the above solution works with the SecondaryContent Dependency property. – Hariprasad Jayprakash May 26 '23 at 04:40