DisplayMember getting reset on DataSource=null - c#

I have a ComboBox whose items are set using the DataSource property. The DataSource is a collection of a custom object (that has a string property 'Value' and int property 'Id'). In the initialise controls, I set the DisplayMember as Value and ValueMember as Id. Now I tried to clear the DataSource by calling,
myComboBox.DataSource = null;
When I did that, my DisplayMember is reset to "" automatically. Am I clearing the DataSource properly ?? Is that the way I should behave ???

I can reproduce it... it isn't something I would have expected, but it looks like you'll simply have to set the DisplayMember back afterwards.
Looking in reflector, this is quite intentional:
if (value == null)
{
this.DisplayMember = "";
}
Not sure of the reasoning behind that, but simply:
string oldDisplayMember = cbo.DisplayMember;
cbo.DataSource = null;
cbo.DisplayMember = oldDisplayMember;
Not pretty, but it'll work.

When there is an Exception thrown or if the Datasource value is null, then the Displaymember is set to an empty string.
Using a .NET code decompiler, there are comments in the code about how this works. I'll try to paraphrase.
Exceptions may be thrown in the following circumstances:
Application code on DataSourceChanged
During binding when the data is being formatted for display
If the DisplayMember value isn't valid in the new Datasource.
The comment also mentions that the actual exception is "swallowed" to keep from breaking expected behavior.
I personally ran into this behavior and since the exception isn't thrown it's difficult to figure out which of these conditions caused the reset. There is probably a better way but for now I'm using this in my custom control.
The Datasource property is valid when setting the DisplayMember after it's been reset so I'm really not sure about why it's failing :
public new object DataSource
{
get { return base.DataSource; }
set
{
string displayMem = this.DisplayMember;
base.DataSource = value;
if (string.IsNullOrEmpty(this.DisplayMember) && string.IsNullOrEmpty(displayMem))
this.DisplayMember = displayMem;
DetermineDropDownWidth();
}
}

Related

WinForm Databound Control Results Return Class Name Not Actual Data

I am having a very hard time finding a solution to my issue. I have scoured the Internet, rebuilt my class several times, and have gone through every line in the debugger.
I have three ListBox controls that I am databinding a BindingList to. In two of the ListBoxes, the data is what is expected; names from the database. The third ListBox does not display names, but the namespace of the items. In each of my classes I have implemented the IBindingList interface. I have bound the list to the DataSource of the control and supplied the appropriate name for the DisplayMember.
In the debugger, I can see the names from the database in the lbRole ListBox after they are bound to the DataSource, but when the form displays they don't appear, just the namespace.
//
// Get all control names.
//
lbUser.DataSource = SharepointTestBusinessLayer.User.ListAll();
lbUser.DisplayMember = "LoginID";
lbControl.DataSource = SharepointTestBusinessLayer.Control.ListAll();
lbControl.DisplayMember = "ControlName";
lbRole.DataSource = SharepointTestBusinessLayer.Role.ListAll();
lbRole.DisplayMember = "RoleName";
In walking through my classes with the debugger, in the RoleItemCollection class, GetEnumerator() is never hit.
BindingList<RoleItem> m_CurrentRoleItemCollection;
public BindingList<RoleItem> CurrentRoleItemCollection { get => m_CurrentRoleItemCollection; set => m_CurrentRoleItemCollection = value; }
public object this[int index] { get => ((IBindingList)CurrentRoleItemCollection)[index]; set => ((IBindingList)CurrentRoleItemCollection)[index] = value; }
public IEnumerator GetEnumerator()
{
return ((IBindingList)CurrentRoleItemCollection).GetEnumerator();
}
I don't know why GetEnumerator() is never his in the RoleItemCollection class as it is hit in the ControlItemCollection and UserItemCollection classes.
Does anything come to mind with this issue you might have some insight into. I can't think of anything else to try.
I think I found out part of the reason for this. When I changed the property name from RoleName to NameOfRole, I got actual data. I don't know why this would be but RoleName as the actual name of the property was in conflict with something I have yet to find. Thank you everyone who took the time to answer my question.

Handling Nullable Properties when posting to a ComboBox DropDownList

