Link to home
Start Free TrialLog in
Avatar of AzraSound
AzraSoundFlag for United States of America

asked on

Combobox auto-selecting entries

i have a rather automated system set up for a current program i am constructing.  one of the forms serves as somewhat of a property list (it can be thought of as similar to the property list used in the VB IDE).  it is simply a flexgrid which allows for in-place editing, and depending on the type of data being entered, either a textbox, or combobox, or even another grid will appear.

the problem arises from the fact that there is certain data we must store which we offer a combobox list of suggested, or recommended, values, but that the user has the ability to override with their own custom value if they wish.  using a combobox seems acceptable since it allows for selection from our list, plus, gives the user the ability to manually type in their own value (with style set to DropDown Combo).  however, if they enter a value that starts with one of the values in the list, that list item is automatically selected and takes the place of their custom value as the combobox's text when the combobox drpos down.  for example:


Private Sub Form_Load()
    With Combo1
        .AddItem "12.34"
        .AddItem "23.45"
        .AddItem "34.56"

        .Text = "3"
    End With
End Sub


now if you run the program, "3" shows in the combobox, but once you dropdown, "34.56" becomes selected and shows as the new value.  is there an elegant way around this?  perhaps a property or API call I am not familiar with?  changes in the UI will most likely not be allowed.


P.S.  i will add points to this for a good solution - just looking for ideas first
Avatar of beckingh
beckingh

Azra: I posted a points question for you on 6/30...
this may work:

you can call it from keypress event or from change

Public Sub TypeAheadCombo(ljCombo As ComboBox)
  Dim lviIndex As Integer
  Dim lvsKeyString As String
  Dim lviKeyLen As Integer
 
  lvsKeyString = ljCombo.Text
  lviKeyLen = Len(lvsKeyString)
  If lviKeyLen > 0 Then
    With ljCombo
      lviIndex = SrchComboList(.hWnd, lvsKeyString, False)
      If lviIndex <> Item_Not_Found Then
        '.SetFocus
        .ListIndex = -1
        .ListIndex = lviIndex
        .SelStart = lviKeyLen
        .SelLength = Len(.Text) - lviKeyLen
      End If
    End With
  End If
End Sub

Public Function SrchComboList(hWnd As Long, _
                                  lvsFind As String, _
                                  Optional fExact As Boolean = False) As Long
  'Finds an entry in a combo box that matches the specified prefix.
  'This search is not case-sensitive.
  'Returns:
  'if found - a valid listindex
  'Else -1
  Dim lvlSearchType As Long
 
  If Len(lvsFind) = 0 Then
    SrchComboList = Item_Not_Found
    Exit Function
  End If
 
  Select Case fExact
    Case True
      lvlSearchType = CB_FINDSTRINGEXACT
     Case False
      lvlSearchType = CB_FINDSTRING
  End Select
  SrchComboList = SendMessageByString(hWnd, lvlSearchType, -1, lvsFind)
End Function


Private Declare Function SendMessageByString Lib "user32" Alias "SendMessageA" (ByVal hWnd As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As String) As Long
Hey AZ!

If you need a quick patch that works, I've got one for you.

Place a textbox without a border on the combo.  Align it accordingly so that it is not noticable, and the numbers of both the textbox and combobox line up.  Then in code add the following

Private Sub Combo1_Change()
  Text1.Text = Combo1.Text
End Sub

Private Sub Combo1_Click()
  Text1.Text = Combo1.Text
End Sub

It will look and feel like your combo, but will not change the value when you hit the dropdown method.

Thoughts?
Avatar of AzraSound

ASKER

thanks Dave!  under normal conditions, this would definitely be a suitable solution, but due to the nature of the UI, this is a bit difficult.  because i have this "property list" of a bit more than 50 entries, i have a bit of code in place for dynamically positioning a textbox, or combobox, or even another grid depending on the type of data that particular "property", or piece of data, holds.  i do see how i can go back and rewrite a lot of the UI code to use this textbox workaround, which i think i would just do for every combobox entry since it shouldnt affect any of those entries that do not suffer from this problem.  if nothing better appears, i guess i will just have to do this.  keep the suggestions coming!
you will save yourself a lot of time and your company a lot of money(paying you to figure this out) by just buying one.  I have used true dbcombo and like it.  sheridan also has one.
Can you make .Text at least 2 chars long by adding space?  i.e. replace:
        .Text = "3"
