Search code examples
c#wpfsortingcomboboxinotifypropertychanged

Is there a way to sort the drop-.down list of a ComboBox in WPF?


Iam currently working on a WPF application which task is to manage orders of a home delivery Pizzeria. It's crutial to have a working database based on phone numbers. The dispatcher writes in the phone number which gives him/her all the details of the customer(name, address, comments, etc.).
I want to have a ComboBox in which you can type the numbers you would like to search for. I somewhat made it work, but I miss an important part that I dont know how to solve. Here is the thing.
I would like to have ComboBox that works like this: You start typing in the number in the .Text field of the ComboBox, which in return will select the most matching phone numbers and sort every other closely matching phone nubmers in the drop-down list from an ObservableColletion (Pic 1). By standard the ComboBox drop-down list is not filtering/sorting the source "list" it got, it only changes the selection in the .Text property accordingly what you type.

Pic 1 Current state of development of the ComboBox

In Hungary we have the following phone number pattern :
[provider[xx]] [card ID[xxx-xxxx]] ->9 nums
I.e : 20 - 324-7784
(20- Telenor, 70 - Vodafone, etc...)

I have the following XAML for the ComboBox.

<ComboBox x:Name="PhoneBox" materialDesign:HintAssist.Hint="Keresés" IsEditable="True" 
          Style="{StaticResource MaterialDesignFloatingHintComboBox}"              
          Width="1000" Height="50" HorizontalAlignment="Left" VerticalAlignment="Center" 
          Margin="150,0,0,0" FontSize="20" 
          ItemsSource="{Binding SELECTEDPHONES, RelativeSource={RelativeSource FindAncestor,
                       AncestorType={x:Type local:Order_placeCustomerData_panel}},
                       UpdateSourceTrigger=PropertyChanged}"
          SelectedItem="{Binding SELECTED, RelativeSource={RelativeSource FindAncestor,
                        AncestorType={x:Type local:Order_placeCustomerData_panel}}}"/>

And here is the current backend of the ComboBox.
Im counting from the beginning which index of a full phone number I've checked, so I can keep track which part is the newly typed in part. First I collect all the phone numbers that satisfy the current condition, and all the phone numbers that dont. Then I exchange each of the good ones with one bad one. During this process I flag them based on their position. If a good one's position is smaller than GoodOnes.Count it goes on the flag list. To finish the procedure, I exchange every flaged item with an already placed good one starting from the top(highest index of good one).
The reason for this is that, when you exchange PHONES[2](bad one) with PHONES[5](good one) and you have 8 good ones, after the process, PHONES[5] would be a "hole" fileld with bad number. good-good-good-good-bad-good-good-good like this.)

        //Propeties and stuff
        private string[] typed = new string[9];
        private int typedIndex = 0;
        private bool isReevaluating = false;
        private ObservableCollection<string> _selectedPhones;
        private string _selected;

        public ObservableCollection<string> SELECTEDPHONES
        {
            get { return _selectedPhones; }
            set 
            {
                _selectedPhones = value;
            }
        }
        public string SELECTED
        {
            get { return _selected; }
            set
            {
                _selected = value;
                OnPropertyChanged("SELECTED");
            }
        }


        //Function for Reeavluating the drop-down list
        private void ReevaluatePhones()
        {
            if (SELECTED == null)
            {
                return;
            }
            isReevaluating = true;

            typed[typedIndex] = SELECTED[typedIndex].ToString();
            string toExamine;
            List<int> Satisfies = new List<int>();
            List<int> notSatisfies = new List<int>();

            for (int j = 0; j < PHONES.Count; j++)
            {
                toExamine = PHONES[j].ToString();
                if (toExamine[typedIndex].ToString() == typed[typedIndex].ToString())
                {
                    Satisfies.Add(j);
                }
                else
                {
                    notSatisfies.Add(j);
                }
            }
            List<int> flagSatisfies = new List<int>();
            int flagLast = 0;
            for (int i = 0; i < Satisfies.Count; i++)
            {
                if (Satisfies[i] < Satisfies.Count)
                {
                    flagSatisfies.Add(Satisfies[i]);
                }

                string temp = PHONES[notSatisfies[i]];
                PHONES[notSatisfies[i]] = PHONES[Satisfies[i]];
                PHONES[Satisfies[i]] = temp;

                flagLast = notSatisfies[i];
            }

            for (int i = 0; i < flagSatisfies.Count; i++)
            {
                string temp = PHONES[flagSatisfies[i]];
                PHONES[flagSatisfies[i]] = PHONES[flagLast];
                PHONES[flagLast] = temp;
                flagLast--;
            }

            isReevaluating = false;
        }

The INotifyPropertyChanged interface is just basically implemented, doesn't contains anything unusuall, just the call order for the event when the ComboBox's selected property changes.
The above code works fine, but when you misstype the phone number and delete some characters from the text field I don't get Notification about the changes, only when you type, never upon deletion.
As far as I've read on MS docs the ComboBox doesnt have an accessible field for the TYPED in values, only for the selected value which is the whole phone number. I cant make the sorting work based on that since I dont have any fundamential point to compare it to.

To summarize.

I would like to sort a drop-down list (which is an ObservableColelction) based on the typed in values of the ComboBox.Text field. For my solution above to work I need to get notified when the value of the .Text gets deleted even partially or completely. Iam open to any kind of guidance, tipps and ideas.

Thanks a lot:)


Solution

  • I would recommend that you use CollectionViewSource https://learn.microsoft.com/en-us/dotnet/api/system.windows.data.collectionviewsource?view=netframework-4.8.
    An instance of this class is declared in window resources.

    The Source property contains a link to your collection. Property Filter - filtering method in the Code Behind Window.
    Properties IsLiveFilteringRequested and IsLiveSortingRequested = true for RealTime filtering and sorting.
    In LiveFilteringProperties - add properties by which you want to filter. In SortDescriptions - sort conditions.
    When ComboBox.Text changes, disable and re-enable IsLiveFilteringRequested for new filtering.

    ComboBox.ItemsSource points to created CollectionViewSource instance in resources.