Problema ON_NOTIFY_REFLECT_EX

Intrebari legate de programarea cu biblioteci precum MFC, ATL, WTL si GDI+.
mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Problema ON_NOTIFY_REFLECT_EX

Post by mesajflaviu » 28 Jun 2011, 12:41

Intr-o aplicatie MDI, cu CMDIChildWnd impartit in mai multe view-uri (un fel de windows explorer), am o problema ciudata la un view (derivat din CListView). Incerc sa reflectez un mesaj (OnColumnClick(...)), dar nu stiu de ce il "receptionez" de doua ori la un singur click ( o performanta, nu gluma :biggrin: ). De ce ? Mentionez ca aceasta problema o am la reflectarea oricarui mesaj ... ( am incercat OnClick(...), etc. ), si nu folosesc nici un control custom de tip lista, doar cel standard, adica GetListCtrl().. Atasez o mica aplicatie de test, in mod debug se poate vedea TRACE-ul la click-ul pe header-ul list-ului.
Attachments
Test2.zip
(90.85 KiB) Downloaded 250 times



User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Problema ON_NOTIFY_REFLECT_EX

Post by Ovidiu Cucu » 28 Jun 2011, 18:55

Incep cu o nota: un mesaj de notificare nu e obligatoriu trimis o singura data.
De exemplu, daca am un control listview cu selectie multipla si userul selecteaza N itemuri, atunci LVN_ITEMCHANGED va fi trimis de cel putin N ori. Trebuie ca sa te uiti in structura NMLISTVIEW ca sa filtrezi pe alea care te intereseaza.
Totusi, notificari ca LVN_COLUMNCLICK sunt, logic, trimise o singura data parintelui pentru un click pe header.

Sa mai vedem o data ce si cum cu mesajele de notificare reflectate.
Sa spunem ca am un control listview intr-un dialog. In clasa dialogului parinte (sa-i zicem CParentDialog) mapez LVN_COLUMNCLICK cu ON_NOTIFY. Normal, la un click pe headerul unei coloane programul va intra in handlerul corespunzator din CParentDialog (este procedura standard in Windows: mesajele de notificare sunt trimise de la control la parinte).

MFC-ul, asa cum am mai spus si mai demult "reflecta" aceste notificari spre control pentru o abordare un pic mai obiectuala, adica sa poata fi tratate direct in clasa controlului.
Acuma, daca mapez si mesajul reflectat (=LVN_COLUMNCLICK) in clasa controlului (sa-i zicem CChildListCtrl) cu ON_NOTIFY_REFLECT, MFC-ul va chema mai intai handlerul din CChildListCtrl si NU il va mai chema pe cel din CParentDialog. Din acest punct de vedere, ce pun in *pResult, NU conteaza (e doar o valoare folosita mai departe in procedura default a dialogului parinte).

In schimb, daca folosesc ON_NOTIFY_REFLECT_EX (cu _EX in coada), handlerul din CChildListCtrl intoarce un BOOL care imi da posiblitatea sa aleg daca handlerul din CParentDialog sa fie apelat si el sau nu.
Daca intorc FALSE, este apelat si handlerul din dialogul parinte.
Daca intorc TRUE, nu mai este apelat handlerul din dialogul parinte.
Asta se intampla cu un control listview intr-un dialog.

Atunci cand e vorba de un CListView, probabil ca lucrurile se intampla un pic diferit. Daca mapezi cu ON_NOTIFY_REFLECT_EX, pentru ambele notificari (cea propriuzisa cat si cea reflectata) se apeleaza acelasi handler din clasa derivata din CListView. O fi vreo galma in MFC (cel putin asta se intampla in VC6.0 si n-am incercat inca sa vad ce se intampla in versiuni mai noi).
Cumva ar fi logic. Ar avea rost rost sa tratezi notifcari de la listview in frame sau splitter sau..? Eu cred ca nu.

In concluzie: in CListView NU utiliza acel ON_NOTIFY_REFLECT_EX (n-are rost si e cu bube) ci, asa cum te invata bunul ClassWizard, ON_NOTIFY_REFLECT (fara _EX).

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Problema ON_NOTIFY_REFLECT_EX

Post by mesajflaviu » 28 Jun 2011, 19:17

Hmm ... mi-ar trebui mesajul reflectat cu ON_NOTIFY_REFLECT_EX sa ajunga si la un control copil pana la urma ... sa vad cu pot rezolva ... oricum, multumesc mult pentru raspuns.

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Problema ON_NOTIFY_REFLECT_EX

