web-dev-qa-db-ja.com

拡張ウィンドウフレームをドラッグしてWPFウィンドウを移動可能にするにはどうすればよいですか?

WindowsエクスプローラーやInternet Explorerなどのアプリケーションでは、タイトルバーの下の拡張フレーム領域をつかんでウィンドウをドラッグできます。

WinFormsアプリケーションの場合、フォームとコントロールは、ネイティブのWin32 APIにできるだけ近くなります。フォームのWndProc()ハンドラーをオーバーライドし、 WM_NCHITTEST ウィンドウメッセージを処理して、システムをだまして、HTCAPTIONを返すことにより、フレーム領域のクリックが実際にタイトルバーのクリックであると考えさせます。 。私は自分のWinFormsアプリでそれを楽しい効果に実行しました。

WPFでは、次のように、同様のWndProc()メソッドを実装して、ウィンドウフレームをクライアント領域に拡張しながら、WPFウィンドウのハンドルにフックすることもできます。

// In MainWindow
// For use with window frame extensions
private IntPtr hwnd;
private HwndSource hsource;

private void Window_SourceInitialized(object sender, EventArgs e)
{
    try
    {
        if ((hwnd = new WindowInteropHelper(this).Handle) == IntPtr.Zero)
        {
            throw new InvalidOperationException("Could not get window handle for the main window.");
        }

        hsource = HwndSource.FromHwnd(hwnd);
        hsource.AddHook(WndProc);

        AdjustWindowFrame();
    }
    catch (InvalidOperationException)
    {
        FallbackPaint();
    }
}

private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    switch (msg)
    {
        case DwmApiInterop.WM_NCHITTEST:
            handled = true;
            return new IntPtr(DwmApiInterop.HTCAPTION);

        default:
            return IntPtr.Zero;
    }
}

問題は、私が盲目的にhandled = trueを設定してHTCAPTIONを返すので、anywhereをクリックしてもウィンドウアイコンまたはコントロールボタンをクリックするとウィンドウがドラッグされることです。つまり、以下で赤くハイライトされているものすべてがドラッグの原因になります。これには、ウィンドウの側面(非クライアント領域)のサイズ変更ハンドルも含まれます。 WPFコントロール、つまりテキストボックスとタブコントロールも、結果としてクリックの受信を停止します。

私が欲しいのは

  1. タイトルバー、および
  2. クライアントエリアの地域...
  3. ...私のコントロールによって占有されていない

ドラッグ可能に。つまり、これらの赤い領域のみをドラッグ可能にしたい(クライアント領域+タイトルバー)。

WndProc()メソッドと残りのウィンドウのXAML /コードビハインドを変更して、HTCAPTIONを返す領域と返さない領域を決定するにはどうすればよいですか? Pointsを使用して、クリックの場所をコントロールの場所と照合する方法に沿って何かを考えていますが、WPFランドでどのように対処するかわかりません。

EDIT [4/24]:非表示のコントロール、またはウィンドウ自体さえもある場合、DragMove()を呼び出すことでMouseLeftButtonDownに応答しますウィンドウ( Rossの答え を参照)。問題は、何らかの理由でウィンドウが最大化されているとDragMove()が機能しないため、Windows 7 Aeroスナップでナイスを再生できないことです。私はWindows 7の統合を予定しているので、私の場合、それは許容できる解決策ではありません。

53
BoltClock

サンプルコード

今朝受け取ったメールのおかげで、これだけの機能を実演する実用的なサンプルアプリを作成するように求められました。私はそれを今やった。 GitHub (または 現在アーカイブされているCodePlex )にあります。リポジトリを複製するか、アーカイブをダウンロードして抽出してから、Visual Studioで開き、ビルドして実行するだけです。

完全なアプリケーション全体はMITライセンスですが、おそらくそれを解体し、アプリのコードを完全に使用するのではなく、コードの一部を自分の周りに配置することになります。ライセンスによってもそうすることができなくなるわけではありません。また、アプリケーションのメインウィンドウのデザインが上記のワイヤーフレームに似ていないことは知っていますが、考え方は質問で提示されたものと同じです。

これが誰かに役立つことを願っています!

ステップバイステップのソリューション

ようやく解決しました。 Jeffrey L Whitledge 、私を正しい方向に向けてくれてありがとう! 彼の答えは受け入れられませんでした。それがなければ、私は解決策を考え出すことができなかったでしょう。EDIT [9/8]:この回答は、より完全なものとして受け入れられました。私はジェフリーに彼の助けのために代わりに素晴らしい大きな賞金を与えています。

後世のために、ここに私がそれをどのようにしたかを示します(私が行くときにジェフリーの答えを関連する箇所に引用します):

マウスクリックの場所を取得し(wParam、lParamなどから)、それを使用してPointを作成します(おそらく何らかの座標変換を使用しますか?)。

この情報は、_WM_NCHITTEST_メッセージのlParamから取得できます。 MSDNが説明する のように、カーソルのx座標はその下位ワードであり、カーソルのy座標はその上位ワードです。

座標は画面全体を基準にしているため、ウィンドウ空間での相対座標に変換するには、ウィンドウでVisual.PointFromScreen()を呼び出す必要があります。

次に、静的メソッドVisualTreeHelper.HitTest(Visual,Point)を呼び出して、thisと、作成したPointを渡します。戻り値は、Zオーダーが最も高いコントロールを示します。

