1

「グリッドへのスナップ」機能を C# の WinForm アプリケーションに追加しようとしていますが、フォームを正しく移動するのに少し問題があります。

望ましい結果は、ユーザーがフォームをクリックしてドラッグすると、フォームの位置がマウスの目的の方向に移動しますが、50 ピクセル単位で移動し、常に最後のグリッド ポイントに切り捨てられます。境界線をクリックしてドラッグすると、同じ 50 ピクセル単位でフォームのサイズが変更されます。

WndProc の WM_SIZING メッセージで新しいサイズを計算することで、サイズ変更を正しく機能させることができました。WM_MOVING で同じことを試みましたが、正しい機能が得られません。

私が得たのは、フォームが開始位置よりも高い (右または下) グリッド ポイントに移動しないことです。マウスが 1 ピクセル下 (上または左) のグリッド ポイントに向かって移動すると、フォームは 50 ピクセルジャンプします。もちろん、これは下のポイントへの望ましい移動ですが、一度だけ移動し、次のジャンプを行う前にマウスが追加のスペースを移動するのを待つ必要がありますが、次の 1 ピクセルのマウス移動に対して 50 ピクセル移動します。

私が見つけたのは、Message.LParam 内のウィンドウの位置が各メッセージで計算され、WM_MOVE が完了したときにのみ更新されることです。これは、マウス ボタンが離されていない場合でも当てはまります。そのため、マウスを上または左に移動すると、ウィンドウが強制的に 50 ピクセル移動し、次のメッセージ (つまり、別の 1 ピクセルのマウス移動) で、LParam は 50 ピクセル低くなり、1 ピクセルで次に低いグリッド ポイントにスナップするのに十分です。 . ただし、マウスが最初のクリック ポイントから 50 ピクセル移動するまでウィンドウを右または下に移動させたくないため、WndProc はウィンドウの位置を同じままにし、開始位置よりも高いポイントに到達することはありません。何らかの理由で、WM_SIZING はこれを行いません。サイズの変更が画面に表示されていなくても、ウィンドウのサイズが変更されているように見えます。

これが私が使用している私のコードです。わかりやすくするために、WM_SIZING 部分は削除されています。

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

この問題についての助けや洞察をありがとう。-ダスティン

4

1 に答える 1

1

WM_MOVING メッセージごとに、ウィンドウ座標は、最後の WM_MOVING メッセージ以降のマウスの相対的な動きによって、前のウィンドウ座標からオフセットされます。たとえば、ウィンドウの左座標が最初は 100 で、マウスが X 方向に 130 から 131 に移動した場合、左座標が 101 の四角形が返され、ハンドラーで 100 にスナップされます。ここで、マウスが X 方向に 131 から 132 に移動したとします。新しい左座標は 102 であると予想されるかもしれません (ウィンドウの移動を開始してから、マウスは X 方向に合計 2 ピクセル移動したため)、実際には 101 になります ( 100 プラス、最後のイベント以降のマウスの 1 ピクセルの相対的な動き)。

その結果、ウィンドウを次のスナップ位置に移動するには、1 回の動作でマウスを 50 ピクセル右にフリックする必要があります。一方、次に低い 50 ピクセルの境界にスナップするには、毎回 1 ピクセルずつ左に移動するだけで済みます。

これを解決するには、スナップされたウィンドウの位置と、スナップされていない場合のウィンドウの位置との間の累積オフセットを追跡する必要があります。次のコードは、実装したいことを達成しているようです。

(もう 1 つの変更は、スナップ位置を決定するときに切り捨てではなく丸めを使用することです。これにより、ドラッグ中のマウス カーソルとタイトル バーの間の最大距離を最小限に抑えることができます。)

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 に答える