winapicomboboxwindows-10ownerdrawn

How do I select item based on typed letters on owner-draw combobox?


On a regular combobox, you can jump to items that starts with the typed letter. e.g., if you have items like "baa", "arch", "foo", "art" and type "a" the item "arch" gets selected, you type "a" again then it jumps to "art". How can I implement this on owner-draw combobox? I thought I could handle WM_CHAR like below in the comobobox's subprocedure, however I couldn't even test it, since setting a procedure to the comobobox like that fails:

      HWND hwndEdit = GetWindow(hwndCombo, GW_CHILD);
      assert(hwndEdit != NULL); // fails here
      lpfnEditWndProc = (WNDPROC) SetWindowLongPtr(hwndEdit, GWLP_WNDPROC, (LONG_PTR) SubClassProc);

the procecdure was going to be like this:

LRESULT CALLBACK SubClassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 
{ 
   switch (msg)
   { 
      case WM_CHAR:
      {
        static int prevIndex = 0;
        static wchar_t buffer[2] = {0};

        memset(&buffer, 0, sizeof(buffer));
        buffer[0] = wParam;
        prevIndex = SendMessage(hwndCombo, CB_FINDSTRING, (WPARAM) prevIndex, (LPARAM) buffer);
        SendMessage(hwndCombo, CB_SETCURSEL, (WPARAM) prevIndex, 0);
        return 0;
      }
      break;
    }

    return CallWindowProc(lpfnEditWndProc, hwnd, msg, wParam, lParam);
}

Here's the full code of owner-draw combobox:

   #pragma comment(lib, "user32.lib")
#pragma comment(lib, "Comctl32.lib")
#pragma comment(lib, "Gdi32.lib")
#pragma comment(lib, "UxTheme.lib")
#pragma comment(lib, "Comdlg32.lib")

#define WIN32_LEAN_AND_MEAN
#define UNICODE
#define _UNICODE

#include <windows.h>
#include <Commctrl.h>
#include <assert.h>
#include <uxtheme.h>
#include <Commdlg.h>

LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
void SetDefaultFont(HWND hwnd);
HFONT LoadSystemDefaultFont();
void DrawBorder(HDC hdc, RECT *rect);
void DrawLine(HDC hdc, LONG x1, LONG y1, LONG x2, LONG y2);
void freeBrushes();
HBRUSH getBrushAt(int index);
void drawColorRect(HDC dc, RECT *editRect, int colorIndex);
void EnableVisualStyles2(void);
LRESULT CALLBACK SubClassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);

HINSTANCE g_hinst;
HFONT hDefaultSystemFont;
HWND hwndCombo;

#define COUNTOF(a) (sizeof(a)/sizeof(a[0]))
#define LIST \
    X(L"Black", RGB(0, 0, 0)) \
    X(L"Red", RGB(255, 0, 0)) \
    X(L"Blue", RGB(0, 0, 255)) \
    X(L"Green", RGB(0, 128, 0)) \
    X(L"Yellow", RGB(255, 255, 0))

#define X(a, b) a,
const wchar_t *items[] = { LIST };
#undef X

#define X(a, b) b,
const int colorCodes[] = { LIST };
#undef X

HBRUSH brushes[COUNTOF(items)];
WNDPROC lpfnEditWndProc;

enum
{
    BTN_A = 20,
    BTN_COMBO,
};

int WINAPI wWinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance,
                    PWSTR lpCmdLine, int nCmdShow) {

  
    HWND hwnd;
    MSG  msg;
    WNDCLASSW wc = {0};
    wc.lpszClassName = L"Application";
    wc.hInstance     = hInstance ;
    wc.hbrBackground = GetSysColorBrush(COLOR_3DFACE);
    wc.lpfnWndProc   = WndProc;
    wc.hCursor       = LoadCursor(0,IDC_ARROW);

    g_hinst = hInstance;

    RegisterClassW(&wc);
    hwnd = CreateWindowW(wc.lpszClassName, L"Combo box",
                  WS_OVERLAPPEDWINDOW | WS_VISIBLE,
                  100, 100, 270, 170, 0, 0, hInstance, 0);  


    while (GetMessage(&msg, NULL, 0, 0)) {
        DispatchMessage(&msg);
    }
    
    DeleteObject(hDefaultSystemFont);
    freeBrushes();

    return (int) msg.wParam;
}

LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, 
        WPARAM wParam, LPARAM lParam)
{
    switch(msg)
    {
        case WM_CREATE:
        {
            hwndCombo = CreateWindow(L"Combobox", NULL, 
                WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST |
                CBS_OWNERDRAWFIXED | CBS_AUTOHSCROLL | WS_VSCROLL | WS_HSCROLL,
                10, 10, 100, 110, hwnd, (HMENU) BTN_COMBO, g_hinst, NULL);
            SendMessage(hwndCombo, CB_SETMINVISIBLE, 5, 0);
            SetDefaultFont(hwndCombo);
            COMBOBOXINFO ci = {0};
            ci.cbSize = sizeof(COMBOBOXINFO);
            GetComboBoxInfo(hwndCombo, &ci);
            lpfnEditWndProc = (WNDPROC)SetWindowLongPtr(ci.hwndList, GWLP_WNDPROC, (LONG_PTR)SubClassProc);
            for (int i = 0; i < COUNTOF(items); ++i)
            {
                SendMessage(hwndCombo, CB_ADDSTRING, 0, (LPARAM) items[i]);
            }
        }
        break;

        case WM_DRAWITEM:
        {
              LPDRAWITEMSTRUCT b = (LPDRAWITEMSTRUCT) lParam;
              SetBkMode(b->hDC, TRANSPARENT);

              if(b->itemState & ODS_SELECTED)
              {
                FillRect(b->hDC, &b->rcItem, (HBRUSH) (COLOR_HIGHLIGHT+1));
                SetTextColor(b->hDC, GetSysColor(COLOR_HIGHLIGHTTEXT+1));
              }
              else
              {
                FillRect(b->hDC, &b->rcItem, (HBRUSH) (COLOR_WINDOW+1));
                SetTextColor(b->hDC, GetSysColor(COLOR_WINDOWTEXT+1));
              }

             if(b->itemID != -1)
             {
                drawColorRect(b->hDC, &b->rcItem, b->itemID);
                DrawText(b->hDC, items[b->itemID], -1, &b->rcItem, DT_CENTER | DT_VCENTER | DT_SINGLELINE);
             }
             
            if(b->itemAction & ODA_FOCUS)
            {
                DrawFocusRect(b->hDC, &b->rcItem);
            }

            return (INT_PTR) TRUE;
        }
        break;

        case WM_DESTROY:
            PostQuitMessage(0);
            break; 
    }

  
    return DefWindowProc(hwnd, msg, wParam, lParam);
}

HFONT LoadSystemDefaultFont()
{
  if(hDefaultSystemFont == NULL) {
    NONCLIENTMETRICS ncm;
    ncm.cbSize = sizeof(NONCLIENTMETRICS);
    SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(NONCLIENTMETRICS), &ncm, 0);
    hDefaultSystemFont = CreateFontIndirect(&ncm.lfMessageFont);
  }
  return hDefaultSystemFont;
}


void SetDefaultFont(HWND hwnd)
{
    SendMessage(hwnd, WM_SETFONT, (LPARAM) LoadSystemDefaultFont(), TRUE);
}

void drawColorRect(HDC dc, RECT *editRect, int colorIndex)
{
    assert(colorIndex >= 0 && colorIndex < COUNTOF(brushes));
    assert(editRect != NULL);

    RECT rt = {0};
    rt.left = editRect->left + 2;
    rt.top = editRect->top - 2;
    rt.right = editRect->right / 5;
    rt.bottom = editRect->bottom - 2;
    InflateRect(&rt, -1, -1);
    DrawBorder(dc, &rt);
    //FrameRect(dc, &rt, getBrushAt(0));
    FillRect(dc, &rt, getBrushAt(colorIndex));
}

void DrawLine(HDC hdc, LONG x1, LONG y1, LONG x2, LONG y2)
{
    MoveToEx(hdc, x1, y1, NULL);
    LineTo(hdc, x2, y2);
}
    
