How to properly bind to a child object? - c#

I have an object Proposal which has a property called CurrentAgency of Agency which in turn has AgencyID, Name, etc...something like this:
Proposal
CurrentAgency
AgencyID
Name
Address
etc...
In my UI, I have a combobox that lists all available Agencies. I have bound it like this:
private BindingSource bndProposal = new BindingSource();
bndProposal.DataSource = typeof(Model.Proposal);
lkpAgency.DataBindings.Add("EditValue", bndProposal, "CurrentAgency.AgencyID");
lkpAgency.Properties.DataSource = FusionLookups.LookupAgencies;
lkpAgency.Properties.DisplayMember = "Name";
lkpAgency.Properties.ValueMember = "ID";
And this works well enough. If the user changes the agency, Proposal.CurrentAgency.AgencyID is automatically updated. However, the problem is that the rest of the properties of the CurrentAgency object are not updated.
What are some of the patterns that are used to handle this kind of situation while not gunking up the code with junk? Do I handing the Format event on the Binding object? Any ideas for clean implementation are welcome.

Have you tried the following?
private BindingSource bndProposal = new BindingSource();
bndProposal.DataSource = typeof(Model.Proposal);
lkpAgency.DataBindings.Add("EditValue", bndProposal, "CurrentAgency");
lkpAgency.Properties.DataSource = FusionLookups.LookupAgencies;
lkpAgency.Properties.DisplayMember = "Name";
lkpAgency.Properties.ValueMember = null;

Related

Combobox options not changing when datasource updated

So the data is loaded properly when I use this method in the Main Window Constructor:
public void Load_Dropdown(configuration con)
{
bindinglist = new BindingList<ListCollection.ListsList>(Get.ListCollection(con).List);
BindingSource bSource = new BindingSource {DataSource = bindinglist};
sharepointListSelect.DataSource = bSource;
sharepointListSelect.DisplayMember = "Title";
sharepointListSelect.ValueMember = "ID";
}
And this works fine, but when I try to call a similar method to update the options at a later time, nothing happens- as in the options in the drop down do not change.
The method I call to update it looks like this:
public bool Reload_Dropdown(configuration con)
{
bindinglist = new BindingList<ListCollection.ListsList>(Get.ListCollection(con).List);
BindingSource bSource = new BindingSource { DataSource = bindinglist };
sharepointListSelect.DataSource = bSource;
sharepointListSelect.DisplayMember = "Title";
sharepointListSelect.ValueMember = "ID";
//There is other logic here, but i left it out as it isn't relevant
return true;
}
Btw, the binding list object is initialized in the class as:
private BindingList<ListCollection.ListsList> bindinglist;
I know, right now I can reuse the first method- but I split up the declaration to debug why it is failing the update in the first place.
I am stupid- So the issue isn't with either method, but how I was calling the method. You cannot call a method from another form if it isn't static. Huge oversight on my part- a bit too accustomed to MVC I suppose.
This solution will work fine, so long as you call it from within the same method. I opted to just have a button that will refresh the dropdown.

Data Binding doesn't work when I assign a new object instance to the bound variable

