画餅展覧会

2009 年 2 月 14 日

CScrollDialogImplにスクロール問題

カテゴリー: C++, Programming, Windows Mobile — jk78 @ 10:29 PM

まだやってるんですよ。CScrollDialogImplってつまりはCScrollImplの問題なんですが。

さて、簡単に行くかと思ったが、そうは問屋が卸さない。水平スクロールバーが表示されたダイアログを、めいっぱい右側にスクロールした状態から、ウィンドウを広げてみる。ダイアログ内の表示が乱れます。

scrolldialogdisorder

通常、この問題の対処は、ダイアログにWS_CLIPCHILDRENを指定するんですが、そうすると今度は、Group Boxの描画と、SetScrollOffset()時の再描画に問題が出ます。結論:WindowsはタコOS。まっとうな人間の使うもんじゃない。(追記:も少し詳しく説明すると、一つにはScrollWindowEx()とSetWindowPos()をこの順で呼び出すと干渉する-これはGDIがタコでしょう。逆に呼び出せばかなりましなのですが-のと、二つにはGROUPBOXコントロールが背景描画をしない-なんで?-ことによります。GROUPBOXはsubclassして逃げるにしても、ScrollWindowEx()とSetWindowPos()の干渉は、複雑な条件判断で醜い行為をしないと逃げられん)

対処としては、WM_SIZEの処理で、親クラスの OnSize() を呼び出す前と後で GetScrollOffset() の戻り値をチェックし、変更があったらダイアログ全体をInvalidate()すると。ああ、きたねえ(笑)。

さあ、いやなことは忘れて、自分の問題に集中しましょう(笑)。

2009 年 2 月 13 日

WTL::CScrollImpl::ScrollToViewのバグ

カテゴリー: C++, Programming, Windows Mobile — jk78 @ 11:31 AM

昨日の続き。WTLのCScrollImpl::ScrollToViewに問題発見。

問題はふたつ(あるいは三つ)。WTL 7.5とWTL 8.0で変わらず。

  1. void ScrollToView(RECT& rect) が座標計算でクライアント座標と「スクロールビュー」座標を混同している。なんでこんな問題が長年放置されてるんだ? 誰も使ってないのか(笑)? 大体、宣言自体が void ScrollToView(const RECT& rect) であるべきだろう。
  2. void ScrollToView(HWND hWnd) が void ScrollToView(RECT& rect) を呼び出すときに、T* pTを介していない。void ScrollToView(RECT& rect) を置き換えただけでは直らない。CScrollImpl::ScrollToView(HWND hWnd) が CScrollImpl::ScrollToView(RECT& rect) を呼び出してしまう。

void ScrollToView(POINT pt) は問題ないでしょう、多分、試してないけど。

で、この問題の対処のためには、ダイアログ実装側で次の宣言/定義をすればよい(昨日のCScrollDialogImplクラスを継承しているという前提で)。

class CMyDlg : public CScrollDialogImpl<CMyDlg>
{
...(省略)
public:
using CScrollDialogImpl<CMyDlg>::ScrollToView;
void ScrollToView(RECT& arg_rect)
{
// rcClient is used only to see its size by .right and .bottom,
// so translation isn't needed.
RECT rcClient = { 0 };
GetClientRect(&rcClient);
// translate arg_rect from client coordinates to "Scroll View Coordinates"
RECT rect = arg_rect;
::OffsetRect(&rect, m_ptOffset.x, m_ptOffset.y);
int x = m_ptOffset.x;
if(rect.left < m_ptOffset.x)
x = rect.left;
else if(rect.right > (m_ptOffset.x + rcClient.right))
x = rect.right - rcClient.right;
int y = m_ptOffset.y;
if(rect.top < m_ptOffset.y)
y = rect.top;
else if(rect.bottom > (m_ptOffset.y + rcClient.bottom))
y = rect.bottom - rcClient.bottom;
SetScrollOffset(x, y);
}
void ScrollToView(HWND hWnd)
{
RECT rect = { 0 };
::GetWindowRect(hWnd, &rect);
::MapWindowPoints(NULL, m_hWnd, (LPPOINT)&rect, 2);
ScrollToView(rect);
}
};

ほとんどWTLからのコピペですが。しかし、見辛い。wordpressにインデント付きのコードを貼り付けるにはどうすりゃいいんだ?

