Setting DataRow as ComboBox's value member - c#

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();
}

Related

Refactoring, changing DataSource types based on selectedIndexChanged event, C#

I am dealing with a web application. What is asked of me is that when the page loads, that my drop down list displays index 0, with specific properties to display and store.
Based on that, there is a second down list to hold a list.
Next on the selectedIndexChanged event, should my index in ddl1 (I'll call it), the data in ddl2 should change.
The data being populated is all from a database query.
I'm in the process of refactoring, and i am trying to find a way so that I can just pass some data to a bindControls method. I'm having passing a general data type that could be cast based on a boolean value I am also passing to the method.
Here's an example
IQueryable<BankAccount> accountQuery = db.BankAccounts.Where(x => x.ClientId
== clientId && x.AccountNumber != accountNumber);
private void bindControls(DropDownList ddl, string textFieldProp, string
dataFieldProp, boolean isBillPayment, object dataSource)
{
//this is where my confusion is... i don't know how to change the type
//of the object
if(isBillPayment == true)
dataSource = typeof(IQueryable<BankAccount>);
ddlDataSource = dataSource.ToList()
ddlDataTextField = textFieldProp;
ddlDataValueField = dataFieldProp;
ddl.DataBind();
}
I know there HAS to be a way that I can assign this data source to what I want.
the query i posted is just an example of what that data source is going to , i have three different possible table queries from either BankAccounts, TransactionTypes, or Payees.
sorry i should have mentioned that, im sorry to for any confusion with it. Maybe that makes more sense now
Use generics:
private void bindControls<IQueryable<T>>(DropDownList ddl, string textFieldProp, string
dataFieldProp, boolean isBillPayment, IQueryable<T> dataSource)
{
// I don't think you really need this.
// if(isBillPayment == true)
// dataSource = typeof(IQueryable<T>);
ddlDataSource = dataSource.ToList()
ddlDataTextField = textFieldProp;
ddlDataValueField = dataFieldProp;
ddl.DataBind();
}
I would recommend casting .ToList() earlier and passing that to your bindControls() method instead of IQueryable.
... but I don't really understand why you're doing it this way. In my opinion, you don't need your boolean switch.

What is different when accessing BindingContext[dataSource] vs BindingContext[dataSource, dataMember]?

We have run into a problem where
We have two instances of the same window in an MDI workspace bound to two separate object models.
The object models have their .Equals and .GetHashCode methods overwritten to be considered equal.
Calling .EndCurrentEdit() on window 2 is triggering a binding update for Window 1
Both windows are setup to use separate a BindingContext
We have discovered the problem has to do with calling
((PropertyManager)ctrl.BindingContext[dataSource]).EndCurrentEdit();
If we change that to
((PropertyManager)ctrl.BindingContext[dataSource, dataMember]).EndCurrentEdit();
It works correctly. It also works correctly if we remove our .Equals and .GetHashCode overrides so the two object models are no longer considered equal.
That doesn't make sense to me because the windows are the same, so the dataMember property would be the same too.
From this link, I believe the definition of these calls is:
public BindingManagerBase this[object dataSource] {
get {
return this[dataSource, ""];
}
}
public BindingManagerBase this[object dataSource, string dataMember] {
get {
return EnsureListManager(dataSource, dataMember);
}
internal BindingManagerBase EnsureListManager(object dataSource, string dataMember) {
BindingManagerBase bindingManagerBase = null;
if (dataMember == null)
dataMember = "";
// Check whether data source wants to provide its own binding managers
// (but fall through to old logic if it fails to provide us with one)
//
if (dataSource is ICurrencyManagerProvider) {
bindingManagerBase = (dataSource as ICurrencyManagerProvider).GetRelatedCurrencyManager(dataMember);
if (bindingManagerBase != null) {
return bindingManagerBase;
}
}
// Check for previously created binding manager
//
HashKey key = GetKey(dataSource, dataMember);
WeakReference wRef;
wRef = listManagers[key] as WeakReference;
if (wRef != null)
bindingManagerBase = (BindingManagerBase) wRef.Target;
if (bindingManagerBase != null) {
return bindingManagerBase;
}
if (dataMember.Length == 0) {
// No data member specified, so create binding manager directly on the data source
//
if (dataSource is IList || dataSource is IListSource) {
// IListSource so we can bind the dataGrid to a table and a dataSet
bindingManagerBase = new CurrencyManager(dataSource);
}
else {
// Otherwise assume simple property binding
bindingManagerBase = new PropertyManager(dataSource);
}
}
else {
// Data member specified, so get data source's binding manager, and hook a 'related' binding manager to it
//
int lastDot = dataMember.LastIndexOf(".");
string dataPath = (lastDot == -1) ? "" : dataMember.Substring(0, lastDot);
string dataField = dataMember.Substring(lastDot + 1);
BindingManagerBase formerManager = EnsureListManager(dataSource, dataPath);
PropertyDescriptor prop = formerManager.GetItemProperties().Find(dataField, true);
if (prop == null)
throw new ArgumentException(SR.GetString(SR.RelatedListManagerChild, dataField));
if (typeof(IList).IsAssignableFrom(prop.PropertyType))
bindingManagerBase = new RelatedCurrencyManager(formerManager, dataField);
else
bindingManagerBase = new RelatedPropertyManager(formerManager, dataField);
}
My dataSource is not an ICurrencyManagerProvider
What is the difference between these two calls, and why does accessing the PropertyManager by only the dataSource result in the bindings for another window with a separate BindingContext being updated?
You don't state this explicitly, so in case you haven't noticed it is the collection look up that is not working as you expect, because of the equal override.
BindingContext[datasource] is a lookup against the collection using datasource as the key.
BindingContext[datasource, datamember] is a lookup against the collection using a composite key.
It is clear from the code that BindingContext is maintaining two separate collections. One a collection on a datasource key and the other a collection based on a composite key.
Obviously your override of equal will twice place similar values to datasource in the BindingContext[datasource] collection, but will result in one collection entry, because the values/keys are the same. Whereas, it will place two entries in BindingContext[datasource, datamember].
If you inspect both collections and can get the counts you'll see that the later collection has more entries.
You have to remember that you have two separate objects that evaluate to equal, not two references to the same object. This is the crux of the problem.
It would appear that when adding the entries to the second collection (BindingContext[datasource, datamember]) datamember evaluates to unique.
When you accessing BindingContext[dataSource], you actually accessing BindingContext[dataSource, ""]. So, there are no difference except HashCode, which uses both DataSource and DataMember values for calculation, that can be seen in your link.
public override int GetHashCode() {
return dataSourceHashCode * dataMember.GetHashCode();
}
The problem in separate BindingContext objects can be that they are not filled correctly.

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.

Find property of control by string in C#

I know that this was asked million times but I do not how to define my search,so please have patience.
I want to make method which is going to set some proprieties on control dynamical refer to sting sent to method.
something like this
public void SetGridColumnProperty(ref DataGridView grid,string columnName, string propretyName, string propertyValue )
{
grid.Columns[columnName].("I know this can't be done as this") = propertyValue;
}
I apologize if my question is hard to understand,
Here what I want to do
I am making WinForms app whic is using lot od Data Grid Views for displaying and editing data.
As I have needs to often change looks of my girds, add columns edit order and other My idea was to hold some kind of metadata for each DataGird on form, And when I showing form to user I want before I load
my form set properties of grid by join metadata and properties of specific grid.
I want to have config files separated of real code in XML or in DataBase so when I do edits on my application I do not have to recompile new code, Just change my xml and at next loding forom it looks as I like
First of all, There is no need to use ref keyword, because DataGridView is a reference type. Second, here is the solution:
public void SetGridColumnProperty(DataGridView grid, string columnName, string propertyName, object propertyValue)
{
DataGridViewColumn dgvColumn = grid.Columns[columnName];
typeof(DataGridViewColumn).GetProperty(propertyName).SetValue(dgvColumn, propertyValue, null);
}
Sample:
SetGridColumnProperty(dataGridView1, "ColumnName", "Width", 500);
Update: Check the CanWrite property of the PropertyInfo class before invoking the SetValue method.
OK so you're talking about Reflection. You can look up a lot of resources which will refer to the Reflection Namespace.
To get you started setting a property you'll proabably want to you use MethodInfo along the following lines:
MethodInfo invokedMethod = grid.GetType().GetProperty("propertyName").GetSetMethod();
invokedMethod.Invoke(targetObject, parameters);
you can use PropertyInfo to get the Property of an Control and assign its value
public void SetGridColumnProperty(ref DataGridView grid,string columnName, string propretyName, string propertyValue )
{
PropertyInfo pInfo = grid.GetType().GetProperty(propretyName);
if (pInfo != null)
{
TypeConverter tc = TypeDescriptor.GetConverter(pInfo.PropertyType);
if (tc.CanConvertFrom(Type.GetType(propertyValue.GetType().ToString())))
{
valToSet = tc.ConvertFromString(propertyValue);
pInfo.SetValue(grid, valToSet, null);
}
}
}
hope this helps
This can be done using reflection, look at GetProperties on the type you need this for. This will return an array of PropertyInfo objects for each property. Check if CanWrite returns true for the property you need and then use SetValue to set your property value.

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.

Categories

Resources