Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2022年03月12日] [更新 2022年03月12日]
コンソール画面に画像を表示
動作確認等 Windows 10 ビルド 19042 + Delphi XE5(UP2) Pro VCL-32



コンソールアプリの画面に画像を表示する例です。TCanvas を生成して使用します。画像を TBitmap に読み込む際、多少のタイムラグが必要です。
標のスケールは、フォントの設定によって違ってきます。




このサンプルは、コンソールウィンドウをリサイズすると画像を描画しません。また、画像をクリックすると、下図のようにクリックした部分を含む領域が欠落します。
この現象を回避するにはコンソールウィンドウの再描画の処理が必要ですが、筆者は、コンソールウィンドウを再描画する方法を知りません。コンソールアプリは、Window のメッセージを捕捉することはできません。コンソールウィンドウはサブクラス化できません。




program Project1;
{$APPTYPE CONSOLE}

uses
  Winapi.Windows,
  System.SysUtils,
  Vcl.Graphics,
  Vcl.Forms;

var
  FhConsole : HWND;
  FhHDC     : HDC;
  FCanvas   : TCanvas;
  FBitmap   : TBitmap;
  FFilePath : string;

begin
  Writeln('コンソール画面に画像を表示 ');

  FhConsole := FindWindow(nil, PChar(Application.ExeName));

  FhHDC   := GetDC(FhConsole);
  FCanvas := TCanvas.Create;
  FBitmap := TBitmap.Create;
  try
    FCanvas.Handle := FhHDC;

    FFilePath := ExtractFilePath(Application.ExeName) + 'smail_27.bmp';
    FBitmap.LoadFromFile(FFilePath);
    // タイムラグが必要
    Sleep(30);
    FCanvas.Draw(50, 50, FBitmap);
  finally
    ReleaseDC(0, FhHDC);
    FreeAndNil(FCanvas);
    FreeAndNil(FBitmap);
  end;

  Readln;
end.


どうしても再描画したい場合、TTimer コンポーネントを使用して定期的に再描画する方法が考えられます。
以下のサンプルは、フォームを使用したコンソールアプリを起動します。フォームに TTimer コンポーネントを配置して使用します。下図が実行結果です。ウィンドウのリサイズの際、多少ちらつきが発生しますが、画像が消えることはありません。

下図は EXE を実行した直後の画像です、その次の画像は、マウスでコンソール画面をスクロールした結果です。コンソールアプリは、スクロール等のメッセージを捕捉できません。この現象の対処方法は不明です。




テストに使用したプロジェクトのソースコードです。コンソールアプリとするために {$APPTYPE CONSOLE} を追加して、フォームを非表示で実行するためのコードを追加しています。


program Project1;

uses
  Vcl.Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.RES}
{$APPTYPE CONSOLE}  // 追加 

begin
  Application.Initialize;
  Application.MainFormOnTaskbar := True;
  Application.ShowMainForm := False;        //追加
  Application.CreateForm(TForm1, Form1);
  Application.Run;
end.


フォームのユニットのコードです。コンソールアプリを操作するということ以外、通常のフォームのコードとの違いはありません。


unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Timer1: TTimer;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private 宣言 }
    FConsoleWnd : HWND;
    FCanvas     : TCanvas;
    FhHDC       : HDC;
    FBitmap     : TBitmap;
  public
    { Public 宣言 }
  end;
var
  Form1: TForm1;

implementation

{$R *.DFM}

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  FConsoleWnd := FindWindow(nil, PChar(Application.ExeName));
  SetConsoleTitle('コンソールアプリの画面に画像を描画');
  Writeln('コンソール画面に画像を表示 ');

  FhHDC := GetDC(FConsoleWnd);
  FCanvas := TCanvas.Create;
  FCanvas.Handle := FhHDC;

  FBitmap := TBitmap.Create;
  FBitmap.LoadFromFile('smail_27.bmp');

  Timer1.Interval := 30;
  Timer1.Enabled := True;
end;

//=============================================================================
//  フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FBitmap);
  FreeAndNil(FCanvas);
  ReleaseDC(0, FhHDC);
end;

//=============================================================================
//  Timer1 の OnTimer イベント処理
//=============================================================================
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  if FBitmap <> nil then begin
    FCanvas.Draw(50, 50, FBitmap);
  end;
end;

end.


この記事の元になった質問は、コマンドプロンプトに画像を表示することでした、コマンドプロンプトの画面に画像を表示するという状態は、個人的にはちょっと考えられません。そこでコンソール画面としています。
コマンドプロンプトの画面の背景を画像にしたいのであれば、以下の記事等を参考にしてください。



試しに、コマンドプロンプトに画像を描画した結果です。フォームを非表示にして実行しています。
スクロール時における現象は、上のサンプルと同じです。




unit Unit1;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls;

type
  TForm1 = class(TForm)
    Timer1: TTimer;
    Button1: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private 宣言 }
    FConsoleWnd : HWND;
    FCanvas     : TCanvas;
    FhHDC       : HDC;
    FBitmap     : TBitmap;
    procedure DrawImage;
  public
    { Public 宣言 }
  end;
var
  Form1: TForm1;

implementation

{$R *.DFM}

