Changing items in bound ComboBox

G

Guest

I have a combo box (dropdown) that is bound to an array of objects with the
member specified by a property name. There is one item on the drop-down that
when selected I want to be able to change the text displayed. I cast the
selected item to my object and set the value that I want to change. The text
shows in the debugger just fine but both the selected text and the drop-down
text is not correct and the properties are never queried to get the new text.
It is like the drop-down is ignoring the fact that the field has been
changed. How do I make sure that the drop-down first, knows to requery for
the changed item and second, displays the correct updated text?

Thank you.

Kevin
 
D

Dave Sexton

Hi Kevin,

Bind your ComboBox to a BindingSource object and set the DataSource of the BindingSource object to your array.

Then, implement a property-changed event for your property. For example, if your property is called, "Name":

/* ISSUE
I just tested this theory to make sure it worked but I found some strange behavior with the BindingSource object.
The following code works as long as the position of the CurrencyManager is changed at least once. I don't know if this is a bug or
by design.

List<Person> people = new List<Person>();
people.Add(new Person("Kevin"));
people.Add(new Person("Dave"));

lstPeople.DisplayMember = "Name";
lstPeople.DataSource = new BindingSource(people, null);

After writing the above code you must also code:

lstPeople.SelectedIndex = 1;
lstPeople.SelectedIndex = 0;

These two lines are required to force the BindingSource to bind to your [Prop]Changed event. I'm not sure what to do if you only
have one element in the array.

Does anyone that can explain this behavior, preferably from Microsoft, want to comment on this?
*/

public class Person {

private string name;
public string Name
{
get { return name; }
set
{
if (name == value)
return;

name = value;
OnNameChanged(EventArgs.Empty);
}
}

public event EventHandler NameChanged;

private void OnNameChanged(EventArgs e)
{
if (NameChanged != null)
NameChanged(this, e);
}
}
 
G

Guest

Thank you for the suggestion. I am way ahead in understanding now. Is what I
don't understand is the "NameChanged" event. It seems from the code that you
fire the event when a property has changed this in turn fires the NameChanged
even. Where and how is the NameChanged event tied to the data bound control?

Thank you for your continued assistance.

Kevin

Dave Sexton said:
Hi Kevin,

Bind your ComboBox to a BindingSource object and set the DataSource of the BindingSource object to your array.

Then, implement a property-changed event for your property. For example, if your property is called, "Name":

/* ISSUE
I just tested this theory to make sure it worked but I found some strange behavior with the BindingSource object.
The following code works as long as the position of the CurrencyManager is changed at least once. I don't know if this is a bug or
by design.

List<Person> people = new List<Person>();
people.Add(new Person("Kevin"));
people.Add(new Person("Dave"));

lstPeople.DisplayMember = "Name";
lstPeople.DataSource = new BindingSource(people, null);

After writing the above code you must also code:

lstPeople.SelectedIndex = 1;
lstPeople.SelectedIndex = 0;

These two lines are required to force the BindingSource to bind to your [Prop]Changed event. I'm not sure what to do if you only
have one element in the array.

Does anyone that can explain this behavior, preferably from Microsoft, want to comment on this?
*/

public class Person {

private string name;
public string Name
{
get { return name; }
set
{
if (name == value)
return;

name = value;
OnNameChanged(EventArgs.Empty);
}
}

public event EventHandler NameChanged;

private void OnNameChanged(EventArgs e)
{
if (NameChanged != null)
NameChanged(this, e);
}
}

--
Dave Sexton

Kevin Burton said:
I have a combo box (dropdown) that is bound to an array of objects with the
member specified by a property name. There is one item on the drop-down that
when selected I want to be able to change the text displayed. I cast the
selected item to my object and set the value that I want to change. The text
shows in the debugger just fine but both the selected text and the drop-down
text is not correct and the properties are never queried to get the new text.
It is like the drop-down is ignoring the fact that the field has been
changed. How do I make sure that the drop-down first, knows to requery for
the changed item and second, displays the correct updated text?

Thank you.

Kevin
 
D

Dave Sexton

Hi Kevin,

The BindingSource class attaches a handler to your [Prop]Changed event, if one exists. That's why it's important that you use a
BindingSource instead of assigning your array directly to the DataSource property of the ComboBox. The BindingSource basically
forwards the event to the ComboBox so that it will refresh its display.

