ウィンドウの大きさを自由に変更できたらいいのに……と思うことがあります。自分で作成したアプリであれば表示位置とサイズを自由に設定できますが、他人がつくったアプリの場合はそうはいきません。さてどうしたものでしょうか?
ここはAPI関数をつかいましょう。
1 2 |
[DllImport("user32.dll", SetLastError = true)] public static extern IntPtr WindowFromPoint(POINT point); |
POINTからウィンドウハンドルを取得できます。
1 2 3 |
[DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int y, int cx, int cy, int uFlags); |
ウィンドウの位置とサイズを変更することができます。最後のuFlagsを指定することで、位置だけとかサイズだけとか常に最前面になるようにすることができます。
1 2 |
[DllImport("user32.dll", SetLastError = true)] public static extern IntPtr GetAncestor(IntPtr hWnd, uint gaFlags); |
指定したウィンドウの祖先のハンドルを取得します。第二引数をGA_PARENTにすることで親ウィンドウを取得、GA_ROOTにすると親ウィンドウのチェーンをたどってルートウィンドウを取得、GA_ROOTOWNERにすることでGetParent 関数が返す親ウィンドウのチェーンをたどって所有されているルートウィンドウを取得します。
これであるポイントを指定すればそこにあるウィンドウのサイズを変更することができます。
デザイナで以下のようなものをつくります。
○と×が組み合わさったマークはビットマップ(64×64ピクセル)です。
コンストラクタ内でこのようにやっておけば背景が透明になります。
1 2 3 4 5 6 7 8 9 10 11 |
public partial class Form1 : Form { Bitmap bmp = null; public Form1() { bmp = (Bitmap)pictureBox1.Image; bmp.MakeTransparent(); pictureBox1.Image = bmp; } } |
そしてピクチャーボックスのうえでクリックされたらカーソルを変更します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
public partial class Form1 : Form { private void PictureBox1_MouseDown(object sender, MouseEventArgs e) { if(e.Button == MouseButtons.Left) { Cursor cur = CreateCursor(bmp, 32, 32); Cursor.Current = cur; pictureBox1.Image = null; } } [DllImport("user32.dll")] [return: MarshalAs(UnmanagedType.Bool)] public static extern bool GetIconInfo(IntPtr hIcon, ref IconInfo pIconInfo); [DllImport("user32.dll")] public static extern IntPtr CreateIconIndirect(ref IconInfo icon); public struct IconInfo { public bool fIcon; // アイコンの場合 true, カーソルの場合 false public int xHotspot; // カーソルのホットスポットの X 座標 public int yHotspot; // カーソルのホットスポットの Y 座標 public IntPtr hbmMask; // 透過用のビットマップハンドル public IntPtr hbmColor; // 画像用のビットマップハンドル } public static Cursor CreateCursor(Bitmap bmp, int xHotSpot, int yHotSpot) { IntPtr ptr = bmp.GetHicon(); IconInfo tmp = new IconInfo(); GetIconInfo(ptr, ref tmp); tmp.xHotspot = xHotSpot; tmp.yHotspot = yHotSpot; tmp.fIcon = false; ptr = CreateIconIndirect(ref tmp); return new Cursor(ptr); } } |
これでマークがとれてカーソルになったように見えます。
ピクチャーボックスでクリックされたらマウスキャプチャします。そうすればこのフォームの外でマウスボタンが離されてもそれを検知することができます。
マウスキャプチャが解除されたらピクチャーボックス上のビットマップを元に戻します。そして自分自身の上でドロップされなかった場合はそのウィンドウの位置情報を表示させます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 |
public partial class Form1 : Form { Bitmap bmp = null; bool isDraging = false; public Form1() { InitializeComponent(); MouseCaptureChanged += Form1_MouseCaptureChanged; pictureBox1.MouseDown += PictureBox1_MouseDown; // 絶対値が大きめの値を指定しておく numericUpDownX.Minimum = -10000; numericUpDownY.Minimum = -10000; numericUpDownWidth.Minimum = -10000; numericUpDownHeight.Minimum = -10000; numericUpDownX.Maximum = 10000; numericUpDownY.Maximum = 10000; numericUpDownWidth.Maximum = 10000; numericUpDownHeight.Maximum = 10000; // ピクチャーボックス上のビットマップの背景を透明にする bmp = (Bitmap)pictureBox1.Image; bmp.MakeTransparent(); pictureBox1.Image = bmp; } private void PictureBox1_MouseDown(object sender, MouseEventArgs e) { if(e.Button == MouseButtons.Left) { isDraging = true; Cursor cur = CreateCursor(bmp, 32, 32); Cursor.Current = cur; pictureBox1.Image = null; Capture = true; } } private void Form1_MouseCaptureChanged(object sender, EventArgs e) { if(!this.Capture && isDraging) { isDraging = false; Rectangle rect = new Rectangle(this.Location, new Size(this.Width, this.Height)); Point mousePos = Control.MousePosition; if(mousePos.X < rect.Left || rect.Right < mousePos.X || mousePos.Y < rect.Top || rect.Bottom < mousePos.Y) { // キャプチャが解除されたとき自分自身の上でないときは情報を表示 ShowData(mousePos); } } } } |
さてどのような情報を表示させるかですが、ウィンドウをX、Y座標、幅、高さ、プロセス名、プロセスID、実行ファイルのパスを表示させます。
まずウィンドウハンドルを取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
public partial class Form1 : Form { [StructLayout(LayoutKind.Sequential)] public struct POINT { public int x; public int y; } [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr WindowFromPoint(POINT point); [DllImport("user32.dll", SetLastError = true)] public static extern IntPtr GetAncestor(IntPtr hWnd, uint gaFlags); public const uint GA_ROOT = 2; IntPtr GetRootWindowHandle(Point mousePos) { POINT pt = new POINT(); pt.x = mousePos.X; pt.y = mousePos.Y; IntPtr hWnd = WindowFromPoint(pt); return GetAncestor(hWnd, GA_ROOT); } } |
これはウィンドウハンドルからウィンドウのタイトルを取得するメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 |
public partial class Form1 : Form { [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] public static extern int GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount); string GetWindowText(IntPtr hWnd) { StringBuilder sb = new StringBuilder(1024); GetWindowText(hWnd, sb, sb.Capacity); return sb.ToString(); } } |
これはウィンドウハンドルからプロセスIDをメソッドです。
1 2 3 4 5 6 7 8 9 10 11 12 |
public partial class Form1 : Form { [DllImport("user32")] private static extern int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); int GetProcessID(IntPtr hWnd) { int processId; GetWindowThreadProcessId(hWnd, out processId); return processId; } } |
これはプロセスIDからプロセス名を取得するメソッドです。
1 2 3 4 5 6 7 8 |
public partial class Form1 : Form { string GetProcessName(int processId) { System.Diagnostics.Process p = System.Diagnostics.Process.GetProcessById(processId); return p.ProcessName.ToString(); } } |
さて実行ファイルのパスはプロセスIDを使って
1 2 |
System.Diagnostics.Process p = System.Diagnostics.Process.GetProcessById(processId); string str = p.MainModule.FileName; |
で取得できるのですが、64ビット環境ではうまくいかない場合があります。プロジェクトのプロパティを適切にセットすればいいのですが、ここでは別の方法を考えます。
System.Management.dllを参照設定に追加すれば、以下の方法で実行されているすべてのプロセスのプロセスID、プロセス名、ファイル名が取得できます。そこで実行されているすべてのプロセスを取得してforeach文で探すという方法です。
1 2 3 4 5 6 7 8 9 10 |
using(System.Management.ManagementClass mc = new System.Management.ManagementClass("Win32_Process")) using(System.Management.ManagementObjectCollection moc = mc.GetInstances()) { foreach(System.Management.ManagementObject mo in moc) { Console.WriteLine("プロセス名:{0}", mo["Name"]); Console.WriteLine("プロセスID:{0}", mo["ProcessId"]); Console.WriteLine("ファイル名:{0}", mo["ExecutablePath"]); } } |
このメソッドはプロセスIDから実行ファイルのパスを取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
public partial class Form1 : Form { string GetPath(int processId) { using(System.Management.ManagementClass mc = new System.Management.ManagementClass("Win32_Process")) using(System.Management.ManagementObjectCollection moc = mc.GetInstances()) { foreach(System.Management.ManagementObject mo in moc) { if(processId.ToString() != mo["ProcessId"].ToString()) continue; else return mo["ExecutablePath"].ToString(); } return ""; } } } |
このメソッドはウィンドウの左上の座標を取得します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
public partial class Form1 : Form { [DllImport("user32.dll")] private static extern bool GetWindowRect(IntPtr hwnd, out RECT lpRect); [StructLayout(LayoutKind.Sequential)] private struct RECT { public int left; public int top; public int right; public int bottom; } Point GetWindowLocation(IntPtr hWnd) { RECT rect; GetWindowRect(hWnd, out rect); return new Point(rect.left, rect.top); } } |
このメソッドはウィンドウのサイズを取得します。
1 2 3 4 5 6 7 8 9 |
public partial class Form1 : Form { Size GetWindowSize(IntPtr hWnd) { RECT rect; GetWindowRect(hWnd, out rect); return new Size(rect.right - rect.left, rect.bottom - rect.top); } } |
したがって以下の方法で、マウスキャプチャが解除されたときのマウスポインタの位置からウィンドウのX、Y座標、幅、高さ、プロセス名、プロセスID、実行ファイルのパスを取得できます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
public partial class Form1 : Form { void ShowData(Point mousePos) { // あとで使うので保存しておく hWndRoot = GetRootWindowHandle(mousePos); textBoxTitle.Text = GetWindowText(hWndRoot); int processId = GetProcessID(hWndRoot); textBoxProcessID.Text = processId.ToString(); textBoxProcessName.Text = GetProcessName(processId); textBoxPath.Text = GetPath(processId); Point pt = GetWindowLocation(hWndRoot); Size size = GetWindowSize(hWndRoot); numericUpDownX.Value = pt.X; numericUpDownY.Value = pt.Y; numericUpDownWidth.Value = size.Width; numericUpDownHeight.Value = size.Height; } } |
次にウィンドウの位置とサイズを変更する方法を考えます。さきほど保存しておいたウィンドウハンドルをSetWindowPos関数に渡せば可能です。ただその前に本当に有効なウィンドウハンドルが保存されているかどうか調べる必要があります。
1 2 |
[DllImport("user32.dll", CharSet = CharSet.Auto)] static extern int IsWindow(IntPtr hWnd); |
API関数 IsWindow関数はウィンドウが存在すると0以外の値を返します。
SetWindowPosメソッドは指定した場所に指定したサイズでウィンドウを表示させるメソッドです。ウィンドウハンドルが不正な場合はfalseを返します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
public partial class Form1 : Form { [DllImport("user32.dll", CharSet = CharSet.Auto)] static extern int IsWindow(IntPtr hWnd); [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool SetWindowPos(IntPtr hWnd, int hWndInsertAfter, int x, int y, int cx, int cy, int uFlags); private const int SWP_SHOWWINDOW = 0x0040; bool SetWindowPos(IntPtr hWnd, Point point, Size size) { if(IsWindow(hWndRoot) != 0) return SetWindowPos(hWnd, HWND_NOTOPMOST, point.X, point.Y, size.Width, size.Height, SWP_SHOWWINDOW); else return false; } } |
有効なウィンドウハンドルが保存されている状態であれば、適切な座標とサイズを指定するとウィンドウを移動させます。そうでない場合はエラーを示すメッセージボックスが表示されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
public partial class Form1 : Form { private void buttonSetSize_Click(object sender, EventArgs e) { int x = (int)numericUpDownX.Value; int y = (int)numericUpDownY.Value; int width = (int)numericUpDownWidth.Value; int height = (int)numericUpDownHeight.Value; if(!SetWindowPos(hWndRoot, new Point(x, y), new Size(width, height))) MessageBox.Show("有効なウィンドウハンドルが保存されていません。", "エラー", MessageBoxButtons.OK, MessageBoxIcon.Error); } } |