Better way to update bound controls when changing the datasource - c#

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

Related

EndEdit on BindingSource updates DataTable, but rowstate still unchanged

I have a bindingsource which has a datasource which is a datatable.
All the winforms controls has added databindings to the bindingsource
I do a value change in the GUI (writes to the controls' .text property)
then on Save i do following
bsSending.EndEdit();
((DataRowView)this.bsSending.Current).Row) now contains the new values, but the RowState is still unchanged. How can this be possible? I haven't any calls to AcceptChanges() before I make the value changes in the GUI
UPDATE:
Don't know why, but it seems that calling the specific row's EndEdit does the trick. The row's parent is a datatable and the table's dataset is the datasource of the bsSending Datasource.
Calling bsSending.EndEdit() only updates the values but doesn't update the rowstate.
I have surfed the .net for similiar problems and they indicate that when calling AcceptChanges() on the dataset BEFORE binding data, then you may get this error (values updated to dataset but rowstate remains unchanged). I haven't seen any solutions to the problem, though, so I keep my workaround solution
I know it's old post.
It can be solved by calling DataRowView.EndEdit directly, but in my case I found the exact reason:
I accidentally bound two properties of one control to different columns.
(In my case I simultaneously bound Devexpress' TextEdit control - EditValue and Text properties to different columns of the underlaying table).
Maybe it will help someone even in 2016+, because it was nasty bug to catch.
I have a similar issue. I have a grid on the first tab page and textboxes on the second tab page, all binded to the same binding source.
I change the row content, the content get changed in the grid but the row start is Unchanged.
My code was:
DataRow dataRow = ((DataRowView)bindingSource1.Current).Row;
if(dataRow.RowState != DataRowState.Modified)
I expected to have a Modified rowState.
The missing code was:
bindingSource1.EndEdit();
Entire solution here:
private void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
if (tabControl1.SelectedIndex == 0)
{
bindingSource1.EndEdit();
DataRow dataRow = ((DataRowView)bindingSource1.Current).Row;
if(dataRow.RowState != DataRowState.Modified)
{
return;
}
DialogResult userOption = MessageBox.Show("Save?", "Confirm", MessageBoxButtons.YesNo, MessageBoxIcon.Question);
if (userOption == System.Windows.Forms.DialogResult.Yes)
{
Save();
}
}
}
My solution:
I have run into the similar situation in one form and I realized that problem was in binding made to labels ToolTip property. First I realized, that when I changed value for null from null to some string, the problem disappears, but after some other changes made to the project, the problem appeared again. When I deleted binding to the ToolTip property, problem disappeared again. Still I do not know if for ever.
#Jan Strnad 's answer enlightened me and helped me fix my issue.
Problem: I was using a binding source in win forms. When binding the controls in the form, I miss clicked and bound a field to the form's Text property in (DataBindings). The same bindingsource field was bound (correctly) to a combobox. Because of this double binding on the field, the rowstate never changed from Unmodified.
Fix: when rows don't update and you use a binding source, first check that the binds are correctly put in place.
Yes, I have the same experience. I am using Visual Studio 2010 and .NET Framework v.4.0. I am working with DataGridView control which is bound to a DataTable.
If the user tries to close the form while the cell value is still in edit mode, I want DataGrid to end the edit mode and ask the user whether he wants to save or lose the changes.
This is the code which works for me:
if (dgv.IsCurrentCellInEditMode)
{
dgv.EndEdit();
updatedData.Rows[dgv.CurrentCell.RowIndex].EndEdit();
}
if (updatedData.GetChanges() != null && updatedData.GetChanges().Rows.Count > 0)
{
// if there are changes, update the dataset
}
I want to point out that I needed to call both - EndEdit() on DataGridView to let it end the editing. Then call EndEdit() on the DataTable to mark the row 'modified'.
To commit row changes programmatically, call the form's Validate method. If your data source is a BindingSource, you can also callBindingSource.EndEdit.
see
IsCurrentRowDirty

WPF - How do I style a row based on a binding property value?