I have a custom class Diary, which has a nullable property DeadlineType
On one of my Forms I have a ComboBox in DropDownList mode, which is populated with all the possible values that DeadlineType can take. When a Diary is selected, the form is populated with all the properties, but I have an issue with the DeadlineType property in that, when it is null, it requires more code than I believe it should i.e.
if (amendDiary.DeadlineType != null)
{
cboDeadlineType.Text = amendDiary.DeadlineType.ToString();
}
else
{
cboDeadlineType.Text = null;
}
I am sure there must be a more concise way to achieve this, or certainly hope so, because this scenario is repeated a lot throughout my application i.e. posting nulls to ComboBoxes.
I did try
cboDeadlineType.Text = amendDiary.DeadlineType.ToString() ?? null
But that gives an 'Object not set to an instance' error.
Try this:
cboDeadlineType.Text = (amendDiary.DeadlineType ?? "").ToString();
Note that setting the ComboBox.Text to null means the same as setting it to "" (The combo Text is empty in both cases).
Another solution is use the Convert.ToString method:
cboDeadlineType.Text = Convert.ToString(amendDiary.DeadlineType);
With that solution, if the input is null, the cboDeadlineType.Text is assigned exactly to null.
try
cboDeadlineType.Text = amendDiary.DeadlineType!=null ?amendDiary.ToString() :"";

What does each ComboBox.Selected... return?

Right now, to be sure I am getting what I want I am using
actionComboBox.Items[actionComboBox.SelectedIndex].ToString()
to retrieve the string that is stored as an item in my TextBox
Does one of the Selected properties return my above statement? I can never seem to get what I want when I use those.
Like, does actionComboBox.SelectedItem as string return the above value?
EDIT:
I guess the true question here is: What do each Selected Property return such as; SelectedItem, SelectedValue, SelectedText.
I think SelectedText returns the text that is selected if you are able to edit the text in the combo box. I don't think you use this property if you have the DropDownList style selected where the user cannot just type values into the combobox.
SelectedValue only applies if you are bind to a datasource. SelectedValue will return the item in the datasource you've selected, or if you have the DisplayMember field filled in, the value of the property/column that you have specified.
SelectedItem will return the selected item if you have just filled in the list items through the designer.
I get burned on these all the time, cause I always forget. The big question in your example is how are the items being populated into the combo box, that will affect the return values of these properties.
ComboBox.Items is a collection of System.Object's, so it can be anything. By default the ComboBox displays the return value of an object's ToString method. Whatever you add to the ComboBox will be what you will get back, though its returned as a System.Object and you will have to convert it back to its original type to access its members.
comboBox.Items.Add("foo");
The above will add a System.String to the ComboBox.
class Foo
{
public String Bar { get; set; }
}
Foo foo = new Foo();
foo.Bar = "Value";
comboBox.Items.Add(foo);
The above will add a Foo to the ComboBox. So to get your values back.
Object obj = comboBox.Items[comboBox.SelectedIndex];
Foo foo = obj as Foo;
if (foo != null) { // check just in case
}
For strings, there's no need for a conversion, calling ToString is fine. It's better to just use SelectedItem instead.
Foo foo = comboBox.SelectedItem as Foo;
if (foo != null) { // again, check to make sure
}
The power of the ComboBox is that since it stores a collection of System.Object, you can store multiple types of objects, but you are in charge of converting it back to whatever usable type it was to begin with when you need to access it.

Adding enum to combobox

Hi may i know how to get the enum value below to bind into the combobox?
I wrote the below code which works well but wonder is this the best way.
public enum CourseStudentStatus
{
Active = 1,
Completed = 2,
TempStopped = 3,
Stopped = 4,
}
//Bind Course Status
Dictionary<string, int> list = new Dictionary<string, int>();
foreach (int enumValue in Enum.GetValues(typeof(CourseStudentStatus)))
list.Add(Enum.GetName(typeof(CourseStudentStatus), enumValue), enumValue);
var column = ((DataGridViewComboBoxColumn)dgv.Columns["studentCourseStatus"]);
column.DataPropertyName = "StudentStatus";
column.DisplayMember = "Key";
column.ValueMember = "Value";
column.DataSource= list.ToList();
----------------- UPDATE -------------------
Hi i have changed my code to this according to Sanjeevakumar Hiremat and it works perfectly.
cbStatus.DataSource = Enum.GetValues(typeof(CourseStudentStatus));
However, when i want to a Get() and want to bind the value back to the cbStatus, it cast error {"Object reference not set to an instance of an object."}
cbStatus.SelectedValue = Course.Status;.
The cbStatus.Datasource is not empty and it has value after bound to cbStatus.DataSource = Enum.GetValues(typeof(CourseStudentStatus));
please advice.
Following should be the simplest way to bind it.
column.DataSource = Enum.GetValues(typeof(CourseStudentStatus));
To get the selected value you need to cast it to the enum type.
CourseStudentStatus selectedValue = (CourseStudentStatus)column.SelectedValue
Enum.GetValues returns an array of the enumType values which can then be bound to any control.
I've tested this on a standalone combobox, not in a combobox column in DataGridView, YMMV.
I don't think there is a best way. I used to do something similar with a GenericListItem<T> class where T is the backing value, in your case, an enum.
This class exposed Display string and Value T properties to bind to. I think I was also overriding ToString because it is the default if you don't specify the DisplayMember. I went further and made a constructor taking just Value and defaulting Display to Value.ToString, which in the case of enums works I think.
I'd then make a List<GenericListItem<T>>, feed that into the DataSource of the column and set the DisplayMember and ValueMember properties accordingly in code. This list is the alternative to the dictionary used in your example.
But I'm not saying it's a better solution :-) however it means you can remove code, say enum iteration, into this class or specialise the class for handling certain data types better, all with the end goal of being inserted into a list and bound to a control.

