adding records, editing deleting - winforms

Can anyone give me what events to handle for syncfusion grid control.
In the context menu, if add record is chosen the the user must be able to add and then save it in the db
thanks
sun

If it is the ContextMenuStrip through which you bounded the context menu to the grid, you can handle the ItemClicked event of it and handle updates to the bounded data source. GridGroupingControl, by default, reflects the changes made in the underlying datasource provided the datasource supports notification on any changes(like IBindingList, IEnumerable, etc.,).
The following code illustrates this considering the bounded datasource as DataView.
// Event Handler
this.gridGroupingControl1.ContextMenuStrip.ItemClicked += new ToolStripItemClickedEventHandler(ContextMenuStrip_ItemClicked);
// Method Invoked
void ContextMenuStrip_ItemClicked(object sender, ToolStripItemClickedEventArgs e)
{
DataTable dt = (this.gridGroupingControl1.DataSource as DataView).Table;
DataRow dr = dt.NewRow();
for (int i = 0; i < dt.Columns.Count; i++)
dr[i] = 0; //default value
dt.Rows.Add(dr);
}
For datasource types(like ArrayList), that don't raise notification on its own for any change on it, once the update to the underlying datasource have been handled, the following code can be handled to refresh the GridGroupingControl inorder to reflect the updated view.
this.gridGroupingControl1.Reinitialize();

Related

Manually adding a new row to DataGridView does not immediately update the bound DataTable

I have a c# windows form app written in .net-4.0
I have a datagridview (dgvItems) which I programmatically bind to a dataset and datatable at the end of an import function.
I have an import button that imports data from a selected excel file into a table called _dtItemAdjustList, once the data has been imported I bind that table to my grid. The code I use to bind the grid is:
dgvItems.DataSource = _dsQtyAdjust;
dgvItems.DataMember = "Item Adjust List";
dgvItems.Refresh();
Everything works fine so far, if I edit the imported data in the datagridview, it updates the bound table _dtItemAdjustList after each cell is edited.
My issue comes in when I try to manually add a row to my datagridview, it doesn't immediately add that new row to the bound datatable.
Example: I put a break point at the end of my dgvItems_CellValueChanged event and added a _dtItemAdjustList.Rows.Count watch and here is what happens.
After the data is imported and I edit one of the existing imported lines the watch shows the correct row count, lets say 5.
Now I click the last * row, and type something into my item# column and hit tab, the break point fires but my row count still only shows 5, I fill out a few more cells in the new row but each time I leave a cell and my CellValueChanged event fires and the row count remains at 5.
Next I add a second manual row, now immediately after I tab out of the first cell I filled out for this second new row, my row counter goes to 6, but technically at this point I've added 2 manual rows to my imported 5, so the counter should read 7
This repeats, basically each new manually added row to the datagridview isn't added to the bound datatable until either a) another row is added or b) I go back and re-edit a cell on an existing row.
Edit
Forgot to mention I tried binding my DataGridView two ways:
DataSource = _dsQtyAdjust //dataset Name
DataMember = "Item Adjust List" // Table Name in the dataset
DataSource = _dtItemAdjustList
It made no difference as far as my new row behaviour goes.
Ok after a lot of trial and error and online searching I found a solution that seems to work.
Instead of binding my DataGridView directly to my DataTable, I created a BindingSource, bound the BindingSource to my DataTable, then bound my DataGridView to the BindingSource and finally in my dgvItems_CellValueChanged event I used a combination of EndEdit() and NotifyCurrentCellDirty(true/false) to make it work.
Code sample:
public partial class frmQuantityAdjustment : Form
{
// In my forms partial class I created a new public BindingSource
public BindingSource bsItemAdjust = new BindingSource();
}
private void frmQuantityAdjustment_Load(object sender, EventArgs e)
{
// In my form_Load event I bound the BindingSource to my DataTable
// and then the DataGridView to the BindingSource
bsItemAdjust.DataSource = _dtItemAdjustList;
dgvItems.DataSource = bsItemAdjust;
dgvItems.Refresh();
}
private void dgvItems_CellValueChanged(object sender, DataGridViewCellEventArgs e)
{
// Finally at the end of my CellValueChanged event I added the
// following code
bsItemAdjust.EndEdit();
dgvItems.NotifyCurrentCellDirty(true);
dgvItems.EndEdit();
dgvItems.NotifyCurrentCellDirty(false);
}
I came across this solution on this thread and after some testing it seems to work perfectly.
Now the new row in my DataGridView is added to the bound DataTable at the end of the CellValueChanged event.

"BindingSource cannot be its own data source" - error when trying to reset the binding source from a method in another class

