ComboBox not updating when object added to bound list - c#

I have an object that represents a client, and that object has a list of the clients branches:
private List<Branch> _branches;
[System.Xml.Serialization.XmlArray("Branches"), System.Xml.Serialization.XmlArrayItem(typeof(Branch))]
public List<Branch> Branches
{
get { return _branches; }
set
{
_branches = value;
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs("Branches"));
}
}
}
In one form (WinForms) I have a ComboBox that I've bound to that list:
// creating a binding list for the branches
var bindingList = new BindingList<Branch>(Client.Branches);
// bind list to combo box
cmbBranches.DataSource = bindingList;
cmbBranches.DisplayMember = "Name";
cmbBranches.ValueMember = "Name";
In another function, I create a new Branch object and add it to the existing list: Client.Branches.Add(newBranch). I would expect this to update the ComboBox but it doesn't. Why not, and how do I make it update? (Edit: I'd also like this to update when removing an object from the list. The reason it doesn't work is, I assume, directly related to why the box isn't updating when Add is called.)
In doing research, I found this SO answer, which seems to imply that it will work. I feel like I'm missing something simple ...
difference between ObservableCollection and BindingList
Edit: Some further information about what I've tried and some additional goals.
I cannot use ObservableCollection<T> instead of List<T> as I need to use Exists in the code. Which the former doesn't have.
I need to update the original list when the new object is added, in addition to updating the drop down box.
To summarize my comments below, I attempted adding this:
var bindingList = (BindingList<Branch>) cmbBranches.DataSource;
bindingList.Add(frmAddBranch.NewBranch);
But that results in the object being added to the ComboBox twice. Somehow by calling bindingList.Add it's "resetting" the data source and doubling up. I cannot find any function that "refreshes" the data display once it's bound. Control.ResetBindings() did not work.

Well, it doesn't work that way. The inner List<T> has no change notification mechanism, so adding directly to inner List<T> will not generate any change notification that would eventually reach the combo box. Most convenient way to do what you want is adding the item through the BindingList<T> instead.

I believe you have to add the items directly to the BindingList (but not to the backing Branches list - the BindingList should take care of this for you).

Related

Create a copy of an ObservableCollection that doesn't update