Post by mesajflaviu » 28 Jun 2011, 20:54

Totusi ceva ramane ciudat ... situatia asta s-ar putea intalni cand :
Atunci cand e vorba de un CListView, probabil ca lucrurile se intampla un pic diferit. Daca mapezi cu ON_NOTIFY_REFLECT_EX, pentru ambele notificari (cea propriuzisa cat si cea reflectata) se apeleaza acelasi handler din clasa derivata din CListView...
dar in acest caz mapez ON_NOTIFY_REFLECT_EX intr-un singur loc, nu in parinte si in copil ... Mai sap ...

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Problema ON_NOTIFY_REFLECT_EX

Post by mesajflaviu » 29 Jun 2011, 09:35

Sa inteleg ca mapand cu ON_NOTIFY_REFLECT_EX trimit notificarea spre un copil, si cu toate ca nu am cod nicaiaeri in alta parte pe acest eveniment, cineva imi raspunde ?

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Problema ON_NOTIFY_REFLECT_EX

Post by Ovidiu Cucu » 29 Jun 2011, 12:38

mesajflaviu wrote:cineva imi raspunde ?
Probabil ca... nu. :D
mesajflaviu wrote:Sa inteleg ca mapand cu ON_NOTIFY_REFLECT_EX trimit notificarea spre un copil, si cu toate ca nu am cod nicaiaeri in alta parte pe acest eveniment
NU.

Hai sa incerc sa repet cateva chestii deja spuse aici si in discutii anterioare, care poate, puse cap a cap lamuresc problema.
Te rog sa citesti cu atentie, char daca poate fi enervanta repetarea anumitor expresii.
  1. In Windows, intotdeauna notificarile se trimit la fereasta parinte atunci cand se intampla ceva in fereastra copil. Am subliniat "intotdeauna", "fereasta" si "parinte".
  2. MFC-ul trateaza mesajul de notificare primit de fereastra parinte si mai intai il "reflecta", adica trimite un mesaj la fereasta copil.
  3. Mesajul reflectat poate fi tratat in clasa C++/MFC ce incapsuleaza fereastra copil. Aceasta este o abordare mai object-oriented decat tratarea notificarilor tuturor copiilor in clasa C++/MFC ce incapsuleaza fereastra parinte. Am subliniat "clasa C++/MFC".
  4. In MFC se pot folosi macro-uri gen ON_MESSAGE, ON_NOTIFY etc, pentru a mapa in clasa C++/MFC mesajele primte de fereastra ce o incapsuleaza la o functie "handler" din acea clasa C++/MFC. Asta in loc de a se scrie la manuta functii fereastra cu switch-uri interminabile ca in raw-WinAPI.
  5. Poti afla ce-i in spatele acelor macro-uri cu F12 si punand breakpoint in handlerul de mesaj apoi mergand pe Call Stack. Vei ajunge la minunati pointeri la functii intr-ul switch pe care MFC-ul l-a scris pentru tine, cu care se apeleaza handerul scris de tine sau de ClassWizard in clasa C++/MFC. Munca de Sisif, daca n-ai ce face sau intradevar te roade curiozitatea, mai ales cand ai de-a face cu mesaje.
  6. ON_NOTIFY_REFLECT, pe care il scrie bunul ClassWizard, mapeaza in clasa C++/MFC ce incapsuleaza fereastra copil un handler pentru mesajul reflectat (de catre MFC). Daca am facut asta... "Aleluia!", hanllerul corespunzator din clasa C++/MFC care incapsuleaza fereastra parinte, NU se mai apeleaza. Pe undeva, in CWnd::OnNotify, MFC-ul zice "eaten by child":

    Code: Select all

       // reflect notification to child window control
       if (ReflectLastMsg(hWndCtrl, pResult))
          return TRUE;        // eaten by child
       // ...
    Ceea ce-i normal sa fie asa si, in majoritatea cazurilor suficient.
  7. ON_NOTIFY_REFLECT_EX pus la mana, iti da sansa ca in functie de valoarea returnata din handlerul din clasa C++/MFC ce incapsuleaza fereastra copil, sa se apeleze si handlerul corespunzator din clasa C++/MFC care incapsuleaza fereastra parinte. De obicei nu e nevoie de asa ceea pentru ca un design corect e cu "ori, ori".
  8. Atunci cand am un cotrol (o fereastra) copil din clasa fereastra "SysListView32" incapsulat intr-o clasa C++/MFC derivata din CListCtrl, de obicei stand intr-o fereastra tip dialog incapsulata intr-o clasa C++/MFC derivata din clasa C++/MFC CDialog, treaba cu mesajele de notificare si mesajele reflectate e clara si functioneaza dupa mecanismul descris la punctele anterioare. Am subliniat "clasa fereastra".
  9. Clasele derivate din CView stau de obicei intr-un frame din framework-ul SDI/MDI. Conform punctului [1], mesajul de notificare este trimis parintelui. Insa framework-ul SDI/MDI ruteaza mesajele frame-ului parinte catre copii/view-uri (cumva invers ca la punctul [2]). De aici problema lui ON_NOTIFY_REFLECT_EX, cu "apelarile duble". Oricum ON_NOTIFY_REFLECT_EX nu face ceea ce presupuneai asa ca In a view, get rid of ON_NOTIFY_REFLECT_EX!
  10. Ca si CListCtrl, CListView incapsuleaza o clasa fereastra "SysListView32". De aceea poti folosi in CListView metodele lui CListCtrl, care in fond si la urma urmei nu fac de cat sa wrapeze SendMessage-uri si functii API pentru control. De asemenea, poti mapa direct aceleasi mesaje reflectate ca si in CListCtrl.
  11. Clasa C++/MFC CListView NU incapsuleaza un obiect tip si CListCtrl. In consecinta, NU se poate ceea ce incearca multi: sa inlocuiesti controlul (fereastra) listview default cu unul "custom/extins incapsulat intr-o clasa derivata din CListCtrl". No reasonable way! Am subliniat "NU" si "No reasonable way!".

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Problema ON_NOTIFY_REFLECT_EX

