環境: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のようです。
どなたか回避方法をご教授ください。 よろしくお願いいたします。
CArray (や std::vector) は追加削除を行うと中身が再配置されるという仕様。
再配置がイヤなら事前にサイズを指定して使うべきもの。
http://msdn.microsoft.com/ja-jp/library/4h2f09ct.aspx
そもそもなぜに「ポインタ」を保持しちゃうの?コンテナ使う意味が台無し。
CArray を使うのであればインデックスを保持するべきだと思うぞ。
再配置がイヤで、ポインタが必要で、連続アクセスが必要ないなら
CArray でなく CList (や std::list) を使え、ということで。
もともとポインタでリストに格納しては?
(remove時にdeleteは必要)
CTypedPtrArray<CPtrArray, CMyData*> m_myInfo;
CMyData* myData = new CMyData();
m_myInfo.Add(myData);
m_list.SetItemData(index, (DWORD_PTR)myData);
>CArray を使うのであればインデックスを保持するべきだと思うぞ。
賛成
まぁ、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.全てのアイテムの追加とアイテムデータの設定を行う
という手順にします。
なるほど、よく分かりました。
事前にデータサイズを格納しておくことで解決しました。
ありがとうございました。
データの管理を自分(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;
}
gak さんの方法も一計ではあるのですが、この場合
1.CListCtrlの「HWND」の寿命の範囲でしか、データが有効でない
といった点に注意が必要ですね。つまりウインドウを消してからでは、
データ保管はできないので、保管部分のデータは別立てにすべき
ということですね。
ツイート | ![]() |