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コントロール、つまりテキストボックスとタブコントロールも、結果としてクリックの受信を停止します。
私が欲しいのは
ドラッグ可能に。つまり、これらの赤い領域のみをドラッグ可能にしたい(クライアント領域+タイトルバー)。
WndProc()
メソッドと残りのウィンドウのXAML /コードビハインドを変更して、HTCAPTION
を返す領域と返さない領域を決定するにはどうすればよいですか? Point
sを使用して、クリックの場所をコントロールの場所と照合する方法に沿って何かを考えていますが、WPFランドでどのように対処するかわかりません。
EDIT [4/24]:非表示のコントロール、またはウィンドウ自体さえもある場合、DragMove()
を呼び出すことでMouseLeftButtonDown
に応答しますウィンドウ( Rossの答え を参照)。問題は、何らかの理由でウィンドウが最大化されているとDragMove()
が機能しないため、Windows 7 Aeroスナップでナイスを再生できないことです。私はWindows 7の統合を予定しているので、私の場合、それは許容できる解決策ではありません。
今朝受け取ったメールのおかげで、これだけの機能を実演する実用的なサンプルアプリを作成するように求められました。私はそれを今やった。 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つの警告があります。
ウィンドウ全体をカバーせず、ウィンドウフレームを部分的にのみ拡張する場合は、クライアントエリアフィラーとしてウィンドウフレームで塗りつぶされていない四角形の上にコントロールを配置する必要があります。
私の場合、図に示すように、タブコントロールのコンテンツ領域が長方形の領域にぴったり収まります。アプリケーションでは、Rectangle
シェイプまたはPanel
コントロールを配置して、適切な色にペイントする必要がある場合があります。このようにして、コントロールにヒットします。
クライアントエリアフィラーに関するこの問題は、次の原因になります。
グリッドまたはその他のトップレベルコントロールに、拡張ウィンドウフレームの背景テクスチャまたはグラデーションがある場合、グリッド領域全体は、完全に透明な領域であってもヒットに応答します。背景( ビジュアルレイヤーでのヒットテスト を参照)。その場合は、グリッド自体へのヒットを無視し、グリッド内のコントロールにのみ注意を払う必要があります。
したがって:
_// 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;
}
}
_
ここにあなたが試すことができるものがあります:
マウスクリックの位置を取得し(wParam、lParamなどから)、それを使用してPoint
を作成します(おそらく何らかの座標変換を使用しますか?)。
次に、静的メソッドVisualTreeHelper.HitTest(Visual,Point)
を呼び出し、this
と、作成したPoint
を渡します。戻り値は、Zオーダーが最も高いコントロールを示します。それがあなたのウィンドウであれば、HTCAPTION
ブードゥーをします。それが他のコントロールである場合は、そうしないでください。
幸運を!
同じことをするために(拡張された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();
}
_
以上です!ソリューションでは、長方形以外のドラッグ可能な領域を実現するために、これらを複数追加する必要があります。
シンプルな方法は、スタックパネルまたはタイトルバーXAMLに必要なすべてのものを作成することです
<StackPanel Name="titleBar" Background="Gray" MouseLeftButtonDown="titleBar_MouseLeftButtonDown" Grid.ColumnSpan="2"></StackPanel>
コード
private void titleBar_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
DragMove();
}