Post by Ovidiu Cucu » 29 Jun 2011, 13:30

Ca sa rezolvi problema acelei liste custom implementata intr-o clasa derivata din CListCtrl, pe care vrei s-o pui intr-un view dintr-o aplicatie SDI/MDI, sunt doua solutii bune:
  1. Pastrezi acea clasa derivata din CListCtrl si pui lista intr-un form view. Solutie rapida dar cu dezavantajul ca nu mai beneficiezi de exemplu de suport pentru Print si Print Preview. Eu n-as alege solutia asta decat daca lista s-ar afla intr-o biblioteca third-party, deci n-as avea incotro.
  2. Renunti la clasa derivata din CListCtrl si faci toate customizarile direct in clasa derivata din CListView. Nu trebuie decat mutat cod de colo-colo, cel mult adaugat GetListCtrl, acolo unde este nevoie. Simplu, curat, eficient.

Note:
  • Solutii rele pot fi o mie :) printre care si aceea cu membru tip CListCtrlEx in CListViewEx.
  • Atunci cand scrii extensii MFC, MFC-ul toarce ca o matza blanda atata timp cat respecti "calea dreapta MFC". Cum te tii de "hack-uri", mai devreme sau mai tarziu, zgarie rau! ;)
  • Am mai lucrat un pic la proiectul List One si am adaugar si un CListOneView. In plus am scos intr-o clasa separata tot ce tine de customizari si o sa vezi ce frumos se potrivesc toate si la control si la view si la ocx.
    Fara redirectari si incrucisari de mesaje si notificari. :)
    It's coming soon.

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Problema ON_NOTIFY_REFLECT_EX

Post by mesajflaviu » 29 Jun 2011, 14:03

Hai sa incerc sa repet cateva chestii deja spuse aici si in discutii anterioare, care poate, puse cap a cap lamuresc problema.
Te rog sa citesti cu atentie, char daca poate fi enervanta repetarea anumitor expresii.
Daca cineva are rabdarea sa imi explice acelasi lucru in mai multe feluri, asta inseamna ca la mine e problema. :oops:

O sa incerc sa adopt a doua solutie, cea in care sa fac customizarile in clasa derivata din CListVew .... si daca in acest mic proiect de test am 3 listview-uri, poate ar fi mai eficient sa derivez o clasa din CListView cu cutomizarile de rigoare, ca apoi cele 3 clase, CMy1View, CMy2View si CMy3View sa fie derivate din aceasta ...

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Problema ON_NOTIFY_REFLECT_EX

