Delphi Advent Calendar 2012 12/24 の記事です。
前回までで、コンソールの基礎的な事柄を述べました
今回 GUI アプリケーションで、コンソールを使う方法を紹介します。
(ずっとコンソールについて書いてきましたが、結局これがやりたかった!)
GUI アプリでも、デバッグ時に現在の状態を表示するためにログを出したりする事が多々あります。
その場合、TMemo をアプリに配置して、そこに Lines.Add('メッセージ')などとしている事が多いのでは無いでしょうか?
しかし、Lines.Add だと数字は IntToStr() で文字列に変換しないと表示できないですし、そもそもデバッグの為だけに TMemo を置くのも馬鹿らしいです。
コンソールが使えれば、それらの悩みも一挙解決です。
コンソールを表示するために、何かインスタンスを作る必要も無いですし、Writeln を使えば文字列も数値も一緒くたに表示できるからです。
しかも、コンソールは表示するだけではなく読み取ることもできます。
コンソールから文字列を受け取って、それに応じてアプリの状態を変更したりできます(デバッグ時に非常に役に立つでしょう)。
GUI アプリでコンソールを使うには2つ方法があります。
1つは CreateProcess の引数に CREATE_NEW_CONSOLE を付ける方法、
もう1つは、AllocConsole を使う方法です。
CREATE_NEW_CONSOLE を使う方法は、アプリの起動時(CreateProcess を呼び出した時)に指定する必要があるため、今回は使えません(自分で自分を呼び出すことはできないため)。
そこで、今回は AllocConsole を使います。
FormCreate でコンソールを割り当て、FormDestroy でコンソールの割り当てを解除しています。
procedure TForm1.Button1Click(Sender: TObject);begin// コンソールに文字列を出力Writeln('Hello, Console !');end;procedure TForm1.FormCreate(Sender: TObject);begin// コンソールを割り当てAllocConsole;end;procedure TForm1.FormDestroy(Sender: TObject);begin// 割り当てたコンソールを開放FreeConsole;end;
Button1 を押すとコンソールに文字列が表示されました!
では、この仕組みを使いやすくライブラリ化し、簡単にデバッグメッセージを出力できるようにしてみます。
それぞれ詳細はコード中のコメントを参照してください。
まず先に使い方です。
コンソールアプリケーションと同様に Writeln/Readln が普通に使えます。
ここでは Readln を使う代わりに、ライブラリ化した関数 ReadConsole を使っています。
procedure TForm1.Button1Click(Sender: TObject);begin// 文字列の表示Writeln('Hello, World !');// 数字や Boolean、文字列などを混在して表示できるWriteln(123456789, ' ', True);end;procedure TForm1.Button2Click(Sender: TObject);varStr: String;begin// 命令をコンソールから読み取るStr := ReadConsole('Input Command: ', True);// exit なら終了if (Str = 'exit') thenClose// notepad なら「メモ帳」を起動else if (Str = 'notepad') thenWinExec('notepad.exe', SW_SHOW)// それ以外なら不明と表示elseWriteln('Unknown command:', Str);end;
uConsole.pas
unit uConsole;interfacetype// コンソールイベントTConsoleEventType = (ceC, // CTRL + C が押されたceBreak, // CTRL + BREAK が押されたceClose, // コンソールウィンドウが閉じられたceLogOff, // ログオフされたceShutdown // シャットダウンされた);// コンソールイベント型TConsoleEvent = procedure(const iType: TConsoleEventType) of object;// コンソールイベントを受け取るリスナーを追加・解除procedure AddConsoleEventListener(const iListener: TConsoleEvent);procedure RemoveConsoleEventListener(const iListener: TConsoleEvent);// コンソールから文字列を読み取るfunction ReadConsole(const iPrompt: String = '';const iToLower: Boolean = False): String;implementationusesWinapi.Windows, Generics.Collections, System.SysUtils;var// コンソールウィンドウのハンドルGWnd: HWND;// イベントリスナを管理するリストGListeners: TList<TConsoleEvent>;// コンソールイベントリスナを追加procedure AddConsoleEventListener(const iListener: TConsoleEvent);beginif (GListeners.IndexOf(iListener) < 0) thenGListeners.Add(iListener);end;// コンソールイベントリスナを削除procedure RemoveConsoleEventListener(const iListener: TConsoleEvent);beginif (GListeners.IndexOf(iListener) > -1) thenGListeners.Remove(iListener);end;// コンソールから文字列を読み取る// iPrompt 読み取り前に表示する文字列(ex. 'Please input your name: ')// iToLower 読み取った文字列を小文字にするなら Truefunction ReadConsole(const iPrompt: String = '';const iToLower: Boolean = False): String;begin// プロンプトの表示if (iPrompt <> '') thenWrite(iPrompt);// コンソールに入力フォーカスを与えるShowWindow(GWnd, SW_SHOW);SetForegroundWindow(GWnd);// 読み込むReadln(Result);// 小文字化if (iToLower) thenResult := LowerCase(Result);end;// コンソールイベントが起きたときに呼ばれる関数function HandlerRoutine(dwCtrlType: DWORD): BOOL; stdcall;varListener: TConsoleEvent;beginResult := True; // False の場合、イベントは OS が適切に処理する//(ex. CTRL + C が押されたらアプリケーションを終了させる)// True の場合、OS は何もしないfor Listener in GListeners doListener(TConsoleEventType(dwCtrlType));end;// コンソールの初期設定// ・コンソールの Window Handle の特定// ・コンソールのタイトルの設定procedure InitConsole;varCap: String;begin// Window Caption に GUID を設定するCap := TGUID.NewGuid.ToString;SetConsoleTitle(PWideChar(Cap));Sleep(40); // Caption が確実に設定されるために 40[msec] 待つ// GUID でウィンドウを探すGWnd := FindWindow(nil, PChar(Cap));if (GWnd <> 0) then// 見つけたらスタイルから System Menu を外す//(コンソールを勝手に閉じられないようにするため)SetWindowLong(GWnd,GWL_STYLE,GetWindowLong(GWnd, GWL_STYLE) and not WS_SYSMENU);// コンソールのタイトルをアプリケーションのパスにするSetConsoleTitle(PWideChar(ParamStr(0)));end;// 初期化initializationbegin// イベントハンドラ管理用リストの生成GListeners := TList<TConsoleEvent>.Create;// アプリケーションにコンソールを割り当てるAllocConsole;// コンソールイベントのハンドラを設定するSetConsoleCtrlHandler(@HandlerRoutine, True);// コンソールの初期設定InitConsole;end;// 終了処理finalizationbegin// コンソールイベントのハンドラを解除SetConsoleCtrlHandler(@HandlerRoutine, False);// 割り当て済みのコンソールを解除FreeConsole;// イベントハンドラ管理用リストの破棄GListeners.Free;end;end.
実行すると、こんな風になります。
0 件のコメント:
コメントを投稿