locked
Weird Autocomplete Combobox Error RRS feed

  • Question

  • Okay, this is a really weird error in my windows form.

    I have included my test vb class which is a test app. Load it up into Visual Studio.Net and run the app. What is does is populate a combo box with some test data and then it implements some autocomplete  code so that as the user types is autocompletes the selection.

    Now here's the weird scenario that breaks the form:

    - User clicks combo arrow to drop down list of items
    - User types the letter 'M'
    - Combo Selects 'Martin' option
    - User hits enter key
    - Combo Selection is still 'Martin'

    - User clicks combo arrow to drop down list of items
    - User types the letter 'A'
    - Combo Selects 'Alan' options
    - User hits enter key
    - Combo Selection is still 'Alan'

    - User clicks submit button
    - Combo Selection says that it is 'Martin'  What's up with that???

    Any help that you can offer would be greatly appreciated.

    Thanks,

    Martin



    Imports test
    Public Class test
        Inherits System.Windows.Forms.Form

    #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
            Dim dtOptions As New DataTable("Options")
            dtOptions.Columns.Add("ID", GetType(String))
            dtOptions.Columns.Add("Text", GetType(String))

            Dim drOption As DataRow
            Dim aText() As String = {"", "Martin", "Scott", "Alan"}
            Dim aIDs() As String = {"", "MartinB", "ScottS", "AlanK"}

            Dim i As Integer = 0

            For i = 0 To aIDs.GetLength(0) - 1
                drOption = dtOptions.NewRow()
                drOption(0) = aIDs(i)
                drOption(1) = aText(i)
                dtOptions.Rows.Add(drOption)
            Next

            cmbTest.DataSource = dtOptions
            cmbTest.ValueMember = "ID"
            cmbTest.DisplayMember = "Text"
        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 btnSubmit As System.Windows.Forms.Button
        Friend WithEvents cmbTest As System.Windows.Forms.ComboBox

        <System.Diagnostics.DebuggerStepThrough()> Private Sub InitializeComponent()
            Me.btnSubmit = New System.Windows.Forms.Button
            Me.cmbTest = New System.Windows.Forms.ComboBox
            Me.SuspendLayout()
            '
            'btnSubmit
            '
            Me.btnSubmit.Location = New System.Drawing.Point(8, 40)
            Me.btnSubmit.Name = "btnSubmit"
            Me.btnSubmit.TabIndex = 6
            Me.btnSubmit.Text = "Submit"
            '
            'cmbTest
            '
            Me.cmbTest.Location = New System.Drawing.Point(8, 8)
            Me.cmbTest.Name = "cmbTest"
            Me.cmbTest.Size = New System.Drawing.Size(121, 21)
            Me.cmbTest.TabIndex = 9
            '
            'test
            '
            Me.AutoScaleBaseSize = New System.Drawing.Size(5, 13)
            Me.ClientSize = New System.Drawing.Size(144, 70)
            Me.Controls.Add(Me.cmbTest)
            Me.Controls.Add(Me.btnSubmit)
            Me.Name = "test"
            Me.Text = "Form1"
            Me.ResumeLayout(False)

        End Sub

    #End Region

        Private Sub btnSubmit_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnSubmit.Click
            MessageBox.Show("Selection on Submit: " & cmbTest.SelectedValue)
        End Sub

        Private Sub cmbTest_Leave(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmbTest.Leave
            Trace.WriteLine("**Leave")
            Trace.WriteLine("Selection after leave routine: " & Me.cmbTest.SelectedValue)
        End Sub

        Private Sub cmbTest_KeyPress(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles cmbTest.KeyPress
            Trace.WriteLine("**Key Press - " & e.KeyChar.ToString())
            Trace.WriteLine("Current Selection: " & cmbTest.SelectedValue)

            If Char.IsControl(e.KeyChar) Then
                Trace.WriteLine("Control Key Pressed")
                Return
            End If

            With Me.cmbTest
                Dim ToFind As String = .Text.Substring(0, .SelectionStart) & e.KeyChar
                Dim Index As Integer = .FindStringExact(ToFind)
                If Index = -1 Then Index = .FindString(ToFind)
                If Index = -1 Then Return
                .SelectedIndex = Index
                .Select(ToFind.Length, .Text.Length - .SelectionStart)
                e.Handled = True
            End With

            Trace.WriteLine("New Selection: " & cmbTest.SelectedValue)
            Trace.WriteLine("")
        End Sub
    End Class

     

    Tuesday, July 5, 2005 5:09 PM

Answers

  • If you're using VS 2005, you can use the new AutoCompleteMode to enable system AutoComplete.  I'm assuming you're not...

    The Windows Forms ComboBox is a wrapper on the Windows OS provided COMBOBOX control (see http://msdn.microsoft.com/library/default.asp?url=/library/en-us/shellcc/platform/commctls/comboboxes/aboutcomboboxes.asp).  You've turned up a subtle issue with the way the system ComboBox handles the interaction between it's list and edit controls (it doesn't appear to expect the index to be changed while the drop down is popped up).  The only work-around I've found is to sync the SelectedIndex when the ComboBox DropDown is popped up.  In VS 2005, we've added an event for this but in VS 2003, you'll need to sub-class the ComboBox to get this behavior.  The following sample shows how to sub-class the ComboBox to add a DropDownCloseUp event.  You can then use this event to sync the SelectedIndex (shown further below).


    Public Class ACComboBox
        Inherits System.Windows.Forms.ComboBox

        Private Const WM_REFLECT_COMMAND As Integer = &H2000 + &H111
        Private Const CBN_CLOSEUP As Integer = &H8

        Public Event DropDownCloseUp As EventHandler

        Private Function HiWord(ByVal val As Integer) As Integer
            Return (val >> 16) And &HFFFF
        End Function

        Protected Overrides Sub WndProc(ByRef m As System.Windows.Forms.Message)
            If (m.Msg = WM_REFLECT_COMMAND) And (HiWord(m.WParam) = CBN_CLOSEUP) Then
                RaiseEvent DropDownCloseUp(Me, EventArgs.Empty)
            End If

            MyBase.WndProc(m)
        End Sub
    End Class

     



    You're code when need to be modified as follows:


        Private Sub cmbTest_KeyPress(ByVal sender As System.Object, ByVal e As System.Windows.Forms.KeyPressEventArgs) Handles cmbTest.KeyPress
            Trace.WriteLine("**Key Press - " & e.KeyChar.ToString())
            Trace.WriteLine("Current Selection: " & cmbTest.SelectedValue)

            If Char.IsControl(e.KeyChar) Then
                Trace.WriteLine("Control Key Pressed")
                Return
            End If

            With Me.cmbTest
                Dim ToFind As String = .Text.Substring(0, .SelectionStart) & e.KeyChar
                Dim Index As Integer = .FindStringExact(ToFind)
                If Index = -1 Then Index = .FindString(ToFind)
                If Index = -1 Then Return
                .SelectedIndex = Index
                .Select(ToFind.Length, .Text.Length - .SelectionStart)
                newIndex = Index
                e.Handled = True
            End With

            Trace.WriteLine("New Selection: " & cmbTest.SelectedValue)
            Trace.WriteLine("")
        End Sub

        Private newIndex As Integer

        Private Sub cmbTest_DropDownCloseUp(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmbTest.DropDownCloseUp
            If (newIndex <> cmbTest.SelectedIndex) Then
                cmbTest.SelectedIndex = newIndex
            End If
        End Sub

     




    Joe Stegman
    The Windows Forms Team
    Microsoft Corp.

    This posting is provided "AS IS" with no warranties, and confers no rights.

    Saturday, July 9, 2005 2:37 AM