[MFC] Cum sincronizez un combo sau listbox cu un array?

Despre MFC, ATL si alte biblioteci C++ de la Microsoft (forum moderat)
Post Reply
User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

[MFC] Cum sincronizez un combo sau listbox cu un array?

Post by Ovidiu Cucu » 18 Nov 2007, 16:41

Problema
Am intr-o clasa document un array de obiecte care de exemplu tine fiecare cate un nume si o descriere.

Code: Select all

 // MyDocument.h
class CMyDocument : public CDocument
{
// ...
// Attributes
protected:
   CMyObjectArray m_myObArray;
// Operations
public:
   const CMyObjectArray& GetArray() const {return m_myObArray;}
// Implementation
protected:
   void FillArray();
// ...
};

Code: Select all

// MyDocument.cpp
void CMyDocument::FillArray()
{
   // Note: next is just an example; 
   // in the real world can be, let's say, records from a database.
   m_myObArray.Add(new CMyObject(_T("DOG"), _T("Text for dog...")));
   m_myObArray.Add(new CMyObject(_T("BEAR"), _T("Text for bear...")));
   m_myObArray.Add(new CMyObject(_T("COW"), _T("Text for cow...")));
   m_myObArray.Add(new CMyObject(_T("CAT"), _T("Text for cat...")));
}
BOOL CMyDocument::OnNewDocument()
{
// ...
   FillArray();
   return TRUE;
}
Unde

Code: Select all

class CMyObject : public CObject
{
   CString m_strName;
   CString m_strText;
public:
   CMyObject(LPCTSTR pszName, LPCTSTR pszText) 
      : m_strName(pszName),
        m_strText(pszText) 
   {}

   CString GetName() const {return m_strName;}
   void GetText(CString& strText) const {strText = m_strText;}
};

typedef CTypedPtrArray <CObArray, CMyObject*> CMyObjectArray;
Intr-un formview, am un control combobox (sau listbox) care este umplut cu numele corespunzatoare.
In acelasi timp am si un edit care trebuie sa afiseze descrierea pentru numele curent selectat.

Code: Select all

 // MyFormView.h
class CMyFormView : public CFormView
{
// Attributes
protected:
   CComboBox  m_comboNames;
   CString    m_strText;
// ...
// Implementation
protected:
   CMyDocument* GetDocument();
   void FillComboBox();
// ...
};

Code: Select all

// MyFormView.cpp
void CMyFormView::OnInitialUpdate()
{
// ...
   FillComboBox();
//...
}

void CMyFormView::FillComboBox()
{
   m_comboNames.ResetContent();
   const CMyObjectArray& arr = GetDocument()->GetArray();
   const int nSize = arr.GetSize();
   for(int nIndex = 0; nIndex < nSize; nIndex++)
   {
      CMyObject* pObj = arr.GetAt(nIndex); 
      m_comboNames.AddString(pObj->GetName());
   }
}
A mai ramas sa sincronizez combobox-ul cu array-ul din document adica sa afisez textul corespunzator pentru numele selectat din combo (in exemplul de mai sus "Text for dog..." daca am selectat "DOG", s.a.m.d).
Simplu, handluiesc mesajul de notificare CBN_SELCHANGE (LBN_SELCHANGE la listbox) in care iau indexul item-ului curent selectat, indexez in array-ul din document sa extrag elementul corespunzator, iau textul si il pun in edit.

Code: Select all

void CMyFormView::OnSelchangeMyCombo() 
{
   m_strText.Empty(); // empty the edit control text
   int nCurSel = m_comboNames.GetCurSel();
   if(CB_ERR != nCurSel)
   {
      const CMyObjectArray& arr = GetDocument()->GetArray();
      // get the text from the corresponding element
      CMyObject* pObj = arr.GetAt(nCurSel);
      pObj->GetText(m_strText);
   }
   // put the new text in the edit control
   UpdateData(FALSE); 
}
Numai ca... hmmm... la "BEAR" apare descrierea pentru dog, la "CAT" pentru bear, etc. Ugh!

Cauza
Cauza problemei porneste de la faptul ca cel mai probabil combo-ul este sortat, adica are stilul CBS_SORT (LBS_SORT pentru listbox). In acest fel CComboBox::AddString nu adauga item-uri in ordinea in care ne-am fi asteptat, deci indexul item-ului nu mai corespunde cu cel din array.

Rezolvare
Pentru a scapa de dureri de cap fie ca avem combo (lisbox) sortat sau nu, memoram indexul array-ului cu CComboBox::SetItemData si il extragem atunci cand avem nevoie cu ajutorul lui CComboBox::GetItemData.

Code: Select all

void CMyFormView::FillComboBox()
{
   m_comboNames.ResetContent();
   const CMyObjectArray& arr = GetDocument()->GetArray();
   const int nArraySize = arr.GetSize();
   for(int nArrayIndex = 0; nArrayIndex < nArraySize; nArrayIndex++)
   {
      CMyObject* pObj = arr.GetAt(nArrayIndex); 
      CString strText = pObj->GetName();
      const int nComboIndex = m_comboNames.AddString(strText);
      m_comboNames.SetItemData(nComboIndex, nArrayIndex);
   }

Code: Select all

void CMyFormView::OnSelchangeMyCombo() 
{
   m_strText.Empty(); // empty the edit control text
   const CMyObjectArray& arr = GetDocument()->GetArray();

   // get the current combobox selection
	const int nCurSel = m_comboNames.GetCurSel();
   if(CB_ERR != nCurSel)
   {
      // get back the array index from the item data
      const int nArrayIndex = m_comboNames.GetItemData(nCurSel);
      CMyObject* pObj = arr.GetAt(nArrayIndex);
      pObj->GetText(m_strText);
   }
   UpdateData(FALSE);
}
Note
  1. Problema este "banala" si probabil majoritatea o cunosc. Totusi s-a intamplat, mai demult la o aplicatie complexa: intr-o clasa de baza se implementase o astfel de sincronizare (ce-i drept un pic mai complicata) bazata pe indexul din control. Nu orice muritor din firma avea dreptul sa umble in modulele de baza, babanul care avea era plecat (probabil in concediu), iar clientul fluiera a paguba... Asa ca noi cei de la munca de jos a trebuit sa umblam in resurse la N module si sa scoatem stilul CBS_SORT si LBS_SORT la cateva sute de combo-uri si listbox-uri. Nu stiu ce s-ar fi intamplat daca clientul ar fi tinut mortis ca listele alea sa ramana sortate... :)
  2. Pe langa problema, dar in contextul exemplului de mai sus: nu uitati niciodata sa faceti "curatenie la locul de munca" ;)!

    Code: Select all

    void CMyDocument::FaCuratenieLaLoculDeMunca()
    {
       const int nArraySize = m_myObArray.GetSize();
       for(int nArrayIndex = 0; nArrayIndex < nArraySize; nArrayIndex++)
       {
          CMyObject* pObj = m_myObArray.GetAt(nArrayIndex);
          delete pObj;
       }
       m_myObArray.RemoveAll();
    }
    void CMyDocument::FillArray()
    {
       FaCuratenieLaLoculDeMunca();
    // ...
    }
    void CMyDocument::OnCloseDocument() 
    {
       FaCuratenieLaLoculDeMunca();
    // ...
    }
<< Back to MFC index



Post Reply