I recently asked this question on here and got the answer. However I'm now trying to apply the same logic on a DataGridView which is bound to a BindingList< T > of Curriculum objects. The Curriculum class has a property of type Year. I'm trying to use a ComboBoxColumn to update the reference the curriculum object has of years.
The comboboxcolumn is bound to a BindingList< T > of years, it errors if I set either the display member or the value member so I left them null. Doing this the datagridview successfully loads and displays the data correctly (I overrode the ToString method on the year class). However, if I choose another year object from the combobox, as soon as it end edits it throws and exception saying it can't convert string to type year.
It looks like I need a TypeConverter to do it, but the problem is the combobox is displaying a descriptive value, which I can't guarantee will be unique to that year object - so I have no way of getting a year object from a given string.
Has anyone got any experience in situations like these, it must be a pretty common thing to want to do but google has failed me on this occasion.
Marlon
Same problem as here. Seems that object binding in a combobox column doesn't work properly and you have to specify a ValueMember.
For the particular project I am working on, I came to the conculsion that it was not worth implementing a custom type descriptor, so instead, I am using a fairly horrible hack:
In the entity that I am binding to, I have the following:
class TestEntity
{
public TestEntity BindingHack_ValueMember
{
get
{
return this;
}
}
public string BindingHack_DisplayMember
{
get
{
return this.ToString();
}
}
}
And the databinding for the column looks like this:
column.DataPropertyName = "Foo";
column.DisplayMember = "BindingHack_DisplayMember";
column.ValueMember = "BindingHack_ValueMember";
A little ugly, perhaps, but it works ...
Related
We have a column bound to a sql integer value that is nullable. When the user tries to "clear" the cell in the DataGrid, we get the validation error
"...could not be converted".
How can I make it empty and bind a "null" value to the underlying column?
I have googled this problem for 2 days now and found anything. I tried to edit my comment to you HCL, but it would never save.
I am familiar with the IValueConverters. I wrote one to handle our percentages (so the user can enter "50 %" and it gets converted to ".5"). I am trying to do something very similar, but when the value comes back as a Null (I wrote a dummy IValueConterter to debug it), I just want it to be saved to the database as a Null. Maybe I just need to set it to DBNullValue? It just seems like a lot of work for something that I think might have a built in property. I tried using the TargetNullValue property on the column (DataGridBoundColumn) and setting that to DBNull.Value, but it didn't change the value (based on my dummy IValueConverter it is still coming in as a regular (string) NULL.
I simply want to be able to save a null value to the database for this (integer type) column.
** Latest addition **
Good point HCL, I forget that the whole world isn't ADO sometimes.
I am binding to a SQL table. The column in question is an int that allows NULLs.
The columns are created using AutoGeneratingColumn, so it basically just hooks them up "automagically" (certain styles such as Right Justify are applied in this method, but not for this column). It takes this approach, because the app is pretty much an "Access" replacement. Our CIO mandated we remove Access from the users, so this was the solution (create our own MS Access). Anyway, since it can open any table (and create tables, columns, etc) it just "AutoGenerates" the columns based on the table that was opened. Although, since there are certain columns that the app inherently knows about such as Discount_Pct, when it encounters one of those columns, it does do some "special stuff" (like assign the IValueConverter I described above). Although, like I said... for this particular column, there isn't anything "special" done to it. It is just a regular SQL integer (nullable) that is "AutoGenerated".
I just found the property I was looking for!!
TargetNullValue
This post explains it: HERE
The problem is, these columns are generated using "AutoGenerateColumns" (because the datasource...a sql table... is "dynamic"). If I could figure out how to get the table column (primitive) type, I already know how to see if it is Nullable, then I can set this value for that column. (I still don't understand why the AutoGenerateColumns doesn't handle this automatically!!)
Extending off of Shayne's self-answer, I discovered that the TargetNullValue property is available on DataGridBoundColumn objects. You could use the additional logic described from this comment to make this conditional. Unfortunately, that post does not explain how to relate its logic to a DataGrid, so here is my solution:
<DataGrid AutoGenerateColumns="True" AutoGeneratingColumn="m_grid_AutoGeneratingColumn"/>
And...
public void m_grid_AutoGeneratingColumn(object sender, DataGridAutoGeneratingColumnEventArgs e)
{
// Make all columns nullable
((DataGridBoundColumn)e.Column).Binding.TargetNullValue = string.Empty;
}
I was so frustrated with this problem. I hope this helps someone.
I decided to go the IValueConverter route for this field too.
Here is what I wrote (and it works, I still feel like there must be an easier way! lol):
[ValueConversion(typeof(Int32), typeof(String))]
public class IntDBNullConverter : IValueConverter
{
public object Convert(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
if (value == null)
return "";
else if (DBNull.Value.Equals(value))
return "";
else
return value.ToString();
}
public object ConvertBack(object value, Type targetType, object parameter, System.Globalization.CultureInfo culture)
{
string str = value as string;
if (String.IsNullOrEmpty(str))
return DBNull.Value; // returns DBNull.Value
Int32 result = 0;
Int32.TryParse(str, out result);
return result;
}
}
This should be a duplicate question but I'm posting it because none of the answers anywhere are working.
I have a dictionary of the types:
private Dictionary<IModule, AssemblyLoadContext> ModuleList = new Dictionary<IModule, AssemblyLoadContext>();
I am trying to bind the names of the IModules (IModule.Handle, for everything that implements IModule) to a combobox.
I've tried many things and searched through every answer on google but nothing works. This is apparently the way you are supposed to do it:
comboBox1.DataSource = new BindingSource(ModuleList, null);
comboBox1.DisplayMember = "Value";
comboBox1.ValueMember = "Key";
When I do this I get a RUNTIME error: (System.ArgumentException: 'Cannot bind to the new display member. (Parameter 'newDisplayMember')'
)
When I try swapping key and value I get this same error: (System.ArgumentException: 'Cannot bind to the new display member. (Parameter 'newDisplayMember')'
)
When I try other combinations of key/value, I get random results. Sometimes it will show the entire class name (not helpful), sometimes it will show the ToString representation (overloaded and works perfectly except doesn't UPDATE after startup), and sometimes it just shows nothing or the program gives an error during runtime.
However no combination of things I have tried actually gets the BOX contents to UPDATE when modules are loaded and unloaded (the modules themselves are definitely loading/unloading and work fine).
This is supposedly working as of many years ago and I can only imagine microsoft broke something in one of their updates because the intended method does NOT work for me.
This is using .NET core 3.1 modules and .NET 5.0 application (required in order for modules to work because microsoft 5.0 exe does not work with microsoft 5.0 dll).
The overloaded ToString method of IModule returns Handle which is a string that names the module, IE "ConsoleModule", and works as intended. Everything else is working except the data binding.
Can anyone else at least confirm this data binding method actually works in .NET 5.0 and/or 3.1? Rapidly losing sanity.
Whenever you have a sequence of similar items, that you want to show in a ComboBox, you need to tell the ComboBox which property of the items should be used to display each item. You were right, this is done using ComboBox.DisplayMember
Your Dictionary<IModule, AssemblyLoadContext> implements IEnumerable<KeyValuePair<IModule, AssemblyLoadContext>, so you can regard it as if it is a sequence of KeyValuePairs. Every KeyValuePair has a Key of type IModule, and a Value of type AssemblyLoadContext.
The IModule and the AssemblyLoadContext have several properties. You need to decide which property of them you want to show.
I am trying to bind the names of the IModules (IModule.Handle)
I guess that every IModule has a property Handle, and you want to display this Handle in the ComboBox.
comboBox1.DisplayMember = nameof(IModule.Handle);
If you need a display only, so no updates, it is enough to convert your original sequence into a list:
Dictionary<IModule, AssemblyLoadContext> myData = ...
comboBox.DataSource = myData.ToList();
However, if you want to update the displayed data, you need an object that implements IBindingList, like (surprise!) BindingList<T>. See BindingList.
You can make a BindingList<KeyValuePair<IModule, AssemblyLoadContext>>, but this is hard to read, hard to understand, difficult to unit test, difficult to reuse and maintain. My advice would be to create a special class for this.
I haven't got a clue what's in the IModule, so you'll have to find a proper class name. I'll stick with:
class DisplayedModule
{
public string DisplayText => this.Module.Handle;
public IModule Module {get; set;}
public AssemblyLoadContext AssemblyLoadContext{get; set;}
}
And in the constructor of your form:
public MyForm()
{
InitializeComponent();
this.ComboBox1.DisplayMember = nameof(DisplayedModule.DisplayText);
This way, if you want to change the text that needs to be displayed, all you have to do is change property DisplayText.
public BindingList<DisplayedModule> DisplayedItems
{
get => (BindingList<DisplayedModule>)this.comboBox1.DataSource;
set => this.comboBox1.DataSource = value;
}
You need procedures to get the initial data:
private Dictionary<IModule, AssemblyLoadContext> GetOriginalData() {...} // out of scope of this question
private IEnumerable<DisplayedModule> OriginalDataToDisplay =>
this.GetOriginalData().Select(keyValuePair => new DisplayedModule
{
Module = keyValuePair.Key,
AssemblyLoadcontext = keyValuePair.Value;
});
I have put this in separate procedures, to make it very flexible. Easy to understand, easy to unit test, easy to change and to maintain. If for instance your Original data is not in a Dictionary, but in a List, or an Array, or from a database, only one procedure needs to change.
To initially fill the comboBox is now a one-liner:
private ShowInitialComboBoxData()
{
this.DisplayedItems = new BindingList<DisplayedModule>
(this.OriginalDataToDisplay.ToList());
}
private void OnFormLoad(object sender, ...)
{
this.ShowInitialComboBoxData();
... // other inits during load form
}
If the operator adds / removed an element to the list, the bindinglist is automatically updated. If something happens, after which you know that the dictionary has been changed, you can simply change the bindingList For small lists that do not change often, I would make a complete new BindingList. If the List changes often, or it is a big list, consider to Add / Remove the original BindingList.
private void AddDisplayedModule(DisplayedModule module)
{
this.DisplayedItems.Add(module);
}
private void RemoveDisplayedMOdule(DisplayedModule module)
{
this.DisplayedItems.Remove(module);
}
private void ModuleAddedToDictionary(IModule module, AssemblyLoadContext assembly)
{
this.AddDisplayedModule(new DisplayedModule
{
Module = module,
AssemblyLoadContext = assembly,
})
}
If the operator makes some changes, and indicates he finished editing the comboBox, for instance by pressing the "Apply Now" button, you can simply get the edited data:
private void ButtonApplyNowClicked(object sender, ...)
{
// get the edited data from the combobox and convert to a Dictionary:
Dictionary<IModule, AssemblyLoadContext> editedData = this.DisplayedItems
.ToDictionary(displayedItem => displayedItem.Module, // Key
displayedItem => displayedItem.AssemblyLoadContext); // Value;
this.ProcesEditedData(editedData);
}
To access the Selected item of the comboBox
DisplayedModule SelectedModule => (DisplayedModule)this.comboBox1.SelectedItem;
Conclusion
By separating you data from the way that it is displayed, changes will be minimal if you decide to change your view: change Combobox into a ListBox, or even a DataGridView. Or if you decide to change your data: not a Dictionary, but a sequence from a Database
I am writing a program where data is being displayed using a DataGridView and I was hoping there was a way to access the visible property of the columns without specifying the index location, or Column Name string value.
public class test {
public static string value1 { get; set; }
public static string value2 { get; set; }
}
I am using the LINQ to SQL datacontext to query information to be displayed into my DataGridView.
As it currently is, I can only seem to find a way to change the Columns visible property as so (assuming DataGridView is instantiated as dgvDATA)
dgvDATA.columns["value1"].visible = false;
Is there any way to do something similar to the following? I assume if there is it would be through databindings, but I tried that and couldn't figure it out.
dgvDATA.column.value1.visible = false;
I found this article stating that the DataGridView does not have this kind of ability built in but there was a workaround where you could add a database field representing if you want it visible or not.
HOWEVER the article was written in 2011 so I find it hard to believe that something like this was never implemented.
https://dotnetbuildingblocks.wordpress.com/2011/07/30/binding-datagrid-column-visibility-to-a-datacontext/
Please let me know if this is possible! Thank you
DataGridView doesn't support what you are asking for.
Anyway, if your only goal is to avoid using hardcoded field names, then using nameof() makes sense.
In your case it will be
dgvDATA.Columns[nameof(test.value1)].Visible = false;
That way you will have no issues refactoring your code later.
Apparently you want some kind of special DataGridViewColumn, where columns can be identified using some identification that is not available in a standard DataGridViewcolumn. For example you want to identify them by the PropertyInfo of the property that is shown in the column, or maybe the database column name of the database column whose values you show in this column.
In object oriented programming, if you want to create a special kind of DataGridViewColumn you should write a derived class:
class MySpecialDataGridViewColumn : DataGridViewColumn
{
public string DatabaseColumnName {get; set;}
}
Normally this would be enough: as long as you make sure that you only add MySpecialDataGridViewColumn objects to your DataGridView. When you fetch a column, typecast it to MySpecialDataGridViewColumn.
var theColumnThatDisplaysFirstName = myDataGridView.Columns // get all columns
.Cast<MySpecidalDataGridViewColumn>() // cast to your type
.Where(column => column.DatabaseCollumnName == "FirstName")
.SingleOrDefault(); // keep the ones with "FirstName"
Be aware that others will still be able to Add other kind of columns to your DataGridView. If you are afraid of this, make sure that you keep your members private and add functionality to Add / Fetch / Remove MySpecialDataGridViewColumns.
If your DataGridView is to be used by many, consider creating a UserControl that contains a private DataGridView, with functionalities to Add / Retrieve / Remove MySpecialDataGridViewColumn objects to the user control. This way others can't misuse your `DataGridView by adding other types of columns
Of course, if you want to allow others to add their own kind of Columns, you could always use OfType<MySpecialDataGridViewColumn> instead of a Cast. This way you ignore the other type of added columns, of which you are certain that they don't display your database columns
I'm new to WPF and I'm trying to figure out how to get the current text value of the selected item in a ComboBox. I saw in this question someone suggested doing MyComboBox.SelectedItem.Text. However, SelectedItem returns object for me, so I only have options like ToString(), Equals, etc. What's going on? I'm using .NET 3.5, developing in VS 2010. The other methods I thought might be of use, like MyComboBox.SelectedValue, also return object. SelectedIndex returns int, but I want a string value. MyComboBox is of type ComboBox. I'm accessing it in a method to handle the SelectionChanged event.
Have you tried MyComboBox.Text ? That will return you the text of the currently selected item.
You can also parse the SelectItem into the type of the datasource you've set it and get the text property you want directly from the object?
ie
MyObject obj = (MyObject)MyComboBox.SelectedItem;
string text = obj.Text;
Each Item is a Object.
The Displayed Data is Object.ToString (Item.ToString)
But you can Use any other Object member, Property or method from Object.
You have added the object to Combo, then you know Object Type and can Cast it.
I am currently trying to bind an entity to a form however I want to have DataConfidenceLevel (see below) bound to a combobox with ConfidenceDescription as the display member. What is the correct way to populate the combobox?
(I am currently using WPF but a Winforms answer is acceptable)
Thanks
Entity Designer http://img19.imageshack.us/img19/374/entity.png
You want to bind a collection to a control and have a releated entity - namely navigation property DataConfidenceLevel of type DataConfidenceLevel - as the display member?
That is usually achieved really simple by overriding ToString(),
public partial class DataConfidenceLevel
{
public override String ToString()
{
return this.ConfidenceDescription;
}
}
and than setting DisplayMember to the DataConfidenceLevel property of the entity you want to bind.
The answer was simpler than I was expecting.
comboBox.DataBindings.Add(new Binding("SelectedItem", this.dataBindingSource, "DataConfidenceLevel", true));
comboBox.DataSource = db.DataConfidenceLevel;
comboBox.DisplayMember = "ConfidenceDescription";
comboBox.ValueMember = "ConfidenceLevelID";
I wrote two blog entries about one approach to handling this situation - it applies to ASP.net, but it might help you out.
Here are the posts, the first one is more of an introduction to the problem, the second entry shows how to pin it all together.
I'm not sure whether this qualifies as "the correct way" but it's certainly an approach :) I'd be happy to hear back if this helps you out!
Edit: After reading danbruc's answer, you can certainly override ToString on the Navigation property as he has suggested (for read only), but that's only a partial answer.
This won't work unless your LINQ query contains the "Include" statement, e.g.
var listOfThings = (from t in db.Thingy
.Include("DataConfidenceLevel")
select t).ToList();
Omitting the .Include() means that nothing will get bound to the column.