CListCtrl::SetItemDataでセットした独自クラスのポインタ情報が上書きされないようにするには?

解決


Pen  2011-12-13 15:32:24  No: 73034  IP: 192.*.*.*

環境:Win7 VS2008 VC++

CListCtrl::SetItemData/GetItemDataを使用して、リストがクリックされたときに、
リストに表示されている情報にたどれるように、データのポインタをSet/Getしています。

データはCArrayの配列に独自クラス(CMyData)を用いています。


ソースコードは以下のようになります。
===。

定義:
CArray<CMyData, CMyData&> m_myInfo;
CListCtrl m_list;

ソース:
void SetDataXXX(int index) {
  CMyData myData;
  /* myDataに適当なデータをSetする */
  m_myInfo.Add(myData);  // 原因となるコード

  m_list.SetItemData( index, (LPARAM)&m_myInfo[index] );

}


SetDataXXX(int index)関数は、データが取得される度にCallされる関数で、
m_myInfoにデータをセットし、m_listに対象のデータのポインタをセットするようにしています。

ちなみに、indexは正しく、SetItemDataの直後にGetItemDataしCMyDataのポインタから情報を取得できることを確認しています。



しかし、SetDataXXXが3回ほど呼ばれると、1回目のSetDataXXXでSetItemDataしたポインタ情報が変更されてしまいます。
原因は、CArray::Addの
  m_myInfo.Add(myData)
で、そのなかでCallされているSetAtGrowのようです。


どなたか回避方法をご教授ください。  よろしくお願いいたします。

編集 削除
tetrapod  2011-12-13 16:27:15  No: 73035  IP: 192.*.*.*

CArray (や std::vector) は追加削除を行うと中身が再配置されるという仕様。
再配置がイヤなら事前にサイズを指定して使うべきもの。
http://msdn.microsoft.com/ja-jp/library/4h2f09ct.aspx

そもそもなぜに「ポインタ」を保持しちゃうの?コンテナ使う意味が台無し。
CArray を使うのであればインデックスを保持するべきだと思うぞ。

再配置がイヤで、ポインタが必要で、連続アクセスが必要ないなら
CArray でなく CList (や std::list) を使え、ということで。

編集 削除
Blue  2011-12-13 17:15:54  No: 73036  IP: 192.*.*.*

もともとポインタでリストに格納しては?
(remove時にdeleteは必要)

CTypedPtrArray<CPtrArray, CMyData*> m_myInfo;


CMyData* myData = new CMyData();
m_myInfo.Add(myData);
m_list.SetItemData(index, (DWORD_PTR)myData);


>CArray を使うのであればインデックスを保持するべきだと思うぞ。
賛成

編集 削除
仲澤@失業者  2011-12-13 17:23:18  No: 73037  IP: 192.*.*.*

まぁ、tetrapodさんの指摘が普通なんです。
Addを繰り返すとCArray::m_pDataがアロケーションされ直される
のですよ。足りなくなって(vv;)。

けど、実は自分はPenさんと同様の方法を使ってます。
では、なんで問題が無いかと言うと

  1.事前に必要な全てのm_myInfo.Add(myData)を実行してから
  2.全てのアイテムデータの設定
   for(i=0 ; i<m_myInfo.GetCount() ; i++){
       list.SetItemData(・・・, &m_myInfo[ i]);}

してるのですね。
m_myInfoの要素数はリストコントロールの寿命に対して
固定値であると言う前提ですね。

さて、では可変にするにはどうするかと言うと、
  1.CListCtrlの全てのアイテムを削除する
  2.m_myInfoの追加又は削除を行う
  3.全てのアイテムの追加とアイテムデータの設定を行う
という手順にします。

編集 削除
Pen  2011-12-13 17:34:57  No: 73038  IP: 192.*.*.*

なるほど、よく分かりました。

事前にデータサイズを格納しておくことで解決しました。

ありがとうございました。

編集 削除
gak  2011-12-13 18:15:15  No: 73039  IP: 192.*.*.*

データの管理を自分(m_myInfo)で行わずに CListCtrl に任せる方法もケースによってはアリなので紹介。

  void ***::SetDataXXX(int index) {
      CMyData *myData = m_list.GetItemData(index);
      if (myData == NULL) {
          myData = new CMyData;
          m_list.SetItemData(index, myData)
      }
      // myData に適当なデータをSetする
  }

  BEGIN_MESSAGE_MAP(***, ...)
      ON_NOTIFY(LVN_DELETEITEM, m_list.GetDlgItemID(), &***::onDeleteItem)
  END_MESSAGE_MAP()
  
  void ***::OnDeleteItem(NMHDR *hdr, LRESULT *result) {
      NM_LISTVIEW *lv = (NM_LISTVIEW*)hdr;
      if (lv->lParam != NULL) {
          delete reinterpret_cast<CMyData*>(lv->lParam);
      }
      *result = 0;
  }

編集 削除
仲澤@失業者  2011-12-13 18:50:42  No: 73040  IP: 192.*.*.*

gak さんの方法も一計ではあるのですが、この場合

  1.CListCtrlの「HWND」の寿命の範囲でしか、データが有効でない

といった点に注意が必要ですね。つまりウインドウを消してからでは、
データ保管はできないので、保管部分のデータは別立てにすべき
ということですね。

編集 削除