Is it safe to use ComboBox.DisplayMember without data binding? - c#

I've always understood that DisplayMember and ValueMember are only supposed to be used when databinding a ComboBox using its DataSource property.
But in some code I'm maintaing I've noticed that using a ComboBox' DisplayMember property does work without databinding. Setting the property determines what is shown in the combobox. Setting ValueMember does not seem to work though (it does not set SelectedValue).
My question is: is it safe to use this behavior? Or is there a risk the behavior may change in upcoming versions of .NET?
I know you'd normally override the ToString method. In the real code, the MyType class is not as simple as in my example. I'm not sure if it would be safe to override its ToString method.
A small sample to show this behavior.
using System;
using System.Windows.Forms;
internal class Program {
public class MyType {
public string MyText { get; set; }
public string MyValue { get; set; }
}
public class MyForm : Form {
private readonly ComboBox _myComboBox;
public MyForm() {
_myComboBox = new ComboBox {DisplayMember = "MyText", ValueMember = "MyValue"};
_myComboBox.Items.Add(new MyType {MyText = "First item", MyValue = "1"});
_myComboBox.Items.Add(new MyType {MyText = "Second item", MyValue = "2"});
_myComboBox.SelectedIndexChanged += _myComboBox_SelectedIndexChanged;
Controls.Add(_myComboBox);
}
private void _myComboBox_SelectedIndexChanged(object sender, EventArgs e) {
var cb = (ComboBox) sender;
System.Diagnostics.Debug.WriteLine(
"Index: {0}, SelectedValue: {1}", cb.SelectedIndex, cb.SelectedValue);
}
}
private static void Main() {
Application.Run(new MyForm());
}
}

Related

C# ComboBox databinding

I have a routine which opens a recordset and builds the Items collection for a combo box. After googling around I found the approach which uses the ComboboxItem class.
public class ComboboxItem
{
public string Text { get; set; }
public object Value { get; set; }
public override string ToString()
{
return Display;
}
}
My code uses this class to Add items to the ComboBox. When I run the app and click the Combobox the correct values are in the list... great! My problem is that when the form loads a record from the database, instead it looking up the appropriate list value which corresponds to the database value, it simply shows the value from the database: eg UK instead of United Kingdom. When I try to save the record, it tries to save "United Kingdom" instead of "UK". So I think the DisplayMember and ValueMember properties need assigning. I assumed that I would need to assign them as "Text" and "Value", but when I do this the Combobox displays a list of identical values. What am I doing wrong please?
Edit: This is a simplified version of what I have put into my ComboBox Class:
public class StandardComboBox : ComboBox
{
protected List<ComboboxItem> DataSourceList = new List<ComboboxItem>();
public bool SetRecordSource(string Criteria)
{
ADODB.Recordset RS = new ADODB.Recordset();
try
{
DataSourceList.Clear();
// Open ADDOB.Recordset RS with the records specified in Criteria
while (RS.EOF == false)
{
ComboboxItem MyComboboxItem = new ComboboxItem();
MyComboboxItem.Value = RS.Fields[0].Value.ToString();
MyComboboxItem.Display = RS.Fields[1].Value.ToString();
DataSourceList.Add(MyComboboxItem);
RS.MoveNext();
}
this.DataSource = DataSourceList;
this.ValueMember = "Value";
this.DisplayMember = "Display";
return true;
}
}
}
First, I think you should name your class better (eg. Country). Then set the name of the property too. Use string as your data type.
public class Country
{
public string ID { get; set; }
public string Name { get; set; }
}
Then you bind the combobox and set the DisplayMember and DisplayValue.
comboBox1.DataSource = listCountry;
comboBox1.DisplayMember = "Name";
comboBox1.ValueMember = "ID";
If you want to take the value, just use SelectedValue.
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show(comboBox1.SelectedValue.ToString());
}
Full source code.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace WindowsFormsApplication4
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
var listCountry = new List<Country>() {
new Country() {ID = "UK", Name = "United Kingdom"},
new Country() {ID = "US", Name = "United States of America"},
};
comboBox1.DataSource = listCountry;
comboBox1.DisplayMember = "Name";
comboBox1.ValueMember = "ID";
}
private void button1_Click(object sender, EventArgs e)
{
MessageBox.Show(comboBox1.SelectedValue.ToString());
}
}
public class Country
{
public string ID { get; set; }
public string Name { get; set; }
}
}
I've managed to work this out now. I'm from an MsAccess background where for example you just assign a combobox with 'UK' and the combobox displays 'United Kingdom'. In C# it's more complicated. When you want to assign a combobox with a value, you have to use the FindString() method to locate the value you want to display, then assign the SelectedIndex property in order to make it display that value. It's a ball ache, but I can build this logic into my class and not have to think about it again. Thanks for your input. –

