Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

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

Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Post by mesajflaviu » 04 Oct 2013, 14:11

Am o clasa derivata in CListBox, numita CMyListBox. Cum "prind" LB_FINDSTRING in PreTranslateMessage in acesta clasa ? Am incercat urmatorul cod:

Code: Select all

BOOL CMyListBox::PreTranslateMessage(MSG* pMsg) 
{
	// TODO: Add your specialized code here and/or call the base class

	if(LB_FINDSTRING == pMsg->message)
		TRACE("A\n");
	if(LBN_KILLFOCUS == message)
		TRACE("B\n");

	return CListBox::PreTranslateMessage(pMsg);
}
fara succes ... am mai incercat sa prind KILLFOCUS, degeaba ... cum pot rezolva aceasta problema ? Alte mesaje pare ca merg pe acolo, de exemplu WM_LBUTTONDOWN sau WM_MOUSEMOVE ... insa cele specifice CListBox, nu ...



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

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Post by Ovidiu Cucu » 04 Oct 2013, 18:26

Nu-ncerca, ca-ncerci degeaba... :)

In primul rand, LBN_KILLFOCUS nu-i un mesaj ci o notificare care vine cu mesajul WM_COMMAND. La fel si celelalte LBN_uri sau alte notificari trimise prin WM_COMMAND sau WM_NOTIFY, nu o sa le prinzi cu ceva de genul

Code: Select all

   if(LBN_KILLFOCUS == pMsg->message)
Mai departe, scrie la documentatie precum ca PreTranslateMessage este...
... used by class CWinApp to translate window messages before they are dispatched to the TranslateMessage and DispatchMessage Windows functions

Deci e chemata pe undeva prin bucla care ia mesaje din coada de mesaje.

Daca scrii, de exemplu

Code: Select all

   int nRet = m_listBox.FindString(-1, _T("Tra-la-la!"));
si te uiti in spate, vezi ceva de genul

Code: Select all

_AFXWIN_INLINE int CListBox::FindString(int nStartAfter, LPCTSTR lpszItem) const
	{ ASSERT(::IsWindow(m_hWnd)); return (int)::SendMessage(m_hWnd, LB_FINDSTRING,
		nStartAfter, (LPARAM)lpszItem); }
Retinem "SendMessage" si ne uitam la acest FAQ: Deosebirea intre PostMessage si SendMessage.
SendMessage nu pune mesajul in coada de mesaje ci il paseaza direct la functia fereastra (a controlului listbox, in cazul nostru).
Nu e pus in coada, deci nu are cum nici sa ajunga la PreTranslateMessage.

Clar pana aici?
Acuma, doar de curiozitate, for fun si/sau sa vezi ca am dreptate, posteaza listei un LB_FINDSTRING cu PostMessage si-ai sa vezi ce se intampla. Doar for fun...

Mai bine insa, lasa in pace pe PreTranslateMessage (ca nu prinde tot ce zboara :)) si mapeaza frumusel mesajele/notificarile.
Pentru LBN_KILLFOCUS te ajuta wizard-ul cu ON_CONTROL_REFLECT.
Pentru care nu (ex. LB_FINDSTRING) mapezi la mana cu ON_MESSAGE, cam asa:

Code: Select all

class CMyListBox : public CListBox
{
    // ...
    afx_msg LRESULT OnLbFindString(WPARAM wParam, LPARAM lParam);
};

Code: Select all

BEGIN_MESSAGE_MAP(CMyListBox, CListBox)
    ON_MESSAGE(LB_FINDSTRING, &CMyListBox::OnLbFindString)
    ON_CONTROL_REFLECT(LBN_KILLFOCUS, &CMyListBox::OnLbnKillfocus)
END_MESSAGE_MAP()

// CMyListBox message handlers
LRESULT CMyListBox::OnLbFindString(WPARAM wParam, LPARAM lParam)
{
    // Face, drege, arde, frige... 
    return Default(); 
}

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

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Post by mesajflaviu » 07 Oct 2013, 09:08

O sa incerc imediat ideea de mai sus.

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

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Post by Ovidiu Cucu » 07 Oct 2013, 11:12

mesajflaviu wrote:O sa incerc imediat ideea de mai sus.
Bine faci! Nu-i doar o idee, asa trebuie facut. :)

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

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Post by mesajflaviu » 07 Oct 2013, 12:45

