概要

 PageSetup及びPrintDlg APIを用いてページ設定及び印刷コモンダイアログを表示してサンプルを印刷します。
 ページ設定時の用紙の表示に印刷内容が表示されるようPageSetup API呼び出し時にフック関数にPagePaintHook関数を設定してカスタム描画しています。
 描画及び印刷内容は四角形とフォントがMS 明朝でMS明朝という文字列でウィンドウサイズ最大、「印刷は用紙サイズから指定した余白を満たす最大の大きさで印刷します。
 以下に動作例を示します。
ファイルメニューを表示

ファイルメニュー・ページ設定:PagetSetup(ページ設定)コモンダイアログを表示

PagetSetup(ページセットアップ)コモンダイアログの用紙をカスタム描画しない場合

ファイルメニュー・印刷:PrintDlg(印刷)コモンダイアログを表示

テスト環境

コンパイラ

Visual C++ 2013 Express 32/64bit

プログラムソースの概要

_tWinMain関数

 Windowsから最初に_tWinMain関数が呼び出されます。
 ウィンドウを作成する場合は、RegisterClass APIによりウィンドウクラスを定義してからCreateWindow APIを呼び出しウィンドウを作成します。
 Windowsは入力等のイベントが発生するとアプリケーションにメッセージを送付します。メッセージはキューに保管されます。アプリケーションはメッセージを取り出し、該当ウィンドウにメッセージを配信します。 メッセージの取り出しから配信までループで処理を行いウィンドウから終了メッセージが届くと、ループを抜けるように記述します。 これらの一連の処理は、通常にCreateWindow APIの後に記述しこれをメッセージループと呼んでいます。
 詳細は以下を参照してください。
 メッセージループについて

WndProc

 RegisterClass APIにより登録することによりWindowsから呼び出されます。
 第2引数にメッセージの種類が格納されていますので、switchステートメントによりメッセージごとの処理を振り分けます。
 自分で処理しないメッセージはDefWindowProc APIに渡せばWindowsが標準的な処理を行ってくれます。

case WM_CREATE:

 ウィンドウの初期化時に呼び出されます。
 PAGESETUPDLG構造体であるpsdの初期化を行っています。
 まずZeroMemory APIでゼロクリアの後、構造体サイズの設定及び親ウィンドウ名、フック関数などを設定しています。
 Flagsメンバーには余白の単位を㎜とするためPSD_INHUNDREDTHSOFMILLIMETERSとPaintフック関数を使用するためPSD_ENABLEPAGEPAINTHOOKを設定しています。

WM_COMMAND

 メニューが選択されたときに呼び出されます。 LOWORD(wp)により選択されたメニューのIDが取得できます。
IDM_QUIT
 WM_CLOSEメッセージを呼びだしプログラムを終了させます。
IDM_PAGESET
 ページ設定メニューから呼び出されます。
 PageSetupDLG APIを呼び出し用紙サイズ・余白等を取得します。
 設定内容を規定のプリンタに反映させるためにプリンタを開き、プリンタの状態を取得しPageSetup APIで設定した内容を反映させプリンタに設定するという流れの処理を行います。
 まずプリンタの名前をPageSetup APIで取得されたPAGESETUPDLG構造体のhDevModeメンバーをGlobalLock APIによりDEVMOD構造体のポインタを取得します。この構造体のメンバーにプリンタ名が含まれているのでpNameにコピーします。
 PRINTER_DEFULTS構造体をZeroMemoryでゼロクリアした後、メンバ0DesiredAccessにセキュリテイ属性としてPRINTER_ACCESS_USEを設定します。PRINTER_ALL_ACCESSを設定すると次のOpenPrinter APIでエラーになります。
 GetPrinter APIを呼び出す際に第3引数にPRINTER_INFO_2の配列を渡しますが、必要なサイズがわかりません。
 1回目の呼び出しでは第3・4引数に0を指定して呼び出すと第5引数に必要なサイズを取得できます。
 GlobalAllocで必要なサイズを確保し、再度GetPrinter APIを呼び出します。
 GetPrinter APIを呼び出したことにより第3引数で指定したPRINTER_INFO_2のポインタにプリンタの現在の状態が格納されています。
 PageSetup APIで取得した用紙サイズ・余白等をPRINTER_INFO_2構造体に反映させます。
 SetPrinter APIにより用紙サイズ・余白等をプリンタへ設定します。
