Comportamento do teclado ComboBoxEx32 (CCOMBOBOXEX)
Pergunta
Eu tenho um aplicativo WTL que usa um controle de ComboBox estendido (a classe Win32 ComboBoxEx32
) com o CBS_DROPDOWNLIST
estilo. Funciona bem (eu posso ter imagens em cada item na caixa), mas o comportamento do teclado é diferente de um ComboBox normal - pressionar uma tecla não pulará para o primeiro item no combo que começa com essa letra.
Por exemplo, se eu adicionar as cordas 'Arnold', 'Bob' e 'Charlie' ao combo, se eu selecionar o combo e pressionar 'B', então 'Bob' não será selecionado.
Alguém sabe como fazer isso funcionar? Atualmente, a única idéia que consigo pensar é de alguma forma subclasse o combinação 'real' (eu posso obter o identificador para isso usando o CBEM_GETCOMBOCONTROL
mensagem) e processo WM_CHARTOITEM
. Este é um pita, então pensei em perguntar se mais alguém já havia encontrado esse problema antes.
Solução
No final, enganchi o controle de ComboBox (obtido com CBEM_GETCOMBOCONTROL
) e preso o WM_CHARTOITEM
mensagem e executei minha própria pesquisa. Posso postar código se mais alguém estiver interessado.
Outras dicas
Eu criei uma solução de trabalho e quero compartilhar isso:
ComboBoxExKeyboardsupport.h
#pragma once
class CComboBoxExKeyboardSupport
{
// Construction
public:
CComboBoxExKeyboardSupport( void );
~CComboBoxExKeyboardSupport( void );
// Attributes
private:
static CSimpleMap<HWND, CComboBoxExKeyboardSupport*> responsibleMap;
HWND hComboBoxHwnd;
WNDPROC fpOriginalWndProc;
// Operations
private:
static LRESULT CALLBACK StaticWndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
LRESULT WndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
LRESULT HandleCharToItemMessage( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam );
bool IsWindowsXPPlatform( void );
bool InputMatches( CString inputChar, CString& itemText );
public:
void Attach( CComboBoxEx& comboBoxEx );
void Detach( void );
};
ComboBoxExKeyboardsupport.cpp
#include "StdAfx.h"
#include "ComboBoxExKeyboardSupport.h"
// Static member
CSimpleMap<HWND, CComboBoxExKeyboardSupport*> CComboBoxExKeyboardSupport::responsibleMap;
CComboBoxExKeyboardSupport::CComboBoxExKeyboardSupport( void )
{
hComboBoxHwnd = nullptr;
fpOriginalWndProc = nullptr;
}
CComboBoxExKeyboardSupport::~CComboBoxExKeyboardSupport( void )
{
Detach( );
}
void CComboBoxExKeyboardSupport::Attach( CComboBoxEx& comboBoxEx )
{
ATLASSERT( hComboBoxHwnd == nullptr );
if( hComboBoxHwnd != nullptr )
return;
if( !IsWindowsXPPlatform( ) )
return;
LONG_PTR lpNewWndProc = reinterpret_cast<LONG_PTR>( StaticWndProc );
LONG_PTR lpOldWndProc = 0;
//----
hComboBoxHwnd = comboBoxEx.GetComboBoxCtrl( )->GetSafeHwnd( );
ATLASSERT( hComboBoxHwnd != nullptr );
// Exchange the WndProc
lpOldWndProc = SetWindowLongPtr( hComboBoxHwnd, GWLP_WNDPROC, lpNewWndProc );
ATLASSERT( lpOldWndProc != 0 );
fpOriginalWndProc = reinterpret_cast<WNDPROC>( lpOldWndProc );
// Remember the handle and the old WndProc
responsibleMap.Add( hComboBoxHwnd, this );
}
void CComboBoxExKeyboardSupport::Detach( void )
{
if( hComboBoxHwnd == nullptr )
return;
//----
LONG_PTR lpResult = 0;
// Reset original WndProc
lpResult = SetWindowLongPtr( hComboBoxHwnd, GWLP_WNDPROC,
reinterpret_cast<LONG_PTR>( fpOriginalWndProc ) );
ATLASSERT( lpResult != 0 );
// Remove handle and WndProc from map
responsibleMap.Remove( hComboBoxHwnd );
//----
hComboBoxHwnd = nullptr;
fpOriginalWndProc = nullptr;
}
bool CComboBoxExKeyboardSupport::IsWindowsXPPlatform( void )
{
OSVERSIONINFO osvi = {0};
bool bResult = false;
//----
osvi.dwOSVersionInfoSize = sizeof( OSVERSIONINFO );
if( GetVersionEx( &osvi ) )
{
// 5.1 = Windows XP
// 5.2 = Windows Server 2003, Windows Server 2003 R2
bResult = ( osvi.dwMajorVersion == 5 &&
( osvi.dwMinorVersion == 1 || osvi.dwMinorVersion == 2 ) );
}
return bResult;
}
LRESULT CComboBoxExKeyboardSupport::StaticWndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
CComboBoxExKeyboardSupport* pResponsibleClass = nullptr;
// Get responsible class from map
pResponsibleClass = responsibleMap.Lookup( hwnd );
ATLASSERT( pResponsibleClass != nullptr );
//----
return pResponsibleClass->WndProc( hwnd, uMsg, wParam, lParam );
}
LRESULT CComboBoxExKeyboardSupport::WndProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
{
// Save originalWndProc because after WM_DESTROY/Detach the member variable is nullptr.
WNDPROC fpOriginalWndProc = this->fpOriginalWndProc;
//----
if( uMsg == WM_DESTROY )
{
Detach( );
}
else if( uMsg == WM_CHARTOITEM )
{
return HandleCharToItemMessage( hwnd, uMsg, wParam, lParam );
}
//----
return ::CallWindowProc( fpOriginalWndProc, hwnd, uMsg, wParam, lParam );
}
LRESULT CComboBoxExKeyboardSupport::HandleCharToItemMessage(
HWND hwnd,
UINT uMsg,
WPARAM wParam,
LPARAM lParam )
{
//----
LRESULT lResult = CB_ERR;
CComboBox* pComboBox = nullptr;
int itemCount = 0;
int itemSelected = 0;
CString itemText;
TCHAR inputCharacter = 0;
//----
pComboBox = (CComboBox*)CComboBox::FromHandle( hwnd );
//----
itemCount = pComboBox->GetCount( );
itemSelected = pComboBox->GetCurSel( );
inputCharacter = static_cast<TCHAR>( LOWORD( wParam ) );
// Search from the current selected item plus one to the end
for( int i = (itemSelected + 1); i < itemCount; i++ )
{
pComboBox->GetLBText( i, itemText );
if( InputMatches( inputCharacter, itemText ) )
{
lResult = i;
break;
}
}
if( lResult == CB_ERR )
{
// Search from the beginning to the selected item minus one.
for( int i = 0; i < itemSelected; i++ )
{
pComboBox->GetLBText( i, itemText );
if( InputMatches( inputCharacter, itemText ) )
{
lResult = i;
break;
}
}
}
//----
return lResult;
}
bool CComboBoxExKeyboardSupport::InputMatches( CString inputChar, CString& itemText )
{
CString firstCharString;
bool bInputMatches = false;
//----
firstCharString = itemText;
firstCharString.Left( 1 );
//----
bInputMatches = firstCharString.CompareNoCase( inputChar ) == 0;
//----
return bInputMatches;
}
Minha sugestão é abandonar o CombOBoxEx e desenhar o ícone com uma caixa de combinação regular do proprietário. O CCOMBOBoxEx é um pouco diferente do ComboBox 'normal', mas de maneiras suficientes que eu suspeito que seja uma reimplementação completa. Observe como um item selecionado parece um pouco diferente do que é selecionado em uma caixa de combinação normal também.
Os controles de desenho do proprietário no WTL são bastante fáceis de implementar com o COWNERDRAW Mixin.
Não é uma resposta para sua pergunta, apenas informando que é assim que eu lido com o CombOBoxEx hoje em dia :)
Em nossa aplicação, o comportamento do teclado que você descreveu foi perdido entre as versões. Como se vê, removemos uma dependência manifesta adicional, que resultou em uma dependência de uma versão mais antiga do COMCTL32.DLL (5.82). Esta linha nas configurações do projeto, propriedades de configuração -> Linker -> Arquivo de manifesto -> Dependências de manifesto adicionais:
type = 'win32' nome = 'microsoft.windows.common-controls' versão = '6.0.0.0' ProcessOarquitetura = ''PublicKeyToken =' 6595b64144ccf1df 'idioma =''
Corrigido para nós.
Usando Walker dependência, pode -se verificar se o aplicativo agora depende apenas do COMCTL32.DLL versão 6.10, que tem o comportamento correto.