Winforms derived combobox property two way binding - c#

I created custom ComboBox control and want to bind custom property "ActiveValue" to a DataSet. I do it in the way:
cboMyComboBox.DataBindings.Add(New System.Windows.Forms.Binding("ActiveValue", Me.dstDetails, "Table.CBOVALUE", True, System.Windows.Forms.DataSourceUpdateMode.OnPropertyChanged, ""))
...
Public Property ActiveValue As String
Get
Return _activeValue
End Get
Set(value As String)
If _activeValue <> value Then
_activeValue = value
Me.Text = _activeValue
End If
End Set
End Property
It retrieves value from DataSet, but it is unable to update. It doesn't matter what value I choose it simply is not updated. This property is simple text field. Tried to implement INotifyPropertyChanged on my derived ComboBox class, but it not helped. Could someone tell me where is the problem? Thanks
UPDATE:
found a bug in my class but Rex provided databinding write methods is also helpful, thanks for your time.

Not sure why as I cannot see your full implementation, but if you really want to force the databinding to write value back to object, try DataBinding.WriteValue(), so in your ComboBox class, do this at appropriate place (probably at some text changed event handler):
theDataBinding = Me.DataBindings(theIndex) ' you may find the binding by the bound field name
theDataBinding.WriteValue()

Related

Databinding issue with custom combobox

I've created a custom read only combobox that works for the most part. However when I download data from an external source, and set up data binding there is some very odd behaviour. Please look at the code below:
cboGender.DataSource = Animal.SpecificGenders;
cboGender.DataBindings.Clear();
cboGender.DataBindings.Add("text", animal, "Gender");
((ReadOnlyComboBox)cboGender).Readonly = true;
When the above line hits, it calls this property:
public bool Readonly
{
get
{
return readOnly;
}
set
{
textBox.Text = this.Text;
ShowControl();
readOnly = value;
}
}
Now, my issue is that when I set ReadOnly to false, this works fine. The 'this.Text' in the above setter shows the value that has been bound to it. However when I set the Reaonly to true, the 'this.Text' shows an empty string. This is the only difference I make. The this.Text refers to the standard Combobox Text property.
Does anybody have any ideas?
The base ComboBox has a DropDownStyle property that can be set to DropDownList which essentially makes the ComboBox read only. Maybe you could do that instead of implementing your own ReadOnly property.
If not, I suspect your problem exists in the ShowControl() method. Even though you're first setting the textBox.Text property, something in the ShowControl() method is preventing the control from updating. And, actually, setting another property from this Property doesn't seem quite right to me.
You're already using DataBinding, so setting the Text property should already be handled elsewhere.

C# Winforms DatagridviewCombobox exception String cannot be converted to class

