none
Using ContentPresenter Properly

    Question

  • I have three dependency properties (Header,Footer,Content) on a UserControl that I want to represent as user-configurable content areas. 

    This approach makes sense to me, but nothing is getting bound.  What am I doing wrong?

        <StackPanel>
            <Border Background="Blue">
                <ContentPresenter ContentSource="Header"  >
                    <ContentPresenter.ContentTemplate >
                        <DataTemplate>
                            <TextBlock Text="{Binding}" />
                                                </DataTemplate>
                    </ContentPresenter.ContentTemplate>
                </ContentPresenter>
            </Border>
       </StackPanel>
    Thursday, October 09, 2008 6:04 PM

Answers

  • DutchMarcel said:

    The rule is: A name has to be unique within a certain NameScope! Page, Window, Styles and (Data/Control)Templates all maintain their own NameScope, so the same names can be used multiple times, but not within the same NameScope.
    There's also a little trick you have to know if you are adding named elements from code instead of xaml.

    Marcel,

    Thanks for your help.  Finally solved the problem but in a different way. Still didn't figure out the namescope issue.  Here is what I did:

    NameScope.SetNameScope(this, new NameScope());
    NameScope.SetNameScope(ContentContent, new NameScope());

    Here is the error:

     Error    21    Cannot set Name attribute value 'HELP' on element 'Button'. 'Button' is under the scope of element  'HeaderFooterPanel', which already had a name registered when it was defined in another scope. 

    In all honesty, after reading the articles I still have no clue *how* I am actually supposed to use namescopes, so I have thown in the towel and solved it another way

    My Solution:

    Derive class from Control and create a control template using templatebinding.  Instead of naming the root element and using the ElementName markup extension, I used the TemplateBinding markup extension.  
    Ex:  Height="{Binding  Path=FooterHeight, RelativeSource={RelativeSource TemplatedParent}}"

    The control template seems like the better soution since there is no additional code need to deal with the namescope issue.

    • Marked as answer by Frankenspank Tuesday, October 14, 2008 2:42 PM
    Tuesday, October 14, 2008 2:41 PM

