複数のページを印字する
- ◆プログラム作成ガイド
- ◆プログラムの開始
- ◆初期表示のデザイン
- ◆データの入力方法
- ◆クラスの作り方
- ◆データをメモリーに
- ◆ファイル読み書き
- ◆データの保存方法
- ◆ツリービューマウス右
- ◆画面表示インプリメント
- ◆複数のページ印字
- ◆DLL化する
- ◆エクセル似の入力
ここに紹介するプログラムは、WIN XP 2003/Windows 7 (32bits) - OS 環境でマイクロソフトのVisual Studio 2010 で作ったものです。
Windows XP/ Windows 7(32bits) での動作は確認済みですが…
複数ページの印刷をインプリメントするのはなかなか難解な部分があります。
AutoAC ver.3.2 までは、複数ページ対応していないため2ページ以降が印刷されませんでした。
MFCのオンラインヘルプを見ると詳しく載っているけど、実際にどう作れば良いのか....
複数ページの印刷をインプリメントするのは、なかなか難解な部分があります。
まずは仕様を決めるのが先です。本プログラムは、各ページが同じフォーマットではありません。 プログラムで必要フォーマットを判断し連続印刷をすることになります。
印刷ページの仕様
-
負荷計算(簡易)は、常に定型の1ページ印刷とした。
-
負荷計算(詳細)は、1ページ目を常に定型とした。 2ページ目から壁データ、連続して窓データを任意の長さに印刷する。
-
空調機選定は、常に定型の1ページ印刷とした。
-
総合負荷計算は、1ページ目は定型とし 2ページ目以降はシステムの運転時間がデータ量に応じて自動的にページが増えるようにした。
-
簡易負荷計算は、データ量より必要な連続ページ印刷とした。
常に1ページの印刷であればプログラムは簡単ですが、データ量から任意にページを増やしていく仕様をプログラムにするのは相当頭を痛めました。 MFCのプログラムヘルプで、複数ページの印刷を扱った説明がありますが、肝心なところは詳しく説明がなく、具体的にどのようにすればよいのか分かりません。
MFCのヘルプの内容を整理してみましょう。
印刷が開始されると、フレームワークが
-
OnPreparPrinting(ページが決定できるとき、していします)
-
OnBeginPrinting
-
OnPreparDC(デバイスコンテキストの設定や任意のページで印刷終了条件設定など)
-
OnPrint(印刷・プレビューの操作関数を設定)
これらの 関数を呼び出しますが、MFC内部であるため実際の動きが見えにくいため混乱させられます。
印刷開始すると、ページの設定などが出来る共通のダイアログが表示されますがこれはOnPreparPrintingのなかで呼ばれます。
ここで印刷ページを指定すると印刷が完了するまで印刷ループの階数(ページ数)が再指定できません。しかし、この時点でページが指定できないケースが多々あります。
例えば、印刷する用紙とデータの量よりページを計算できないことです。
プログラム固有の設定をしたいときは、上の3つの関数をオーバーライドしてその関数内に指定すればよいのですが、
ここで扱う CDC *pDC(デバイスコンテキスト)と CPrintInfo *pInfo(印刷制御の構造体)が画面表示に使うときと印刷・更にプレビューで自動的に変化することです。この変化が理解できれば全体像が見えてくるのです。
MFCのヘルプだけを読んでも全部理解出来ませんでした。試行錯誤しながらプログラムを通して理解するのが結局早道みたいです。
OnPrint関数内部にOnDrawを書くと、画面と全く同じ印刷が出来ますが、ページコントロールが出来ません。
OnPrintは印刷専用関数と割り切り、印刷するフォーマットを別関数でつくりこれをOnPrint関数内に書くのが良さそうです。MFCヘルプには、以下のような表現がありますが。。。。
印刷開始前にページが不明な時の処理や、各ページの区間を決めるのに、デバイスコンテキストの「ビュー原点を移動し、クリッピングで印刷範囲を移動する」などの説明がありますが、プレビュー機能を合わせて使う場合、プレビューDCと印刷DCを操作しなければならず、相当厄介なプログラムになりそうで、この方法はあきらめました。
以前Pipeプログラムを作った時、この様な任意のページを印刷するため、印刷する各行の内容を計算時点でアレーに保存し、印刷時点にそれを呼び出して各ページのアレンジをしながらページコントロールしました。 今回のプログラムは更に扱うデータの種類が多く同じ方法は使いたくないため、少し違う方法としました。
基本的なプログラムの考え
-
画面の連続表示情報を行毎に専用のアレーに保存します。
-
印刷開始でOnPrint関数が呼ばれたら、印刷用紙の高さと行より1ページに印刷できる最大行を計算します。
-
データの量から、印刷ページの数を計算します。
-
< class="auto-style18"> ページをパラメータにし、データの最後までアレーを読みながら印刷処理する関数を呼び出します。
-
データの最後を判断し、印刷を停止します。
具体的には、以下のようなコードを書きました。もっと高度なテクニックがあると思いますが私の知識ではここまでです。
1ページの印刷最大行とページの計算
自作関数を作りました。
#include "math.h"
////////////////////////////////////////
// ヘッダ・フッタを除く印字範囲による総ページの計算
int CAutoACView::UpdatePage(CPrintInfo* pInfo, int nHeight, int
nLines_Detail)
{
// 1ページの印字可能最大行の取得
int MaxRow = abs( pInfo->m_rectDraw.Height() / nHeight ) - 4; // タイトル行を考慮し-4とした
int p = (int)ceil( (double)nLines_Detail/(double)MaxRow );
// 壁・窓の負荷がともにない時、強制的にページを1とし負荷がないことのメッセージを出す。
if( p == 0 ) p = 1;
m_nMaxRow = MaxRow; // 1ページに書ける最大行数
return p+1; // 表紙ページを追加
}
このプログラムは、2種類のフォントが使えるようにして有りますのが、フォントの種類より改行幅 (nHeight) を計算します。
/////////////////////////////////////////// フォントポイントの選択
void CAutoACView::SetFontPointNumber(int nFont)
{ // C_POINT -20
m_nFont = nFont; m_nHeight = abs( nFont * ( C_POINT - 5 ));
m_nWidth = abs( nFont * C_POINT );
}
画面表示のデータをアレーに保存
専用にクラスを作りました。
//////////////////////////////////////////////
Print Format Classclass CPrintFormat{public:
CPrintFormat() {
m_nType = 0; //壁データ・窓データなどの区別
m_nIndex = 0; // ファイル内の番号取得
m_nData = 0; // 印刷する行数値
m_nLast = 0; // 最後の行番号
}; CPrintFormat( const CPrintFormat& s ){*this = s;};
virtual ~CPrintFormat() {};
const CPrintFormat& operator = ( const CPrintFormat& s ){
m_nType = s.m_nType;
m_nIndex = s.m_nIndex;
m_nData = s.m_nData;
m_nLast = s.m_nLast;
return *this; };
int m_nType;
int m_nIndex;
int m_nData;
int m_nLast;};
このクラスを使ったアレーをAutoACViewクラスに作りました。
private: CArray<CPrintFormat, CPrintFormat> m_ArrayPrint;
後は、画面表示の関数(OnDraw)にアレー挿入のコードを書きます。
if( pDoc->m_bPrintDetail == TRUE ) { // 詳細版の表示
if( pDoc->m_Result.GetSize( ) > 0 ){
m_ArrayPrint.RemoveAll( ); // 印刷フォーマットのリセット
int n = pDoc->m_ArrayAircon[m_nIndex].m_ArrayWall.GetSize();
for( int i=1; n>=i; i++ ){
lp = Draw_Wall_Detail( pDC, &lp, m_nIndex, i, n );
pFt.m_nType = 1; //1番を壁よりの負荷計算とした
pFt.m_nIndex = m_nIndex;
pFt.m_nData = i;
pFt.m_nLast = n;
m_ArrayPrint.Add( pFt ); // 印刷フォーマット作成
}
n = pDoc->m_ArrayAircon[m_nIndex].m_ArrayGlass.GetSize( );
for( i=1; n>=i; i++){
lp = Draw_Glass_Detail( pDC, &lp, m_nIndex, i, n );
pFt.m_nType = 2;
pFt.m_nIndex = m_nIndex;
pFt.m_nData = i;
pFt.m_nLast = n;
m_ArrayPrint.Add( pFt ); // 印刷フォーマット作成
}
}
}
以降省略
壁や窓の負荷がない場合のコードは省略しています。
lp = Draw_Wall_Detail( pDC, &lp, m_nIndex, i, n ); と lp = Draw_Glass_Detail( pDC, &lp, m_nIndex, i, n ); は pDCデバイスコンテキストを使って &lpポイントにファイル内番号のデータを計算し画面に表示する関数です。
更に、データ番号が 1 の時はタイトルを, 最後の番号の時は印刷終了判断するようにしてあります。そのためnの引数が必要になりました。 複数ページがあるとき、印刷行数がページに対してどの程度なのかを判断しなければなりません。この判断は邪道のような方法ですが以下としました。
///////////////////////////////////////////////////
// ページの印刷コントロール
PAGE_POS CAutoACView::PageControl(int nPage, int nLine_Page, int n)
{
PAGE_POS pg;
pg.start = 0;
pg.end = 0;
int start = nLine_Page * ( nPage - 2 ) + 1;
int end = nLine_Page * ( nPage - 1 );
nbsp; for( int i=start; end>i; i++ ){
if( i >= n ) break;
}
pg.start = start;
pg.end = i;
return pg;
}
この行情報を使い印刷関数を操作します。
以下のコードをOnPrint の中にコーディングすれば自動改行・改ページで指定の項目を印刷できます。
//////////////////////////////////////
// 印字ルーチン
void CAutoACView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
CAutoACDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
UINT nIndex = m_nIndex;
int nPage = pInfo->m_nCurPage;// 現在の印刷ページ
int nHeight = m_nHeight; // 文字の改行高さ
// 印刷余白を設定
pInfo->m_rectDraw.top -= T_MARGIN; //
Topの余白を設定する
pInfo->m_rectDraw.left += L_MARGIN; //
左の余白を設定する
// ヘッダーの印字
pInfo->m_rectDraw = PrintPageHeaderFooter( pDC, pInfo, nHeight );
CPoint point = pInfo->m_rectDraw.TopLeft(); // プリンタの印字開始座標
// 詳細データ印刷の時のページなどを取得
if( pDoc->m_bPrintDetail == TRUE ){
m_nMaxPage = UpdatePage( pInfo, nHeight, m_nLines_Detail ); // データより印字ページ算出
if( pDoc->m_bSmpPrint == TRUE )
m_nMaxPage = m_nMaxPage - 1; // 簡易負荷計算は表紙ページが不要
} else{ // 詳細が設定されてないページの設定
m_nMaxPage = 1;
}
pInfo->SetMaxPage( m_nMaxPage ); // ページ数を設定
// ----------- 表紙およびデータの印字
if( pDoc->m_bLdPrint == TRUE ) { // 負荷計算の印字
if( nPage > 1 && pDoc->m_bPrintDetail == TRUE )
Print_Detail(pDC, &point, nPage );// 詳細部分の印字
else
Draw_Main( pDC, &point, nIndex ); // 簡易版の印字
}
// ------------ 総合負荷計算の印字
if( pDoc->m_bDyPrint == TRUE ){
if( nPage > 1 && pDoc->m_bPrintDetail == TRUE )
Print_Detail(pDC, &point, nPage);// 詳細部分の印字
else
Draw_Main_Dy(pDC, &point);
}
// ------------ 空調機選定の印字
if( pDoc->m_bEqPrint == TRUE )
Draw_Main_Eq(pDC, &point);
// ------------ 詳細部分の印字
if( pDoc->m_bSmpPrint == TRUE )
// 詳細部分(2ページ以降)の印刷ルーチン
void CAutoACView::Print_Detail(CDC *pDC, CPoint *point, int nPage)
{
PAGE_POS pg;
CPoint lp = *point;
int type = 0; // 印刷の種類
int nIndex = 0; // ファイル内の番号
int nLine = 0; // 行番号
int nLast = 0; // 最後行番号
int size = m_ArrayPrint.GetSize();
pg = PageControl( nPage, m_nMaxRow, size );
for( int n=pg.start; pg.end>=n; n++ ){
if( m_ArrayPrint.GetSize() > 0 ){
type =
m_ArrayPrint[n-1].m_nType;
nIndex =
m_ArrayPrint[n-1].m_nIndex;
nLine =
m_ArrayPrint[n-1].m_nData;
nLast =
m_ArrayPrint[n-1].m_nLast;
}
switch( type ){
case 1:
if( nLast == 0 )
lp =
Draw_NoWall_Detail( pDC, &lp );
画面出力と同じ関数を使います。
else
lp =
Draw_Wall_Detail( pDC, &lp, nIndex, nLine, nLast );画面出力と同じ関数を使います。
break;
case 2:
if( nLast == 0 )
lp =
Draw_NoGlass_Detail( pDC, &lp );
画面出力と同じ関数を使います。
else
lp =
Draw_Glass_Detail( pDC, &lp, nIndex, nLine, nLast );画面出力と同じ関数を使います。
break;
case 3:
lp = Draw_DyCondition( pDC, &lp,
n );画面出力と同じ関数を使います。
break;
case 4:
lp = Draw_Main_Simple( pDC, &lp,
n, nLast );画面出力と同じ関数を使います。
break;
}
}
}
その他、ページヘッダーやフッターのプログラムも必要ですが、MFC のヘルプに詳しい説明がありますし、チュートリアルなどを見ればサンプルコードがありますのでここでは省略します。
プログラム上の注意点
印刷コマンドを呼ぶとまずOnPreparePrinting(CPrintInfo* pInfo)関数がフレームワークから呼ばれますが、この時点ではページが判断できません。
pInfo のメンバであるSetMaxPage( )をセットする時はAutoACViewクラスの自作メンバの m_nMaxPageを指定するとプレビューを行えば自動的にデータの量からページを計算して動作します。
もしここで定数の1などを指定してしまうとその後でpInfo->SetMaxPage( )を指定しなおしても1ページしか印刷できなくなってしまいます。
しかしながら、プレビューをしないとやはり1ページしか印刷されません。これを解決するには、印刷行をプログラムでモニターし、OnPreparDC関数で印刷最終行を判断し、CPrintInfoのメンバ変数の m_bContinuePrinting=FALSEとしなければなりません。
しかし、この場合印刷ダイアログにページが表示されません。難しいものです、何か良い方法はないものでしょうか。
OnBeginPrinting(CDC* pDC, CPrintInfo* pInfo)関数に追加コーディングは不要です。
OnEndPrinting(CDC* /*pDC*/, CPrintInfo* /*pInfo*/)関数にもコーデでィング不要です。
ツールバーやメニューに印刷などのボタン追加
ツールバーのプレビューボタンを追加する場合、常にアクティブな表示にするためには、CMainFrame クラスにインプリメントする必要があります。更にCScrollView::OnFilePrintPreview(); を呼ぶように設定しないと左のツリービューを選定しているときは空のビューを表示してしまいます。
まず、CAutoACViewクラスにpublic で以下のような関数を作成します。
/////////////////////////////////////////
// ファイルのプレビュー
void CAutoACView::Preview_Func()
{
CScrollView::OnFilePrintPreview();
}
ツールバーにボタンのアイコンを追加し、ID_FILE_PRINT2と指定してください。
更にCMainFrameにボタンに対応したインプリメントをしてください。
////////////////////////////////////////////////////////
// ツールバーおよびメニューのプレビュー
void CMainFrame::OnFilePrintPreview2()
{
Active_RightView(); // 強制的に右のビューをアクティブにする。
CWnd* pWnd = m_wndSplitter.GetPane(0, 1);
CAutoACView* pView = DYNAMIC_DOWNCAST(CAutoACView, pWnd);
pView->Preview_Func(); // AutoACビュークラスのCScrollView::OnFilePrintPreview()を呼ぶ
}
メニューのプレビューもIDをツールバーのIDと同じものに変更してください、MFCが標準で作成するコードはフレームビューのOnFilePrintPreview( )を呼ぶので、分割ビューに対応しないためです。
ツールバーおよびメニューの印刷ボタンも同様にしないとアクティブなビューが左を選択しているとき、何も印刷されません。
このプログラムの欠点
ビュークラスのメンバに大量のアレーや変数を作るのは面倒なことと、ページ数も多くて3~4ページ位だろうと思い、 表示コード内で計算しながら表示するコードとしたため、表示速度が相当遅くなっています。
特に負荷計算では、計算時点と、表示時点で相当量の計算を繰り返し行っていますので、目に見えて速度が遅くなってしまいました。
やはり、退避メモリーに計算結果を保存し、表示の時に呼び出して利用した方がよさそうです。
これで、大体の考えがわかると思いますが、姑息な手段をあちらこちらに使い実際のプログラムには相当
苦労させられました。
ページ先頭に戻る
最終更新日: 2020/02/04