I have an object that I have bound to a control on a form using C# WinForms (targetting .NET 4.5.2). I have implemented INotifyPropertyChanged, and when I modify a Property of this object, it updates on the Form's control as expected. However, when I change this object's instance to a new instance, it will no longer update the control, even if I try to modify a specific Property.
class User : INotifyPropertyChanged
{
string _name = "";
public string Name
{
get { return _name; }
set { _name = value; OnPropertyChanged("Name"); }
}
public event PropertyChangedEventHandler PropertyChanged;
protected void OnPropertyChanged(string property)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(property));
}
public User() { }
public User(string name)
{
Name = name;
}
public User(User otherUser)
{
Name = otherUser.Name;
}
}
and on the Form I have
User currentUser = new User("Example Name");
lblName.DataBindings.Add("Text", currentUser, "Name");
which updates properly. I can change name using currentUser.Name = "Blahblah"; and it works fine. When I try to call currentUser = new User("New Name");, it will no longer update no matter what I do.
It is my understanding that the specific instance of the object is what the controls are bound to. My primary goal is to not have to manually go through each Property of larger objects and manually change everything over every single time I want to change instances.
My question: is there a way to change instances of an object without removing the binding to the control?
To get the desired behavior, you should not bind directly to the concrete instance like you do currently:
lblName.DataBindings.Add("Text", currentUser, "Name");
Instead, you need some intermediary. The easiest is to use the BindingSource component for that purpose.
Add a BindingSource field to the form:
private BindingSource dataSource;
Then initialize it and bind the controls to it one time (usually in form Load event):
dataSource = new BindingSource { DataSource = typeof(User) };
lblName.DataBindings.Add("Text", dataSource, "Name");
// ...
Now anytime you want to bind to a User instance, you simply assign it to the DataSource property of the BindingSource:
initial:
dataSource.DataSource = new User("Example Name");
later:
dataSource.DataSource = new User("New Name");
Ivan showed you how to solve the problem. Here in this answer I'll try to show you what the problem is and why it works this way.
Short answer
Your label is bound to an object not the variable. The variable is just like a pointer to the object. So replacing the variable doesn't have any impact on the object which your label is using as data source. Just the variable points to the new object. But your label is using the previous object. It has its own pointer to the previous object.
Consider these facts to know what is happening:
User currentUser = new User("Example Name");
When you assign an object to a variable, in fact the variable points to the object.
lblName.DataBindings.Add("Text", currentUser, "Name");
You performed data-binding to the object. The control will be bound to the object not the variable.
currentUser.Name = "Blahblah";
You changed the value of Name property and since you implemented INotifyPropertyChanged the control will be notified of changes and will refresh its Text.
But the main statement which didn't work as you expected:
currentUser = new User("New Name");
Here you made the currentUser variable point to another object. It doesn't have anything to do with the previous object which it was pointing to. It just points to another object.
The Binding which you added to the label, still uses the previous object.
So it's normal to not have any notification, because the object which is in use by the label didn't changed.
How the BindingSource solved the problem?
The BindingSource raises ListChanged event when you assign a new object to its DataSource and it makes the BindingObject fetch new value from DataSource and refresh Text property. You can take a look at this post: How to make a binding source aware of changes in its data source?

Trying to Bind List<T> to CheckedListBox in WinForms c#

I am using WinForms C#
Is there any way to get following behavior:
bind List to CheckedListBox
When I add elements to list CheckedList box refereshes
When I change CheckedListBox the list changes
I tried to do the following:
Constructor code:
checkedlistBox1.DataSource = a;
checkedlistBox1.DisplayMember = "Name";
checkedlistBox1.ValueMember = "Name";
Field:
List<Binder> a = new List<Binder> { new Binder { Name = "A" } };
On button1 click:
private void butto1_Click(object sender, EventArgs e)
{
a.Add(new Binder{Name = "B"});
checkedListBox1.Invalidate();
checkedListBox1.Update();
}
But the view does not update .
Thank You.
Change this line:
List<Binder> a = new List<Binder> { new Binder { Name = "A" } };
to this:
BindingList<Binder> a = new BindingList<Binder> { new Binder { Name = "A" } };
It will just work without any other changes.
The key is that BindingList<T> implements IBindingList, which will notify the control when the list changes. This allows the CheckedListBox control to update its state. This is two-way data binding.
Also, you could change these two lines:
checkedListBox1.Invalidate();
checkedListBox1.Update();
to this (more readable and essentially does the same thing):
checkedListBox1.Refresh();
Two things you may wish to look at:
Use a BindingList
Add a BindableAttribute to your Name property
Does your List<Bender> need to be some kind of observable collection, like ObservableCollection<Bender> instead?
The proper way of binding a checked listbox is:
List<YourType> data = new List<YourType>();
checkedListBox1.DataSource = new BindingList<YourType>(data);
checkedListBox1.DisplayMember = nameof(YourType.Name);
checkedListBox1.ValueMember = nameof(YourType.ID);
Note to self.
The issue i have every time binding it is that the properties DataSource, DisplayMember and ValueMember are not suggested by intellisense and i get confused.

ToolStripComboBox.SelectedItem change does not propagate to binding source

