30
Dec
2008

Multe aplicatii permit customizarea diverselor obiecte si reprezentari ale aplicatiilor. Un astfel de exemplu este si binecunoscutul Internet Explorer care permite utilizatorului sa particularizeze barile de comenzi rapide (toolbars) ce le are la dispozitie prin intermediul unui meniu de tip popup ce apare in momentul in care acesta executa un click dreapta in zona barilor de comenzi rapide (toolbars).

exemplu Internet Explorer
Vezi imaginea la dimenisunea normala

Un alt exemplu unde un astfel de meniu isi gaseste utilitatea este acela de a customiza partea de afisare si incarcare a datelor in aplicatii ce contin diverse tabele de date incarcate in diverse controale de tip List Control sau Grid. Astfel de aplicatii permit filtrarea datelor si afisarea/ascunderea unor coloane pornind de la un meniu popup apelat printr-un right-click pe controlul respectiv sau header-ul acestuia.

Pornind de la aceasta idee am implementat o clasa CDynamicPopupMenu ce permite construirea si utilizarea unui astfel de meniu, utilizand-o in cadrul unei aplicatii MFC de tip dialog cu un list control.

Aceasta clasa foloseste un container STL std::map, pe o structura ce permite stocarea propietatilor item-urilor meniului, astfel incat in momentul construirii meniului, acesta sa arate si sa se comporte asa cum ne dorim.

Adaugarea elemente de meniu

Metoda de adaugarea a unui element in meniu are urmatoarea definitie:

void AddItemData(const int item_id, const int parent_id, 
                 bool is_visible, bool check_flag, bool has_child, 
                 const std::wstring item_name, bool enable_flag);

unde:

  • item_id: reprezinta ID-ul elementului de meniu, ID dupa care se detecteaza si metoda/codul de tratare a selectiei/deselectiei;
  • parent_id: este ID-ul item-ului parent de la care s-a pornit un nou group de item-uri (drop-down menu); daca item-ul respectiv e parte a meniului initial, parent_id-ul acestuia va fi 0;
  • is_visible: acest flag este folosit pentru a seta selectarea/deselectarea (bifarea) acestui item, item ce are efect de interactiune cu aplicatia ce-l utilizeaza. In cazul aplicatiei de test, acest flag este true pentru toate item-urile aferente coloanelor ce dorim a fi afisate. In cazul item-urilor de tipul Select All / Checked All pentru ca nu dorim sa inseram astfel de coloane, il punem pe false.
  • check_flag: este flag-ul care permite bifarea/debifarea unui item din meniu;
  • has_child: daca este true, permite definirea unui group de item-uri (drop-down menu);
  • item_name: defineste numele item-ului din meniu in format unicode;
  • enable_flag: defineste daca un item din meniu este activat sau dezactivat ( enable / disable).

Adaugarea unui element separator

Metoda de adaugare a unui element separator este:

void AddItemSeparator(const int item_id, const int parent_id);

unde:

  • item_id: reprezinta ID-ul item-ului din meniu, ID dupa care se detecteaza si metoda/codul de tratare a selectiei/deselectiei;
  • parent_id: este ID-ul item-ului parent de la care se s-a pornit un nou group de item-uri (drop-down menu); daca item-ul respectiv e parte a meniului initial, parent_id-ul acestuia va fi 0.

Exemplu de adaugare a item-urilor si separatorilor

In cadrul aplicatiei de test, in cadrul metodei CtestPopupMenuDlg::SetDefaultMapValues(void), printre altele, se regasesc urmatoarele apeluri:

m_pCustPPMenu->AddItemData(MI_MAINITEM_1, 0, true, true, false, _T("Item 1"), true);                       

m_pCustPPMenu->AddItemSeparator(MI_MAIN_SEP1,0);

m_pCustPPMenu->AddItemData(MI_MAINITEM_4_GROUP_1, 0, true, true, true,   
                                                         _T("Group 1"), true);

m_pCustPPMenu->AddItemData(MI_GROUP_1_SUBITEM_1, MI_MAINITEM_4_GROUP_1,
                             false, false, false, _T("G1-Select All"), true);

m_pCustPPMenu->AddItemData(MI_GROUP_1_SUBITEM_2, MI_MAINITEM_4_GROUP_1, 
                                 true, false, false, _T("G1-Item 12"), true);

Obtinerea parametrilor interni ai meniului

Pentru a avea acces la containerul intern in care sunt salvati parametrii interni ai meniului dinamic, e nevoie de folosirea metodei

DynamicMenuData* GetMenuData();

dupa cum urmeaza:

DynamicMenuData* pItemsMap = m_pCustPPMenu->GetMenuData();

Crearea si afisarea meniului

Creearea meniului se face dupa adaugarea elementelor acestuia si la apelarea metodei TrackPopupCustomMenu() . Definitia acestei metode este:

DWORD TrackPopupCustomMenu(POINT point, HWND hWnd);

unde:

  • point: reprezinta coodronatele mouse-ului in locul unde se creeaza meniul;
  • hWnd: handler-ul ferestrei parinte unde se creeaza meniul.

