Adorner as popup not working

1k views Asked by At

I tried to create a Popup in WPF using a adorner so that I can greyout the background. The idea was that it would accept any kind of UIelement, greyout and lock all others (like showDialog in forms), and show the uielement.

It is based on the tutorial from this website.

I have one class called popup:

class PopUp : Adorner, IDisposable
{
    private static readonly Brush _screenBrush = new SolidColorBrush(Color.FromArgb(0x7f, 0x7f, 0x7f, 0x7f));
    private static UIElement _childElement;
    private static Window _window;
    private AdornerLayer _layer;

    public static IDisposable Overlay(UIElement childElement)
    {
        _window = Application.Current.MainWindow;
        var grid = LogicalTreeHelper.GetChildren(_window).OfType<Grid>().FirstOrDefault();
        var adorner = new PopUp(grid, childElement) { _layer = AdornerLayer.GetAdornerLayer(grid) };
        adorner._layer.Add(adorner);
        return adorner as IDisposable;
    }

    private PopUp(UIElement parentParentElement, UIElement childElement)
        : base(parentParentElement)
    {
        _childElement = childElement;

        if (childElement != null)
        {
            AddVisualChild(childElement);
        }

        GetFocus(this);
    }

    protected override int VisualChildrenCount
    {
        get { return _childElement == null ? 0 : 1; }
    }

    protected override Visual GetVisualChild(int index)
    {
        if (index == 0 && _childElement != null)
        {
            return _childElement;
        }

        GetFocus(this);
        return base.GetVisualChild(index);
    }

       protected override Size MeasureOverride(Size constraint)
       {
           _childElement.Measure(constraint);
           return _childElement.RenderSize;
       }

       protected override Size ArrangeOverride(Size finalSize)
       {
           if (_childElement == null) return finalSize;
           var adorningPoint = new Point(0, 0);
           _childElement.Arrange(new Rect(adorningPoint, this.AdornedElement.RenderSize));
           return finalSize;
       }

      protected override void OnRender(DrawingContext drawingContext)
      {
          _screenBrush.Opacity = 0.5;
          drawingContext.DrawRectangle(_screenBrush, null, WindowRect());
          base.OnRender(drawingContext);
      }

    private Rect WindowRect()
    {
        if (_window == null)
        {
            throw new ArgumentException("cant get main window");
        }

        var transformToAncestor = this.AdornedElement.TransformToAncestor(_window);
        var windowOffset = transformToAncestor.Inverse.Transform(new Point(0, 0));

        // Get a point of the lower-right corner of the window
        var windowLowerRight = windowOffset;
        windowLowerRight.Offset(_window.ActualWidth, _window.ActualHeight);
        return new Rect(windowOffset, windowLowerRight);
    }

    private void GetFocus(UIElement element)
    {
        element.Focusable = true;
        Keyboard.Focus(element);
        element.IsEnabled = true;
    }

    public void Dispose()
    {
       _layer.Remove(this);
    }
}

This class does the adorner magic and draws a UIelement on top of the first or default grid inside the current window.

I created a static class PopUpExtender:

 public static class PopUpExtender
{
    private static Dictionary<UIElement, IDisposable> _popUps;        

    public static void ShowAsPopUp(this UIElement child)
    {
        if (_popUps == null)
        {
            _popUps = new Dictionary<UIElement, IDisposable>();
        }

        _popUps.Add(child, PopUp.Overlay(child));
    }

    public static void ClosePopUp(this UIElement child)
    {
        if (!_popUps.ContainsKey(child)) return;
        var disposableChild = _popUps[child];
        disposableChild.Dispose();
        _popUps.Remove(child);
    }}

That uses extension methods to allow a UIelement to close and show.

Everything seems to work ok but the window (and other elements) can still be used by the keyboard. I tried using isEnabled = false but that makes it a little ugly, i'm trying to have the same effect as showdialog().

Second problem is that when a popup opens a popup the parent freezes and doesn't accept any input (nothing does..).

I hope someone can help me. Maybe adorners aren't the best idea to do this and if someone has any suggestions or better ideas please share.

1

There are 1 answers

2
Sheridan On BEST ANSWER

Here is a simple example of an overlay that doesn't require any Adorners.

XAML:

<Window x:Class="YourApp.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    ... >
    <Window.Resources>
        <BooleanToVisibilityConverter x:Key="BooleanToVisibilityConverter" />
    </Window.Resources>

    <!-- Define your normal XAML here -->

    <Rectangle Fill="#7FFFFFFF" Visibility="{Binding YourBoolProperty, 
        HorizontalAlignment="Stretch" VerticalAlignment="Stretch" 
        Converter={StaticResource BooleanToVisibilityConverter}}" />
</Window>

In code:

// Show overlay
YourBoolProperty = true;

...

// Hide overlay
YourBoolProperty = false;