IDM_PRINT
 印刷メニューから呼び出されます。
 PRINTDLG構造体を初期化してPrintDlg APIを呼び出します。このAPIが正常に終了するとTRUEを返します。
 PrintDlg APIで取得されたデバイスコンテキストを使用してStartDoc APIを呼び出します。
 StartPageにより新しいページの開始を指定します。
 用紙の四隅には印刷できない領域があるため余白の値から控除する必要があります。
 印刷できない領域の左と上はデバイスコンテキストhdcを指定してGetDeviceCapsで以下のように簡単に取得できます。
DWORD offsetX1 = GetDeviceCaps(hdc, PHYSICALOFFSETX); //左(ピクセル)
DWORD offsetY1 = GetDeviceCaps(hdc, PHYSICALOFFSETY); //上(ピクセル)
 印刷できない領域の右と下は直接取得できないので用紙サイズから印刷可能領域と反対側の領域を控除します。
//右(ピクセル)     用紙幅                   印刷可能領域幅       左印刷不可能領域幅
GetDeviceCaps(pd.hDC, PHYSICALWIDTH)-GetDeviceCaps(hdc, HORZRES)-offsetX1
//下(ピクセル)     用紙高さ                 印刷可能領域高さ     上印刷不可能領域高さ
GetDeviceCaps(pd.hDC, PHYSICALHEIGHT)-GetDeviceCaps(pd.hDC, VERTRES)-offsetY1
 なおPageSetup APIで取得された余白は1/100ミリ単位ですのでピクセルに変換する必要があります。
 プリンタのインチあたりの解像度は以下の方法で取得できます。
DPIx = GetDeviceCaps(pd.hDC, LOGPIXELSX);
DPIy = GetDeviceCaps(pd.hDC, LOGPIXELSY);
 1/100ミリ単位をピクセルに変換は、余白(ミリ)*DPI/25.4/100で計算できます。
 浮動小数点が計算式に出てくるので分母を2540にすれば整数で計算できます。
 余白の値はPageSetup APIで使用したPAGESETUPDLG構造体のRECT型のrtMarginメンバーで取得できます。
 ピクセル単位で四隅の印刷できない領域を計算し、余白ピクセル値から控除します。
 プリンタの描画座標は印刷可能領域の左上が0,0となっていますので余白の部分の座標を使わないようにしなければなりません。
 印刷できない領域と余白を考慮して実際に印刷する領域を計算しRECT型に保存します。
 bigFontPaint関数にデバイスコンテキストとRECT型を渡し印刷します。
 EndPage APIにより今のページを終了させ  全部のフォントを印刷したらEndDoc APIにより印刷ジョブが終了したことを指定します。

WM_CLOSE

DestoryWindowを呼び出してウィンドウを終了させます。

case WM_DESTROY:

ウィンドウが閉じるときに呼び出されます。
PostQuitMessage APIにより終了コードを指定して、ウィンドウプロシージャーを終了させます。

case WM_PAINT:

 ウィンドウを再描画する必要があるときに呼び出されます。
 他のウィンドウに隠れ再びフォアグラウンドになった場合などウィンドウの再描画はWindowsが面倒を見ないのでプログラマの仕事となっています。
 BeginPaint APIを呼び出して、描画に必要なデバイスコンテキストのハンドルを取得します。
 BeginPaint APIの第2引数のポインタにはPAINTSTRUCT構造体のポインタを渡します。
 BeginPaint API終了後、PAINTSTRUCT構造体には再描画が必要な領域の座標等の値が格納されています。
 GetClientRect APIによりクライアントウィンドウサイズを取得します。
 bigFontPaint関数にデバイスコンテキストとRECT型を渡しウィンドウに描画します。
 再描画が終了したことをWindowsに知らせるためにEndPaint APIを使用してます。このAPIを呼び出さないと何度もWM_PAINTメッセージが発生し暴走します。

PagePaintHook

 PageSetupで指定したイメージ表示用のフック関数です。

WM_PSD_GREEKTEXTRECT

 イメージ表示前に呼び出されるメッセージです。
 デバイスコンテキストと印刷領域RECTを取得してbigFontPaint関数を呼び出しイメージを表示します。

