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.