MFC(SDI)クライアント領域に描画したものの拡大・縮小

[戻る]
MFC一年生 2007/12/06(木) 18:55:57
環境:
WindowsXP SP2
MicrosoftVisualStudio2005 VisualC++ MFC SDI(ScrollView)

質問:
はじめましてこんにちは。
最近C++を勉強し、MFCをやり始めたものです。

http://mailsrv.nara-edu.ac.jp/~asait/visual_cpp/scroll/scroll3.htm#section3
のホームページを使って勉強させていただいています。
そこで、クライアント領域に表示したものを、マウスのホイールボタンが
押されたら、押されたところを中心に拡大・縮小するプログラムを
作成しようとしているのですが、
クライアント領域に表示するところまではできたのですが、
マウスのホイールボタンが押されたときに、画像を拡大・縮小する方法が
わかりませんでした。

調べてみると、
SetWindowOrg、SetWindowExt で座標を変化させる方法。
MSDNでは
「ビューのスクロールと拡大/縮小」
http://msdn2.microsoft.com/ja-jp/library/xaa11ye0(VS.80).aspx
「CScrollView クラス」
http://msdn2.microsoft.com/ja-jp/library/3t073d82(VS.80).aspx

マウスのカーソル座標を受け取る方法は
BOOL GetCursorPos(LPPOINT lpPoint);

などがあることまではわかったのですが、MSDNのMFCに関するところは
C++のように例文がなく、どのように使ったらよいかわかりませんでした。

どなたか、お分かりになられる方がおりましたら、この初心者にわかるよう
よろしくご教授ください。
初心者的な質問で申し訳ありませんが、よろしくお願いします。

また、先輩方は、MSDN等をみてプログラムが組めるようになる勉強をどのように
したのか、合わせてお答えしていただければ幸いです。
重ね重ねよろしくお願いします。

2007/12/06(木) 23:13:36
「画像」との事なのでビットマップの拡大縮小の話と仮定しますが、「ビューのスク
ロールと拡大/縮小」はそういう意味ではないようです。

MSペイントの拡大などの動作のようなものならば、画像にしろ直接描画にしろ
ビューの大きさの変更を行ったうえで描画時に拡大して描画する事になると思い
ます。

実際にどのように描画しているかによりますが、ビットマップの拡大縮小を簡便に
行うならばCDC::StretchBltを利用するのが手っ取り早いかと思います。
なお、このStretchBltは出力イメージの品質が余り高くありません。
CDC::GetStretchBltModeをHALFTONEに設定する事で品質を有る程度高
める事は可能ですが、それ以上を望む場合は自分で拡大縮小のアルゴリズム
を考えるか、既存のライブラリなどを流用する事になります。
CDC::StretchBltとCDC::GetStretchBltModeはそれぞれ同名でSDKのAPIが
存在するので、そちらでも構いません。

画像の拡大や縮小で速度が気になる場合は、場合は予め拡大してメモリなど
に保持しておく方法もあり、スクロール処理の速さを考える上では有効かもしれ
ませんが、拡大縮小処理に時間がかかるようになります。
これらの方法を組み合わせる事で、高速で拡大縮小が可能でありながら見た
目上スクロール処理も高速に済ますことも出来ます。

MFC一年生 2007/12/07(金) 06:54:22
早速返答いただきましてありがとうございます。

画像?とその他の差があまり分からないのですが、他は
何というのでしょうか・・・?
一応、座標データをファイルから読み込み、その座標
データに基づいて、MovetoとLinetoを使って描画をして
います。ソースを乗せようと思ったのですが、スパムエラー?
という表示が出て投稿できませんでした。
分かりづらくてすいません。。。

ビットマップとは少し違うような気もするのですが、
どうなのでしょうか?

wood 2007/12/07(金) 08:41:42
的をはずしていたら、ごめんなさい

VC サンプル「SCRIBBLE サンプル 」を改造してみたらどうですかね

マウスのホイールボタンが押されたときに
サンプルで描画された領域をDIBなどを使いBMPに一度保存し
別ウィンドウ(MDI内の子ウィンドウ)でBMPの拡大/縮小を行う

これができたらSWAPサンプルでSDI ビューの表示切替を行い
あたかも同じウィンドウ上で作業しているように見せる

IE(ブラウザー)と同等の拡大縮小を実現させるには
Windows EXEアプリでは、かなり難しいと思いますよ

