WPF Combobox Autocomplete TextSearch like "Contains" instead of "Start with" - c#

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 :)

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

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

Related

Editable ComboBox with selectedvalue(path) from itemsource OR user input

Hey I'm trying to create a combobox that allows a user to select an item from the list (shown in list as fx. "2324 - James - 21"), put a property from that item in the textbox (fx. "James"), but also allow for user input of that one property. (fx. "Jack")
What I've got so far is a combobox that allows for user input, but when an item is selected, instead of the property (As specified in SelectedValuePath), the items ToString is put in the combobox text field, instead of just the name.
Any ideas how to fix it? This is my setup now:
Person class:
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public int Age { get; set; }
public override string ToString()
{
return $"{Id} - {Name} - {Age}";
}
}
XAML:
<ComboBox Text="{Binding PersonName, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding People, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"
SelectedValue="{Binding PersonName, UpdateSourceTrigger=PropertyChanged}"
IsEditable="True" SelectedValuePath="Name" />
Edit:
People is an ObservableCollection<Person>, PersonName is a string bound to a field personName and OnPropertyChanged(); is called in its set method. The ViewModel Inherits from an ObservableObject class that implements INotifyPropertychanged.
Edit 2:
Okay, so I've been trying this instead:
<ComboBox Text="{Binding PersonName, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding People, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"
SelectedItem="{Binding Person, UpdateSourceTrigger=PropertyChanged}"
IsEditable="True" />
Aaand in the viewmodel:
public Person SelectedPerson
{
get => selectedPerson;
set
{
selectedPerson = value;
OnPropertyChanged();
if (SelectedPerson != null) PersonName = SelectedPerson.Name;
}
}
Buuut, when I select a person from the list, the combbox text field is set to Persons ToString value, not SelectedPerson.Name... Or rather; it's probably set to SelectedPerson.Name first, but then SelectedPerson.ToString() automatically by the ComboBox :/
Edit 3:
Alright, I've been trying to interrupt the text change, when it comes from SelectionChanged in a really hacky way, but I can't figure out how to "Cancel" a text change. Here's my code so far, but TextChanged doesn't seem to allow for reverting the text, as far as I can see, and PreviewTextInput is only triggered when the change is form user input :/.
Useless code removed. Almost identical to edit 4, but without the "oldtext" field.
Edit 4:
I solved it, in a horrible way. It works, but I really wish there was a better way. Maybe one of you has a better solution? :) Anyway this is the solution I came to:
Dependency property thing that reverts text after it's been forcefully changed by the combobox:
public class ComboBoxBehaviour
{
private static bool overrideTextChange;
private static string oldText;
public static bool GetDisconnectTextFromSelectedItem(ComboBox comboBox)
{
return (bool)comboBox.GetValue(DisconnectTextFromSelectedItemProperty);
}
public static void SetDisconnectTextFromSelectedItem(ComboBox comboBox, bool value)
{
comboBox.SetValue(DisconnectTextFromSelectedItemProperty, value);
}
public static readonly DependencyProperty DisconnectTextFromSelectedItemProperty =
DependencyProperty.RegisterAttached(
"DisconnectTextFromSelectedItem",
typeof(bool),
typeof(ComboBoxBehaviour),
new UIPropertyMetadata(false, OnDisconnectTextFromSelectedItemChanged));
private static void OnDisconnectTextFromSelectedItemChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
var comboBox = d as ComboBox;
if (comboBox == null) return;
if (e.NewValue is bool == false) return;
if ((bool)e.NewValue)
{
comboBox.SelectionChanged += HandleSelection;
comboBox.Loaded += ComboBoxOnLoaded;
}
else
{
comboBox.SelectionChanged -= HandleSelection;
comboBox.Loaded -= ComboBoxOnLoaded;
}
}
private static void ComboBoxOnLoaded(object sender, RoutedEventArgs e)
{
ComboBox comboBox = (ComboBox)e.Source;
TextBox txtBox = (TextBox)comboBox.Template.FindName("PART_EditableTextBox", comboBox);
txtBox.TextChanged += TxtBoxOnTextChanged;
}
private static void TxtBoxOnTextChanged(object sender, TextChangedEventArgs e)
{
if (overrideTextChange)
{
((TextBox) e.Source).Text = oldText;
overrideTextChange = false;
e.Handled = true;
}
}
private static void HandleSelection(object sender, RoutedEventArgs e)
{
ComboBox comboBox = (ComboBox)e.Source;
TextBox txtBox = (TextBox)comboBox.Template.FindName("PART_EditableTextBox", comboBox);
oldText = txtBox.Text;
overrideTextChange = true;
e.Handled = true;
}
}
Xaml combobox:
<ComboBox Text="{Binding PersonName, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding People, UpdateSourceTrigger=PropertyChanged, Mode=OneWay}"
SelectedItem="{Binding Person, UpdateSourceTrigger=PropertyChanged}"
IsEditable="True" helpers:ComboBoxBehaviour.DisconnectTextFromSelectedItem="True" />
Edit 5:
Close, but no cigar. turns out calling
((TextBox) e.Source).Text = oldText;
does not update my combobox text binding, so it shows the correct text, but the string in the viewmodel is wrong - it's the ToString text, instead of the short text. So how do I force the combobox binding to refresh, when I don't have access to the combobox in the text change context? More "hacking"? Hmm...
Edit 6:
Alright I got it working by changing TxtBoxOnTextChanged to this;
private static void TxtBoxOnTextChanged(object sender, TextChangedEventArgs e)
{
if (overrideTextChange) //The combo box changed the text of the textbox.
{
TextBox textBox = (TextBox) e.Source; //The textbox
ComboBox comboBox = (ComboBox)textBox.TemplatedParent; //The combobox.
comboBox.Text = oldText; //Set the text of the combobox to update the property on the viewmodel.
textBox.Text = oldText; //Set the text of the textbox manually, because setting the combobox text doesn't change it for some reason.
overrideTextChange = false;
e.Handled = true; //Done!
}
}
Still hoping someone's got a better solution, but it works!
If you want to display the value of the Name property instead of ToString(), you should set the DisplayMemberPath property to "Name".
I need to show ToString in the list, but Name in the input field.
Then you should set PersonName to the string that you want to display in the input field and don't bind SelectedValue to PersonName.
You can only actually select a value that's in the source collection.

