OnPrepareDC() の実行回数

[戻る]
テッパン 2015/02/11(水) 11:32:17
VS2010, MFC, SDI で印刷アプリを作っています。

複数ページの印刷で、何ページから何ページまで印刷したかを記録する必要があり、印刷
したページ番号を、配列 m_nPrintedPage[] に格納してみました。

CMyProjView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
    if (pDC->IsPrinting())
    {
        m_nPrintedPage[m_nPPAray] = pInfo->m_nCurPage;
        m_nPPAray++;        // 印刷開始前に m_nPPAray = 0; としています。
        ...(印刷のためのコード)...
    }
}

結果を OnEndPrinting() 関数内で調べたところ、同じページ番号が2回ずつ格納されて
いました。
例:(3〜5ページを印刷した場合) m_nPrintedPage[] = {3, 3, 4, 4, 5, 5, 0, 0...}

この動作は正しいでしょうか?
OnPrepareDC() はページ毎に2回ずつ実行されると考えていいでしょうか?
(印刷は正常に行われています。)

解説を見てもそのようなことは書かれていません。
例:マルチページドキュメント
https://msdn.microsoft.com/ja-jp/library/vstudio/w7wzay73(v=vs.110).aspx

ご存じの方がいらっしゃいましたらご教授ください。
よろしくお願いいたします。

ITO 2015/02/12(木) 13:02:35
うーーん

ここに書いてあります。
https://msdn.microsoft.com/ja-jp/library/sk4fs3es(v=vs.100).aspx

テッパン 2015/02/12(木) 16:49:28
ITO 様、有り難うございます。

教えて戴いたページは、CPrintInfo::m_nCurPage の説明です。
「フレームワークは、ドキュメントのページごとにこのメンバーに異なる値を指定して、
CView::OnPrepareDC と CView::OnPrint を呼び出します。」と書いてあります。

これが、『フレームワークは・・・ CView::OnPrepareDC を2度呼び出す』と書いてあれ
ば納得なのですが・・・

ITO 2015/02/12(木) 17:08:57
>CMyProjView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
失礼しました。
ここで実際に処理していたのですね。
>OnPrepareDC() はページ毎に2回ずつ実行されると考えていいでしょうか?
うーーん、
たまたま2回だったということはないでしょうか?

https://technet.microsoft.com/en-us/lync/aa248127(v=vs.60)
ここでは、「OnPrint()」で処理するように書いてありますね。

テッパン 2015/02/13(金) 06:43:41
OnPaint() の中で、印刷したページ番号を格納しました。

CMyProjView::OnPrepareDC(CDC* pDC, CPrintInfo* pInfo)
{
    if (pDC->IsPrinting())
    {
        m_nPrintedPage[m_nPPAray] = pInfo->m_nCurPage;
        m_nPPAray++;        // 印刷開始前に m_nPPAray = 0; としています。
        ...(印刷のためのコード)...
    }
}

結果:(3〜5ページを印刷した場合) m_nPrintedPage[] = {3, 4, 5, 0, 0, 0, 0, 0...}

これで解決です。
ありがとうございました。

K 2015/02/13(金) 07:40:12
解決したようなので今さらですが。

VS2008 ですが MFC のソースを見てみると
viewprnt.cpp の CView::OnFilePrint() で OnPrepareDC() を
ページごとに 2 回呼び出しているように読めます。

テッパン 2015/02/13(金) 08:47:26
K様、有り難うございます。
やはりそうですか。
OnPrepareDC() が2回呼び出されるのは異常ではないと、安心しました。

OnPrint() の中で、印刷したページ番号を取得できたとして解決としましたが、その後、
例えば3ページ印刷しようとして用紙が1枚しかセットされておらず、印刷が中断した場
合でも、OnPaint() は3ページ分動作してしまうことが判りました。
これでは目的に合わないので、最初から考え直すことにしました。

ITO 2015/02/13(金) 17:31:44
> viewprnt.cpp の CView::OnFilePrint() で OnPrepareDC() を
> ページごとに 2 回呼び出しているように読めます。
なるほど、フォロー助かります。

>例えば3ページ印刷しようとして用紙が1枚しかセットされておらず、印刷が中断した
>場合でも、OnPaint() は3ページ分動作してしまうことが判りました。

おそらくもうテッパンさんの 印刷処理は終了しています。

https://msdn.microsoft.com/ja-jp/library/cc428280.aspx
ここの、ジョブ/スプールの処理になっていると思います。
http://support.microsoft.com/kb/160129/JA
これもかな?

キャンセルされたかどうかを調べるのは難しいと思います。

テッパン 2015/02/14(土) 08:30:44
教えて戴いた印刷ジョブ/スプールを理解しようと努力しましたが、プリンタ側で何ペー
ジまで印刷できて何ページから印刷できていないかの情報を取得するのは至難の業と、諦
めることにしました。
皆様、ありがとうございました。

ITO 2015/02/14(土) 13:06:11
うーーん、
プリンタ側は、何ページからは関係ないですね。
そこは、
>結果:(3〜5ページを印刷した場合) m_nPrintedPage[] = {3, 4, 5, 0, 0, 0, 0, 0...}
この値を信用するしかないと思います。
あとは、実際に印刷されたページ数(途中キャンセルの有無)をチェックすればいいと思
いますが、どうでしょうか?

テッパン 2015/02/15(日) 11:48:49
あきらめるな!、と励まして戴いて有り難うございます。
実際に印刷されたページ数を取得しようと、コードを一応書いてみたのですが、始めのと
ころでつまづいています。