Setting DataRow as ComboBox's value member

I am filling items to my ComboBox from a XML file using a DataTable. Currently I have it set so that one column is ComboBox's displaymember and another is it's value member. However this may not always work for me, since I have to set the selectedItem parameter, and value member may not be unique.
I don't know if there is a duplicate of the value member in the table or not, so my idea was that I would put entire DataRow as the value member of the ComboBox and then use ComboBox.SelectedITem = (DataRow)some_data_row; for selecting, and it would always select the right ComboBox object.
How would I accomplish this? IS there a better way of doing this? I'm open to suggestions, however it is very important that I can get to both, display member and value member.
Thank you for your help!
EDIT: Maybe I wasn't clear enough before, however while I am asking if this is the best approach here, I am also asking how to do this. If I don't set the valuemember parameter, the SelectedItem is of DataRowView type... Please note, that I want to use the selectedValue parameter to select items from ComboBox, and if I try to do that without explicitly setting the value member an exception is thrown.
If you bind a ListBox to a DataTable, you're actually binding it to a DataView that represents that DataTable (DataTable implements IListSource, and that returns a DataView). You can't directly set SelectedItem to a DataRow instance, you have to set it to a DataRowView instance. Unfortunately there's no easy way to obtain a DataRowView from a DataRow.
You would do better to just do all of your interactions through a DataRowView. This will allow you to set SelectedItem explicitly.
You cannot use the SelectedValue property, you must use SelectedItem for this.
First of all thank you Adam Robinson, I'm sure your answer was correct, but it just wasn't what I wanted to hear. I solved my problem in a different way and I think it may be useful to someone else, so I am posting it here.
What I did was I created a new class, in my case I named it ListObject, which had a property DataRow (as you will see later it works for other types too, I just used this since this is what I actually wanted as my Item value property). It also overrides methods:
String ToString()
bool Equals(object obj)
int GetHashCode() --is not needed in my case, however Visual Studio
warns you it should be overridden.
The idea was that I could fill ComboBox.Items collections with objects of my own class, display a custom string (if I had not worked it out like this, my next question on Stack overflow would probably be about customizing DisplayMembers when reading items from a DataRow) and compare only one class's item (in my case DataRow).
So here is the code and it works great (at least for what I wanted to do with it).
public class ListObject
{
public DataRow element;
public String DisplayObject = null;
public ListObject(DataRow dr)
{
element = dr;
}
public ListObject(DataRow dr, String dspObject)
{
element = dr;
DisplayObject = dspObject;
}
public override String ToString()
{
if (DisplayObject == null) throw new Exception("DisplayObject property was not set.");
return element[DisplayObject].ToString();
}
public override bool Equals(object obj)
{
if (obj.GetType() == typeof(ListObject))
return Equals(((ListObject)obj).element, this.element);
else return base.Equals(obj);
}
public override int GetHashCode()
{
return base.GetHashCode();
}
}
In my case it works great since I can just fill the ComboBox's with a foreach statement:
dtUsers.ReadXml(Program.Settings.xmlInputUsers);
foreach(DataRow dr in dtUsers.Rows)
{
cmbUser.Items.Add(new ListObject(dr, "Name"));
}
And when I get the DataRow I want selected I just do this:
cmbUser.SelectedItem = new ListObject(dlg.SelectedDataRow);
Where I don't have to worry about the DisplayMember etc, because only DataRow's will be compared, and your display parameters will still be set from when you filled ComboBox.Items collection. Also since toString method is overridden you can really customize your output.
Creating this class was only possible because of msdn article on ComboBox.SelectedItem Property in which it was noted, that SelectedItem property works using the IndexOf method. This method uses the Equals method to determine equality.
This is the most simple way to get DataTable to a combobox
private void load() {
DataTable dt = // get data from DB
comboBox1.ValueMember = null; // allows you to get all fields in the obj to combobox
comboBox1.DisplayMember = "ccType";//label displayed from dt
comboBox1.DataSource = dt;
}
//to test
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
DataRowView current = (DataRowView)comboBox1.SelectedValue;
string drs = current.Row["ID"].ToString();
}

Categories

Resources