以前、閉じたときのフォームの位置を記憶する 本当に鳩でも分かるC#講座という記事を書きました。今回はそのアプリケーションのフォームではなく別のアプリケーションのウィンドウの位置を記憶させたり、記憶させていた場所に移動させる方法を考えます。ここでいう「別のアプリケーション」とはC#で書かれているとは限りません。Windowsアプリケーション全般です。
Contents
他のアプリケーションの位置とサイズを知る
まず実行されているアプリケーションのウィンドウの位置を把握しなければなりません。ウィンドウハンドルがわかれば、位置の移動やサイズの変更、さらにはタイトルバーの書き換えなども可能になります。そこで最初にデスクトップに表示されているウィンドウハンドルを取得するところからはじめます。
表示されているすべての親を持たないウィンドウハンドルを取得する
取得の対象は親ウィンドウをもたないウィンドウであり、表示されているウィンドウだけに限定します。表示されることのないウィンドウの位置とかサイズを変更してもあまり意味はないので・・・。
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 |
public partial class Form1 : Form { [DllImport("user32")] extern static int FindWindow(String lpClassName, String lpWindowName); [DllImport("user32")] extern static int GetParent(int hwnd); [DllImport("user32")] extern static int IsWindowVisible(int hwnd); [DllImport("user32")] private extern static int GetWindow(int hwnd, int wCmd); private const int GW_HWNDNEXT = 2; public List<int> GetWindowHandles() { List<int> windowHandles = new List<int>(); int hwnd = FindWindow(null, null); while (hwnd != 0) { if (GetParent(hwnd) == 0 && IsWindowVisible(hwnd) != 0) { windowHandles.Add(hwnd); } hwnd = GetWindow(hwnd, GW_HWNDNEXT); } return windowHandles; } } |
実行ファイルのパスからアプリケーションが実行されているかを知る
ウィンドウハンドルを知ることができてもアプリケーションを終了して再起動すればウィンドウハンドルも変更されてしまいます。しかし実行ファイルがあるパスは変わりません。そこで実行ファイルのパスから現在、そのアプリケーションが実行されていないかを調べる方法を考えます。
System.Management.dllを参照設定に追加すれば、実行されているすべてのプロセスのプロセスID、プロセス名、ファイル名が取得できます。ただしウィンドウをもたないプロセスは必要ないのでHaveWindowHandleで除外しています。
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 |
public partial class Form1 : Form { [DllImport("user32")] private extern static int GetWindowThreadProcessId(int hwnd, out int lpdwprocessid); bool HaveWindowHandle(string id) { List<int> windowHandles = GetWindowHandles(); foreach (int hWnd in windowHandles) { GetWindowThreadProcessId(hWnd, out int lpdwProcessId); if (lpdwProcessId == int.Parse(id)) return true; } return false; } void Function() { 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 (HaveWindowHandle(mo["ProcessId"].ToString())) { // プロセス名 mo["Name"].ToString() // プロセスID mo["ProcessId"].ToString() // 実行ファイルのパス mo["ExecutablePath"].ToString() } } } } } |
GetWindowThreadProcessId関数を使えばウィンドウハンドルからプロセスIDを調べることができます。また上記のやり方でプロセスID、プロセス名、ファイル名がセットで取得できるので、実行ファイルのパスからウィンドウハンドルを求めることができるのです。もちろんそのときにそのアプリが実行されてたらという条件つきですが・・・。
アプリの作成
では早速はじめましょう。まず仕様については以下のとおりです。
作成するアプリの仕様について
まずリストビューをふたつ作成します。ひとつは現在実行されているプロセスを表示するためのものです。ここにはプロセス名、プロセスID、実行ファイルのパスが表示されます。
そして[アプリケーションを監視する]ボタンをおすとそのアプリケーションウィンドウの位置とサイズが0.5おきに表示されます。途中で監視していたアプリケーションを終了すると最後に取得されたウィンドウの位置とサイズが表示されたままになります。
[登録する]ボタンをおすと監視しているアプリケーションのウィンドウの位置とサイズが実行ファイルのパスとともに登録されます。この時点ではファイルに保存されるわけではありません。
[保存する]ボタンをおすと登録された内容がファイルとして登録されます。また[読み込み]ボタンを押すとファイルとして保存されていた内容が読み込まれます。
最後に[反映させる]ボタンがおされると、もしそのアプリケーションが実行されていた場合、ウィンドウの位置とサイズが登録されていた状態に変更されます。
でざいな
リストビューの初期化
アプリケーションが開始されたらふたつのリストビューを初期化します。
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 |
public partial class Form1 : Form { public Form1() { InitializeComponent(); InitListView1(); InitListView2(); } void InitListView1() { listView1.View = View.Details; listView1.GridLines = true; listView1.FullRowSelect = true; ColumnHeader columnHeader; columnHeader = new ColumnHeader(); columnHeader.Text = "プロセス名"; listView1.Columns.Add(columnHeader); columnHeader = new ColumnHeader(); columnHeader.Text = "プロセスID"; listView1.Columns.Add(columnHeader); columnHeader = new ColumnHeader(); columnHeader.Text = "ファイル名"; columnHeader.Width = 300; listView1.Columns.Add(columnHeader); } void InitListView2() { listView2.View = View.Details; listView2.GridLines = true; listView2.FullRowSelect = true; ColumnHeader columnHeader; columnHeader = new ColumnHeader(); columnHeader.Text = "ファイル名"; columnHeader.Width = 300; listView2.Columns.Add(columnHeader); columnHeader = new ColumnHeader(); columnHeader.Text = "Location"; listView2.Columns.Add(columnHeader); columnHeader = new ColumnHeader(); columnHeader.Text = "Size"; listView2.Columns.Add(columnHeader); } } |
実行されているプロセスを取得する
次に[実行されているプロセスを取得する]ボタンが押されたときの処理を示します。現在実行されているプロセスのなかでウィンドウが表示されているものだけ集めて、リストビューに表示させています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public partial class Form1 : Form { private void buttonGetAllProcessInfo_Click(object sender, EventArgs e) { listView1.BeginUpdate(); listView1.Items.Clear(); 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 (HaveWindowHandle(mo["ProcessId"].ToString())) { ListViewItem listViewItem = new ListViewItem(mo["Name"].ToString()); listViewItem.SubItems.Add(mo["ProcessId"].ToString()); listViewItem.SubItems.Add(mo["ExecutablePath"].ToString()); listView1.Items.Add(listViewItem); } } } listView1.EndUpdate(); } } |
アプリケーションを監視する
次に[アプリケーションを監視する]ボタンが押されたときの処理を示します。
まずリストビューでどのアイテムが選択されているか調べます。そして選択されているアイテムからプロセスのIDと実行ファイルのパスがわかるので、そこから探したいウィンドウハンドルを探します。
ウィンドウハンドルを取得することができたら自作メソッドのShowWindowInfoでウィンドウの位置とサイズを表示します。
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 58 59 60 |
public partial class Form1 : Form { string ExecutablePath = ""; // 監視しようとしているアプリケーションの実行ファイルのパス int WindowHandle = 0; // 監視しようとしているアプリケーションのウィンドウハンドル int ProcessId = 0; // 監視しようとしているプロセスのID Point LastPoint = Point.Empty; Size LastSize = Size.Empty; private void buttonMonitorApplication_Click(object sender, EventArgs e) { // 選択されているリストアイテムから監視しようとしているプロセスに関する情報を集める var items = listView1.SelectedItems; if (items.Count == 0) return; // タイマーが動いているときに実行されることもあるのでいったん止める // Timer.Tickのイベントハンドラも外す Timer.Stop(); Timer.Tick -= Timer_Tick; ExecutablePath = items[0].SubItems[2].Text; // 実行ファイルのパス int id = int.Parse(items[0].SubItems[1].Text); // プロセスのID // 表示されているウィンドウのウィンドウハンドルをすべて集める List<int> windowHandles = GetWindowHandles(); WindowHandle = 0; // いったんフィールド変数に保存されているウィンドウハンドルをクリア foreach (int hWnd in windowHandles) { // ウィンドウハンドルからプロセスIDを取得する。探したいidと一致しているだろうか? GetWindowThreadProcessId(hWnd, out int lpdwProcessId); if (lpdwProcessId == id) { // 一致している場合はフィールド変数にウィンドウハンドルとプロセスIDを保存してから // ウィンドウハンドルからウィンドウの位置・サイズを取得して表示させる WindowHandle = hWnd; ProcessId = id; ShowWindowInfo(); // 以降、0.5秒おきにウィンドウの位置とサイズを表示させる Timer.Interval = 500; Timer.Tick += Timer_Tick; Timer.Start(); break; } } // WindowHandle == 0のときはウィンドウハンドルを取得できなかった場合である if (WindowHandle == 0) { Timer.Stop(); Timer.Tick -= Timer_Tick; ShowWindowInfoNone(); MessageBox.Show("取得できませんでした"); } } } |
ウィンドウの位置とサイズをリアルタイムで表示する
ShowWindowInfoメソッドでおこなわれる処理を示します。API関数のGetWindowRect関数からウィンドウの位置とサイズを求めてこれを表示します。
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 |
public partial class Form1 : Form { [DllImport("user32.dll")] private static extern bool GetWindowRect(int hwnd, out RECT lpRect); [StructLayout(LayoutKind.Sequential)] private struct RECT { public int left; public int top; public int right; public int bottom; } void ShowWindowInfo() { GetWindowRect(WindowHandle, out RECT rect); label1.Text = String.Format("プロセスID:{0} を監視中!", ProcessId); label2.Text = String.Format("X = {0}, Y = {1}", rect.left, rect.top); label3.Text = String.Format("Width = {0}, Height = {1}", rect.right - rect.left, rect.bottom - rect.top); // 最後に取得されたデータを保存しておく LastPoint = new Point(rect.left, rect.top); LastSize = new Size(rect.right - rect.left, rect.bottom - rect.top); } // ウィンドウハンドルを取得できなかったら情報なしを表示する void ShowWindowInfoNone() { label1.Text = "プロセスID:None"; label2.Text = "ウィンドウの座標:None"; label3.Text = "ウィンドウのサイズ:None"; } } |
途中でアプリケーションが終了したときの対策
またタイマーが起動しているときは監視しているアプリケーションの情報を表示しつづければよいのですが、ユーザーによって終了されるかもしれないので、その対策もしておきます。IsWindow関数は渡されたウィンドウハンドルが無効である場合は0を返します。そこでIsWindow(WindowHandle) == 0のときはタイマーを停止させて最後に取得されたウィンドウの状態を表示します。
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 |
public partial class Form1 : Form { [DllImport("user32.dll", CharSet = CharSet.Auto)] static extern int IsWindow(int hWnd); private void Timer_Tick(object sender, EventArgs e) { if (WindowHandle == 0) return; if (IsWindow(WindowHandle) == 0) { WindowHandle = 0; Timer.Stop(); Timer.Tick -= Timer_Tick; ShowLastWindowInfo(); return; } else { ShowWindowInfo(); } } // ウィンドウハンドルが無効のときは、プロセスが終了した旨を表示し、 // 最後に取得されたデータを表示する void ShowLastWindowInfo() { label1.Text = String.Format("プロセスID:{0} が終了!", ProcessId); label2.Text = String.Format("X = {0}, Y = {1}", LastPoint.X, LastPoint.Y); label3.Text = String.Format("Width = {0}, Height = {1}", LastSize.Width, LastSize.Height); } } |
ウィンドウの位置とサイズの登録
[登録する]ボタンが押されたときの処理を示します。AppInfoオブジェクトを生成してそのなかにデータを保存します。そしてリストのなかに格納します。それと同時にふたつめのリストビューに登録したデータ(実行ファイルのパス、ウィンドウの位置とサイズ)を表示させます。
1 2 3 4 5 6 7 8 |
public class AppInfo { public string ExecutablePath = ""; public int LastPointX = 0; public int LastPointY = 0; public int LastSizeWidth = 0; public int LastSizeHeight = 0; } |
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 |
public partial class Form1 : Form { List<AppInfo> AppInfos = new List<AppInfo>(); private void buttonRegister_Click(object sender, EventArgs e) { AppInfo appInfo = new AppInfo(); appInfo.ExecutablePath = ExecutablePath; appInfo.LastPointX = LastPoint.X; appInfo.LastPointY = LastPoint.Y; appInfo.LastSizeWidth = LastSize.Width; appInfo.LastSizeHeight = LastSize.Height; AppInfos.Add(appInfo); AddListViewItem2(); } void AddListViewItem2() { listView2.BeginUpdate(); listView2.Items.Clear(); foreach (AppInfo info in AppInfos) { ListViewItem listViewItem = new ListViewItem(info.ExecutablePath); listViewItem.SubItems.Add(String.Format("{0}, {1}", info.LastPointX, info.LastPointY)); listViewItem.SubItems.Add(String.Format("{0}, {1}", info.LastSizeWidth, info.LastSizeHeight)); listView2.Items.Add(listViewItem); } listView2.EndUpdate(); } } |
ファイルとして保存する
[保存する]ボタンが押されたときの処理を示します。Docオブジェクトを作成して、これをXMLファイルとして保存します。
1 2 3 4 5 6 7 |
using System.Xml.Serialization; using System.IO; public class Doc { public List<AppInfo> AppInfos = new List<AppInfo>(); } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
public partial class Form1 : Form { private void buttonSaveFile_Click(object sender, EventArgs e) { SaveFileDialog dialog = new SaveFileDialog(); dialog.Filter = "データ(*.dat)|*.dat"; if (dialog.ShowDialog() == DialogResult.OK) { Doc doc = new Doc(); doc.AppInfos = AppInfos; XmlSerializer xml = new XmlSerializer(typeof(Doc)); StreamWriter sw = new StreamWriter(dialog.FileName); xml.Serialize(sw, doc); sw.Close(); } dialog.Dispose(); } } |
ファイルを読み出す
[読み込み]ボタンが押されたときの処理を示します。ファイルからデータを読み出して、ここからふたつめのリストビューにデータを表示させます。
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 { private void buttonOpenFile_Click(object sender, EventArgs e) { OpenFileDialog dialog = new OpenFileDialog(); dialog.Filter = "データ(*.dat)|*.dat"; if (dialog.ShowDialog() == DialogResult.OK) { XmlSerializer xml = new XmlSerializer(typeof(Doc)); StreamReader sr = new StreamReader(dialog.FileName); Doc doc = (Doc)xml.Deserialize(sr); sr.Close(); AppInfos = doc.AppInfos; AddListViewItem2(); } dialog.Dispose(); } } |
情報を反映させる
[反映させる]ボタンを押したときの処理を示すまえに、ウィンドウを移動させるAPI関数を使った移動の処理を示します。自作メソッドのSetWindowPosから同名のAPI関数SetWindowPosを呼び出しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public partial class Form1 : Form { [DllImport("user32.dll", SetLastError = true)] [return: MarshalAs(UnmanagedType.Bool)] private static extern bool SetWindowPos(int hWnd, int hWndInsertAfter, int x, int y, int cx, int cy, int uFlags); private const int SWP_SHOWWINDOW = 0x0040; const int HWND_NOTOPMOST = -2; void SetWindowPos(int hWnd, Point point, Size size) { SetWindowPos(hWnd, HWND_NOTOPMOST, point.X, point.Y, size.Width, size.Height, SWP_SHOWWINDOW); } } |
では[反映させる]ボタンを押したときの処理を示します。ふたつめのリストビューで選択されている位置とAppInfosのインデックスから実行ファイルのパスを取得します。この実行ファイルのパスをもつアプリケーションが実行されているときはそのウィンドウの位置とサイズを登録されている位置に変更します。
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 |
public partial class Form1 : Form { private void buttonReflect_Click(object sender, EventArgs e) { var indexes = listView2.SelectedIndices; int index = indexes[0]; string executablePath = AppInfos[index].ExecutablePath; string idString = ""; // まず実行されているプロセスのなかで実行ファイルのパスがexecutablePathであるものを探す 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 (HaveWindowHandle(mo["ProcessId"].ToString())) { if (mo["ExecutablePath"].ToString() == executablePath) { idString = mo["ProcessId"].ToString(); break; } } } } // 見つからない場合はなにもしない if (idString == "") return; // 実行されているプロセスのなかでパスがexecutablePathであるものが見つかった場合は // そのウィンドウハンドルを取得する // すべての親を持たないウィンドウハンドルを取得する // GetWindowThreadProcessId関数で得られるIDと一致するものがあるはずなので探す List<int> windowHandles = GetWindowHandles(); int id = int.Parse(idString); foreach (int hWnd in windowHandles) { GetWindowThreadProcessId(hWnd, out int lpdwProcessId); if (lpdwProcessId == id) { // 見つかったらウィンドウの位置とサイズを変更 int x = AppInfos[index].LastPointX; int y = AppInfos[index].LastPointY; int w = AppInfos[index].LastSizeWidth; int h = AppInfos[index].LastSizeHeight; SetWindowPos(hWnd, new Point(x, y), new Size(w, h)); break; } } } } |