I understand this has been asked several times but I am unable to find a toolkit that allows for a combobox in a WPF DataGrid that allows to filter and autocomplete, none of the questions provide any solution that I was able to make work. The behavior I need in my combobox column is:
- Pressing on drop down of the cell will show all objects in the ItemSource.
- As soon as you press on the "text box" portion of the combobox it would filter the drop down as you enter/delete text and depending on the text in the combobox.
- Entering something invalid would show "not found" in the drop down. Pressing out or losing focus would just retain the last valid item in that combobox.
I have tried several options that I couldnt either get to work with a datagrid or they simply did not have the requirements above. Examples are:
- DotNetProjects.WpfToolkit
- Syncfusion.ComboBoxAdv
I have created a simple test application to test several options. My models are a Fruit and Vegetable object with just one property (Name). I have a Customer object that has a name and item (fruit or vegetable). Models
public class Vegetable : IsFruitOrVegetable, INotifyPropertyChanged
{
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
RaisePropertyChanged("Name");
}
}
public Vegetable()
{
}
public Vegetable(string _Name)
{
this.Name = _Name;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string PropertyName)
{
var property = PropertyChanged;
if (property != null)
property(this, new PropertyChangedEventArgs(PropertyName));
}
}
public class Fruit: IsFruitOrVegetable, INotifyPropertyChanged
{
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
RaisePropertyChanged("Name");
}
}
public Fruit()
{
}
public Fruit(string _Name)
{
this.Name = _Name;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string PropertyName)
{
var property = PropertyChanged;
if (property != null)
property(this, new PropertyChangedEventArgs(PropertyName));
}
}
public class Customer : INotifyPropertyChanged
{
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
RaisePropertyChanged("Name");
}
}
private IsFruitOrVegetable item;
public IsFruitOrVegetable Item
{
get
{
return item;
}
set
{
item = value;
RaisePropertyChanged("Item");
}
}
private int totalQuantity;
public int TotalQuantity
{
get
{
return totalQuantity;
}
set
{
totalQuantity = value;
RaisePropertyChanged("TotalQuantity");
}
}
public Customer()
{
}
public Customer(string _Name)
{
this.Name = _Name;
Item = null;
TotalQuantity = 1;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string PropertyName)
{
var property = PropertyChanged;
if (property != null)
property(this, new PropertyChangedEventArgs(PropertyName));
}
}
My viewmodel consists of a list of my customers (CustomersList) and itemsList (list of items for fruits and vegetables).
public class MainViewModel : INotifyPropertyChanged
{
private ObservableCollection<Customer> customersList;
public ObservableCollection<Customer> CustomersList
{
get
{
return customersList;
}
set
{
customersList1 = value;
}
}
private ObservableCollection<IsFruitOrVegetable> itemsList;
public ObservableCollection<IsFruitOrVegetable> ItemsList
{
get
{
return itemsList;
}
set
{
itemsList = value;
}
}
private IsFruitOrVegetable itemOption;
public IsFruitOrVegetable ItemOption
{
get
{
return itemOption;
}
set
{
itemOption = value;
}
}
public MainViewModel()
{
ItemsList = new ObservableCollection<IsFruitOrVegetable>();
ItemsList.Add(new Fruit("Apple"));
ItemsList.Add(new Fruit("Banana"));
ItemsList.Add(new Fruit("Avocado"));
ItemsList.Add(new Fruit("Blueberries"));
ItemsList.Add(new Vegetable("Broccoli"));
ItemsList.Add(new Vegetable("Cabbage"));
ItemsList.Add(new Vegetable("Carrot"));
ItemsList.Add(new Vegetable("Cauliflower"));
CustomersList = new ObservableCollection<Customer>();
CustomersList.Add(new Customer("Bob"));
CustomersList.Add(new Customer("Tony"));
CustomersList.Add(new Customer("John"));
CustomersList[0].Item = ItemsList[0];
CustomersList[1].Item = ItemsList[1];
CustomersList[2].Item = ItemsList[2];
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string PropertyName)
{
var property = PropertyChanged;
if (property != null)
property(this, new PropertyChangedEventArgs(PropertyName));
}
}
My view for:
<Label Content="DEFAULT COMBOBOX" HorizontalAlignment="Left" Height="32" Margin="22,10,0,0" VerticalAlignment="Top" Width="207"/>
<ComboBox HorizontalAlignment="Left" Margin="22,47,0,0" VerticalAlignment="Top" Width="120" DisplayMemberPath="Name"
SelectedValue="{Binding ItemOption, Source={StaticResource viewModel}, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding ItemsList,Source={StaticResource viewModel}}"/>
<DataGrid HorizontalAlignment="Left" Height="291" Margin="22,90,0,0" VerticalAlignment="Top" Width="207"
ItemsSource="{Binding CustomersList,Source={StaticResource viewModel}}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Customer" Binding="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="*"/>
<DataGridComboBoxColumn Header="Item" ItemsSource="{Binding ItemsList,Source={StaticResource viewModel}}" Width="*"
DisplayMemberPath="Name"
SelectedItemBinding="{Binding Item, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" />
</DataGrid.Columns>
</DataGrid>
I know alot of solutions implement a template column but I cant find a combobox that works with the intent I want to implement this. Can someone provide a super simple example that allows or point me in the right direction?
CodePudding user response:
I have done something similar, try https://github.com/DamnjanMarkovic/WPFMedicalData.
CodePudding user response:
The behaviour is very specific. If you do not find any ComboBoxes, that behave the correct way you always can create it by yourself.
One Way could be a ToggleButton in Combination with an Popup. For Reusability you also can put it into an UserControl
Here a very minimalistic example, how you could start creating the control by yourself (not functional and tested).
<ToggleButton x:name="MyButton" />
<Popup
PlacementTarget="{Binding ElementName=MyButton}"
Width="{Binding ActualWidth, ElementName=MyButton}">
<Border
BorderBrush="Black"
BorderThickness="2"
Background="White">
<StackPanel>
<!-- Some Other Controlls like an TextBox for searching the contents -->
<!-- List View for displaying the ItemSource -->
</StackPanel>
</Border>
</Popup>
To impement it into a Datagrid i would Recomment the TemplateCollumn.
<DataGridTemplateColumn Header="Header">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<uc:CustomUserControl />
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
Note that for the ItemSource requires a relative source if it is not part of your items model.
ItemsSource="{Binding RelativeSource={RelativeSource AncestorType={x:Type DataGrid}, Mode=FindAncestor}, Path=DataContext.YourItemSource}"