So I am trying to bind a collection of objects (IList<>) to a WPF datagrid. I would like to make the row background a different color if the 'artist' property is null or empty. I am checking the value stored at the property on the LoadingRow datagrid event. Currently my implementation seems to style all of the rows with an empty or null 'artist' property correctly. The problem is that is also styles the rows where the property is not null or empty in some cases. So some rows are given the red background even though the rows 'artist' property is not null. Can anyone tell me why this could be??
Here is the LoadingRow event:
private void trackGrid_LoadingRow(object sender, DataGridRowEventArgs e)
{
Track t = e.Row.DataContext as Track;
if (String.IsNullOrEmpty(t.Artist))
{
e.Row.Background =
new System.Windows.Media.SolidColorBrush(System.Windows.Media.Color.FromArgb(255, 255, 125, 125));
}
}
i use a view model in situations like these, it allows binding directly to the colour of the row.
check out the coloured rows sample project on this site
It seems like the easiest way for that to happen would be for the test to get called more than once for the same row, and the second time the value is no longer empty. I can't guess as to why that would happen, but in the meantime, it should be easy to test with:
else
{
e.Row.Background = DependencyProperty.UnsetValue;
}

How do you cache a row without raising a "Row provided already belongs to a DataGridView" error?

I want to cache a DataGridView row between 'refreshes' i.e. between a Rows.Clear() and a Columns.Clear(). However it seems that calling the Clear() methods does not unbind the data from the DataGridView instance, An example,
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
DataGridViewRow cachedRow = new DataGridViewRow();
private void button1_Click(object sender, EventArgs e)
{
this.dataGridView1.Rows.Clear();
this.dataGridView1.Columns.Clear();
DataGridViewColumn aColumn = new DataGridViewTextBoxColumn();
this.dataGridView1.Columns.Add(aColumn);
this.dataGridView1.Rows.Add(cachedRow);
}
}
This is done on a Form containing a DataGridView and a Button. Clicking the button twice gives the "Row provided already belongs to a DataGridView" error.
There has been some discussion online about this that suggests that it may be a bug, however this was around 2004.
Once a row is part of a gridview, you can't re-add it. The row itself keeps track of what DataGridView it is in. I would suggest making a copy of the cached row and adding the copy to the view. Since you make a new copy each time it won't be in the view. Alternatively, you can go through and remove only those rows that have not been cached from the view, leaving the cached rows behind so that you don't need to re-add it.
Clone it to a DataRow() and then DataTable.ImportRow to the originating DataTable.
I'm not sure why you'd want this behviour? You should only remove the rows in the grid that you want to remove.
You should look into implementing ObservableCollection and a Binding component. This way, if an item is removed from your object model, then it is automatically removed from the grid. This saves you having to perform what sounds like manual data binding and avoids this problem altogether.
If you're using DataSet or typed DataSets, then the observable functionality is already implemented for you - you just need to bind the datatable to the grid. If you have a table object in memory, you'll notice that you can load another one, then use the DataTable.Merge function to combine the results.

Sync a WinForm with DatagridView