Valoarea intoarsa de aceasta metoda este valoarea ID-ului itemului selectat din meniu.

In cadrul aplicatiei de test, crearea meniului sa se realizese pe metoda de tratare a evenimentului de right-click pe controlul de tip list, utilizat ( NM_RCLICK ).

void CtestPopupMenuDlg::OnNMRclickList1(NMHDR *pNMHDR, LRESULT *pResult)
{
	POINT		point;
	::GetCursorPos(&point);

	int nSelItem = m_pCustPPMenu->TrackPopupCustomMenu(point, m_ctrlList.m_hWnd);

	if (0 < nSelItem)
	{
		pNMHDR->hwndFrom	= m_ctrlList.m_hWnd;
		pNMHDR->idFrom	= nSelItem;
		pNMHDR->code	= WM_NOTIFY;

		OnNotify( 0, (LPARAM)pNMHDR, pResult);
	}
	
	*pResult = 0;
}

Se observa apelul metodei TrackPopupCustomMenu(), folosind punctul in care mouse-ul este cand s-a facut apelul metodei OnNMRclickList1() si handler-ul controlului list.

Daca s-a selectat un item din meniu, se apeleaza metoda OnNotify(), metoda ce primeste ca parametru si un pointer la o structura de notifcare a mesajelor, NMHDR. In cadrul acestei structuri se salveaza ID-ul item-ului selectat.

Folosind mesajul WM_NOTIFY si metoda OnNotify() se informeaza fereastra parinte a controlului ca un eveniment a avut loc in cadrul meniului.

BOOL CtestPopupMenuDlg::OnNotify(WPARAM wParam, LPARAM lParam, LRESULT* pResult)
{
  NMHDR *p = (NMHDR*) lParam;
  _ASSERT(p);
  _ASSERT(m_pCustPPMenu);
  bool bFlag = false;

  switch (p->idFrom)
  {
  case MI_MAINITEM_1:
    m_pCustPPMenu->GetItemCheckedFlag(MI_MAINITEM_1, bFlag);
    m_pCustPPMenu->SetCheckedItemFlag(MI_MAINITEM_1, !bFlag);
    FillData();
    break;
  case MI_MAINITEM_2:
    m_pCustPPMenu->GetItemCheckedFlag(MI_MAINITEM_2, bFlag);
    m_pCustPPMenu->SetCheckedItemFlag(MI_MAINITEM_2, !bFlag);
    FillData();
    break;

    // =======================
    //  metoda aceasta contine tratarea si a altor item-uri ale meniului 
    // =======================
		
  default:
    break;
  }
	
  return CDialog::OnNotify(wParam, lParam, pResult);
}

Dupa cum se poate observa se apeleaza metoda GetItemCheckedFlag() pentru a detecta starea de selectie (bifat/nebifat) a item-ului dat prin ID. Apoi, pe starea detectata se aplica negatia si se apeleaza metoda SetCheckedItemFlag(). Ulterior, apelez metoda ce produce schimbari la nivelul list-controlului, in functie de actiunea exercitata asupra meniului (metoda FillData()).

Interactiunea meniului cu fereastra parinte (controlul List)

In cadrul aplicatei de test, am decis ca interactiunea dintre meniul dinamic creat si controlul de tip list sa se realizeze in cadrul metodei FillData().

Pentru a putea folosii informatiile salvate anterior in containerul intern al clasei CDynamicPopupMenu e nevoie de initializarea unui pointer de tip DynamicMenuData cu adresa returnata de metoda GetDynamicMenuData().

void CtestPopupMenuDlg::FillData()
{
	_ASSERT(m_pCustPPMenu);

	DynamicMenuData *pItemsMap = m_pCustPPMenu->GetDynamicMenuData();

	int nCol = 0;

	if ((NULL != pItemsMap) && (!pItemsMap->empty()))
	{
		// reset columns
		int nColumnCount = m_ctrlList.GetHeaderCtrl()->GetItemCount();
		for (int i=0; i < nColumnCount;i++)
			m_ctrlList.DeleteColumn(0); 

		for (iterDynMenu itm = pItemsMap->begin(); 
                                          itm != pItemsMap->end(); ++itm)
		{
			if (m_pCustPPMenu->GetIsVisible(itm) &&
                                     m_pCustPPMenu->GetIsChecked(itm))
			{
			   m_ctrlList.InsertColumn(nCol++, 
                          (*itm).second.sItemName.c_str(), LVCFMT_LEFT,  70);
			}
		}
	}
}

Folosind pointerul la containerul intern al meniului, iteram containerul intern, iar pentru acele item-uri care au flag-urile de vizibilitate si selectie pe true inseram coloane in control list-ul aplicatiei.

In mod asemanator, daca folosind astfel de meniuri, aplicatia poate aplica filtre pe date reale.

Clasa CDynamicPopupMenu contine si alte metode utile pentru customizarea interfetei si a propietatilor meniurilor de tip popup si drop-down. Ele pot fi folosite in diverse situatii, pentru a schimba comportamentul unor astfel de meniuri, cerute de aplicatii reale.