Delphi Programming / Object Pascal
サイトのトップページへ リンクのページへ ロゴマーク
[掲載 2005年01月01日] [更新 2012年04月04日] Delphi サンプルプログラム集
   
015_プリンタ設定関係ダイアログ API の使用方法
動作確認等 Windows 7 U64(SP1) + Delphi XE Pro
015_PrintDlgAPI.zip [2,185 KB] 2012年04月04日版(EXE同梱)



  • 2012年04月03日
  • PrintDlgEx と PageSetupDlg 関数のサンプルを追加
  • 構造体 (レコード型) の表を追加して,説明記事を大幅に追加




プリンタ設定関係ダイアログの API


印刷ダイアログ,プリンタの設定ダイアログ,ページ設定ダイアログの Windows API 関数のサンプルコードです.
Delphi の TPrintDialog,TPrinterSetupDialog,TPageSetupDialog のコンポーネントは,これらのAPI をカプセル化したものです.この API を使用する場面はあまりないと思いますが,掲示板などでたまに質問があったのでテストしてみました.

その他のダイアログの構造体の説明も MSDN の [Common Dialog Box Library] にあります.Delphi のダイアログは,これらのコモンダイアログのライブラリをカプセル化したものです.該当する構造体を操作すれば,ダイアログのカスタマイズが可能です.

Delphi の印刷とプリンタ設定ダイアログを使用する場合,あらかじめ,プリンタの設定をしておけば,その設定でダイアログを表示します.そして,ダイアログを終了した後,Printer の Canvas に印刷コードを書けば,その設定で印刷されます.

一方,Windows API の PrintDlg 関数,PrintDlgEx 関数で直接設定する場合は,例えプリンタの設定をしていても,その内容はダイアログに反映されません.各 API 関数の構造体のメンバーに値をセットをしないと,常に「通常使用するプリンタ」のデフォルトの値で表示されます.
また,ダイアログを終了しても,プリンタオブジェクトに対して,構造体のメンバーから設定値を取出して設定しないと,設定内容は反映されません.

一方,ワードやエクセルのように,現在のシステムの現在のプリンタの設定をデフォルトの設定値として使用するアプリケーションがあります.この場合,プリンタの設定関係の API を使用して,あらかじめデフォルトの値を設定しておくことができます.
ただし,元の設定に戻しておかないと,他のアプリケーションが影響を受けることになります.






01_[ 印刷 ] [ プリンタ設定 ] ダイアログ - PrintDlg 関数


PrintDlg 関数のテストプログラムです.
引数は 1 つで,TPrintDlg 構造体です.この構造体の Flags に PD_PRINTSETUP を含めると {プリンタの設定} ダイアログとなります.
下の表はこの構造体のメンバのリストです.


表1
TPrintDlg 構造体(レコード型)のメンバー
変数名と型は Delphi XE の CommDlg.pas のもの

メンバー 説明
lStructSize DWORD この構造体のサイズを必ず指定する
hwndOwner HWND ダイアログの親ウィンドウを指定する
0 でもよいがモーダル表示のためには指定した方がよい
hDevMode HGLOBAL DEVMODE 構造体のハンドル
実行後はダイアログで設定した値を返す
この情報でダイアログを表示する
hDevNames HGLOBAL プリンタドライバ名,デバイス名,出力ポート名等を格納した DEVNAMES 構造体のポインタを指定する
実行後はダイアログで設定した値を返す
hDevMode の指定よりも優先される
hDC HDC Flags の値が PD_RETURNDC または PD_RETURNIC の時に有効
システムにデバイスコンテキストまたインフォーメーションの作成を任せた場合に,ここにそのハンドルを返す
Flags DWORD ダイアログの初期化や各コントロールの表示等の制御のフラグを指定する.値は下表の組み合わせ
終了時には,ダイアログで設定した値が返ってくる
nFromPage Word 印刷開始ページ番号を指定する
最初のページは1.nFromPage < = nToPage にすること
終了時は,ダイアログの設定値を返す
nToPage Word 印刷終了ページ番号を指定する
その他は nFromPage と同じ
nMinPage Word 印刷可能な最初のページを指定する.最小値は 1
nMaxPage Word 印刷可能な最後のページ番号を指定する
nCopies Word 印刷部数を指定する
終了時は,ダイアログで設定した値を返す
Flags に PD_USEDEVMODECOPIESANDCOLLATE を指定している場合は hDevModeのdmCopies の値が使われ,戻り値も dmCopies の値に格納され,nCopies の値は常に 1 が返る
hInstance HINST Flags に以下のいずれかの値を指定した場合に指定する
PD_ENABLEPRINTTEMPLATE,PD_ENABLESETUPTEMPLATE
この場合,この hInstanceは
lpPrintTemplateName または lpSetupTemplateName で指定したダイアログボックステンプレート (EXE とか DLL) のインスタンスのハンドルとする
lCustData LPARAM lpfnPrintHook または lpfnSetupHook を使用する場合に使用
アプリ独自のカスタムデータを設定する
フックのコールバック関数では,WM_INITDIALOG メッセージの時にこの値を受け取ることができる
lpfnPrintHook - [印刷] ダイアログの動作仕様を変更するためのコールバック関数のアドレスを指定する (参考リンク参照)
Flags に PD_ENABLEPRINTHOOK を指定した場合に有効
lpfnSetupHook - [プリンタの設定] ダイアログの動作仕様を変更するためのコールバック関数のアドレスを指定する (参考リンク参照)
Flags に PD_ENABLESETUPHOOK を指定した場合に有効
lpPrintTemplateName PWideChar [印刷] ダイアログテンプレートのリソース名を指定する
これで標準の [印刷] ダイアログを置き換える
Flags に PD_ENABLEPRINTTEMPLATE を指定した場合に有効
lpSetupTemplateName PWideChar [プリンタの設定] ダイアログテンプレートのリソース名を指定
これで標準の [プリンタの設定] ダイアログを置き換える
Flags に PD_ENABLESETUPTEMPLATE を指定した場合に有効
hPrintTemplate HGLOBAL [印刷] ダイアログテンプレートのリソースハンドルを指定
これで標準の [印刷] ダイアログを置き換える
Flags に PD_ENABLEPRINTTEMPLATEHANDLE を指定した場合に有効
hSetupTemplate HGLOBAL [プリンタの設定] ダイアログテンプレートのリソースハンドルを指定する
これで標準の [プリンタの設定] ダイアログを置き換える
Flags に PD_ENABLESETUPTEMPLATEHANDLE を指定した場合に有効


