How to Bind Listbox in WPF to a generic list? - c#

i'm having trouble getting a clear answer for this.
I have a Static class (DataHolder) that holds a static list with a complex type (CustomerName and CustomerID properties).
I want to bind it to a ListBox in WPF but add another item that will have the word "All" for future drag and drop capablilities.
Anyone?

Create a ViewModel Class you can databind to! The ViewModel can reference the static class and copy the items to its own collection and add the all item to it.
Like this
public class YourViewModel
{
public virtual ObservableCollection<YourComplexType> YourCollection
{
get
{
var list = new ObservableCollection<YourComplexType>(YourStaticClass.YourList);
var allEntity = new YourComplexType();
allEntity.Name = "all";
allEntity.Id = 0;
list.Insert(0, allEntity);
return list;
}
}
}
Note, sometimes, you need empty Items. Since WPF can't databind to null values you need to use the same approach to handle it. The empty business entity has been a best practice for it. Just google it.

That "All" item has to be part of the list you bind your ListBox against. Natuarally you can not add that item to the DataHolder list because it holds items of type Customer (or similar). You could of course add a "magic" Customer that always acts as the "All" item but that is for obvious reasons a serious case of design smell (it is a list of Customers after all).
What you could do, is to not bind against the DataHolder list directly but introduce a wrapper. This wrapper would be your ViewModel. You would bind your ListBox agains a list of CustomerListItemViewModel that represents either a Customer or the "All" item.
CustomerViewModel
{
string Id { get; private set; }
string Name { get; set; }
public static readonly CustomerViewModel All { get; private set; }
static CustomerViewModel()
{
// set up the one and only "All" item
All = new CustomerViewModel();
All.Name = ResourceStrings.All;
}
private CustomerViewModel()
{
}
public CustomerViewModel(Customer actualCustomer)
{
this.Name = actualCustomer.Name;
this.Id = actualCustomer.Id;
}
}
someOtherViewModel.Customers = new ObservableCollection<CustomerViewModel>();
// add all the wrapping CustomerViewModel instances to the collection
someOtherViewModel.Customers.Add(CustomerViewModel.All);
And then in your Drag&Drop code somewhere in the ViewModel:
if(tragetCustomerViewModelItem = CustomerViewModel.All)
{
// something was dropped to the "All" item
}
I might have just introduced you to the benefits of MVVM in WPF. It saves you a lot of hassle in the long run.

If you use binding than the data provided as the source has to hold all of the items, ie. you can't databind and then add another item to the list.
You should add the "All" item to the DataHolder collection, and handle the 'All' item separately in your code.

Related

WPF Binding Datagrid to List of ObservableCollections of different types

