WPF: Overlay Control

WPF: Overlay Control

8 Comments

If you haven’t had a chance to mess around with an [AdornerDecorator], I highly recommend that you do.  They’re extremely useful because they allow you to overlay WPF controls on top of any other WPF controls.  In WinForms you would have had to use a transparent window acting as an invisible popup to draw over several regions of other controls; it was a pain and generally pretty non-reusable.

The one problem I’ve faced with the supplied Adorner related items is that there isn’t a control in WPF that handles my (and possibly your) most common use-case, which is wanting to just declaratively specify a single piece of content to overlay on top of the child content.  So I took a little time this afternoon to write one for another control I’ve been working on I plan to post about later.

The idea is pretty simple; you start with a ContentControl, change its template to have a AdornerDectorator. Then provide 2 new dependency properties, one for the content to show in the adorner layer and another to control the that content’s visibility.

Here’s what my Overlay class looks like:

[TemplatePart(Name = "PART_OverlayAdorner", Type = typeof(AdornerDecorator))]
public class Overlay : ContentControl
{
    public static readonly DependencyProperty OverlayContentProperty =
        DependencyProperty.Register("OverlayContent", typeof(UIElement), typeof(Overlay),
        new FrameworkPropertyMetadata(new PropertyChangedCallback(OnOverlayContentChanged)));
 
    public static readonly DependencyProperty IsOverlayContentVisibleProperty =
        DependencyProperty.Register("IsOverlayContentVisible", typeof(bool), typeof(Overlay),
        new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsOverlayContentVisibleChanged)));
 
    private UIElementAdorner m_adorner;
 
    static Overlay()
    {
        DefaultStyleKeyProperty.OverrideMetadata(typeof(Overlay), new FrameworkPropertyMetadata(typeof(Overlay)));
    }
 
    [Category("Overlay")]
    public UIElement OverlayContent
    {
        get { return (UIElement)GetValue(OverlayContentProperty); }
        set { SetValue(OverlayContentProperty, value); }
    }
 
    [Category("Overlay")]
    public bool IsOverlayContentVisible
    {
        get { return (bool)GetValue(IsOverlayContentVisibleProperty); }
        set { SetValue(IsOverlayContentVisibleProperty, value); }
    }
 
    private static void OnOverlayContentChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Overlay overlay = d as Overlay;
        if (overlay != null)
        {
            if (overlay.IsOverlayContentVisible)
            {
                overlay.RemoveOverlayContent();
                overlay.AddOverlayContent();
            }
        }
    }
 
    private static void OnIsOverlayContentVisibleChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        Overlay overlay = d as Overlay;
        if (overlay != null)
        {
            if ((bool)e.NewValue)
            {
                overlay.AddOverlayContent();
            }
            else
            {
                overlay.RemoveOverlayContent();
            }
        }
    }
 
    private void AddOverlayContent()
    {
        if (OverlayContent != null)
        {
            m_adorner = new UIElementAdorner(this, OverlayContent);
            m_adorner.Add();
 
            AdornerLayer parentAdorner = AdornerLayer.GetAdornerLayer(this);
            parentAdorner.Add(m_adorner);
        }
    }
 
    private void RemoveOverlayContent()
    {
        if (m_adorner != null)
        {
            AdornerLayer parentAdorner = AdornerLayer.GetAdornerLayer(this);
            parentAdorner.Remove(m_adorner);
 
            m_adorner.Remove();
            m_adorner = null;
        }
    }
 
    #region Class UIElementAdorner
 
    private class UIElementAdorner : Adorner
    {
        private List<UIElement> m_logicalChildren;
        private UIElement m_element;
 
        public UIElementAdorner(UIElement adornedElement, UIElement element)
            : base(adornedElement)
        {
            m_element = element;
        }
 
        public void Add()
        {
            base.AddLogicalChild(m_element);
            base.AddVisualChild(m_element);
        }
 
        public void Remove()
        {
            base.RemoveLogicalChild(m_element);
            base.RemoveVisualChild(m_element);
        }
 
        protected override Size MeasureOverride(Size constraint)
        {
            m_element.Measure(constraint);
            return m_element.DesiredSize;
        }
 
        protected override Size ArrangeOverride(Size finalSize)
        {
            Point location = new Point(0, 0);
            Rect rect = new Rect(location, finalSize);
            m_element.Arrange(rect);
            return finalSize;
        }
 
        protected override int VisualChildrenCount
        {
            get { return 1; }
        }
 
        protected override Visual GetVisualChild(int index)
        {
            if (index != 0)
                throw new ArgumentOutOfRangeException("index");
 
            return m_element;
        }
 
        protected override IEnumerator LogicalChildren
        {
            get
            {
                if (m_logicalChildren == null)
                {
                    m_logicalChildren = new List<UIElement>();
                    m_logicalChildren.Add(m_element);
                }
 
                return m_logicalChildren.GetEnumerator();
            }
        }
    }
 
    #endregion
}

