tags:

views:

372

answers:

4

I have a list of items (potentially large) from which the user must select one. I'd like to allow the user to type the first few letters of the desired item to jump to the correct place in the list. By default, each keypress jumps to the first item starting with that letter, so you can't type the first several letters. Is there any straightforward way to do this? Any CodeProject or other such example?

I've looked for hours, and found any number of samples for IAutocomplete, but that won't help here because I need to guarantee that the result is in the list.

The only way I can think to do this is to derive from CListBox, capture the keystrokes myself, find the item, run a timer so that new keystrokes after a sufficient pause will start a new search... since I'm not an MFC jock, this is daunting. Any tips much appreciated.

One clarifying note: my ultimate goal is actually to get this keyboard behavior for a ComboBox of DropDownList style (i.e. no edit box). The lack of an edit box rules out most autocomplete code, and the need for ComboBox functionality means I can't use CListCtrl by itself.

+1  A: 

Can you use a CListView CListCtrl instead? They work like that by default.

RichieHindle
Well, I don't know. There seems to almost no documentation on CListView (Google, MSDN, Visual Studio Help) so I don't know how to use it. I'm still in Visual Studio .NET 2003, if that has any impact. Any pointers on how to try CListView?
@rfeague: Sorry! MFC calls it a CListCtrl despite the name for the underlying control being "ListView". It's called "List Control" in the Dialog Editor Toolbox.
RichieHindle
Thanks Richie. This is going in the right direction, but the quirks of CListCtrl are making me insane. I can get it to display my list, and it does (mostly) jump to the right item when a few letters are typed, but it doesn't scroll the initially-selected item into view, and it shows elipses on the end of the items when they are selected (but only when they are selected). So, I think you gave me the right answer, and I thank you. Any other pointers you might have on these remaining issues would be most welcome. I can't believe how much time I've invested in this should-be-simple issue.
@rfeague: The important thing about CListCtrl is that it's the same control that Windows Explorer uses for its file list. So by default it's in Icon view (if I recall correctly) and behaves as you say, but you can put it into Details view and add one or more columns for your data to go in, resize those columns, and so. You can also show icons like Windows Explorer does.
RichieHindle
Thanks Richie. SetExtendedStyle(LVS_EX_FULLROWSELECT) fixed ellipsis problem, EnsureVisible(i, FALSE) brought item into view.Unfortunately, I forgot to say that I need to make this work in a ComboBox-style (it needs to "roll up"), so now I'm hunting around to see if anyone has made a ComboBox that uses CListCtrl.I found this control that does exactly that: http://www.codeproject.com/KB/combobox/CustomComboBox.aspxbut it seems a little flakey in initial testing. It's amazing to me to think that there's no straightforward way to get this seemingly simple behavior!
+3  A: 

I've implemented such a functionality in core Win32. Heres the code.