発想が豊かになることはよいことですが・・・

>MSDN等をみてプログラムが組めるようになる勉強をどのように
したのか、合わせてお答えしていただければ幸いです

1.1つのアプリを作るために、WEBやMSDNを自分の知る限りの単語で検索しまくる
2.1行のステートメントのために、それが掲載されている数千円の書籍を購入する
3.ここの掲示板でけなされても「わからんものはわからん!」と言いながら
  聞く
4.理解が深まり次第、言葉を変えて目的にあった質問を繰り返す
  当然、知りえる単語も増えるので、WEB検索等は怠らずにね

わたしは本業言語はCOBOLですのでC・C++は趣味の範囲でしかないため
1行を理解するのに 5・6ケ月かかることも普通にありますよ
(=^エ^=)ニャハハ

2007/12/07(金) 23:38:26
>画像?とその他の差があまり分からないのですが、他は
>何というのでしょうか・・・?
ビットマップ(ラスタ)を転送する場合と、ブラシ等(ベクタ)で描画する場合、という
感じで分けて書きました。
ビットマップの場合は無理矢理拡大する必要がありますが、ブラシやフォントで描
画する場合はそれぞれのサイズと座標を変えるほうが省メモリかつ高精度な場合
が多いと思います。


>MovetoとLineto
との事なので、StretchBltは使わないほうが良さそうです。

提示されている参考ページですと、ビュー全体に対する相対的な大きさで描画
しているようです。
このサンプルに沿った作り方をしていて、表示上の大きさを変えたい場合は
CScrollView::SetScrollSizesの第一引数に「全体サイズ(論理単位)×倍
率」を与えるだけでいいと思います。


>BMPの拡大/縮小を行う
は余りお勧めできません。
確実にジャギ(滑らかな筈の線が矩形の組み合わせのようになるとか)を起こしま
す。
意図的にジャギを起こすのならば構いませんが、そうでなければ手間や描画時
間が減る以外のメリットは無い様に思います。
そのメリットも、描画内容によっては減るどころか増える可能性もあります。

参考にされているページのように画像的ではない内容を扱う場合には、精度を
わざわざ落とす必要も無いかと思います。

MFC一年生 2007/12/11(火) 01:33:13
麩さま、woodさま、返信本当にありがとうございます!!
お二方に教えていただいたことを調べていたら時間が経ってしまいまして、
返信が遅れてしまい、申し訳ありません。

描画というといわゆるお絵かき的なほうの関数が目立つような気がします。
StretchBltなどはやはり「画像処理」のための関数なのですね・・・
私の今回の事例では不向きなのですね。

>1行を理解するのに 5・6ケ月かかることも普通にありますよ
!?とてもそんなに時間はかけられないです・・・(^_^;)
>IE(ブラウザー)と同等の拡大縮小を実現させるには
>Windows EXEアプリでは、かなり難しいと思いますよ
Windows EXEアプリとIEとでは違うのですか・・・?
IEもMDIの一種なのだと思ってました。。。

そこで、麩さまの言うとおり、SetScrollSizesをマウスのスクロールが押され
る度にアップデートしたいのですが、今現在使っているソースが以下のように
なっているのですが、これをサイズ変更をして再描画(更新描画)するように
するためにはどのようにすればよいのでしょうか・・・?
void CGridMasterView::OnInitialUpdate()
{
    CScrollView::OnInitialUpdate();
    CSize sizeTotal;
    sizeTotal.cx = PaperWidth = 2965; 
    sizeTotal.cy = PaperHeight = 2100;
    SetScrollSizes(MM_LOMETRIC, sizeTotal);
}

MFC一年生 2007/12/11(火) 01:36:35
ソースコードの続きです。
void CGridMasterView::OnDraw(CDC* pDC)
{
ーーーーファイル読み込み処理ーーーー
ーーーー描画処理ーーーーー
    int ox, oy;
    CSize totalSize = GetTotalSize();
    ox = totalSize。cx/2;     右移動の数値
    oy = - totalSize。cy/2;   下移動の数値
    CPen p(PS_SOLID, 1, RGB(0, 0, 0));
    CPen* oldp=pDC->SelectObject(&p);
    int i,w,h,*x,*y ;
    w = 2*ox ; h = 2*oy ;
    x=new int 「IMax+2] ; y=new int [IMax+2] ;
    x[1] = (int)( dat[1]*w/max) + 5 ;
    y[1] = (int)(-dat[1]*h/max) + h/2 ;
    pDC->MoveTo(x[1],y[1]) ;
    for (i=1;i<=IMax;i++) {
        x[i] = (int)( dat[i]*w/max) + 5 ;
        y[i] = (int)(-dat[i]*h/max) + h/2 ;
        pDC->LineTo(x[i],y[i]) ;
    }
    if(i>=IMax) break ;
    else {
        x[1] = (int)( dat[i+1]*w/max)+5 ;
        y[1] = (int)(-dat[i+1]*h/max) + h/2 ;
        pDC->MoveTo(x[1],y[1]) ;
    }
    pDC->SelectObject(oldp);
}

