Metallic Skeleton
Metallic Skeleton

Reputation: 689

WPF Combobox Autocomplete TextSearch like "Contains" instead of "Start with"

I am trying to implement WPF Combobox Autocomplete TextSearch like "Contains" instead of "Start with".

Couple of question threads are there but could not find any concrete solution.

I was following the answer by @Evgenii: WPF combobox textsearch with contains

In the SetText(DependencyObject element, string text) method, the value of "text" parameter is always a "DeviceNumber" string. So my text is not reflecting there.

Here is my own sample code https://drive.google.com/open?id=1eqK5bh5SQJPxHeb-zzOuBHIpYapv-h18

Any reason?

Is anyone successfully implemented Text Search with Contains? Please guide.

I thank you for every answer I get but working code is much appreciable :)

Upvotes: 0

Views: 3449

Answers (3)

Sameh Tohamy
Sameh Tohamy

Reputation: 41

I just like things as simple as possible, i'v searched too many threads and sites for a satisfying solution yet i did not find any, so i tried to do it from scratch, and as i said i like things simple, so no viwmodels no dlls

Step 1 : The handler:

create a handler class (mine called CustomAutoCompleteTextBox) and here is the code

using System.Windows;
using System.Windows.Controls;
using System.Windows.Controls.Primitives;
using System.Windows.Input;

namespace CapitalCRM
{
    public class CustomAutoCompleteTextBox
    {
        private TextBox MyTextBox;
        private Popup MyPopup;
        private ListBox popLst;
        private string _currentInput = "";
        private string _currentSuggestion = "";
        private string _currentText = "";

        private int _selectionStart;
        private int _selectionLength;

        private List<string> SuggestionValues;
        public CustomAutoCompleteTextBox(TextBox target, List<string> suggestionValues,Popup pop=null,ListBox listBox=null)
        {
            MyTextBox = target;
            MyPopup = pop;
            popLst = listBox;
            if(suggestionValues == null)
                suggestionValues = new List<string>();
            SuggestionValues = suggestionValues;

            //Auto complete with drop down list appears
            if (popLst != null)
            {
                MyPopup.Margin=new Thickness(MyPopup.Margin.Left,MyTextBox.ActualHeight,MyPopup.Margin.Right,MyPopup.Margin.Bottom);
                MyTextBox.TextChanged += OnTextBoxChanged;
               // MyTextBox.KeyDown += (s, e) => { TextboxKeyDown(e); };
                MyTextBox.PreviewKeyDown += (s, e) => { TextboxPreviewKeyDown(e); };
                popLst.KeyDown += (s, e) => { ListKeyDown(e); };
                pop.PreviewMouseDown += (s, e) => { ListPreviewMouseDown(e); };
            }
            else MyTextBox.TextChanged += SuggestionBoxOnTextChanged; // simple autocomplete, no dorpdown appeares

        }
        private void TextboxPreviewKeyDown(KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                if (MyPopup != null)
                {
                    MyPopup.IsOpen = false;
                }

                e.Handled = true;
            }
            else if (e.Key == Key.Down)
            {
                if (popLst != null && MyPopup != null && popLst.Items.Count > 0 && !(e.OriginalSource is ListBoxItem))
                {
                    Task.Delay(50).ContinueWith(_ =>
                    {
                        Application.Current.Dispatcher.Invoke(new Action(() =>
                        {
                            popLst.Focus();
                            popLst.SelectedIndex = 0;
                            ListBoxItem lbi =popLst.ItemContainerGenerator.ContainerFromIndex(popLst.SelectedIndex) as ListBoxItem;
                            if (lbi != null)
                                lbi.Focus();
                            e.Handled = true;
                        }));
                    });
                }
            }
        }
        private void ListKeyDown(KeyEventArgs e)
        {
            if (e.OriginalSource is ListBoxItem)
            {
                if (e.Key == Key.Enter)
                {
                    if (popLst.Items.Count > 0 && popLst.SelectedIndex >= 0)
                        MyTextBox.Text = popLst.Items[popLst.SelectedIndex].ToString();
                    MyPopup.IsOpen = false;
                    Task.Delay(50).ContinueWith(_ =>
                    {
                        Application.Current.Dispatcher.Invoke(new Action(() =>
                        {
                            MyTextBox.Focus();
                            MyTextBox.CaretIndex = MyTextBox.Text.Length;
                        }));
                    });
                }
            }
           
        }
        private void ListPreviewMouseDown(MouseEventArgs e)
        {
            if (e.LeftButton == MouseButtonState.Pressed)
            {
                TextBlock tb=e.OriginalSource as TextBlock;
                if(tb != null)
                {
                    MyTextBox.Text=tb.Text;
                    MyPopup.IsOpen = false;
                    e.Handled = true;
                    Task.Delay(50).ContinueWith(_ =>
                    {
                        Application.Current.Dispatcher.Invoke(new Action(() =>
                        {
                            MyTextBox.Focus();
                            MyTextBox.CaretIndex=MyTextBox.Text.Length;
                        }));
                    });
                }
            }
            
        }
        private void SuggestionBoxOnTextChanged(object sender, TextChangedEventArgs e)
        {

            var input = MyTextBox.Text;
            if (input.Length > _currentInput.Length && input != _currentSuggestion)
            {
                _currentSuggestion = SuggestionValues.FirstOrDefault(x => x.StartsWith(input));
                if (_currentSuggestion != null)
                {
                    _currentText = _currentSuggestion;
                    _selectionStart = input.Length;
                    _selectionLength = _currentSuggestion.Length - input.Length;

                    MyTextBox.Text = _currentText;
                    MyTextBox.Select(_selectionStart, _selectionLength);
                }
            }
            _currentInput = input;
        }
        private void OnTextBoxChanged(object sender, TextChangedEventArgs e)
        {
            if (!MyTextBox.IsFocused) { if (MyPopup != null) MyPopup.IsOpen = false; return; }
            var input = MyTextBox.Text;

            if (string.IsNullOrEmpty(input))
            {
                MyPopup.IsOpen = false;
                return;
            }
            var lst = SuggestionValues.Where(z => z.ToLower().Contains(input.ToLower())).ToList();
            if (lst.Count > 0 && MyPopup != null && popLst != null)
            {
                popLst.ItemsSource = lst;
                MyPopup.IsOpen = lst.Count>0;
            }
            else if (MyPopup != null) MyPopup.IsOpen = false;
        }
    }
}

