WPF Overlays or better Adorner

Introduction

What is an Adorner? An Adorner can be used to draw on top of another element. For example imagine that you have an application that shows data in a ListView. When the user enters a search you want to show a loading status that disables the UI (something like the screen shot below)

Loading Adorner

In such a scenario an adorner comes very handy because you can basically draw a shade over the UI and maybe put in some text… for more info click here.

The Adorner class

In WPF to build an adorner is relatively simple ( well if I can do it anyone can 😀 ). The first thing you will need to do is to create a class that inherits from the Adorner class. The Adorner class constructor accepts a parameter of type UIElement which is the UIElement that will be adorned i.e the adorner will be rendered on top of this element. So your constructor for the Custom Adorner would look something like this

public LoadingAdorner(Implement adornerElement)
            : base(adornerElement)
{ }

Now that we sub classed the Adorner class and we have our code compiling we can start drawing. To draw you have to override a method from the Adorner class called OnRender. This method takes a DrawingContext as parameter which you can use to draw whatever you like. Let create a small example, let’s draw a Rectangle 🙂

protected override void OnRender(
    System.Windows.Media.DrawingContext drawingContext)
{
    drawingContext.DrawRectangle(Brushes.Blue, new Pen(Brushes.Red, 1), 
                new Rect(new Point(0,0), DesiredSize));
    base.OnRender(drawingContext);
}

So the code above will draw a blue Rectangle with a Red border.

Using the Custom Adorner


To use the custom adorner that we have created you will need to add it to an AdornerLayer. You can do this by calling the static method GetAdornerLayer of the AdornerLayer class. This method will walk up the VisualTree and potentially find an AdornerLayer which can be then used to render adorners. Once you have an instance of the AdornerLayer you can call the Add method and pass an instance of your custom adorner class. Once you add your Custom Adorner in the AdornerLayer, it will be rendered inside that AdornerLayer without disturbing your UIElement. The code to do this should look similar to this

AdornerLayer parentAdorner = AdornerLayer.GetAdornerLayer(mainPane);
parentAdorner.Add(new LoadingAdorner(mainPane));

Note: If the AdornerLayer.GetAdornerLayer is returning you null it means that you have no AdornerLayer in your VisualTree. You can add an AdornerLayer by using the AdornerDecorator in your XAML. Something like this.

<AdornerDecorator>
    <DockPanel Name="mainPane">
       
    </DockPanel>
</AdornerDecorator>

Some other useful tips


If you want to render a UIElement inside an adorner you will have to communicate this to the layout system so that it knows that it needs to layout the element. Usually you do not do such a thing in an adorner but still you can find this very useful. I used this when I was creating drag and drop for listboxes.

An adorner is also affected by Transforms. By this I mean, if you have a Layout/Render transform on the element being adorned, for example a ScaleTrasform, the adorner will be transformed as well. I encountered this while developing Jasema v2

The Demo Application


I created a small Demo application which is available for download here. The application basically has a relatively simple user interface. Basically I created a Listview that is binding to some data and a search box so that the user can filter that data by name. When the user clicks search, the user interface draws an Adorner signaling a loading status (I created a small hack so that I simulate some workload by using Thread.Sleep(2000). Do not do that in your real life application! ). In this demo I used the MVC design pattern by using WPF commands which is really interesting. For more info on MVC and WPF visit this post.

Download the demo project

GridViewColumn DisplayMemberBinding vs CellTemplate

GridViewColumn has 2 different ways how one can specify the content of a cell. One can use the DisplayMemberBinding or also a CellTemplate. DisplayMemberBinding is used when you just want to show some text inside your GridViewColumn. You can use this property as follows

<ListView>
    <ListView.View>
         <GridView>
             <GridViewColumn DisplayMemberBinding="{Binding Name}"
                                    Header="Name"/>
         </GridView>
    </ListView.View>
</ListView>

So basically all you have to do is specify a binding to the property of the object being bound. In reality the DisplayMemberBinding will create a TextBlock and bind the Text property of the TextBlock to the binding that you specify. Yet if you need more than just text in a TextBlock then DisplayMemberBinding is not what you should be looking for. For such scenario one should use the CellTemplate.

The CellTemplate property of the GridViewColumn allows you to enter a DataTemplate where you can put in whatever you like as controls to be rendered by the GridView. Let me give you an example of how one can use the CellTemplate to do something more than Text

<ListView>
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Image">
                <GridViewColumn.CellTemplate>
                    <DataTemplate>
                        <Image Source="{Binding Image}"/>
                    </DataTemplate>
                </GridViewColumn.CellTemplate>
            </GridViewColumn>
        </GridView>
    </ListView.View>
</ListView>

So in the above example the ListView is now rendering an image where the source of the image is set via a binding to a property of the object being bound to. This gives you much more flexibility in terms of what you can do inside a Cell. For even more advanced scenarios where you have to decide how to render the cell according to the object being bound you can also use the CellTemplateSelector.

Final and most important point about these 2 properties…
DO NOT USE THEM TOGETHER!

If you set the DisplayMemberBinding it is useless to set the CellTemplate because the GridView will only use the DisplayMemberBinding and ignore the CellTemplate. So always make sure that you only set one of these properties and not both at the same time otherwise you will not get the results that you were hoping for.

Want to read some more on ListView? Click here