こんにちは。
LBS_OWNERDRAWVARIABLEを指定したリストボックスの挙動について質問があります。
環境:WindowsXP、VC++6.0、MFC、SDI
CListBoxから派生して作成したオーナードローリストボックスがあります。
作成は以下のように行っております。
BOOL CMyListBox::Create(const RECT &rect, CWnd *pParentWnd)
{
return CListBox::Create(
WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_TABSTOP |
LBS_NOINTEGRALHEIGHT | LBS_NOTIFY | LBS_OWNERDRAWVARIABLE |
LBS_DISABLENOSCROLL,
rect, pParentWnd, IDC_MY_LISTBOX);
}
元はLBS_OWNERDRAWFIXEDを指定していたのですが、
項目ごとのバランスを取るために高さを可変にしました。
しかしLBS_OWNERDRAWVARIABLEにすると、マウスホイールでスクロールさせた時の挙動が
おかしくなってしまいます。
通常下方向にスクロールさせると、スクロールバーのつまみは下方向へ移動し、
リストは項目が上に流れるようにアニメーションします。
しかしこのスタイルを設定すると、
マウスホイールを下に回したとき、スクロールバーのつまみが下方向へ移動するのに
リストの項目も下方向に流れるようにアニメーションします。
上に回すときは正常に動くようで問題はありません。
このときおかしいのは見た目だけで、スクロール後の表示位置は正常なようです。
調べてみたところ、どうやらスムーズスクロールがONの環境の場合、
LBS_OWNERDRAWFIXEDを持つリストボックスで発生するバグ(?)のようです。
デフォルトのリストボックスプロシージャの不具合でしょうか?
現在は以下のようにOnMouseWheel()を自前で処理しています。
BOOL CMyListBox::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
// TODO:
UINT uSBCode = SB_THUMBPOSITION;
int nPos;
int nMaxPos;
int nMinPos;
nPos = GetScrollPos(SB_VERT);
GetScrollRange(SB_VERT, &nMinPos, &nMaxPos);
nPos += (zDelta < 0) ? 3 : -3;
nPos = max(nMinPos, min(nMaxPos, nPos));
DefWindowProc(WM_VSCROLL, MAKEWPARAM(uSBCode, nPos), (LPARAM)NULL);
// これだと動作はおかしいままでした
// CListBox::OnVScroll(uSBCode, nPos, NULL);
return TRUE;
}
こうすると、スムーズスクロールはされなくなるものの、
とりあえずそれっぽく動くようになります。
しかし、なぜか直接スクロールバーをマウスで操作した直後やアプリ起動直後に
ホイールを回すと、その1度だけはスムーズスクロールされ、
項目のアニメーションする方向もやはり逆(下方向の場合だけ)になってしまいます。
それ以降はホイールだけの操作だと、プログラム通りに単に3項目ずつずれます。
そのときはスクロールアニメーションはありません。
スムーズスクロール自体はされなくてもいいのですが、
この特定の条件下で一度だけ変な挙動をする部分をなんとか無くしたいのですが、
何かよい方法をご存知の方はいらっしゃらないでしょうか。
よく見てみると、
現在の画面が下にずれていき、
上からスクロール後の画面が降りてくる。
という動作をしているようですね。
#なので、上スクロールの場合でも不自然。
とりあえず、処理中の描画を禁止してみると
うまくいくようですが、いかがでしょう。(WinXP)
BOOL CMyListBox::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
SetRedraw(FALSE);
CListBox::OnMouseWheel(nFlags, zDelta, pt);
SetRedraw(TRUE);
RedrawWindow(NULL, NULL, RDW_UPDATENOW|RDW_INVALIDATE|RDW_FRAME);
return TRUE;
}
他に、スクロールバー操作やSetTopIndex()でも
同じアニメーションをしますから、同じ対策が必要ですかね。
dairygoodsさんレスありがとうございます
> #なので、上スクロールの場合でも不自然。
確認してみると、確かに上スクロールの場合も変でした・・・
リストが下方向に流れているのでてっきり正常なのかと思ってしまいましたが、
おっしゃるように一度下に流れた後、上から降りてきてました
> とりあえず、処理中の描画を禁止してみるとうまくいくようです
教えていただいた方法を試してみたところ、ほぼ理想的な動作を実現できました
以下がそのコードです
void CMyListBox::OnVScroll(UINT nSBCode, UINT nPos, CScrollBar* pScrollBar)
{
// TODO:
SetRedraw(FALSE);
CListBox::OnVScroll(nSBCode, nPos, pScrollBar);
SetRedraw(TRUE);
RedrawWindow(NULL, NULL,
RDW_UPDATENOW | RDW_INVALIDATE | RDW_FRAME | RDW_ERASE);
}
BOOL CMyListBox::OnMouseWheel(UINT nFlags, short zDelta, CPoint pt)
{
// TODO:
SetRedraw(FALSE);
CListBox::OnMouseWheel(nFlags, zDelta, pt);
SetRedraw(TRUE);
RedrawWindow(NULL, NULL,
RDW_UPDATENOW | RDW_INVALIDATE | RDW_FRAME | RDW_ERASE);
return TRUE;
}
RedrawWindowのフラグにRDW_ERASEを入れたのは、どうやらこれを入れないと
リストを最下部にスクロールした時、もし項目が入らない程度の余白ができると
そこに描画内容が残ってしまうようだからです
> 他に、スクロールバー操作やSetTopIndex()でも同じ対策が必要ですかね。
これらも確認しました
スクロールバーもつまみやボタンで操作すると問題ないようですが、
つまみとボタンの間をクリック(PAGEUPやPAGEDOWN?)した時に変な動作をしますね
普段使わない部分なので気がつきませんでした
これに関しては上に示したOnVScroll()で解決しました
またSetTopIndex()ですが、これも確かに動作がおかしくなっていました
現在この関数は特に使っていないので問題はないのですが、
今後使うようになる可能性も否定できないので、念のため以下のように
int CMyListBox::SetTopIndex(int nIndex)
{
int nRet;
SetRedraw(FALSE);
nRet = CListBox::SetTopIndex(nIndex);
SetRedraw(TRUE);
RedrawWindow(NULL, NULL,
RDW_UPDATENOW | RDW_INVALIDATE | RDW_FRAME | RDW_ERASE);
return nRet;
}
としておきました
ひとつ問題があるとすればRedrawWindowにRDW_ERASEを入れると少しちらつきが
目立ってしまうことですが、これは少しいじればなんとかなりそうです
以上です
おかげさまでLBS_OWNERDRAWFIXEDで妥協せずにすみそうです
どうもありがとうございました