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.
Related
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()
The problem. I have a databinding to the fields within a property on my form for a large number of textboxes on the form itself. I am changing not just a field value within the property, but the entire property (resetting to a previously saved default).
What I wanted to do was something like this:
((CurrencyManager)BindingContext[Row]).Refresh();
Doesn't work, BindingContext[Row] is a PropertyManager, not a CurrencyManager, and doesn't have a refresh method. PushData() is protected and not accessable. I also tried calling this.Refresh(), and individually on each control I tried calling c.Refresh...Now What?
Here's the code Im currently using:
private void btnReset_ButtonClicked(object sender, EventArgs e)
{
Row = ResetRow.Clone();//use clone to break the reference, dont want to be updating the ResetRow when Row fields change.
foreach (Control c in pnlBody.Controls)
if (c.DataBindings.Count > 0)
c.DataBindings.Clear();
DataBindandSet();//re-apply all the databindings (50)
}
//just a sample of the databinding function itself
private void DataBindandSet()
{
txtAzimuth.DataBindings.Add(new NullableBinding("Text", Row, "Azimuth", true));
txtBHTemp.DataBindings.Add(new NullableBinding("Text", Row, "BHTemp", true));
//repeat 48 more times
}
Update
I set the databindings to the full databinding string, to see if setting the null value default had any effect, it didnt.
txtAzimuth.DataBindings.Add(new NullableBinding("Text", Row, "Azimuth", true, DataSourceUpdateMode.OnPropertyChanged, ""));
Row is a public property on the form itself. ResetRow is a private property on the form, its assigned to a clone of the initial row on Form Load and never changed, it serves as a backup. The value of some of the properties (but not all) is allowed to be null, and should display as an empty string. This is what the NullableBinding class does behind the scenes
Can anyone think of a better way to do this than removing and re-applying every single one of the databindings?
It's not a whole lot better, using Binding.ReadValue might save the hassle of unbinding and rebinding, assuming the object you are binding to remains the same:
foreach (Control c in pnlBody.Controls)
foreach (Binding b in c.DataBindings)
b.ReadValue();
If, on the other hand, you are changing the underlying bound object, this will not work. I suggest putting a BindingSource between the Bindings and the Row object, this way you only have to re-bind the binding source and not all 50 bindings individually. Either by a call to BindingSource.CancelEdit() or by resetting the data source:
RowBindingSource.DataSource = null;
RowBindingSource.DataSource = this.Row;
Try this one: TableAdapter.Fill(dataTable)
My example: this.bankingaccountTableAdapter.Fill(this.bankDataSet.bankingaccount);
I have a Silverlight DataGrid, not an asp Gridview, that gets populated just fine. I have added a checkbox column for the user to select which items they want to download. My goal is to create a 'cart' where the user can select their items, then click a button to add them. Then they will go to a checkout page etc..
My problem so far is that I am having trouble checking to see if the user checked a check box or not. My foreach loops through fine I think, but I get a 'nullreferenceexception' in the bool IsChecked line. During a breakpoint, none of the values came back as null that I saw but it obviously isnt working the way I hoped.
foreach (var row in gridResults.ItemsSource)
{
bool IsChecked = (bool)((CheckBox)gridResults.Columns[8].GetCellContent(row)).IsChecked;
if (IsChecked)
{
List<string> lstFile = new List<string>();
string fileName = (gridResults.SelectedItem as JobSearchResult).FileName;
lstFile.Add(fileName);
}
}
Through the filename, I can find the files on the server for them to download, but How can i check if they checked the ones they wanted or not?
Without knowing precisely how you are populating your datagrid, it is difficult to tell you what the exact cause of your issue is. But I will tell you how I would handle this situation.
Create an ObservableCollection of your object type. Set the ItemsSource of the DataGrid to the ObservableCollection. Then, in the data grid row, create a binding for the IsChecked property of the checkbox to some public boolean value on your object (perhaps call that IsSelected). Make sure to set the binding mode to TwoWay.
Then, all you have to do is use a linq query on the ObservableCollection. Remember that Silverlight is a presentation layer, and you should not rely upon it at all to perform business logic.
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.