C#でWindowsアプリを開発するときの小ネタ集。
ウィンドウハンドルを取得する方法
Win32 APIでウィンドウを操作したりするときに必要になるのが、ウィンドウハンドルです。
自分のウィンドウなら、Control.Handle
プロパティがあるので簡単です。
IntPtr hWnd = this.Handle;
自分で起動したプロセスのメインウィンドウは、Process.MainWindowHandle
で取得できます。ただし、すぐにウィンドウが開くわけではないので、ウィンドウが開くまで待つ必要があります。MicrosoftのドキュメントにはProcess.WaitForInputIdle()
を使えとありますが、アプリの構造によってはうまく動作しません。
Process proc = Process.Start( "hoge.exe" );
// proc.WaitForInputIdle(); // うまくいかない場合がある
while ( proc.MainWindowHandle == 0 ) { // こっちのほうが確実
await Task.Delay( 10 );
}
IntPtr hWnd = proc.MainWindowHandle;
TextBoxのキャレットを隠す方法(WinForms)
TextBoxをdisableにするとキャレットが消せるけど、文字が灰色になってしまいます。HideCaret()
というWin32 APIを使えば、disableにせずともキャレットを消せますが、1回だけだとフォームがアクティブになったときまた復活するので、アクティブになるたびに消してやればOK。
[LibraryImport( "user32.dll" )]
[return: MarshalAs( UnmanagedType.Bool )]
private static partial bool HideCaret( IntPtr hWnd );
public Form1() {
InitializeComponent();
this.Activated += Form1_Activated;
}
private void Form1_Activated( object? sender, EventArgs e ) {
HideCaret( textBox1.Handle );
}
マウスカーソルを消す方法(WinForms)
Cursor.Hide()
とCursor.Show()
を使えば簡単なのですが、実はこれ、HideとShowの回数が合っていなければならない、すなわち、2回Hideしたら2回Showしなければならないという、厄介な仕様があります。この仕様が便利なこともあるのかもしれませんが、たいていは厄介なだけなので、一枚かませて簡略化しましょう。
public class CursorControl {
private static bool show = true;
public static void Show() {
if ( !show ) {
Cursor.Show();
show = true;
}
}
public static void Hide() {
if ( show ) {
Cursor.Hide();
show = false;
}
}
}
DataGridViewのコンボボックスをワンクリックで開く方法(WinForms)
DataGridViewのコンボボックスは、2回クリックしないとドロップダウンが開かないという不自然な仕様になっています。これをワンクリックで開くようにするには、セルが選択されたときにドロップダウンを開くキー操作を送ってやります。
dataGridView1.CellEnter += DataGridView1_CellEnter;
private void DataGridView1_CellEnter( object? sender, DataGridViewCellEventArgs e ) {
if ( sender == null ) {
return;
}
DataGridView dgv = (DataGridView)sender;
if ( dgv.Columns[ e.ColumnIndex ].CellTemplate is DataGridViewComboBoxCell ) {
SendKeys.Send( "{F4}" );
}
}
この例は、セルテンプレートを用いる場合の方法です。対象のコンボボックスかどうか判定する条件(2個目のif文)は、DataGridViewの設定に依存します。
コンソールアプリと対話する方法
ProcessStartInfoにSTDINとSTDOUTのリダイレクトを設定すれば簡単じゃね?と思うのですが、なぜか、StreamReaderの挙動がおかしくなります。具体的に言うと、STDOUTに何も出力されていない時、StreamReader.Read()
やStreamReader.Peek()
が永遠に戻ってこなくなります。それを回避するため、StreamReader.ReadAsync()
で非同期に読み取って、StringBuilder上に文字列として構築するようにします。
STDIN(StreamWriter)のほうは普通に扱って問題ありません。
private Process? proc;
private StreamReader? stdout;
private StreamWriter? stdin;
void launch() {
var psi = new ProcessStartInfo( "hoge.exe" );
psi.UseShellExecute = false;
psi.RedirectStandardOutput = true;
psi.RedirectStandardInput = true;
proc = Process.Start( psi );
stdout = proc?.StandardOutput;
stdin = proc?.StandardInput;
}
private char[] stdoutBuffer = new char[ 1000 ];
private StringBuilder sbStdout = new StringBuilder();
private async Task readStdout() {
if ( stdout != null ) {
int count = await stdout.ReadAsync( stdoutBuffer, 0, stdoutBuffer.Length );
sbStdout.Append( stdoutBuffer, 0, count );
}
}
モニターのリフレッシュレートを取得する方法
指定したウィンドウがあるモニターのリフレッシュレートを取得します。
まず、MonitorFromWindow()
でウィンドウが存在するモニターを取得して、GetMonitorInfoW()
でモニターのデバイス名を取得して、最後にEnumDisplaySettingsW()
で現在の設定を取得します。
やることは簡単なのですが、Win32 API関係の定義が多くてとても長くなります。サンプルはgistに入れました。
https://gist.github.com/yamamaya/4a4c3051fa03a2425549f10ef11dafd5
アプリにキー操作を送る方法
自分以外の特定のアプリにキー操作を送るには、SendKeys.Send()
を使います。キー操作の記述方法は以下のページを参照してください。
SendKeys.Send()
には、送り先を指定する方法がなく、その時点でアクティブなウィンドウに送られます。そのため、キー操作を送る前に、送り先のウィンドウをWin32 APIのSetForegroundWindow()
でアクティブにてやります。
[LibraryImport( "user32.dll" )]
[return: MarshalAs( UnmanagedType.Bool )]
private static partial bool SetForegroundWindow( IntPtr hWnd );
SetForegroundWindow( hWnd );
SendKeys.Send( "%({ENTER})" ); // Alt+Enter
SetForegroundWindow()
とSendKey.Send()
の間でユーザーが手動でウィンドウを切り替えたりした場合の動作は保証できません。Hook等を駆使して、ユーザーによる操作を全面的に禁止する必要があります。
操作を全面的に禁止する方法
Win32 APIのキーボードフックとマウスフックという仕掛けを使います。なにしろ、PCのすべての操作を奪ってしまうので、開発やデバッグがそれなりに面倒です。興味のある方はググってみてください。後で追記するかも?
実行中のファイルの情報を取得する方法
Assembly.Get****Assembly()
というメソッドが何種類かあるけど、違いがよく判らなくなってます。昔は起動用のEXEファイルとか、アセンブリを格納したDLLとか区別されていたけど、今はDLLしか返さないような?実行中のファイル名やアセンブリのバージョンが取得できます。
string? filename = Assembly.GetExecutingAssembly()?.Location;
Version? version = Assembly.GetExecutingAssembly()?.GetName().Version;
Windowsアカウントの権限を取得する方法
Thread.GetDomain().SetPrincipalPolicy( PrincipalPolicy.WindowsPrincipal );
var pri = (WindowsPrincipal)Thread.CurrentPrincipal;
bool isAdmin = pri.IsInRole( WindowsBuiltInRole.Administrator );
bool isGuest = pri.IsInRole( WindowsBuiltInRole.Guest );
管理者権限になる方法
ProcessStartInfo.Verb
にRunAs
を設定して自分自身を起動しなおします。もちろん他の実行ファイルにしてもOK。なお、通常、画面が暗転してUACのダイアログが出ます。
static void Main( string[] args ) {
if ( 力が欲しいか? ) {
var psi = new ProcessStartInfo();
psi.WorkingDirectory = Environment.CurrentDirectory;
psi.FileName = Assembly.GetEntryAssembly().Location;
psi.Verb = "RunAs";
psi.Arguments = string.Join( " ", args );
Process.Start( psi );
return;
}
}