次の表は,PrintDlg 関数の引数である構造体の,Flags メンバーで設定可能な値のリストです.
また,図は Delphi の印刷ダイアログとプリンタ設定のダイアログのプロパティです.これらを較べてみると,Flags メンバーで指定可能なフラグが,Delphi の,これらのコンポーネントでも設定可能なことが分かります.


図1
TPrintDialog コンポのプロパティ
  • Delphi の TPrintDialog には,PrintDlg 関数の Flags で設定可能なプロパティがほとんど用意されている
  • 各プロパティの意味については,Delphi のオンラン委ヘルプの TPrinter, TPrintDialog の記事で確認できる
図2
TPrinterSetupDialog のプロパティ
  • こちらはオプションの設定がほとんどない

表2
Flags の値 (Delphi XE の CommDlg.pas のもの)

PD_ALLPAGES ページ範囲の [全て] を選択状態にする
これがデフォルトの設定
PD_SELECTION ページ範囲の [選択した部分] を選択状態にする
PD_PAGENUMS ページ範囲の [ページ指定] を選択状態にする
nMinPage = nMaxPage の時は無効
PD_NOSELECTION ページ範囲の [選択した部分] を選択不可にする
PD_NOPAGENUMS ページ範囲の [ページ指定] を選択不可にする
PD_COLLATE [部単位で印刷] をチェックする
PD_PRINTTOFILE [ファイルへ出力] をチェックする
PD_PRINTSETUP [印刷] ダイアログではなく,[プリンタの設定] ダイアログを表示する
この指定がない場合は [印刷] ダイアログ
PD_NOWARNING デフォルトプリンタが無い場合に表示されるメッセージを表示しないようする
PD_RETURNDC [印刷]ダイアログから戻った時にプリンタのデバイスコンテキストhDCメンバに格納する
PD_RETURNIC [印刷] ダイアログから戻った時にプリンタインフォメーションコンテキストをhDCメンバに返す
PD_RETURNDEFAULT [印刷]ダイアログを表示しない
デフォルトプリンタの情報を hDevMode と hDevNames に格納して返す.hDevNames と hDevMode を 0 にしておかないと失敗する
PD_SHOWHELP ダイアログボックスにヘルプボタンを表示する
hwndOwner が 0 だと,表示しても反応しない
PD_ENABLEPRINTHOOK [印刷] ダイアログのフックのコールバック処理を有効にする (lpfnPrintHook の指定を有効にする)
PD_ENABLESETUPHOOK [プリンタ設定] ダイアログのコールバック処理を有効にする (lpfnSetupHook の指定を有効にする)
PD_ENABLEPRINTTEMPLATE [印刷] ダイアログのテンプレート(ダイアログのリソース,lpPrintTemplateName) の使用を有効にする
PD_ENABLESETUPTEMPLATE [プリンタの設定] ダイアログのテンプレート (ダイアログのリソース,lpSetupTemplateName) の使用を有効にする
PD_ENABLEPRINTTEMPLATEHANDLE [印刷] ダイアログのテンプレート(ダイアログのリソース,hPrintTemplate) の使用を有効にする
PD_ENABLESETUPTEMPLATEHANDLE [プリンタの設定]ダイアログのテンプレート(ダイアログのリソース,hSetupTemplate) の使用を有効にする
PD_USEDEVMODECOPIES hDevMode メンバーの dmCopies の値を使用するを指定するフラグだが,MSDN の記事によると,nCopies メンバと Flags メンバの PD_COLLATE フラグで操作可能なので特に意識する必要なしの記述がある
PD_USEDEVMODECOPIESANDCOLLATE PD_USEDEVMODECOPIES と同じ
PD_DISABLEPRINTTOFILE [ファイルへ出力] を選択不可にする
PD_HIDEPRINTTOFILE [ファイルへ出力] のチェックを非表示にする
PD_NONETWORKBUTTON [ネットワーク] ボタンを非表示にする


次の表はコールバック関数の定義と引数のリストです.
ダイアログは,コールバック関数を呼び出す前に,WM_INITDIALOG メッセージを処理します.他の全てのメッセージは,コールバック関数が受け取ります.操作の対象となるメッセージとして,例えば以下のようなものがあります.
他のメッセージは主に,WM_COMMAND で検出できます.また,コールバック関数内で,メッセージの送信は,PostMessage を使用します.


リスト1
コールバック関数の定義
function(Wnd: HWND; Message: UINT; wParam: WPARAM; lParam: LPARAM): UINT stdcall;


表3
ダイアログのコールバック関数の引数

引数 説明
Wnd HWND 印刷ダイアログのハンドル
Message UINT メッセージの種類
wParam WPARAM メッセージに関する情報.Message の値によって異なる
lParam LPARAM メッセージに関する情報.Message の値によって異なる
Message の値が WM_INITDIALOG の場合は PRINTDLG 構造体のポインタ
戻り値 UINT 0 にするとコールバック内の処理が実行される
0 以外にするとダイアログの既定の動作となる


テストプログラムです.
最初の 2 つは印刷ダイアログで,次の 2 つがプリンタの設定ダイアログです.コールバック関数を利用する際の参考コードと,あらかじめ,用紙サイズと印刷の向きを設定してからダイアログを表示するサンプルがあります.


図3
設計時画面
図4
[印刷] ダイアログ 1
  • Button1 クリック
  • PrintDlgAPI をデフォルトで使用
