Reputation: 689
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
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
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
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:
Upvotes: 2