This could probably be solved from different angles, but I'm kind of stuck right now.
I want to have a Datagrid, which can add Data of different types into lists in the backend.
I have a list of ObservableCollections, which all have different types of elements in it (I'm using MVVM and I have a couple of different model classes). In my frontend, I have a DataGrid, which is supposed to display the data of the lists depending on which value is selected in a Combobox. It also has to happen dynamicly and can't use fixed grids, as things are added and removed constantly.
I got the Binding to work, so it displays the lists if there are any entries by making the lists the type ObservableCollection.
However, if they are empty, the column headers will disappear and thus, the user cannot enter a new row of the correct type, which is necessary.
This is what I use right now to initiate the Collections:
foreach (var lvm in ListOfValues)
{
Type listType = lvm.GetListType();
string lvmName = listType.Name;
// Create ObservableCollection
ObservableCollection<object> observableCollection = new ObservableCollection<object>
{
// add a basic line to signal the list type - THIS IS A WORKAROUND
Activator.CreateInstance(listType),
};
TemplateEntries.Add(new TemplateEntry { ListName = lvmName, Values = observableCollection });
}
Each TemplateEntry from the class contains the before named list of data. The TemplateEntries Property is just an ObservableCollection of TemplateEntries:
public ObservableCollection<TemplateEntry> TemplateEntries { get; set; } = new ObservableCollection<TemplateEntry>();
public class TemplateEntry : ObservableObject
{
public string ListName { get; set; }
public ObservableCollection<object> Values { get; set; }
public int Count { get { return Values.Count; } }
}
And in XAML I just have this line so far to display it:
<DataGrid Grid.Column="0" Grid.Row="1" Margin="5,5,5,5" AutoGenerateColumns="True" Name="Grid" CanUserAddRows="True" ItemsSource="{Binding ValuesToDisplay}" />
So my question: How can I remove the Activator.CreateInstance Line workaround and signal the datagrid which type it should use?
After some time, I managed to figure it out myself. The solution was to use the non-generic type IList instead of ObservableCollection for the Values of the TemplateEntry class, then cast the ObservableCollection into it (after creating it using Reflection). The DataGrid will accept this.
foreach (var lvm in ListModel.ListVMs)
{
Type listType = lvm.GetListType();
string lvmName = listType.Name;
Type collectionType = typeof(ObservableCollection<>).MakeGenericType(listType);
// Create ObservableCollection
IList observableCollection = (IList)Activator.CreateInstance(collectionType);
TemplateEntries.Add(new TemplateEntry { ListName = lvmName, Values = observableCollection });
}

Updating a single ObservableCollection but show in 2 different ListViews

Suppose I have one ObservableCollection that I insert, update and delete from.
public ObservableCollection<Item> AllItems {get;set;}
The Item class (which implements INotifyPropertyChanged) looks like this:
public class Item
{
public int Id {get;set;}
public int Kind {get;set;}
public string Text {get;set;}
}
I have this bound in my View in one ListView. Everything works, meaning if I delete or add or updates Items in the ObservableCollection from my ViewModel, they are updating in my ListView as expected.
But now I need to split the ListView in 2 ListViews in my View. The first ListView must display all items where Item.Kind=1. And the other ListView must have all items where Item.Kind=2.
I don't mind binding to two different ObservableCollections from my ViewModel, but I would really like a solution where I can just do:
var item = new Item { Id=22, Kind=1, Text="SomeText" };
AllItems.Add(item);
AllItems.First(x => x.Id==22).Text = "SomeOtherText";
AllItems.Remove(item);
And then these operations are automatically reflected in the 2 ListViews. So in the above example, the new Item is only viewed, updated and removed from the first ListView (where Item.Kind=1).
Is that possible?
EDIT: I should say that I have tried to use two CollectionViewSources with the same ObservableCollection as source. But that fails and does not seem to be the correct solution.
create two ListCollectionViews with different filters - one for each ListView
public ICollectionView Items_1 { get; private set; }
public ICollectionView Items_2 { get; private set; }
public ViewModel
{
Items_1 = new ListCollectionView(AllItems);
Items_1.Filter = o => (o as Item).Kind == 1;
Items_2 = new ListCollectionView(AllItems);
Items_2.Filter = o => (o as Item).Kind == 2;
}

Binding combobox to another combobox

I have a problem with binding combobox to another combobox. I'm trying to dynamically pass a parameter (id) from first combobox to the method of initiating second combobox. For example, if I selected the first item in first combobox, then second combobox will initialize with parameter that selected from first combobox.
XAML:
<ComboBox Name="ItServiceCmbox" ItemsSource="{Binding ItServiceMetricsNames}" DisplayMemberPath="ServiceName" SelectedValuePath="ServiceId" />
<ComboBox Name="MetricCmbox" ItemsSource="{Binding SelectedItem.MetricId, ElementName=ItServiceCmbox}" DisplayMemberPath="MetricName" SelectedValuePath="MetricId"/>
C#:
public partial class MainWindow : Window
{
readonly MetricsValuesHelper _metricsValuesHelper = new MetricsValuesHelper(new Repository());
public static int SelectedService;
public static int SelectedMetric;
public ObservableCollection<ItServiceMetricsNames> ItServiceMetricsNames { get; set; }
public MainWindow()
{
InitializeComponent();
this.DataContext = this;
SelectedService = Convert.ToInt32(ItServiceCmbox.SelectedItem);
ItServiceMetricsNames = new ObservableCollection<ItServiceMetricsNames>();
ItServiceMetricsNames.Add(new ItServiceMetricsNames()
{
ServiceId = _metricsValuesHelper.GetServiceId(),
ServiceName = _metricsValuesHelper.GetServiceName(),
MetricId = _metricsValuesHelper.GetMetricId(SelectedService),
MetricName = _metricsValuesHelper.GetMetricName(SelectedService)
});
}
}
And ItServiceMetricsNames class:
public class ItServiceMetricsNames
{
public List<int> ServiceId { get; set; }
public List<string> ServiceName { get; set; }
public List<int> MetricId { get; set; }
public List<string> MetricName { get; set; }
}
Is it possible? Thanks for any answers!
This is a messy, naive implementation I did last year that seemed to work. There's definitely a better way out there. Instead of trying to do any actual binding in my xaml I made event handlers. You may create event handlers for ComboBoxes that are triggered whenever the sending ComboBox loses focus, closes it's DropDown, changes selection, etc.
If you want one ComboBox dependent on another, you may make the dependent ComboBox disabled until a selection is made in the independent ComboBox. Once a selection is made, you populate and enable the dependent ComboBox with the appropriate data.
Event handlers in your code will look something like this:
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
// Independent ComboBox is the sender here
ProcessComboBoxes(sender as ComboBox);
}
The ProcessComboBoxes method will look different depending on what you're trying to do. But, essentially, it will identify the target/dependent ComboBox that you want to conditionally populate -- do this either with a Dictionary that maps from ComboBox to ComboBox or something you find suiting. After identifying the target, you will clear any items previously added, and then repopulate with your new ones. Below is a method in pseudocode (practically).
private void ProcessComboBoxes(ComboBox senderBox)
{
ComboBox dependentBox = lookupDependent[senderBox];
var itemType = itemTypes[senderBox.selectedIndex];
var listOfItemsNeeded = lookupItemsByType[itemType];
dependentBox.Items.Clear();
foreach (string item in listOfItemsNeeded){
dependentBox.Items.Add(item);
}
dependentBox.IsEnabled = true;
}
Don't forget to add your eventhandlers to your xaml. Make sure to pay close attention to the call hierarchy of events and determine when exactly you want your dependent ComboBox to be repopulated.