I am building a control in xamarin forms that binds to a list of objects. To get this binding to work I need to use observable collections (otherwise propertychanged methods don't fire).
I noticed a really frustrating interaction as a result of needing to use OC's as opposed to lists. Whenever the binded OC updates, the values in my controls are automatically updated, even if they are just references of the OC, Here is how i am copying the OC.
//Internal list of events
private List<EventItem> _events;
void OnEventsChanged(ObservableCollection<EventItem> eventsCollection)
{
//Error handle
List<EventItem> events = eventsCollection.ToList();
//Do something
_events = events;
}
The problem comes when the OC updates, I want to check for new/deleted AND altered objects. The issue is that when the OC updates, it is updating the internal list (_events) aswell. This means when I go to do comparisons between the old & new values, they are the same.
Honestly I don't really understand how c# handles copying references of objects around, I had a similar issue a while back with DateTime.Now being calculated as opposed to copying the value of the already initialised object.
var time = DateTime.Now;
await Task.Delay(1000);
var time2 = time; //This is 1 second later than time, not the value of time (which is what I wanted)
I have used Objective-C in the past and that has the concept of MutableCopy where you can assign a new list from an existing one, they have the same values but aren't linked.
How can I do this in C# so that my controls internal list is only updated by me and not the OC?
Thanks
That's perfectly normal. If I have enough time, I'll try to explain it to you.
In a nutshell, the observableList (or a List) is a list of reference to the objects and not a list of objects. The thing is that the objects are not copied inside a list but the list contains a reference to the different objects. That means that if you do something like ToList(), you get another list of references to the exact same objects.
Now to solve your problem. Just create a new list with new objects with something like
var newList = oldList.Select(x => new Model(x)).ToList();
And of course the Model class has a constructor that accept a Model as a parameter and copy the properties.
When you write _events = events;, you create not a new object, but a reference for the same object. https://learn.microsoft.com/en-us/dotnet/csharp/programming-guide/classes-and-structs/index .
You should to clone (create a copy of object itself) as it mentioned in comment by #Matt.

Adding a record to EF 4.1 does not reflect in listbox

I have a data bound list box that was built using the EF 4.1 model first. So all my classes where built for me. Because I have three controls that reflect the table data that traces back from the foreign key. this.lstBox2.ItemSource = entityContext.TableObject2.ToList() will return every record. Not the records that the M-D are showing limited by the foreign key constraint.
TableObject2 class2 = new TableObject2();
class2.value1 = 0;
class2.value2 = "new location";
using (TKOEntities entityContext = new TKOEntities())
{
entityContext.TableObject2.AddObject(class2);
entityContext.SaveChanges();
this.lstBox2.ItemsSource = null;
}
SaveChanges does update the data into the database. But the control is not refreshed (this.lstBox2.Refresh() doesn't work). If I try to set the value to the control. I also get the us ItemControl.ItemSource error. How do I assign the control the updated values saved to the entitycontext?
The model changes are not automatically propagated to the view-model or your view (as I can see you don't have a MVVM view-model?)
You'd normally need to Bind and do PropertyChanged.
This is writing from a device and very rough and fast (I may have mistyped something etc.)...
<ListBox ItemsSource={Binding YourCollectionProperty} >
in your 'view-model' (or if that's your 'control', which I don't recommend, then do something like {Binding ElementName=_mywin, Path=YourCollectionProperty}) define the property as...
public ObservableCollection<POCOItem> YourCollectionProperty
{
get
{
return _collection ?? (_collection = WrapModel());
}
set
{}}
Implement IPropertyChanged interface
When your model is updated...
_collection = null;
OnPropertyChanged("YourCollectionProperty");
ObservableCollection is useless here (an array/list will do just the same - keeping model collection in sync isn't easy.
So, if you need to be closely tied to your model - you could make your model navigation property ICollection to be ObservableCollection (though there're pros and cons but not to get into that).
e.g. see this one Do I need to implement INotifyPropertyChanged when using EF Code-First?
In that case - when just adding new items - that should go automatically to you ListBox.
If you refresh the collection - then do the above (set PropertyChanged for the collection property)
Item Properties are not automatically updated - unless you implement IPropertyChanged on your model as well.
Instead of:
this.lstBox2.ItemsSource = null;
Try fetching the data from entityContext after you save the changes to rebuild your list:
this.lstBox2.ItemsSource = entityContext.TableObject2.ToList();
There is a better approach you could take I'm sure, but to quickly get your databack out of the db and into the list, this should work.
Also a quick code bit, you can use object initializer:
TableObject2 class2 = new TableObject2 { value1 = 0, value2 = "new location" };

How to force DataGridView to re-populate its rows using IQueryable<TEntity>

BlaEntities TestContext = new BlaEntities();
IQueryable<TestEntity> Entities = TestContext.TestEntity;
TestDataGridView.DataSource = Entities;
When I assign Entities to TestDataGridView's DataSource directly; I don't have to do anything to reflect my changes to the grid.
TestEntity entity = Entities.First();
entity.Title = "What up!?";
This is more than enough to see the change in the TestDataGridView. One exception I encountered was that if I add another row to the TestContext using TestContext.AddToTestEntity(...) , it doesn't show up in the grid (contrary to deleting it) but I got it working using the BindingSource's Add method.
BindingSource source = new BindingSource{DataSource = Entities};
TestDataGridView.DataSource = source;
source.Add(CreateNewTestEntity());
Now the only obstacle left in my way is this:
If I use filtering - like TestContext.Where(t => t.Active) - use it as DataSource to my grid, then change the first record's Active property to false, how do I refresh/reload the grid to reflect this without creating another instance of BlaEntities?
IQueryable<TestEntity> FilteredEntities =
TestContext.TestEntity.Where(t => t.Active);
TestDataGridView.DataSource = FilteredEntities;
TestEntity temp = FilteredEntities.First();
temp.Active = false;
I see it is not active anymore in grid but since the grid should show only the active records, how can I remove it from the grid without removing it from the source?
When I iterate over FilteredEntities, I can see that the temp isn't there anymore but I still can see and edit it in the grid. So what I need is something forces grid to iterate its DataSource (which is FilteredEntities) as well and populate itself again
I tried calling TestContext's Refresh method and BindingSource's reset methods.
I tried changing TestDataGrid.DataSource to null, then changing it back to FilteredEntities hoping to re-populate the rows, didn't work either.
It works if I save my changes using TestContext.SaveChanges() and use another instance of BlaEntities like TestDataGridView.DataSource = new BlaEntities().TestEntity.Where(t => t.Active) but I need to use my current instance.
So the question is, how can I make the TestGridView to reload its contents using FilteredEntities.
Any advice would be greatly appreciated. Thank you.
since you already have a BindingSource have a look at the ResetBindings method
//edit:
as from the comments below:
i would approach this problem with a factory for "Entities" ... if that factory would hold a ref to the last created IQueryable, it could implement the interface IQueryable itself, by forwarding all interface methods to that created object ... so it could act as a wrapper for your datasource that can replace that datasource by recreating it based on the predicate function and the actual state of all objects

Use deserialized XML data in TabController + DataGrid

After this: Acess to a DataGrid through C# code and manipulate data inside the DataGrid I decided that i should deserialize my XML data and use it that way because i need basic CRUD manipulation in my application.
I already have my xml data class (using XSD tool, you can find the class here -> http://pastebin.com/6GWFAem6) and have my data deserialized, problem is:
I need a TabControl with as many tabs as Semestre in my xml, each tab will have GPASemestre.Nome header.
Inside each tab i need a DataGrid with Cadeiras of that specific Semestre.
I need to be able to CRUD data in the DataGrid and the tabs.
Questions:
To solve all of this what do you think is best? Creating everything (tabs + datagrid) and make the necessary binds (which i don't really know what they will be) / populate the DataGrid somehow, in C# only? Or there is a way to simplify code in C# using XAML?
Cadeiras are stored in arrays so, each time i add a new one, i need to create a new array (or create a new array with more spaces and manage it), i already saw some questions here where ppl used List's but where having troubles with it, is it possible to use a list or not? If so, what do i have to change in the XSD auto generated class?
Thanks in advance!
I would suggest the use of data-binding and data-templating (read those if you have not yet done so) for as much as possible, for that to work well the auto-generated classes need to be adjusted to support it.
The first step is implementing INotifyPropertyChanged in all the properties which are not collections so that if the property is changed the UI will update automatically. Only use arrays for deserialisation at best, after that copy the elements to a property which is of type ObservableCollection<T>, or any other collection which implements INotifyCollectionChanged so that the grids update when a new item is added to the collection and never touch the arrays again.
You could also make the Array property "virtual" (has no backing field, just manipulates on get & set), e.g.:
//The property & field used for binding and editing
private readonly ObservableCollection<GPASemestre> _ObservableSemestre = new ObservableCollection<GPASemestre>();
public ObservableCollection<GPASemestre> ObservableSemestre { get { return _ObservableSemestre; } }
//The property used for serialisation/deserialisation
public GPASemestre[] Semestre
{
get
{
return ObservableSemestre.ToArray();
}
set
{
ObservableSemestre.Clear();
foreach (var item in value)
{
ObservableSemestre.Add(item);
}
}
}

Is there a collection that doesn't subscribe to INotifyPropertyChanged?

I have a class that uses INotifyPropertyChanged and have a List bound to a grid. Every time that object changes it updates the values in the grid. However for historical reasons I want to add that object to a collection/list and bind to a history grid to show all the changes. However every list I have tried seems to subscribe to INotifyPropertyChanged so I only ever get the same amount of items in my history grid.
Is there a list/collection that doesn't subscribe to INotifyPropertyChanged?
Thanks
If your List is a list of references to a class, you will need to do a "deep clone" of the entire list in order to maintain a historical copy. Otherwise, a copy of the list's contents will still be references to the "live" objects which are getting changed. Doing this will require code such as:
// This requires a way to "Clone" your object...
List<YourClass> listCopy = originalList.Select(item => item.Clone()).ToList();
If the list contains value types (struct), you can just create a new List<YourType> and copy the original elements over.
// If your type is a value type, you can just copy the list directly...
var listCopy = originalList.ToList();
Also - this has nothing to do with INotifyPropertyChanged. Lists, themselves, do nothing with the PropertyChanged event.
Consider using Mode=OneTime in your binding. http://msdn.microsoft.com/en-us/magazine/cc163299.aspx#S3

Categories

Resources