//-----------------------------------------------------------------------------
//  CreateProcess関数を使用して指定したプロセスを起動
//  起動したプロセスが終了するまで待つ
//-----------------------------------------------------------------------------
function WinExecAndWait32V2(FileName: String; Visibility: Integer): DWORD;
var
  LStartupInfo : TStartupInfo;
  LProcessInfo : TProcessInformation;
  LiRet        : DWORD;
begin
  Result := 0;

  FillChar(LStartupInfo, SizeOf(LStartupInfo), #0);
  LStartupInfo.cb          := SizeOf(TStartupInfo);
  //以下の4行とdwFlagsにSTARTF_USERPOSITION,STARTF_USESIZEを追加
  //位置とサイズを指定する必要がなければこれらの設定は不要
  LStartupInfo.dwX         := 300;
  LStartupInfo.dwY         := 300;
  LStartupInfo.lpTitle     := PChar('コマンドプロンプト');
  LStartupInfo.dwFlags     := STARTF_USESHOWWINDOW or
                              STARTF_USEPOSITION or
                              STARTF_USESIZE;
  LStartupInfo.wShowWindow := Visibility;

  SetLength(FileName, Length(FileName));

  //参照カウンタ対策
  UniqueString(FileName);
  if not CreateProcess(nil,
                       PChar(FileName),
                       nil,
                       nil,
                       False,
                       NORMAL_PRIORITY_CLASS,
                       nil,
                       nil,
                       LStartupInfo,
                       LProcessInfo) then begin
    Result := WAIT_FAILED;
  end else begin
    //プロセスが起動するまで待つ
    repeat
      LiRet := WaitForInputIdle(LProcessInfo.hProcess, 50);
      Application.ProcessMessages;
    until LiRet <> WAIT_TIMEOUT;

    // 起動後の処理
    // 本サンプルでは画像を表示
    Form1.DrawImage;

    //起動したプロセスが終了するまで待つ
    while WaitForSingleObject(LProcessInfo.hProcess, 100) = WAIT_TIMEOUT do
      Application.ProcessMessages;

    GetExitCodeProcess(LProcessInfo.hProcess, Result);
    CloseHandle(LProcessInfo.hProcess);
    CloseHandle(LProcessInfo.hThread);
  end;
end;

//=============================================================================
//  フォーム生成時の処理
//=============================================================================
procedure TForm1.FormCreate(Sender: TObject);
begin
  WinExecAndWait32V2('Cmd.exe /k cd /d c:\&dir', SW_SHOWNORMAL);
  PostMessage(Handle, WM_CLOSE, 0, 0);
end;

//=============================================================================
//  フォーム破棄時の処理
//=============================================================================
procedure TForm1.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FBitmap);
  FreeAndNil(FCanvas);
  ReleaseDC(0, FhHDC);
end;

//=============================================================================
//  Timer1 の OnTimer イベント処理
//=============================================================================
procedure TForm1.Timer1Timer(Sender: TObject);
begin
  if FBitmap <> nil then begin
    FCanvas.Draw(100, 50, FBitmap);
  end;
end;

//-----------------------------------------------------------------------------
//  コマンドプロンプトが起動したら行う処理
//-----------------------------------------------------------------------------
procedure TForm1.DrawImage;
begin
  Sleep(100);
  FConsoleWnd := FindWindow('ConsoleWindowClass', nil);

  FhHDC := GetDC(FConsoleWnd);
  FCanvas := TCanvas.Create;
  FCanvas.Handle := FhHDC;

  FBitmap := TBitmap.Create;
  FBitmap.LoadFromFile('smail_27.bmp');

  Timer1.Interval := 30;
  Timer1.Enabled := True;
end;

end.


下図は、コマンドプロンプトで、本ページのサンプルで使用した画像ファイル名 smail_27.bmp をタイプして (実際にはコピペして) [Enter] キーを押下した結果です。Windows フォトビューアで画像を表示しています。
これは、拡張子 .bmp に関連付けされているアプリが起動して、そのアプリが画像を表示していることになります。つまり、コマンドプロンプトでは、実際には Windows フォトビューアを起動するコマンドを引数付きで実行しているのと同じです。

コマンドプロンプトは、このようにコマンドを実行する用途に使用されています。コマンドプロンプトのウィンドウに表示できるのは文字列、つまりテキストだけです。コマンドプロンプトには、画像等、グラフィック関係の描画機能はありません。
コンソールアプリケーションは、テキストペースのアプリケーションです。コンソールアプリのウィンドウに表示可能なのもテキストだけです。コンソールアプリにも、画像等を描画する機能はありません。

コマンドプロンプトのウィンドウとコンソールアプリのウィンドウは似ていますが、その機能と用途は全く違います。




[備考]
console (コンソール) を直訳すると「操作卓」です。コンピュータの分野では、コンピュータの制御用装置の総称です。一般的に、入出力の機能を持つ物の意味で使用されています。それが何を指しているのかは、文脈によって異なります。キーボードとモニタが一体となっている装置をコンソールと言うこことも、モニタの画面をコンソール画面と言うこともあります。この意味では、コマンドプロンプトの画面も、コンソールアプリのウィンドウもコンソール画面と言えます。
本ページでは、コンソールを、Windows のコンソールアプリケーションの意味で使用しています。