1

I am here with another problem.

I have setup my comboBox such that it accepts only those characters which matches with the name of any items in the comboBoxItems.

Now here I am stuck with a problem. Please have a look at my code then I will explain you the problem :

private void myComboBox_KeyUp(object sender, KeyEventArgs e)
    {
        // Get the textbox part of the combobox
        TextBox textBox = cbEffectOn.Template.FindName("PART_EditableTextBox", cbEffectOn) 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 shouldRemoveLastChar = true;

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

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

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

In the last if condition I am removing the last character from the combobox. But user can use arrow keys or mouse to change the position of the cursor and enter the text at the middle of the text.

So if by entering a character at the middle of the text if the text becomes invalid I mean if it does not match the Items in the ComboBox then I should remove the last entered character. Can anybody suggest me how to get the last inserted character and remove it?

Update :

string OldValue = "";

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

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

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

    OldValue = textBox.Text;

    bool shouldReplaceWithOldValue = true;

    string NewValue = textBox.Text.Insert(textBox.CaretIndex,e.Key.ToString()).Remove(textBox.CaretIndex + 1,textBox.Text.Length - textBox.CaretIndex);

    for (int i = 0; i < items.Count; i++)
    {
        // legal character input
        if (NewValue != "" && items.ElementAt(i).StartsWith(NewValue, StringComparison.InvariantCultureIgnoreCase))
        {
            shouldReplaceWithOldValue = false;
            break;
        }
    }

    //// illegal character input
    if (NewValue != "" && shouldReplaceWithOldValue)
    {
        e.Handled = true;
    }

}

Here I have tried to move all the code in KeyDown event to solve the above problem. This code works just fine but have 1 problem.

If I have any item named Birds & Animals then After typing Birds and a space I cannot type &.

I know what is the problem but don't know the solution.

The Problem is : To type & I have to press shift key and then press the 7 key. But both are sent as different keys.

Solutions that I think about : 1) I should move my code to KeyUp event. But here the problem of long press and fast typing will arise. 2) I think I should replace e.Key with something. But don't know what.

Khushi
  • 1,031
  • 4
  • 24
  • 48
  • Are you trying to create an Intellisense ComboBox? – Maverik Oct 28 '13 at 12:47
  • I don't know, what do you mean by Intellisense comboBox. I basically want to force the users to type in the text that matches one of the items of the ComboBox. – Khushi Oct 28 '13 at 15:45

3 Answers3

2

Instead of KeyUp event, subscribe to TextChanged event on your ComboBox Textbox. In event handler you can get the offset where the change has occured. You can use your validation logic inside the hanlder and delete the character at the offset if it makes Text invalid.

     private void Window_Loaded(object sender, RoutedEventArgs e)
    {
        TextBox textBox = cbEffectOn.Template.FindName("PART_EditableTextBox", cbEffectOn) as TextBox;
        textBox.TextChanged += new TextChangedEventHandler(textBox_TextChanged);
    }

    void textBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        int index = e.Changes.First().Offset;
    }
Nitin
  • 18,344
  • 2
  • 36
  • 53
  • It does not work as expected. 1) It does not allow me type any characters either legal or illegal. 2) If I select any Item from the combobox and move caret to any other position and try to type any character it allows me to type one illegal character. 3) I can delete any character from the text combobox even from the middle of the text by using delete or backspace. – Khushi Oct 25 '13 at 19:45
  • I have just copy pasted your code to try it and now it becomes strange enough. It gives me an compile time error that TextCompositionEventArgs does not contain a definition for handled. – Khushi Oct 25 '13 at 19:54
  • i dont know what i was thinking.. might be its time for me to go to sleep..updated the answer to give you the index at which character is entered. – Nitin Oct 25 '13 at 20:17
  • @Khushi let me know if you find any issue with this – Nitin Oct 25 '13 at 20:51
2

I'm not sure if this is what you're trying to do but I feel like you're trying to do what we typically see in visual studio Intellisense fitlering out results as we type.

Instead of removing the keystrokes, you should be using the validation mechanisms that WPF provides. Here's a sample of how this could work.

Scenarios covered:

  1. Input matches a combox item completely: TypedInput & SelectedItem both show full match.
  2. Input matches some element partially: TypedInput shortlists the popup list. The binding shows the matching text while SelectedItem remains null.
  3. Input doesn't match any item in list either from start or at some random point: user is visually given feedback (with possibility to add additional feedback information) with typical red outline. The TypedInput remains at last valid entry, SelectedItem may or may not be null depending on if the last TypedInput matched any item or not.

Full Code:

MainWindow.xaml

<Window x:Class="Sample.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:l="clr-namespace:Sample"
        Title="MainWindow" Height="350" Width="525" 
        DataContext="{Binding Source={x:Static l:MainWindowViewModel.CurrentInstance}}">
    <StackPanel>
        <TextBlock>
            <Run Text="Typed valid text" />
            <Run Text="{Binding TypedText}"/>
        </TextBlock>
        <TextBlock>
            <Run Text="Valid SelectedItem" />
            <Run Text="{Binding SelectedItem}"/>
        </TextBlock>
        <ComboBox ItemsSource="{Binding FilteredItems}" IsEditable="True" IsTextSearchEnabled="False" SelectedItem="{Binding SelectedItem}">
            <ComboBox.Text>
                <Binding Path="TypedText" UpdateSourceTrigger="PropertyChanged">
                    <Binding.ValidationRules>
                        <l:ContainsValidationRule />
                    </Binding.ValidationRules>
                </Binding>
            </ComboBox.Text>
        </ComboBox>
    </StackPanel>
