拖动“WPF 弹出控件”

本文关键字:控件 WPF 拖动 | 更新日期: 2023-09-27 17:47:21

WPF Popup控件很好,但在我看来有些有限。 有没有办法在打开弹出窗口时"拖动"弹出窗口(就像使用窗口的 DragMove() 方法一样)?

这可以没有大问题吗,还是我必须自己编写弹出类的替代品?谢谢

拖动“WPF 弹出控件”

这是一个使用拇指的简单解决方案。

  • XAML 中的子类弹出窗口和代码隐藏
  • 添加宽度/高度设置为 0 的缩略图(这也可以在 XAML 中完成)
  • 侦听弹出窗口中的鼠标按下事件,并在拇指上引发相同的事件
  • 拖动三角洲上的移动弹出窗口

XAML:

<Popup x:Class="PopupTest.DraggablePopup" ...>
    <Canvas x:Name="ContentCanvas">
    </Canvas>
</Popup>

C#:

public partial class DraggablePopup : Popup 
{
    public DraggablePopup()
    {
        var thumb = new Thumb
        {
            Width = 0,
            Height = 0,
        };
        ContentCanvas.Children.Add(thumb);
        MouseDown += (sender, e) =>
        {
            thumb.RaiseEvent(e);
        };
        thumb.DragDelta += (sender, e) =>
        {
            HorizontalOffset += e.HorizontalChange;
            VerticalOffset += e.VerticalChange;
        };
    }
}

弹出窗口没有拖动移动。只是一个小的解决方法,您可以对此进行许多改进。

<Popup x:Name="pop" IsOpen="True" Height="200" Placement="AbsolutePoint"  Width="200">
   <Rectangle Stretch="Fill" Fill="Red"/>            
</Popup>

在后面的代码中,添加此鼠标移动事件

   pop.MouseMove += new MouseEventHandler(pop_MouseMove);
   void pop_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            pop.PlacementRectangle = new Rect(new Point(e.GetPosition(this).X,
                e.GetPosition(this).Y),new Point(200,200));
        }
    }

基于Jobi Joy的答案,我找到了一个可重用的解决方案,允许您在现有控件/页面的xaml中添加为控件。这不可能添加为具有名称的 Xaml,因为它具有不同的范围。

    [ContentProperty("Child")]
    [DefaultEvent("Opened")]
    [DefaultProperty("Child")]
    [Localizability(LocalizationCategory.None)]
    public class DraggablePopup : Popup
    {
        public DraggablePopup()
        {
            MouseDown += (sender, e) =>
            {
                Thumb.RaiseEvent(e);
            };
            Thumb.DragDelta += (sender, e) =>
            {
                HorizontalOffset += e.HorizontalChange;
                VerticalOffset += e.VerticalChange;
            };
        }
        /// <summary>
        /// The original child added via Xaml
        /// </summary>
        public UIElement TrueChild { get; private set; }
        public Thumb Thumb { get; private set; } = new Thumb
        {
            Width = 0,
            Height = 0,
        };
        protected override void OnInitialized(EventArgs e)
        {
            base.OnInitialized(e);
            TrueChild = Child;
            var surrogateChild = new StackPanel();
            RemoveLogicalChild(TrueChild);
            surrogateChild.Children.Add(Thumb);
            surrogateChild.Children.Add(TrueChild);
            AddLogicalChild(surrogateChild);
            Child = surrogateChild;
        }
    }

实现此目的的另一种方法是将弹出窗口的位置设置为鼠标点。 这使得弹出窗口最初出现在鼠标光标的位置。

然后,您可以使用 Thumb 或 MouseMove 事件来设置 Popup 的 HorizontalOffset & VerticalOffset。 当用户拖动弹出窗口时,这些属性会将弹出窗口从其原始位置移开。

请记住将水平偏移和垂直偏移重置为零,以便下次使用弹出窗口!

移动速度过快时丢失鼠标的问题可以解决

<小时 />

这是从msdn获取的:

新窗口包含弹出窗口的子内容。

Popup 控件将对其子内容的引用作为逻辑子项进行维护。创建新窗口后,Popup 的内容将成为窗口的可视子项,并且仍然是 Popup 的逻辑子项。相反,弹出窗口仍然是其子内容的逻辑父级。

<小时 />

换句话说,弹出窗口的子项显示在独立窗口中。

因此,当尝试执行以下操作时:
Popup.CaptureMouse()捕获的是包装窗口,而不是弹出窗口本身。相反,使用 Popup.Child.CaptureMouse() 会捕获实际的弹出窗口。

所有其他事件都应使用 Popup.Child 注册。

Popup.Child.MouseMovePopup.Child.LostCapture

这已经过测试并且工作正常

其他人对此的说法相反,我 100% 同意 Jobi Joy 的答案(老实说,这应该是公认的答案)。我看到一条评论,指出答案中的解决方案会导致内存碎片。这是不可能的,因为创建新结构根本不会导致内存碎片;事实上,使用结构可以节省内存,因为它们是堆栈分配的。此外,我认为这实际上是重新定位弹出窗口的正确方法(毕竟,Microsoft添加 PlacementRectangle 属性是有原因的),所以这不是黑客。然而,附加拇指并期望用户始终将弹出窗口放在画布上是非常黑客的,并不总是一个实用的解决方案。

Private Point startPoint;
 private void Window_PreviewMouseLeftButtonDown(object sender, MouseButtonEventArgs e)
    {
        startPoint = e.GetPosition(null);
    }
private void Window_MouseMove(object sender, MouseEventArgs e)
    {
        if (e.LeftButton == MouseButtonState.Pressed)
        {
            Point relative = e.GetPosition(null);
            Point AbsolutePos = new Point(relative.X + this.Left, relative.Y + this.Top);
            this.Top = AbsolutePos.Y - startPoint.Y;
            this.Left = AbsolutePos.X - startPoint.X;
        }
    }

这适用于拖动我的窗口,但就像有人告诉我如果我移动鼠标快速一样,它会离开窗口并停止引发事件。更不用说拖拽一点也不顺畅。有谁知道如何正确地做到这一点,漂亮而流畅的拖动,而不会在拖动太快时丢失它???如果可能的话,发布一个简单的示例,而不是一个会让像我这样的初学者迷失在代码中的整个教程。谢谢!