Drag WPF Popup control

33,307

Solution 1

Here's a simple solution using a Thumb.

  • Subclass Popup in XAML and codebehind
  • Add a Thumb with width/height set to 0 (this could also be done in XAML)
  • Listen for MouseDown events on the Popup and raise the same event on the Thumb
  • Move popup on DragDelta

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;
        };
    }
}

Solution 2

There is no DragMove for PopUp. Just a small work around, there is lot of improvements you can add to this.

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

In the code behind , add this mousemove event

   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));

        }
    }

Solution 3

Building off of Jobi Joy's answer, I found a re-useable solution that allows you to add as a control within xaml of an existing control/page. Which was not possible adding as Xaml with a Name since it has a different scope.

    [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;
        }
    }

Solution 4

The issue with loosing the mouse when moving too fast, could be resolved


This is taken from msdn:

The new window contains the Child content of Popup.

The Popup control maintains a reference to its Child content as a logical child. When the new window is created, the content of Popup becomes a visual child of the window and remains the logical child of Popup. Conversely, Popup remains the logical parent of its Child content.


In the other words, the child of the popup is displayed in standalone window.

So when trying to the following:
Popup.CaptureMouse() is capturing the wrapper window and not the popup itself. Instead using Popup.Child.CaptureMouse() captures the actual popup.

And all other events should be registered using Popup.Child.

Like Popup.Child.MouseMove, Popup.Child.LostCapture and so on

This has been tested and works perfectly fine

Solution 5

Another way of achieving this is to set your Popup's placement to MousePoint. This makes the popup initially appear at the position of the mouse cursor.

Then you can either use a Thumb or MouseMove event to set the Popup's HorizontalOffset & VerticalOffset. These properties shift the Popup away from its original position as the user drags it.

Remember to reset HorizontalOffset and VerticalOffset back to zero for the next use of the popup!

Share:
33,307
Joachim Kerschbaumer
Author by

Joachim Kerschbaumer

Updated on July 31, 2022

Comments

  • Joachim Kerschbaumer
    Joachim Kerschbaumer almost 2 years

    the WPF Popup control is nice, but somewhat limited in my opinion. is there a way to "drag" a popup around when it is opened (like with the DragMove() method of windows)?

    can this be done without big problems or do i have to write a substitute for the popup class myself? thanks

  • RedGlyph
    RedGlyph over 13 years
    Ouch! No, use a Thumb and its DragDelta event instead (opt. DragStarted/DragCompleted if you want to manage states). It's much more efficient, won't fragment the memory and will not risk to lose the mouse as the example above. That's how dragging is done properly :) See here for an example.
  • Jobi Joy
    Jobi Joy over 13 years
    I agree , that solution I have given is just a hack, As you said DragDelta is more performant than MoveMove.
  • Joachim Kerschbaumer
    Joachim Kerschbaumer about 13 years
    you could capture the mouse to avoid the problem you mentioned when the mouse gets out of the window
  • Joachim Kerschbaumer
    Joachim Kerschbaumer almost 13 years
    But what if you dont't wanna misuse a popup as a thumb (which i guess i meant in RedGlyphs comment), but want to move a more complex popup (e.g. video overlay). using an explicit window is not option in my scenario.
  • Alan
    Alan over 11 years
    Yes, capturing mouse input is the proper way to handle dragging. All of this e.Leftbutton == Pressed is a bit lazy and causes some side-effects.
  • Vassi
    Vassi over 11 years
    Perhaps i'm missing something simple that a seasoned WPF developer would know, but how is this re-usable? When I plant the control in a different XAML context (like trying to reuse it in a window) any content I specify overrides the Canvas element to which the thumb is bound.
  • Shankar Raju
    Shankar Raju about 10 years
    I have tried this solution. Works great! Thank you Jacob. I am facing only one issue: I have a view inside this Thumb and I am able to drag the entire view fine, however if that view has a scrollbar, then dragging the scrollbar is causing the dragging the thumb, and hence the entire view. Is there a way to avoid this?