</Window>

MainWindow.xaml.cs

namespace Sample
{
    public partial class MainWindow { public MainWindow() { InitializeComponent(); } }
}

ContainsValidationRule.cs -- Meat of the solution

namespace Sample
{
    using System.Globalization;
    using System.Linq;
    using System.Windows.Controls;

    public class ContainsValidationRule : ValidationRule
    {
        public override ValidationResult Validate(object value, CultureInfo cultureInfo)
        {
            var result = MainWindowViewModel.CurrentInstance.Items.Any(x => x.ToLower(cultureInfo).Contains((value as string).ToLower(cultureInfo)));
            return new ValidationResult(result, "No Reason");
        }
    }
}

MainWindowViewModel - Supporting ViewModel Singleton

namespace Sample
{
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Linq;
    using System.Runtime.CompilerServices;

    public sealed class MainWindowViewModel : INotifyPropertyChanged
    {
        private string _typedText;
        private string _selectedItem;
        private static readonly MainWindowViewModel Instance = new MainWindowViewModel();

        private MainWindowViewModel()
        {
            Items = new[] { "Apples", "Apples Green", "Bananas", "Bananas & Oranges", "Oranges", "Grapes" };
        }

        public static MainWindowViewModel CurrentInstance { get { return Instance; } }

        public string SelectedItem
        {
            get { return _selectedItem; }
            set
            {
                if (value == _selectedItem) return;
                _selectedItem = value;
                OnPropertyChanged();
            }
        }

        public string TypedText
        {
            get { return _typedText; }
            set
            {
                if (value == _typedText) return;
                _typedText = value;
                OnPropertyChanged();
                OnPropertyChanged("FilteredItems");
            }
        }

        public IEnumerable<string> Items { get; private set; }

        public IEnumerable<string> FilteredItems
        {
            get
            {
                return Items == null || TypedText == null ? Items : Items.Where(x => x.ToLowerInvariant().Contains(TypedText.ToLowerInvariant()));
            }
        }
        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}
Maverik
  • 5,619
  • 35
  • 48
  • I have tried your answer and it works well. Now the application that is under development will be used by very low-educated people in future when it is completed. Don't you think that the solution given by you will be harder for them than what I asked? I want to know your opinion. – Khushi Oct 28 '13 at 19:06
  • I think giving visual feedback is better than forcing something on a user especially the heavy handed approach you're using. Even if they're low-educated, they're still intelligent beings by very definition and if you tell them what the bright red outline means, they'll understand. Not only that, you can use `Validation.ErrorTemplate` to add proper error reasons that tell them right there and then what they've done wrong. This is how MS intended for these things to be solved, swimming upstream will only make your life miserable and users will generally hate you for eating keystrokes. – Maverik Oct 29 '13 at 10:24
  • Thanks, I like your advise. – Khushi Oct 30 '13 at 07:06
  • Happy to be of any assistance :) Good Luck! – Maverik Oct 30 '13 at 11:10
1

Have you considered using a string variable to hold the last legal text value in the text box portion of the combo box?

Initially, this string would be empty, as the user has not typed anything yet, then as each KeyUp event is handled, if an invalid character is input, then the previous string value is used to replace the text of the text box; otherwise the previous string value is now updated with the new complete string; awaiting anymore input by the user.

Karl Anderson
  • 34,606
  • 12
  • 65
  • 80
  • In that case I also have to remember the caret position. And what happens if user presses a key for long time. I mean Long Press. – Khushi Oct 25 '13 at 19:31
  • @Khushi - I suppose you can handle `KeyDown` in that case. – Karl Anderson Oct 25 '13 at 19:33
  • What do you mean by above commet? – Khushi Oct 25 '13 at 19:46
  • For long presses, the `KeyDown` event may be more useful than the `KeyUp` event, because the `KeyDown` happens before the `KeyUp` event. – Karl Anderson Oct 25 '13 at 19:53
  • I have tried to remember the OldValue of the combobox in KeyDown event and compared the new values with each items in the combobox and if I did not find a match then I replace the text of combobox with OldValue in keyup event. Upto this point I am successful. Can you help me on longpresses? – Khushi Oct 25 '13 at 20:10
  • @Khushi - did you try `KeyDown` instead of `KeyUp` for long presses? – Karl Anderson Oct 25 '13 at 20:11
  • @Khushi - per the accepted answer to [Difference between the KeyDown Event, KeyPress Event and KeyUp Event in Visual Studio](http://stackoverflow.com/questions/5871383/difference-between-the-keydown-event-keypress-event-and-keyup-event-in-visual-s), the `KeyDown` event should cover your long press scenario. – Karl Anderson Oct 25 '13 at 20:13
  • Just a minute I will read that page and tell you if I can solve my problem or not. – Khushi Oct 25 '13 at 20:15
  • I have been trying for much more time for solving the long presses problem but instead of getting the solution to that problem I got one more problem. If user types with a good speed and if he types some illegal values then those values are allowed by the combobox. I am updating my question with current code that I have. – Khushi Oct 25 '13 at 22:05
  • Please see the updated question. I have moved all the code in keyDown event. Now I have solved the old problems but got 1 new problem as mentioned above in the updated question. – Khushi Oct 26 '13 at 22:26