26
Sep
2008

O aplicatie poate integra in interfata grafica asociata oricare dintre controalele Windows, standard sau comune, dar un control derivat poate evidentia, in plus, alte elemente relevante utilizatorului. Un control derivat poate extinde functionalitatea unui control existent completand-i sau adaugandu-i noi functii de afisare si de raspuns la mesajele asociate.

Introducere

Visual C++ Feature Pack este un packet aditional la Visual Studio 2008 Standard (sau orice versiune superioara) ce include actualizari majore pentru MFC cu extensii pentru librariile existente si implementarea TR1 ca set de adaugari la standardul C++0x.

Pe site-uri precum www.codeproject.com sau www.codeguru.com sunt prezentate diferite implementari pentru controale derivate din controalele standard sau comune, iar acum Microsoft incearca sa standardizeze o mare parte dintre acestea incluzandu-le ca packet aditional la pachetul de dezvoltare Visual Studio 2008.

Noul pachet vine impreuna cu un set de exemple printre care si NewControls care prezinta cateva dintre caracteristicile noilor controale introduse. Controlul CMFCPropertyGridCtrl este unul dintre cele mai interesante, tocmai prin posibilitatea de a concentra mai multe informatii intr-o form bine organizata, arborescenta, utila, de exemplu, pentru definirea unui set de optiuni la o aplicatie. Acest control este preluat ca si concept, din Borland Delphi (Object Inspector).

Controlul permite introducerea de valori pentru proprietti prin asocierea anumitor controale cu acestea. Controlul CDateTimeCtrl si CIPAddressCtrl nu sunt suportate, in acest scop, de catre control, iar pentru asigurarea compatibilitatii cu acesta este nevoie de constructii specifice.

Acest articol prezinta o solutie privind extinderea functionalitatii controlului CMFCPropertyGridCtrl cu suport pentru proprietti de tip data / timp si adresa IP. La crearea noilor tipuri de proprietti am "imprumutat" cateva din tehnicile utilizate la implementarea controlului CSpinCtrl ca suport pentru introducerea de date in controlul CMFCGridPropertyCtrl.

Daca nu va intereseaza detaliile de implementare pentru noile proprietati, in sectiunea "Utilizare" sunt prezintate modalitati de inserare, setare si extragere valori din proprietati de acest tip.

Detalii de implementare

CMFCPropertyGridDateTimeProperty

CDateTimeCtrl incapsuleaza functionalitatea unui control de tip alegere data si timp. Ofera suport pentru asocierea valorii curente din control cu un obiect de tip COleDateTime, CTime sau SYSTEMTIME. Setarea valorii unei proprietati in controlul CMFCPropertyGridCtrl se realizeaza prin intermediul unui obiect de tip _variant_t care incapsuleaza functionalitatea unui tip de data VARIANT. Legatura dintre COleDateTime si _variant_t se realizeaza prin intermediul operatorului COleDateTime::operator DATE. Astfel, din punct de vedere functional, un control CDateTimeCtrl si o proprietate de tip CMFCPropertyGridProperty pot fi asociate fara constructii suplimentare.

Clasa CMFCPropertyGridDateTimeProperty, derivata din CMFCPropertyGridProperty suprascrie cateva din functiile clasei de baza si incapsuleaza noi date membre specifice stilului unui control CDateTimeCtrl plus formatul in care este prezentata data sau timpul.

class CMFCPropertyGridDateTimeProperty : public CMFCPropertyGridProperty
{
      DECLARE_DYNAMIC(CMFCPropertyGridDateTimeProperty)

      BOOL m_style;
      BOOL m_updown;
      CString m_setformat;
      CString m_format;

public:
      CMFCPropertyGridDateTimeProperty(const CString& strName, COleDateTime &nValue,
                  LPCTSTR lpszDescr = NULL, DWORD dwData = 0, BOOL style = TRUE,
                  BOOL updown = FALSE, LPCTSTR setFormat = NULL, LPCTSTR format = NULL);
 
      virtual BOOL OnUpdateValue();
      virtual CString FormatProperty();
      virtual CString FormatOriginalProperty();

protected:
      virtual CWnd* CreateInPlaceEdit(CRect rectEdit, BOOL& bDefaultFormat);
      virtual BOOL OnSetCursor() const { return FALSE; /* Use default */ }
      BOOL IsValueChanged() const;
};

Constructorul utilizat este similar cu cel din clasa de baza la care se adauga parametrii specifici datelor membre noi introduse.

La activarea unei proprietati se creeaza controlul asociat prin intermediul functiei CreateInPlaceEdit, se seteaza stilul acestuia in functie de parametrii specificati, forma de prezentare a datei sau a timpului si se atribuie valoarea initiala.

Clasa CPropDateTimeCtrl utilizata este derivata din CDateTimeCtrl pentru interactionarea corecta a controlului asociat proprietatii cu fereastra parinte.