Somewhere in your message loop that processes the list box insert:

 switch(message)
{   
   case WM_CHAR:       
 if(HandleListBoxKeyStrokes(hwnd, wParam) == FALSE)
                return FALSE;

....

Heres the code (propably not fully complete):

/* ======================================================================== */
/* ======================================================================== */
#define RETURNr(a, b) // homegrown asserts

BOOLEAN HandleListBoxKeyStrokes(HWND hwnd, UINT theKey)   

{
    #define MAXCHARCACHEINTERVALL 600.0  // Max. milisecs time offset to consider as typed 'at once'
    static char sgLastChars[255] = {'0'};
    static double  sgLastCharTime = 0.;

static HWND    sgLasthwnd = NULL;


if(GetSecs() - sgLastCharTime > MAXCHARCACHEINTERVALL ||
    sgLasthwnd != hwnd) 
    *sgLastChars = 0;

if(theKey == ' ' && *sgLastChars == 0)
    return TRUE; 

sgLastCharTime = GetSecs();
sgLasthwnd = hwnd; 

AppendChar(sgLastChars, toupper(theKey));

if(strlen(sgLastChars) > 1)
{
        LONG l = GetWindowLong(hwnd, GWL_STYLE);
        Char255 tx;
        GetClassName(hwnd, tx, sizeof(tx));
        if(  (! stricmp(tx, "Listbox") && 
              ! (l & (LBS_EXTENDEDSEL | LBS_MULTIPLESEL)) ) ||
             (! stricmp(tx, "ComboBox") &&  // combo Box support
                 l & CBS_DROPDOWNLIST   &&
              ! (l & (CBS_OWNERDRAWFIXED | CBS_OWNERDRAWVARIABLE)) ) )
        {
            long Count, l, BestMatch = - 1, BestMatchOff = 0;
            long LBcmdSet[] = {LB_GETCOUNT, LB_GETTEXTLEN  , LB_GETTEXT};
            long CBcmdSet[] = {CB_GETCOUNT, CB_GETLBTEXTLEN, CB_GETLBTEXT};
            long *cmdSet = (! stricmp(tx, "ComboBox")) ? CBcmdSet : LBcmdSet;

            RETURNr((Count = SendMessage(hwnd, cmdSet[0], 0, 0)) != LB_ERR, 0);
            for(int i = 0; i < Count; i++)


         {
                    RETURNr((l = SendMessage(hwnd, cmdSet[1], i, 0)) != LB_ERR, TRUE);
                    RETURNr( l < sizeof(tx), TRUE);
                    RETURNr((l = SendMessage(hwnd, cmdSet[2], i, (LPARAM)&tx)) != LB_ERR, TRUE);
                    strupr(tx);
                    if(! strncmp(tx, sgLastChars, strlen(sgLastChars)))
                    {
                        SelListBoxAndNotify(hwnd, i);
                        return FALSE;
                    }
                    char *p;
                    if(p = strstr(tx, sgLastChars))
                    {
                        int off = p - tx;
                        if(BestMatch == -1 || off < BestMatchOff)
                        {
                           BestMatch = i;
                           BestMatchOff = off;
                        }
                    }
                }
                // If text not found at start of string see if it matches some part inside the string
                if(BestMatch != -1)
                        SelListBoxAndNotify(hwnd, BestMatch);
                // Nothing found - dont process
                return FALSE;
            }
        }
        return TRUE;
    }
    /* ======================================================================== */
    /* ======================================================================== */

    void SelListBoxAndNotify(HWND hwnd, int index)

    {
    // i am sorry here - this is some XVT-toolkit specific code.
    // it has to be replaced with something similar for native Win32
        WINDOW win    = xvtwi_hwnd_to_window(hwnd);
        WINDOW parent = xvt_vobj_get_parent(win);
        xvt_list_set_sel(win, index, 1);
        EVENT evt;
        memset(&evt, 0, sizeof(evt));
        evt.type = E_CONTROL;
        evt.v.ctl.id = GetDlgCtrlID(hwnd);
        evt.v.ctl.ci.v.lbox.dbl_click = FALSE;
        xvt_win_dispatch_event(parent, &evt);  
    }
    /* ======================================================================== */
    /* ======================================================================== */

double  GetSecs(void)

{
        struct timeb timebuffer;
        ftime(&timebuffer);
        return (double)timebuffer.millitm + 
              ((double)timebuffer.time * 1000.) - // Timezone needed for DbfGetToday
              ((double)timebuffer.timezone * 60. * 1000.);
}
    /* ======================================================================== */
    /* ======================================================================== */

char    AppendChar(char *tx, char C)

{       int i;

        i = strlen(tx);
        tx[i    ] = C;
        tx[i + 1] = 0;
        return(C);
}
RED SOFT ADAIR
Wow, thanks Stefan! I'll give this a try. It looks like I could easily apply this to a normal ComboBox, which would be fabulous.
+4  A: 

After much unnecessary pain, I've discovered that the real correct answer is simply to use LBS_SORT. Simply by specifying this style, the basic vanilla listbox supports the incremental search keyboard shortcut style I wanted. Without LBS_SORT (or CBS_SORT for a combobox), you get the irritating and almost-useless jump-to-first-letter-only behavior. I didn't try LBS_SORT because my list contents were added in sorted order anyway.

So the dozen or so hours of investigating custom controls, etc., all for naught because the Microsoft documentation makes no mention of this important behavioral difference in the description of LBS_SORT!!

Thanks to everyone who contributed.

+1 for coming back with the solution - thanks, I didn't know that about listboxes. I feel your pain re. the documentation. 8-(
RichieHindle
A: 

Try this javascript solution: http://www.mingyi.org/other/autocomplete.html

Amogh