複数のページを印字する

  

ここに紹介するプログラムは、WIN XP 2003/Windows 7 (32bits) - OS 環境でマイクロソフトのVisual Studio 2010 で作ったものです。

Windows XP/ Windows 7(32bits) での動作は確認済みですが…

 

複数ページの印刷をインプリメントするのはなかなか難解な部分があります。
AutoAC ver.3.2 までは、複数ページ対応していないため2ページ以降が印刷されませんでした。
MFCのオンラインヘルプを見ると詳しく載っているけど、実際にどう作れば良いのか....


複数ページの印刷をインプリメントするのは、なかなか難解な部分があります。

まずは仕様を決めるのが先です。本プログラムは、各ページが同じフォーマットではありません。 プログラムで必要フォーマットを判断し連続印刷をすることになります。

 

印刷ページの仕様

常に1ページの印刷であればプログラムは簡単ですが、データ量から任意にページを増やしていく仕様をプログラムにするのは相当頭を痛めました。 MFCのプログラムヘルプで、複数ページの印刷を扱った説明がありますが、肝心なところは詳しく説明がなく、具体的にどのようにすればよいのか分かりません。

 

MFCのヘルプの内容を整理してみましょう。

印刷が開始されると、フレームワークが

これらの 関数を呼び出しますが、MFC内部であるため実際の動きが見えにくいため混乱させられます。

 

印刷開始すると、ページの設定などが出来る共通のダイアログが表示されますがこれはOnPreparPrintingのなかで呼ばれます。

ここで印刷ページを指定すると印刷が完了するまで印刷ループの階数(ページ数)が再指定できません。しかし、この時点でページが指定できないケースが多々あります。

 

例えば、印刷する用紙とデータの量よりページを計算できないことです。

プログラム固有の設定をしたいときは、上の3つの関数をオーバーライドしてその関数内に指定すればよいのですが、

 

ここで扱う   CDC *pDC(デバイスコンテキスト)と CPrintInfo *pInfo(印刷制御の構造体)が画面表示に使うときと印刷・更にプレビューで自動的に変化することです。この変化が理解できれば全体像が見えてくるのです。

 

MFCのヘルプだけを読んでも全部理解出来ませんでした。試行錯誤しながらプログラムを通して理解するのが結局早道みたいです。

OnPrint関数内部にOnDrawを書くと、画面と全く同じ印刷が出来ますが、ページコントロールが出来ません。

OnPrintは印刷専用関数と割り切り、印刷するフォーマットを別関数でつくりこれをOnPrint関数内に書くのが良さそうです。MFCヘルプには、以下のような表現がありますが。。。。

 

印刷開始前にページが不明な時の処理や、各ページの区間を決めるのに、デバイスコンテキストの「ビュー原点を移動し、クリッピングで印刷範囲を移動する」などの説明がありますが、プレビュー機能を合わせて使う場合、プレビューDCと印刷DCを操作しなければならず、相当厄介なプログラムになりそうで、この方法はあきらめました。

 

以前Pipeプログラムを作った時、この様な任意のページを印刷するため、印刷する各行の内容を計算時点でアレーに保存し、印刷時点にそれを呼び出して各ページのアレンジをしながらページコントロールしました。 今回のプログラムは更に扱うデータの種類が多く同じ方法は使いたくないため、少し違う方法としました。

 

基本的なプログラムの考え

具体的には、以下のようなコードを書きました。もっと高度なテクニックがあると思いますが私の知識ではここまでです。

 

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 )

        Print_Detail(pDC, &point, nPage+1); //////////////////////////////////////
// 詳細部分(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)関数がフレームワークから呼ばれますが、この時点ではページが判断できません。

 

Info のメンバである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