Intradevar nu stiam pe unde "merge" LB_FINDSTRING, si tin minte ca am mai facut chestii de genul asta ... dar se pare ca nu mi-au intrat in minte cum trebuie ... :( dupa asa explicatie ampla ca mai sus, sant convins ca va functiona, doar ca vrand de fapt sa dezvolt un control existent, o pot face doar "printre picaturi" ...

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

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Post by mesajflaviu » 11 Oct 2013, 15:26

Functioneaza solutia de mai sus, numai ca nu face ce vreau eu :D ... am un CMyComboBox, unde am subclasat lista in felul urmator:

Code: Select all

HBRUSH CMyComboBox::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 
{
	HBRUSH hbr = CComboBox::OnCtlColor(pDC, pWnd, nCtlColor);

	// TODO: Change any attributes of the DC here

	if(CTLCOLOR_LISTBOX == nCtlColor)
	{
		if(NULL == m_ListBox.GetSafeHwnd())
		{
			m_ListBox.SubclassWindow(pWnd->GetSafeHwnd());
			m_pWndProc = reinterpret_cast<WNDPROC>(GetWindowLong(m_ListBox, GWL_WNDPROC));
			SetWindowLong(m_ListBox, GWL_WNDPROC, reinterpret_cast<long>(MyComboBoxListBoxProc));
		}
	}

	// TODO: Return a different brush if the default is not desired

	return hbr;
}
unde MyComboBoxListBoxProc arata cam asa:

Code: Select all

extern "C" LRESULT FAR PASCAL MyComboBoxListBoxProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{
	if(LB_FINDSTRING == nMsg)
		nMsg = LB_FINDSTRINGEXACT;

	return CallWindowProc(m_pWndProc, hWnd, nMsg, wParam, lParam);
}
si merge OK. Cand dropdown-ul combo-ului este jos si tastez o litera in editbox, nu vreau sa imi selecteze automat un cuvand din combo care incepe cu acea litera ... Am vrut sa scap de acest cod, in modul urmator:

Code: Select all

HBRUSH CMyComboBox::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 
{
	HBRUSH hbr = CComboBox::OnCtlColor(pDC, pWnd, nCtlColor);

	// TODO: Change any attributes of the DC here

	if(CTLCOLOR_LISTBOX == nCtlColor)
	{
		if(NULL == m_ListBox.GetSafeHwnd())
			m_ListBox.SubclassWindow(pWnd->GetSafeHwnd());
	}

	// TODO: Return a different brush if the default is not desired

	return hbr;
}
unde m_List este de tip CMyListBox ... apoi am aplicat solutia de mai sus:

Code: Select all

LRESULT CMyListBox::OnLbFindString(WPARAM wParam, LPARAM lParam)
{
	TRACE("%d %s %d\n", wParam, (LPCTSTR)lParam, Default());
	return -1;
}
codul trece pe acolo, dar nu isi face treaba ... care este diferenta intre cele 2 solutii ?

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

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Post by Ovidiu Cucu » 12 Oct 2013, 14:04

Daca vrei si vrei neaparat sa prinzi mesajele trimise unei liste drop-down asociate unui combo, poti face asa:
  1. prinzi notificarea CBN_DROPDOWN;
  2. trimiti la combo mesajul CB_GETCOMBOBOXINFO sau chemi CComboBox::GetComboBoxInfo;
  3. din structura COMBOBOXINFO extragi handle-ul listei drop-down (hwndList);
  4. folosind acel handle, subclasezi lista drop-down.
  5. etc, vezi exemplul de mai jos.
Pe scurt si scris in graba, cam asa ar arata un scheleton de clasa derivata din CCoboBox care manareste procedura listei drop-down:

Code: Select all

// DemoComboBox.h :  CDemoComboBox class definition
//
#pragma once

class CDemoComboBox : public CComboBox
{
// Attributes
public:
    static LPCTSTR const m_pszListProcProp;

// Implementation
private:
    static LRESULT CALLBACK 
        _DropDownListProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam);

// Message handlers
protected:
    DECLARE_MESSAGE_MAP()
    afx_msg void OnCbnDropdown();
    afx_msg void OnCbnCloseup();
};

Code: Select all

// DemoComboBox.cpp : CDemoComboBox class implementation
//
#include "stdafx.h"
#include "DemoComboBox.h"

LPCTSTR const CDemoComboBox::m_pszListProcProp = _T("ListProcProp");

LRESULT CALLBACK CDemoComboBox::_DropDownListProc(HWND hWnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    switch(uMsg)
    {
    case LB_FINDSTRING:
        {
            TRACE0("\nLB_FINDSTRING message received");
            // Do whatever your muscle wants!
        }
        break;
    // ...
    }
    // Get the default drop-down list window procedure
    WNDPROC wndProcList = (WNDPROC)::GetProp(hWnd, CDemoComboBox::m_pszListProcProp);
    ASSERT(NULL != wndProcList);
    // Call the default drop-down list window procedure
    return CallWindowProc(wndProcList, hWnd, uMsg, wParam, lParam); 
}

Code: Select all