How to add items to ViewModel ObservableCollection property from list control? WPF MVVM

This is my first MVVM project, I hope can be clear.
Having this in Model:
public class Category
{
public int CategoryId { get; set; }
public string Description { get; set; }
}
In ViewModel:
public class CategoryViewModel : MyViewModelBase
{
private ObservableCollection<Category> categories;
public ObservableCollection<Category> Categories
{
get { return categories; }
set
{
categories = value;
NotifyPropertyChanged(nameof(Categories));
}
}
}
In View (XAML)
The items are bound to a ComboBox:
<ComboBox x:Name="cboCategories"
HorizontalAlignment="Left
VerticalAlignment="Top"
Width="250"
IsEditable="True"
ItemsSource="{Binding Categories}"
SelectedValuePath="CategoryId"
DisplayMemberPath="Description" />
Is there a way to add a new item (Category) to the ObservableCollection property when the user writes a new entry on the control?
I've been able to do it by showing up a little Window with a TextBox, but I'd like to know if is possible to short this process.
I'm not really familiar with WPF, any help would be appreciated.
Say you had one collection of Category and that is bound to the itemssource of your combo.
You then bind selecteditem to a property of type Category with a propfull so you have setter where you can put code.
When that setter fires you get a selected Category.
You could then do what you like with it.
One option would be to add it to another observablecollection.
The pattern of acting when you select an item off a list is described here:
https://social.technet.microsoft.com/wiki/contents/articles/30564.wpf-uneventful-mvvm.aspx#Select_From_List_IndexChanged
In that you would add the chef to another observablecollection in DoSomethingWhenChefChanged.
You could handle the TextBoxBase.TextChanged attached event and for example raise a command of the view model or add the item to the ObservableCollection directly, e.g.:
private void cboCategories_TextChanged(object sender, TextChangedEventArgs e)
{
var cmb = sender as ComboBox;
var viewModel = DataContext as CategoryViewModel;
if (viewModel != null)
{
viewModel.Categories.Add(new Category() { Description = cmb.Text });
}
}
XAML:
<ComboBox x:Name="cboCategories"
IsEditable="True"
TextBoxBase.TextChanged="cboCategories_TextChanged" ... />
If you want to invoke the attached command using an interaction trigger, you could create your own custom EventTrigger as suggested here:
http://joyfulwpf.blogspot.se/2009/05/mvvm-invoking-command-on-attached-event.html
https://social.msdn.microsoft.com/Forums/vstudio/en-US/c3e9fad4-16ee-4744-8b0e-1ea0abfc5da7/how-to-handle-scrollviewerscrollchanged-event-in-mvvm?forum=wpf

