CListCtrlなどの派生クラスの使いかた

解決


ひる  2009-07-27 23:43:33  No: 70708

CListCtrlの派生クラスを作り、自分自身でリスト項目を作成し、
その際にnewで確保した情報のポインタをItemDataに入れています。
このポインタは、自身のDeleteAllItemsの中で一緒に解放しています。

ところが、DeleteAllItemsやDeleteItemなどはvirtualでないため、
外で基本クラスのポインタなどを使って操作されたら
リスト項目のみが削除され、ポインタを解放できなくなってしまいます。
さらに、勝手にInsertItemなどでアイテムを挿入されても困ります。

こういう場合、MFCではどのような実装をするべきなのでしょうか。
「基本クラスにキャストせずに用意された関数だけ呼んでもらう」
などといった利用者の使いかたに委ねることになるのでしょうか。


仲澤@失業者  2009-07-28 02:03:03  No: 70709

リストコントロールは、「ある唯一のデータの表示方法の1つ」である
と考えましょう。つまり「ある唯一のデータ」と「表示方法」は
1対多の対応関係になります。
この場合いくつかの「表示方法」が存在しなくても、また「表示方法」
が一つも存在しなくても「ある唯一のデータ」は存在していなければ
なりません。
従ってアイテムのlparamに設定するデータは常に「ある唯一のデータ」
のポインタのコピーでなければなりません。
つまり、リストコントロールに実体を管理させてはならないのです。
当たり前ですね(^^)。


gak  2009-07-28 02:06:39  No: 70710

> 外で基本クラスのポインタなどを使って操作されたら
> リスト項目のみが削除され、ポインタを解放できなくなってしまいます。
アイテムの削除がなされた際
  LVN_DELETEALLITEMS
  LVN_DELETEITEM
が WM_NOTIFY 経由で通知されるので其処で開放してやれば良い。

> さらに、勝手にInsertItemなどでアイテムを挿入されても困ります。
削除時と同様に
  LVN_INSERTITEM
が通知されるので、そのタイミングで”newで確保した情報のポインタ”とやらを設定してやれば良い。

WM_NOTIFY は CListCtrl の親に投げられるメッセージだが、MFC のメッセージリフレクション
(ON_CONTROL_REFLECT)を利用してやればCListCtrlの派生クラス側でこれらを処理できる。


subaru  2009-07-28 02:44:40  No: 70711

動作を大きくカスタマイズしていると
InsertItemなんかはたとえvirtualであっても
呼ばれては困るかもしれませんね。

リストビューとは別のコントロールだと考えるなら
CListCtrlから派生せずCWndから派生して
CListCtrlの変数をメンバに持つようにするとか。
必要な実装をいちいち用意しなくてはいけないのは面倒だけど
堅牢性は高くなります。


ひる  2009-08-03 08:09:28  No: 70712

> つまり、リストコントロールに実体を管理させてはならないのです。
> 当たり前ですね(^^)。

アプリ内の複数のダイアログに置かれることを想定しているため、
CListCtrlを派生し、コントロール自身でデータを探して表示するようにし、
使う側はその派生クラスをダイアログに持つだけという実装をしたのですが、
こういうことはせずに、CListCtrlのまま使い、
ダイアログ側に毎回同じ準備処理を置くべきということでしょうか。

> アイテムの削除がなされた際
>   LVN_DELETEALLITEMS
>   LVN_DELETEITEM
> が WM_NOTIFY 経由で通知されるので其処で開放してやれば良い。

外からは追加や削除をしないでほしいというのが実現したいことなのです。
説明が足りませんでした。失礼しました。

> 動作を大きくカスタマイズしていると
> InsertItemなんかはたとえvirtualであっても
> 呼ばれては困るかもしれませんね。

過去に同じような方法でフォント名選択コンボボックスなどを
作ったこともありますが、このときも同じような疑問を持ちました。
このときは文字列のみのアイテムで、ポインタなどは絡んでいなかったので、
勝手に追加や削除をされても大して痛くありませんでしたが。

