はじめまして。
いま、VC6でMFCを使ってプログラムをしています。
プロジェクトの作成ウィザードを使ってSDIのプロジェクトを作り
ViewクラスのOnDrawに下のように記述しました。
void CHogeView::OnDraw(CDC* pDC)
{
CHogeDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: この場所にネイティブ データ用の描画コードを追加します。
CRect rcClient;
rcClient.SetRect(0, 0, 200, 200);
pDC->Rectangle(rcClient);
CRect rcClip = rcClient;
rcClip.DeflateRect(30, 30, 30, 30);
CRgn rgnClip;
rgnClip.CreateRectRgnIndirect(rcClip);
pDC->SelectClipRgn(&rgnClip);
pDC->MoveTo(rcClient.TopLeft());
pDC->LineTo(rcClient.BottomRight());
pDC->SelectClipRgn(NULL);
rgnClip.DeleteObject();
}
すると、意図した通りに枠が描画され、その中にクリッピングされた直線が描画されま
した。
次にOnPrintに同じような形で以下のように書き、印刷プレビューで
確認したのですが、こちらは枠だけが描画され、直線が描画されません。
void CHogeView::OnPrint(CDC* pDC, CPrintInfo* pInfo)
{
// TODO: この位置に固有の処理を追加するか、または基本クラスを呼び出してください
CRect rcClient;
rcClient.SetRect(0, 0, 5000, 5000);
pDC->Rectangle(rcClient);
CRect rcClip = rcClient;
rcClip.DeflateRect(1000, 1000, 1000, 1000);
CRgn rgnClip;
rgnClip.CreateRectRgnIndirect(rcClip);
pDC->SelectClipRgn(&rgnClip);
pDC->MoveTo(rcClient.TopLeft());
pDC->LineTo(rcClient.BottomRight());
pDC->SelectClipRgn(NULL);
rgnClip.DeleteObject();
CView::OnPrint(pDC, pInfo);
}
これを画面の方と同様にクリッピングされた直線が描画されるようにするには
どうしたらいいのでしょうか。
VCにはサービスパックの6があたっています。
よろしくお願いします。
たぶんですが、プリンタのデバイスコンテキストにはSelectClipRgn()がうまく
効かないのではないでしょうか。
試しに、OnDraw()内でオフライン描画した後、PrintDCにBitBltしたら、うまく
いきました。
もし、意味が分からなかったら連絡ください。
サンプルコードを載せます。
ちなみに、デフォルトのMFCの実装では、OnDraw()はOnPrint()からも呼ばれま
すので、OnPrint()のオーバーライドは不要ですよ。
画面とプリンタで出力サイズが違って困る場合は、
CDC::SetMapMode()
で画面とプリンタの出力サイズを同じにします。
bunさん回答ありがとうございます。
OnPrintを削除し、OnDrawを下のようにしてみました。
void CHogeView::OnDraw(CDC* pDC)
{
CHogeDoc* pDoc = GetDocument();
ASSERT_VALID(pDoc);
// TODO: この場所にネイティブ データ用の描画コードを追加します。
CRect rcClient;
rcClient.SetRect(0, 0, 200, 200);
CDC dc;
dc.CreateCompatibleDC(pDC);
int nDc = dc.SaveDC();
CBitmap bitmap;
bitmap.CreateCompatibleBitmap(pDC, rcClient.Width(), rcClient.Height());
dc.SelectObject(&bitmap);
dc.Rectangle(rcClient);
CRect rcClip = rcClient;
rcClip.DeflateRect(30, 30, 30, 30);
CRgn rgnClip;
rgnClip.CreateRectRgnIndirect(rcClip);
dc.SelectClipRgn(&rgnClip);
dc.MoveTo(rcClient.TopLeft());
dc.LineTo(rcClient.BottomRight());
dc.SelectClipRgn(NULL);
rgnClip.DeleteObject();
if (pDC->IsKindOf(RUNTIME_CLASS(CPreviewDC)))
{
/////////////////////////////////////////////////
// ここにpDC->SetMapModeを記述?
/////////////////////////////////////////////////
}
pDC->BitBlt(rcClient.left, rcClient.top, rcClient.Width(), rcClient.Height
(), &dc, 0, 0, SRCCOPY);
dc.RestoreDC(nDc);
dc.DeleteDC();
bitmap.DeleteObject();
}
画面への描画は変更前と変わらずで来ましたが、印刷の際の
> 画面とプリンタで出力サイズが違って困る場合は、
> CDC::SetMapMode()
> で画面とプリンタの出力サイズを同じにします。
というところがよく分かりません。
申し訳ないのですが詳しく教えていただけませんでしょうか。
>if(pDC->IsKindOf(RUNTIME_CLASS(CPreviewDC)))
RUNTIME_CLASSはあまり使わないほうがいいです。
こちらを使ってみたらどうですか。
pDC->IsPrinting()
>> 画面とプリンタで出力サイズが違って困る場合は、
>> CDC::SetMapMode()
>> で画面とプリンタの出力サイズを同じにします。
> というところがよく分かりません。
MSDNの「SCRIBBLE」 を参考にしてみたらいいとおもいます。
「MSDN CD」にあります
ここまでのやりとりからして、ヒントだけ教えれば、あとは分かりそうですね。
デバイスコンテキストにはマッピングモードというのがあって、その設定値によ
って、どのように線が引かれるかが変わるのです。
マッピングモードの指定を省略した場合は、MM_TEXTになります。
これが、ほえほえむーさんのよくご存じの座標系であり、たとえば、
> dc.LineTo(0, 0);
> dc.LineTo(20, 20);
としたとき、MM_TEXTなら 左上から右に20ドット、下に20ドット先に線が引かれ
ます。問題なのは純粋に1ドットの点であることです。
画面とプリンタでは、通常、プリンタの方が解像度が高いので、プリンタの方は
出力結果が小さくなってしまうのです。
(なお、MSDNではドットのことを[デバイス ピクセル]と呼んでいます)
そこで、たとえば、
dc.SetMapMode(MM_LOMETRIC);
とします。
すると、以降の描画はドットとは無関係に 0.1mm 単位という物理的な単位での描
画になります。つまり、先ほどの例である
> dc.LineTo(0, 0);
> dc.LineTo(20, 20);
なら、画面だろうがプリンタだろうが、
0.1mm × 20 = 2.0mm 右側
0.1mm × 20 = 2.0mm 上側
に向かって線が引かれます。
解像度の高いプリンタであればあるほどドット数は増えますが、物理的なサイズ
は一定なわけです。これが WYSIWYG(What You See Is What You Get)と呼ばれる
ものになります。
ここで注意すべきは、Y軸の方向が変わることです。
MM_TEXT以外の座標系は、全てY軸のプラス方向が上側(数学座標系)であることで
す。そのため、
dc.SetMapMode(MM_LOMETRIC);
だけを行うと、原点(0, 0)のデフォルト位置は左上ですので、見えている画面は
第四象限(Y軸がマイナス値)になってしまいます。これだと扱いづらいので、原
点位置を移動するのが、
CDC::SetViewPortOrg()
です。
最後にもう一つ知っておくべきキーワードが、[論理単位]です。
CDC::MoveTo(), CDC::LineTo()
などのCDCクラスのメンバのMSDNを参照すると、[論理x座標]とか[論理y座標]な
どと書かれています。
[論理~]と書かれているのが、上記の説明のように、マッピングモードによって
実際のスケールが変わる座標系です。
それに対して、
CWnd::GetClientRect()
などのようなCDCクラス以外のメンバはマッピングモードの影響を受けません。
つまり、常に MM_TEXT であるかのように動作します。
マッピングモードに依存する座標系 : 論理座標系
マッピングモードに依存しない座標系 : デバイス座標系
これでは、座標系がバラバラですので、困ってしまいます。
そこで、相互の座標系を変換するのが、以下のメンバです。
CDC::DPtoLP() : デバイス座標系 → 論理座標系
CDC::LPtoDP() : 論理座標系 → デバイス座標系
これだけの情報があれば、おそらくMSDNを読みとけるものと思います。
そうそう、
上記の説明通りなので、通常はOnDraw()内で、OnPaint()から呼ばれたか、
OnPrint()から呼ばれたかを意識する必要はありませんが、
何らかの理由で、どうしてもそれを知りたい場合は、
CDC::IsPrinting()
で分かります。
リージョンの座標は通常論理単位ですが、クリッピングリージョンとして
使用するときだけデバイス単位として解釈されます。ご注意ください。
印刷プレビュー時は、MFC内部で特殊な座標マッピングが行われているため、
クリッピングリージョンをそのまま使おうとするとうまく行きません。
(座標マッピングとは何か、については↑のbunさんの投稿を参照してください)
私も過去にこの罠にはまったことがあります。
詳しくは以下のURLを参照してください。
http://support.microsoft.com/kb/402004/ja