さて、WTLのバグリポートってどこにするの?

追記:もう一つ見つけた。ScrollToView()では無くてOnSize()だけど。関数の最後から6行目ぐらいにScrollWindowEx()呼び出しがあるけど、3番目のパラメータが (m_uScrollFlags & ~SCRL_SCROLLCHILDREN) となっているのは、どう考えても (m_uScrollFlags & ~SW_SCROLLCHILDREN) の誤りだ。幸い両方ともたまたま同じ値なので実害は発生しないけど。品質悪いねえ。

2009 年 2 月 12 日

WTLでScrollable Dialog

カテゴリー: C++, Programming, Windows Mobile — jk78 @ 6:12 PM

ちょっと複雑なダイアログを表示する必要があって、Pocket PCの画面には入りきらない。Pocket PC Developer Networkにスクロールするダイアログのサンプルがあったよな…と思い覗いてみる。ありました。

QA: How do I scroll the contents of a dialog?

しかし、このサンプルを動かしてみると問題発見。画面の縦横切り替えはPocket PC 2003 SEからついた機能なのか、これで、縦画面でダイアログを表示した後、横画面に切り替えると、システムが縦画面の高さ分を表示するためのスクロールバーを出してくれる(表示しきれないコントロールがある場合のみ)。このシステムの用意するスクロールバーと干渉するのである。ご覧の通り:

scrolldlgdemodblbar

あはははは。これじゃ使えません。しょうがないな、といってここ数日かけて自作のスクロール・ダイアログ・クラスを作りましたよ。ATL::CDialogImpl継承して。ああ、苦労したけど、やっと完成した。

と思った途端に、WTLにはatlscrl.hで数々のスクロール・ウィンドウをサポートしていることを知った。あああ。あの苦労は何だったの。がっかりしちゃいました。WTLって悪くないんだけど、ドキュメントが無いんでせっかくの機能が見つけられない。困ったものです。

もっともWTLのatlscrl.hにはダイアログを直接サポートするクラスは無い。いろいろ読み込んだ結果、CScrollWindowImplクラスをそっくりコピーして、ATL::CWindowImplの代わりにATL::CDialogImplを継承する、CScrollDialogImplクラスを作成してみたところ、あっさり動きました。デスクトップでもPocket PCでも。

template <class T, class TBase = CWindow>
class ATL_NO_VTABLE CScrollDialogImpl : public ATL::CDialogImpl<T, TBase>, public WTL::CScrollImpl<T>
{
...中身はCScrollWindowImplからコピー(30行ほど)
}

これでCDialogImplの代わりに使えます。CScrollImplの使い方サンプルは以下が良いかな。

ATL/WTLによるWindowsプログラミング:スクロール

動かしてみると、スクロール動作がぎこちないね。私の自作ダイアログはDeferWindowPos使ったんできれいだったぞ(追記:私のコードはビットマップスクロールをしてないのでスムーズだったのかも)。それと、このままではフォーカスの当たるコントロールに自動スクロールする機能が無いので、それだけ追加しなきゃ(ScrollToView()関数があるので楽そう 追記:使い物にならなかった。脚注参照)。も一つ、ダイアログ内にスクロールバーコントロールがあると、そのメッセージを自分のコードで遮断しないと、ダイアログのスクロールバーが動いちゃいます。俺のコードはその辺の面倒ぐらい見たぞ。

下のスクロールバーを出すために書いたコードは基本的に3行だけです。OnInitDialog()で、

SetScrollSize(content_size_x, content_size_y);
SetScrollLine(10, 10);
SetScrollPage(100, 100);

追記:上の三行の前にCScrollImpl::GetSystemSettings()もしくはSCrollImpl::OnCreate()を呼び出しておくべきですね。

cscrolldialogimplv

cscrolldialogimplh

しかし、Pocket PCのウィンドウを全画面表示しているのに、ウィンドウのスクロールバーが最外側1ドットにボーダーを表示するのが気にいらねえ(笑)。

※CScrollImpl::ScrollToView()は使い物にならん。RECTを渡してやるとRECTを表示する範囲内でできるだけ原点に近いほうにスクロールする。だから画面の真中に表示されているコントロールをクリックすると右下に逃げたりする。このコードどういう目的で設計したんだ?追記:よく分からん。調べなおします。追記の追記:調べました。バグでしょう。

古い投稿 »

Powered by WordPress