Populate control after selectedindexchange event has fired

I have a MVVM WPF project where I have an devexpress accordian control which is populated with xml template items from a ViewModel. That works great, but my problem is when I click on one of the items in the accordian control and the selectedIndexChanged event is fired. I want to handle that in the MVVM manner and get the selected items value(which is a path to an xml file) from the accordian control, fetch the content of the xml file and databind a textbox control with the content of the xml file. The following is what I have tried so far.
Here is my xaml user control
<dxa:AccordionControl Grid.Column="0" x:Name="accordianTemplateMenu"
SelectionMode="Single" SelectionUnit="SubItemOrRootItem" ItemsSource="
{Binding TemplateItems}"
ChildrenPath="TemplateItems" DisplayMemberPath="Header >
<dxmvvm:Interaction.Behaviors>
<dxmvvm:EventToCommand EventName="SelectedItemChanged" Command="
{Binding EditCommand}">
<dxmvvm:EventToCommand.EventArgsConverter>
<Common:AccordionEventArgsConverter/>
</dxmvvm:EventToCommand.EventArgsConverter>
</dxmvvm:EventToCommand>
</dxmvvm:Interaction.Behaviors>
</dxa:AccordionControl>
<GridSplitter Grid.Column="1" />
<TextBlock Grid.Column="2" x:Name="templateItemContainer">
<Run Name="run" Text="{Binding XML}" ></Run>
</TextBlock>
This boils down to the AccordionEventArgsConverter which gets me the event arguments from the selecteditem in the accordian control:
public class AccordionEventArgsConverter :
EventArgsConverterBase<AccordionSelectedItemChangedEventArgs>
{
protected override object Convert(object sender,
AccordionSelectedItemChangedEventArgs args)
{
if (args != null)
{
return args;
}
return null;
}
}
And finally my viewmodel:
class TemplateMenuViewModel
{
private List<TemplateItem> _templateItems;
public TemplateMenuViewModel()
{
EditCommand = new DelegateCommand<object>(Edit, CanEdit);
}
public List<TemplateItem> TemplateItems
{
get
{
TemplateProvider provider = new TemplateProvider();
return provider.GetTemplateMenuItems("pathToMenuItems");
}
set { _templateItems = value; }
}
public ICommand<object> EditCommand { get; private set; }
public void Edit(object accordianItemArgs)
{
}
public bool CanEdit(object accordianItemArgs)
{
return accordianItemArgs != null;
}
}
I am able to get into the public void Edit method, which is great because from there I can use the accordianItemArgs to get the xml content, but how do I "return"/databind the xml content to the textblock element in the xaml file?
There are a couple of things:
You need the TemplateMenuViewModel to define an XML property. It looks like your TextBlock is already binding to it.
Then you need your ViewModel to implement the INotifyPropertyChanged interface. It doesn't look like you're doing that, then raise a property changed event when the XML text is set.
You should set your Text="{Binding XML}" with a Mode of OneWay:
Text="{Binding XML, Mode=OneWay}"
If you need more information on how to implement INotifyPropertyChanged, check out this tutorial: https://www.tutorialspoint.com/mvvm/mvvm_first_application.htm.