Because of the issue I found I'm not so sure that my example is your best option. Instead, you can create a custom BindingSource or
better yet create a strong-Typed collection of People, to use my example, and implement IBindingList.

Another way is to use a strong-Typed DataSet.

--
Dave Sexton

Kevin Burton said:
Thank you for the suggestion. I am way ahead in understanding now. Is what I
don't understand is the "NameChanged" event. It seems from the code that you
fire the event when a property has changed this in turn fires the NameChanged
even. Where and how is the NameChanged event tied to the data bound control?

Thank you for your continued assistance.

Kevin

Dave Sexton said:
Hi Kevin,

Bind your ComboBox to a BindingSource object and set the DataSource of the BindingSource object to your array.

Then, implement a property-changed event for your property. For example, if your property is called, "Name":

/* ISSUE
I just tested this theory to make sure it worked but I found some strange behavior with the BindingSource object.
The following code works as long as the position of the CurrencyManager is changed at least once. I don't know if this is a bug
or
by design.

List<Person> people = new List<Person>();
people.Add(new Person("Kevin"));
people.Add(new Person("Dave"));

lstPeople.DisplayMember = "Name";
lstPeople.DataSource = new BindingSource(people, null);

After writing the above code you must also code:

lstPeople.SelectedIndex = 1;
lstPeople.SelectedIndex = 0;

These two lines are required to force the BindingSource to bind to your [Prop]Changed event. I'm not sure what to do if you only
have one element in the array.

Does anyone that can explain this behavior, preferably from Microsoft, want to comment on this?
*/

public class Person {

private string name;
public string Name
{
get { return name; }
set
{
if (name == value)
return;

name = value;
OnNameChanged(EventArgs.Empty);
}
}

public event EventHandler NameChanged;

private void OnNameChanged(EventArgs e)
{
if (NameChanged != null)
NameChanged(this, e);
}
}

--
Dave Sexton

Kevin Burton said:
I have a combo box (dropdown) that is bound to an array of objects with the
member specified by a property name. There is one item on the drop-down that
when selected I want to be able to change the text displayed. I cast the
selected item to my object and set the value that I want to change. The text
shows in the debugger just fine but both the selected text and the drop-down
text is not correct and the properties are never queried to get the new text.
It is like the drop-down is ignoring the fact that the field has been
changed. How do I make sure that the drop-down first, knows to requery for
the changed item and second, displays the correct updated text?

Thank you.

Kevin
 
O

Otis Mukinfus

I have a combo box (dropdown) that is bound to an array of objects with the
member specified by a property name. There is one item on the drop-down that
when selected I want to be able to change the text displayed. I cast the
selected item to my object and set the value that I want to change. The text
shows in the debugger just fine but both the selected text and the drop-down
text is not correct and the properties are never queried to get the new text.
It is like the drop-down is ignoring the fact that the field has been
changed. How do I make sure that the drop-down first, knows to requery for
the changed item and second, displays the correct updated text?

Thank you.

Kevin

Kevin,

You will need to use the IEditableObject and INotifyPropertyChanged interfaces
on your object.

After doing that save your objects in a BindingList<T> object and use the
BindingList collection as the DataSource of your BindingSource object that Dave
suggested you use.

Her is a sample of an object that uses this methodology and a BindingList<T>
objects that stores a list of these objects.

///////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;