bigFontPaint

 引数RECTで指定された領域いっぱいに文字と四角形を描画します。
 引数RECTのメンバーbottom-topと同一の大きさのフォントをCreateFont APIにより作成します。
 SelectObjectにより新しいフォントを選択するとともに旧フォントハンドルをhOldFontに格納します。
 GetTextExtentPoint32 APIにより新しいフォントで描画した場合の縦横のピクセル数をSIZE構造体に格納します。
 クライアントの縦横比と描画した場合の縦横比を比較しクライアント幅を描画するフォントの幅が上回る場合には比率により新しいフォントを作成しGetTextExtentPoint32 APIを呼び出し必要な大きさを再度求めます。
 指定した大きさと選択されたフォントの高さが同じとは限らないので横幅が大きい場合は再度フォントの高さを修正します。
 TextOut APIにより先ほどのSIZE構造体に格納されている描画範囲からクライアント領域の中央に文字を表示します。
 描画が終了したら、SelectObject APIでフォントを元に戻し作成したフォントをDeleteObject APIにより削除します
 ペンとブラシを取得し引数RECTで示される大きさの四角形をRectangle APIで描画します。

プログラムソース

pagesetup.cpp

//	ページセットアップコモンダイアログ(PageSetupDLG)の表示
//	Visual C++ 2013 32/64bit

#include <windows.h>
#include <tchar.h>
#include <math.h>
#include "resource.h"

//	ウィンドウプロシージャー
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
// サンプルページの表示
UINT CALLBACK PagePaintHook(HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam);
//	RECTの範囲に巨大文字を描画
void bigFontPaint(HDC hdc, RECT* r);

TCHAR szClassName[] = TEXT("PaseSetup");

HINSTANCE hInst;

int WINAPI _tWinMain(HINSTANCE hInstance, HINSTANCE hPreInst,TCHAR* lpszCmdLine, int nCmdShow){
	HWND hWnd;
	MSG lpMsg;
	WNDCLASS myProg;
	hInst = hInstance;

	if (!hPreInst) {
		myProg.style =CS_HREDRAW | CS_VREDRAW;
		myProg.lpfnWndProc =WndProc;
		myProg.cbClsExtra =0;
		myProg.cbWndExtra =0;
		myProg.hInstance =hInstance;
		myProg.hIcon =NULL;
		myProg.hCursor =LoadCursor(NULL, IDC_ARROW);
		myProg.hbrBackground =(HBRUSH)GetStockObject(WHITE_BRUSH);
		myProg.lpszMenuName = _TEXT("IDM_MENU");
		myProg.lpszClassName =szClassName;
		if (!RegisterClass(&myProg))
			return FALSE;
	}
	hWnd = CreateWindow(szClassName,
		szClassName,
		WS_OVERLAPPEDWINDOW,
		CW_USEDEFAULT,
		CW_USEDEFAULT,
		200,
		150,
		NULL,
		NULL,
		hInstance,
		NULL);
	ShowWindow(hWnd, nCmdShow);
	UpdateWindow(hWnd);
	while (GetMessage(&lpMsg, NULL, 0, 0)) {
		TranslateMessage(&lpMsg);
		DispatchMessage(&lpMsg);
	}
	return int(lpMsg.wParam);
}

TCHAR pName[MAX_PATH];//現在選択されているプリンタ名