図5
[印刷]ダイアログ 2
  • Button2 クリック
  • コールバック関数内で以下の処理
    [ファイルへ出力] を非表示
    [部単位で印刷] を非表示
図6
[プリンタの設定] ダイアログ 1
  • Button3 クリック
図7
[プリンタの設定] ダイアログ 2
  • Button4 クリック
  • 用紙サイズと印刷の向きを指定してからダイアログを表示

リスト2
PrintDlg 関数を使用した印刷ダイアログとプリンタの設定ダイアログのテストコード
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, CommDlg;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    Button4: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
    procedure Button4Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses Printers;

{$R *.dfm}

//=============================================================================
//  PringDlg APIで[印刷]ダイアログの表示 1
//  引数の構造体の設定を,ほとんどデフォルトのままで実行
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  PrintDlgRec : TPrintDlg;
begin
  //構造体を0で埋める(つまり初期化)
  FillChar(PrintDlgRec, SizeOf(TPrintDlg), #0);

  //構造体(TPrintDlg)の各メンバの設定
  with PrintDlgRec do begin
    lStructSize := SizeOf(TPrintDlg);
    hWndOwner   := Handle;
    Flags       := 0;
  end;

  //ダイアログ表示
  if PrintDlg(PrintDlgRec) then begin
    //ここに印刷のコードを書く
    //ダイアログの設定内容が,構造体の各メンバーから取得できる
  end;
end;

//-----------------------------------------------------------------------------
//  PringDlg APIのコールバック関数([印刷]ダイアログ用)
//  このコードに実用性はない.このように使用できるというサンプル
//-----------------------------------------------------------------------------
function PrintHookProc(Wnd: HWND; Message: UINT; wParam: WPARAM;
  lParam: LPARAM): UINT stdcall;
var
  hWindow: HWND;
begin
  Result := 0;

  //初期化の時に処理(当然?)
  if Message = WM_INITDIALOG then begin
    //[ファイルへ出力]部分のハンドルを取得
    hWindow := GetDlgItem(Wnd, 1040);
    //そのハンドルのウィンドウを非表示
    ShowWindow(hWindow, SW_HIDE);

    //[部単位で印刷]部分のハンドルを取得
    hWindow := GetDlgItem(Wnd, 1041);
    //そのハンドルのウィンドウを非表示
    ShowWindow(hWindow, SW_HIDE);
  end;
end;

//=============================================================================
//  PringDlg APIを使用した[印刷]ダイアログの表示 2
//  コールバック関数の使用例
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  PrintDlgRec : TPrintDlg;
begin
  //構造体を0で埋める(つまり初期化)
  FillChar(PrintDlgRec, SizeOf(TPrintDlg), #0);

  //構造体(TPrintDlg)の各メンバの設定
  with PrintDlgRec do begin
    lStructSize   := SizeOf(TPrintDlg);
    hWndOwner     := Handle;
    //テストのため頁と部数を設定
    nFromPage     := 1;
    nToPage       := 6;
    //nMinPaeとnMaxPageがなければ[部単位で印刷]は非表示
    nMinPage      := 0;
    nMaxPage      := 4;
    //nCopiesがなければ当然[部単位で印刷]は無効表示となる
    nCopies       := 3;
    //コールバック関数を使用するフラグ
    Flags         := PD_ENABLEPRINTHOOK;
    //使用するコールバック関数のアドレスをセット
    lpfnPrintHook := @PrintHookProc;
  end;

  //ダイアログ表示
  if PrintDlg(PrintDlgRec) then begin
    //ここに印刷のコードを書く
    //ダイアログの設定内容が,構造体の各メンバーから取得できる
  end;
end;

//=============================================================================
//  PringDlg APIを使用した[プリンタの設定]ダイアログの表示 1
//  メンバーのFlagsにPD_PRINTSETUPを含めると,[プリンタ設定]ダイアログとなる
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  PrintDlgRec: TPrintDlg;
begin
  //構造体を0で埋める(つまり初期化)
  FillChar(PrintDlgRec, SizeOf(TPrintDlg), #0);

  //各パラメータ(TPringDlg構造体)の設定
  with PrintDlgRec do begin
    lStructSize := SizeOf(TPrintDlg);
    hWndOwner   := Handle;
    Flags       := PD_PRINTSETUP;
  end;

  //ダイアログ表示
  if PrintDlg(PrintDlgRec) then begin
    //ここに印刷のコードを書く
    //ダイアログの設定内容が,構造体の各メンバーから取得できる
  end;
end;

//=============================================================================
//  PringDlg APIを使用した[プリンタの設定]ダイアログの表示 2
//  メンバーのFlagsにPD_PRINTSETUPを含めると,[プリンタ設定]ダイアログとなる
//
//  用紙サイズと印刷の向きを指定してダイアログを表示する例
//  usesにPrintersが必要
//=============================================================================
procedure TForm1.Button4Click(Sender: TObject);
var
  PrintDlgRec : TPrintDlg;
  ADevice     : array[0..MAX_PATH - 1] of Char;
  ADriver     : array[0..MAX_PATH - 1] of Char;
  APort       : array[0..MAX_PATH - 1] of Char;
  ADeviceMode : THandle;
  ADevMode    : PDeviceMode;
begin
  Printer.GetPrinter(ADevice, ADriver, APort, ADeviceMode);
  //ADeviceModeのメモリをロックしDEVMODE構造体のポインタを取得
  ADevMode := GlobalLock(ADeviceMode);
  try
    //実際には該当メンバーをサポートしているかチェックしてからセットするのがよい
    ADevMode^.dmPaperSize   := DMPAPER_A3;
    ADevMode^.dmOrientation := DMORIENT_LANDSCAPE;
  finally
    GlobalUnlock(ADeviceMode);
  end;

  //構造体を0で埋める(つまり初期化)
  FillChar(PrintDlgRec, SizeOf(TPrintDlg), #0);

  //各パラメータ(TPageSetupDlg構造体)の設定
  with PrintDlgRec do begin
    lStructSize := SizeOf(TPrintDlg);
    hWndOwner   := Handle;
    hDevMode    := ADeviceMode;
    Flags       := PD_PRINTSETUP;
  end;

  //ダイアログ表示
  if PrintDlg(PrintDlgRec) then begin
    //ここに印刷のコードを書く
    //ダイアログの設定内容が,構造体の各メンバーから取得できる
  end;
end;

end.


指定したプリンタ名でダイアログを表示するには,hDevNames メンバーに値を設定することになっていますが,次のコードのように,現在のプリンタを変更して,hDevMode メンバーに,DEVMODE 構造体のハンドルをセットする方法のほうが簡単です.
この方法であれば,プリンタ名だけでなく,用紙サイズ,印刷の方向など,他のプロパティも同時に設定してダイアログを表示できます.


リスト3
プリンタの番号 (インデックス) で,プリンタ名を指定してダイアログ表示

procedure TForm1.Button4Click(Sender: TObject);
var
  AIndex      : Integer;
  ADevice     : array[0..MAX_PATH - 1] of Char;
  ADriver     : array[0..MAX_PATH - 1] of Char;
  APort       : array[0..MAX_PATH - 1] of Char;
  ADeviceMode : THandle;
  ADevMode    : PDeviceMode;
  PrintDlgRec : TPrintDlg;
begin
  //プリンタのインデックスを求める(簡略法)
  AIndex := Printer.Printers.IndexOf('プリンタ名');
  Printer.PrinterIndex := AIndex;
  //Printer.PrinteIndexのプリンタの初期化
  //この3行で実行しないと,ADeviceModeの内容が更新されない
  Printer.GetPrinter(ADevice, ADriver, APort, ADeviceMode);
  Printer.SetPrinter(ADevice, ADriver, APort, 0);
  Printer.GetPrinter(ADevice, ADriver, APort, ADeviceMode);

  //プリンタのプロパティの設定をしなければこのコードは不要
  ADevMode := GlobalLock(ADeviceMode);
  try
    //必要であればここでプリンタのプロパティの設定を行う
  finally
    GlobalUnlock(ADeviceMode);
  end;

  //構造体を0で埋める(つまり初期化)
  FillChar(PrintDlgRec, SizeOf(TPrintDlg), #0);

  //各パラメータ(TPageSetupDlg構造体)の設定
  with PrintDlgRec do begin
    lStructSize := SizeOf(TPrintDlg);
    hWndOwner   := Handle;
    Flags       := PD_PRINTSETUP;
    hDevMode    := ADeviceMode;
  end;

  //ダイアログ表示
  if PrintDlg(PrintDlgRec) then begin
    //ここに印刷のコードを書く
    //ダイアログの設定内容が,構造体の各メンバーから取得できる
  end;
end;




02_[ 印刷 ] ダイアログ - PrintDlgEx 関数


PrintDlgEx 関数のテストプログラムです.
引数は 1 つで,TPrintDlgEx 構造体です.構造体のメンバー等の内容は,[参考リンク] の記事を参考にしてください.PrintDlg 関数との違いは,
  • [印刷] ダイアログ専用
  • [適用] ボタンがある
  • [ページ指定] の項目で範囲も指定可能
  • [プロパティシート] の追加が可能
となっています.
[適用] ボタンをクリックすすると,その後 [キャンセル] しても,関数実行後に,構造体に[適用] した時の値が格納されます.[プロパティシート] は,ダイアログにタブを追加し,そのタブに独自のダイアログを表示するものです.このダイアログは,DLL から,あるいはリソースから読み込みます.
以下のサンプルプログラムでは,リソースエディタで作成したダイアログを表示しています.

コールバック関数は,[730] [05_UrlDownloadToFile における進捗ダイアログの表示] のコールバック関数と同じく,インターフェイス型となっています.IPrintDialogCallback というインタフェースを実装します.Delphi XE には実装されていません.
ただし,プロパティシート用のコールバック関数は,PrintDlg 関数で説明したコールバック関数と同じ定義となっています.

PrintDlgEx 関数もその構造体の定義も,Delphi XE にはありません.そこで,以下のサンプルではコード内で定義しています.


図8
設計時画面
図9
[印刷] ダイアログ 1
図10
[印刷] ダイアログ 2
  • Button2 クリック
  • [ページ指定] を設定してから表示
図11
[印刷]ダイアログ 3
  • Button3 クリック
  • 独自のプロパティシートを追加表示

リスト4
PrintDlgEx 関数を使用した印刷ダイアログのテストコード
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, CommDlg, CommCtrl;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

//-----------------------------------------------------------------------------
//  PrintDlgEx用の定数と構造体(レコード型)と関数の定義
//  Delphi XEにないものだけを追加
//-----------------------------------------------------------------------------
const
  START_PAGE_GENERAL  = $FFFFFFFF;
  PD_RESULT_APPLY     = 2;
  PD_RESULT_CANCEL    = 0;
  PD_RESULT_PRINT     = 1;
  PD_EXCLUSIONFLAGS   = $01000000;
  PD_CURRENTPAGE      = $00400000;
  PD_NOCURRENTPAGE    = $00800000;
  PD_USELARGETEMPLATE = $10000000;

type
  PPRINTPAGERANGE = ^TPRINTPAGERANGE;
  tagPRINTPAGERANGE = packed record
    nFromPage : DWORD;
    nToPage   : DWORD;
  end;
  TPrintPageRange  = tagPRINTPAGERANGE;

  PPRINTDLGEX = ^TPRINTDLGEX;
  tagPDEX = packed record
    lStructSize         : DWORD;
    hwndOwner           : HWND;
    hDevMode            : HGLOBAL;
    hDevNames           : HGLOBAL;
    hDC                 : HDC;
    Flags               : DWORD;
    Flags2              : DWORD;
    ExclusionFlags      : DWORD;
    nPageRanges         : DWORD;
    nMaxPageRanges      : DWORD;
    lpPageRanges        : PPRINTPAGERANGE;
    nMinPage            : DWORD;
    nMaxPage            : DWORD;
    nCopies             : DWORD;
    hInstance           : HINST;
    lpPrintTemplateName : PAnsiChar;
    lpCallback          : PUNKNOWN;
    nPropertyPages      : DWORD;
    lphPropertyPages    : HPropSheetPage; //CommCtrl.pas
    nStartPage          : DWORD;
    dwResultAction      : DWORD;
  end;
  TPrintDlgEx = tagPDEX;

  TPageRange = packed record
    nFromPage, nToPage : DWord;
  end;

  function PrintDlgEx(var PrintDlgEx: TPrintDlgEx): HRESULT; stdcall;
//-----------------------------------------------------------------------------

var
  Form1: TForm1;

implementation

{$R *.dfm}
//リソースをインクルード
{$R TEST.RES}

function PrintDlgEx; external 'comdlg32.dll' name 'PrintDlgExW';

//=============================================================================
//  PringDlgEx APIで[印刷]ダイアログの表示 1
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  PDEXRec : TPrintDlgEx;
begin
  //構造体を0で埋める(つまり初期化)
  FillChar(PDEXRec, SizeOf(PDEXRec), #0);

  //構造体(レコード型)の各メンバーに値をセット
  with PDEXRec do begin
    lStructSize    := SizeOf(TPrintDlgEx);
    hWndOwner      := Handle;
    Flags          := PD_ALLPAGES or PD_NOPAGENUMS;
    nMaxPageRanges := 3;
    nMinPage       := 1;
    nMaxPage       := 10;
    nCopies        := 1;
    nStartPage     := START_PAGE_GENERAL;
  end;

  if PrintDlgEx(PDEXRec) = PD_RESULT_PRINT then begin
    //ここに印刷コードを書く
    //ダイアログで設定した内容は,構造体の各メンバーから取得する
  end;
end;

//=============================================================================
//  PringDlgEx APIで[印刷]ダイアログの表示 2
//  ページ指定を設定してから表示
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  PDEXRec        : TPrintDlgEx;
  PageRangeArray : array[0..1] of TPrintPageRange;
begin

  //構造体を0で埋める(つまり初期化)
  FillChar(PDEXRec, SizeOf(PDEXRec), #0);

  //ページ指定の場合の,ページの範囲を設定
  PageRangeArray[0].nFromPage := 1;
  PageRangeArray[0].nToPage   := 3;
  PageRangeArray[1].nFromPage := 5;
  PageRangeArray[1].nToPage   := 8;

  //構造体(レコード型)の各メンバーに値をセット
  with PDEXRec do begin
    lStructSize    := SizeOf(TPrintDlgEx);
    hWndOwner      := Handle;
    Flags          := PD_PAGENUMS;
    nMaxPageRanges := 3;
    nPageRanges    := Length(PageRangeArray);
    lpPageRanges   := @PageRangeArray[0];
    nMinPage       := 1;
    nMaxPage       := 10;
    nCopies        := 1;
    nStartPage     := START_PAGE_GENERAL;
  end;

  if PrintDlgEx(PDEXRec) = PD_RESULT_PRINT then begin
    //ここに印刷コードを書く
    //ダイアログで設定した内容は,構造体の各メンバーから取得する
  end;
end;

//-----------------------------------------------------------------------------
//  TPropSheetPage(プロパティシート用構造体)のコールバック関数
//  pfnDlgProcメンバの値に,この関数名のポインタを指定する
//
//  この関数内では,EndDialogを呼び出してはいけない.戻り値は通常0とする
//-----------------------------------------------------------------------------
function PropSheetHookProc(Wnd: HWND; Message: UINT; wParam: WPARAM;
      lParam: LPARAM): UINT stdcall;
var
  TextEdit : String;
begin
  Result := 0;

  if Message = WM_COMMAND then begin
    //[OK]ボタンクリック
    //if wParam = IDOK でも可
    if wParam = 1 then begin
      MessageDlg('[ OK ] ボタンを押しましたね ',mtInformation, [mbOK], 0);
    end else
    //[参照]ボタンクリック
    if wParam = 102 then begin
      TextEdit := 'プロパティシート追加のテスト';
      SetDlgItemText(Wnd, 101, PChar(TextEdit));
    end;
  end;
end;

//=============================================================================
//  PringDlgEx APIで[印刷]ダイアログの表示 3
//  ページ指定を設定してから表示
//  ダイアログにユーザ設定のタブシート(プロパティシート)を追加する例
//  ここでは,リソースファイル(.RES)に格納してあるダイアログを表示
//
//  プロパティシート関係のコードには,usesにCommCtrlが必要
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  hpsp    : array[0..0] of PPropSheetPage;
  psp     : TPropSheetPage;
  PDEXRec : TPrintDlgEx;
begin
  //プロパティシート用構造体を初期化
  FillChar(psp, SizeOf(TPropSheetPage), #0);

  with psp do begin
    dwSize      := SizeOf(TPropSheetPage);
    dwFlags     := PSP_USETITLE;
    pszTemplate := MakeIntResource('DIALOG_1');
    pfnDlgProc  := @PropSheetHookProc;
    hInstance   := hInstance;
    //deFlagsにPSP_USETITLEがあると,元のダイアログのCatioonの変更が可能
    pszTitle := '印刷ファイル';
  end;
  //上の設定で0番目のプロパティシートを作成
  hpsp[0] := CreatePropertySheetPage(psp);


  //PrintDlgEXで使用する構造体を0で埋める(つまり初期化)
  FillChar(PDEXRec, SizeOf(PDEXRec), #0);

  with PDEXRec do begin
    lStructSize    := SizeOf(TPrintDlgEx);
    hWndOwner      := Handle;
    Flags          := PD_ALLPAGES or PD_NOPAGENUMS;
    nMaxPageRanges := 3;
    nMinPage       := 1;
    nMaxPage       := 10;
    nCopies        := 1;
    //プロパティシートの最初のタブを起動時にアクティブにする
    //通常は,START_PAGE_GENERALにしておく
    nStartPage     := 0;

    nPropertyPages   := Length(hpsp);
    lphPropertyPages := @hpsp[0];
  end;

  if PrintDlgEx(PDEXRec) = PD_RESULT_PRINT then begin
    //ここに印刷コードを書く
    //ダイアログで設定した内容は,構造体の各メンバーから取得する
  end;
end;

end.




03_リソースエディタでダイアログを作成


上のサンプルのプロパティシートに表示しているダイアログは,下図のように,リソースエディタで作成しました.リソースエディタとして XN Resource Editor の日本語版を使用し,リソースファイル名を TEST.RES として保存しています.
このリソースエディタ,XN Resource Editor の元の英語版で日本語を使用すると,リソースが壊れてしまうことがあります.


  • 図12
  • リソースエディタで,プロパティシートに表示するダイアログを作成

上のサンプルコードでは,リソースの有無の確認をしていません.実際には,以下のようなコードで確認した方がいいでしょう.


リスト5
リソースの存在を確認するコード
var
  hRes : HRSRC;
begin
  //該当リソースがない場合は,戻りが0となる
  hRes := FindResource(hInstance, 'DIALOG_1', RT_DIALOG);




04_[ ページ設定 ] ダイアログ - PageSetupDlg 関数


PapgeSetupDlg 関数のテストプログラムです.
引数は 1 つで,TPageSetupDlg 構造体です.下の表はこの構造体のメンバのリストです.


表4
TPageSetupDlg 構造体(レコード型)のメンバー
変数名と型は Delphi XE の CommDlg.pas のもの

メンバー 説明
lStructSize DWORD この構造体のサイズを必ず指定する
hwndOwner HWND ダイアログの親ウィンドウを指定する
0 でもよいがモーダル表示のためには指定した方がよい
hDevMode HGLOBAL DEVMODE 構造体のハンドル
実行後はダイアログで設定した値を返す
この情報でダイアログを表示する
hDevNames HGLOBAL プリンタドライバ名,デバイス名,出力ポート名等を格納した DEVNAMES 構造体のポインタを指定する
実行後はダイアログで設定した値を返す
hDevMode の指定よりも優先される
Flags DWORD ダイアログの初期化や各コントロールの表示等の制御のフラグを指定する.値は下表の組み合わせ
終了時には,ダイアログで設定した値が返ってくる
ptPaperSize TPoint ダイアログで選択した用紙のサイズが格納される
出力専用
Flags メンバの値によって寸法の単位が異なる
PSD_INHUNDREDTHSOFMILLIMETERS (1/1000 inch 単位)
PSD_INTHOUSANDTHSOFINCHES (1/100 mm 単位)
rtMinMargin TRect ページの余白の最低値を指定する TRect 構造体
ptPaperSize と同じく Flags メンバによって単位が異なる
rtMargin TRect ページ余白を指定する TRect 構造体
実行後にダイアログの設定値を返す
ptPaperSize と同じく Flags メンバによって単位が異なる
hInstance HINST Flags に以下のいずれかの値を指定した場合に指定する PSD_ENABLEPAGESETUPTEMPLATE
PSD_ENABLEPAGESETUPTEMPLATEHANDLE
この場合,この hInstance は   lpPapgeSetupTemplateName か hPageSetupTemplateName で指定したダイアログボックステンプレート (EXE とか DLL )のインスタンスのハンドルとする
lCustData LPARAM lpPapgeSetupTemplateName か hPageSetupTemplateName を使用する場合に使用
アプリ独自のカスタムデータを設定する
フックのコールバック関数では,WM_INITDIALOG メッセージの時にこの値を受け取ることができる
lpfnPageSetupHook Integer [ページ設定] ダイアログの動作仕様を変更するためのコールバック関数のアドレスを指定する
Flags に PSD_ENABLEPAGESETUPHOOK を指定した場合に有効
lpfnPagePaintHook Integer [ページ設定] ダイアログの動作仕様を変更するためのコールバック関数のアドレスを指定する
Flags に PSD_ENABLEPAGEPAINTHOOK を指定した場合に有効
lpPageSetupTemplateName PAnsiChar [ページ設定] ダイアログテンプレートのリソース名を指定する
これで標準のダイアログを置き換える
Flags に PSD_ENABLEPAGESETUPTEMPLATE を指定した場合のみ有効
hPageSetupTemplate HGLOBAL [ページ設定] ダイアログテンプレートのリソースハンドルを指定する
これで標準のダイアログを置き換える
Flags に PSD_ENABLEPAGESETUPTEMPLATEHANDLE を指定した場合に有効


次の表は,PageSetupDlg 関数の引数である構造体の,Flags メンバーで設定可能な値のリストです.
また,図は Delphi のページ設定ダイアログのプロパティです.これらを較べてみると,Flags メンバーで指定可能なフラグが,Delphi の,これらのコンポーネントでも設定可能なことが分かります.


図13
TPageSetupDialog のプロパティ
  • PageSetupDlg 関数の Flags で設定可能なプロパティがほとんど用意されている

表5
Flags の値 (Delphi XE の CommDlg.pas のもの)

PSD_DEFAULTMINMARGINS 使用するプリンタデフォルトの余白最小値を用いる.デフォルトの設定
PSD_MARGINS,PSD_MINMARGINS がある場合は,それらが優先となる
PSD_INWININIINTLMEASURE 予約済み
PSD_MINMARGINS rtMinMargin メンバの設定を有効にする
PSD_MARGINS rtMarginメンバの設定を有効にする
PSD_INTHOUSANDTHSOFINCHES ダイアログでの 余白の入力単位を 1/1000 インチとする
PSD_INHUNDREDTHSOFMILLIMETERS ダイアログでの余白の入力単位を 1/100 mm とする
PSD_DISABLEMARGINS [余白] グループのエディットボックスを無効にし,余白の設定をできないようにする
PSD_DISABLEPRINTER [プリンタの設定] ボタンを無効にする
PSD_NOWARNING デフォルトプリンタが無い場合の警告メッセージを表示しないようにする
PSD_DISABLEORIENTATION 印刷方向の設定を無効にする
PSD_RETURNDEFAULT hDevNames と hDevMode メンバにデフォルトプリンタで初期化された DEVMODE と DEVNAMES 構造体のハンドルを返すようにする
PSD_DISABLEPAPER 用紙サイズと給紙方法の設定を無効にする
PSD_SHOWHELP ダイアログボックスにヘルプボタンを表示する hwndOwner が 0 だと,表示しても反応しない
PSD_ENABLEPAGESETUPHOOK lpfnPageSetupHook の設定を有効にする
PSD_ENABLEPAGESETUPTEMPLATE [ページ設定」ダイアログのテンプレート(リソース)であるlpPageSetupTemplateNameの設定を有効にする
PSD_ENABLEPAGESETUPTEMPLATEHANDLE 「ページ設定」ダイアログのテンプレート(リソース) である hPageSetupTemplate の設定を有効にする
PSD_ENABLEPAGEPAINTHOOK lpfnPagePaintHook の設定を有効にする
PSD_DISABLEPAGEPAINTING サンプルページ(プレビューの様な画像)の描画をしない
ただし,描画のフックが有効な場合は,このフラグは無効となる
PSD_NONETWORKBUTTON [プリンタの設定] ボタンをクリックして表示されるダイアログの [ネットワーク] ボタンを無効にする


lpfnSetupHoo と lpfnPrintHook メンバーで指定するコールバック関数は,PrintDlg 関数の定義と同じです.lpfnPrintHook メンバに指定するフック関数は,下図のような [サンプルページ] の描画をカスタマイズするために使用するものです.
これも Delphi では TPageSetupDialog コンポーネントのイベントとして用意されています.
Flags に PSD_DISABLEPAGEPAINTING を含めると,このサンプルページが白紙で表示されます.

lpfnPrintHook のフック関数内で処理可能なメッセージ類は,PagePaintHook callback function の記事を参考にしてください.


図14
TPageSetupDialog のイベント
  • [サンプルページ] 描画用のイベント
図15
[サンプルページ] の意味

テストプログラムです.
コールバック関数の使用例と,あらかじめ,用紙サイズと印刷の向きを設定してからダイアログを表示するサンプルがあります.


図16
設計時画面
図17
[ページ設定] ダイアログ 1
  • Button1 クリック
  • Flags に何も設定していない場合
図18
[ページ設定] ダイアログ 2
  • Button2 クリック
  • コールバック関数内でダイアログの上に文字列を描画した例
図19
[ページ設定] ダイアログ 3
  • Button3 クリック
  • 用紙サイズ,印刷の向き,余白を設定してからダイアログを表示

リスト6
PageSetupDlg 関数を使用したページ設定ダイアログ表示のテストコード
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, CommDlg;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    Button3: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Button3Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses Printers;

{$R *.dfm}

//=============================================================================
//  PageSetupDlg APIで[ページ設定]ダイアログの表示 1
//  引数の構造体の設定を,ほとんどデフォルトのままで実行
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  PageSetupDlgRec : TPageSetupDlg;
begin
  //構造体を0で埋める(つまり初期化)
  FillChar(PageSetupDlgRec, SizeOf(TPageSetupDlg), #0);

  //各パラメータ(TPageSetupDlg構造体)の設定
  with PageSetupDlgRec do begin
    lStructSize := SizeOf(TPageSetupDlg);
    hWndOwner   := Handle;
    Flags       := 0;
  end;

  //ダイアログ表示
  if PageSetupDlg(PageSetupDlgRec) then begin
    //構造体の各メンバーから値を取出せる
  end;
end;

//-----------------------------------------------------------------------------
//  PageSetupDlg APIのコールバック関数
//  ダイアログの操作用のlpfnPageSetupHookメンバのコールバック関数
//-----------------------------------------------------------------------------
function PageSetupHookProc(Wnd: HWND; Message: UINT; wParam: WPARAM;
  lParam: LPARAM): UINT stdcall;
var
  TargetRect : TRect;
  hWindowHDC : HDC;
  ACanvas    : TCanvas;
begin
  Result := 0;

  if Message = WM_INITDIALOG then begin
    //ダイアログの表示位置を変更
    SetWindowPos(Wnd, 0, 200, 30, 0, 0, SWP_NOSIZE or SWP_NOZORDER);
  end else
  if Message = WM_CTLCOLORDLG then begin
    hWindowHDC := GetWindowDC(Wnd);

    //ウィンドウのデバイスハンドルを直接操作するコードでもよいが,
    //TCanvasを使用した方が簡単なので,TCanvasを生成して実行
    ACanvas        := TCanvas.Create;
    try
      ACanvas.Handle := hWindowHDC;

      GetClientRect(Wnd, TargetRect);
      ACanvas.Brush.Style := bsClear;
      ACanvas.Font.Color  := clRed;
      ACanvas.Font.Size   := 20;
      TargetRect.Bottom   := 200;

      DrawText(ACanvas.Handle, 'ページ設定ダイアログテスト', -1, TargetRect,
               DT_CENTER  or DT_VCENTER or DT_SINGLELINE);
    finally
      FreeAndNil(ACanvas);
    end;
  end;
end;

//=============================================================================
//  PageSetupDlg APIで[ページ設定]ダイアログの表示 2
//  コールバック関数の使用例
//  構造体のlpfnPageSetupHookを使用した例
//  この他に,サンプルページ描画用のフック関数が使用可能
//=============================================================================
procedure TForm1.Button2Click(Sender: TObject);
var
  PageSetupDlgRec : TPageSetupDlg;
begin
  //構造体を0で埋める(つまり初期化)
  FillChar(PageSetupDlgRec, SizeOf(TPageSetupDlg), #0);

  //各パラメータ(TPageSetupDlg構造体)の設定
  with PageSetupDlgRec do begin
    lStructSize       := SizeOf(TPageSetupDlg);
    hWndOwner         := Handle;
    Flags             := PSD_ENABLEPAGESETUPHOOK;
    lpfnPageSetupHook := @PageSetupHookProc;
  end;

  //ダイアログ表示
  if PageSetupDlg(PageSetupDlgRec) then begin
    //構造体の各メンバーから値を取出せる
  end;
end;

//=============================================================================
//  PageSetupDlg APIで[ページ設定]ダイアログの表示 3
//  用紙サイズと印刷の方向と余白を設定してからダイアログを表示する例
//  usesにPrintersが必要
//=============================================================================
procedure TForm1.Button3Click(Sender: TObject);
var
  PageSetupDlgRec : TPageSetupDlg;
  ADevice         : array[0..MAX_PATH - 1] of Char;
  ADriver         : array[0..MAX_PATH - 1] of Char;
  APort           : array[0..MAX_PATH - 1] of Char;
  ADeviceMode     : THandle;
  ADevMode        : PDeviceMode;
begin
  Printer.GetPrinter(ADevice, ADriver, APort, ADeviceMode);
  //ADeviceModeのメモリをロックしDEVMODE構造体のポインタを取得
  ADevMode := GlobalLock(ADeviceMode);
  try
    //実際には該当メンバーをサポートしているかチェックしてからセットするのがよい
    ADevMode^.dmPaperSize   := DMPAPER_A3;
    ADevMode^.dmOrientation := DMORIENT_LANDSCAPE;
  finally
    GlobalUnlock(ADeviceMode);
  end;

  //構造体を0で埋める(つまり初期化)
  FillChar(PageSetupDlgRec, SizeOf(TPageSetupDlg), #0);

  //各パラメータ(TPageSetupDlg構造体)の設定
  with PageSetupDlgRec do begin
    lStructSize     := SizeOf(TPageSetupDlg);
    hWndOwner       := Handle;
    hDevMode        := ADeviceMode;
    rtMargin.Top    := 3000;
    rtMargin.Left   := 2500;
    rtMargin.Right  := 2000;
    rtMargin.Bottom := 3200;
    Flags           := PSD_MARGINS or PSD_INHUNDREDTHSOFMILLIMETERS;
  end;

  //ダイアログ表示
  if PageSetupDlg(PageSetupDlgRec) then begin
    //構造体の各メンバーから値を取出せる
  end;
end;

end.




05_TPrinter を使用した印刷コードの実装


PrintDlg 関数,PrintDlgEx 関数を使用してプリンタ関係の設定をした場合,基本的には,StartDoc 等の Windows API を使用して印刷コードを書くことになります.
PrintDlg 関数や PrintDlgEx 関数で設定した内容は,TPrinter オブジェクトには反映されません.

Windows API のダイアログ関数で設定を行った場合でも,TPrinter を使用して印刷コードを実装するには,該当プリンタの DEVMODE 構造体の値を使用して,プリンタの設定を行う必要があります.その参考コードです.

  • (1) hDevNames メンバーからプリンタ名を取出す
  • (2) そのプリンタ名のプリンタを現在のプリンタとする
  • (3) hDevMode メンバーの値で,そのプリンタの DEVMODE 構造体を設定
  • (4) TPrinter を使用した印刷コードを書く
という手順となります.コードについては以下のリンクの記事も参考にしてください.


リスト7
TPrinter を使用して印刷する場合の参考コード
uses に Printers が必要
unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, CommDlg;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    { Private 宣言 }
  public
    { Public 宣言 }
  end;

var
  Form1: TForm1;

implementation

uses Printers;

{$R *.dfm}

//=============================================================================
//  TPrinterを使用して印刷する場合の参考コード
//=============================================================================
procedure TForm1.Button1Click(Sender: TObject);
var
  AIndex      : Integer;
  ADevice     : array[0..MAX_PATH - 1] of Char;
  ADriver     : array[0..MAX_PATH - 1] of Char;
  APort       : array[0..MAX_PATH - 1] of Char;
  ADeviceMode : THandle;
  ADevMode    : PDeviceMode;
  PrintDlgRec : TPrintDlg;
  Offset      : PChar;
  PrinterName : String;
  hPrinter    : THandle;
  Mode        : Cardinal;
  Names       : PDevNames;
begin
  FillChar(PrintDlgRec, SizeOf(TPrintDlg), #0);

  with PrintDlgRec do begin
    lStructSize := SizeOf(TPrintDlg);
    hWndOwner   := Handle;
    Flags       := PD_PRINTSETUP;
  end;

  if PrintDlg(PrintDlgRec) then begin
    Update;
    Names := GlobalLock(PrintDlgRec.hDevNames );
    try
      Offset      := PChar(Names);
      PrinterName := String(Offset + Names.wDeviceOffset);
    finally
      GlobalUnlock(PrintDlgRec.hDevNames);
    end;

    AIndex := Printer.Printers.IndexOf(PrinterName);
    Printer.PrinterIndex := AIndex;
    Printer.GetPrinter(ADevice, ADriver, APort, ADeviceMode);
    Printer.SetPrinter(ADevice, ADriver, APort, 0);
    Printer.GetPrinter(ADevice, ADriver, APort, ADeviceMode);

    ADevMode := GlobalLock(ADeviceMode);
    try
      ADeviceMode := PrintDlgRec.hDevMode;
    finally
      GlobalUnlock(ADeviceMode);
    end;
    Printer.SetPrinter(ADevice, ADriver, APort, ADeviceMode);

    Printer.Title := '印刷テスト';
    Printer.BeginDoc;
    try
      Printer.Canvas.Font.Size := 25;
      Printer.Canvas.TextOut(0, 0, 'プリンタ関係ダイアログAPIのテスト');
    finally
      Printer.EndDoc;
    end;
  end;
end;

end.