namespace AddBookLib
{
[Serializable]
public class Address : IEditableObject, INotifyPropertyChanged
{

#region private fields

/// <summary>
/// struct that holds current or backup data
/// </summary>
private struct AddressData
{
internal int addressID;
internal string address1;
internal string address2;
internal string city;
internal string state;
internal string postalCode;
internal string country;
}

private AddressData _backup;
private AddressData _current;
private bool _editing;

#endregion private fields

#region ctors

/// <summary>
/// Creates a new instance of an Address object
/// </summary>
public Address()
{
_current = new AddressData();
_backup = new AddressData();
}

/// <summary>
/// Init ctor
/// </summary>
/// <param name="addressID">Numeric ID of Address object</param>
/// <param name="address1">Firts address line</param>
/// <param name="address2">Second address line</param>
/// <param name="city">City where address is located</param>
/// <param name="state">State where address is located</param>
/// <param name="postalCode">Postal Code where address is located</param>
/// <param name="country">Country where address is located</param>
public Address(int addressID, string address1, string address2,
string city, string state, string postalCode, string country)
{
_current.addressID = addressID;
_current.address1 = address1;
_current.address2 = address2;
_current.city = city;
_current.state = state;
_current.postalCode = postalCode;
_current.country = country;
}

#endregion ctors

#region EventHandlers

/// <summary>
/// Fired when a property changes
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;

#endregion EventHandlers

#region properties

/// <summary>
/// Numeric Identifier for address
/// </summary>
public int AddressID
{
get
{
return _current.addressID;
}
set
{
_current.addressID = value;
_firePropertyChangedNotification("AddressID");
}
}

/// <summary>
/// First address line
/// </summary>
public string Address1
{
get
{
return _current.address1;
}
set
{
_current.address1 = value;
_firePropertyChangedNotification("Address1");
}
}

/// <summary>
/// Second address line
/// </summary>
public string Address2
{
get
{
return _current.address2;
}
set
{
_current.address2 = value;
_firePropertyChangedNotification("Address2");
}
}

/// <summary>
/// City where the address is located
/// </summary>
public string City
{
get
{
return _current.city;
}
set
{
_current.city = value;
_firePropertyChangedNotification("City");
}
}

/// <summary>
/// State where address is located
/// </summary>
public string State
{
get
{
return _current.state;
}
set
{
_current.state = value;
_firePropertyChangedNotification("State");
}
}

/// <summary>
/// Postal code where address is located
/// </summary>
public string PostalCode
{
get
{
return _current.postalCode;
}
set
{
_current.postalCode = value;
_firePropertyChangedNotification("PostalCode");
}
}

/// <summary>
/// Country where the address is located
/// </summary>
public string Country
{
get
{
return _current.country;
}
set
{
_current.country = value;
_firePropertyChangedNotification("Country");
}
}

#endregion properties

#region private methods

/// <summary>
/// Fires the PropertyChanged event
/// </summary>
private void _firePropertyChangedNotification(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}

#endregion private methods

#region public methods

#region IEditableObject Members

/// <summary>
/// Places the object in edit mode and backs up original values
/// </summary>
public void BeginEdit()
{
if (!_editing)
{
_backup = _current;
_editing = true;
}
}

/// <summary>
/// Cancels the edit mode and restores original values
/// </summary>
public void CancelEdit()
{
if (_editing)
{
_current = _backup;
_editing = false;
}
}

/// <summary>
/// Commits editing changes
/// </summary>
public void EndEdit()
{
if (_editing)
{
_backup = new AddressData();
_editing = false;
}
}

#endregion

#endregion public methods
}
}

////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;

namespace AddBookLib
{
[Serializable]
public class AddressList : BindingList<Address>
{
/// <summary>
/// Default ctor
/// </summary>
public AddressList() : base()
{

}

/// <summary>
/// Init ctor
/// </summary>
public AddressList(List<Address> addresses) : base(addresses)
{

}
}
}

////////////////////////////////////////////////////////////////////////////

Sorry it's so long ;o)


Good luck with your project,

Otis Mukinfus
http://www.arltex.com
http://www.tomchilders.com
 
D

Dave Sexton

Hi Otis,

Nice. I tested your solution and it worked fine for me, however Kevin should be aware that IEditableObject and the "backup" data is
not required for the property change notifications.

To answer the op, the only thing required is the INotifyPropertyChanged implementation.

It's still unclear to me, however, if the behavior related to the BindingSource object and the [Prop]Changed syntax should be
expected, as explained in a previous post. Will anyone comment on this?

--
Dave Sexton

Otis Mukinfus said:
I have a combo box (dropdown) that is bound to an array of objects with the
member specified by a property name. There is one item on the drop-down that
when selected I want to be able to change the text displayed. I cast the
selected item to my object and set the value that I want to change. The text
shows in the debugger just fine but both the selected text and the drop-down
text is not correct and the properties are never queried to get the new text.
It is like the drop-down is ignoring the fact that the field has been
changed. How do I make sure that the drop-down first, knows to requery for
the changed item and second, displays the correct updated text?

Thank you.

Kevin

Kevin,

You will need to use the IEditableObject and INotifyPropertyChanged interfaces
on your object.

After doing that save your objects in a BindingList<T> object and use the
BindingList collection as the DataSource of your BindingSource object that Dave
suggested you use.

