This should be a duplicate question but I'm posting it because none of the answers anywhere are working.
I have a dictionary of the types:
private Dictionary<IModule, AssemblyLoadContext> ModuleList = new Dictionary<IModule, AssemblyLoadContext>();
I am trying to bind the names of the IModules (IModule.Handle, for everything that implements IModule) to a combobox.
I've tried many things and searched through every answer on google but nothing works. This is apparently the way you are supposed to do it:
comboBox1.DataSource = new BindingSource(ModuleList, null);
comboBox1.DisplayMember = "Value";
comboBox1.ValueMember = "Key";
When I do this I get a RUNTIME error: (System.ArgumentException: 'Cannot bind to the new display member. (Parameter 'newDisplayMember')'
)
When I try swapping key and value I get this same error: (System.ArgumentException: 'Cannot bind to the new display member. (Parameter 'newDisplayMember')'
)
When I try other combinations of key/value, I get random results. Sometimes it will show the entire class name (not helpful), sometimes it will show the ToString representation (overloaded and works perfectly except doesn't UPDATE after startup), and sometimes it just shows nothing or the program gives an error during runtime.
However no combination of things I have tried actually gets the BOX contents to UPDATE when modules are loaded and unloaded (the modules themselves are definitely loading/unloading and work fine).
This is supposedly working as of many years ago and I can only imagine microsoft broke something in one of their updates because the intended method does NOT work for me.
This is using .NET core 3.1 modules and .NET 5.0 application (required in order for modules to work because microsoft 5.0 exe does not work with microsoft 5.0 dll).
The overloaded ToString method of IModule returns Handle which is a string that names the module, IE "ConsoleModule", and works as intended. Everything else is working except the data binding.
Can anyone else at least confirm this data binding method actually works in .NET 5.0 and/or 3.1? Rapidly losing sanity.
Whenever you have a sequence of similar items, that you want to show in a ComboBox, you need to tell the ComboBox which property of the items should be used to display each item. You were right, this is done using ComboBox.DisplayMember
Your Dictionary<IModule, AssemblyLoadContext> implements IEnumerable<KeyValuePair<IModule, AssemblyLoadContext>, so you can regard it as if it is a sequence of KeyValuePairs. Every KeyValuePair has a Key of type IModule, and a Value of type AssemblyLoadContext.
The IModule and the AssemblyLoadContext have several properties. You need to decide which property of them you want to show.
I am trying to bind the names of the IModules (IModule.Handle)
I guess that every IModule has a property Handle, and you want to display this Handle in the ComboBox.
comboBox1.DisplayMember = nameof(IModule.Handle);
If you need a display only, so no updates, it is enough to convert your original sequence into a list:
Dictionary<IModule, AssemblyLoadContext> myData = ...
comboBox.DataSource = myData.ToList();
However, if you want to update the displayed data, you need an object that implements IBindingList, like (surprise!) BindingList<T>. See BindingList.
You can make a BindingList<KeyValuePair<IModule, AssemblyLoadContext>>, but this is hard to read, hard to understand, difficult to unit test, difficult to reuse and maintain. My advice would be to create a special class for this.
I haven't got a clue what's in the IModule, so you'll have to find a proper class name. I'll stick with:
class DisplayedModule
{
public string DisplayText => this.Module.Handle;
public IModule Module {get; set;}
public AssemblyLoadContext AssemblyLoadContext{get; set;}
}
And in the constructor of your form:
public MyForm()
{
InitializeComponent();
this.ComboBox1.DisplayMember = nameof(DisplayedModule.DisplayText);
This way, if you want to change the text that needs to be displayed, all you have to do is change property DisplayText.
public BindingList<DisplayedModule> DisplayedItems
{
get => (BindingList<DisplayedModule>)this.comboBox1.DataSource;
set => this.comboBox1.DataSource = value;
}
You need procedures to get the initial data:
private Dictionary<IModule, AssemblyLoadContext> GetOriginalData() {...} // out of scope of this question
private IEnumerable<DisplayedModule> OriginalDataToDisplay =>
this.GetOriginalData().Select(keyValuePair => new DisplayedModule
{
Module = keyValuePair.Key,
AssemblyLoadcontext = keyValuePair.Value;
});
I have put this in separate procedures, to make it very flexible. Easy to understand, easy to unit test, easy to change and to maintain. If for instance your Original data is not in a Dictionary, but in a List, or an Array, or from a database, only one procedure needs to change.
To initially fill the comboBox is now a one-liner:
private ShowInitialComboBoxData()
{
this.DisplayedItems = new BindingList<DisplayedModule>
(this.OriginalDataToDisplay.ToList());
}
private void OnFormLoad(object sender, ...)
{
this.ShowInitialComboBoxData();
... // other inits during load form
}
If the operator adds / removed an element to the list, the bindinglist is automatically updated. If something happens, after which you know that the dictionary has been changed, you can simply change the bindingList For small lists that do not change often, I would make a complete new BindingList. If the List changes often, or it is a big list, consider to Add / Remove the original BindingList.
private void AddDisplayedModule(DisplayedModule module)
{
this.DisplayedItems.Add(module);
}
private void RemoveDisplayedMOdule(DisplayedModule module)
{
this.DisplayedItems.Remove(module);
}
private void ModuleAddedToDictionary(IModule module, AssemblyLoadContext assembly)
{
this.AddDisplayedModule(new DisplayedModule
{
Module = module,
AssemblyLoadContext = assembly,
})
}
If the operator makes some changes, and indicates he finished editing the comboBox, for instance by pressing the "Apply Now" button, you can simply get the edited data:
private void ButtonApplyNowClicked(object sender, ...)
{
// get the edited data from the combobox and convert to a Dictionary:
Dictionary<IModule, AssemblyLoadContext> editedData = this.DisplayedItems
.ToDictionary(displayedItem => displayedItem.Module, // Key
displayedItem => displayedItem.AssemblyLoadContext); // Value;
this.ProcesEditedData(editedData);
}
To access the Selected item of the comboBox
DisplayedModule SelectedModule => (DisplayedModule)this.comboBox1.SelectedItem;
Conclusion
By separating you data from the way that it is displayed, changes will be minimal if you decide to change your view: change Combobox into a ListBox, or even a DataGridView. Or if you decide to change your data: not a Dictionary, but a sequence from a Database
Related
I have asked this question before and it got flagged for not being detailed enough so I thought I would rephrase it and better detail my problem.
Summarise the problem:
First of all, I was following this tutorial 'Using SQLite in C# - Building Simple, Powerful, Portable Databases for Your Application' (https://www.youtube.com/watch?v=ayp3tHEkRc0). The creator of this tutorial had this form:
In the form you could add a first name and a listname and it shows up in the List People. This was his code-behind:
I followed the tutorial and I decided I would replicate this but instead of 'Person' it will display the user's statistics:
I want to be able to display the SQLite table content inside a listbox. But the problem is that in WPF there is no such thing as 'DataSource' or 'DisplayMember'. This is my code so far:
public partial class databasetestform : Window
{
List<UserStatistics> userstatistics = new List<UserStatistics>();
public databasetestform()
{
InitializeComponent();
}
private void LoadStatistics_List()
{
userstatistics = SQLiteDataAccess.LoadStatistics();
WriteUpStatisticsList();
}
public void WriteUpStatisticsList()
{
// Unsure what to do here
}
private void Load_button_Click(object sender, RoutedEventArgs e)
{
LoadStatistics_List();
UserStatistics s = new UserStatistics();
int result1;
int result2;
if(int.TryParse(AVGtxtbox.Text, out result1))
{
s.GamesPlayed = result1;
SQLiteDataAccess.SaveStatistics(s);
}
if (int.TryParse(AVGtxtbox.Text, out result2))
{
s.GamesPlayed_ScoreAverage = result2;
SQLiteDataAccess.SaveStatistics(s);
}
AVGtxtbox.Text = "";
PLYDtxtbox.Text = "";
}
}
Describe what you've tried
I've tried for many hours to find a work-around, I tried to use 'ItemsSource' instead of DataSource but I still had no work-around for 'DisplayMember'.
Hope I can get a solution to this.
Thanks,
Seems like you're on the right track.
ItemsSource is the correct property to use for your data source. It accepts any IEnumerable so your List<T> should work just fine.
Instead of DisplayMember, I think you're looking for DisplayMemberPath. You can set this to the name of a property declared on your UserStatistics object and the ListBoxItems will display the value of that property.
The C# would be as simple as:
MyListBox.ItemsSource = userstatistics;
DisplayMemberPath would usually be set on the ListBox tag in the XAML:
<ListBox Name="MyListBox" DisplayMemberPath="SomePropertyName"/>
The above gives you the simple answer, which does work, but there are a couple other concepts you should be aware of and maybe look into later on:
In WPF, one generally tries to avoid giving a UI element an explicit name and referencing it in code. The goal behind this is to try and separate the functional components of the program from the visual interface.
To acheive this, the visual components (declared in XAML) are usually connected to the functional code (written in C# or VB.NET) using data binding.
This isn't a completely strict rule, however, and I've broken it on a few occasions when it made scense, or when referencing a UI element by name was just far, far easier.
While DisplayMemberPath works for displaying the value of a single property, sometimes you need a more complex display of items. This can be accomplished using data templates and the ItemTemplate property. It requires knowledge and use of data binding.
In order to get data binding in WinForms (to a DataGridView, for instance) to work anything like you'd hope and add/delete rows as the collection changes, you have to use a BindingList (or DataTable) instead of a generic List. The problem is, almost nobody has the first instinct to code with a BindingList instead of a List in their libraries.
The BindingList implements two events that the List doesn't have and these must be the difference in data binding action (also, a property to suppress the second event):
AddingNew
ListChanged
RaiseListChangedEvents
Similarly, the DataTable has two events which probably enable similar functionality:
RowDeleted
TableNewRow
EDIT: As the helpful SO community pointed out here and in another article, a List can be converted (maybe more accurately encapsulated?) by calling the correct BindingList constructor:
BindingList<MyType> MyBL = new BindingList<MyType>();
MyList.ForEach(x => MyBL.Add(x));
My situation is a little more complicated as illustrated by the code below.
EDIT Added INotifyPropertyChanged stuff that must exist in the real library.
public class RealString : INotifyPropertyChanged
{
private int _KnotCount = 0;
private List<KnotSpace> _KnotSpacings = new List<KnotSpace>();
public RealString()
{
KnotSpacings.Add(new KnotSpace());
}
public int KnotCount
{
get { return _KnotCount; }
set
{
int requiredSpacings = 0;
_KnotCount = value;
// Always one more space than knots
requiredSpacings = _KnotCount + 1;
if (requiredSpacings < KnotSpacings.Count)
{
while (requiredSpacings < KnotSpacings.Count)
{
KnotSpacings.Add(new KnotSpace());
}
}
else if (requiredSpacings > KnotSpacings.Count)
{
while (requiredSpacings > KnotSpacings.Count)
{
KnotSpacings.Remove(KnotSpacings.Last());
}
}
this.OnPropertyChanged(this, "KnotCount");
}
}
public List<KnotSpace> KnotSpacings { get => _KnotSpacings; }
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(object sender, string PropertyName)
{
if (this.PropertyChanged == null) return;
this.PropertyChanged(sender, new PropertyChangedEventArgs(PropertyName));
}
}
public class KnotSpace
{
private double _Spacing = 10;
public double Spacing { get => _Spacing; set => _Spacing = value; }
}
The things in the list are displayed in the UI, and the properties of the things in the list are modified in the UI, but the UI doesn't directly add/remove things from the list except by changing the KnotCount property. Wrapping the KnotSpacings property in a BindingList doesn't result in the BindingList updating when KnotSpacings is updated by changing the KnotCount property.
EDIT OK, more clarification...
BindingList BL = new BindingList<KnotSpace>(MyRealString.KnotSpacings);
DataGridView1.AutoGenerateColumns = true;
DataGridView1.DataSource = BL;
NumericUpDown1.DataBindings.Add("Value", MyRealString, "KnotCount", false, DataSourceUpdateMode.OnPropertyChanged);
The BindingList has no more success tracking the changes to the underlying List property (KnotSpacings) than the Windows controls. So data binding the controls to the BindingList doesn't accomplish much. BindingList works great if UI adds/removes items from the BindingList because it does the same operations in the underlying List. But then I would need to replicate the add/remove action and logic of the library in my UI and that's a breaking change in waiting.
EDIT Major changes made to my original post attempting to: (1) Clarify the problem. (2) Distinguish it as not a duplicate question (although one of the several questions was a dup). (3) Acknowledge the helpful efforts of others that would be lost if I deleted the post.
First Off, there is a better way to pass a List<T> to a BindingList<T>. BindingList<T> has a constructor that accepts a List<T> which copies the List's elements into the BindingList, like so:
List<int> myList = new List<int>();
BindingList<int> myBindingList = new BindingList<int>(myList);
But that's not your question, really. To answer your question simply - Correct, List<T> is not a good choice for two-way binding in WinForms. As List<T> does not have any events notifying for elements added, you can really only guarantee a one-way binding - data entry may work, but things break down when trying to refresh on, say, items being added to the List.
That said, you mention that these libraries are modifying a List<T> that you have access to during the modifications. I would argue that a good Library would use the Interface pattern to use, modify, and pass collections. Although List<T> and BindingList<T> are very different classes, they both implement IList<T>, ICollection<T>, and IEnumerable<T>. So any function which accepts any of those interfaces as a parameter would accept either a List<T> or a BindingList<T> (for example: public void DoSomethingWithCollection(IEnumerable<int> collection) could accept List<int>, BindingList<int>, or any other collection that implements IEnumerable<int>). The Interface pattern is a well-known standard at this point in C#'s lifespan, and though nobody's first instinct would be to use a BindingList<T> over a List<T>, their first instinct should absolutely be to use an IEnumerable<T> (or IList<T> or ICollection<T>) over a List<T>.
Where possible, it would be better for binding to pass your List to the BindingList's constructor, then never use the List again - instead, use the Add and Remove methods of the BindingList to manage it's internal collection.
If you use the BindingList<T> constructor that accepts an instance of IList<T>, then that instance is used to back the BindingList<T>, and changes in the IList<T> are reflected in the BindingList.
That's not the end of the story, however. WinForms databinding is structured in such a way that, the further away you get from simple, single-property 2-way binding, the more things you have to cover yourself.
For example, the INotifyPropertyChanged interface is implemented by classes that are used as a data source to notify of a change in a child property (like your KnotCount property).
For more complex scenarios, one would not use BindingList<T>, but would derive a class from it and override one or more of the data binding mechanisms. Ditto for the BindingSource class.
There is a lot of boilerplate behind the data binding mechanism, but almost every portion of it is open to derivation in order to customize the behavior. It is sometimes useful to draw out an object graph of the classes and interfaces used in data binding (lots of reading the documentation involved) to give yourself a good mental overview of the whole process.
I have an ObservableCollection<SchedulesInMonth> object, the class is something like this:
public class SchedulesInMonth : INotifyPropertyChanged
{
private Dictionary<int, Dictionary<int, Schedule>> _schedules;
public Dictionary<int, Dictionary<int, Schedule>> Schedules
{
get => _schedules; set
{
_schedules = value;
OnPropertyChanged("Schedules");
}
}
// other property and method not related to this problem
// skipped for simplicity
}
the Schedules contain Dictionary of 28-31 schedules each day with a day in month as key, each schedules of a day is composed with 0-3 schedule(s), because the work shift is 3 each day.
If I do this:
ScheduleInMonths[row].Schedules[row].Clear();
The datagrid is not updated, even though the ObservableCollection is changed.
EDIT:
upon my discovery, if I do this:
var temp = ScheduleInMonths[row].Schedules;
temp[col].Clear();
ScheduleInMonths[row].Schedules = temp;
The datagrid that bounded to ObservableCollection is updated.
You don't actually have an ObservableCollection here. That's a specific type in WPF that has a lot of useful behaviors. You just have a property that conforms to the INotifyPropertyChanges interface (which I assume you've implemented, even though your same code doesn't show it).
The reason the second one works is because you are setting the dictionary again... you actually need to assign it to something to get it to trigger. You only get the update when you assign a new dictionary not when you change a value inside the existing dictionary. Unfortunately, there is no built in "ObservableDictionary" class, so you don't have many good options here. There are lots of helpful suggestions for how to do that in this question: General Observable Dictionary Class for DataBinding/WPF C#
I recently asked this question on here and got the answer. However I'm now trying to apply the same logic on a DataGridView which is bound to a BindingList< T > of Curriculum objects. The Curriculum class has a property of type Year. I'm trying to use a ComboBoxColumn to update the reference the curriculum object has of years.
The comboboxcolumn is bound to a BindingList< T > of years, it errors if I set either the display member or the value member so I left them null. Doing this the datagridview successfully loads and displays the data correctly (I overrode the ToString method on the year class). However, if I choose another year object from the combobox, as soon as it end edits it throws and exception saying it can't convert string to type year.
It looks like I need a TypeConverter to do it, but the problem is the combobox is displaying a descriptive value, which I can't guarantee will be unique to that year object - so I have no way of getting a year object from a given string.
Has anyone got any experience in situations like these, it must be a pretty common thing to want to do but google has failed me on this occasion.
Marlon
Same problem as here. Seems that object binding in a combobox column doesn't work properly and you have to specify a ValueMember.
For the particular project I am working on, I came to the conculsion that it was not worth implementing a custom type descriptor, so instead, I am using a fairly horrible hack:
In the entity that I am binding to, I have the following:
class TestEntity
{
public TestEntity BindingHack_ValueMember
{
get
{
return this;
}
}
public string BindingHack_DisplayMember
{
get
{
return this.ToString();
}
}
}
And the databinding for the column looks like this:
column.DataPropertyName = "Foo";
column.DisplayMember = "BindingHack_DisplayMember";
column.ValueMember = "BindingHack_ValueMember";
A little ugly, perhaps, but it works ...
I am trying to understand the difference between the following 2 examples.
First, this is how I currently assign Data to a control in my WinForm App.
lkuCounty.Properties.DataSource = Person.CountyList();
lkuCounty.Properties.PopulateColumns();
lkuCounty.Properties.DisplayMember = "CountyName";
lkuCounty.Properties.ValueMember = "CountyID";
lkuCounty.Properties.Columns[0].Visible = false;
lkuCounty.Properties.Columns[2].Visible = false;
lkuCounty.Properties.Columns[3].Visible = false;
This seems to work though I'll admit that if it is slightly off I probably lack the experience to tell just by looking at the code. Also of note, Person.CountyList() actually returns a DataTable :\
Now how all of the examples I find seem to say I should do this.
memberBindingSource.DataSource = Person.CountyList();
lkuCounty.Properties.DataSource = memberBindingSource;
lkuCounty.Properties.PopulateColumns();
lkuCounty.Properties.DisplayMember = "CountyName";
lkuCounty.Properties.ValueMember = "CountyID";
lkuCounty.Properties.Columns[0].Visible = false;
lkuCounty.Properties.Columns[2].Visible = false;
lkuCounty.Properties.Columns[3].Visible = false;
Is there a benefit to using the BindingSource? Is there a negative to doing it the OTHER WAY?
For context, this is a WinForm CRUD app in C# using SQL 2005.
For standard DataSets/DataTables, the BindingSource merely provides another layer of indirection between your controls and the actual data source.
However, most data-aware controls can only be bound to certain data sources (those implementing IList, IListSource, IBindingList or IBindingListView). This presents a problem if you need to use a custom object as a data source, because said object then needs to implement at least one of those interfaces.
So you can either implement the entire IList interface in your business object - or you could inherit your object from the List class and bind it up to a BindingSource, which you then bind to your Control(s).
The long and the short: unless you're certain your data sources will always be DataTables and the like, use a BindingSource. It does add a slight performance overhead, but it can make your life a lot easier.
There is also some very nice state-management functionality built into the BindingSource, which comes in very handy if your application is stateful. Instead of you writing custom state-handling code, just let the BindingSource handle things for you!
You can bind directly to any object, as in the first example. However, that object will need to implement many of the data binding interfaces for it to respond intelligently to the events fired by the control.
For example, bind a List of Person to a DataGridView. Now, click a column header to sort a column. It does not work because List does not implement the needed interface. Try the same thing with a DataTable. The column sorting magically works. That's because DataTable implements all the needed interfaces for Data Binding.
public class Person
{
public string Name { get; set; }
public int Age { get; set; }
}
private void Form1_Load(object sender, EventArgs e)
{
//does not sort...
dataGridView1.DataSource = new List<Person>
{
new Person{ Age=11, Name="Jimmy" },
new Person{ Age=12, Name="Suzie" }
};
}
You can write your own classes that implement the data binding interfaces. It's alot of work. Here's a great book on the subject:
Data Binding with Windows Forms 2.0: Programming Smart Client Data Applications with .NET