Link to home
Start Free TrialLog in
Avatar of wtconway
wtconway

asked on

ComboBox automatically select item on key press

Hello,

I have a combobox that once you enter the control, sets the .DroppedDown property to True so it will expand the box to show the contents. Then a user can start typing stuff in and it will scroll to any matches in the box. From there I want the user to be able to hit the ENTER key and the top item be "selected."

I've tried doing this and it appears to be working; however, I only sets the .Text property to whatever the top item is and doesn't actually affect the .SelectedText or .SelectedValue (which is ultimately what I care about).

So what this means is that in order for the top shown item to be "selected" I have to hit the DOWN ARROW and then ENTER for it to process the selection change and modify the appropriate properties.

Any ideas on how I can eliminate the DOWN ARROW step? I have not overridden the combobox keypress eventhandler in anyway. Here's the Handler I have for it:

    Private Sub cbo_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles cboFrom.KeyDown, cboTo.KeyDown
        Dim cbo As ComboBox = CType(sender, ComboBox)
        If e.KeyCode = Keys.Enter Then
            cbo.DroppedDown = False
            If cbo.Name = "cboFrom" Then cboTo.Focus() 'Move the focus to the next object
            If cbo.Name = "cboTo" Then txtAmount.Focus() 'Move the focus to the next object
        Else
            If cbo.DroppedDown = False Then
                cbo.DroppedDown = True
            End If
        End If
    End Sub

Thank you in advance.
Avatar of Sancler
Sancler

Just typing in text does not actually alter the selectedText or selectedValue.  What you need to do is use cbo.FindString to locate the first item in the combobox that matches the .Text and set the selected index to that.  Like this

    Dim index As Integer
    index = cbo.FindString(cbo.Text)
    cbo.SelectedIndex = index

Roger
Include the following class in the solution you need auto-complete combos in:


--- 8< ---
Public Class CAutoCompleteCombo
   'This variable will keep track of whether to skip the operation because
   'of a special key having been pressed.
   Private Shared mfSkip As Boolean = False

   'Determine whether any special key was pressed.
   Public Shared Sub KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs)
      'Retrieve a reference to the ComboBox that sent this event.
      Dim Combo As ComboBox = CType(sender, ComboBox)

      If Asc(e.KeyChar) = Keys.Escape Then
         'Clear the text.
         Combo.SelectedIndex = -1
         Combo.Text = ""
         mfSkip = True
      ElseIf Char.IsControl(e.KeyChar) Then
         mfSkip = True
      Else
         mfSkip = False
      End If
   End Sub

   'Perform the text substituion.
   Shared Sub TextChanged(ByVal sender As Object, ByVal e As System.EventArgs)
      'Retrieve a reference to the ComboBox that sent this event.
      Dim Combo As ComboBox = CType(sender, ComboBox)

      If Combo.Text <> "" And Not mfSkip Then
         'Search for a matching entry.
         Dim MatchText As String = Combo.Text
         Dim Match As Integer = Combo.FindString(MatchText)

         'If a matching entry is found, insert it now.
         If Match <> -1 Then
            Combo.SelectedIndex = Match

            'Select the added text so it can be replaced
            'if the user keeps typing.
            Combo.SelectionStart = MatchText.Length
            Combo.SelectionLength = Combo.Text.Length - Combo.SelectionStart
         End If
      End If
   End Sub
End Class
--- 8< ---

Note that this code is not my own, but I - sadly - don't know who has written it.

Anyway, in order for the above to be working, you'll need to have your combos' appropriate events be handled by that class.
So, in your form's Load-event, add the handlers like this:
      AddHandler cboYourCombo.KeyPress, AddressOf CAutoCompleteCombo.KeyPress
      AddHandler cboYourCombo.TextChanged, AddressOf CAutoCompleteCombo.TextChanged

Et oilá, you're ready to go. That is, if I understood your question right anyway ... :-)

Cheers & HTH,
Olaf
Avatar of Howard Cantrell
Sample code for a combobox type ahead and a piece for colored combox lines....

'FORM 1

Public Class ctlComboBoxTypeAhead
    Inherits System.Windows.Forms.Form
    Public Declare Function SendMessageByString Lib "user32" Alias "SendMessageA" (ByVal hWnd As IntPtr, ByVal wMsg As Int32, ByVal wParam As Int32, ByVal lParam As String) As Int32
    Public Const CB_FINDSTRING As Integer = &H14CS
    Public Const CB_FINDSTRINGEXACT As Integer = &H158S