The XAML you will need to add to your Generic.xaml:

<Style TargetType="{x:Type local:Overlay}">
    <Setter Property="Template">
        <Setter.Value>
            <ControlTemplate TargetType="{x:Type ContentControl}">
                <AdornerDecorator x:Name="PART_OverlayAdorner">
                    <ContentPresenter/>
                </AdornerDecorator>
            </ControlTemplate>
        </Setter.Value>
    </Setter>
</Style>

The end result looks like this when used. The IsOverlayContentVisible should be used in your control/datatemplate triggers to turn the overlay on and off.

<local:Overlay>
    <ListBox />
    <local:Overlay.OverlayContent>
        <TextBlock VerticalAlignment="Center" HorizontalAlignment="Center">Loading!</TextBlock>
    </local:Overlay.OverlayContent>
</local:Overlay>
  • Eze

    Hello,
    the example code is broken.
    Look at the properties definitions: OverlayContentProperty and IsOverlayContentVisibleProperty

  • Eze

    These are the complete properties definitions:

    public static readonly DependencyProperty OverlayContentProperty = DependencyProperty.Register(“OverlayContent”, typeof(UIElement), typeof(Overlay),
    new FrameworkPropertyMetadata(new PropertyChangedCallback(OnOverlayContentChanged)));

    public static readonly DependencyProperty IsOverlayContentVisibleProperty = DependencyProperty.Register(“IsOverlayContentVisible”, typeof(bool), typeof(Overlay),
    new FrameworkPropertyMetadata(new PropertyChangedCallback(OnIsOverlayContentVisibleChanged)));

  • http://www.nickdarnell.com Nick Darnell

    Looks like my code formatter stripped it off. I’ll fix it later tonight, thanks.

    I should probably also just put the file up for download at the bottom.

  • Willy

    Hi, I’ve tried your code. It doesn’t seems to work. I got null exception in
    AdornerLayer parentAdorner = AdornerLayer.GetAdornerLayer(this);

    Any help will be appreciated.

    Thanks,

    • NickDarnell

      That happens when you’re attempting to show the overlay prior to the control being loaded. Using this code you’ll need to wait until the window has finished loading before attempting to display the overlay.

      I’ve made a lot of changes to this code over time, I should really add a newer version up here. One change fixes that problem so you don’t have to worry about order of operations.

  • ride

    Is this the latest version? Where is the download link you were talking about? Could you share your latest version of this example?
    Thx a lot J

  • visitor

    Any update on this? I’m also getting null exception in
    AdornerLayer parentAdorner = AdornerLayer.GetAdornerLayer(this);

  • Dirkster

    This is kind of late but you posted exactly what I needed. I was able to develop your solution into a more stable version: https://msgbox.codeplex.com/wikipage?title=User%20Notification%20Demo&referringTitle=Documentation

    (see zip file at bottom of page) and use your pattern to pop-up messages over any control that can be hosted in a ContentControl. Thanks being careful to not post the fixed version as everyone here requested about 3 years ago.

Archives

Back to Top