I am encountering an exception when selecting a new value from a datagridviewcombobox(dropdown menu) control embedded in a datagridview. The combobox is populated by a BindingSource, which is populated with instances of my class. I can display the options in the menu properly, and select one, but changing focus to a new control (committing the change I guess) causes an exception to appear: Invalid Cast from System.String to myclass. The stack trace (if Im using that word right)shows the source was
System.Windows.Forms.DataGridView.PushFormattedValue
cascading down to
System.Convert.DefaultToType
A more explicit explanation is below (sorry its so long, but I wanted to make it reproducable):
I have an empty class called Occupant, with no properties(the problem exists when Occupant also has a string Name property so it's not that). I have a BindingSource called OccupantSource, with its DataSource pointing to Occupant.
I also have a class called Car, with one Occupant property called Driver.
In my Form_Load(), I call OccupantSource.AddNew() twice, and call CarSource.AddNew() once.
I have a DataGridView control, whose DataSource is CarSource (the BindingSource consisting of Cars). The DGV has one column, displaying the Driver property of the cars in CarSource. It is a DataGridViewComboBoxColumn, with DataPropertyName set to driver.
So what I want is to show rows of cars in the Datagridview, with one of the columns being a combobox I can dropdown and choose a driver from existing instances of Occupant. But I get the exception.
Is this something I'm misunderstanding? Can you not use instances of a class to populate a DataGridViewComboBox?
I ran into exactly the same problem and was scratching my head, using my google-fu for hours attempting to solve it. This link helped me finally gave me a good explanation.
http://www.pcreview.co.uk/forums/datagridview-combobox-column-error-listing-objects-t2344961.html
The way I fixed it was to change up the DisplayMember.
I had a refernce to 'Self' on the class that returned 'this' - I was using this for both DisplayMember and ValueMember thinking that it would just ToString() the property from DisplayMember.
Reading your explanation, you might not have DisplayMember and ValueMember set at all? If this is the case, try setting them correctly (and don't use a reference to 'this' for display member!) and it might fix it.

How to bind a List of a DataObject to a Grid with BindingSources?

In an assembly I created a class like the following:
[DataObject(true)]
public class A
{
public int Foo{get;set;}
[DataObjectMethod[DataObjectMethodType.Select)]
public static List<A> GetAllA(string ConnectionString)
{
// return filled List<A>
}
}
Now I want to display this List with a Gridcontrol under Winforms. I though of a DataGrid.
Though I'm coming from ASP.net I'd first think of
this.dataGridView1.DataSource = A.GetAllA(ConnectionString)
Works, but I'd prefer a better databinding with BindingSources. (Because I've always heard that thats the way to go)
I managed to drop a BindingSource onto the form and set the DataSource property to class A.
But where can I set the SelectMethod and its parameters? If I set DataSource property of the dataGridView to the BindingSource, it will only display an empty line.
Is this the right way to go? Will it only require some additional clicks in the wizard, or do I need to read tons of documentation to get this working?
Edit: Is there even a way to achieve automatically binding to my select method? Or does the BindingSource only supports mapping the columns, but not actually binding the data, meaning I'm required to set the DataSource property nevertheless?
You need to create a DataSource. Click "Data" menu and select "Add New DataSource..."
Connecting to Data in Visual Studio Overview
http://msdn.microsoft.com/en-us/library/wxt2cwcc(VS.80).aspx
To connect your application to data in
a database, Web service, or object,
run the Data Source Configuration
Wizard
by selecting Add New Data Source from
the Data Sources
Window.
Public Class A
Private _field As String
Public Property Field() As String
Get
Return _field
End Get
Set(ByVal value As String)
_field = value
End Set
End Property
End Class
Public Class AListing
Inherits List(Of A)
End Class
Use AListing as the object when adding a data source. Good for grid views or detail forms that provide navigation. It is up to you to populate it.
Use A as the object when adding a data source. Good for a dialog when you only need to bind to one instance. It is up to you to populate it.
A DataSource just helps the designer configure data binding. You still have to fill the objects. If you do not care about designer support, calling as you do is fine. Using a BindingSource just allows you to use an object like a "data table". Using your example, if I use a BindingSource, I could handle the CurrentChanged event for any additional processing.
this.dataGridView1.DataSource = A.GetAllA(ConnectionString);
//-or-
this.bindingSource1.DataSource = A.GetAllA(ConnectionString);
Have class A retrieve the connection string from the configuration file rather than as a parameter on the GetAllA method. Once your method has no parameters it should be possible to select it in the wizard.

A ComboBox Data Binding Question

I have an interesting data binding question related to combobox. Hope someone has some good suggestion.
I have a simple form, it contains a file picker and a comboxbox. Every time a file is picked, I read a list of strings from the file to a List object and I set comboBox.DataSource = listOfStrings.
In the form load event, I set comboBox.DataBindings.Add("SelectedItem", myObject, "PickedValue");
The purpose is clear: every time a string is selected from the combobox, I want to write the string to myObject.PickedValue.
That is the whole story.
Now I launch the form, rather than go pick a file, I check the combobox first. Of course, at this point, comboBox.DataSource is null, comboBox.SelectedItem is null, too. But the data binding on the comboBox is already setup (since the setting is in form load event). Now my focus cannot be moved from the combobox to anywhere else.
I think the reason is, when I try to check the combobox, it has null as SelectedItem. When I try to move the focus to somewhere else, the data binding of the combobox is triggered. Underlying, it tries to convert the selected item to string and update myObject.PickedValue with that converted string. Since you cannot convert a null to a string, the data binding validation fails, and the validation mechanism doesn't allow my focus to be moved elsewhere and I am sucked at this moment, cannot even move to pick a file.
My question is, what is the normal binding setup work-flow for my application scenario to prevent this trap? What is the correct order of setting up such a data binding so I can check my combobox before its data source is filled by something?
FYI, I tried to bind myObject.PickedValue to SelectedText property of the combobox (I noticed that SelectedText is a string and never be null, even when SelectedItem is null). But interestingly, even if I select something from the combobox, SelectedText is still empty string when data binding is triggered. What's wrong here?
Thanks for any help.
The failure is a little simpler than you describe: Your ComboBox will fail just because there is no selected item, because there's nothing to select from.
I would just disable the ComboBox if there's nothing to select from. It's pretty easy to do. Remember to hook up a PropertyChanged event in your data object; the binding source will find it automatically with reflection.
class MyData
{
public event PropertyChangedEventHandler PropertyChanged;
// ...
public HasListOfStrings { get { return ListOfStrings != null && 0 < ListOfStrings.Count; } }
private void LoadListOfStrings
{
// ... load the list of strings ...
if ( PropertyChanged) {
PropertyChanged(this, "ListOfStrings");
PropertyChanged(this, "HasListOfStrings");
}
}
}
In the designer, bind the 'Enabled' property of the 'ComboBox' to the HasListOfStrings property. You can do it in code with:
listOfStringsComboBox.Bindings.Add ("Enabled", bindingSource, "HasListOfStrings");
I also recommend you change the AutoValidate property of the container (or container's container) to EnableAllowFocusChange.
This doesn't seem right; it should be possible to set a string property to null. Possibly the focus problem lies elsewhere. Have you tried setting a breakpoint on your property setter to confirm your theory?
The SelectedText property of a combo box refers to text that has been selected in the text portion of the combobox. This only works if the dropdown style is set to combo. Basically it's the selected text of the text box portion of the combo control (the reason a combobox is called "combo" is because it is a combination of a textbox and a selection list). You would ordinarily expect this property to be empty unless the user was editing the text portion of the combo.
If you want a workaround for this problem that is consistent with a good user experience, try disabling the combo box on form load, then enabling it when a file is picked.

How do I use databinding with Windows Forms radio buttons?

I have a binary field in my database that is hard to describe in a UI using a single "Is XXXX?"-type checkbox. I'd rather use a pair of radio buttons (e.g. "Do it the Foo way" and "Do it the Bar way"), but right now all the other fields on my form are data-bound to a business object. I'd like to data-bind the pair of radio buttons to the business object as well, but haven't come up with a good way to do it yet. I can bind one of the buttons to the field, such that the field is set "true" if the button is selected, but while selecting the other button does de-select the first one (that is, the two radio buttons are properly paired), the value of the field does not update to reflect this.
I'd like to be able to say
button1.DataBindings.Add(new Binding("checked", source, "useFoo"));
button2.DataBindings.Add(new Binding("checked", source, "!useFoo"));
but I'm pretty sure that will throw when it runs. Is there an easier way, or should I just put more thought into how to word a single checkbox? I don't want to add extra functions to handle something this trivial...
ETA: A commenter has suggested considering a dropdown (ComboBox). I had thought about this, but how would I data-bind that to a boolean field in a database/Property in a business object? If I bind the SelectedItem to the useFoo property, what would go in the Items collection? Would I have to add just "True" and "False", or could I somehow add a key/value pair object that ties a displayed item ("Use Foo" / "Do Not Use Foo") to the boolean value behind it? I'm having trouble finding docs on this.
About the answer: the solution I wound up using involved modifying the business object -- the basic idea is very similar to the one posted by Gurge, but I came up with it separately before I read his response. In short, I added a separate property that simply returns !useFoo. One radio button is bound to source.UseFoo, and the other is bound to source.UseBar (the name of the new property). It's important to make sure the new property has both getters and setters, or you'll wind up with really odd behavior.
Bind the RadioButton that is directly linked to your boolean value (ie is checked when the value is true).
Add an event handler to the CheckedChanged event on this RadioButton that looks like the following :
private void radioButton_CheckedChanged(object sender, EventArgs e)
{
foreach (Binding b in ((Control)sender).DataBindings)
b.WriteValue();
}
I have found a way of doing this using DataSet/DataTable.
I make a calculated column in the DataTable with the expression IIF(Foo=true, false, true). Let's call that column Bar.
Bar is of type Boolean. Now you can bind one RadioButton.Checked to Foo and one to Bar.
To get Bar checking/unchecking to propagate back to Foo you must go to the generated DataTable code and add one line, the last one in this sample:
[global::System.Diagnostics.DebuggerNonUserCodeAttribute()]
public bool Bar {
get {
try {
return ((bool)(this[this.tableradio.BarColumn]));
}
catch (global::System.InvalidCastException e) {
throw new global::System.Data.StrongTypingException("The value for column \'Bar\' in table \'radio\' is DBNull.", e);
}
}
set {
this[this.tableradio.BarColumn] = value;
this[this.tableradio.FooColumn] = !value;
}
}
If your business object implements INotifyPropertyChanged (which makes binding work nicer), you can add the following code to the visual interface where BO is your business object and is declared withevents and BO.Value is the boolean property you would like to bind to.
Public Property NotValue() As Boolean
Get
Return Not BO.Value
End Get
Set(ByVal value As Boolean)
BO.Value = Not value
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("NotValue"))
End Set
End Property
Private Sub BO_PropertyChanged(ByVal sender As Object, ByVal e As System.ComponentModel.PropertyChangedEventArgs) Handles BO.PropertyChanged
If e.PropertyName = "Value" Then
RaiseEvent PropertyChanged(Me, New PropertyChangedEventArgs("NotValue"))
End If
End Sub
The following bindings will hook up the radio buttons.
RBTrue.DataBindings.Add(New Binding("Checked", Me.BO, "Value", False, DataSourceUpdateMode.OnPropertyChanged))
RBFalse.DataBindings.Add(New Binding("Checked", Me, "NotValue", False, DataSourceUpdateMode.OnPropertyChanged))
The visual interface should also implement INotifyPropertyChanged. This method works both ways: the original value gets updated if the interface changes and if the original value changes the interface will update correctly.
I came across the same problem, and found out it's actually possible with standard databinding:
button1.DataBindings.Add(new Binding("checked", source, "useFoo", false, DataSourceUpdateMode.OnPropertyChanged);
// no need to databind button 2, since button1.checked
// is set to false when button2 is checked
By default, DataSourceUpdateMode is set to OnValidation. By setting it to OnPropertyChanged, the change is propagated immediately to the databound object.
My business object implements INotifyPropertyChanged;
By testing this solution, I found out that my OnPropertyChanged event was fired twice when I was clicking button2. To prevent this, simply set
button1.CausesValidation = false;
button2.CausesValidation = false;
as shown here:
TextBox leave causes PropertyChanged get fired twice
Do it manually in code. When you load the form set your radio buttons according to your database. When you press a "save" button store de state as you wish. This example stores radios as bit fields in the database.
// Load radio state
radioButton1.Checked = ((myModel)myBindingSource.DataSource).boolOption1;
radioButton2.Checked = ((myModel)myBindingSource.DataSource).boolOption2;
-
// Save radio state
((myModel)myBindingSource.DataSource).boolOption1 = radioButton1.Checked;
((myModel)myBindingSource.DataSource).boolOption2 = radioButton2.Checked;
I tried to drag items from the "Data Sources" panel in Visual Studio 2015 when I defined my radio buttons as booleans in the table but there is issues with the booleans not being updated to false when another radio button is selected. When changing the "causesValidation" to false radiobuttons unchecked automatically when jumping between other text input fields.
You should only have to bind one of the controls as long as you make sure they are in the same group.
You could also consider a DropDown.

Categories