We are binding a DataGridview using BindingSource. So in the main thread we have given like this.
class1BindingSource = new BindingSource();
class1BindingSource.DataSource = class1List;
this.dataGridView1.DataSource = class1BindingSource;
After that i have a placed a background worker in the form and is triggering in a button click.
i.e. in the button click
this.backgroundWorker1.RunWorkerAsync()
In the BackgroundWorker DoWork Event i am trying to update the BindingSource and there by trying to update the DataGridview.
So the BindingSource reset is done in a method in another class.
DoWork Event
Class2 cl2 = new Class2();
cl2.UpdateBindingSource(class1BindingSource);
UpdateBindingSource Method
public void UpdateBindingSource(BindingSource bs)
{
Class1 c1 = bs.Current as Class1;
for (int i = 0; i < 1000; i++)
{
lock (bs.SyncRoot)
{
c1.MyProperty1 = i;
bs.ResetItem(0);
}
}
}
Now i am getting an exception like BindingSource cannot be its own data source. Do not set the DataSource and DataMember properties to values that refer back to BindingSource.
If i am doing this in my DoWork Event then i can reset the item in the control thread itself using BeginInvoke method.
But actually i am trying to simulate our application scenario. So i want to solve this in this format.
Can any one help me on this.
The problem is that you can't update a BindingSource within a thread other than the gui thread. This is due the fact, that the BindingSource will fire some events which will then be received by your data grid view which will then start to update itself, which will fail cause it won't be done on the gui thread.
So right before you call RunWorkerAsync() you should call class1BindingSource.SuspendBinding() and within your RunWorkerCompleted you should call class1BindingSource.ResumeBinding().
Also ensure that within your DoWork you won't call any methods on the binding source (like you did with bs.ResetItem(0)).
And also remove this lock statement. It simply doesn't make any sense (in your example) and if you really need it (in your real code) consider using some private object _Gate = new Object(); within your class to avoid any deadlocks from the outer world, cause bs.SyncRoot is publicly available.
I had the same problem:
- BindingSource that had elements with INotifyPropertyChanged
- A separate Task that updated the elements.
The suggested solutions SuspendBinding etc didn't work. BindingSource should have done something like IsInvokeRequired.
Luckily Ivan Stoev came with the brilliant idea of subclassing the BindingSource and do something similar as IsInvokeRequired. Thank you Ivan!
Link: Update BindingSource from a different Task
UpdateBindingSource() does not take much time, so no need to use backgroundworker. You can invoke UpdateBindingSource() in the main thread.
Also, keep datagridview manipulation in the main thread.

Perform actions after WPF DataGrid data is completely bound

I have an old Windows Forms application at work which I am converting to WPF. As part of this, there is a button which, when clicked, creates a brand new DataGridView and adds it to the page, binding it to data from a SQL query. This data is bever written back to the database, but a new column is added to the end of the data with a checkbox on it, and when the checkbox is changed the ID from the row is passed into another method along with the state of the checkbox.
In WPF, I have the dynamic grid creation working, and the data binding. Having found I couldn't directly add the column to the DataGrid itself, I've added it to the source DataSet table before binding. This works and whos all the data along with a checkbox for each row. However, I can't get an event to fire when the an individual checkbox is clicked.
I have managed to find some code at http://forums.silverlight.net/t/11547.aspx, which causes the row to commit whenever the checkbox is changed, as my code to pass the ID and bool value currently resides in an EditEnding event. Initially the UpdateSourceTrigger was added to each checkbox by looping over each row in the checkbox column during the "Loaded" event of the DataGrid, but this is now failing, and after some internet searching it seems to be because "Loaded" doesn't guarantee that the data has finished binding, and I'm finding that DataGrid.Items contains more items than there are containers in the grid at the point the event fires. Below is the code I'm trying to use to bind the UpdateTrigger, but at row 32 suddenly the ContainerFromIndex method returns null. There are 69 items in dgvNew.Items at that point.
dgvNew.Loaded += new RoutedEventHandler(delegate(object sender, RoutedEventArgs e)
{
for (var i = 0; i < dgvNew.Columns.Count; i++)
{
DataGridBoundColumn column = dgvNew.Columns[i] as DataGridBoundColumn;
if (column != null && column.Header.ToString() == "HasPermission")
{
for (var j = 0; j < dgvNew.Items.Count; j++)
{
DataGridRow row = (DataGridRow)dgvNew.ItemContainerGenerator.ContainerFromIndex(j);
UpdateSourceTriggerHelper.SetUpdateSourceTrigger(column.GetCellContent(row), true);
}
}
}
});
I've also tried wrapping the for loop up in an event handler for the ItemContainerGenerator.StatusChanged and checking that Status is ItemsGenerated, but that didn't help either.
Can anyone see where I'm going wrong, in code or in understanding?