> CListCtrlから派生せずCWndから派生して
> CListCtrlの変数をメンバに持つようにするとか。
> 必要な実装をいちいち用意しなくてはいけないのは面倒だけど
> 堅牢性は高くなります。

この方法も考えて作ってみたのですが、
コントロールのイベント(LVN_ITEMCHANGEDなど)などが
リストコントールの仕組みのまま親ウィンドウに渡らなくなり、
ダイアログにもリストコントロールのまま置けなってしまいました。
こういうところもすべて用意してあげないといけないのでしょうけど、
ネットを探してもこういう使い方をしている例が出てこないということは、
やはりこの手のコントロールを作るときには
・InsertItemは禁止
・DeleteAllItemsやDeleteItemも禁止
というようなコメントを入れることになるのでしょうか。


subaru  2009-08-03 22:53:20  No: 70713

>コントロールのイベント(LVN_ITEMCHANGEDなど)などが
>リストコントールの仕組みのまま親ウィンドウに渡らなくなり、
>ダイアログにもリストコントロールのまま置けなってしまいました。

こういうのは普通カスタムコントロールとして配置します。
親ウィンドウへ通知するのであればその仕組みも
考えなけいといけません。
カスタムコントロールの作成方法は探せばいくつか見つかると思います。

CListCtrlのソースを参考にすれば、ひるさんの望むような
クラスも作れるかもしれません。
ただしその場合は、リストコントロールであることに変わりなく、
CListCtrlの別実装という感じのクラスになります。
(同一のリストコントロールが自作クラスだけでなく
CListCtrlクラスからもAttachできる)

>やはりこの手のコントロールを作るときには
>・InsertItemは禁止
>・DeleteAllItemsやDeleteItemも禁止
>というようなコメントを入れることになるのでしょうか。

アプリ内でルールを決めて使う分にはいいでしょうが、
ライブラリーとして作成するのであればおすすめできません。


subaru  2009-08-04 02:33:56  No: 70714

カスタムコントロールで作る場合、MFCクラスでハンドルできるのは
インスタンスに関連付けられた後になり、
ダイアログに貼り付けて使う場合はそんなに簡単でないので補足しておきます。

とりあえずこんな感じで作ればできるかなと思います。
・ウインドウプロシージャーを用意して(DefWindowProcを使わない)
WM_CREATEでCListCtrlを構築、子ウインドウとして作成後、
SetPropでCWndのハンドルに関連付ける。
WM_SIZEでサイズ変更、WM_DESTROYで後始末など行う。
・PreSubclassWindowをオーバーライドしてGetPropで
CListCtrlへのポインタを取得しメンバ変数に保存。
(リストへの操作はこのポインタを通して行う)

ダイアログを表示する前にウインドウクラスを登録しておく必要があります。
ちなみにMFCは詳しくないのでCListCtrlへのポインタを取得する
タイミングがPreSubclassWindowで適当かどうかは怪しいです。


ひる  2009-08-09 05:41:15  No: 70715

詳しい実装方法の解説ありがとうございます。
複数のダイアログで使い回せるような専用機能を持った
リストコントロールやコンボボックスなどを作るためには、
それらが本来持っている機能を外に見せないようにしなくてはいけないのですね。

カスタムコントロール自体は作った経験はありますが、
内部にCListCtrlを持って仲介するようなことは未経験ですので、
じっくり勉強させていただきます。
ありがとうございます。


ひる  2009-10-26 22:22:28  No: 70716

3ヶ月前の話を蒸し返してしまいますが、ご了承ください。
VC2008のクラスを見ていたところ、CMFCFontComboBoxという、
CComboBoxを派生させているクラスを見つけました。
これなどは、自分が実装方法に疑問を持ったような派生クラスのようです。
http://msdn.microsoft.com/ja-jp/library/bb983806.aspx

まだVC2008は手元に無いため、
できれば詳しいかたにお伺いしたいのですが、
このような派生クラスは、基本クラスのメンバ関数で操作された際には
どのような動きになるのでしょうか?(CComboBox::AddStringなど)


※返信する前に利用規約をご確認ください。

※Google reCAPTCHA認証からCloudflare Turnstile認証へ変更しました。






  このエントリーをはてなブックマークに追加