void DrawBorder(HDC hdc, RECT *rect)
{
    DrawLine(hdc, rect->left, rect->top, rect->left, rect->bottom);
    DrawLine(hdc, rect->left, rect->top, rect->right, rect->top);
    DrawLine(hdc, rect->right, rect->top, rect->right, rect->bottom);
    DrawLine(hdc, rect->left, rect->bottom, rect->right, rect->bottom);
}

HBRUSH getBrushAt(int index)
{
    assert(index >= 0 && index < COUNTOF(brushes));
    HBRUSH b = brushes[index];
    if(b == NULL) {
        int code = colorCodes[index];
        brushes[index] = CreateSolidBrush(code);
        b = brushes[index];
    }
    return b;
}

void freeBrushes()
{
    for(int i = 0; i < COUNTOF(brushes); ++i){
        DeleteObject(brushes[i]);
        brushes[i] = NULL;
    }
}

void EnableVisualStyles2(void)
{
    TCHAR dir[MAX_PATH] = {0};
    GetSystemDirectory(dir, sizeof(dir) / sizeof(*dir));

    ACTCTX actCtx = {0};
    actCtx.cbSize = sizeof(ACTCTX);
    actCtx.dwFlags =  ACTCTX_FLAG_RESOURCE_NAME_VALID |
                      ACTCTX_FLAG_SET_PROCESS_DEFAULT |
                      ACTCTX_FLAG_ASSEMBLY_DIRECTORY_VALID;
    actCtx.lpSource = L"shell32.dll";
    actCtx.lpAssemblyDirectory = dir;
    actCtx.lpResourceName = (LPCTSTR) 124;
    ULONG_PTR cookie = FALSE;
    HANDLE h = CreateActCtx(&actCtx);
    assert(h != INVALID_HANDLE_VALUE);
    ActivateActCtx(h, &cookie);
}

LRESULT CALLBACK SubClassProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) 
{
 
    switch (msg)
   { 
      case WM_CHAR:
      {
        static int prevIndex = 0;
        static wchar_t buffer[2] = {0};

        buffer[0] = wParam;
        MessageBox(NULL, buffer, L"", MB_OK);

        prevIndex = SendMessage(hwndCombo, CB_FINDSTRING, (WPARAM) prevIndex, (LPARAM) buffer);
        SendMessage(hwndCombo, CB_SETCURSEL, (WPARAM) prevIndex, 0);
        return 0;
      }
      break;
    }

    return CallWindowProc(lpfnEditWndProc, hwnd, msg, wParam, lParam);
}

UPDDATE sorry, I past the code sample from other file than the owner-draw combobox. As pointed out by @Song Zhu - MSFT, set the procedure to properly but that still doesn't process WM_CHAR.


Solution

  • The drop-down COMBOBOX does not have an EDIT control. Instead, it has a drop-down list.

    I recommend you to use GetComboBoxInfo to get the child window of the COMBOBOX control.

    Try to modify the code as:

    COMBOBOXINFO ci{};
    ci.cbSize = sizeof(COMBOBOXINFO);
    GetComboBoxInfo(hwndCombo, &ci);
    lpfnEditWndProc = (WNDPROC)SetWindowLongPtr(ci.hwndList, GWLP_WNDPROC, (LONG_PTR)SubClassProc);
    

    Of course, you can use this code to see that hwndItem of COMBOBOXINFO returns NULL. You can adjust which handle you need to control according to your combobox type.

    Edit:

    Your message loop code and setting style are wrong(need CBS_HASSTRINGS), please refer to:

        while (GetMessage(&msg, NULL, 0, 0)) {
            TranslateMessage(&msg);
            DispatchMessage(&msg);
        }
    
    ......
    
     hwndCombo = CreateWindow(L"Combobox", NULL, 
                    WS_CHILD | WS_VISIBLE | CBS_DROPDOWNLIST |
                    CBS_OWNERDRAWFIXED | CBS_AUTOHSCROLL | WS_VSCROLL | WS_HSCROLL | CBS_HASSTRINGS,
                    10, 10, 100, 110, hwnd, (HMENU) BTN_COMBO, g_hinst, NULL);