//	ウィンドウプロシージャー

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam){
	HDC hdc;
	PAINTSTRUCT ps;
	static int nowDPIx = 96;
	static int nowDPIy = 96;
	static UINT nowScale = 100;
	static PAGESETUPDLG psd;

	switch (msg) {
		case WM_CREATE:{
			ZeroMemory(&psd, sizeof(psd));
			psd.lStructSize = sizeof(psd);
			psd.hwndOwner = hWnd;
			psd.Flags = PSD_INHUNDREDTHSOFMILLIMETERS | PSD_ENABLEPAGEPAINTHOOK;//ミリ単位に設定
			psd.lpfnPagePaintHook = PagePaintHook;
			DWORD size = sizeof(pName) / sizeof(pName[0]);
			GetDefaultPrinter(pName, &size);
			break;
		}
		case WM_COMMAND:
			switch (LOWORD(wParam)) {
				case IDM_QUIT:
					SendMessage(hWnd, WM_CLOSE, 0, 0L);
					break;
				case IDM_PAGESET:{
						DEVMODE* pDev = 0;
						PRINTER_DEFAULTS pdef;
						HANDLE hPrn;
						DWORD dwNeeded,dwSize;
						HGLOBAL hMem;
						if (PageSetupDlg(&psd)){	//	ページ設定ダイアログを表示
							pDev = (DEVMODE*)GlobalLock(psd.hDevMode);
							if (pDev == NULL)
								return FALSE;
							_tcscpy_s(pName, sizeof(pName) / sizeof(pName[0]), pDev->dmDeviceName);
							ZeroMemory(&pdef, sizeof(pdef));
							pdef.DesiredAccess = PRINTER_ACCESS_USE;
							if (OpenPrinter(pName, &hPrn, &pdef) == 0){
								break;
							}
							GetPrinter(hPrn, 2, 0, 0, &dwNeeded);
							dwSize = dwNeeded;
							hMem = GlobalAlloc(GHND, dwSize);
							if (hMem == NULL){
								ClosePrinter(hPrn);
								break;
							}
							PRINTER_INFO_2* prnInfo=(PRINTER_INFO_2 *)GlobalLock(hMem);
							if (prnInfo == NULL){
								GlobalFree(hMem);
								ClosePrinter(hPrn);
								break;
							}
							if (GetPrinter(hPrn, 2, (LPBYTE)prnInfo, dwSize, &dwNeeded)==0){
								GlobalUnlock(hMem);
								GlobalFree(hMem);
								ClosePrinter(hPrn);
								break;
							}
							prnInfo->pDevMode->dmOrientation = pDev->dmOrientation;//用紙の向き
							prnInfo->pDevMode->dmPaperSize = pDev->dmPaperSize;//用紙サイズ
							prnInfo->pDevMode->dmPaperLength = pDev->dmPaperLength;//用紙縦長さ0.01ミリ単位
							prnInfo->pDevMode->dmPaperWidth = pDev->dmPaperWidth;//用紙横長さ0.01ミリ単位
							prnInfo->pDevMode->dmDefaultSource = pDev->dmDefaultSource;//予約済み0
							prnInfo->pDevMode->dmPrintQuality = pDev->dmPrintQuality;//プリンタ解像度
							prnInfo->pDevMode->dmColor = pDev->dmColor;//カラー / モノクロ
							if (SetPrinter(hPrn, 2, (LPBYTE)prnInfo, 0) == 0)
								break;
							GlobalUnlock(hMem);
							GlobalFree(hMem);
							GlobalUnlock(psd.hDevMode);
							GlobalFree(psd.hDevMode);
							ClosePrinter(hPrn);
						}
					}
					break;
				case IDM_PRINT:{
					PRINTDLG pd;
					DOCINFO di;
					memset(&pd, 0, sizeof(PRINTDLG));
					pd.lStructSize = sizeof(PRINTDLG);
					pd.hwndOwner = NULL;
					pd.hDevMode = NULL;
					pd.hDevNames = NULL;
					// 	pd.Flags = デバイスコンテキストを返す 「選択した部分」ラジオボタンを使用不可にします 「ファイルへ出力」チェックボックスを隠します。
					pd.Flags = PD_RETURNDC |
						PD_NOPAGENUMS | PD_NOSELECTION | PD_HIDEPRINTTOFILE;
					pd.nCopies = 1;		//	印刷部数1
					pd.nFromPage = 1;	//	スタートページ1
					pd.nToPage = 1;		//	最後のページ1
					pd.nMinPage = 1;	//	ページ範囲の最小値1
					pd.nMaxPage = 1;	//	ページエディットコントロールの最大値1
					memset(&di, 0, sizeof(DOCINFO));
					di.cbSize = sizeof(DOCINFO);
					di.lpszDocName = szClassName;//ドキュメント名
					if (PrintDlg(&pd) == TRUE) {	//	ダイアログ表示
						StartDoc(pd.hDC, &di);
						StartPage(pd.hDC);
						RECT rect;
						DWORD offsetX1 = GetDeviceCaps(pd.hDC, PHYSICALOFFSETX); //印刷可能領域X
						DWORD offsetY1 = GetDeviceCaps(pd.hDC, PHYSICALOFFSETY); //印刷可能領域Y
						DWORD DPIx = GetDeviceCaps(pd.hDC, LOGPIXELSX);
						DWORD DPIy = GetDeviceCaps(pd.hDC, LOGPIXELSY);
						DWORD prnWidth = GetDeviceCaps(pd.hDC, HORZRES);	//	印刷可能範囲横幅
						DWORD prnHeight = GetDeviceCaps(pd.hDC, VERTRES);	//	印刷可能範囲縦幅
						DWORD paperWidth = GetDeviceCaps(pd.hDC, PHYSICALWIDTH);//	用紙横幅
						DWORD paperHeight = GetDeviceCaps(pd.hDC, PHYSICALHEIGHT);//	用紙縦幅
						DWORD offsetX2 = paperWidth - prnWidth - offsetX1;	// 右印刷不可能幅
						DWORD offsetT2 = paperHeight - prnHeight - offsetY1;	// 下印刷不可能幅
						rect.left=psd.rtMargin.left* DPIx / 2540-offsetX1;
						rect.top =psd.rtMargin.top* DPIy / 2540-offsetY1;
						rect.right =paperWidth- psd.rtMargin.right*DPIx / 2540-offsetX1;
						rect.bottom =paperHeight - psd.rtMargin.bottom*DPIy / 2540-offsetY1;
						bigFontPaint(pd.hDC, &rect);
						EndPage(pd.hDC);
						EndDoc(pd.hDC);
						DeleteDC(pd.hDC);
					}
				}
			}
			break;
		case WM_CLOSE:
			DestroyWindow(hWnd);
			break;
		case WM_DESTROY:
			PostQuitMessage(0);
			break;
		case WM_PAINT:{
			hdc = BeginPaint(hWnd, &ps);
			RECT rect;
			GetClientRect(hWnd, &rect);
			bigFontPaint(hdc, &rect);
			EndPaint(hWnd, &ps);
			break;
		}
		default:
			return(DefWindowProc(hWnd, msg, wParam, lParam));
	}
	return (0L);
}