ListBox forcing update of items

I created a ListBoxItem where I have a property Name and override ToString() to give back name. That works nicely when I add new items.
But now I need to force the ListBox to update the labels when I change the name of my ship. I thought Refresh or Update would do that but that doesn't work.
I might be missing something very easy here.
public class ShipListBoxItem
{
public ListBox Parent { get; set; }
public ShipType Ship { get; set; }
public ShipListBoxItem()
{
Ship = new ShipType();
}
public ShipListBoxItem(ShipType st)
{
Ship = st;
}
public override string ToString()
{
return Ship.Name;
}
public void UpdateListBox()
{
Parent.Refresh(); //My problem is here. Update doesn't work either.
}
public static ShipListBoxItem AddToListBox(ListBox lb, ShipType ship)
{
ShipListBoxItem li = new ShipListBoxItem(ship);
li.Parent = lb;
lb.Items.Add(li);
return li;
}
}
If you use a List<T> as the DataSource for the listbox it is pretty easy to have changes to items show up. It also means there is no real reason to have a special class for adding a ShipListBoxItem to a ListBox, your basic Ship class may work:
class ShipItem
{
public enum ShipTypes { BattleShip, Carrier, Destroyer, Submarine, Frigate };
public ShipTypes Ship { get; set; }
public string Name { get; set; }
public ShipItem(string n, ShipTypes st)
{
Name = n;
Ship = st;
}
public override string ToString()
{
return String.Format("{0}: {1}", Ship.ToString(), Name);
}
}
The form related stuff:
private void Form1_Load(object sender, EventArgs e)
{
// add some ships
Ships = new List<ShipItem>();
Ships.Add(new ShipItem("USS Missouri", ShipTypes.BattleShip));
Ships.Add(new ShipItem("USS Ronald Reagan", ShipTypes.Carrier));
lb.DataSource = Ships;
}
private void button1_Click(object sender, EventArgs e)
{
// change a ship name
lb.DataSource = null; // suspend binding
this.Ships[0].Name = "USS Iowa";
lb.DataSource = Ships; // rebind
lb.Refresh();
}
As an alternative, you can also tell the Listbox to use a specific property for the display using DisplayMember:
lb.DataSource = Ships;
lb.DisplayMember = "Name";
This would use the Name property in the listbox instead of the ToString method. If your list is changing a lot, use a BindingList instead. It will allow changes to the list show up in the ListBox as you add them without toggling the DataSource.
Try this
ListBox.RefreshItems()
msdn
EDIT: You can use an extended class like this:
public class FooLisBox : System.Windows.Forms.ListBox
{
public void RefreshAllItems()
{
RefreshItems();
}
}
private void button1_Click(object sender, EventArgs e)
{
(listBox1.Items[0] as ShipListBoxItem).Ship.Name = "AAAA";
listBox1.RefreshAllItems();
}
I managed to solve my problem.
Mostly, thanks Jose M.
I ran into a problem however. RefreshItems() triggers OnSelectedIndexChanged()
so my overridden class looks like this
public class MyListBox : ListBox
{
public bool DoEvents{ get; set; } // Made it public so in the future I can block event triggering externally
public MyListBox()
{
DoEvents = true;
}
public void RefreshAllItems()
{
SuspendLayout();
DoEvents = false;
base.RefreshItems(); // this triggers OnSelectedIndexChanged as it selects the selected item again
DoEvents = true;
ResumeLayout();
}
// I only use this event but you can add all events you need to block
protected override void OnSelectedIndexChanged(EventArgs e)
{
if (DoEvents)
base.OnSelectedIndexChanged(e);
}
}

Class variable availablility after event is fired

I'm new to event programming, and I'm obviously misunderstanding something that I'm trying to do.
I have a Windows Forms application that subscribes to events from another class. T
//Class that provides event handler to Windows Forms application.
class Foo
{
public string Value{get; set;}
// Lots of other code
public void OnEventFired(object sender, EventArgs e)
{
// Attempt to access variable Value here.
}
}
From the Windows Form code I'm first setting the variable Value in class Foo before triggering the event that will execute the code in OnEventFired above.
What I'm seeing is that when used in the event handler the variable Value doesn't contain the value that was set before the event was fired (Value is null).
I know I can extend EventArgs to include the variable data, but I'm trying to understand why what I'm doing doesn't work.
Here's a short example which works. Compare this to your code to work out what's wrong.
using System;
using System.Windows.Forms;
class Foo
{
public string Value { get; set; }
public void HandleClick(object sender, EventArgs e)
{
((Control)sender).Text = Value;
}
}
class Program
{
public static void Main()
{
Foo foo = new Foo { Value = "Done" };
Button button = new Button { Text = "Click me!" };
button.Click += foo.HandleClick;
Form form = new Form
{
Controls = { button }
};
Application.Run(form);
}
}
My guess is that you've hooked up the event handler using a different instance of Foo than the one you've set Value in. For example, like this:
Foo foo = new Foo { Value = "Done" };
...
// Different instance of Foo!
button.Click += new Foo().HandleClick;
... but it's hard to tell without seeing any more code.
The only reason that you can not access the variable Value is
Value is not set
You are binding event to a different instance, not the one with the Value been set.
The best would be to get the Value in constructor, so that it is guaranteed that the Value is set.
class Foo
{
public string Value { get; set; }
public Foo(Value value)
{
}
public void HandleClick(object sender, EventArgs e)
{
((Control)sender).Text = Value;
}
}

