I have created a usercontrol that is essentially an image with a combobox to provide some custom functionality. My current code has the object types hardcoded and I need help/pointers modifying the code to allow this to function on any object data types so I dont have to re-write this code for every combobox with different object types. From what I read online there is no way to make my usercontrol use a generic type . Is there anyway I can refactor this code to get the same result. Here is the result that I am looking for(and currently have). It is basically a combobox with an icon and it allows filtering the drop down based on the values entered.

My simplified working code - two object types Fruit and Vegetable that share a IsFruitOrVegetable interface:
public class Fruit: IsFruitOrVegetable, INotifyPropertyChanged
{
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
RaisePropertyChanged("Name");
}
}
public FoodType Type { get; set; }
public Fruit()
{
this.Type = FoodType.Fruit;
}
public Fruit(string _Name)
{
this.Name = _Name;
this.Type = FoodType.Fruit;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string PropertyName)
{
var property = PropertyChanged;
if (property != null)
property(this, new PropertyChangedEventArgs(PropertyName));
}
}
public class Vegetable : IsFruitOrVegetable, INotifyPropertyChanged
{
private string name;
public string Name
{
get
{
return name;
}
set
{
name = value;
RaisePropertyChanged("Name");
}
}
public FoodType Type { get; set; }
public Vegetable()
{
this.Type = FoodType.Vegetable;
}
public Vegetable(string _Name)
{
this.Name = _Name;
this.Type = FoodType.Vegetable;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string PropertyName)
{
var property = PropertyChanged;
if (property != null)
property(this, new PropertyChangedEventArgs(PropertyName));
}
}
public enum FoodType
{
None,
Vegetable,
Fruit
}
public interface IsFruitOrVegetable
{
FoodType Type { get; set; }
string Name { get;set;}
}
public class NoneType : IsFruitOrVegetable
{
public FoodType Type { get; set; }
public string Name { get; set; }
public NoneType()
{
Name = "NotFound";
Type = FoodType.None;
}
}
Customer object that holds a IsFruitOrVegetable:
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");
}
}
public Customer()
{
}
public Customer(string _Name)
{
this.Name = _Name;
Item = null;
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string PropertyName)
{
var property = PropertyChanged;
if (property != null)
property(this, new PropertyChangedEventArgs(PropertyName));
}
}
My ViewModel:
public class MainViewModel : INotifyPropertyChanged
{
private ObservableCollection<Customer> customersList;
public ObservableCollection<Customer> CustomersList
{
get
{
return customersList;
}
set
{
customersList = value;
}
}
// This is static, so that the ComboBox can revert back to all of the items
private static ObservableCollection<IsFruitOrVegetable> itemsList;
public static ObservableCollection<IsFruitOrVegetable> ItemsList
{
get
{
return itemsList;
}
set
{
itemsList = 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 Fruit("Watermelon"));
ItemsList.Add(new Vegetable("Broccoli"));
ItemsList.Add(new Vegetable("Cabbage"));
ItemsList.Add(new Vegetable("Carrot"));
ItemsList.Add(new Vegetable("Cauliflower"));
ItemsList.Add(new Vegetable("Potato"));
ItemsList.Add(new Vegetable("Cucumber"));
ItemsList.Add(new Vegetable("Kale"));
ItemsList.Add(new Vegetable("Bellpepper"));
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));
}
}
Usercontrol.xaml:
<Grid DataContext="{Binding ElementName=Root}">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="30"/>
<ColumnDefinition/>
</Grid.ColumnDefinitions>
<!-- Image at the side of the ComboBox -->
<Image Height="30" Width="30" Grid.Column="0">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding SelectedItem.Type}" Value="{x:Static local:FoodType.Vegetable}">
<Setter Property="Source" Value="/Resources/VegetableIcon.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding SelectedItem.Type}" Value="{x:Static local:FoodType.Fruit}">
<Setter Property="Source" Value="/Resources/FruitIcon.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<!-- Actual ComboBox -->
<ComboBox x:Name="CustomCombo" Grid.Column="1"
IsTextSearchEnabled="False" IsReadOnly="True" KeyDown="CustomCombo_KeyDown"
DropDownOpened="CustomCombo_DropDownOpened" DropDownClosed="CustomCombo_DropDownClosed"
IsEditable="True" TextSearch.TextPath="Name"
Text="{Binding Text, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
SelectedItem="{Binding SelectedItem, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding ItemsSource, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}">
<ComboBox.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<!-- StackPanel consisting of an Image and TextBlock as ItemTemplate-->
<Image Height="30" Width="30">
<Image.Style>
<Style TargetType="{x:Type Image}">
<Style.Triggers>
<DataTrigger Binding="{Binding Path=Type}" Value="{x:Static local:FoodType.Vegetable}">
<Setter Property="Source" Value="/Resources/VegetableIcon.png"/>
</DataTrigger>
<DataTrigger Binding="{Binding Path=Type}" Value="{x:Static local:FoodType.Fruit}">
<Setter Property="Source" Value="/Resources/FruitIcon.png"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<TextBlock Text="{Binding Path=Name}" VerticalAlignment="Center"/>
</StackPanel>
</DataTemplate>
</ComboBox.ItemTemplate>
</ComboBox>
</Grid>
CustomComboBox.xaml.cs:
public partial class CustomComboBox : UserControl, INotifyPropertyChanged
{
#region States
public static readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register(
"ItemsSource",
typeof(ObservableCollection<IsFruitOrVegetable>),
typeof(CustomComboBox),
new PropertyMetadata(null)
);
public ObservableCollection<IsFruitOrVegetable> ItemsSource
{
get
{
return (ObservableCollection<IsFruitOrVegetable>)GetValue(ItemsSourceProperty);
}
set
{
SetValue(ItemsSourceProperty, value);
RaisePropertyChanged("ItemsSource");
}
}
public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register(
"SelectedItem",
typeof(IsFruitOrVegetable),
typeof(CustomComboBox),
new PropertyMetadata(null)
);
public IsFruitOrVegetable SelectedItem
{
get
{
return (IsFruitOrVegetable)GetValue(SelectedItemProperty);
}
set
{
SetValue(SelectedItemProperty, value);
RaisePropertyChanged("SelectedItem");
}
}
private string text;
public string Text
{
get
{
return text;
}
set
{
text = value;
// Search Function
if (value != null && value != "")
{
var items = new ObservableCollection<IsFruitOrVegetable>();
foreach (var item in MainViewModel.ItemsList)
{
if (item.Name.ToLower().Contains(Text.ToLower()))
{
items.Add(item);
}
}
if (items.Count == 0)
{
items.Add(new NoneType());
}
ItemsSource = items;
} else
{
ItemsSource = MainViewModel.ItemsList;
}
RaisePropertyChanged("Text");
RaisePropertyChanged("ItemsSource");
}
}
#endregion
public CustomComboBox()
{
InitializeComponent();
// Sets initial Last Selected Item
if (SelectedItem != null)
{
lastSelectedItem = SelectedItem;
}
}
public event PropertyChangedEventHandler PropertyChanged;
public void RaisePropertyChanged(string PropertyName)
{
var property = PropertyChanged;
if (property != null)
property(this, new PropertyChangedEventArgs(PropertyName));
}
#region Event Handlers
private IsFruitOrVegetable lastSelectedItem;
// Saves Last Selected Item when the drop down is opened
private void CustomCombo_DropDownOpened(object sender, EventArgs e)
{
ComboBox comboBox = sender as ComboBox;
comboBox.IsReadOnly = false;
if (SelectedItem != null && SelectedItem.Type != FoodType.None)
{
lastSelectedItem = SelectedItem;
}
Text = "";
}
// Validates if Selected Item is of Type None, then it will revert back to
// the last selected Item which is saved
private void CustomCombo_DropDownClosed(object sender, EventArgs e)
{
ComboBox comboBox = sender as ComboBox;
comboBox.IsReadOnly = true;
if (SelectedItem == null || SelectedItem.Type == FoodType.None)
{
if (lastSelectedItem != null)
{
SelectedItem = lastSelectedItem;
Text = SelectedItem.Name;
} else
{
SelectedItem = null;
Text = "";
}
} else if (SelectedItem != null && SelectedItem.Type != FoodType.None)
{
Text = SelectedItem.Name;
}
}
#endregion
private void CustomCombo_KeyDown(object sender, KeyEventArgs e)
{
...
}
}
Finally my view:
<Window.Resources>
<vm:MainViewModel x:Key="viewModel"/>
</Window.Resources>
<Grid>
<DataGrid HorizontalAlignment="Left" Height="291" Margin="22,90,0,0" VerticalAlignment="Top" Width="300"
ItemsSource="{Binding CustomersList,Source={StaticResource viewModel}}" AutoGenerateColumns="False">
<DataGrid.Columns>
<DataGridTextColumn Header="Customer" Binding="{Binding Name, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}" Width="*"/>
<DataGridTemplateColumn Header="Item" Width="*">
<DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<local:CustomComboBox SelectedItem="{Binding Item, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
ItemsSource="{Binding ItemsList, Source={StaticResource viewModel}}"/>
</DataTemplate>
</DataGridTemplateColumn.CellTemplate>
</DataGridTemplateColumn>
</DataGrid.Columns>
</DataGrid>
</Grid>