CMyProjView::EndPrinting(CDC* /*pDC*/, CPrintInfo* pInfo)
{
    DWORD cByteNeeded;
    PRINTER_INFO_2 *pPrinterInfo = NULL;
    GetPrinter(pInfo->m_pPD, 2, NULL, 0, &cByteNeeded);  ←この行でエラー
    pPrinterInfo = (PRINTER_INFO_2 *)malloc(cByteNeeded);
    ・・・以下、GetPrinter()、EnumJobs() まで
}

pInfo->m_pPD がいけないかと、OpenPrinter() でハンドルを取得してみても同じです。
    HANDLE hPrinter;
    OpenPrinter(_T("EPSON EP-301"), &hPrinter, NULL); ←この関数は成功
    GetPrinter(hPrinter, 2, NULL, 0, &cByteNeeded);  ←この行でエラー

コードを OnPrint() の中に書いても、GetPrinter() がエラーは同じです。
お気づきのことがありましたらご教授ください。

テッパン 2015/02/15(日) 14:22:19
GetPrinter() のエラーコードは、6番、ERROR_INVALID_HANDLE となっています。

テッパン 2015/02/15(日) 14:48:46
失礼しました。訂正します。エラーコードは、

GetPrinter(pInfo->m_pPD,... のとき、ERROR_INVALID_HANDLE です。

OpenPrinter(_T("EPSON EP-301"), &hPrinter, NULL);
GetPrinter(hPrinter, 2, NULL, 0, &cByteNeeded);
のとき、122番、ERROR_INSUFFICIENT_BUFFER になります。

テッパン 2015/02/15(日) 16:02:44
GetPrinter(hPrinter, 2, NULL, 0, &cByteNeeded);
の戻りが ERROR_INSUFFICIENT_BUFFER であるのは予定の結果なんですね。

お手本「プリンタと印刷ジョブの状態の取得方法」
http://support.microsoft.com/kb/160129/JA
の中の
GetPrinter(hPrinter, 2, (LPSTR)pPrinterInfo, cByteNeeded, &cByteUsed)
3番目の引数が (LPSTR) は誤記で、(LPBYTE) が正みたい。

EnumJobs() のあと、PagesPrinted を取得するところで奮戦中です。

テッパン 2015/02/16(月) 08:55:43
EnumJobs() のあとに、次のコードを入れてやってみましたが、何ページ印刷しても、結
果は 0 ページとなります。

DWORD result = pJobStorage->PagesPrinted;
wchar_t test[16];
_itow_s(result, test, 16, 10);
MessageBox(CString(test));

印刷が終わる前に結果が表示されてしまいます。
ちなみに、result = pJobStorage->TotalPages; をやると、印刷するトータル枚数が正し
く表示されます。

EndPrinting() の中にコードを書いていますが、これがいけないのでしょうか。
ネットを検索すると、”PagesPrinted は not reliable”という記事が出ています。
当方、Windows 8.1 です。

ITO 2015/02/16(月) 13:44:03
reliableは、正確なという意味のようです。
notがつくので不正確な........これはうまくないですね。
何回か「GetJob()」をリトライしても結果は変わらないですか?
駄目だとすると他になにか手がないか調べないといけませんね。

ITO 2015/02/16(月) 14:45:13
PagesPrinted
  The number of pages that have printed. This value may be zero if the print 
 job does not contain page delimiting information.

 英訳は下手なので要約すると、ペ−ジの区切りが分からないと'0'返すみたいです。
僕も、今は対策が分かりませんね。

テッパン 2015/02/17(火) 06:48:28
さまざまなタイミングでプリンタ側の情報を取得できるよう、実験的にアプリに押ボタン
を設けて、その中に上記のコードを書きました。
pJobStorage->PagesPrinted が示す値は、次の通りです。

1.プリンタのダイアログが表示されているタイミングであれば、印刷を指示したトータ
ル枚数となる。(まだ印刷の途中であっても)
2.プリンタのダイアログが消えた後では、0枚となる。(まだ印刷が進行中であっても
)
3.例として、3ページの印刷を指示し、用紙を1枚のみセットして印刷すると、1枚目
を印刷中(プリンタダイアログが出ているとき)は、3枚という値が返り、プリンタダイ
アログが一旦消える。
消えた後では0枚が返る。
その後「用紙不足」の警告のあるプリンタダイアログが出る。このとき押ボタンを押すと
、印刷を指示した3枚という値が返る。

結論として、自分の目的にかなわないようです。

ITO 2015/02/17(火) 13:20:45
うーーん、
CPrintInfoでヘッダ・フッダの設定ができるみたいです。
 ページの区切りが出来れば、PagesPrintedの値も取得できるか?
ですね。
今のところ、それぐらいしかないですね。

テッパン 2015/02/18(水) 16:27:47
明記していませんでした。
印刷対象はドキュメントではなく、CViewクラスの OnPrint 中のコード
pDC->TextOut(x, y, str);
IPicture::Render(hDC, x, y, cx, cy, ...);
などです。
ページごとに、文字や画像が変わります。

> CPrintInfoでヘッダ・フッダの設定ができるみたいです。
CPrintInfo構造体のメンバーには、それらしいものがありません。
サンプルがないか探しましたが、見つかりませんでした。

ITO 2015/02/19(木) 11:50:59
https://msdn.microsoft.com/ja-jp/library/ddkzks56(v=vs.90).aspx

今のところ候補は、
次の二つですね。
m_rectDraw
m_strPageDesc

ほかの構造体もあるかも知れませんね。

スレの目的は解決したみたいなので、一度終了して別スレ立てたほうが
いいかも知れませんね。

テッパン 2015/02/20(金) 08:19:13
本スレッド、終了とさせて戴きます。
ありがとうございました。
別スレは、も少し勉強してからにします。

古いスレッドには追加発言できません。