Post by Ovidiu Cucu » 29 Jun 2011, 14:44

mesajflaviu wrote:O sa incerc sa adopt a doua solutie, cea in care sa fac customizarile in clasa derivata din CListVew ....
Good choice!
si daca in acest mic proiect de test am 3 listview-uri, poate ar fi mai eficient sa derivez o clasa din CListView cu cutomizarile de rigoare, ca apoi cele 3 clase, CMy1View, CMy2View si CMy3View sa fie derivate din aceasta ...
Correct!

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Problema ON_NOTIFY_REFLECT_EX

Post by mesajflaviu » 30 Jun 2011, 10:59

Cel mai simplu pentru mine ar fi fost sa iau componente din CListCtrlEx si sa le pun in CListView-ul custom ... dar am vazut ca nu e asa simplu ( sau nu stiu eu ). Nu inteleg, principial vorbind, cum pot lua rutinele de sortare de exemplu din CListCtrlEx si sa le implementez in CListViewEx-ul meu ... sau e vorba de o dezvoltare de la inceput a unui nou CLIstView ?

... alte implementari custom ale CListView am vazut ca au sortare dar nu mai au SetItemData/GetItemData ...

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Problema ON_NOTIFY_REFLECT_EX

Post by Ovidiu Cucu » 30 Jun 2011, 15:43

Pai nu vad ce ar putea fi in CListCtrlEx si nu ai putea muta in CListViewEx, inclusiv sortarea.
Singura diferenta majora e ca, acolo unde se apeleaza metodele CListCtrl,

il loc de
SetItemData
GetItemData
SortItems
s.a.m.d.

pui
CListCtrl& list = GetListCtrl();
list.SetItemData
list.GetItemData
list.SortItems
s.a.m.d.

Functiile callback de sortare ar trebui sa arate identic si la fel, cu mentiunile de mai sus.
Ai intalnit vreo problema mai subtila?

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Problema ON_NOTIFY_REFLECT_EX

Post by mesajflaviu » 01 Jul 2011, 19:32

Am dat peste o problema : nu pot aduce hWnd valid pentru header-ul listview-ul :

Code: Select all

void CListViewExt::PreSubclassWindow()
{
	CListView::PreSubclassWindow();

	HWND hWnd = (HWND)::SendMessage(m_hWnd,LVM_GETHEADER,0,0);
if(hWnd)TRACE("OK\n");else TRACE("NULL\n");
	hWnd = ListView_GetHeader(m_hWnd);
if(hWnd)TRACE("OK\n");else TRACE("NULL\n");
	m_HeaderCtrl.SubclassWindow(hWnd);
...
...
}
dupa cum se vede, am incercat in doua feluri, din ambele rezulta handle null ...
Cum pot rezolva problema ?

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Problema ON_NOTIFY_REFLECT_EX

Post by Ovidiu Cucu » 01 Jul 2011, 23:40

Iti mai ramane o incercare cu GetListCtrl().GetHeaderCtrl() si le-ai epuizat pe toate trei. ;)
Pai, daca in CListViewExt::PreSubclassWindow() nu obtii headerul, inseamna ca nu a fost creat inca.
Eu zic, cel mai potrivit loc ar fi sa-l subclasezi in CListViewExt::OnInitialUpdate.
Ceva de genul

Code: Select all

void CListViewExt::OnInitialUpdate()
{
   if(NULL == m_headerCtrl.m_hWnd) // avoid subclassing twice in SDI
   {
      CHeaderCtrl* pHeader = GetListCtrl().GetHeaderCtrl();
      ASSERT(NULL != pHeader); // just in case.
      m_headerCtrl.SubclassWindow(pHeader->m_hWnd);
   }
}
Nu uita sa apelezi CListViewExt::OnInitialUpdate() in clasa derivata. De obicei il pune wizard-ul dar e bine de stiut.

mesajflaviu
Membru++
Membru++
Posts: 687
Joined: 10 Sep 2008, 21:40
Judet: Cluj

Re: Problema ON_NOTIFY_REFLECT_EX

Post by mesajflaviu » 02 Jul 2011, 17:32

Am terminat clasa CListViewExt, si merge super fain ! Excelenta ideea ! Multumesc inca o data !