C# WPF MVVM ComboBox Binding

First of all, what I'm trying to do is a "simple" binding of a ComboBox to my source.
The structure is something like:
public class Data
{
public ObservableList<string> List {get;set;}
public string Selected {get;set;}
}
Also, it implements INotifyPropertyChanged interface.
My problem is, i found several solutions to do this via XAML, unfortunately i can't do it with XAML since my ComboBoxes have to be generated during runtime.
So my question is, how i can bind my ComboBox to Data.List, and also the selected item (value?) to Data.Selected, and this one should be TwoWay so my Data class knows that something was selected. Keep in mind this has to be through c# code (XAML is no option unfortunately).
Thanks in advance. :)
It's pretty easy. Assuming, that Data has properties instead of fields:
public class Data
{
public Data()
{
List = new ObservableCollection<string>
{
"Apple", "Orange", "Lime"
};
}
public ObservableCollection<string> List { get; private set; }
public string Selected { get; set; }
}
you can write this:
var comboBox = new ComboBox
{
DataContext = new Data()
};
comboBox.SetBinding(ComboBox.ItemsSourceProperty, new Binding("List"));
comboBox.SetBinding(ComboBox.SelectedItemProperty, new Binding("Selected")
{
Mode = BindingMode.TwoWay
});
To add ComboBox into visual tree, just call proper method for the container. E.g., this will work with any ContentControl (like Window):
AddChild(comboBox);
how i can bind my combobox to Data.List, and also the selected item (value?)
Create a custom composite user control which contains the combobox. Map the combobox's properties to two dependencies properties created on the custom control, one to load the data and the other to provide an on demand selected item's data. Any plumbing needs are done inside the codebehind which ultimately provides all the magic.
Then you can create/bind this control dynamically in codebehind as needed in the other page you are working on.
Sounds like a sort of "recursive binding". If your combos are in a container control, what you need is bound the container to a collection of your single combo model, so each view in the ItemsControl will be bound to a single combo model.

Make ComboBox accept only specific type

