【C#】ウィンドウ列挙とアクティブウィンドウ切り替え
2009-10-03 04:03:07 Sat
あれから・・・
うまくいかないことがあり、ちょっといろいろと試しました。
今はとりあえずこれに落ち着いていますが、なぜうまく動作しないかチェックしていません。
アプリ作成環境ではうまく動いていたのですが、実際に使用を想定している端末では、
どうもウィンドウの出方が違うのか、はたまたイベント絡みでうまくいかないことがるのか。。。
ともあれ。
何を増やしたかあまり定かでないくらいにカオス&スパゲッティになってしまったのですが、
// EnumWindows API関数の宣言
[DllImport("user32", EntryPoint = "EnumWindows")]
extern static int EnumWindows(EnumerateWindowsCallback lpEnumFunc, int lParam);
によって、デスクトップのアクティブなウィンドウ以外も取得できます。
同じプロセス内の親ウィンドウや子ウィンドウも可能です。
//指定Window プロセスID取得API関数の宣言
[DllImport("user32.dll",CharSet = System.Runtime.InteropServices.CharSet.Auto)]
extern static IntPtr FindWindow(string lpClassName, string lpWindowName);
これは引数で指定したプロセスIDを取得します。
あるクラス名やウィンドウ名のプロセスを取得する場合に使用します。
//指定Window アクティブ変更API関数の宣言
[DllImport("user32.dll")]
extern static bool SetForegroundWindow(IntPtr hWnd);
指定したプロセスIDをアクティブなウィンドウにする忌み嫌われているようなAPI。
むりやり。。。
あ、一番初めに
// コールバックメソッドのデリゲート
private delegate int EnumerateWindowsCallback(IntPtr hWnd, int lParam);
を記述したのですが、デリゲートについてはあまりちゃんと理解できていません。
今後の課題ですがおまじないでとりあえず。
// ウィンドウを列挙するためのコールバックメソッド
public static int EnumerateWindows(IntPtr hWnd, int lParam)
このあたりもコールバックメソッドというのがちゃんと理解できていない。
他人のふんどしで土俵をとっているだけとも、
守破離ともいう。
守破離としたいと思う。
今は先人に学ぶとき。。。。
うまくいかないことがあり、ちょっといろいろと試しました。
今はとりあえずこれに落ち着いていますが、なぜうまく動作しないかチェックしていません。
アプリ作成環境ではうまく動いていたのですが、実際に使用を想定している端末では、
どうもウィンドウの出方が違うのか、はたまたイベント絡みでうまくいかないことがるのか。。。
ともあれ。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; using System.Diagnostics; //[DllImport xxxx]するために必要です。 using System.Runtime.InteropServices; //ファイル入出力のために必要です。 using System.IO; namespace WindowsFormsApplication1 { public partial class Form1 : Form { // コールバックメソッドのデリゲート private delegate int EnumerateWindowsCallback(IntPtr hWnd, int lParam); //Windows API のための宣言 [DllImport("user32.dll")] extern static IntPtr GetForegroundWindow(); [DllImport("user32.dll")] extern static int GetWindowText(IntPtr hWnd, StringBuilder text, int length); [DllImport("user32.dll")] extern static int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); // EnumWindows API関数の宣言 [DllImport("user32", EntryPoint = "EnumWindows")] extern static int EnumWindows(EnumerateWindowsCallback lpEnumFunc, int lParam); // IsWindowVisible API関数の宣言 [DllImport("user32", EntryPoint = "IsWindowVisible")] extern static int IsWindowVisible(IntPtr hWnd); //指定Window プロセスID取得API関数の宣言 [DllImport("user32.dll",CharSet = System.Runtime.InteropServices.CharSet.Auto)] extern static IntPtr FindWindow(string lpClassName, string lpWindowName); //指定Window アクティブ変更API関数の宣言 [DllImport("user32.dll")] extern static bool SetForegroundWindow(IntPtr hWnd); static public StreamWriter LogFile = new StreamWriter(System.Environment.GetFolderPath(Environment.SpecialFolder.Personal) + "\\LogFile.txt", true); //直前の作業中ウィンドウタイトルを保存しておく変数を宣言します。 static public String WindowCheck = ""; public Form1() { InitializeComponent(); Application.ApplicationExit += new EventHandler(this.ThisApplicationExit); } //タイマーが有効なときに、定期的に発生するイベント private void timer1_Tick(object sender, EventArgs e) { //現在時刻を取得します。ログに記録する時刻、および時計表示に使います。 // String LoggingTime = System.DateTime.Now.ToString().Substring(11, 8); String LoggingTime = System.DateTime.Now.ToString(); //ウィンドウタイトルの取得 IntPtr hWnd = GetForegroundWindow(); StringBuilder Caption = new StringBuilder(1024); GetWindowText(hWnd, Caption, 1024); label1.Text = "現在の時刻 : " + LoggingTime; //自身のフォームがアクティブでなければ強制的にアクティブにする IntPtr intPrt = FindWindow(null, this.Text); if (intPrt != IntPtr.Zero) { SetForegroundWindow(intPrt); } if (Caption.ToString() != WindowCheck) { WindowCheck = Caption.ToString(); LogFile.WriteLine(LoggingTime + "<>" + Caption); if (Caption.ToString().Contains("エラー")) { // MessageBox.Show("電卓が選択されている"); // ウィンドウハンドルからプロセスIDを取得 int id; GetWindowThreadProcessId(hWnd, out id); Process process = Process.GetProcessById(id); process.CloseMainWindow(); process.Close(); process.Dispose(); } // 列挙を開始 EnumWindows(new EnumerateWindowsCallback(EnumerateWindows), 0); } } private void Form1_Load(object sender, EventArgs e) { LogFile.WriteLine(System.DateTime.Now.ToString() + "<>「作業ログの取得と記録」が起動しました。"); // MessageBox.Show ("アプリケーションの起動。"); } //ApplicationExitイベントハンドラ private void ThisApplicationExit(object sender, EventArgs e) { // MessageBox.Show("アプリケーションの終了。"); LogFile.WriteLine(System.DateTime.Now.ToString() + "<>「作業ログの取得と記録」を終了しました。"); LogFile.Close(); //ApplicationExitイベントハンドラを削除 Application.ApplicationExit -= new EventHandler(ThisApplicationExit); } private void label1_Click(object sender, EventArgs e) { } // ウィンドウを列挙するためのコールバックメソッド public static int EnumerateWindows(IntPtr hWnd, int lParam) { // ウィンドウが可視の場合 if (IsWindowVisible(hWnd) != 0) { StringBuilder sb = new StringBuilder(0x1000); // ウィンドウのキャプションを取得 if (GetWindowText(hWnd, sb, 0x1000) != 0) { if (sb.ToString().Contains("エラー")) { // ウィンドウハンドルからプロセスIDを取得 int id; GetWindowThreadProcessId(hWnd, out id); Process process = Process.GetProcessById(id); process.CloseMainWindow(); process.Close(); process.Dispose(); } } } // 列挙を継続するには0以外を返す必要がある return 1; } } }
何を増やしたかあまり定かでないくらいにカオス&スパゲッティになってしまったのですが、
// EnumWindows API関数の宣言
[DllImport("user32", EntryPoint = "EnumWindows")]
extern static int EnumWindows(EnumerateWindowsCallback lpEnumFunc, int lParam);
によって、デスクトップのアクティブなウィンドウ以外も取得できます。
同じプロセス内の親ウィンドウや子ウィンドウも可能です。
//指定Window プロセスID取得API関数の宣言
[DllImport("user32.dll",CharSet = System.Runtime.InteropServices.CharSet.Auto)]
extern static IntPtr FindWindow(string lpClassName, string lpWindowName);
これは引数で指定したプロセスIDを取得します。
あるクラス名やウィンドウ名のプロセスを取得する場合に使用します。
//指定Window アクティブ変更API関数の宣言
[DllImport("user32.dll")]
extern static bool SetForegroundWindow(IntPtr hWnd);
指定したプロセスIDをアクティブなウィンドウにする忌み嫌われているようなAPI。
むりやり。。。
あ、一番初めに
// コールバックメソッドのデリゲート
private delegate int EnumerateWindowsCallback(IntPtr hWnd, int lParam);
を記述したのですが、デリゲートについてはあまりちゃんと理解できていません。
今後の課題ですがおまじないでとりあえず。
// ウィンドウを列挙するためのコールバックメソッド
public static int EnumerateWindows(IntPtr hWnd, int lParam)
このあたりもコールバックメソッドというのがちゃんと理解できていない。
他人のふんどしで土俵をとっているだけとも、
守破離ともいう。
守破離としたいと思う。
今は先人に学ぶとき。。。。
【C#】プロセスハンドル
2009-10-03 02:09:04 Sat
アクティブウィンドウのプロセスを取得して、
そのウィンドウが特定の名前ならウィンドウを閉じてしまいたい、
ということをやりたくなりました。
以前にとある雑誌でC#の記事があって、とりあえず作ってみたのが、
アクティブウィンドウのタイトルを1秒ごとに取得して前のタイトルと異なっていれば、
MyDocumentの直下にLogFile.logというログファイルに時間とタイトルを書き込んでいくアプリを作成しました。
これを利用して作ってみました。
いくつか要点を。
.NETフレームワークにはアクティブなウィンドウを取得するAPIはなさそう(と書いていたきがするけどもうあるかもしれない)なので、WindowsAPIを利用して取得することに。
そのための記述は以下。
//Windows API のための宣言
[DllImport("user32.dll")]
extern static IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
extern static int GetWindowText(IntPtr hWnd, StringBuilder text, int length);
[DllImport("user32.dll")]
extern static int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
上から順に
ウィンドウを取得
ウィンドウタイトルを取得
ウィンドウのプロセスIDを取得
となっています。
すべてuser32.dllを参照しています。
クラスのコンストラクタで
public Form1()
{
InitializeComponent();
Application.ApplicationExit += new EventHandler(this.ThisApplicationExit);
}
と記述することで、ウィンドウを終了するときのイベントを自身のアプリケーションに追加しています。
終了時に実行されるのは、this.ThisApplicationExitとして渡したユーザ定義メソッドです。
このフォームアプリは、Timer(timer1という名前)を仕込んでいます。
0.1秒(100ミリ秒)ごとにイベントが送出されるのでこれを以下のようにハンドルしています。
//タイマーが有効なときに、定期的に発生するイベント
private void timer1_Tick(object sender, EventArgs e)
{
//なんか処理
}
イベント関連を最後まで流すと、
private void Form1_Load(object sender, EventArgs e)
{
LogFile.WriteLine(System.DateTime.Now.ToString() + "<>「作業ログの取得と記録」が起動しました。");
// MessageBox.Show ("アプリケーションの起動。");
}
にて、フォームのロード時に1回だけ上記のメソッドにロードイベントが送出され、ログファイルにシステム時間を記述しています。
そして最後に、上記のコンストラクタで追加したアプリケーション終了時のメソッド。
//ApplicationExitイベントハンドラ
private void ThisApplicationExit(object sender, EventArgs e)
{
// MessageBox.Show("アプリケーションの終了。");
LogFile.WriteLine(System.DateTime.Now.ToString() + "<>「作業ログの取得と記録」を終了しました。");
LogFile.Close();
//ApplicationExitイベントハンドラを削除
Application.ApplicationExit -= new EventHandler(ThisApplicationExit);
}
最後に、このアプリケーションからアプリケーション終了時のイベントを削除しています。
これをやらないとメモリにいつまでも残ってしまうようです。
アクティブなウィンドウのプロセスIDを取得し、
そのプロセスIDをもとにプロセスを取得するのは以下。
// ウィンドウハンドルからプロセスIDを取得
int id;
GetWindowThreadProcessId(hWnd, out id);
System.Diagnostics.Process process = System.Diagnostics.Process.GetProcessById(id);
process.CloseMainWindow();
process.Close();
process.Dispose();
これによって、プロセスをアプリの好きにできます。
ここまできたらもう今回の目的は果たせそうです。
Process#Kill はコンソールアプリに使うべきで、GUIアプリには使うべきではないようなので、
Process#CloseMainWindow でウィンドウを閉じます。
で、プロセスクローズなどで掃除しています。
なお、今回は、
if(Caption.ToString() == "電卓")
として、「電卓」というタイトルのアプリを起動したら閉じるようにしました。
calcです。
ただ、タイトルは開いたファイル名によって変わるものもあるため、
そういうときはプロセス名であったり、正規表現でタイトルと合致するかを判断する必要があると思います。
とりあえず、今回はここまで。。。
そのウィンドウが特定の名前ならウィンドウを閉じてしまいたい、
ということをやりたくなりました。
以前にとある雑誌でC#の記事があって、とりあえず作ってみたのが、
アクティブウィンドウのタイトルを1秒ごとに取得して前のタイトルと異なっていれば、
MyDocumentの直下にLogFile.logというログファイルに時間とタイトルを書き込んでいくアプリを作成しました。
これを利用して作ってみました。
using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Windows.Forms; //[DllImport xxxx]するために必要です。 using System.Runtime.InteropServices; //ファイル入出力のために必要です。 using System.IO; namespace WindowsFormsApplication1 { public partial class Form1 : Form { //Windows API のための宣言 [DllImport("user32.dll")] extern static IntPtr GetForegroundWindow(); [DllImport("user32.dll")] extern static int GetWindowText(IntPtr hWnd, StringBuilder text, int length); [DllImport("user32.dll")] extern static int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId); static public StreamWriter LogFile = new StreamWriter(System.Environment.GetFolderPath(Environment.SpecialFolder.Personal) + "\\LogFile.txt", true); //直前の作業中ウィンドウタイトルを保存しておく変数を宣言します。 static public String WindowCheck = ""; public Form1() { InitializeComponent(); Application.ApplicationExit += new EventHandler(this.ThisApplicationExit); } //タイマーが有効なときに、定期的に発生するイベント private void timer1_Tick(object sender, EventArgs e) { //現在時刻を取得します。ログに記録する時刻、および時計表示に使います。 // String LoggingTime = System.DateTime.Now.ToString().Substring(11, 8); String LoggingTime = System.DateTime.Now.ToString(); //ウィンドウタイトルの取得 IntPtr hWnd = GetForegroundWindow(); StringBuilder Caption = new StringBuilder(1024); GetWindowText(hWnd, Caption, 1024); label1.Text = "現在の時刻 : " + LoggingTime; if (Caption.ToString() != WindowCheck) { WindowCheck = Caption.ToString(); LogFile.WriteLine(LoggingTime + "<>" + Caption); if(Caption.ToString() == "電卓") { // MessageBox.Show("電卓が選択されている"); // ウィンドウハンドルからプロセスIDを取得 int id; GetWindowThreadProcessId(hWnd, out id); System.Diagnostics.Process process = System.Diagnostics.Process.GetProcessById(id); process.CloseMainWindow(); process.Close(); process.Dispose(); } } } private void Form1_Load(object sender, EventArgs e) { LogFile.WriteLine(System.DateTime.Now.ToString() + "<>「作業ログの取得と記録」が起動しました。"); // MessageBox.Show ("アプリケーションの起動。"); } //ApplicationExitイベントハンドラ private void ThisApplicationExit(object sender, EventArgs e) { // MessageBox.Show("アプリケーションの終了。"); LogFile.WriteLine(System.DateTime.Now.ToString() + "<>「作業ログの取得と記録」を終了しました。"); LogFile.Close(); //ApplicationExitイベントハンドラを削除 Application.ApplicationExit -= new EventHandler(ThisApplicationExit); } private void label1_Click(object sender, EventArgs e) { } } }
いくつか要点を。
.NETフレームワークにはアクティブなウィンドウを取得するAPIはなさそう(と書いていたきがするけどもうあるかもしれない)なので、WindowsAPIを利用して取得することに。
そのための記述は以下。
//Windows API のための宣言
[DllImport("user32.dll")]
extern static IntPtr GetForegroundWindow();
[DllImport("user32.dll")]
extern static int GetWindowText(IntPtr hWnd, StringBuilder text, int length);
[DllImport("user32.dll")]
extern static int GetWindowThreadProcessId(IntPtr hWnd, out int lpdwProcessId);
上から順に
ウィンドウを取得
ウィンドウタイトルを取得
ウィンドウのプロセスIDを取得
となっています。
すべてuser32.dllを参照しています。
クラスのコンストラクタで
public Form1()
{
InitializeComponent();
Application.ApplicationExit += new EventHandler(this.ThisApplicationExit);
}
と記述することで、ウィンドウを終了するときのイベントを自身のアプリケーションに追加しています。
終了時に実行されるのは、this.ThisApplicationExitとして渡したユーザ定義メソッドです。
このフォームアプリは、Timer(timer1という名前)を仕込んでいます。
0.1秒(100ミリ秒)ごとにイベントが送出されるのでこれを以下のようにハンドルしています。
//タイマーが有効なときに、定期的に発生するイベント
private void timer1_Tick(object sender, EventArgs e)
{
//なんか処理
}
イベント関連を最後まで流すと、
private void Form1_Load(object sender, EventArgs e)
{
LogFile.WriteLine(System.DateTime.Now.ToString() + "<>「作業ログの取得と記録」が起動しました。");
// MessageBox.Show ("アプリケーションの起動。");
}
にて、フォームのロード時に1回だけ上記のメソッドにロードイベントが送出され、ログファイルにシステム時間を記述しています。
そして最後に、上記のコンストラクタで追加したアプリケーション終了時のメソッド。
//ApplicationExitイベントハンドラ
private void ThisApplicationExit(object sender, EventArgs e)
{
// MessageBox.Show("アプリケーションの終了。");
LogFile.WriteLine(System.DateTime.Now.ToString() + "<>「作業ログの取得と記録」を終了しました。");
LogFile.Close();
//ApplicationExitイベントハンドラを削除
Application.ApplicationExit -= new EventHandler(ThisApplicationExit);
}
最後に、このアプリケーションからアプリケーション終了時のイベントを削除しています。
これをやらないとメモリにいつまでも残ってしまうようです。
アクティブなウィンドウのプロセスIDを取得し、
そのプロセスIDをもとにプロセスを取得するのは以下。
// ウィンドウハンドルからプロセスIDを取得
int id;
GetWindowThreadProcessId(hWnd, out id);
System.Diagnostics.Process process = System.Diagnostics.Process.GetProcessById(id);
process.CloseMainWindow();
process.Close();
process.Dispose();
これによって、プロセスをアプリの好きにできます。
ここまできたらもう今回の目的は果たせそうです。
Process#Kill はコンソールアプリに使うべきで、GUIアプリには使うべきではないようなので、
Process#CloseMainWindow でウィンドウを閉じます。
で、プロセスクローズなどで掃除しています。
なお、今回は、
if(Caption.ToString() == "電卓")
として、「電卓」というタイトルのアプリを起動したら閉じるようにしました。
calcです。
ただ、タイトルは開いたファイル名によって変わるものもあるため、
そういうときはプロセス名であったり、正規表現でタイトルと合致するかを判断する必要があると思います。
とりあえず、今回はここまで。。。