トップ回答者
CView (CWnd ?) 派生クラスが自動作成するスクロールバーの表示状態を取得するには?

質問
-
お世話になります。
VC++ 6.0 MFC、MDI 子フレーム内の CView 派生クラスに関する質問です。
Internet Explorer の中央ボタンを押した時の自動スクロール ( パンニングというそうですが ) を実装しようとして、調べた結果、ScrollWindow 関数は使われず、ウィンドウ自身がスクロール作業をやっている様でした。
なので、CScrollView クラスではなく、CView を基本クラスとし、OnCreate で SetScrollInfo を使ってスクロールバーを表示、そして OnInitialUpdate でスクロールサイズの設定等を行い、無事スクロールバーが表示されるようになりました。
が、スクロールバーの管理をフレームワーク側に任せている為、スクロールバーの表示 / 非表示がおかしくなります。具体的には、スクロールサイズ以上に CView を拡大した場合、スクロールバーが上手く非表示になりません。調べた所、OnSize の引数 cx と cy がスクロールバーの表示 / 非表示の状態に合わせて変わっているようです。
OnSize で GetScrollInfo を呼び出し、SCROLLINFO.nMax 以上の値かどうかで cx と cy にスクロールバーが占めている値を加算して、SetScrollInfo を呼び出せばいいのか、OnNcCalcSize で対処出来るのか、そもそも CView 派生クラスから呼び出せる GetScrollBarCtrl 関数に変わるような関数 (CView 派生クラスでは CWnd 派生クラスと同じく NULL が返ります) が存在しないのか、疑問だらけです。
CView 派生クラスのメンバとしてスクロールバーを作成すれば、この様な問題にならないのは判っていますが、今模索しているのは IE の様なスクロール機能を追加する、可能な限り簡単な方法なので、避けたい所です。
かなり無茶な質問であるのは自覚していますが、何かよい解決方法はないでしょうか?- 編集済み ミッヒー 2009年6月5日 17:16
回答
すべての返信
-
totojo さん、ありがとうございます。
今、ダウンロードして、実行してみましたが、フリッカを起こす事も、スクロール原点のウィンドウがぶれる事もまったくありませんね。
私は http://www.microsoft.com/msj/code.aspx の December 1997 Vol. 12 No. 12 サンプル、および自分のプログラムでフリッカかスクロール原点のウィンドウがぶれるという現象、そして VC++ 6.0 の spy++ が ScrollWindow を import していない、という事から CScrollView では駄目だという結論でしたが、ご指摘のプログラムは綺麗にスクロールしていますね。
感動すると共に、自分の知識の無さを痛感しました。
ただ、さまざまなウィンドウに対してパンニングを行うサンプルなので、アルゴリズムを読み解くのに時間がかかりそうです。
よって、このスレッドは totojo さんの回答で終わりたいと思います。
totojo さん、非常に参考になるサイトを紹介して頂き、ありがとうございました。 -
今更ですが、質問と結論が別方向に行ってしまったので、CView ( CWnd もおそらく同様) が持っているスクロールバーの制御方法をば。
C???View.h
// アトリビュート
protected:
CSize m_sizTotal;
C???View.cpp
void C???View::OnInitialUpdate()
{
CView::OnInitialUpdate();
// TODO: この位置に固有の処理を追加するか、または基本クラスを呼び出してください
// ビュー全体のサイズ設定
m_sizTotal.cx = ???;
m_sizTotal.cy = ???;
// スクロールバーを初期化
SCROLLINFO si;
si.cbSize = sizeof(SCROLLINFO);
si.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_TRACKPOS;
si.nMin = 0;
si.nPos = 0;
si.nTrackPos = 0;
SetScrollInfo(SB_HORZ, &si, TRUE);
SetScrollInfo(SB_VERT, &si, TRUE);
// スクロール範囲設定の為、WM_SIZE 経由で WM_NCCALCSIZE を呼び出し
CRect rcClient;
GetClientRect(rcClient);
SendMessage(WM_SIZE, SIZE_RESTORED, MAKELPARAM(rcClient.Width(), rcClient.Height()));
}
void C???View::OnNcCalcSize(BOOL bCalcValidRects, NCCALCSIZE_PARAMS FAR* lpncsp)
{
// TODO: この位置にメッセージ ハンドラ用のコードを追加するかまたはデフォルトの処理を呼び出してください
// クライアントサイズを取得 (ボーダー等込みの値)
int iWidth = lpncsp->rgrc[0].right - lpncsp->rgrc[0].left;
int iHeight = lpncsp->rgrc[0].bottom - lpncsp->rgrc[0].top;
if (WS_BORDER & GetStyle())
{ // ウィンドウがボーダーを持っていれば、そのサイズを除外
iWidth -= 2 * ::GetSystemMetrics(SM_CXBORDER);
iHeight -= 2 * ::GetSystemMetrics(SM_CYBORDER);
}
if (WS_EX_CLIENTEDGE & GetExStyle())
{ // ウィンドウがクライアントエッジを持っていれば、そのサイズを除外
iWidth -= 2 * ::GetSystemMetrics(SM_CXEDGE);
iHeight -= 2 * ::GetSystemMetrics(SM_CYEDGE);
}
// スクロールバーがないと仮定し、スクロールバーが必要か確認
bool bHorz;
bool bVert;
if (iWidth < m_sizTotal.cx) bHorz = true;
else bHorz = false;
if (iHeight < m_sizTotal.cy) bVert = true;
else bVert = false;
// 水平、垂直スクロールバーの占めるサイズによりスクロールバーが必要になっていないか、再確認
if (bVert && !bHorz && iWidth - ::GetSystemMetrics(SM_CXVSCROLL) < m_sizTotal.cx)
{
bHorz = true;
}
if (bHorz && !bVert && iHeight - ::GetSystemMetrics(SM_CYHSCROLL) < m_sizTotal.cy)
{
bVert = true;
}
if (bHorz)
{ // 水平スクロールバーが必要な為、最大サイズを設定 (ページサイズはクライアント幅を利用)
SCROLLINFO siHorz;
GetScrollInfo(SB_HORZ, &siHorz);
siHorz.nMax = m_sizTotal.cx;
siHorz.nPage = iWidth - (bVert ? ::GetSystemMetrics(SM_CXVSCROLL) : 0);
SetScrollInfo(SB_HORZ, &siHorz, FALSE);
}
if (bVert)
{ // 垂直スクロールバーが必要な為、最大サイズを設定 (ページサイズはクライアント高を利用)
SCROLLINFO siVert;
GetScrollInfo(SB_VERT, &siVert);
siVert.nMax = m_sizTotal.cy;
siVert.nPage = iHeight - (bHorz ? ::GetSystemMetrics(SM_CYHSCROLL) : 0);
SetScrollInfo(SB_VERT, &siVert, FALSE);
}
// 本当はここで ShowScrollBar を呼び出したいのだが、何故か上手く行かない
CView::OnNcCalcSize(bCalcValidRects, lpncsp);
}
C???View::OnSize(UINT nType, int cx, int cy)
{
CView::OnSize(nType, cx, cy);
// TODO: この位置にメッセージ ハンドラ用のコードを追加してください
// スクロールバーの表示 / 非表示
ShowScrollBar(SB_HORZ, cx < m_sizTotal.cx ? TRUE : FALSE);
ShowScrollBar(SB_VERT, cy < m_sizTotal.cy ? TRUE : FALSE);
}
以上のようにすればメンバ変数としてスクロールバーを持っていなくても、CView ( 多分 CWnd ) が元から持っているスクロールバーを利用出来ました。なお、スクロールバーの処理については割愛しました。
不要な書き込みだったかも知れませんが、タイトルで検索し、元からあるスクロールバーを利用しようとする方が居られるかも知れないので投稿させて頂きました。
一応確認はしましたが、バグがないとは断言出来るほど確認していないので、この処理を実装して、バグに合われた方、投稿をお願いします。- 編集済み ミッヒー 2009年6月30日 4:50 たびたびすいません・・・。
-
すいません、コード書いた本人が真っ先にバグに遭遇しました。
既にご覧になった皆さんにはご迷惑をお掛けしました。
OnNcCalcSize 関数ですが、
if (bHorz)
ではなくて、
{ // 水平スクロールバーが必要な為、最大サイズを設定 (ページサイズはクライアント幅を利用)
SCROLLINFO siHorz;
GetScrollInfo(SB_HORZ, &siHorz);
siHorz.nMax = m_sizTotal.cx;
siHorz.nPage = iWidth - (bVert ? ::GetSystemMetrics(SM_CXVSCROLL) : 0);
SetScrollInfo(SB_HORZ, &siHorz, FALSE);
}
if (bVert)
{ // 垂直スクロールバーが必要な為、最大サイズを設定 (ページサイズはクライアント高を利用)
SCROLLINFO siVert;
GetScrollInfo(SB_VERT, &siVert);
siVert.nMax = m_sizTotal.cy;
siVert.nPage = iHeight - (bHorz ? ::GetSystemMetrics(SM_CYHSCROLL) : 0);
SetScrollInfo(SB_VERT, &siVert, FALSE);
}
SCROLLINFO siHorz;
GetScrollInfo(SB_HORZ, &siHorz);
if (bHorz)
{ // 水平スクロールバーが必要な為、最大サイズを設定 (ページサイズはクライアント幅を利用)
siHorz.nMax = m_sizTotal.cx;
siHorz.nPage = iWidth - (bVert ? ::GetSystemMetrics(SM_CXVSCROLL) : 0);
}
else
{
siHorz.nPos = 0;
siHorzTrackPos = 0;
}
SetScrollInfo(SB_HORZ, &siHorz, FALSE);
SCROLLINFO siVert;
GetScrollInfo(SB_VERT, &siVert);
if (bVert)
{ // 垂直スクロールバーが必要な為、最大サイズを設定 (ページサイズはクライアント高を利用)
siVert.nMax = m_sizTotal.cy;
siVert.nPage = iHeight - (bHorz ? ::GetSystemMetrics(SM_CYHSCROLL) : 0);
}
else
{
siVert.nPos = 0;
siVert.nTrackPos = 0;
}
SetScrollInfo(SB_VERT, &siVert, FALSE);
としないと、過去のスクロール位置が残ったままになってしまいますね。
スクロールバーの表示 / 非表示にしか関心が行っていませんでした。
重ね重ね、既にご覧になった皆さんにはお詫び致します。- 編集済み ミッヒー 2009年7月1日 5:50 言い訳