All replies

  • Ok,

    Is what I am trying to accomplish here really all that difficult?

    Here is how I want to use it:

    <MyControl>

      <MyControl.Header>You can put whatever you want in here!!!!<MyControl.Header>
         <Button>Click me!!!!</Button>
      <MyControl.Footer><Button>Footer</Button></MyControl.Footer>

    </MyControl>
    Thursday, October 09, 2008 7:49 PM
  • For attached properties to work, you need to register a dep property as RegisterAttached.Also give your stackpanel a name and set its datacontext to "this" in your constructor such that the binding knows where to look for header. I may be wrong or might not have got whatyou wanted to do.

     
     public static DependencyProperty HeaderProperty; 
            static UserControl3() 
            { 
                PropertyMetadata HeaderMetaData = new PropertyMetadata("Header"); 
                HeaderProperty = DependencyProperty.RegisterAttached("Header"typeof (string), typeof (UserControl3), 
                                                                     HeaderMetaData); 
            } 
            public static string GetHeader(DependencyObject target) 
            { 
                return (string) target.GetValue(HeaderProperty); 
            } 
            public static void SetHeader(DependencyObject target,string value) 
            { 
                target.SetValue(HeaderProperty,value); 
            } 
     


    Thursday, October 09, 2008 8:54 PM
  • Thanks Buddi,

    I am still getting the same result....I am probably missing something REALLY simple.  As an FYI, I extended an Expander control to include multiple content areas in the header, and I just used dependency props.  It's so strange why this isn't working.


       public object Header
            {
                get {
                     object result=(object)GetValue(HeaderProperty);
                     return "Getttttttttit";// result;
                }
                set {
                    SetValue(HeaderProperty, value); }
            }

            public static string GetHeader(DependencyObject target)
            {
                return (string)target.GetValue(HeaderProperty);
            }
            public static void SetHeader(DependencyObject target, string value)
            {
                target.SetValue(HeaderProperty, value);
            }

            // Using a DependencyProperty as the backing store for HeaderWidget.  This enables animation, styling, binding, etc...
            public static readonly DependencyProperty HeaderProperty =

                DependencyProperty.RegisterAttached("Header", typeof(object), typeof(HeaderFooterPanel), new PropertyMetadata("Header"));


    ----

                <controls:HeaderFooterPanel Header="Test"     BorderBrush="#FF00FF" Width="300" Height="300">
                </controls:HeaderFooterPanel>

                <controls:HeaderFooterPanel   BorderBrush="#FF00FF" Width="300" Height="300">
                    <controls:HeaderFooterPanel.Header>
                        <TextBlock>By the power of greyskull!</TextBlock>
                    </controls:HeaderFooterPanel.Header>
                </controls:HeaderFooterPanel>

    ------


    <UserControl <!--cruft removed for redability -->  Name="Root" >
        <StackPanel Name="Container">
            <Border Name="HeaderBorder" Background="GreenYellow">
                <ContentPresenter Name="HeaderPresenter"  ContentSource="Header">
                    <ContentPresenter.ContentTemplate>
                        <DataTemplate>
                           <TextBlock Text="{Binding}" Background="Beige" />
                        </DataTemplate>                   
                    </ContentPresenter.ContentTemplate>
                </ContentPresenter>
            </Border>
       </StackPanel>
    </UserControl>
    Thursday, October 09, 2008 9:57 PM
  • You can make it work, but it has a nasty catch!
    If you define a UserControl like this:
    <UserControl x:Class="MultiRegionUserControlTest.UserControl1" 
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">  
        <Grid> 
            ....  
        </Grid> 
    </UserControl> 
    and you use it like this:
    <uc:UserControl1 Margin="25" Header="This is my header!" Footer="This is my footer!">  
        <Button Content="This is my content!"/>  
    </uc:UserControl1> 
    you will not see any header or footer, just the button! The reason is that you specified the Button to be the content of your usercontrol, so it will replace anything you defined inside UserControl1.xaml, because that was the original content of the usercontrol.

    You can make a workaround for this using a custom dependency property for the content, just like you need for Header and Footer, and use it like this:
    <uc:UserControl1 Margin="25" Header="This is my header!" Footer="This is my footer!">  
        <uc:UserControl1.CustomContent> 
            <Button Content="This is my content!"/>  
        </uc:UserControl1.CustomContent> 
    </uc:UserControl1> 
    Or, if you add the [ContentProperty("CustomContent")] attribute to the UserControl1 class, you can use it like before:
    <uc:UserControl1 Margin="25" Header="This is my header!" Footer="This is my footer!">  
        <Button Content="This is my content!"/>  
    </uc:UserControl1> 
     

    I uploaded my complete test project here:
    http://cid-027cdb908d14e584.skydrive.live.com/self.aspx/WPFSamples/MultiRegionUserControlTest.zip

    hth,
    Marcel

    • Proposed as answer by chaiguy1337 Friday, August 06, 2010 5:42 PM
    Friday, October 10, 2008 8:57 AM
  • Excellent example  and good explication Marcel.  Works pretty much the way I need it to.  I am going to go with your solution of setting the text right on the content panel. I do however have a few remaining questions.  

    I decided to use a grid as my base control since it just makes sense, now the grid doesn't come with a customcontent area, how would I add one in manually?


    Binding:

    When do I use TemplateBinding, Binding or ContentSource?

    Attribute

    What exactly does that attribute you mentioned above do? Does it "redirect" the ContentProperty dependency property to the CustomContent property  

    Data Template

    My default template for the content did not work - IE the DataTemplate wasn't replaced, and the ToString method of the control I tried to set as the content was just used for the text attribute.

                   <ContentPresenter.ContentTemplate>
                        <DataTemplate>
                           <TextBlock Text="{Binding}" Background="Beige" />
                        </DataTemplate>
                       
                    </ContentPresenter.ContentTemplate>
    Friday, October 10, 2008 3:37 PM
  • Frankenspank said:

    ...
    I decided to use a grid as my base control since it just makes sense, now the grid doesn't come with a customcontent area, how would I add one in manually?
    ...

    Not sure what you mean. My testproject uses a Grid. I just placed a ContentPresenter at the right place inside the Grid.

    Frankenspank said:

    ...
    Binding:

    When do I use TemplateBinding, Binding or ContentSource?
    ...

    Binding can be used anywhere to get a value from somewhere else. Target has to be a dependency property. Source can be a dependency property, a INotifyPropertyChanged-enabled property, or a standard .Net property, but the last one does not provide automatic change notification. More info here:
    http://www.codeproject.com/KB/WPF/GuidedTourWPF_3.aspx

    ContentSource and TemplateBinding are only used inside a ControlTemplate. TemplateBinding is just a simplified notation for a binding to a property on the templated control.

    Frankenspank said:

    ...
    Attribute

    What exactly does that attribute you mentioned above do? Does it "redirect" the ContentProperty dependency property to the CustomContent property  
    ...

    Basically, it tells the xaml parser to put the given content in the specified property. More info here:
    http://msdn.microsoft.com/en-us/library/system.windows.markup.contentpropertyattribute.aspx

    Frankenspank said:

    ...
    Data Template

    My default template for the content did not work - IE the DataTemplate wasn't replaced, and the ToString method of the control I tried to set as the content was just used for the text attribute.

                   <ContentPresenter.ContentTemplate>
                        <DataTemplate>
                           <TextBlock Text="{Binding}" Background="Beige" />
                        </DataTemplate>
                       
                    </ContentPresenter.ContentTemplate>

    That's because {Binding} is referring to the whole object you put in the CustomContent property. You should pick a property. If you put a Button as the CustomContent of the UserControl, you should change the binding to {Binding Content}.

    hth,
    Marcel
    Monday, October 13, 2008 8:54 AM
  • I've got the control looking absolutely fantastic, but there is a big problem.  As soon as I name an attribute, I get a  compile error saying that the name was already set.  All the name attributes on the control are set using x:Name.....
    Monday, October 13, 2008 9:08 PM
  • The rule is: A name has to be unique within a certain NameScope! Page, Window, Styles and (Data/Control)Templates all maintain their own NameScope, so the same names can be used multiple times, but not within the same NameScope.
    There's also a little trick you have to know if you are adding named elements from code instead of xaml.

    I think this article does a good job explaining this all:
    http://marlongrech.wordpress.com/2008/08/02/namescope-my-name-is-marlon-you-know/

    hth,
    Marcel
    Tuesday, October 14, 2008 6:41 AM
  • DutchMarcel said:

    The rule is: A name has to be unique within a certain NameScope! Page, Window, Styles and (Data/Control)Templates all maintain their own NameScope, so the same names can be used multiple times, but not within the same NameScope.
    There's also a little trick you have to know if you are adding named elements from code instead of xaml.

    Marcel,

    Thanks for your help.  Finally solved the problem but in a different way. Still didn't figure out the namescope issue.  Here is what I did:

    NameScope.SetNameScope(this, new NameScope());
    NameScope.SetNameScope(ContentContent, new NameScope());

    Here is the error:

     Error    21    Cannot set Name attribute value 'HELP' on element 'Button'. 'Button' is under the scope of element  'HeaderFooterPanel', which already had a name registered when it was defined in another scope. 

    In all honesty, after reading the articles I still have no clue *how* I am actually supposed to use namescopes, so I have thown in the towel and solved it another way

    My Solution:

    Derive class from Control and create a control template using templatebinding.  Instead of naming the root element and using the ElementName markup extension, I used the TemplateBinding markup extension.  
    Ex:  Height="{Binding  Path=FooterHeight, RelativeSource={RelativeSource TemplatedParent}}"

    The control template seems like the better soution since there is no additional code need to deal with the namescope issue.

    • Marked as answer by Frankenspank Tuesday, October 14, 2008 2:42 PM
    Tuesday, October 14, 2008 2:41 PM
  • Well, you probably should not have created a new NameScope, but just use RegisterName like explained in the last section of Marlon Grech's article.

    But, it's always a good experience to find your own solutions, so good job at that! :-)

    Marcel
    Tuesday, October 14, 2008 3:44 PM
  • DutchMarcel said:

     
    Frankenspank said:

    ...
    Data Template

    My default template for the content did not work - IE the DataTemplate wasn't replaced, and the ToString method of the control I tried to set as the content was just used for the text attribute.

                   <ContentPresenter.ContentTemplate>
                        <DataTemplate>
                           <TextBlock Text="{Binding}" Background="Beige" />
                        </DataTemplate>
                       
                    </ContentPresenter.ContentTemplate>

    That's because {Binding} is referring to the whole object you put in the CustomContent property. You should pick a property. If you put a Button as the CustomContent of the UserControl, you should change the binding to {Binding Content}.

    hth,
    Marcel

    I figured this problem out, but never posted the problem.  It isn't because the binding is referring to the whole object.  In my case, it should be bound to the entire object.   The reason is that you need to define a custom template for the string and a custom template for the object. 

    As usual, the MSDN mentions this in passing and does a lousy job of explaining it.  But essentially, you'll need to create your data templates for each type in a resources block instead of defining them inline. More info can be found here:

    http://msdn.microsoft.com/en-us/library/ms742521.aspx#Styling_DataType


    Tuesday, October 21, 2008 8:10 PM
  • I'm not sure if you misunderstood my explanation or if I do not understand your last post, but I'll have another go at it...
    I created an example below with 2 different ways to use a datatemplate. Maybe it helps to explain.

    First of all, a datatemplate is just the presentation of an (data)object. So if you have a Person object you can create a datatemplate to present a Person in the way that you want.
    For this to work, the DataContext property of a ContentPresenter is set to the very object you want to present. This DataContext is then inherited further down the visual tree, so all the elements in your datatemplate have access to it.

    If you use the '{Binding}' extension like in this datatemplate
    <DataTemplate> 
        <TextBlock Text="{Binding}" Background="Beige" />   
    </DataTemplate> 
    ... you are actually specifying that the Text property should be filled with whatever is in the DataContext of the TextBlock element, which is the whole (Person)object. And since the Text property is of type string, WPF will automatically use the ToString method on the (Person)object for this.

    You can copy/paste my sample directly into Kaxaml or XamlPad. It does not need code behind. It is based on some xml data but the principle stays the same.
    The object that I want to present here with a datatemplate is one 'entry' from a list of tax records.
    For the ListBox I created a datatemplate called 'itemTemplate'. To understand a ListBox you have to know that each item in the list is wrapped in a container (usually ListBoxItem) and that ListBoxItem.DataContext is set to the very item that it contains.
    In my case the datacontext of each ListBoxItem is set to the System.Xml.XmlElement (= 'entry' object) it contains. This makes the datacontext available to every element in my datatemplate. But I have to point each binding to the right property of the object in the datacontext. This total package makes up my presentation of an 'entry' object.

    Since I created the 'itemTemplate' datatemplate as a resource of the Page, I was able to use it again to present the first 'entry' object in the list in a ContentPresenter in the first GroupBox. Just set the content to the object you want to present and specify a datatemplate as the way how it should be presented.

    In the 2nd groupbox I put a ContentPresenter that should also present the 1st 'entry' object, but I defined the datatemplate directly inside the ContentTemplate property. Note that in this way it can't be re-used anywhere else.

    To make a long post even longer, this is my example:

    <Page  
      xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" 
      xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">  
      <Page.Resources> 
        <XmlDataProvider x:Key="xmlData" XPath="/taxrecords">  
           <x:XData> 
              <taxrecords xmlns="">  
                <entry name="Opal Harrison" state="AL" income="$51,466.81" age="27" /> 
                <entry name="Eugene Black" state="FL" income="$13,314.89" age="71" /> 
                <entry name="Opal Chang" state="NC" income="$225,115.15" age="41" /> 
                <entry name="Gary Waters" state="WI" income="$151,788.49" age="39" /> 
                <entry name="Xavier Davis" state="AK" income="$136,344.97" age="66" /> 
                <entry name="Stacy Harrison" state="TX" income="$122,432.82" age="32" /> 
              </taxrecords> 
           </x:XData> 
        </XmlDataProvider> 
          
        <DataTemplate x:Key="itemTemplate">  
          <StackPanel> 
             <TextBlock FontWeight="Bold" Text="{Binding XPath=@name}" /> 
             <StackPanel Orientation="Horizontal">  
                <TextBlock Text="{Binding XPath=@state}" /> 
                <TextBlock Text=", " /> 
                <TextBlock Text="{Binding XPath=@age}" /> 
                <TextBlock Text=", " /> 
                <TextBlock Text="{Binding XPath=@income}" /> 
             </StackPanel> 
          </StackPanel> 
       </DataTemplate> 
     
      </Page.Resources> 
      <Grid>    
        <Grid.RowDefinitions> 
          <RowDefinition/> 
          <RowDefinition Height="Auto"/>  
          <RowDefinition Height="Auto"/>  
        </Grid.RowDefinitions> 
     
        <ListBox Grid.Row="0"   
                 ItemsSource="{Binding Source={StaticResource xmlData}, XPath=entry}" 
                 ItemTemplate="{StaticResource itemTemplate}"/>  
          
        <GroupBox Grid.Row="1" Margin="10" Header="DataTemplate as Page resource (same as for the ListBox)">  
          <ContentPresenter Content="{Binding Source={StaticResource xmlData}, XPath=entry[1]}" 
                            ContentTemplate="{StaticResource itemTemplate}"/>  
        </GroupBox> 
     
        <GroupBox Grid.Row="2" Header="Inline DataTemplate" Margin="10">  
          <ContentPresenter Content="{Binding Source={StaticResource xmlData}, XPath=entry[1]}">  
            <ContentPresenter.ContentTemplate> 
              <DataTemplate> 
                <StackPanel Orientation="Horizontal" TextElement.Foreground="Red">  
                  <TextBlock FontWeight="Bold" Text="{Binding XPath=@name}"/>  
                  <TextBlock Text=", "/>  
                  <TextBlock Text="{Binding XPath=@state}"/>  
                  <TextBlock Text=", "/>  
                  <TextBlock Text="{Binding XPath=@age}"/>  
                  <TextBlock Text=", "/>  
                  <TextBlock Text="{Binding XPath=@income}"/>  
                </StackPanel> 
              </DataTemplate> 
            </ContentPresenter.ContentTemplate> 
          </ContentPresenter> 
        </GroupBox> 
          
      </Grid> 
    </Page> 

    Note: I could have used the Content and ContentTemplate properties of the GroupBox's themselves instead of the additional ContentPresenters, but I thought is was more clear this way.

    hth,
    Marcel
    Wednesday, October 22, 2008 8:12 AM