with:
        .Text = ""
        .SelText = " 3"
ASKER CERTIFIED SOLUTION
Avatar of ameba
ameba
Flag of Croatia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
>>I have used true dbcombo and like it.  sheridan also has one.

do they offer some sort of property that turns this "feature" on/off?  can you give me some costs for these components?


>>Can you make .Text at least 2 chars long by adding space?

very interesting idea ameba!  i'll play with it and see if it'll work.
> keep the suggestions coming!

OK, add ImageCombo control (MS Windows Common Controls)

Private Sub Form_Load()
    With ImageCombo1.ComboItems
        .Add , , "12.34"
        .Add , , "23.45"
        .Add , , "34.56"
        ImageCombo1.Text = "3"
    End With
End Sub
Open a form in a new vb project and add a combo box "Combo1" and paste these code


Option Explicit
Private Const CB_FINDSTRING = &H14C
Private Const CB_ERR = -1


Private Declare Function SendMessage Lib "user32" Alias "SendMessageA" (ByVal hwnd As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long

Public Sub AutoMatch(cbComboBox As ComboBox, KeyAscii As Integer)
    Dim Idx As Long, pos As Integer
    Dim strSearch As String
   
    With cbComboBox
        If KeyAscii = vbKeyBack Then
            If .SelStart > 1 Then
                strSearch = Mid(.Text, 1, .SelStart - 1)
            Else
                strSearch = ""
            .Text = ""
            End If
        Else
            strSearch = Mid(.Text, 1, .SelStart) & Chr(KeyAscii)
        End If
       
        'Call SendMessage to get the Index of an item (if any)
        Idx = SendMessage(.hwnd, CB_FINDSTRING, -1, ByVal strSearch)
        'If idx equals CB_ERR, then the search failed
        If Idx > CB_ERR Then            'Store the caret position
            pos = Len(strSearch)
            .Text = .List(Idx)
            .ListIndex = Idx            ' Re-highlight the trailing text
            .SelStart = pos
            .SelLength = Len(.Text) - pos
            KeyAscii = 0
        End If
    End With

End Sub

Private Sub Combo1_KeyPress(KeyAscii As Integer)
    Call AutoMatch(Combo1, KeyAscii)
End Sub

Private Sub Form_Load()
    With Combo1
        .AddItem "Shaheen"
        .AddItem "Shelly"
        .AddItem "Jaimie"
        .AddItem "Patrick"
        .AddItem "Rose"
        .AddItem "Rob"
        .AddItem "Chris"
        .AddItem "Peter"
    End With
End Sub
yeah, ImageCombo doesnt look like it has this problem, but it also looks like its using a ListView for its dropdown.  but i am also using some APIs to allow for autocompletion, changing the dropdown height/width, etc, so it probably isnt very practical to change to an ImageCombo.

the idea to prepend with a single space appears promising...i will need to do some more tests against some input data and saving to an output file to make sure it isnt going to cause any new problems.
for those posting auto-completion code, please read my question carefully and realize that this is not what i am looking for...thanks.
Did u try mine. What is ur question. Can u elaborate it?
>>What is ur question. Can u elaborate it?

start a new project, add a combobox to your form, and paste this code:

Private Sub Form_Load()
   With Combo1
       .AddItem "12.34"
       .AddItem "23.45"
       .AddItem "34.56"

       .Text = "3"
   End With
End Sub


now run the project.  when the form shows up, the number "3" shows in the combobox.  click the arrow to dropdown the combobox's contents, and "34.56" will appear highlighted and become the combobox's new text value (before it is manually selected - this is the default behavior of a combobox).  i do NOT want this to happen.  i want to be able to have the number "3" remain as the combobox's text even after its list of items are dropped down.
Avatar of Ryan Chong
Hi AzraSound,

Maybe a bad suggestion here: use timer to change the combobox's text when drop-down?
sheridan's you can turn it off and on
price is 295 usd

it has a data combo, grid, option buttons, command buttons and enhanced dbcombo.  they specalize in databinding, but you can use the additem property just like a regular combo.
this is the most painless and cheapest solution, and appears to work well.  thanks to all others for their input.
Thanks.

Yes, I tried ImageCombo control and it doesn't work with Size_Combo function, so dropdown height/width cannot be set very easy.

Maybe you can disable small combo button, or provide your own button, or just tell users
to use keyboard keys (arrows, F4), since that small button has autosearch 'feature'

Here is the code (I modified my combo wrapper class which allows strings for ItemData, and few other 'corrections' - e.g. original combo doesn't raise Click when needed)

' Form1, add 2 textboxes
Option Explicit
Private WithEvents wcb As cwCbo

Private Sub Form_Load()
    ' our combo
    Set wcb = New cwCbo
    Set wcb.Combo = Combo1
    With wcb
        .AddItem "12.34", "0"
        .AddItem "23.45", "1"
        .AddItem "34.56", "2"
        Combo1.Text = "3"
    End With

    ' normal combo
    With Combo2
        .AddItem "12.34"
        .AddItem "23.45"
        .AddItem "34.56"
        .Text = "3"
    End With
   
    Show
    Print "To open dropdown press:", "  down arrow, F4 or up arrow"
    Print "To close, press:", "  F4, Enter, or Escape"
End Sub

Private Sub Form_Unload(Cancel As Integer)
    Set wcb = Nothing
End Sub

Private Sub wcb_Clicked(ItemData As String)
    Caption = Combo1.Text
End Sub



' class cwCbo, wrapper for combobox
'
' Usage
'-----------------------------
' in form declarations:
' Private WithEvents wcb As cwcbo
' in Form_Load:
'    Set wcb = New cwCbo
'    Set wcb.Combo = Combo1
' in Form_Unload:
'    Set wcb = Nothing
'-----------------------------
Option Explicit
Private Declare Function LockWindowUpdate Lib "user32" (ByVal hwndLock As Long) As Long
Private Declare Function SendMessageAny Lib "user32" Alias "SendMessageA" (ByVal hwnd _
    As Long, ByVal wMsg As Long, ByVal wParam As Long, lParam As Any) As Long
Private Declare Function SendMessageString Lib "user32" Alias "SendMessageA" (ByVal hwnd _
    As Long, ByVal wMsg As Long, ByVal wParam As Long, ByVal lParam As String) As Long
Private Const CB_GETDROPPEDSTATE = &H157
Private Const CB_SHOWDROPDOWN = &H14F
Private Const CB_FINDSTRING = &H14C
Private Const CB_FINDSTRINGEXACT = &H158
Private Const CB_ADDSTRING = &H143
Private Const WM_SETREDRAW = &HB
'
Public WithEvents Combo As VB.ComboBox
Public Event Clicked(ItemData As String)
Private loading As Boolean
Private oldText As String
Private keys() As String
Private numitems As Long
Private hwnd As Long

Private Sub Combo_Click()
    On Error Resume Next
    If loading = False Then
        If SendMessageAny(Combo.hwnd, CB_GETDROPPEDSTATE, 0&, 0&) = 0 Then
            If Combo.ListIndex > -1 Then RaiseEvent Clicked(keys(Combo.ListIndex))
        End If
    End If
End Sub

Private Sub Combo_KeyDown(KeyCode As Integer, Shift As Integer)
        Dim lret As Long, sav As String, savpos As Long, savlen As Long
    On Error Resume Next
    If KeyCode = vbKeyF4 Or KeyCode = vbKeyDown Or KeyCode = vbKeyUp Then
        If Shift = 0 Then
            sav = Combo.Text
            savpos = Combo.SelStart
            savlen = Combo.SelLength
            If SendMessageAny(Combo.hwnd, CB_GETDROPPEDSTATE, 0&, 0&) = 0 Then
                KeyCode = 0
                SetCB True, sav, savpos, savlen
            ElseIf KeyCode = vbKeyF4 Then
                KeyCode = 0
                SetCB False, sav, savpos, savlen
                SelectIfExact
            End If
        End If
    ElseIf KeyCode = vbKeyEnd Then
        If Shift = 2 Then
            Combo.ListIndex = Combo.ListCount - 1
            KeyCode = 0
        End If
    ElseIf KeyCode = vbKeyHome Then
        If Shift = 2 Then
            Combo.ListIndex = 0
            KeyCode = 0
        End If
    ElseIf KeyCode = vbKeyReturn Then
        sav = Combo.Text
        savpos = Combo.SelStart
        savlen = Combo.SelLength
        lret = SendMessageAny(Combo.hwnd, CB_GETDROPPEDSTATE, 0&, 0&)
        If lret = 1 Then
            KeyCode = 0
            SetCB False, sav, savpos, savlen
        End If
        SelectIfExact
    ElseIf KeyCode = vbKeyEscape Then
        lret = SendMessageAny(Combo.hwnd, CB_GETDROPPEDSTATE, 0&, 0&)
        If lret = 1 Then
            KeyCode = 0
            SetCB False, oldText, 0, Len(oldText)
        End If
        SelectIfExact
    End If
End Sub

Public Sub SetCB(ShowDropped As Boolean, Text As String, Start As Long, Length As Long)
    Dim lret As Long
   
    loading = True
    Redraw False
    lret = SendMessageAny(Combo.hwnd, CB_SHOWDROPDOWN, ShowDropped, 0&)
    Combo.Text = Text
    Combo.SelStart = Start
    Combo.SelLength = Length
    Redraw True
    loading = False
    If ShowDropped Then oldText = Text
End Sub

Public Sub SelectIfExact()
    If Combo.ListIndex > -1 Then
        RaiseEvent Clicked(keys(Combo.ListIndex))
    Else
        Combo.ListIndex = SendMessageString(Combo.hwnd, CB_FINDSTRINGEXACT, -1, Combo.Text)
    End If
End Sub

Public Sub Clear()
    Erase keys
    numitems = 0
    Combo.Clear
End Sub

Public Sub AddItem(ByVal Text As String, Key As String)
    On Error Resume Next
    numitems = numitems + 1
    If numitems Mod 100 = 1 Then
        If hwnd = 0 Then hwnd = Combo.hwnd
        ReDim Preserve keys(numitems + 100)
    End If
    keys(numitems - 1) = Key
    'Combo.AddItem Text                             ' non-API version
    SendMessageString hwnd, CB_ADDSTRING, 0&, Text  ' API version
End Sub

Public Property Get ItemData() As String
    If Combo.ListIndex > -1 Then ItemData = keys(Combo.ListIndex)
End Property

Public Sub Redraw(NewRedraw As Boolean)
    If hwnd = 0 Then hwnd = Combo.hwnd
    SendMessageAny hwnd, WM_SETREDRAW, -NewRedraw, 0&
End Sub
>' Form1, add 2 textboxes
should be:
' Form1, add 2 ComboBoxes    :-)
thanks for that ameba.  when i have some time, i will see if i can incorporate it into the project.
I have set up an Auto-Select combobox that drills down the values in the combo, shows you how many values you've typed in, responds to the backspace and any other keys you define and will also find by first character if the same key is pressed twice at the beginning.  I just have partial code, and the comboboxes are particular to my app, however I believe you can get the idea.

using System;
using System.Windows.Forms;

namespace drillcombo
{
  public class drillcombo : System.Windows.Forms.Form
  {  
    public string currentcbo = ""; //current combobox
    public int combokeycnt = 0;  //number of acceptable keystrokes entered
    public DateTime combolastkeyed = ( DateTime.Now ); //last time a key was entered
   
      private void InitialzeComponent()
    {
      //example event declaration
      this.cbosub.KeyDown += new KeyEventHandler(combo_KeyDown);
      this.cbosub.KeyPress += new KeyPressEventHandler(this.combo_KeyPress);
    }

    //Actual combobox code
    private void combo_KeyDown(object sender, System.Windows.Forms.KeyEventArgs e)
    {
      //GetCombo() converts sender into the actual combox that sent it.
      //if new combobox, reset key count.
      if ( GetCombo(sender).Name != currentcbo )
      {
        combokeycnt = 0;
      }
     
      //get the actual combobox being used so we don't have to make so many calls
      //to GetCombo()
      System.Windows.Forms.ComboBox cbodummy = GetCombo( sender );
      //if item is not a combobox, exit, we don't need it.
      if ( cbodummy == null )
        return;
     
      //Reset key count if wait between keys has been longer than 10 seconds.
      if (combokeycnt != 0 && (combolastkeyed.Subtract(DateTime.Now)).Seconds > 10 )
      {
        combokeycnt = 0;
       
      }
      //Reset the time between keys
      combolastkeyed = DateTime.Now;
 
      //If backspace, decrement the count and reduce the combo selection length
      if (e.KeyCode == Keys.Back && combokeycnt != 0)
      {
        combokeycnt--;
        cbodummy.SelectionLength = combokeycnt;
      }
      else //otherwise get the keydata
      {
        string keychar = "";
       
        //if the (string)KeyData has length 1, pass it.
        if (e.KeyData.ToString().Length == 1)
        {
          keychar = e.KeyData.ToString().ToLower();
        }
        else  //otherwise filter out the values we can use and return for anything else
        {
          switch ( e.KeyCode )
          {
            case Keys.Space:
              keychar = " ";
              break;
            case Keys.OemMinus:
              keychar = "-";
              break;
            case Keys.Oemcomma:
              keychar = ",";
              break;
            case Keys.OemSemicolon:
              keychar = ";";
              break;
            default:
              return;
          }
        }
       
        //Append key value to the text entered so far determined by getting the
        //combokeycount from the currently selected text in the combobox
        string findchars = cbodummy.Text.Substring( 0, combokeycnt ) + keychar;
       
        //if first letter is a repeat, jump to the next index that starts with
        //that same letter.  Reset combokeycount to check only first letter
        if ( findchars.Length == 2 && cbodummy.Text.Substring(0,1) == keychar )
        {
          findchars = keychar;
          combokeycount = 0;
        }
        //find the string in the combo list
        int combindex = cbodummy.FindString( findchars );
        //if comboindex != -1 (failure), select the found item and increase the
        //selection length
        if ( combindex >= 0 )
        {
          combokeycnt++;
          GetCombo(sender).SelectedIndex = combindex;
          GetCombo(sender).SelectionLength = combokeycnt;
        }    
      }
      //Handling the keys to prevent actuall typing into the combobox is done
      //in the KeyPress event
    }
 
    private void combo_KeyPress(object sender, System.Windows.Forms.KeyPressEventArgs e)
    {
      //Verify it's a combobox and then handle the key so no value actually goes
      //into the combo.
      if (GetCombo( sender ) != null)
        e.Handled = true;
    }
 
 
    private System.Windows.Forms.ComboBox GetCombo(object sender)
    {
      //takes the sender object and returns which combo it is.  Brute force,
      //I know, but it works.  Tried a swtich and it didn't, so...
      if ( sender == this.cbocomp )
      {
        return this.cbocomp;
      }
      else if ( sender == this.cbocreate)
      {
        return this.cbocreate;
      }
      else if ( sender == this.cboproduct)
      {
        return this.cboproduct;
      }
      else if ( sender == this.cbosub)
      {
        return this.cbosub;
      }
      else
      {
        return null;
      }
    }
  }
}


I hope this makes sense.  Let me know if there are any questions.

Ross Elbling
Just having fun programming


 
I have a question...why are you posting C# code in a Visual Basic (non .NET) thread?
Yeah.  Doh!  I thought I limited my search to C# only.  Please disregard actual code and look at the ideas instead, if they apply.
see EE Guidelines, Asking Questions http:/help.jsp#hi46

or use this short version (ameba's guidelines :)

How do I ask a question
1) Select the right TA http:/allTopics.jsp
2) Click "Ask a question" (or "Ask an expert") link and follow instructions
Just giving you a hard time...no worries.
None at all. 8^)>