Dominick
Dominick

Reputation: 476

WPF - Expand control from left edge to window edge

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

Upvotes: -1

Views: 176

Answers (1)

Ivan
Ivan

Reputation: 44

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>

Upvotes: 0

Related Questions