NOTES here: you have two options for the autocomplete 1- Autocomplete with a dropdown list shows all items contains the input string 2- Simple autocomplete which select the first item that starts with the input

Step 2: The XAML

Add a holder for TextBox and Popup i chosed a grid so the popup and list do not push the textbox position and never affect it's layout UI

<Grid Grid.Column="1" Grid.Row="0">
    <TextBox Grid.Row="0" Grid.Column="1" x:Name="txtCustName" Style="{StaticResource TextBoxRoundedShadow}"
        Text="{Binding Path=curCust.Name, Mode=TwoWay}" LostFocus="txtCustName_LostFocus" HorizontalAlignment="Stretch" VerticalAlignment="Stretch"/>
    <Popup x:Name="namesPopup" Grid.Row="0" Grid.Column="1">
        <ListBox x:Name="popnamesLst"/>
    </Popup>
</Grid>

That way we still keep all normal TextBox events specially Binding property

Step 3 : The call and use simply call the handler like so

var nameSearch = new CustomAutoCompleteTextBox(txtCustName, Program.CustomersNamesList,namesPopup,popnamesLst);

note that if popup is null or popuplst is null the handler will automatically switch to simple autocomplete Hope it helps

Upvotes: 0

Byoung Sam Moon
Byoung Sam Moon

Reputation: 306

make custom combobox control.

public class SearchComboBox : ComboBox
{
    TextBox editableTextBox;

    public override void OnApplyTemplate()
    {
        base.OnApplyTemplate();

        editableTextBox = GetTemplateChild("PART_EditableTextBox") as TextBox;

        editableTextBox.TextChanged += EditableTextBox_TextChanged;
    }

    private void EditableTextBox_TextChanged(object sender, TextChangedEventArgs e)
    {
        ICollectionView ICV = ItemsSource as ICollectionView;

        if(ICV != null)
        {
            if (string.IsNullOrEmpty(editableTextBox.Text.Trim()))
                ICV.Filter = null;
            else
                ICV.Filter = new Predicate<object>(i => ((Equipment)i).equipmentLabel.Contains(editableTextBox.Text));

            IsDropDownOpen = true;
        }

    }
}

modify you EquipmentScreenViewModel Code. add ICollectionView type property

public  class EquipmentScreenViewModel
{
    public string SelectedEquipmentRego { get; set; }
    public ObservableCollection<Equipment> AllEquipments { get; set; }

    private ICollectionView _allEquipCollection = null;

    public ICollectionView AllEquipCollection
    {
        get
        {
            if (_allEquipCollection == null && AllEquipments != null)
            {
                _allEquipCollection = CollectionViewSource.GetDefaultView(AllEquipments);
            }

            return _allEquipCollection;
        }
    }
}

XAML

<Grid>
    <local:SearchComboBox x:Name="cmbAlternativeAsset" 
                            Width="200" IsEditable="True" 
                            FontSize="12" Foreground="#494949"
                            VerticalAlignment="Center"
                            HorizontalAlignment="Stretch"   
                            SelectedItem="{Binding SelectedEquipmentRego, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" 
                            ItemsSource="{Binding AllEquipCollection}" SelectedValuePath="equipmentRego"
                            DisplayMemberPath="equipmentLabel" IsTextSearchEnabled="False"
        />
</Grid>

Binding ItemsSource to CollectionView and IsTextSearchEnabled false. Good Luck

Upvotes: 1

OMR
OMR

Reputation: 12188

i recommend using AutoCompleteBox, it 's just like ComboBox, it has ItemsSource and SelectedItem and all like ComboBox

you can use it property 'AutoCompleteBox.FilterMode' which take AutoCompleteFilterMode enumeration, the enumerations include:Contains, ContainsCaseSensitive, ContainsOrdinal and other helpful ... here is how you use it:

https://www.broculos.net/2014/04/wpf-autocompletebox-autocomplete-text.html and here it an example of using filter mode:

https://learn.microsoft.com/en-us/previous-versions/windows/silverlight/dotnet-windows-silverlight/dd833103(v=vs.95)?redirectedfrom=MSDN

Upvotes: 2

Related Questions