Her is a sample of an object that uses this methodology and a BindingList<T>
objects that stores a list of these objects.

///////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;

namespace AddBookLib
{
[Serializable]
public class Address : IEditableObject, INotifyPropertyChanged
{

#region private fields

/// <summary>
/// struct that holds current or backup data
/// </summary>
private struct AddressData
{
internal int addressID;
internal string address1;
internal string address2;
internal string city;
internal string state;
internal string postalCode;
internal string country;
}

private AddressData _backup;
private AddressData _current;
private bool _editing;

#endregion private fields

#region ctors

/// <summary>
/// Creates a new instance of an Address object
/// </summary>
public Address()
{
_current = new AddressData();
_backup = new AddressData();
}

/// <summary>
/// Init ctor
/// </summary>
/// <param name="addressID">Numeric ID of Address object</param>
/// <param name="address1">Firts address line</param>
/// <param name="address2">Second address line</param>
/// <param name="city">City where address is located</param>
/// <param name="state">State where address is located</param>
/// <param name="postalCode">Postal Code where address is located</param>
/// <param name="country">Country where address is located</param>
public Address(int addressID, string address1, string address2,
string city, string state, string postalCode, string country)
{
_current.addressID = addressID;
_current.address1 = address1;
_current.address2 = address2;
_current.city = city;
_current.state = state;
_current.postalCode = postalCode;
_current.country = country;
}

#endregion ctors

#region EventHandlers

/// <summary>
/// Fired when a property changes
/// </summary>
public event PropertyChangedEventHandler PropertyChanged;

#endregion EventHandlers

#region properties

/// <summary>
/// Numeric Identifier for address
/// </summary>
public int AddressID
{
get
{
return _current.addressID;
}
set
{
_current.addressID = value;
_firePropertyChangedNotification("AddressID");
}
}

/// <summary>
/// First address line
/// </summary>
public string Address1
{
get
{
return _current.address1;
}
set
{
_current.address1 = value;
_firePropertyChangedNotification("Address1");
}
}

/// <summary>
/// Second address line
/// </summary>
public string Address2
{
get
{
return _current.address2;
}
set
{
_current.address2 = value;
_firePropertyChangedNotification("Address2");
}
}

/// <summary>
/// City where the address is located
/// </summary>
public string City
{
get
{
return _current.city;
}
set
{
_current.city = value;
_firePropertyChangedNotification("City");
}
}

/// <summary>
/// State where address is located
/// </summary>
public string State
{
get
{
return _current.state;
}
set
{
_current.state = value;
_firePropertyChangedNotification("State");
}
}

/// <summary>
/// Postal code where address is located
/// </summary>
public string PostalCode
{
get
{
return _current.postalCode;
}
set
{
_current.postalCode = value;
_firePropertyChangedNotification("PostalCode");
}
}

/// <summary>
/// Country where the address is located
/// </summary>
public string Country
{
get
{
return _current.country;
}
set
{
_current.country = value;
_firePropertyChangedNotification("Country");
}
}

#endregion properties

#region private methods

/// <summary>
/// Fires the PropertyChanged event
/// </summary>
private void _firePropertyChangedNotification(string propName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propName));
}
}

#endregion private methods

#region public methods

#region IEditableObject Members

/// <summary>
/// Places the object in edit mode and backs up original values
/// </summary>
public void BeginEdit()
{
if (!_editing)
{
_backup = _current;
_editing = true;
}
}

/// <summary>
/// Cancels the edit mode and restores original values
/// </summary>
public void CancelEdit()
{
if (_editing)
{
_current = _backup;
_editing = false;
}
}

/// <summary>
/// Commits editing changes
/// </summary>
public void EndEdit()
{
if (_editing)
{
_backup = new AddressData();
_editing = false;
}
}

#endregion

#endregion public methods
}
}

////////////////////////////////////////////////////////////////////////////////////

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Text;

namespace AddBookLib
{
[Serializable]
public class AddressList : BindingList<Address>
{
/// <summary>
/// Default ctor
/// </summary>
public AddressList() : base()
{

}

/// <summary>
/// Init ctor
/// </summary>
public AddressList(List<Address> addresses) : base(addresses)
{

}
}
}

////////////////////////////////////////////////////////////////////////////

Sorry it's so long ;o)


Good luck with your project,

Otis Mukinfus
http://www.arltex.com
http://www.tomchilders.com
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top