CWnd* CMFCPropertyGridDateTimeProperty::CreateInPlaceEdit(CRect rectEdit,
                                                          BOOL& bDefaultFormat)
{
      CPropDateTimeCtrl* pWndDateTime = new CPropDateTimeCtrl(this,
                                                              m_pWndList->GetBkColor());

      rectEdit.InflateRect(4,2,0,3);

      DWORD style=WS_VISIBLE | WS_CHILD;
      if(m_style==TRUE)
            style|=DTS_SHORTDATEFORMAT|m_updown ? DTS_UPDOWN : 0;
      else
            style|=DTS_TIMEFORMAT|DTS_UPDOWN; 

      pWndDateTime->Create(style, rectEdit, m_pWndList, AFX_PROPLIST_ID_INPLACE);

      if(!m_setformat.IsEmpty())
            pWndDateTime->SetFormat(m_setformat);

      pWndDateTime->SetTime(m_varValue);

      bDefaultFormat = TRUE;

      return pWndDateTime;

}

Controlul CMFCPropertyGridCtrl capteaza toate mesajele destinate lui si fiecarui control activ asociat unei proprietati. Verifica daca un mesaj este asociat cu un asfel de control si i-l paseaza in caz afirmativ. La randul lui controlul asociat trebuie ss paseze, daca este cazul, masejele ferestrei "copil" corespunzatoare. CDateTimeCtrl este un control complex care este "parinte" pentru mai multe ferestre "copil". Pentru fiecare mesaj asociat interactiunii cu mouse-ului este cautata ferastra "copil" care trebuie sa-l primesca.

LRESULT CPropDateTimeCtrl::WindowProc(UINT message, WPARAM wParam, LPARAM lParam)
{
      if(message&ht;=WM_MOUSEFIRST && message<=WM_MOUSELAST)
      {

            POINT pt;
            GetCursorPos(&pt);

            CWnd *wnd=NULL;
            CRect rect;
            wnd=GetWindow(GW_CHILD);

            while(wnd)
            {
                  wnd->GetWindowRect(rect);
                  if(rect.PtInRect(pt))
                  {
                        wnd->SendMessage(message,wParam,lParam);
                        return TRUE;
                  }

                  wnd=wnd->GetWindow(GW_HWNDNEXT);
            }
      }

      if(message==WM_DESTROY)
            SetFont(NULL,FALSE);

      return CDateTimeCtrl::WindowProc(message, wParam, lParam);
}

Functiile OnUpdateValue si IsValueChanged permit notificarea detinatorului (owner-ului) controlului in cazul schimbarii valorii curente a proprietatii si determina daca valoarea introdusa a fost modificata.

Functiile FormatProperty si FormatOriginalProperty formateaza valoarea curenta a datei sau a timpului intr-un sir de caractere. Daca nu se specifica un format de afisare pentru proprietate la initializarea controlului Property Grid, atunci este utilizat formatul implicit pentru data, respectiv timp.

BOOL CMFCPropertyGridDateTimeProperty::OnUpdateValue()
{
      ASSERT_VALID(this);
      ASSERT_VALID(m_pWndInPlace);
      ASSERT_VALID(m_pWndList);
      ASSERT(::IsWindow(m_pWndInPlace->GetSafeHwnd()));

      COleDateTime lCurrValue = COleDateTime(m_varValue.date);
      CDateTimeCtrl* pProp = (CDateTimeCtrl*) m_pWndInPlace;

      COleDateTime datetime;
      pProp->GetTime(datetime);
      m_varValue.date = datetime;

      if (lCurrValue != COleDateTime(m_varValue.date))
            m_pWndList->OnPropertyChanged(this);

      return TRUE;
}

BOOL CMFCPropertyGridDateTimeProperty::IsValueChanged() const
{
      ASSERT_VALID(this);

      if (m_varValueOrig.vt != m_varValue.vt)
            return FALSE;

      const COleDateTime var(m_varValue);
      const COleDateTime var1(m_varValueOrig);

      if(m_varValue.vt==VT_DATE)
            return var != var1;

      return FALSE;
}

CString CMFCPropertyGridDateTimeProperty::FormatProperty()
{
      CString strVal;

      if(m_format.IsEmpty())
      {
            setlocale(LC_ALL, "");
            strVal = COleDateTime(m_varValue).Format(m_style?_T("%x"):_T("%X"));
      }
      else
            strVal = COleDateTime(m_varValue).Format(m_format);

      return strVal;
}

CString CMFCPropertyGridDateTimeProperty::FormatOriginalProperty()
{
      CString strVal;
      if(m_format.IsEmpty())
      {
            setlocale(LC_ALL, "");
            strVal = COleDateTime(m_varValueOrig).Format(m_style?_T("%x"):_T("%X"));
      }
      else
            strVal = COleDateTime(m_varValueOrig).Format(m_format);

      return strVal;
}

