首页 > 解决方案 > WPF - 将控件从左边缘扩展到窗口边缘

问题描述

我目前正在使用 Popup 控件作为工具提示的屏幕 (C#/WPF) 以提供附加功能(例如复制和粘贴数据)。当单元格悬停在网格视图中时,弹出窗口会显示。我想要完成的是在单元格正下方显示弹出窗口,并允许它向右扩展但不超过屏幕边缘。

到目前为止,我已经能够让它显示在单元格下方并成为单元格的确切宽度。但是,我无法让它扩展到屏幕右侧。我所有的尝试要么没有结果,要么弹出窗口扩大了屏幕的整个宽度。

我一直在尝试将这些SystemParameters属性与element.PointToScreen(new Point(0, 0))没有成功的结合使用。我也尝试过PresentationSource.FromVisual(gridCell)使用 进行一些操作并获得目标点source.CompositionTarget.TransformFromDevice.Transform(),但同样没有成功。

显示位于特定点并扩展(向右)到屏幕边缘的 Popup 控件的最简单方法是什么?TIA

标签: c#wpfpopupwpf-positioning

解决方案


我想,你所需要的 - 弹出位置校正。您可以定义自定义行为。我解决了类似的问题,想和你分享我的代码。我相信你可以改变它以获得必要的行为。

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>

推荐阅读