My binding is set up as this:
_selectXAxisUnitViewModelBindingSource = new BindingSource();
_selectXAxisUnitViewModelBindingSource.DataSource = typeof(SelectXAxisUnitViewModel);
_selectedUnitComboBoxBindingSource = new BindingSource();
_selectedUnitComboBoxBindingSource.DataSource = _selectXAxisUnitViewModelBindingSource;
_selectedUnitComboBoxBindingSource.DataMember = "AvailableUnits";
_selectedUnitComboBox.ComboBox.DataSource = _selectedUnitComboBoxBindingSource;
_selectedUnitComboBox.ComboBox.DisplayMember = String.Empty;
_selectedUnitComboBox.ComboBox.ValueMember = String.Empty;
_selectedUnitComboBox.ComboBox.DataBindings.Add("SelectedItem",
_selectXAxisUnitViewModelBindingSource,
"SelectedUnit", true, DataSourceUpdateMode.OnPropertyChanged);
// this is a bug in the .Net framework: http://connect.microsoft.com/VisualStudio/feedback/details/473777/toolstripcombobox-nested-on-toolstripdropdownbutton-not-getting-bindingcontext
_selectedUnitComboBox.ComboBox.BindingContext = this.BindingContext;
The property "AvailableUnits" is a collection of strings and the "SelectedUnit" is a string-property. Now the dropdown list is populated as expected, but when I select and item in the list, the change is not propagated to the binding source. Any idea what I am doing wrong?
Update:
I Created a little test project and this problem occurs when I add a ToolStripComboBox as a subitem of another ToolStripItem. If I add the ToolStripItem directly to the MenuStrip everything works fine. The BindingContext is not assigned to the ToolStripComboBox when added as a sub item (see my code-comment) and my fix doesn't seem to do whats necessary to get this to work.
Can you change
_selectXAxisUnitViewModelBindingSource.DataSource = typeof(SelectXAxisUnitViewModel);
To
_selectXAxisUnitViewModelBindingSource.DataSource = new SelectXAxisUnitViewModel();

unable to edit DataGridView populated with results of LINQ query

When i use the results of a linq-to-xml query to populate a datagridview, i cannot edit the datagridview. i've tried setting the datagridview's readonly property to false and that doesn't help. I also added an event handler for cellBeginEdit and put a breakpoint there, but it doesn't get hit. Any idea what i'm doing wrong, or if this isn't possible?
public class MergeEntry
{
public string author { get; set; }
public string message { get; set; }
}
...
var query = from entry in xmlDoc.Descendants("entry")
select new MergeEntry
{
author = entry.Element("author").Value,
message = entry.Element("msg").Value,
}
var queryAsList = query.ToList();
myBindingSource.DataSource = queryAsList;
myDataGridView.DataSource = myBindingSource;
Yes, it is possible to bind to a list created from Linq-To-Xml. I tried to reproduce your problem. I did the following:
Created an empty WindForm project (VS 2008 and .Net 3.5)
Added a DataGridView to the Form.
Wired the CellBeginEdit and CellEndEdit.
Placed the following code in the Form's constructor
string testXML =
#"<p><entry>
<author>TestAuthor1</author>
<msg>TestMsg1</msg>
</entry></p>
";
XElement xmlDoc = XElement.Parse(testXML);
var query = from entry in xmlDoc.Descendants("entry")
select new MergeEntry
{
author = entry.Element("author").Value,
message = entry.Element("msg").Value,
}; //You were missing the ";" in your post, I am assuming that was a typo.
//I first binded to a List, that worked fine. I then changed it to use a BindingList
//to support two-way binding.
var queryAsList = new BindingList<MergeEntry>(query.ToList());
bindingSource1.DataSource = queryAsList;
dataGridView1.DataSource = bindingSource1;
When running the WinForm app, an editable grid was displayed and the CellBeginEdit and CellEndEdit events were fired when they should have been. Hopefully trying to reproduce using the above steps will help you locate the issue you are facing.
Since you're using ToList, your DataSource is actually a List<MergeEntry>, so the fact that it comes from a Linq query doesn't change anything. I suspect the ReadOnly property of the columns (not the DGV) is set to true... I see no other reason why the grid wouldn't be editable
This solution may not efficient but it works for me,
I moved all data (which is from LINQ) into a new collection and passed that new collection as datasource to gridview.
Now gridview allow us to edit cells.

Categories