Page 1 of 1

Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Posted: 04 Oct 2013, 14:11
by mesajflaviu
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 ...

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Posted: 04 Oct 2013, 18:26
by Ovidiu Cucu
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(); 
}

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Posted: 07 Oct 2013, 09:08
by mesajflaviu
O sa incerc imediat ideea de mai sus.

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Posted: 07 Oct 2013, 11:12
by Ovidiu Cucu
mesajflaviu wrote:O sa incerc imediat ideea de mai sus.
Bine faci! Nu-i doar o idee, asa trebuie facut. :)

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Posted: 07 Oct 2013, 12:45
by mesajflaviu
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" ...

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Posted: 11 Oct 2013, 15:26
by mesajflaviu
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 ?

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Posted: 12 Oct 2013, 14:04
by Ovidiu Cucu
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);
    }
}

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Posted: 12 Oct 2013, 14:45
by Ovidiu Cucu
[ 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. :)

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Posted: 14 Oct 2013, 09:41
by mesajflaviu
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 ...

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Posted: 14 Oct 2013, 18:58
by Ovidiu Cucu
  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.

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Posted: 15 Oct 2013, 09:20
by mesajflaviu
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 ...

Re: Cum "prind" LB_FINDSTRING in PreTranslateMessage ?

Posted: 15 Oct 2013, 16:12
by Ovidiu Cucu
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. ;)