#Region " Windows Form Designer generated code "

    Public Sub New()
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call
        InitializeCombobox()

    End Sub

    'Form overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
    Friend WithEvents ComboBox1 As System.Windows.Forms.ComboBox
    Friend WithEvents cbo As System.Windows.Forms.ComboBox
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        Me.ComboBox1 = New System.Windows.Forms.ComboBox
        Me.cbo = New System.Windows.Forms.ComboBox
        Me.SuspendLayout()
        '
        'ComboBox1
        '
        Me.ComboBox1.DrawMode = System.Windows.Forms.DrawMode.OwnerDrawFixed
        Me.ComboBox1.DropDownStyle = System.Windows.Forms.ComboBoxStyle.DropDownList
        Me.ComboBox1.Location = New System.Drawing.Point(16, 16)
        Me.ComboBox1.Name = "ComboBox1"
        Me.ComboBox1.Size = New System.Drawing.Size(232, 21)
        Me.ComboBox1.TabIndex = 0
        '
        'cbo
        '
        Me.cbo.Location = New System.Drawing.Point(16, 88)
        Me.cbo.Name = "cbo"
        Me.cbo.Size = New System.Drawing.Size(232, 21)
        Me.cbo.TabIndex = 1
        Me.cbo.Text = "ComboBox2"
        '
        'ctlComboBoxTypeAhead
        '
        Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
        Me.ClientSize = New System.Drawing.Size(292, 135)
        Me.Controls.Add(Me.cbo)
        Me.Controls.Add(Me.ComboBox1)
        Me.Name = "ctlComboBoxTypeAhead"
        Me.Text = "ctlComboBoxTypeAhead"
        Me.ResumeLayout(False)

    End Sub

