Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

WPF ComboBox with IsEditable="True" - How can I indicate that no match was found?

Using the following simple text box as an example:

<ComboBox IsEditable="True" SelectedItem="{Binding}">
    <ComboBoxItem>Angus/ComboBoxItem>
    <ComboBoxItem>Jane</ComboBoxItem>
    <ComboBoxItem>Steve</ComboBoxItem>
</ComboBox>

I would like to allow the user to find their selection by typing in a name, so I have set IsEditable equal to true. Acceptable values for the property bound to SelectedItem are any one of the options in the list, or no selection (null). The problem is, there is no error indication by default in the event someone types in a name that isn't in the list.

For example: a user could type "Bob", causing the SelectedItem property to be null, but not realize that Bob doesn't exist in the list. Instead I would like to provide a visual indication as soon as the ComboBox's Text property is not null or empty AND SelectedItem is null, and stop them from typing any more?

My initial thought was a custom validation rule, but I don't know how to access both the Text and SelectedItem properties of the combobox.

like image 703
Simon T Avatar asked Jan 05 '11 22:01

Simon T


3 Answers

As a starter, you might want to let the user see if they are typing in one of the available options.

1) Search "autocomplete combobox" online.

2) Check these out:

http://weblogs.asp.net/okloeten/archive/2007/11/12/5088649.aspx

http://www.codeproject.com/KB/WPF/WPFCustomComboBox.aspx

3) Also try this:

    <ComboBox IsEditable="true" TextSearch.TextPath="Content">
        <ComboBoxItem Content="Hello"/>
        <ComboBoxItem Content="World"/>
    </ComboBox>

The above code snippet is a primite way to provide that "visual indication" you're looking for. If the user types in 'h', then 'hello' will appear in the input textbox. However, this on its own won't have a mechanism to stop the user from typing in an illegal character.

4) This is a more advanced version:

    <ComboBox Name="myComboBox" IsEditable="true" KeyUp="myComboBox_KeyUp">
        <ComboBoxItem Content="Hello"/>
        <ComboBoxItem Content="World"/>
        <ComboBoxItem Content="WPF"/>
        <ComboBoxItem Content="ComboBox"/>
    </ComboBox>

Code-behind:

    private void myComboBox_KeyUp(object sender, KeyEventArgs e)
    {
        // Get the textbox part of the combobox
        TextBox textBox = myComboBox.Template.FindName("PART_EditableTextBox", myComboBox) as TextBox;

        // holds the list of combobox items as strings
        List<String> items = new List<String>();

        // indicates whether the new character added should be removed
        bool shouldRemove = true;

        for (int i = 0; i < myComboBox.Items.Count; i++)
        {
            items.Add(((ComboBoxItem)myComboBox.Items.GetItemAt(i)).Content.ToString());
        }

        for (int i = 0; i < items.Count; i++)
        {
            // legal character input
            if(textBox.Text != "" && items.ElementAt(i).StartsWith(textBox.Text))
            {
                shouldRemove = false;
                break;
            }
        }

        // illegal character input
        if (textBox.Text != "" && shouldRemove)
        {
            textBox.Text = textBox.Text.Remove(textBox.Text.Length - 1);
            textBox.CaretIndex = textBox.Text.Length;
        }
    }

Here, we don't let the user continue typing in once we detect that no combobox item starts with the text in the textbox. We remove the character added and wait for another character.

like image 187
user1234567 Avatar answered Jan 02 '23 12:01

user1234567


This solution is based on user1234567's answer, with a couple changes. Instead of searching the Items list it simply checks the ComboBox's SelectedIndex for a value >= 0 to see if a match was found and it resolves RB's concern about holding down a key inserting more than one character. It also adds an audible feedback when it rejects characters.

private int _lastMatchLength = 0;

private void myComboBox_GotFocus(object sender, RoutedEventArgs e)
{
    _lastMatchLength = 0;
}

private void myComboBox_KeyUp(object sender, KeyEventArgs e)
{
    ComboBox cBox = sender as ComboBox;

    TextBox tb = cBox.Template.FindName("PART_EditableTextBox", cBox) as TextBox;
    if (tb != null)
    {
        if (cBox.SelectedIndex >= 0)
        {
            _lastMatchLength = tb.SelectionStart;
        }
        else if (tb.Text.Length == 0)
        {
            _lastMatchLength = 0;
        }
        else
        {
            System.Media.SystemSounds.Beep.Play();
            tb.Text = tb.Text.Substring(0, _lastMatchLength);
            tb.CaretIndex = tb.Text.Length;
        }
    }
}
like image 32
Jim Hansen Avatar answered Jan 02 '23 11:01

Jim Hansen


This is a good solution until you have a lot of records in the combo box. I would do it this way:

Declare this at the top of the file

List<String> items = new List<String>(); 

private void myComboBox_KeyUp(object sender, KeyEventArgs e) 
{ 
  TextBox textBox = myComboBox.Template.FindName("PART_EditableTextBox", myComboBox) as TextBox; 

     // indicates whether the new character added should be removed 
    bool shouldRemove = true; 

    // this way you don't fill the list for every char typed
    if(items.Count <= 0)
    { 
       for (int i = 0; i < myComboBox.Items.Count; i++) 
       { 
           items.Add(((ComboBoxItem)myComboBox.Items.GetItemAt(i)).Content.ToString()); 
       } 
    }
    // then look in the list
    for (int i = 0; i < items.Count; i++) 
    { 
        // legal character input 
        if(textBox.Text != "" && items.ElementAt(i).StartsWith(textBox.Text)) 
        { 
            shouldRemove = false; 
            break; 
        } 
    } 

    // illegal character input 
    if (textBox.Text != "" && shouldRemove) 
    { 
        textBox.Text = textBox.Text.Remove(textBox.Text.Length - 1); 
        textBox.CaretIndex = textBox.Text.Length; 
    } 
} 

unless the binding is continue adding records to the combo box, I think is a more efficient lookup

like image 35
Oscar Avatar answered Jan 02 '23 12:01

Oscar