ポイントに対してテストするビジュアルとして、Gridではなくトップレベルのthisコントロールを渡す必要がありました。同様に、ウィンドウかどうかをチェックする代わりに、結果がnullかどうかをチェックする必要がありました。 nullの場合、カーソルはグリッドの子コントロールのいずれにもヒットしていません。つまり、空いているウィンドウフレーム領域にヒットしています。とにかく、重要なのはVisualTreeHelper.HitTest()メソッドを使用することでした。

さて、私の手順に従っている場合に当てはまる可能性のある2つの警告があります。

  1. ウィンドウ全体をカバーせず、ウィンドウフレームを部分的にのみ拡張する場合は、クライアントエリアフィラーとしてウィンドウフレームで塗りつぶされていない四角形の上にコントロールを配置する必要があります。

    私の場合、図に示すように、タブコントロールのコンテンツ領域が長方形の領域にぴったり収まります。アプリケーションでは、RectangleシェイプまたはPanelコントロールを配置して、適切な色にペイントする必要がある場合があります。このようにして、コントロールにヒットします。

    クライアントエリアフィラーに関するこの問題は、次の原因になります。

  2. グリッドまたはその他のトップレベルコントロールに、拡張ウィンドウフレームの背景テクスチャまたはグラデーションがある場合、グリッド領域全体は、完全に透明な領域であってもヒットに応答します。背景( ビジュアルレイヤーでのヒットテスト を参照)。その場合は、グリッド自体へのヒットを無視し、グリッド内のコントロールにのみ注意を払う必要があります。

したがって:

_// In MainWindow
private bool IsOnExtendedFrame(int lParam)
{
    int x = lParam << 16 >> 16, y = lParam >> 16;
    var point = PointFromScreen(new Point(x, y));

    // In XAML: <Grid x:Name="windowGrid">...</Grid>
    var result = VisualTreeHelper.HitTest(windowGrid, point);

    if (result != null)
    {
        // A control was hit - it may be the grid if it has a background
        // texture or gradient over the extended window frame
        return result.VisualHit == windowGrid;
    }

    // Nothing was hit - assume that this area is covered by frame extensions anyway
    return true;
}
_

ウィンドウの空いている領域のみをクリックしてドラッグすると、ウィンドウを移動できるようになりました。

しかし、それだけではありません。最初の図で、ウィンドウの境界を構成する非クライアント領域もHTCAPTIONの影響を受けたため、ウィンドウのサイズを変更できなくなったことを思い出してください。

これを修正するために、カーソルがクライアント領域または非クライアント領域にヒットしているかどうかを確認する必要がありました。これをチェックするために、私は DefWindowProc() 関数を使用してHTCLIENTが返されるかどうかを確認する必要がありました:

_// In my managed DWM API wrapper class, DwmApiInterop
public static bool IsOnClientArea(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam)
{
    if (uMsg == WM_NCHITTEST)
    {
        if (DefWindowProc(hWnd, uMsg, wParam, lParam).ToInt32() == HTCLIENT)
        {
            return true;
        }
    }

    return false;
}

// In NativeMethods
[DllImport("user32.dll")]
private static extern IntPtr DefWindowProc(IntPtr hWnd, int uMsg, IntPtr wParam, IntPtr lParam);
_

最後に、これが最後のウィンドウプロシージャメソッドです。

_// In MainWindow
private IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
{
    switch (msg)
    {
        case DwmApiInterop.WM_NCHITTEST:
            if (DwmApiInterop.IsOnClientArea(hwnd, msg, wParam, lParam)
                && IsOnExtendedFrame(lParam.ToInt32()))
            {
                handled = true;
                return new IntPtr(DwmApiInterop.HTCAPTION);
            }

            return IntPtr.Zero;

        default:
            return IntPtr.Zero;
    }
}
_
29
BoltClock

ここにあなたが試すことができるものがあります:

マウスクリックの位置を取得し(wParam、lParamなどから)、それを使用してPointを作成します(おそらく何らかの座標変換を使用しますか?)。

次に、静的メソッドVisualTreeHelper.HitTest(Visual,Point)を呼び出し、thisと、作成したPointを渡します。戻り値は、Zオーダーが最も高いコントロールを示します。それがあなたのウィンドウであれば、HTCAPTIONブードゥーをします。それが他のコントロールである場合は、そうしないでください。

幸運を!

15

同じことをするために(拡張されたAeroグラスをWPFアプリでドラッグ可能にする)、Googleでこの投稿を見つけました。私はあなたの答えを読みましたが、もっと簡単なものがあるかどうか調べるために検索を続けることにしました。

コードをあまり必要としないソリューションを見つけました。

コントロールの後ろに透明なアイテムを作成し、ウィンドウの DragMove() メソッド。

拡張されたAeroグラスの上に表示される私のXAMLのセクションは次のとおりです。

_<Grid DockPanel.Dock="Top">
    <Border MouseLeftButtonDown="Border_MouseLeftButtonDown" Background="Transparent" />
    <Grid><!-- My controls are in here --></Grid>
</Grid>
_

そして、コードビハインド(これはWindowクラス内にあるため、DragMove()を直接呼び出すことができます):

_private void Border_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
    DragMove();
}
_

以上です!ソリューションでは、長方形以外のドラッグ可能な領域を実現するために、これらを複数追加する必要があります。

6
Ross

シンプルな方法は、スタックパネルまたはタイトルバーXAMLに必要なすべてのものを作成することです

 <StackPanel Name="titleBar" Background="Gray" MouseLeftButtonDown="titleBar_MouseLeftButtonDown" Grid.ColumnSpan="2"></StackPanel>

コード

  private void titleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
     {
         DragMove();
     }
1
Hady Mahmoodi