BEGIN_MESSAGE_MAP(CDemoComboBox, CComboBox)
    ON_CONTROL_REFLECT(CBN_DROPDOWN, &CDemoComboBox::OnCbnDropdown)
    ON_CONTROL_REFLECT(CBN_CLOSEUP, &CDemoComboBox::OnCbnCloseup)
END_MESSAGE_MAP()

// CDemoComboBox message handlers
void CDemoComboBox::OnCbnDropdown()
{
    TRACE0("\nCDemoComboBox::OnCbnDropdown");
    // Get the drop-down list handle
    COMBOBOXINFO cbi = {0};
    cbi.cbSize = sizeof(COMBOBOXINFO);
    GetComboBoxInfo(&cbi);
    HWND hWndList = cbi.hwndList;
    // Subclass the drop-down list window procedure
    WNDPROC wndProcList = (WNDPROC) 
        ::SetWindowLong(hWndList, GWL_WNDPROC, (LONG)&CDemoComboBox::_DropDownListProc);
    // Keep in mind the default drop-down list window procedure
    VERIFY(::SetProp(hWndList, m_pszListProcProp, wndProcList));

    // BEGIN DEMO/TEST
    // TODO: remove this!
    FindString(-1, _T("Portocala"));
    // END DEMO/TEST
}

Code: Select all

void CDemoComboBox::OnCbnCloseup()
{
    TRACE0("\nCDemoComboBox::OnCbnCloseup");
    // Get the drop-down list handle
    COMBOBOXINFO cbi = {0};
    cbi.cbSize = sizeof(COMBOBOXINFO);
    GetComboBoxInfo(&cbi);
    HWND hWndList = cbi.hwndList;

    if(::IsWindow(hWndList))
    {
        // Get the default drop-down list window procedure
        WNDPROC wndProcList = (WNDPROC)::GetProp(hWndList, CDemoComboBox::m_pszListProcProp);
        ASSERT(NULL != wndProcList);
        // Set the default drop-down list window procedure
        ::SetWindowLong(hWndList, GWL_WNDPROC, (LONG)wndProcList);
    }
}

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

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Post by Ovidiu Cucu » 12 Oct 2013, 14:45

[ continuare ]
In principiu, pentru ceea ce vrei sa obtii tu nu ai nevoie sa prinzi mesajele trimise listei.
Urmatorul exemplu (tot pe scurt si scris in graba) selecteaza un item din lista drop-down atunci cand utilizatorul scrie in editul cobo-ului...

Code: Select all

// ...
    ON_CONTROL_REFLECT(CBN_EDITCHANGE, &CDemoComboBox::OnCbnEditchange)
END_MESSAGE_MAP()
// ...
void CDemoComboBox::OnCbnEditchange()
{
    TRACE0("\nCDemoComboBox::OnCbnEditchange");
    // Show drop-down list
    ShowDropDown();
    // Get the combobox child edit control
    CEdit* pEdit = _GetEditControl();
    if(NULL != pEdit->GetSafeHwnd())
    {
        // Get the edit text 
        CString strEditText;
        pEdit->GetWindowText(strEditText);
        // Search in in drop-down list
        const int nPos = FindString(-1, strEditText);
        if(CB_ERR != nPos)
        {
            // Select the appropriate intem in drop-down list
            SetCurSel(nPos);
        }
    }
}

Code: Select all

CEdit* CDemoComboBox::_GetEditControl()
{
    CEdit* pEdit = NULL;
    CWnd* pWndChild = GetWindow(GW_CHILD);
    CString strClassName = _T("edit");
    while(NULL != pWndChild)
    {
        const int nMaxCount = 64;
        TCHAR pszClassName[nMaxCount] = {0};
        ::GetClassName(pWndChild->m_hWnd, pszClassName, nMaxCount);
        if(!strClassName.CompareNoCase(pszClassName))
        {
            pEdit = (CEdit*)pWndChild;
            break;
        }
        pWndChild = pWndChild->GetWindow(GW_HWNDNEXT);
    }
    return pEdit;
}
...lasand subclasarile si alte giumbuslucuri pentru scopuri mai nobile. :)

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

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Post by mesajflaviu » 14 Oct 2013, 09:41

E material de studiu aici ... :)

Da, vreau sa subclasez dropdown-ul listei, dar acolo am o clasa de sine statatoare (CMyListBox) in care as vrea sa fac mai multe lucruri ... printre care sa si "deturnez" acel LB_FINDSTRING ... de subclasat, am subclasat lista asa cum am mai aratat:

Code: Select all

