ObservableCollection contain Dictionary<int, Dictionary<int, <Object>> not trigger on change - c#

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#

Related

Binding Dictionary to ComboBox in C# .net winforms

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

BindingList vs List - WinForms 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.

WPF<-> entity binding

I have problem with updating data from my datasource(database through entity fw) to wpf-windows. I generate files using entity framework, so i'm accesing data from datebase this way:
public partial class sampleWindow : Window
{
myEntity en = new myEntity();
public sampleWindow()
{
InitializeComponent();
Bind();
}
private void Bind()
{
var list = from o in en.table select o;
someDatagrid.ItemsSource = list.ToList();
}
This method, firstly, was adequate for my program, i was refreshing 'Bind' method after i was doing some operations on database, so the data in my datagrids or combos was fresh. The problem occurs when i was changing database in diffrent wpf-windows. I have read that I should implement observable interface and use load instead of itemsSource. I tried to do it but i'm begginer and my attempts faild miserably. Could someone tell me step by step, what i should do?
You need a Singleton to manage your data, combined with using an ObservableCollection to expose the data. When the collection is changed by any view, it will notify any subscribers to the observation and they will automatically update.
See: Example of bindable list in XAML app (first part)
Example of Singleton
You would want to use a singleton for the instance of your entity as The Sharp Ninja mentioned. His article in the link he posted does a good job of explaining. You will want to use an observable collection to bind your ItemSource to. When an item is added or removed from an Observable collection the UI is automatically notified. The problem you are going to have is that there is not a .ToObservableCollection()
extension method build in to .net so you will have to implement your own.
I use this extension method
public static ObservableCollection<T> ToObservableCollection<T>(
this IEnumerable<T> enumeration)
{
return new ObservableCollection<T>(enumeration);
}
So now your bind method can set your ItemSource to the observable collection
private void Bind()
{
var list = from o in en.table select o;
someDatagrid.ItemsSource = list.ToObservableCollection();
}
There are so many and better ways (MVVM pattern) to accomplish this than your approach. To keep it simple it can be accomplished this way:
//Gets the Load() extention method available for DbSet
using System.Data.Entity;
private void Bind()
{
myEntity.table.Load();
/*Local returns an obvervable collection convenient for data binding.
This is a synchronized local view of your data. It means any item added , deleted and updated will be reflected in your controls.*/
var obsColl = myEntity.table.Local;
someDatagrid.ItemsSource = obsColl;
}

ReactiveUI SelectMany form of CreateDerivedCollection

Using ReactiveUI 6.5, I'm trying achieve something like a SelectMany LINQ expression with the CreateDerivedCollection feature of RxUI. The objects in my source collection (type 'A') have an IsSelected property, as well as another collection property where the items are of type 'B'. I want to end up with an IReactiveDerivedList<B> which is a flattened list of all of the B objects from the A's that are selected. Hopefully that makes sense.
To make it a little more concrete, let's use an example of an app for browsing log files. Say we have a LogFileViewModel type and our main screen's view model has a list of these. Each instance in the list represents a log file on the system, and we present a list of these that the user can select. It's a multi-select list so they can select more than one at a time.
The LogFileViewModel then has an IsSelected boolean property, which will be set to true/false as the user selects or deselects the corresponding item in the list. And it has a property which is a List<LogEntry>. Each LogEntry object of course represents one entry in the corresponding log file.
What I want to do then is have a reactive list in the main view model which is a list of all of the LogEntry objects for all of the currently selected LogFileViewModel objects. The ReactiveList of selected log files is easy, but I'm stuck on the second part. Here's what the main view model would basically look like:
public class MainViewModel
{
public MainViewModel()
{
//This gets initialized with the log files somehow, doesn't matter
LogFiles = new List<LogFileViewModel(...);
SelectedLogFiles = LogFiles.CreateDerivedCollection(l => l, l => l.IsSelected);
SelectedLogFileEntries = ? //How to create this one?
}
public List<LogFileViewModel> LogFiles { get; private set; }
public IReactiveDerivedList<LogFileViewModel> SelectedLogFiles { get; private set; }
public IReactiveDerivedList<LogEntry> SelectedLogFileEntries { get; private set; }
}
Is there a known way to do this that I'm just not seeing? If not, any clever ideas to achieve this behavior? :-)
Edit :
Looks like I missed this question in my initial search. Paul provided the "clever" solution to this problem about 2 years ago. So my question now ... is this still the best way to achieve this behavior?

updating datagrid with BindingList

I've been looking at the difference between a BindingList and an observablecollection and List. From what I've read, it seems like the BindingList is the only collection type that will notify if an object in it has one of its properties changed. I cannot get this to work.
I have a property on a ViewModel called Matches, which returns a BindingList created out of a list of CarMatch objects in another class. (Cars m_Cars = new Cars();) My DataGrid on the View is bound to this Matches property in the VM.
public BindingList<CarMatch> Matches
{
get
{
Return new BindingList<CarMatch>(m_Cars.Matches);
}
}
Now, in the code I change one of the CarMatch object's properties, say.. automaticTrans = true from false. Matches[0].automaticTrans = true. I want to see that change in the DataGrid. Without implementing INotifyPropertyChanged inside of the CarMatch class, is there a way to update the datagrid from the viewmodel? Using INotifyPropertyChanged on Matches does not seem to do it. There is something about this I just don't understand, and could use an example to look at.
CarMatch (not Matches) has to implement INotifyPropertyChanged. But consider using ObservableCollection unless you really need some of the additional scenarios offered by BindingList: with ObservableCollection, INotifyPropertyChanged comes for free. And, more importantly, BindingList doesn't scale well.
try
dataGrid.Items.Refresh();
but keep in mind that is a expensive call if you have lots of data and you call it several times in a short period of time.

Categories