WPF DataGrid ItemSource Refresh - GridColumns shows strange behaviour

I am developing WPF UserControl based on WPF DataGrid, to support dynamic column generation with our own business based context menu.
I've created Dependency Property called DataSource, when I set DataSource calling a custom method to Bind my dataSource to Create columns on the fly and set ItemSource property. All works fine the first time. I have a context menu called Refresh, while the user clicks Refresh the SQL will execute and the same cycle of the above-mentioned actions will happen. During the second time, the rows and columns are created perfectly. But when I do Horizontal scroll the Column headers are NOT showing properly, it loses their visual state while scrolling.
My Custom Property - DataSource
public static DependencyProperty DataSourceProperty =
DependencyProperty.Register("DataSource", typeof(GridDataModel), typeof(MyGridView),
new PropertyMetadata((dependencyObject, eventArgs) =>
{
if (eventArgs.OldValue != null)
{
((GridDataModel)eventArgs.OldValue).Dispose();
}
BindToDataSource((MyGridView)dependencyObject, (GridDataModel)eventArgs.NewValue);
}));
My Custom method which is calling everytime I set DataSource property:
private static void BindToDataSource(MyGridView view, GridDataModel dataModel)
{
if (view.ViewModel != null)
{
BindingOperations.ClearAllBindings(view.GridView);
view.GridView.Items.Clear();
view.GridView.Columns.Clear();
view.GridView.ItemsSource = null;
view.ViewModel.Dispose();
}
view.ViewModel = new MyGridViewModel(dataModel);
view.ViewModel.PrepareGridView();
view.LayoutRoot.DataContext = view.ViewModel;
view.CreateColumns();
view.GridView.SetBinding(DataGrid.ItemsSourceProperty, new Binding("DisplayRows"));
}
The Below code I used to call on Refresh Menu Click:
private void OnRefreshClick(object sender, RoutedEventArgs e)
{
var data = new TestDataAccess();
DataSource = data.MakeGridModel("select Top 200 * from ApplicationUSer"); //Assigning DataSource Again, which will call the above method.
GridView.UpdateLayout();
}
After refresh, you could see the column alignment goes strange when doing the horizontal scroll.
Tried using GridColumnWidth =0, and setting again to Auto, Tried GridView.UpdateLayout().
I solved the above problem my self.
Instead of BindingOperations.ClearAllBindings() i used BindingOperations.ClearBinding(view.GridView, DataGrid.ItemSourceProperty) - which cleared out only ItemSource so that i can regain memory by Items.Clear() for every time i bind the data.
Due to ClearAllBindings, its clears headers panel bindings also, so its looses ParentTemplate.Width property, because of that strange problem happend during horizontal scroll.

Accessing controls in newly added listview row WPF C#

I have a listview in WPF and C# that uses an ObservableCollection to populate the data. When a certain event is fired, I call a function to insert a new row into the collection. I then need to access the controls in that row.
The problem i am having is that after i insert the row, and I try to loop through the listview rows, this row is displaying as null in my loop.
if (addNewNote)
{
_resultsCollection.Insert(0, new ResultsData
{
Notes = "Data to Insert",
// ... rest of fields
});
} // end if (addNewNote)
for (int currRowIndex = 0; currRowIndex < this.ResultsList.Items.Count; currRowIndex++)
{
System.Windows.Controls.ListViewItem currRow =
(System.Windows.Controls.ListViewItem)this.ResultsList.ItemContainerGenerator.ContainerFromIndex(currRowIndex);
if (currRow != null)
{
System.Windows.Controls.TextBox tb = FindByName("EditNotesTextBox", currRow) as System.Windows.Controls.TextBox;
// do stuff with controls ...
}
}
Here currRow is always null, when the currRowIndex is zero.
This should not be the case, because i have just added it. Is it because I am trying to access it in the same function where I insert it, and the listview has not yet been updated? I am able to access every other row in the listview. Is there a better solution? Thanks!
In order to newly added item to be accessible first the container for that item (UI) needs to be generated by ItemContainerGenrator. The generation is done on the same (UI) thread, so you cannot access containers right after you have added an item.
You can subscribe to ResultsList.ItemContainerGenerator.StatusChanged event and in the event handler check if the status is GeneratorStatus.ContainersGenerated then you can obtain the container.
ResultsList.ItemContainerGenerator.StatusChanged += OnGeneratorStatusChanged;
...
private void OnGeneratorStatusChanged(object sender, EventArgs e)
{
if (MyListBox.ItemContainerGenerator.Status == GeneratorStatus.ContainersGenerated)
{
// Access containers for newly added items
}
}
Please refer to this article by Dr.WPF that describes how it works in detail.

Resources