HBRUSH CMyComboBox::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 
{
   HBRUSH hbr = CComboBox::OnCtlColor(pDC, pWnd, nCtlColor);

   // TODO: Change any attributes of the DC here

   if(CTLCOLOR_LISTBOX == nCtlColor)
   {
      if(NULL == m_ListBox.GetSafeHwnd())
         m_ListBox.SubclassWindow(pWnd->GetSafeHwnd());
   }

   // TODO: Return a different brush if the default is not desired

   return hbr;
}
unde m_List este de tip CMyListBox ... de subclasat se subclaseaza, dar dupa aceea nu mi-a mai iesit "inlocuirea" LB_FINDSTRING cu LB_FINDSTRINGEXACT ... o sa iau cod de mai sus si osa incerc sa-l adaptez la ce imi trebuie ...

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

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Post by Ovidiu Cucu » 14 Oct 2013, 18:58

  1. La ce bun sa inlocuiesti LB_FINDSTRING cu LB_FINDSTRINGEXACT? Poate ca maine-poimaine o sa vrea careva sa-l inlocuiasca cu WM_QUERYENDSESSION... :D
  2. De ce sa faci hijack la lista drop-down, pentru ceva care poti tot atat de bine face cu mesajele lui combo?
    De exemplu:

    Code: Select all

        ON_MESSAGE(CB_FINDSTRING, OnCbFindString)
    END_MESSAGE_MAP()
    
    // CDemoComboBox message handlers
    LRESULT CDemoComboBox::OnCbFindString(WPARAM wParam, LPARAM lParam)
    {
        return SendMessage(CB_FINDSTRINGEXACT, wParam, lParam);
    }
  3. Lasa handlerul lui WM_CTLCOLOR centru ce a fost destinat, adica pentru controlat culori NU pentru subclasari si alte briz-brizuri.
  4. Vezi ca lista drop-down a unui combobox e din clasa ComboLBox si nu-i chiar unul si acelasi lucru cu controlul Windows comun din clasa ListBox, cel care este tinut in MFC de catre CListBox.
  5. Code: Select all

          if(NULL == m_ListBox.GetSafeHwnd())
             m_ListBox.SubclassWindow(pWnd->GetSafeHwnd());
    Dar daca acel drop-down este distrus intre timp?
    Nu-i bine.

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

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Post by mesajflaviu » 15 Oct 2013, 09:20

Am vrut sa atasez de lista dropdown un tooltip control, pentru asta mi-ar fi trebuit o clasa de sine statatoare CMyListBox ... si poate si as fi pus pe acolo si alte lucruri ... sa vad ce pot face ...

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

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Post by Ovidiu Cucu » 15 Oct 2013, 16:12

OK, sa zicem...
Poti face si asa:

Code: Select all

BEGIN_MESSAGE_MAP(CDemoComboBox, CComboBox)
    ON_CONTROL_REFLECT(CBN_DROPDOWN, &CDemoComboBox::OnCbnDropdown)
    ON_CONTROL_REFLECT(CBN_CLOSEUP, &CDemoComboBox::OnCbnCloseup)
    // ...
END_MESSAGE_MAP()

Code: Select all

void CDemoComboBox::OnCbnDropdown()
{
    TRACE0("\nCDemoComboBox::OnCbnDropdown");

    // Get the drop-down list handle
    HWND hWndList = _GetDropdownListHandle();
    ASSERT(NULL != hWndList);

    // Subclass drop-down list 
    VERIFY(m_listBox.SubclassWindow(hWndList));
}

Code: Select all

void CDemoComboBox::OnCbnCloseup()
{
    TRACE0("\nCDemoComboBox::OnCbnCloseup");

    // Unsubclass drop-down list
    m_listBox.UnsubclassWindow();
}

Code: Select all

HWND CDemoComboBox::_GetDropdownListHandle()
{
    HWND hWnd = NULL;
    COMBOBOXINFO cbi = {0};
    cbi.cbSize = sizeof(COMBOBOXINFO);

    BOOL bRet = GetComboBoxInfo(&cbi);
    if(bRet)
        hWnd = cbi.hwndList;

    return hWnd;
}
Iar daca mai lucrezi cumva cu un MFC vechi, care nu are CComboBox::GetComboBoxInfo, te salveaza mesajul CB_GETCOMBOBOXINFO.

Code: Select all

#ifndef CB_GETCOMBOBOXINFO
#define CB_GETCOMBOBOXINFO          0x0164
#endif

// ...

Code: Select all

HWND CDemoComboBox::_GetDropdownListHandle()
{
    HWND hWnd = NULL;
    COMBOBOXINFO cbi = {0};
    cbi.cbSize = sizeof(COMBOBOXINFO);

    BOOL bRet = SendMessage(CB_GETCOMBOBOXINFO, 0, (LPARAM)&cbi);
    if(bRet)
        hWnd = cbi.hwndList;

    return hWnd;
}
Nu cred ca mai foloseste cineva sisteme mai vechi decat Windows XP, asa ca-i mai bine sa-l lasi pe WM_CTLCOLOR sa-si vada de treaba lui. ;)

Post Reply