P.S. Asa cum spunea Ovidiu :
ON_NOTIFY_REFLECT_EX pus la mana, iti da sansa ca in functie de valoarea returnata din handlerul din clasa C++/MFC ce incapsuleaza fereastra copil, sa se apeleze si handlerul corespunzator din clasa C++/MFC care incapsuleaza fereastra parinte. De obicei nu e nevoie de asa ceea pentru ca un design corect e cu "ori, ori".
daca in CListViewExt am de facut ceva in OnColumnClick :

Code: Select all

// CListViewExt.h
	afx_msg LRESULT OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult);
si

Code: Select all

// CListViewExt.cpp
	ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, CListViewExt::OnColumnclick)
...
...

LRESULT CListViewExt::OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult)
{
	NM_LISTVIEW* phdr = reinterpret_cast<NM_LISTVIEW*>(pNMHDR);
	SortOnColumn(phdr->iSubItem, TRUE);
	*pResult = 0;

	return *pResult;
}
... degeaba mai incerc in TestList6View ( derivata din CListViewExt ) sa "prind" OnColumnClick :

Code: Select all

// TestList6View.h
	afx_msg LRESULT OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult);

Code: Select all

// TestList6View.cpp
	ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, CListViewExt::OnColumnclick)

LRESULT CTestList6View::OnColumnclick(NMHDR* pNMHDR, LRESULT* pResult) 
{
	NM_LISTVIEW* phdr = reinterpret_cast<NM_LISTVIEW*>(pNMHDR);
	// TODO: Add your control notification handler code here

	m_nColumnSort = phdr->iSubItem;
	*pResult = 0;
TRACE("+%d\n",(BOOL)*pResult);
	return *pResult;
}
nu mai merge decat cu urmatorul prototip : ( cand bineinteles, nu mai am OnColumnClick(...) in CListViewExt )

Code: Select all

// TestList6View.cpp
	ON_NOTIFY_REFLECT(LVN_COLUMNCLICK, CTestList6View::OnColumnclick)
... totusi, n-am reusit sa prind daca vreau in ambele clase acest eveniment, cu orice incercare ... in acest caz, nu se mai poate ?

User avatar
Ovidiu Cucu
Fondator
Fondator
Posts: 3778
Joined: 11 Jul 2007, 16:10
Judet: Iaşi
Location: Iasi
Contact:

Re: Problema ON_NOTIFY_REFLECT_EX

Post by Ovidiu Cucu » 02 Jul 2011, 20:18

Poate n-am inteles bine dar la tine pare pe dos...

Sa zicem ca avem CBaseListView derivat din CListView si CDerivedListView, derivat din CBaseListView.
Ca o paranteza: daca ai pus CDerivedListView cu wizard-ul ai grija sa inlocuiesti peste tot in fisierul de implementare, CListView cu CBaseListView, inclusiv in macrourile gen IMPLEMENT_DYNCREATE si BEGIN_MESSAGE_MAP.
In al doilea rand uita de ON_NOTIFY_REFLECT_EX si ai grija ca functia handler mapata cu ON_NOTIFY_REFLECT trebuie sa intoarca void si nu BOOL sau LRESULT. Trebuie sa respecti exact prototipul handlerelor. Altfel, desi VS6.0 nu spune nimic la compilare (din cauza unor nenorocite de cast-uri C-style) iar in DEBUG de regula merge, in RELEASE are toate sansele sa crape.

Normal se intampa cam asa: Daca mapez LVN_COLUMNCLICK in CDerivedListView handlerul din CBaseListView nu se mai apeleaza si nu invers asa cum am inteles ca ai spus tu.
E corect asa. Sa spunem ca nu-ti place ce se face in clasa de baza, si-atunci scrii totul cap-coada.
Daca totusi vrei sa faci ceva si in clasa de baza si in cea derivata pe notificarea LVN_COLUMNCLICK, atunci pui in CBaseListView o functie virtuala pe care o apelezi din handler (sa-i zicem HandleColumnClick)

Astfel ai doua posibilitati de customizare in CDerivedListView:
  1. Renunti la tot ce-i implementat in CBaseListView si mapezi LVN_COLUMNCLICK in CDerivedListView unde faci totul cap-coada.
  2. Nu mapezi LVN_COLUMNCLICK in CDerivedListView dar suprascrii functia virtuala HandleColumnClick. Faci ce faci vrei in ea si eventual chemi si CBaseListView::HandleColumnClick.
Am atasat un demo simplu.
LVwTest.zip
(18.14 KiB) Downloaded 246 times

Post Reply