-1

I am currently working on a screen (C#/WPF) which uses a Popup control as a tooltip in order to provide additional functionality (such as copy & paste of data). The popup shows when hover of a cell in grid view. What I am trying to accomplish is to show the popup directly below the cell and allowing it to expand towards the right but not past the edge of the screen.

So far I have been able to get it to show below the cell and be the exact width of the cell. However, I am not able to get it to expand to the right of the screen. All my attempts have either resulted in nothing or the popup expanding the full width of the screen.

I have been trying to use the SystemParameters properties in combination with the element.PointToScreen(new Point(0, 0)) without success. I have also tried some manipulation with PresentationSource.FromVisual(gridCell) and get target points using source.CompositionTarget.TransformFromDevice.Transform(), again without success.

What is the simplest way to show a Popup control that is located at a specific point and expands (rightward) to the edge of the screen? TIA

Dominick
  • 322
  • 1
  • 3
  • 16

1 Answers1

0

I think, all you need - popup position correction. You could define custom behavior. I solved similar problem and want to share my code with you. I believe you can change it in order to get necessary behavior.

using System;
using System.ComponentModel;
using System.Windows;
using System.Windows.Controls.Primitives;
using System.Windows.Interactivity;

namespace YourNameSpace
{
    public class PopupExtendedPlacementBehavior : Behavior<Popup>
    {
        private static DependencyPropertyDescriptor PopupChildDescriptor
            = DependencyPropertyDescriptor.FromProperty(Popup.ChildProperty, typeof(Popup));

        private static FrameworkPropertyMetadata PlacementMetadata
            = new FrameworkPropertyMetadata(ExtendedPlacementMode.Default);

        public static DependencyProperty PlacementProperty
            = DependencyProperty.Register("Placement", typeof(ExtendedPlacementMode), typeof(PopupExtendedPlacementBehavior), PlacementMetadata);

        private static FrameworkPropertyMetadata IndentFromTargetMetadata
            = new FrameworkPropertyMetadata(0.0);

        public static DependencyProperty IndentFromTargetProperty
            = DependencyProperty.Register("IndentFromTarget", typeof(double), typeof(PopupExtendedPlacementBehavior), IndentFromTargetMetadata);
        
        public ExtendedPlacementMode Placement
        {
            get => (ExtendedPlacementMode)GetValue(PlacementProperty);
            set => SetValue(PlacementProperty, value);
        }
        public double IndentFromTarget
        {
            get => (double)GetValue(IndentFromTargetProperty);
            set => SetValue(IndentFromTargetProperty, value);
        }

        protected override void OnAttached()
        {
            base.OnAttached();

            AssociatedObject.Opened += OnChangePlacement;
            AssociatedObject.SizeChanged += OnChangePlacement;

            PopupChildDescriptor.AddValueChanged(AssociatedObject, OnChangePlacement);
        }

        protected override void OnDetaching()
        {
            AssociatedObject.Opened -= OnChangePlacement;
            AssociatedObject.SizeChanged -= OnChangePlacement;

            PopupChildDescriptor.RemoveValueChanged(AssociatedObject, OnChangePlacement);

            base.OnDetaching();
        }

        private void OnChangePlacement(object sender, EventArgs e)
        {
            if (CanSetPlacement())
            {
                SetPlacement(Placement);
            }
        }

        private bool CanSetPlacement()
        {
            return
                AssociatedObject.Placement == PlacementMode.Bottom
                ||
                AssociatedObject.Placement == PlacementMode.Top 
                ||
                AssociatedObject.Placement == PlacementMode.Right
                ||
                AssociatedObject.Placement == PlacementMode.Left;
        }

        private void SetPlacement(ExtendedPlacementMode mode)
        {
            var offset = new Point();

            switch (mode)
            {
                case ExtendedPlacementMode.Default:
                    return;
                case ExtendedPlacementMode.Left | ExtendedPlacementMode.Top:
                    AssociatedObject.Placement = PlacementMode.Left;
                    offset = GetOffset(AssociatedObject, GetTopOffset);
                    offset.X -= IndentFromTarget;
                    offset.Y -= IndentFromTarget;
                    break;
                case ExtendedPlacementMode.Left | ExtendedPlacementMode.Bottom:
                    AssociatedObject.Placement = PlacementMode.Left;
                    offset = GetOffset(AssociatedObject, GetBottomOffset);
                    offset.X -= IndentFromTarget;
                    offset.Y += IndentFromTarget;
                    break;
                case ExtendedPlacementMode.Right | ExtendedPlacementMode.Top:
                    AssociatedObject.Placement = PlacementMode.Right;
                    offset = GetOffset(AssociatedObject, GetTopOffset);
                    offset.X += IndentFromTarget;
                    offset.Y -= IndentFromTarget;
                    break;
                case ExtendedPlacementMode.Right | ExtendedPlacementMode.Bottom:
                    AssociatedObject.Placement = PlacementMode.Right;
                    offset = GetOffset(AssociatedObject, GetBottomOffset);
                    offset.X += IndentFromTarget;
                    offset.Y += IndentFromTarget;
                    break;
                case ExtendedPlacementMode.Left:
                    AssociatedObject.Placement = PlacementMode.Left;
                    offset = GetOffset(AssociatedObject, GetHorizontalCenterOffset);
                    offset.X -= IndentFromTarget;
                    break;
                case ExtendedPlacementMode.Right:
                    AssociatedObject.Placement = PlacementMode.Right;
                    offset = GetOffset(AssociatedObject, GetHorizontalCenterOffset);
                    offset.X += IndentFromTarget;
                    break;
                case ExtendedPlacementMode.Center:
                    AssociatedObject.Placement = PlacementMode.Center;
                    break;
                case ExtendedPlacementMode.Top:
                    AssociatedObject.Placement = PlacementMode.Top;
                    offset = GetOffset(AssociatedObject, GetVerticalCenterOffset);
                    offset.Y -= IndentFromTarget;
                    break;
                case ExtendedPlacementMode.Bottom:
                    AssociatedObject.Placement = PlacementMode.Bottom;
                    offset = GetOffset(AssociatedObject, GetVerticalCenterOffset);
                    offset.Y += IndentFromTarget;
                    break;
            }

            AssociatedObject.HorizontalOffset = offset.X;
            AssociatedObject.VerticalOffset = offset.Y;
        }

        private static Point GetOffset(Popup popup, Func<FrameworkElement, FrameworkElement, Point> getOffset)
        {
            var target = popup.PlacementTarget as FrameworkElement;
            var child = (popup.Child ?? popup) as FrameworkElement;

            if (target != null && child != null)
            {
                return getOffset(target, child);
            }

            return new Point();
        }

        private static Point GetHorizontalCenterOffset(FrameworkElement target, FrameworkElement popup)
        {
            var y = (target.ActualHeight - popup.ActualHeight) / 2;

            return new Point(0.0, y);
        }

        private static Point GetVerticalCenterOffset(FrameworkElement target, FrameworkElement popup)
        {
            var x = (target.ActualWidth - popup.ActualWidth) / 2;

            return new Point(x, 0.0);
        }

        private static Point GetTopOffset(FrameworkElement target, FrameworkElement popup)
        {
            var y = -popup.ActualHeight;

            return new Point(0.0, y);
        }
        
        private static Point GetBottomOffset(FrameworkElement target, FrameworkElement popup)
        {
            var y = target.ActualHeight;

            return new Point(0.0, y);
        }
    }

    [Flags]
    public enum ExtendedPlacementMode
    {
        Default = 0,
        Left = 1,
        Right = 2,
        Top = 4,
        Bottom = 8,
        Center = 16
    }
}
<UserControl
    xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
    xmlns:beh="clr-namespace:YourNameSpace;assembly=YourAssembly">

    <Popup>
        <i:Interaction.Behaviors>
            <beh:PopupExtendedPlacementBehavior Placement="Bottom,Right" IndentFromTarget="4"/>
        </i:Interaction.Behaviors>
    </Popup>
</UserControl>
Ivan
  • 34
  • 4
  • Wow! I did not expect such a comprehensive answer so fast. I really appreciate it. I will try integrating this with my current code and let you know if I have any question. Thanks again. – Dominick Aug 26 '21 at 14:29
  • I just tried this but it doesn't seem to do what I wanted. For example, if I used this: The popup would be shown a little below the grid view cell, however the popup's width is still the width of the cell. Instead, I want it to show where it is but have it extend out to the right side of the screen so all the information can become visible. – Dominick Aug 26 '21 at 14:37
  • You are also able to manipulate with the popup size. There is possibility to get screen from point (see https://stackoverflow.com/questions/1927540/how-to-get-the-size-of-the-current-screen-in-wpf) and recalculate size as you want. – Ivan Aug 26 '21 at 14:41
  • Thanks I will take a look at it. I appreciate the feedback. – Dominick Aug 26 '21 at 16:08