Binding Element to Property from Inherited Data Context

I have a WPF control pane with sixteen of the same child control containing a combobox that needs to be bound to a list in the Parent Control code behind. I was really struggling to get this list to bind until I found this: Binding objects defined in code-behind.
Setting DataContext="{Binding RelativeSource={RelativeSource Self}}" on the Parent Control allowed me to bind the combobox on the child control directly.
The problem is that now I want to create a Data Template to display the list items properly, but nothing I put in the Binding or Relative Source Displays anything.
ControlPane.xaml
<UserControl
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlPane"
x:Name="CtrlPaneWpf"
DataContext="{Binding RelativeSource={RelativeSource Self}}"
>
ControlPane.xaml.cs
public class TwoStringClass
{
public string string1;
public string colorHex;
}
public ObservableCollection<TwoStringClass> TwoStringClass1
{
get
{
ObservableCollection<TwoStringClass> cmbclrs = new ObservableCollection<TwoStringClass>();
cmbclrs.Add(new TwoStringClass() { string1 = "This", colorHex = "#FF0000" });
cmbclrs.Add(new TwoStringClass() { string1 = "That", colorHex = "#FF352E2" });
cmbclrs.Add(new TwoStringClass() { string1 = "The Other", colorHex = "#FFF4F612" });
return cmbclrs;
}
}
ChildControl.xaml
<UserControl
x:Name="ChildControl"
>
<ComboBox x:Name="cmbFontColor" ItemsSource="{Binding TwoStringClass1}" >
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel>
<TextBlock Text="{Binding string1}" />
<Rectangle Fill="{Binding colorHex}" />
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</UserControl>
I know the Binding is Working because I get the correct number of (blank) items in the Combobox and can see the class name if I remove the ItemTemplate.
I can't figure out for the life of me why binding to the property name isn't working here as it does when the list comes from a control's own code behind.
There must be some other information I need to add to the TextBlock binding, but no matter what DataContext or RelativeSource I try, I always get blank items.
Data binding in WPF works with public properties only, not with fields. Your item class should look like this:
public class TwoStringClass
{
public string string1 { get; set; }
public string colorHex { get; set; }
}
That said, there are widely accepted naming convention, according to which you should use Pascal case for property names, e.g. String1 and ColorHex.
I believe the answer to your question is the same as the answer to a question I posted recently, which was answered by StewBob. Here is my slightly modified version of his answer, which should also fix the issue you are having.
You can see my original thread here: WPF ListBox with CheckBox data template - Binding Content property of Checkbox not working
I see you added "This", "That", and "The Other" as your data source, presumably for simplicity in posting this question to SO. Please be aware that if your true underlying data source can change, when doing DataBinding, your class needs to implement INotifyPropertyChanged for the data to properly display in the UI. An example:
public class TwoStringClass: INotifyPropertyChanged
{
private string _String1;
private string _ColorHex;
public string String1
{
get
{
return _String1;
}
set
{
if (value != _String1)
{
_String1 = value;
NotifyPropertyChanged("String1");
}
}
}
public string ColorHex
{
get
{
return _ColorHex;
}
set
{
if (value != _ColorHex)
{
_ColorHex = value;
NotifyPropertyChanged("ColorHex");
}
}
}
private void NotifyPropertyChanged(string info)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(info));
}
}
public event PropertyChangedEventHandler PropertyChanged;
}
This shows the class, two properties, and the needed event and method for implementing INotifyPropertyChanged.