CMFCPropertyGridIPAdressProperty

CIPAddressCtrl asigura functionalitatea unui control comun similar cu un control de editare care permite setarea unei adrese IP in format numeric. Ofera suport pentru asocierea valorii curente din control cu tip de data DWORD. Structura ULONG_VARIANT este definita pentru introducerea unui tip de data ULONG intr-o structura VARIANT prin intermediul unui constructor, necesara la implementarea constuctorului clasei CMFCPropertyGridIPAdressProperty. Clasa CMFCPropertyGridIPAdressProperty este construita similar ca si CMFCPropertyGridDateTimeProperty suprascriindu-se cateva din functiile clasei de baza.

struct ULONG_VARIANT: public VARIANT
{
      ULONG_VARIANT(ULONG val)
      {
            vt=VT_UI4;
            ulVal=val;
      }
};

class CMFCPropertyGridIPAdressProperty : 
      public CMFCPropertyGridProperty
{
      DECLARE_DYNAMIC(CMFCPropertyGridIPAdressProperty)

public:
      CMFCPropertyGridIPAdressProperty(
         const CString& strName, 
         in_addr &nValue, 
         LPCTSTR lpszDescr = NULL, 
         DWORD dwData = 0);

      virtual BOOL OnUpdateValue();
      virtual CString FormatProperty();
      virtual CString FormatOriginalProperty();

protected:
      virtual CWnd* CreateInPlaceEdit(CRect rectEdit, BOOL& bDefaultFormat);
      virtual BOOL OnSetCursor() const { return FALSE; /* Use default */ }
};

CMFCPropertyGridIPAdressProperty::CMFCPropertyGridIPAdressProperty(
         const CString& strName,
         in_addr &nValue, 
         LPCTSTR lpszDescr, 
         DWORD dwData):
      CMFCPropertyGridProperty(strName, ULONG_VARIANT(nValue.s_addr), lpszDescr, dwData)
{}

Functia CreateInPlaceEdit() creeaza controlul si seteaza valoarea curenta. O adresa IP poate fi stocata intr-o variabila de tip in_addr care poate fi convertita la un tip VARIANT prin setarea tipului VT_UI4 si completarea campului ulVal. Clasa CPropIPAddressCtrl este derivata din CIPAddressCtrl pentru interactionarea corecta a controlului asociat proprietatii cu fereastra parinte.

CWnd* CMFCPropertyGridIPAdressProperty::CreateInPlaceEdit(
   CRect rectEdit, BOOL& bDefaultFormat)
{
      CPropIPAddressCtrl* pWndIPAddress =
                  new CPropIPAddressCtrl(this,m_pWndList->GetBkColor());

      rectEdit.InflateRect(4,2,0,3);

      DWORD style=WS_VISIBLE | WS_CHILD;

      pWndIPAddress->Create(style, rectEdit,
                            m_pWndList, AFX_PROPLIST_ID_INPLACE);
      pWndIPAddress->SetFont(m_pWndList->GetFont(),FALSE);
      pWndIPAddress->EnableSetFont(FALSE);
      pWndIPAddress->SetAddress(m_varValue.ulVal);

      bDefaultFormat = TRUE;

      return pWndIPAddress;
}

Functia IsValueChanged nu mai este suprascrisa deoarece clasa de baza asigura suport pentru tipul VARIANT VT_UI4 in functia specificata. Celelalte functii sunt suprascrise pentru asigurarea legaturii dintre controlul CIPAddressCtrl si variabila de tip ULONG in care este stocata adresa IP.

BOOL CMFCPropertyGridIPAdressProperty::OnUpdateValue()
{
      ASSERT_VALID(this);
      ASSERT_VALID(m_pWndInPlace);
      ASSERT_VALID(m_pWndList);
      ASSERT(::IsWindow(m_pWndInPlace->GetSafeHwnd()));

      in_addr lCurrValue;
      lCurrValue.s_addr = m_varValue.ulVal;

      CPropIPAddressCtrl* pProp = (CPropIPAddressCtrl*) m_pWndInPlace;

      pProp->GetAddress(m_varValue.ulVal);
      m_varValue.ulVal=ntohl(m_varValue.ulVal);

      if (lCurrValue.s_addr != m_varValue.ulVal)
            m_pWndList->OnPropertyChanged(this);

      return TRUE;
}

CString CMFCPropertyGridIPAdressProperty::FormatProperty()
{
      CString strVal;
      in_addr address;

      address.s_addr=m_varValue.ulVal;
      strVal=inet_ntoa(address);

      return strVal;
}

CString CMFCPropertyGridIPAdressProperty::FormatOriginalProperty()
{
      CString strVal;
      in_addr address;

      address.s_addr=m_varValueOrig.ulVal;
      strVal=inet_ntoa(address);

      return strVal;
}