I have a Form with a DataGridView which DataSource is a BindingSource to a table. This view will have a single row selection and a button to delete, edit the current selected row in a popup Form and a insert button that will use the same Form as well.
My question is how can I sync the pop Form with the current row?
I tryied to use the RowStateChanged event to get and store the current selected Row to be used in the Form but I coudnt. After the event I get the row that was selected before.
Other thing I dont understand yet in C# how to have a single recordSet and know wich is the current record even if its a new being inserted in a way that once in the Form all data being entered will show up at the same time in the DataGridView.
You don't have to sync the form with the current row. That's what a BindingSource is for.
When you do simple binding to a BindingSource, then every time its current item changes, the bound controls get updated, and every time the values in the bound controls change, the underlying values in the bound item get updated. When you do complex binding (i.e. the bound control displays the BindingSource's list, not just the current item), changing Position on the BindingSource will also change the current position in the bound control, and vice versa. So in this case, you want to bind the controls on your second form using simple binding, and the DataGridView on your first using complex binding.
The only unusual thing you need to do is make sure that both forms are using the same BindingSource. When you do that, clicking on a new row in the DataGridView updates the Position on the BindingSource, and the BindingSource pushes the values from the current bound item out to all of the simply-bound controls that are bound to it.
This is easily accomplished. Assuming that Form1 is the form with the DataGridView, and Form2 is the one with the simply-bound controls, do this:
In Form1:
private BindingSource Source = new BindingSource();
Form1_Load(object sender, EventArgs e)
{
// set Source's DataSource to your DataTable here.
mainDataGridView.DataSource = source;
// create DataGridView columns and their bindings here.
Form2 f2 = new Form2();
f2.TopMost = true;
f2.Source = Source;
f2.Show();
}
In Form2:
public BindingSource Source { get; set; }
public void Form2_Load(object sender, EventArgs e)
{
idTextBox.DataBindings.Add("Text", Source, "id");
descriptionTextBox.DataBindings.Add("Text", Source, "description")
}
You can easily keep it in sync, but not using BindingSource.
Windows Forms data binding is built on top of a few interfaces the most important are:
IList and IBindingList. The first one is just responsible for providing access to elements by their index in the list(to make it simple) the second one actually is more complicated.
IBindingList - (which is implemented by BindingSource) has methods to support:
change notification
adding new "empty" element
sorting
searching
The one that is important for you is of course change notification. Unfortunately BindingSource doesn't implement that bit of code. You may do 2 things here - either implement your version of BindingSource with change notification or try to mess with DGV and textboxes/comboboxes events to update the data.
Personally I've done the first one(I can share the code I have).
"Other thing I dont understand yet in C# how to have a single recordSet and know wich is the current record even if its a new being inserted in a way that once in the Form all data being entered will show up at the same time in the DataGridView."
Every form has a BindingContext object that keeps CurrencyManagers - one for each DataSource. That way every control bound to the same data source knows which record is current. Actually what BindingNavigator does is messing with the apropriate CurrencyManager and calling its methods. (I have no idea why it requires BindingSource instead of in the minimum IList or for full functionality IBindingList)

Controlling C# DataGridView with an Arraylist in VS2008

I'm having some problems with a datagridview element I'm using in VS2008.
This DataGridView is actually a tab in a TabControl element.
I gave it 5 colums which need to be filled up with elements from a costum Object i made.
It's basically a small library application which contains a main class and several classed derived from it. They all have a ToString() method which represents the data as a string of keywords containing the values needed for me to fill up the datagridview.
I only need the first 5 though, some objects will have up to 12 keywords.
Currently, Whenever I add an object, the datagrid doesn't fill itself, instead it adds an amount of columns equall to the amount of keywords the specific object has.
What i'm currently doing is this:
public void libDataGrid_Click(object sender, EventArgs e)
{
if(this.manager.Lib.LibList[0] != null)
{
libDataGrid.DataSource = this.manager.Lib.LibList;
libDataGrid.Refresh();
}
}
this.manager.Lib.LibList returns and ArrayList, in which all objects are stored. The ArrayList can contain elements of all derived classes, but since they are all connected, the string representation will always contain the elements I need to fill up the grid.
I don't see how I can filter only the first five and them have them put in the correct colums.
And another thing. Currently I can only refresh the DataGridView by clicking it. It should change on when I switch to it switch to its specific tab on the Tabcontrol I mean.
I tried adding an argument for SelectedIndexChanged, but that does nothing really...
Or at least, it doesn't appear to do anything.
What I mean is I commented out the code above and added this instead:
public void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
libDataGrid.DataSource = this.manager.Lib.LibList;
libDataGrid.Refresh();
}
This refreshes it everytime the tab is changed, no matter to which one.
I had to remove the if-statement, since it gave me an Exception. Probably because the length of the ArrayList isn't set on initialisation.
I'm a little confused by the question, but here are some thoughts:
DataGridView has an AutoGenerateColumns property; if you don't want it to create its own columns, set this to false
To bind to existing columns, the DataPropertyName must be set on each
DataGridView (in cmomon with any list control using TypeDescriptor) will hugely prefer List<T> (for some T != object) to ArrayList, since it can get meta-data even for an empty list. In general, in 2.0 using ArrayList is a mistake.
I can only give a partial answer but I think the reason that
public void tabControl1_SelectedIndexChanged(object sender, EventArgs e)
{
libDataGrid.DataSource = this.manager.Lib.LibList;
libDataGrid.Refresh();
}
isn't working, is because you need to add this line where tabControl1 is being initialized. I've had this problem where VS won't do this itself.
tabControl1.SelectedIndexChanged += new EventHandler(tabControl1_SelectedIndexChanged);
If I am understanding your problem, it seems similar to a problem that I was struggling with recently in this thread on DataGridViews in C#/.NET2.0
Try calling:
libDataGrid.Invalidate();
This should force Windows to redraw your control. No need to reattach the datasource and refresh. (I think you can safely comment out those 2 lines.)
Also: What was the Exception that you were getting?
And did you use the "Data Source Configuration Wizard" to help you with the dataGridView?

Categories