WPF ComboBox Binding To Object On Initial Load

I have a combo box that is bound to a list of model objects. I've bound the combo box SelectedItem to a property that is the model type. All of my data binding works beautifully after the window has been loaded. The SelectedItem is set properly and I'm able to save the object directly with the repository.
The problem is when the window first loads I initialize the SelectedItem property and my combobox displays nothing. Before I moved to binding to objects I was binding to a list of strings and that worked just fine on initialization. I know I'm missing something but I can't figure it out.
Thanks in advance for any guidance you can provide.
(One note about the layout of this page. The combo boxes are actually part of another ItemTemplate that is used in a ListView. The ListView is bound to an observable collection in the main MV. Each item of this observable collection is itself a ModelView. It is that second ModelView that has the SelectedItem property.)
Here is my Model:
public class DistributionListModel : Notifier, IComparable
{
private string m_code;
private string m_description;
public string Code
{
get { return m_code; }
set { m_code = value; OnPropertyChanged("Code"); }
}
public string Name
{
get { return m_description; }
set { m_description = value; OnPropertyChanged("Name"); }
}
#region IComparable Members
public int CompareTo(object obj)
{
DistributionListModel compareObj = obj as DistributionListModel;
if (compareObj == null)
return 1;
return Code.CompareTo(compareObj.Code);
}
#endregion
}
Here the pertinent code in my ModelView:
public MailRoutingConfigurationViewModel(int agencyID)
: base()
{
m_agencyID = agencyID;
m_agencyName = DataManager.QueryEngine.GetAgencyName(agencyID);
IntializeValuesFromConfiguration(DataManager.MailQueryEngine.GetMailRoutingConfiguration(agencyID));
// reset modified flag
m_modified = false;
}
private void IntializeValuesFromConfiguration(RecordCheckMailRoutingConfiguration configuration)
{
SelectedDistributionList = ConfigurationRepository.Instance.GetDistributionListByCode(configuration.DistributionCode);
}
public DistributionListModel SelectedDistributionList
{
get { return m_selectedDistributionList; }
set
{
m_selectedDistributionList = value;
m_modified = true;
OnPropertyChanged("SelectedDistributionList");
}
}
And finally the pertinent XAML:
<UserControl.Resources>
<DataTemplate x:Key="DistributionListTemplate">
<Label Content="{Binding Path=Name}" />
</DataTemplate>
</UserControl.Resources>
<ComboBox
ItemsSource="{Binding Source={StaticResource DistributionCodeViewSource}, Mode=OneWay}"
ItemTemplate="{StaticResource DistributionListTemplate}"
SelectedItem="{Binding Path=SelectedDistributionList, Mode=TwoWay}"
IsSynchronizedWithCurrentItem="False"
/>
#SRM, if I understand correctly your problem is binding your comboBox to a collection of objects rather than a collection of values types ( like string or int- although string is not value type).
I would suggest add a two more properties on your combobox
<ComboBox
ItemsSource="{Binding Source={StaticResource DistributionCodeViewSource},
Mode=OneWay}"
ItemTemplate="{StaticResource DistributionListTemplate}"
SelectedItem="{Binding Path=SelectedDistributionList, Mode=TwoWay}"
SelectedValuePath="Code"
SelectedValue="{Binding SelectedDistributionList.Code }"/>
I am assuming here that DistributionListModel objects are identified by their Code.
The two properties I added SelectedValuePath and SelectedValue help the combobox identify what properties to use to mark select the ComboBoxItem by the popup control inside the combobox.
SelectedValuePath is used by the ItemSource and SelectedValue by for the TextBox.
don't call your IntializeValuesFromConfiguration from the constructor, but after the load of the view.
A way to achieve that is to create a command in your viewmodel that run this method, and then call the command in the loaded event.
With MVVM light toolkit, you can use the EventToCommand behavior... don't know mvvm framework you are using but there would probably be something like this.

Categories

Resources