PATIO 2007/12/11(火) 11:00:03
考え方は二つあると思います。

1.現在の描画処理は弄らないで座標のマッピングで調整してしまう方法。
2.描画倍率等を考慮して描画する座標その物を毎回計算する方法。

1の方法の場合はCDCクラスのマッピング系の関数でやります。
実際の描画は変えずに表示する時のマッピングで調整して拡大、縮小します。

2.の方法場合は、元の形状の座標点列を別に保持しておいて
中心にすべき座標と表示する時の倍率を使って描画時の座標を計算で
算出して描画する方法です。

私はもっぱら2の方法を使う事が多いです。
別に2の方が良いほうだとは思っていませんが、
頭で考える時に直接的に考えられるのでこちらを使っています。

とりあえず、両方試して見て描画結果等も確認した上で方法を選ぶと
良いのではないかと思います。

MFC一年生 2007/12/11(火) 18:57:51
PATIO様、ありがとうございます^^
>1.現在の描画処理は弄らないで座標のマッピングで調整してしまう方法。
>2.描画倍率等を考慮して描画する座標その物を毎回計算する方法。

>1の方法の場合はCDCクラスのマッピング系の関数でやります。
>実際の描画は変えずに表示する時のマッピングで調整して拡大、縮小します。
どのような関数を使えばよいのでしょうか・・・?
今現在のプログラムですと、クライアント領域?の大きさに合わせて描画する
プログラムになっているので、
void CGridMasterView::OnInitialUpdate()
{
    CScrollView::OnInitialUpdate();
    CSize sizeTotal;
    sizeTotal.cx = PaperWidth = 2965; 
    sizeTotal.cy = PaperHeight = 2100;
    SetScrollSizes(MM_LOMETRIC, sizeTotal);
}
のsizeTotalの数字を変えられるようなプログラムを組めばよいと思ったので
すが、マウスのスクロールを受け取って、動的にsizeTotal.cx、cyの値を変え
る方法がわかりません・・・。
また、cx、cyの値が変えられたら、それをアップデートして再描画しないと
ユーザーが何かアクションを起こすまで描画状態が変わらないような気がする
のですが、どうしたらよいのでしょうか?
よろしくお願いします(m>o<)m

>2.の方法場合は、元の形状の座標点列を別に保持しておいて
>中心にすべき座標と表示する時の倍率を使って描画時の座標を計算で
>算出して描画する方法です。
つまり、マウスの座標を受け取ってそこに近い部分を描画しなおすということ
なのでしょうか・・・?

2007/12/12(水) 05:05:25
言葉が毎回変わっているようなので確認します。
拡大・縮小のタイミングは、マウスホイールの回転ですね?
とりあえずマウスホイールの回転としておきます。

>Windows EXEアプリとIEとでは違うのですか・・・?
>IEもMDIの一種なのだと思ってました。。。
確かにそうですが、色々機能が有りすぎて、あれを単純なプログラムで再現でき
るとは思わないほうが良いかと思います。

>これをサイズ変更をして再描画(更新描画)するように
>するためにはどのようにすればよいのでしょうか・・・?
変更したいタイミングで同じ処理を呼んでしまって構いません。
拡大・縮小なので、
    sizeTotal.cx = (long)(2965*m_dZoom); 
    sizeTotal.cy = (long)(2100*m_dZoom);
等とする必要はあります。
正しくはOnUpdateのなかに上記処理を書き、変更したいタイミングでドキュメント
を更新すべきですが。

変更したいタイミングはおそらくマウスホイール回転時なので、クラスウィザードなど
でWM_MOUSEWHEELのハンドラOnMouseWheelを追加してそこに書くことにな
ると思います。

