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?

11
  • 1
    How to add content to user control. Commented Aug 31, 2016 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)? Commented Aug 31, 2016 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. Commented Aug 31, 2016 at 13:44
  • Also, ExpanderContent should be of type Object, same as ContentControl.Content, ContentPresenter.Content. or whatever. Commented Aug 31, 2016 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? Commented Aug 31, 2016 at 13:54

2 Answers 2

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> 
Sign up to request clarification or add additional context in comments.

12 Comments

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 <ContentPresenter /> is my concern.
@Jason Is that secondary view/viewmodel content the same every time, or would you parameterize that as well?
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 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?
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.
|
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.

2 Comments

I'll give that a try. Thanks.
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.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.