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.
Related
I have a NumericUpDown control, which is a part of a UserControl.
The UserControl has Value property:
[Browsable(true)]
public override double Value
{
get { return this.ControlValue; }
set
{
this.ControlValue = value;
InvokePropertyChanged(new PropertyChangedEventArgs("Value"));
}
}
I used DataBindings for the NumericUpDown:
NumericUpDown.DataBindings.Add(nameof(NumericUpDown.Value), this, nameof(UserControl.Value), false, DataSourceUpdateMode.OnPropertyChanged);
The Value property used to be Int32, but I had to change it to Double. And suddenly the binding stopped working.
I know for sure the Value property is changing, but the NumericUpDown's value doesn't.
Correction: it appears the Binding only fails to update NumericUpDown's value when the Value property is changed. Changing NumericUpDown's value DOES change the Value property.
So, the problem actually was not in the Binding or the NumericUpDown control, but rather in the "override" keyword. In this case, the binding was confused which property was changing - UserControl's Value or its base class. This was solved by using "new" instead of "override".
Still can't understand why I didn't have this problem earlier. This code is 4-5 months old and worked perfectly before I changed Value property type from Int32 to Double.
I tried to set DataSource of CheckedListBox like this:
private void Form1_Load(object sender, EventArgs e)
{
checkedListBox1.DisplayMember = "Name";
checkedListBox1.ValueMember = "Checked";
_bindingList = new BindingList<CustomBindingClass>(
new List<CustomBindingClass>
{
new CustomBindingClass {Checked = CheckState.Checked, Name = "Item1"},
new CustomBindingClass {Checked = CheckState.Checked, Name = "Item2"},
new CustomBindingClass {Checked = CheckState.Unchecked, Name = "Item3"},
});
checkedListBox1.DataSource = _bindingList;
}
And It's working but partially. I'm able to do the fallowing later
_bindingList.RemoveAt(0);
or _bindingList[0].Name = "TestTest"; and CheckedListBox updates well except items are not checked. This is not working
_bindingList[0].Checked=CheckState.Checked;
I also tested to do it when CheckedProperty from my CustomBindingClass is of type bool, but doesn't works either. Any suggestion what should be the type of ValueMember property ?
Consider these facts:
CheckedListBox does't have a built-in data-binding support for checking items. You need to handle check state of items yourself.
You set checkedListBox1.ValueMember = "Checked";. You didn't set item check state, you just said when you select the item, the value which returns by SelectedValue comes from Checked property of your object which is behind the seected item. For example you can use this code in a Click event of a Button to see the result; regardless of check-state of items, the message box, will show value of Checked property of the object behind the item:
MessageBox.Show(checkedListBox1.SelectedValue.ToString());
Selecting and checking items are completely different.
I prefer to use DataGridView for such purpose. You can simply have a CheckBox column and a readonly TextBox column and bind DataGridView to the list of your objects.
If you need to have two-way data binding, you need to implement INotifyPropertyChanged interface regardless of what control you are using to show data. If you don't implement that interface, when changing properties on your model ListChange event will not raise and you can not see changes in UI automatically.
If you take a look at CheckedListBox class, you'll notice that DataSource, DisplayMember and ValueMember are marked with
[Browsable(false)]
[EditorBrowsable(EditorBrowsableState.Never)]
This a common technique used in Windows Forms controls to indicate that some public properties inherited from a base class (hence cannot be removed) are not applicable for that concrete derived class and should not be used.
There must be a reason for doing that for the aforementioned properties of the CheckedListBox. As you already saw, it's "sort of working", but the point is that it isn't guaranteed to work at all. So don't use them. If you wish, create a helper class that holds CheckedListBox and BindingList, listens to ListChanged event and synchronizes the control.
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()
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.
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.