>ソースコードの続きです。
何をしたいのか理解出来ませんでした。
とりあえず結構変な気がします。
「if(i>=IMax) break ;」や確保した配列を一部しか初期化しないで使っている
点などです。
拡大などが出来てもこれは読みにくいだけでなくバグの原因にもなりうるかと思い
ます。
まぁ、余計なお世話ですね、すいません。

>考え方は二つあると思います。
方法は多分3つになるかと思います。
1は同じですが、
2は、描画時にデータの座標を拡大する場合と、拡大操作時にデータの座標を
拡大する場合です。
どちらが良いかは元データの形態によります。

>どのような関数を使えばよいのでしょうか・・・?
OnDraw内部でpDCのマッピング関数を呼んで下さいとしか言えません。
MSDNを引きましょう。慣れると便利です。

>今現在のプログラムですと、クライアント領域?の大きさに合わせて描画する
その辺りは気をつけないと予期せぬ結果が得られる事になります。
とりあえず、参考にされたらしいサイトのサンプルでは原点と軸のみでグラフ自体
は固定サイズでした。

>再描画しないと…描画状態が変わらない
そうです。InvalidateRectあたりで全画面を更新対象にして下さい。

>つまり、マウスの座標を受け取ってそこに近い部分を描画しなおすということ
>なのでしょうか・・・?
マウスの座標必要なのは、拡大する瞬間だけです。
そして、描画は描画要求が来るたびに行います。

MFC一年生さんはなにか取り違えているように思いますが、
これらの方法の違いは、OnDrawの中の表示をどうするか、のようなものだと考え
て良いと思います。

正しくは、拡大時に現在の情報(スクロール位置)を退避しておき、ビューを作り
直し、退避した情報を元にスクロールし、再描画します。



マッピング関数は試さなかったのですが、スクロール位置はどちらの方法でも保存
する必要があるように感じました。
どうでしょうか?

PATIO 2007/12/13(木) 11:35:52
マウスカーソルの付近だけ描画しなおすでは、言われているアクションになら無いと
思いますけれど。自分がしたいオペレーションをするにはどんな画面の更新が必要かを
認識した上で処理を考えないときちんと纏まりません。

マウスホイールを回した時にその時のマウスカーソルの位置を中心にして拡大/縮小を
おこなうのであれば、基本的にクライアント領域内の形状を表示している部分は
全面書き換えになると思います。
スクロールにしても拡大/縮小にしても見えている部分をどう描画するかですから
基本的な考え方は変わりません。

極端な話で行くとどちらの処理でも見えている部分だけを描画するのが、
一番効率が良いわけです。画面外を描画したところで見えないわけですから
意味が有りません。ですから、見えている部分をどう描画すれば、
見ている側からスクロールや拡大/縮小を行なっているように見えるかと言う事に
なります。但し、これを厳密にやりだすと計算やら判断文が増えてきますから
やりすぎても逆効果になります。
まずは、見えてないところも書いてしまうつもりで見えている部分が意図通りの
絵になるにはどう書けば良いかを考えた方が良いと思います。
拡大/縮小と形状の移動は、座標変換の計算としては難しいものでは有りません。
この辺は自分自身でよく整理してみると良いかと思います。
紙の上に形状を書いて見て意図した部分が何処になるのか枠を書いてみるとか
すると関係がつかめてくるかもしれないですね。

MFC一年生 2007/12/26(水) 16:31:19
PATIOさま、麩さま、woodさま、本当にありがとうございました。
いろいろと試していたため返信が大変遅れてしまいました。本当に申し訳ありません。
本日、やっとこさできました!!
みなさま、本当にありがとうございました!
マウスのカーソル位置をOnMouseWheelで取得し、そこを中心に描画しなおすことで解決
できました。
また、みなさま、特に麩さまには、プログラムのほかの部分に関してもご指摘いただき
まして、「そういうところを気にするのか!」と、とても勉強になりました。ありがと
うございました。
woodさまには、勉強法等のご指導もいただき、この2週間、とても役に立ちました。これ
からもがんばりたいと思います。ありがとうございました。
PATIOさま、プログラムを組む際の考え方を多く教えていただき、想像しやすくな
りました。また、よい、悪いの差がよくわかり、とても勉強になりました。ありがとう
ございました。

また何かありましたらお世話になりたいと思います。その際は、よろしくお願いしま
す。
私も、早く皆様のように質問に答えられる人間になれるようにがんばりたいと思いま
す。本当にありがとうございました。

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