Change bound Property when programmatically changing SelectedIndex

I've set up a simple form. A ListBox takes values from a list in the 'business object' displaying the Name property and providing the Value property.
In additon the ListBox's SelectedItem property is bound to a property in the same business object.
Using the UI to select a value from the list correctly changes the objects property (checked when the button is clicked) and the correct value is available. So far so good.
However, if the ListBox's SelectedIndex property is changed in the code, then the UI correctly changes as expected but the business property does not change - it would appear to have missed the change event. This is true for both setting in the constructor and in the button event handler (see the code).
What have I missed or what am I doing incorrectly.
(I've only included the code I've written - not VS wizard generated stuff)
class Frequency
{
public String Name { get; set; }
public Int16 Value { get; set; }
public Frequency(String name, Int16 value)
{
Name = name;
Value = value;
}
}
class FrequencyList : System.ComponentModel.BindingList<Frequency>
{
}
class Model
{
public static FrequencyList FrequencyValues = new FrequencyList()
{
new Frequency("Slowest", 100),
new Frequency("Slow", 150),
new Frequency("Medium", 1000),
new Frequency("Fast", 5500),
new Frequency("Fastest", 10000)
};
public Frequency StartFrequency { get; set; }
public void DoStuff()
{
if (StartFrequency == null)
return;
Int16 freq = StartFrequency.Value;
}
}
public partial class Form1 : Form
{
private Model myModel = new Model();
public Form1()
{
InitializeComponent();
// Bind the list to a copy of the static model data
this.listBox1.DataSource = Model.FrequencyValues;
// Bind the control to the model value
this.listBox1.DataBindings.Add("SelectedItem", myModel, "StartFrequency");
// Select the start value
this.listBox1.SelectedIndex = 3;
}
private void button1_Click(object sender, EventArgs e)
{
Int16 f = (Int16)listBox1.SelectedValue;
this.myModel.DoStuff();
int new_index = listBox1.SelectedIndex + 1;
if (new_index >= listBox1.Items.Count)
new_index = 0;
listBox1.SelectedIndex = new_index;
}
}
You don't want the Click event, you want the SelectedIndexChanged event. This will trigger regardless of whether the user or the program instigates the change.

Winforms binding questions involving structs

Hi coders I have yet another question involving data binding in winforms. I set up a test applications where I have a bindinglist composed of structs called CustomerInfo. I have bound a listbox control to this list and spun a thread to add CustomerInfo items to the bindinglist.
namespace dataBindingSample {
public partial class Form1 : Form {
public BindingList<CustomerInfo> stringList = new BindingList<CustomerInfo>();
public Thread testThread;
public Form1() {
InitializeComponent();
stringList.AllowNew = true;
stringList.RaiseListChangedEvents = true;
listBox1.DataSource = stringList;
testThread = new Thread(new ThreadStart(hh_net_retask_request_func));
testThread.Priority = ThreadPriority.Normal;
}
private void hh_net_retask_request_func() {
int counter = 1;
while (true) {
CustomerInfo cust = new CustomerInfo();
cust.Name = "Customer "+ counter.ToString();
this.Invoke((MethodInvoker)delegate {
stringList.Add(cust);
});
counter++;
Thread.Sleep(1000);
}
}
private void Form1_Load(object sender, EventArgs e) {
testThread.Start();
}
}
public struct CustomerInfo {
public string Name {
set {
name = value;
}
get {
return name;
}
}
private string name;
}
}
What I see in the list box is the name of the struct dataBindingSample.CustomerInfo as opposed to the property of the struct. I was under the impression that non complex binding took the first available property.
Please educate me as to what I am doing wrong.
Thanks,
You'll need to either add an override of ToString() to your CustomerInfo class that returns what you'd like displyed in your list box, or set listBox1.DisplayMemer = "Name" before setting the DataSource.

Categories

Resources