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 のコンソールアプリケーションの意味で使用しています.