I currently have a ComboBox in my Windows Forms Application. In order to specify which values the ComboBox will contain, I set DataSource property of the ComboBox to some array so that ComboBox contains values from that array. I could also use Items.Add() to add new values to ComboBox. However, I want to make sure that ComboBox can be populated with objects of some specific type. So, if I have a class called X, then I want to make it so that only an array of type X can be used as a data source for the ComboBox. Right now, ComboBox accepts objects of type System.Object. How can I achieve it? Is there a property of ComboBox that I need to set to be equal to my data type's name? Or is there an event that will check whether an object added to my ComboBox is of the needed type and will throw an exception if not?
I was thinking of creating a new class as a subtype of ComboBox, and overriding the Add method of Items property so that Add checks whether its argument is of the needed type (not sure if and how I can do it). Even if I do that, there are still other ways to add new values into ComboBox (AddRange, CopyTo, etc.), so I think there should be a more elegant solution to this problem.
If you want to control the type of item that the ComboBox can contain, you could try creating a new class derived form ComboBox, but you'd run into the problem that it still has the ComboBox.ObjectCollection Items property which would still accept any type! And (unfortunately for your idea of overriding) the Add method isn't virtual.
The only practical solution that I could think of would be to abstract the ComboBox somehow. If this isn't shared code, I would recommend just creating a method that you would use to add items to the ComboBox. Something like:
// NOTE: All items that are added to comboBox1 need to be of type `SomeType`.
private void AddItemToComboBox(SomeType item)
{
comboBox1.Items.Add(item);
}
Any attempt to add a non-SomeType object to the ComboBox would be met with a compiler error. Unfortunately, there's no easy way to prevent someone from still adding a non-SomeType item to ComboBox.Items directly.
Again, if this isn't shared code, it shouldn't really be an issue.
You can hide Items property by your
own Items property of custom type which taking as parameter original ItemsCollection
Example class for testing
public class Order
{
public Int32 ID { get; set; }
public string Reference { get; set; }
public Order() { }
public Order(Int32 inID, string inReference)
{
this.ID = inID;
this.Reference = (inReference == null) ? string.Empty : inReference;
}
//Very important
//Because ComboBox using .ToString method for showing Items in the list
public override string ToString()
{
return this.Reference;
}
}
With next class I tried wrap ComboBox's items collection in own type.
Where adding items must be concrete type
Here you can add other methods/properties you need (Remove)
public class ComboBoxList<TCustomType>
{
private System.Windows.Forms.ComboBox.ObjectCollection _baseList;
public ComboBoxList(System.Windows.Forms.ComboBox.ObjectCollection baseItems)
{
_baseList = baseItems;
}
public TCustomType this[Int32 index]
{
get { return (TCustomType)_baseList[index]; }
set { _baseList[index] = value; }
}
public void Add(TCustomType item)
{
_baseList.Add(item);
}
public Int32 Count { get { return _baseList.Count; } }
}
Here custom combobox class derived from ComboBox
Added: generic type
public class ComboBoxCustomType<TCustomType> : System.Windows.Forms.ComboBox
{
//Hide base.Items property by our wrapping class
public new ComboBoxList<TCustomType> Items;
public ComboBoxCustomType() : base()
{
this.Items = new ComboBoxList<TCustomType>(base.Items);
}
public new TCustomType SelectedItem
{
get { return (TCustomType)base.SelectedItem; }
}
}
Next code used in the Form
private ComboBoxCustomType<Order> _cmbCustom;
//this method used in constructor of the Form
private void ComboBoxCustomType_Initialize()
{
_cmbCustom = new ComboBoxCustomType<Order>();
_cmbCustom.Location = new Point(100, 20);
_cmbCustom.Visible = true;
_cmbCustom.DropDownStyle = ComboBoxStyle.DropDownList;
_cmbCustom.Items.Add(new Order(0, " - nothing - "));
_cmbCustom.Items.Add(new Order(1, "One"));
_cmbCustom.Items.Add(new Order(2, "Three"));
_cmbCustom.Items.Add(new Order(3, "Four"));
_cmbCustom.SelectedIndex = 0;
this.Controls.Add(_cmbCustom);
}
Instead of overriding ComboBox (which wont work as stated in itsme86's answer) you could override usercontrol, add a combobox to this, and then only expose the elements that you wish to work with. Something similar to
public partial class MyComboBox<T> : UserControl where T: class
{
public MyComboBox()
{
InitializeComponent();
}
public void Add(T item)
{
comboBox1.Items.Add(item);
}
public IEnumerable<T> Items
{
get { return comboBox1.Items.Cast<T>(); }
}
}
Please note however that some pieces of automated software rely on access the the underlying controls however so this may cause some issues.
This approach never changes the Items of the combobox so they will still store as objects but when you access them, you are casting them to the correct type and only allowing them to be added of that type. You can create a new combobox via
var myCB = new MyComboBox<ItemClass>();

Categories