2

I'm basically writing a simple floating panel whose location can be changed via dragging its title bar (which is a grid itself). But I can't get it working! It seems MouseEventArgs.GetPosition returns a wrong point. What am I missing here?

public class FloatingPanel : Grid
    {
        Grid gridTitle;

        bool dragging = false;
        Point lastPos;

        public FloatingPanel(UserControl gadget)
        {
            this.MouseMove += FloatingPanel_MouseMove;

            gridTitle = new Grid();
            gridTitle.Height = 25;
            gridTitle.VerticalAlignment = System.Windows.VerticalAlignment.Top;
            gridTitle.Background = Brushes.Cyan;
            gridTitle.MouseLeftButtonDown += gridTitle_MouseLeftButtonDown;
            gridTitle.MouseLeftButtonUp += gridTitle_MouseLeftButtonUp;
            this.Children.Add(gridTitle);

            this.Height = gadget.Height + 25;
            this.Width = gadget.Width;

            gadget.VerticalAlignment = System.Windows.VerticalAlignment.Bottom;
            this.Children.Add(gadget);
        }

        void gridTitle_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
        {
            dragging = false;
        }

        void gridTitle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
        {
            lastPos = Mouse.GetPosition(this);
            dragging = true;
        }

        void FloatingPanel_MouseMove(object sender, MouseEventArgs e)
        {
            if(dragging)
            {
                Vector delta = e.GetPosition(this) - lastPos;
                this.Margin = new Thickness(this.Margin.Left + delta.X, this.Margin.Top + delta.Y, this.Margin.Right, this.Margin.Bottom);
                lastPos = e.GetPosition(this);
            }
        }
    }  

I've also tried using System.Windows.Forms.Cursor.Position and System.Windows.Forms.Control.MousePosition which give the position on the screen. But no luck.
Solution: The problem was solved by 3 modifications(as Sphinxxx pointed out):
- Using MouseEventArgs.GetPosition(null) instead of MouseEventArgs.GetPosition(this)
- Capturing and releasing the mouse in mousedown and mouseup events using Mouse.Capture(gridTitle) and Mouse.Capture(null)
- Setting the grid's horizontal and vertical alignment. (This seems odd to me. Why does nod setting the alignment cause a problem?)

SepehrM
  • 1,087
  • 2
  • 20
  • 44

1 Answers1

2

In _MouseMove, you're trying to calculate the movement using e.GetPosition(this), but that only gets the mouse pointer position relative to your Grid. You need to find the position relative to some other UI element, e.g. the containing Window in order to know how much the grid should move.

Try e.GetPosition(null) (that is null instead of this) in both _MouseLeftButtonDown and _MouseMove to calculate correct deltas.

This article illustrates the difference: Getting the Mouse Position Relative to a Specific Element

EDIT: More robust FloatingPanel:

In the constructor, avoid having a gadget that may end up on top of the title bar by putting them in two separate RowDefinitions (and let WPF handle widths and heights):

public FloatingPanel(FrameworkElement gadget)
{
    gridTitle = new Grid();
    gridTitle.Height = 25;
    gridTitle.Background = Brushes.Cyan;

    gridTitle.MouseLeftButtonDown += gridTitle_MouseLeftButtonDown;
    gridTitle.MouseMove += gridTitle_MouseMove;
    gridTitle.MouseLeftButtonUp += gridTitle_MouseLeftButtonUp;

    //Create two grid rows - one to hold the title bar..
    this.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
    Grid.SetRow(gridTitle, 0);
    this.Children.Add(gridTitle);

    //..and one two hold the gadget:
    this.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
    Grid.SetRow(gadget, 1);
    this.Children.Add(gadget);
}

In the ..ButtonDown/..ButtonUp handlers, make the title bar "capture" (and release) the mouse movements so the mouse pointer doesn't "slide off" when moving too fast:

void gridTitle_MouseLeftButtonUp(object sender, MouseButtonEventArgs e)
{
    //Relese a previous capture:
    Mouse.Capture(null);
    dragging = false;
}

void gridTitle_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    lastPos = Mouse.GetPosition(null);

    //Capture the mouse to ensure we get all future mouse movements:
    Mouse.Capture(gridTitle);
    dragging = true;
}

EDIT 2: Alternative without Mouse.Capture():

...
gridTitle.MouseLeftButtonDown += gridTitle_MouseLeftButtonDown;

//gridTitle.MouseMove += gridTitle_MouseMove;

//The parent Window isn't available yet here in the constructor,
//so we must wait for our Loaded event to hook it up:
this.Loaded += (s, e) => { Window.GetWindow(this).MouseMove += gridTitle_MouseMove; };

gridTitle.MouseLeftButtonUp += gridTitle_MouseLeftButtonUp;
...
Sphinxxx
  • 12,484
  • 4
  • 54
  • 84
  • Unfortunately that doesn't solve the problem. I've also tried using `System.Windows.Forms.Cursor.Position` and `System.Windows.Forms.Control.MousePosition` which give the position on the screen. But no luck. – SepehrM Dec 31 '13 at 17:40
  • It works fine here. Did you change all three calls to `.GetPosition()`? What exactly isn't working? Does your grid not move at all, does it only move now and then, or does it move the wrong way? – Sphinxxx Dec 31 '13 at 18:07
  • It moves very slowly! Here is how I'm using it in MainWindow's constructor: UserControl1 c = new UserControl1(); FloatingPanel p = new FloatingPanel(c); layoutRoot.Children.Add(p); (layoutRoot is the MainWindow's content grid) – SepehrM Dec 31 '13 at 18:13
  • There may be a problem with the "gadget" growing in size and overlaying the title bar, or you may lose `MouseMove` events if moving the mouse too fast. I have edited my answer to include a few suggested changes. – Sphinxxx Dec 31 '13 at 18:57
  • Thanks for the time and effort you're putting into this matter. Using your code, the problem is solved for vertical move but moving horizontally has the same problem! And even though I've explicitly defined the width and height of the user control, FloatingPanel's size goes to infinite and stretches all over the parent window. (FloatingPanel and the user control should have the same size) – SepehrM Dec 31 '13 at 19:13
  • 1
    Ok, that one can be fixed. `FloatingPanel` is a `Grid`, and a Grid stretches (both horizontally and vertically) by default. Add these two lines in `FloatingPanel`'s constructor: `this.HorizontalAlignment = HorizontalAlignment.Left; this.VerticalAlignment = VerticalAlignment.Top;` – Sphinxxx Dec 31 '13 at 19:28
  • You're right, my bad. That did the trick, everything's working fine now! – SepehrM Dec 31 '13 at 19:39
  • I need to capture the mouse in somewhere else. Any idea on how to prevent the pointer from "sliding off" without using `Mouse.Capture`? – SepehrM Jan 02 '14 at 13:47
  • 1
    Well, maybe you can listen for `MouseMove`s on the whole `Window` and not just on `gridTitle`? I'll update the answer. – Sphinxxx Jan 12 '14 at 16:18