#End Region
    Private Sub ComboBox1_KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles ComboBox1.KeyPress
        Dim KeyAscii As Short = Asc(e.KeyChar)
        Select Case KeyAscii
            Case System.Windows.Forms.Keys.Back

            Case Is >= 32
                'perform auto search
                If ComboBox1.SelectionLength > 0 Then
                    ComboBox1.SelectedText = Chr(KeyAscii)
                Else
                    ComboBox1.Text += Chr(KeyAscii)
                End If
                KeyAscii = 0
                Call TypeAheadCombo(ComboBox1)
        End Select

        If KeyAscii = 0 Then
            e.Handled = True
        End If
    End Sub
    Private Sub InitializeCombobox()
        With ComboBox1
            .Items.Clear()
            .Items.Add("one")
            .Items.Add("two")
            .Items.Add("other")
            .Items.Add("new")
            .Items.Add("newer")
            .Items.Add("newbee")
            .Items.Add("safe")
            .Items.Add("error")
            .Items.Add("apple")
            .Items.Add("alter")
            .Items.Add("ables")
            .Items.Add("absents")
        End With
        With cbo
            .Items.Clear()
            .Items.Add("one")
            .Items.Add("two")
            .Items.Add("other")
            .Items.Add("new")
            .Items.Add("newer")
            .Items.Add("newbee")
            .Items.Add("safe")
            .Items.Add("error")
            .Items.Add("apple")
            .Items.Add("alter")
            .Items.Add("ables")
            .Items.Add("absents")
        End With
    End Sub
    Public Sub TypeAheadCombo(ByVal ljCombo As Object)
        Dim lviIndex As Short
        Dim lvsKeyString As String
        Dim lviKeyLen As Short

        lvsKeyString = ljCombo.Text
        lviKeyLen = lvsKeyString.Length
        If lviKeyLen > 0 Then
            With ljCombo
                lviIndex = SrchComboList(.Handle, lvsKeyString, False)
                If lviIndex <> -1 Then
                    .SelectedIndex = -1
                    .SelectedIndex = lviIndex
                    .SelectionStart = lviKeyLen
                    .SelectionLength = Len(.Text) - lviKeyLen
                End If
            End With
        End If
    End Sub
    Public Function SrchComboList(ByVal hWnd As IntPtr, ByVal lvsFind As String, Optional ByVal fExact As Boolean = False) As Short
        '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 Short

        If Len(lvsFind) = 0 Then
            SrchComboList = -1
            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
    'cbo is the comboBox control
    Private Sub cbo_KeyUp(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles cbo.KeyUp
        AutoCompleteCombo_KeyUp(sender, e)
    End Sub
    '********************* Better way of doing Type Head *************************
    Public Shared Sub AutoCompleteCombo_KeyUp(ByVal cbo As ComboBox, ByVal e As KeyEventArgs)
        '**************
        ' you can use it 2 ways.

        'either add the handles keyword after the sub name to handle your KeyUp event
        'eg. Public Shared Sub AutoCompleteCombo_KeyUp(ByVal cbo As ComboBox, ByVal e As KeyEventArgs) Handles cboSuburb.KeyUp

        'or

        'i use it as a generic sub so call it from the KeyUp event as follows:

        '  Private Sub cboSuburb_KeyUp(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles cboSuburb.KeyUp
        '            AutoCompleteCombo_KeyUp(sender, e)
        '        End Sub
        '**************
        Dim sTypedText As String
        Dim iFoundIndex As Integer
        Dim oFoundItem As Object
        Dim sFoundText As String
        Dim sAppendText As String

        'Allow select keys without Autocompleting
        Select Case e.KeyCode
            Case Keys.Back, Keys.Left, Keys.Right, Keys.Up, Keys.Delete, Keys.Down
                Return
        End Select

        'Get the Typed Text and Find it in the list
        sTypedText = cbo.Text
        iFoundIndex = cbo.FindString(sTypedText)

        'If we found the Typed Text in the list then Autocomplete
        If iFoundIndex >= 0 Then

            'Get the Item from the list (Return Type depends if Datasource was bound
            ' or List Created)
            oFoundItem = cbo.Items(iFoundIndex)

            'Use the ListControl.GetItemText to resolve the Name in case the Combo
            ' was Data bound
            sFoundText = cbo.GetItemText(oFoundItem)

            'Append then found text to the typed text to preserve case
            sAppendText = sFoundText.Substring(sTypedText.Length)
            cbo.Text = sTypedText & sAppendText

            'Select the Appended Text
            cbo.SelectionStart = sTypedText.Length
            cbo.SelectionLength = sAppendText.Length

        End If
    End Sub

    Private Sub ComboBox1_DrawItem(ByVal sender As Object, ByVal e As System.Windows.Forms.DrawItemEventArgs) Handles ComboBox1.DrawItem
        Dim c As System.Drawing.Color

        If e.Index < 0 Then
            e.DrawBackground()
            e.DrawFocusRectangle()
            Exit Sub
        End If
        'Determine colour to use
        If e.Index Mod 2 = 0 Then
            c = Color.Pink
        Else
            c = Color.Gray
        End If

        e.DrawBackground()
        e.DrawFocusRectangle()

        e.Graphics.DrawRectangle(New Pen(c), New Rectangle(e.Bounds.X, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height))
        e.Graphics.FillRectangle(New SolidBrush(c), New RectangleF(e.Bounds.X, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height))
        e.Graphics.DrawString(Me.ComboBox1.Items.Item(e.Index), Me.ComboBox1.Font, New SolidBrush(Color.Black), New RectangleF(e.Bounds.X, e.Bounds.Y, e.Bounds.Width, e.Bounds.Height))
    End Sub
End Class
AutoComplete autoExpand ComboBox:


Option Strict On
Option Explicit On

Imports System.Windows.Forms

Public Class AutoCompleteCombo
    Inherits ComboBox
    Private mResetOnClear As Boolean = False

    Protected Overrides Sub RefreshItem(ByVal index As Integer)
        MyBase.RefreshItem(index)
    End Sub

    Protected Overrides Sub SetItemsCore(ByVal items As System.Collections.IList)
        MyBase.SetItemsCore(items)
    End Sub

    Public Shadows Sub KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles MyBase.KeyPress
        Dim intIndex As Integer
        Dim strEntry As String

        Me.DroppedDown = True

        If Char.IsControl(e.KeyChar) Then
            If MyBase.SelectionStart <= 1 Then
                If mResetOnClear Then
                    MyBase.SelectedIndex = 0
                    MyBase.SelectAll()
                Else
                    MyBase.Text = String.Empty
                    MyBase.SelectedIndex = -1
                    MyBase.SelectedIndex = -1
                End If
                e.Handled = True
                Exit Sub
            End If
            If MyBase.SelectionLength = 0 Then
                strEntry = MyBase.Text.Substring(0, MyBase.Text.Length - 1)
            Else
                strEntry = MyBase.Text.Substring(0, MyBase.SelectionStart - 1)
            End If
        ElseIf (Not Char.IsLetterOrDigit(e.KeyChar)) And (Not Char.IsWhiteSpace(e.KeyChar)) Then  '< 32 Or KeyAscii > 127 Then
            Exit Sub
        Else
            If MyBase.SelectionLength = 0 Then
                strEntry = UCase(MyBase.Text & e.KeyChar)
            Else
                strEntry = MyBase.Text.Substring(0, MyBase.SelectionStart) & e.KeyChar
            End If
        End If

        intIndex = MyBase.FindString(strEntry)

        If intIndex <> -1 Then
            MyBase.SelectedIndex = intIndex
            MyBase.SelectionStart = strEntry.Length
            MyBase.SelectionLength = MyBase.Text.Length - MyBase.SelectionStart
        End If
        e.Handled = True
        Exit Sub
    End Sub

    'Make the Enter key work like the Tab key
    Protected Overrides Function ProcessCmdKey(ByRef msg As System.Windows.Forms.Message, ByVal keyData As System.Windows.Forms.Keys) As Boolean

        If msg.WParam.ToInt32() = CInt(Keys.Enter) Then
            SendKeys.Send("{Tab}")
            Return True
        End If

        Return MyBase.ProcessCmdKey(msg, keyData)

    End Function

    Public Property ResetOnClear() As Boolean
        Get
            Return mResetOnClear
        End Get
        Set(ByVal Value As Boolean)
            mResetOnClear = Value
        End Set
    End Property

End Class
Avatar of wtconway

ASKER

I really like the suggestion amyhxu presented. However, I cannot seem to get it to work properly. I have created a new class with the content of her class and have not altered it in any way.

I have another class that writes out my own control which is just two comboboxes that use the AutoCompleteCombo control like so:

    Friend WithEvents cboTo As AutoCompleteCombo
    Friend WithEvents cboFrom As AutoCompleteCombo
    Me.cboTo = New AutoCompleteCombo
    Me.cboFrom = New AutoCompleteCombo

I have no event handlers that work with cboFrom or cboTo; yet I cannot figure out why the code would not work. I can see the ComboBox drop down its contents and navigate to a certain item once I start typing and it even goes to the next combobox when I hit {ENTER}, but it just won't select the top item nor the item in the .Text property.
I'm a little confused when you were saying that you want to select the top item. What is a top item? When a user types something into the combobox, the first matching item in the list will be highlighted. And when the user hits ENTER key, that highlighted item is selected, and the corresponding .Text and .SelectedValue property changes. Note that only the items in the list can be selected. This is how the combobox I provided would work.

If the behavior I described is what you want, can you try creating a new project with a form and the AutoCompleteCombo class and only add the combobox to the form to see how it works (also make sure you are using .NET framework 1.1 and service pack 1). If it's not what you expected, can you explain the top item to me?
What I mean by top item is exactly what you said in the first paragraph. For example, the item I'm looking for is '3456-John Doe'

I want the user to be able to start typing '345' and it automatically navigate to '3456-John Doe'. If the user hits enter; however, nothing gets changed. But I can see the selection rectangle being drawn over the item (the blue rectangle that changes the item text color to white).

How would I determine which version and sp of .NET I'm running? And I'm assuming that I have to be running that on the development AND client machines?
Concerning the auto-complete without actually selecting the entry - this might be feasible if you derive your control. However, it won't be the smartest approach as you'll have to display the user's input somewhere, and with the typed in keys overwriting the current value without actually changing it, nobody would know about the original one. With that in mind, you'll be striving away from what is considered the general and/or ergonomical and/or intuitive way of handling user-input resp. UI-design.

Concerning the how-to for finding the .NET-version and SP-status: There's no way except for checking the win-registry.

Check this path:
     HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\NET Framework Setup\NDP\v1.1.4322
Keys therein:
- Install: Will be $1 if the framework has been installed
- SP: Will be $1 if SP1 has been installed

Under the NDP-path the framework-installations should be found. I don't have either 1.0 nor 2.0 installed, so somebody might have to confirm, but I guess MS learned a little about continuity anway ... :-)
Control Panel --> Add Or Remove Programs --> Microsoft .NET Framework 1.1  (SP1 is shown as Hotfix, don't remember the exact number, will tell you tomorrow). You should always make sure both development machine and client machines have Microsoft .NET Framework 1.1 and SP1. That's the basic requirement for running .NET applications. They come in as  Windows Update (Critical Update).

Seems you need to handle special characters other than the numbers, letters and space, i.e. "-". Do you have more special characters in the combobox items? Can you list them for me? The combobox class needs a little modification to handle those characters. I can send you the modified code if you give me the list of them.

BTW, did you try creating a very simple app with only one combobox as I suggested in my last post?


Another way for check framework and sp:

Open IE --> Tools --> Windows Update will lead you to the available critical updates page. If you don't see Microsoft .NET framework 1.1 and SP1 in that list, that means your computer already has them. NOTE: this only applies to Win XP machines, other versions of operating system like Win98 or Win98 SE will list them as normal updates instead of critical ones.
SP1 in control panel is called Microsoft .NET Framework 1.1 Hotfix (KB886903)
Ok I do have v1.1.4322 installed. I'm about to create a simple application that uses the drop down class you provided, amyhxu: hopefully it will work. Below are some examples of what my drop down box would contain:

Select One
1234-009B-First Union
2345-009C-Palmetto Bank
3456-009E-CCB
4567-009F-Wachovia
5678-009G-Bank Of America
6789-009H-Southtrust-ACH
I have some results for you. I could not get the small application to work with simple alphanumeric characters populating the comboboxes. I'm posting my code for the form (minus the design generated code), as well as my custom class that puts two of your comboboxes inside a panel (creating a row). Please take a look at it at your convenience. Thank you.

#Region "TheForm"

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click 'Text="Create Row"
        row = New cboRow
        Dim i As Integer
        row.cboFrom.Items.Add("Select One") : row.cboTo.Items.Add("Select One")
        For i = 1 To 20
            'just adding some random stuff
            row.cboFrom.Items.Add("Test" & (i * 2 + 33).ToString())
            row.cboTo.Items.Add("Test" & (i * 2 + 33).ToString())
        Next
        row.Top = Button1.Top + Button1.Height + 20
        row.Left = Button1.Left
        Me.Controls.Add(row)
    End Sub

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click 'Text="Read Values"
        MsgBox(row.cboFrom.SelectedIndex)
    End Sub
#End Region

#Region "My Class"
Public Class cboRow
    Inherits System.Windows.Forms.UserControl


#Region " Windows Form Designer generated code "

    Public Sub New()
        MyBase.New()

        'This call is required by the Windows Form Designer.
        InitializeComponent()

        'Add any initialization after the InitializeComponent() call

    End Sub

    'UserControl overrides dispose to clean up the component list.
    Protected Overloads Overrides Sub Dispose(ByVal disposing As Boolean)
        If disposing Then
            If Not (components Is Nothing) Then
                components.Dispose()
            End If
        End If
        MyBase.Dispose(disposing)
    End Sub

    'Required by the Windows Form Designer
    Private components As System.ComponentModel.IContainer

    'NOTE: The following procedure is required by the Windows Form Designer
    'It can be modified using the Windows Form Designer.  
    'Do not modify it using the code editor.
    Friend WithEvents pnl As System.Windows.Forms.Panel
    Friend WithEvents txtAmount As System.Windows.Forms.TextBox
    Friend WithEvents cboTo As AutoCompleteCombo
    Friend WithEvents cboFrom As AutoCompleteCombo
    Friend WithEvents btnDelete As System.Windows.Forms.Button
    <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
        Me.pnl = New System.Windows.Forms.Panel
        Me.txtAmount = New System.Windows.Forms.TextBox
        Me.cboTo = New AutoCompleteCombo
        Me.cboFrom = New AutoCompleteCombo
        Me.btnDelete = New System.Windows.Forms.Button
        Me.pnl.SuspendLayout()
        Me.SuspendLayout()
        '
        'Panel1
        '
        Me.pnl.Controls.Add(Me.btnDelete)
        Me.pnl.Controls.Add(Me.txtAmount)
        Me.pnl.Controls.Add(Me.cboTo)
        Me.pnl.Controls.Add(Me.cboFrom)
        Me.pnl.Dock = System.Windows.Forms.DockStyle.Top
        Me.pnl.Location = New System.Drawing.Point(0, 0)
        Me.pnl.Name = "Panel1"
        Me.pnl.Size = New System.Drawing.Size(584, 40)
        Me.pnl.TabIndex = 1
        '
        'txtAmt
        '
        Me.txtAmount.Location = New System.Drawing.Point(408, 8)
        Me.txtAmount.Name = "txtAmt"
        Me.txtAmount.Size = New System.Drawing.Size(80, 21)
        Me.txtAmount.TabIndex = 2
        Me.txtAmount.TextAlign = HorizontalAlignment.Right
        '
        'cboTo
        '
        Me.cboTo.Location = New System.Drawing.Point(208, 8)
        Me.cboTo.Name = "cboTo"
        Me.cboTo.Size = New System.Drawing.Size(160, 21)
        Me.cboTo.TabIndex = 1
        '
        'cboFrom
        '
        Me.cboFrom.Location = New System.Drawing.Point(32, 8)
        Me.cboFrom.Name = "cboFrom"
        Me.cboFrom.Size = New System.Drawing.Size(160, 21)
        Me.cboFrom.TabIndex = 0
        '
        'btnDelete
        '
        Me.btnDelete.Location = New System.Drawing.Point(0, 8)
        Me.btnDelete.Name = "btnDelete"
        Me.btnDelete.Size = New System.Drawing.Size(24, 24)
        Me.btnDelete.TabIndex = 3
        'Me.btnDelete.Text = "X"
        Me.btnDelete.Image = Image.FromFile(Application.StartupPath & "\delete_icon.gif")
        '
        'cboRow
        '
        Me.Controls.Add(Me.pnl)
        Me.Name = "cboRow"
        Me.Size = New System.Drawing.Size(490, 30)
        Me.pnl.ResumeLayout(False)
        Me.ResumeLayout(False)

    End Sub

#End Region

    Public Event DeleteButtonPressed(ByVal s As cboRow)
    Public Event ValueChanged(ByVal s As cboRow)

#Region "Privates"
    Private _FromValue As String
    Private _ToValue As String
    Private _Amount As String
#End Region

#Region "Properties"
    Public ReadOnly Property FromValue() As String
        Get
            Return cboFrom.SelectedValue
        End Get
    End Property
    Public ReadOnly Property ToValue() As String
        Get
            Return cboTo.SelectedValue
        End Get
    End Property
    Public ReadOnly Property Amount() As String
        Get
            Return txtAmount.Text
        End Get
    End Property
#End Region

#Region "Subs"
    'Public Shadows Sub TextChanged(ByVal sender As Object, ByVal e As System.EventArgs) Handles cboFrom.TextChanged, cboTo.TextChanged
    '    'Retrieve a reference to the ComboBox that sent this event.
    '    Dim Combo As ComboBox = CType(sender, ComboBox)

    '    If Combo.Text <> "" Then
    '        'Search for a matching entry.
    '        Dim MatchText As String = Combo.Text
    '        Dim Match As Integer = Combo.FindString(MatchText)

    '        'If a matching entry is found, insert it now.
    '        If Match <> -1 Then
    '            Combo.SelectedIndex = Match

    '            'Select the added text so it can be replaced
    '            'if the user keeps typing.
    '            Combo.SelectionStart = MatchText.Length
    '            Combo.SelectionLength = Combo.Text.Length - Combo.SelectionStart
    '        End If
    '    End If
    'End Sub
#End Region

#Region "Event Handlers"

    Private Sub cboRow_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles MyBase.Load
        cboFrom.Focus()
    End Sub

    Private Sub txtAmount_Enter(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtAmount.Enter
        Dim amt As TextBox = CType(sender, TextBox)
        If amt.Text = "" Or amt.Text = "$0.00" Or amt.Text = "0" Then
            amt.Text = ""
        End If
    End Sub

    'Private Sub cbo_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles cboFrom.KeyDown, cboTo.KeyDown
    '    Dim cbo As ComboBox = CType(sender, ComboBox)
    '    If e.KeyCode = Keys.Enter Then
    '        cbo.DroppedDown = False
    '        If cbo.Name = "cboFrom" Then cboTo.Focus()
    '        If cbo.Name = "cboTo" Then txtAmount.Focus()
    '    Else
    '        'If cbo.DroppedDown = False Then
    '        '    cbo.DroppedDown = True
    '        'End If
    '    End If
    'End Sub

    Private Sub txtAmount_Leave(ByVal sender As Object, ByVal e As System.EventArgs) Handles txtAmount.Leave
        Dim txt As String = CType(sender, TextBox).Text
        Dim amt As Double = txt
        Try
            amt = CType(txt, Double)
            CType(sender, TextBox).Text = String.Format("{0:c}", amt)
        Catch ex As Exception

        End Try
    End Sub

    Private Sub txtAmount_KeyDown(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyEventArgs) Handles txtAmount.KeyDown
        If e.KeyCode = Keys.Enter Then
        End If
    End Sub

    Private Sub btnDelete_Click(ByVal sender As Object, ByVal e As System.EventArgs) Handles btnDelete.Click
        RaiseEvent DeleteButtonPressed(Me)
    End Sub

#End Region

End Class

#End Region
I'll take a close look at your code when I have time. But what I suggested was that you can try creating only one form with one combobox populated with some fake data and see if that works. In your code, you are using the custom control which is too complicated to tell where the problem is. If in that simple test the combobox works fine, then the problem is in your custom control class; if not, we'll check what's wrong with the combobox. I've used this combobox class many times and never had a problem. So it might be something else that's naughty.
I just tried a simple test and could not get it to work. I am also posting that code for your perusal. Here it is, minus the windows generated code.

    Dim cbo as New AutoCompleteCombo

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click 'Text="Read Values"
        MsgBox(cbo.SelectedIndex)
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click 'Text="Create Combo"
        cbo.Name = "myAutoCombo"
        cbo.Top = Button1.Top + Button1.Height + 5
        cbo.Left = Button1.Left
        Me.Controls.Add(cbo)
        Dim i As Integer
        For i = 1 To 5
            cbo.Items.Add("Hello " & i)
        Next
    End Sub

Selected index came back with -1 after I started typing in "Hel" and then hit the TAB button and "Hello 1" was the shown text. Ideas?
This is becoming interesting: I see what problem you are facing. The selectedIndex property gives the result of previous selection.

I used the same code in my apps except for this line: Me.DroppedDown = True. Without this line, everything is working, but you won't be able to auto expand the combobox. Even if this line is not in this class but in the combobox's keydown event, you'll still get the same error. Haven't figured out a solution yet. The following is the code I used that's working without the autoexpand feature:

Option Strict On
Option Explicit On

Imports System.Windows.Forms

Public Class AutoCompleteCombo
    Inherits ComboBox
    Private mResetOnClear As Boolean = False

    Protected Overrides Sub RefreshItem(ByVal index As Integer)
        MyBase.RefreshItem(index)
    End Sub

    Protected Overrides Sub SetItemsCore(ByVal items As System.Collections.IList)
        MyBase.SetItemsCore(items)
    End Sub

    Public Shadows Sub KeyPress(ByVal sender As Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles MyBase.KeyPress
        Dim intIndex As Integer
        Dim strEntry As String

        If Char.IsControl(e.KeyChar) Then
            If MyBase.SelectionStart <= 1 Then
                If mResetOnClear Then
                    MyBase.SelectedIndex = 0
                    MyBase.SelectAll()
                Else
                    MyBase.Text = String.Empty
                    MyBase.SelectedIndex = -1
                    MyBase.SelectedIndex = -1
                End If
                e.Handled = True
                Exit Sub
            End If
            If MyBase.SelectionLength = 0 Then
                strEntry = MyBase.Text.Substring(0, MyBase.Text.Length - 1)
            Else
                strEntry = MyBase.Text.Substring(0, MyBase.SelectionStart - 1)
            End If
        ElseIf (Not Char.IsLetterOrDigit(e.KeyChar)) And (Not Char.IsWhiteSpace(e.KeyChar)) And (e.KeyChar <> "-") Then  
            Exit Sub
        Else
            If MyBase.SelectionLength = 0 Then
                strEntry = UCase(MyBase.Text & e.KeyChar)
            Else
                strEntry = MyBase.Text.Substring(0, MyBase.SelectionStart) & e.KeyChar
            End If
        End If

        intIndex = MyBase.FindString(strEntry)

        If intIndex <> -1 Then
            MyBase.SelectedIndex = intIndex
            MyBase.SelectionStart = strEntry.Length
            MyBase.SelectionLength = MyBase.Text.Length - MyBase.SelectionStart
        End If
        e.Handled = True
        Exit Sub
    End Sub

    'Make the Enter key work like the Tab key
    Protected Overrides Function ProcessCmdKey(ByRef msg As System.Windows.Forms.Message, ByVal keyData As System.Windows.Forms.Keys) As Boolean

        If msg.WParam.ToInt32() = CInt(Keys.Enter) Then
            SendKeys.Send("{Tab}")
            Return True
        End If

        Return MyBase.ProcessCmdKey(msg, keyData)

    End Function

    Public Property ResetOnClear() As Boolean
        Get
            Return mResetOnClear
        End Get
        Set(ByVal Value As Boolean)
            mResetOnClear = Value
        End Set
    End Property

End Class



 
wtconway,

have you even tried my approach with the two event-handlers (requiring two lines of code for each combo using it) ..?
I've been using it for quite some time now and never experienced any difficulties. The major advantage in using it is that I won't have to setup any reference in order to use an inherited combo with the form-designer.
Still, if you use Amy's control, why not add it to your toolbox so you can use the designer to setup the basic props?

Cheers,
Olaf
Amy, would your posted code still cause the combobox to attempt to complete the text? Event without the dropdown effect?

Olaf, I tried your suggestion with the CAutoCompleteCombo and still no relief. Code follows:

    Dim WithEvents cbo As New ComboBox

    Private Sub Button2_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button2.Click
        MsgBox(cbo.SelectedIndex)
    End Sub

    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        cbo.Name = "myAutoCombo"
        cbo.Top = Button1.Top + Button1.Height + 5
        cbo.Left = Button1.Left
        Me.Controls.Add(cbo)
        Dim i As Integer
        For i = 1 To 5
            cbo.Items.Add("Hello " & i)
        Next
        AddHandler cbo.KeyPress, AddressOf CAutoCompleteCombo.KeyPress
        AddHandler cbo.TextChanged, AddressOf CAutoCompleteCombo.TextChanged
    End Sub
Now I see what you mean - it seems I haven't ever used the code as you would like to.
If you add this line to the TextChanged-sub within the CAutoCompleteCombo-class (right before "End Sub") ...
   Console.WriteLine(Combo.Text & ": " & Combo.SelectedIndex.ToString)
... and this sub to your form-class ...
   Private Sub cbo_LostFocus(ByVal sender As Object, ByVal e As System.EventArgs) Handles cbo.LostFocus
      Console.WriteLine(cbo.Text & ": " & cbo.SelectedIndex.ToString)
   End Sub
... you'll notice that the SelectedIndex-property is being set appropriately, but as soon as the combo looses focus, it'll be reset to -1. I have no idea as to why this happens, but it sure is reproducable.
If, OTOH, you enter a letter and a corresponding entry is being selected by the auto-complete stuff AND you simply open the combo (Alt-down or mouse, won't matter), hit <TAB> to go to another control on the form, the SelectedIndex-prop will actually keep the value.

I'd sure consider this behaviour to be a bug but, even after some 30mins trying, I wasn't able to come up with a work-around. :-(

Amy?

Cheers,
Olaf
Well this just sucks. It looks like I'll have to force my users to actually mimic the dropdown affect. Could that possibly be detected on the box when the user hits tab and automatically drop it down and then close it right back to reset the property?
Told ya, I fumbled around with this "§$! for at least half an hour, and that included trying to open and close the combo via code. Didn't help at all.
But I'll look into it again later tonight - it's 9:22pm here already and I there's work for another half an hour left ...

Cheers,
Olaf
I just checked out an article on CodeProject (http://www.codeproject.com/vb/net/MultiColumnFlatCombo.asp). Dropped in a button with this code ...
   MessageBox.Show(comboTest.SelectedItem.ToString)
... behind that  button's click-event. Worked!

Would you want to try that out ..?

Cheers,
Olaf
ASKER CERTIFIED SOLUTION
Avatar of Olaf_Rabbachin
Olaf_Rabbachin
Flag of Germany 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
Olaf,

Good news!!! It all works...all I had to do was append the .Leave event handler to your original AutoComplete class and now voila....perfect bliss. I appreciate all of your time and effort on this problem and if you ever need anything, you let me know. Thank you so much.

-Jeff Evans
Jeff,

you sure are welcome.

Cheers,
Olaf