Creating Adorner content in XAML


I’ve been working on a problem of having a “popup” control in my project.  For various reasons the Popup class isn’t working for this scenario, because the scenario is outside the designed scope of Popup.


So I’ve been looking at the AdornerLayer as a way to get a popup effect.  One of the things I noticed early was that most adorner samples show override OnRender and provide their own visuals.  I want to actually reuse existing controls.  Luckily I found Josh Smith’s UIElementAdorner (http://blogs.infragistics.com/blogs/joshs/archive/2008/09/12/adorning-xamdatagrid-with-a-popup-editor.aspx and http://shevaspace.blogspot.com/2007_02_01_archive.html).


Very helpful and many thanks to Josh & friends for figuring that out. 


However I don’t want to create the controls and add the adorner to the AdornerLayer all in code.  I’d much rather do this in XAML, which has the added advantage in our project of allowing retemplating to change the content of the adorner.


So after playing around for a while I got this to work.  I call the result ContentAdorner, after ContentControl.


The adorner defines an attached property called AdornerContentTemplateProperty, which is of type DataTemplate.  In XAML one sets the value for this property with the DataTemplate of the content one wants in the AdornerLayer, like so:


    <Grid >
        <local:ContentAdorner.AdornerContentTemplate>
            <DataTemplate>
                <Border BorderBrush=”Red” BorderThickness=”2″
                        VerticalAlignment=”Top”
                        HorizontalAlignment=”Left”>
                    <Grid>
                        <Grid.RowDefinitions>
                            <RowDefinition Height=”Auto”/>
                            <RowDefinition Height=”Auto”/>
                        </Grid.RowDefinitions>
                        <Grid.ColumnDefinitions>
                            <ColumnDefinition Width=”Auto”/>
                            <ColumnDefinition Width=”Auto”/>
                        </Grid.ColumnDefinitions>
                        <TextBlock Text=”It works” />
                        <Button Margin=”8″ Grid.Column=”1″ Grid.Row=”0″>Adorner Button</Button>
                        <TextBox Grid.Column=”0″ Grid.ColumnSpan=”2″ Grid.Row=”1″ />
                    </Grid>
                </Border>
            </DataTemplate>
        </local:ContentAdorner.AdornerContentTemplate>
        <Button>One</Button>
    </Grid>


The change handler for the AdornerContentTemplate property creates an instance of ContentAdorner and adds it to the AdornerLayer.  The adorner instance has 1 direct child, which is a ContentControl.  The ContentControl’s ContentTemplate is bound to the value of the AdornerContentTemplate property.


When the ContentControl is loaded it processes the template and the controls appear.


One issue I discovered was that the property is set before the target control is loaded and this causes GetAdornerLayer to return null.  I addressed this by hooking the Loaded event and creating the adorner at that time.


An obvious drawback to this is that one doesn’t have direct access to the Adorner instance, and therefore can’t modify any of the properties on the adorner.  Another is that you are limited to just defining 1 adorner.  I hope to follow up with another article which tackles these issues as I take this idea and make real production code from it.


Attached is the full source for the ContentAdorner.   Remember this is a quick and dirty proof-of-concept and not meant to be production quality.


Enjoy

ContentAdorner.cs

Comments (5)

  1. Katharina says:

    Hi,

    thank you, I found this class very helpful and have a solution how to get a reference to the created adorner by using a static function like this:

    public static Adorner CreateContentAdorner(DependencyObject element, DataTemplate value)

    {

     ContentAdorner.SetAdornerContentTemplate(element, value);

     AdornerLayer al = AdornerLayer.GetAdornerLayer(element as Visual);

     Adorner[] ads = al.GetAdorners(element as UIElement);

     return ads.Last();

    }

    Katharina

  2. DrPizza says:

    OK, so I’ve been playing around with this and it seems somewhat imperfect to me.

    The template is never applied to the adorner itself (Adorner doesn’t plumb itself into FrameworkElement’s template mechanism), which means that the adorner has limited (at best) access to generated content. Events like ContentAdorner.Loaded fire before the template gets applied (and the content generated).

  3. MSDNArchive says:

    DrPizza, I’m not sure exactly what you mean.  

    Its been a while since I wrote this post, so I’m trying to remember my thinking to help figure out what you mean.

    I’m guessing you might mean that data bindings don’t work because it doesn’t inherit the same data context.  If that is right then one should be able to fix that by setting the Content property of the inner Content control to whatever you want.

    In my projects I never had to get that working, or deal with needing multiple adorners – so I never followed up with getting this working general purpose.

    Now days I’m working w/ Silverlight, which doesn’t have an AdornerLayer.

  4. Bharat says:

    can you please provide the complete sample code.

    Bharat Mane