// サンプルページの表示
UINT CALLBACK PagePaintHook(HWND hdlg, UINT uiMsg, WPARAM wParam, LPARAM lParam){
	static HDC hdc = 0;
	static RECT* rect;
	static PAGESETUPDLG* psdp = 0;
	switch (uiMsg){
	case WM_PSD_GREEKTEXTRECT:
		hdc = (HDC)wParam;
		rect = (RECT*)lParam; // 印刷部分
		if (hdc && rect){
			bigFontPaint(hdc, rect);
			return TRUE;
		}
		break;
	}
	return FALSE;
}

//	RECTの範囲に巨大文字を描画
void bigFontPaint(HDC hdc, RECT* r){
	RECT& rect = *r;
	double scr = double(rect.right - rect.left) / double(rect.bottom - rect.top);
	int h = rect.bottom;
	TCHAR* str = _TEXT("MS明朝");
	TCHAR* fontName = _TEXT("MS 明朝");
	HFONT hFont = CreateFont(
		h, 0, 0, 0, 0, FALSE, FALSE, FALSE,
		SHIFTJIS_CHARSET, OUT_DEFAULT_PRECIS,
		CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
		VARIABLE_PITCH, fontName
		);
	SIZE size;
	HFONT hOldFont = (HFONT)SelectObject(hdc, hFont);
	GetTextExtentPoint32(hdc, str, _tcslen(str), &size);
	double font = double(size.cx) / double(size.cy);
	if (scr < font){	//	横がはみ出ている
		do{
			h = h*(rect.right - rect.left) / size.cx;
			SelectObject(hdc, hOldFont);
			DeleteObject(hFont);
			hFont = CreateFont(
				h, 0, 0, 0, 0, FALSE, FALSE, FALSE,
				SHIFTJIS_CHARSET, OUT_DEFAULT_PRECIS,
				CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY,
				VARIABLE_PITCH, fontName
				);
			SelectObject(hdc, hFont);
			GetTextExtentPoint32(hdc, str, _tcslen(str), &size);
		} while ((rect.right - rect.left) < size.cx);
	}
	TextOut(hdc, ((rect.right - rect.left) - size.cx) / 2 + rect.left, ((rect.bottom - rect.top) - size.cy) / 2 + rect.top, str, (int)_tcslen(str));
	SelectObject(hdc, hOldFont);
	DeleteObject(hFont);
	HBRUSH hBrush = (HBRUSH)GetStockObject(NULL_BRUSH);
	HBRUSH hOldBrush = (HBRUSH)SelectObject(hdc, hBrush);
	HPEN hPen = (HPEN)GetStockObject(BLACK_PEN);
	HPEN hOldPen = (HPEN)SelectObject(hdc, hPen);
	Rectangle(hdc, r->left, r->top, r->right, r->bottom);
	SelectObject(hdc, hOldBrush);
	SelectObject(hdc, hOldPen);
}

resource.h

#define IDM_PAGESET	1010
#define IDM_PRINT	1020
#define IDM_QUIT	1030


resource.rc

#include <windows.h>
#include "resource.h"

IDM_MENU MENU DISCARDABLE 
BEGIN
	POPUP "ファイル(&F)"
	BEGIN
		MENUITEM "ページ設定(&U)",IDM_PAGESET
		MENUITEM "印刷(&P)", IDM_PRINT
		MENUITEM "終了(&X)", IDM_QUIT
	END
END

ソースファイルと実行ファイルのダウンロード

ダウンロード pagesetup.zip(40.9kByte)
ZIPファイルに含まれるファイル
pagesetup.cpp
pagesetup.exe
resource.h
resource.rc