1

I'm trying to add a "snap to grid" functionality to a WinForm application in C#, but I am having a bit of a problem getting the form to move correctly.

The desired result is that the user clicks and drags the form and the position of the form is moved in the desired direction of the mouse but in 50 pixel increments, and always rounded down to the last grid point. The resize of the form is to for the same 50 pixel increment when the border is clicked and dragged.

I have been able to get the resize to work correctly by calculating the new size in the WM_SIZING message in WndProc. I attempted to do the same thing in the WM_MOVING, but I am not getting the correct functionality.

What I get is that the form will not move to any grid point higher (right or down) than it's start location, and when the mouse is moved towards a grid point lower (top or left) by 1 pixel the form jumps 50 pixels. Of course this is the desired movement to the lower point, but it should only move once and then wait for the mouse to move the additional space before making the next jump, however it move 50 pixels for the next 1 pixel of mouse movement.

What I have found is that the position of the window in the Message.LParam is calculated at each message and is only updated when the WM_MOVE is completed. This is true even if the mouse button has not been released. So moving the mouse top or left forces the window to move 50 pixels and on the next message (ie: another 1 pixel of mouse movement) the LParam is now 50 pixels lower and the 1 pixel is enough to snap to the next lower grid point. However, since I don't want the window to move right or down till the mouse has moved 50 pixels from it's first click point, the WndProc leaves the window position the same and it never reaches a point higher than it's starting position. For some reason the WM_SIZING does not do this. It sees the window as changing size even if the size change is not visible on the screen.

Here is my code I am using. The WM_SIZING part has been removed for clarity.

private const int WM_MOVING = 0x216;
private const int WM_MOVE = 0x3;

struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_MOVING)
    {
        RECT rc = (RECT)Marshal.PtrToStructure(m.LParam, typeof(RECT));

        int w = rc.Right - rc.Left;
        int h = rc.Bottom - rc.Top;

        rc.Top = ((int)rc.Top / 50) * 50;
        rc.Left = ((int)rc.Left / 50) * 50;
        rc.Right = rc.Left + w;
        rc.Bottom = rc.Top + h;

        Marshal.StructureToPtr(rc, m.LParam, true);
    }
    base.WndProc(ref m);
}

Thank you for any help or insight to this issue. -Dustin

4

1 回答 1

1

对于每个 WM_MOVING 消息,窗口坐标与前一个窗口坐标的偏移量是自上一个 WM_MOVING 消息以来鼠标的相对运动。例如,如果窗口的左坐标最初是 100,并且鼠标在 X 中从 130 移动到 131,您将收到一个左坐标为 101 的矩形,该矩形在您的处理程序中被捕捉回 100。现在假设鼠标在 X 中从 131 移动到 132:您可能期望新的左坐标为 102(因为自从您开始移动窗口以来,鼠标在 X 中总共移动了 2 个像素),但它实际上是 101( 100 加上自上次事件以来鼠标的 1 像素相对运动)。

因此,您需要在一次动作中将鼠标向右滑动 50 像素,然后才能让窗口移动到下一个捕捉位置。另一方面,您只需每次向左移动 1 个像素即可捕捉到下一个最低的 50 像素边界。

为了解决这个问题,您需要跟踪捕捉的窗口位置与没有捕捉的窗口位置之间的累积偏移量。以下代码似乎完成了您想要实现的功能。

(另一种修改是在确定捕捉位置时使用舍入而不是截断。这可以最大限度地减少拖动时鼠标光标和标题栏之间的最大可能距离。)

private const int WM_MOVING = 0x216;
private const int WM_EXITSIZEMOVE = 0x231;

struct RECT
{
    public int Left;
    public int Top;
    public int Right;
    public int Bottom;
}

private int LeftOffset = 0;
private int TopOffset = 0;

protected override void WndProc(ref Message m)
{
    if (m.Msg == WM_MOVING)
    {
        RECT rc = (RECT)Marshal.PtrToStructure(m.LParam, typeof(RECT));
        int w = rc.Right - rc.Left;
        int h = rc.Bottom - rc.Top;

        int newTop = (int)Math.Round((rc.Top + TopOffset) / 50.0) * 50;
        int newLeft = (int)Math.Round((rc.Left + LeftOffset) / 50.0) * 50;

        TopOffset = rc.Top + TopOffset - newTop;
        LeftOffset = rc.Left + LeftOffset - newLeft;

        rc.Top = newTop;
        rc.Left = newLeft;
        rc.Right = newLeft + w;
        rc.Bottom = newTop + h;

        Marshal.StructureToPtr(rc, m.LParam, true);
    }
    else if (m.Msg == WM_EXITSIZEMOVE)
    {
        LeftOffset = 0;
        TopOffset = 0;
    }
    base.WndProc(ref m);
}
于 2013-10-06T18:51:02.340 回答