I have an EventHandler DonorUpdatedHandler() in a c# WinForm that updates a datagridview through a method RefreshGridData() in which I assign the datasource again. The RefreshGridData() is called when event fired but the grid never updates. BTW, RefreshGridData() works fine from a Form.Activated event handler. Can you suggest why?
private void gridDonors_CellContentClick(object sender, DataGridViewCellEventArgs e)
{
var sendergrid = (DataGridView)sender;
if(sendergrid.Columns[e.ColumnIndex] is DataGridViewButtonColumn && e.RowIndex >=0)
{
int id = (int) sendergrid.Rows[e.RowIndex].Cells["id"].Value;
frmAddDonor frmDonor = new frmAddDonor();
frmDonor.editMode = true;
frmDonor.DonorUpdated += new System.EventHandler(this.DonorUpdatedHandler);
frmDonor.Show();
frmDonor.LoadRecordById(id);
}
}
private void RefreshGridData()
{
BindingSource source = new BindingSource();
BindingList<Donor> list = new BindingList<Donor>(donorModel.all());
source.DataSource = list;
gridDonors.DataSource = source;
}
private void DonorUpdatedHandler(Object sender, EventArgs e)
{
RefreshGridData();
}
I have figured out the problem. Nothing wrong in the code. It was the issue of synchronization of writes and reads with the Jet OLE DB Provider. I was writing and reading through different OleDbConnections. I have used a Singleton and that apparently solved the problem. Thanks.
Details:
https://support.microsoft.com/en-us/help/200300/how-to-synchronize-writes-and-reads-with-the-jet-ole-db-provider-and-a
Related
My DataGridView sorting method does not work and would not be used wiht the compiler.
Where i use dgv:
public void LoadData(IList conTable)
{
var mtc = new Conversions();
dgvDetailedTable.DataSource = null;
dgvDetailedTable.DataSource = mtc.ToSortableBindingList(conTable);
dgvDetailedTable.RowTemplate.Height = UiConsts.RowHeight;
}
The Sorting event:
private void DgvDetailedTable_Sorted(object sender, EventArgs e)
{
var itemsToSelect = new MisTable[_selectedDetailedItems.Length];
_selectedDetailedItems.CopyTo(itemsToSelect, 0);
DgvOperations.MarkSelectedItems(dgvDetailedTable, itemsToSelect);
}
I hope this help, but I'm not sure you should use Sorted event for it.
You can use this code below at the end of binding and when you add a new item it will sort properly again.
public void LoadData(IList conTable)
{
var mtc = new MisTableConversions();
dgvDetailedTable.DataSource = null;
dgvDetailedTable.DataSource = mtc.ToSortableBindingList(conTable);
dgvDetailedTable.RowTemplate.Height = UiConsts.RowHeight;
// Use sorting here
this.DgvDetailedTable.Sort(this.DgvDetailedTable.Columns["Name"], ListSortDirection.Ascending);
}
I am attempting to write code that will display data in a datagridview based off of the size of car selected in a combo box. When this code initially runs, it defaults to economy sized, and displays the correct information in the datagridview. However, when a different size is selected in the combo box, the text boxes update correctly while the datagridview remains the same. What can I do to make it update every time the combo box is changed? I thought the code in "private void cboSize_selectionChangeCommitted()" would accomplish this, but there was no change in the output.
namespace carForm
{
public partial class Form1 : Form
{
_Cars_1_DataSet cDataSet;
BindingSource sizeBindingSource;
BindingSource vehicleBindingSource;
CarsDataClass clsCarsData;
Boolean gridInitialized;
public Form1()
{
InitializeComponent();
}
private void exitToolStripMenuItem_Click(object sender, EventArgs e)
{
this.Close();
}
private void Form1_Load(object sender, EventArgs e)
{
// TODO: This line of code loads data into the '_Cars_1_DataSet.Reservations' table. You can move, or remove it, as needed.
this.reservationsTableAdapter.Fill(this._Cars_1_DataSet.Reservations);
// TODO: This line of code loads data into the '_Cars_1_DataSet.Vehicle' table. You can move, or remove it, as needed.
this.vehicleTableAdapter.Fill(this._Cars_1_DataSet.Vehicle);
// TODO: This line of code loads data into the '_Cars_1_DataSet.CarSize' table. You can move, or remove it, as needed.
this.carSizeTableAdapter.Fill(this._Cars_1_DataSet.CarSize);
clsCarsData = new CarsDataClass();
cDataSet = clsCarsData.GetDataSet();
//Binding source sizes
sizeBindingSource = new BindingSource();
sizeBindingSource.DataSource = cDataSet;
sizeBindingSource.DataMember = "CarSize";
//Binding source vehicles
vehicleBindingSource = new BindingSource();
vehicleBindingSource.DataSource = cDataSet;
vehicleBindingSource.DataMember = "Vehicle";
//Combo box
cboSize.DataSource = sizeBindingSource;
cboSize.DisplayMember = "Size";
cboSize.ValueMember = "SizeCode";
//bind other controls
txtDaily.DataBindings.Add("text", sizeBindingSource, "DailyRate");
txtMileage.DataBindings.Add("text", sizeBindingSource, "MileageRate");
//execute combo box
cboSize_SelectionChangeCommitted(cboSize, e);
}
private void cboSize_SelectionChangeCommitted(object sender, EventArgs e)
{
string carSelected;
carSelected = Convert.ToString(cboSize.SelectedValue);
if (!gridInitialized)
{
dgvVehicles.DataSource = vehicleBindingSource;
gridInitialized = true;
ChangeGridColumns();
}
vehicleBindingSource.Filter = "CarSize = '" + carSelected + "'";
}
private void ChangeGridColumns()
{
//Change column headers
//dgvVehicles.Columns["Inv_ID"].Visible = false;
}
}
}
Try using SelectedIndexChanged event from the events menu after clicking on the combobox in the design view.
This should populate in your code:
private void comboBox1_SelectedIndexChanged(object sender, EventArgs e)
{
Console.WriteLine("test!");
}
In there you can put this logic from your code:
string carSelected;
carSelected = Convert.ToString(cboSize.SelectedValue);
if (!gridInitialized)
{
dgvVehicles.DataSource = vehicleBindingSource;
gridInitialized = true;
ChangeGridColumns();
}
vehicleBindingSource.Filter = "CarSize = '" + carSelected + "'";
Use SelectedIndexChanged instead of SelectionChangeCommitted.
In my window application there are many screens with grid. And I have used DataTable as DataSource of the grid and DataTable have some really large data sets (> 50,000), which take a lot time to load data on screen if we load all at a time while loading the UI get un-responsive till all data not get loaded, So that I have implemented incremental loading in that grid using Background Worker.
Here is the code :
// DoWork Event of the background Wroker.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
try
{
while (bgstop)
{
e.Result = addNewRecord();
if (Convert.ToBoolean(e.Result) == false)
{
e.Cancel = true;
bgstop = false;
killBGWorker();
break;
}
}
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
// to add/merge the records in the DataTable
private bool addNewRecord()
{
int flag = 0;
try
{
Thread.Sleep(500); //optional
DataTable tableAdd = getTableData();
if (tableAdd.Rows.Count > 0)
{
dtRecords.Merge(tableAdd); // dtRecords is the DataTable which attached to grid
flag++;
}
else
backgroundWorker1.WorkerSupportsCancellation = true;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
if (flag > 0)
return true;
else
return false;
}
// To get the next slot of Records from the DataBase
private DataTable getTableData()
{
DataTable dt = new DataTable();
start = nextRows * noOfRows;
stop = start + noOfRows;
dt = SQLHelper.getAllRecords(totalRows,noOfRows, start + 1, stop);
nextRows++;
return dt;
}
// kill the backgroudworker after the all data/records get loaded from database to grid/DataTable
private void killBGWorker()
{
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.CancelAsync();
}
Above code get the first defined number of records (say 200) and after that in the background worker started and start fetching the data in a slot and merge that with grid DataSource till all data (say >50,000 records) get loaded into the grid.
But still have some issue with UI interaction, UI not get hang for 2-3 seconds many time till all records from DataBase get loaded into the grid.
I gone through this but in that example DataModel was used but in my case there is no DataModel they just fetched in DataTable from DataBase and right now we can't move to DataModel. Is there any other way to achieve incremental Loading with good UI interaction ?OR Is there any way to implement IBindingList in current scenario ?
You can achieve that by changing the DataGridView from BindingMode to VirtualMode.
The following changes will re-use as much as possible what you already have and you will see that the DataGridView gets loaded incrementally. I don't know how much records you fetch at once, but you can keep that number low.
Set the property VirtualMode to true. Remove any values from the property DataSource. Add as many Unbounded columns to your DataGridView as you have columns in your DataGrid (this could be done automatic if needed).
Add an eventhandler for CellValueNeeded.
Add the following code to that handler:
private void dataGridView1_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
e.Value = dtRecords.Rows[e.RowIndex][e.ColumnIndex];
}
On you backgroundworker1 set the property WorkerReportsProgress to True
Add an eventhandler to your backgroundworker for ProgressChanged.with the following code:
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.dataGridView1.RowCount = (int) e.UserState;
}
In your method addNewRecord add below this line:
dtRecords.Merge(tableAdd); // dtRecords is the DataTable which attached to grid
// added to bring the number of records to the UI thread
backgroundWorker1.ReportProgress(42, dtRecords.Rows.Count);
And with that your datagridview should now load its data incrementally. The trick really is setting the RowCount property. That signals to the datagrid if it can show a record and it adapts its scrollbar to the same.
My solution is using BindingSource like this:
// To take data in silent
BackgroundWorker m_oWorker;
// To hold my data. tblDuToanPhanBo is my data type
List<tblDuToanPhanBo> lst2 = new List<tblDuToanPhanBo>();
BindingSource bs = new BindingSource();
// replace 50000 with your total data count
int totalData = 500000;
// No of rows to load a time by BackgroundWorker
int RowsToTake = 2000;
// No of rows loaded
int RowsTaken = 0;
Take first portion of data and let BackgroundWorker do the rest:
private void UserControl1_Load(object sender, EventArgs e)
{
m_oWorker = new BackgroundWorker();
m_oWorker.DoWork += new DoWorkEventHandler(m_oWorker_DoWork);
m_oWorker.ProgressChanged += new ProgressChangedEventHandler (m_oWorker_ProgressChanged);
m_oWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler (m_oWorker_RunWorkerCompleted);
// QLQT is my DataContext
using (QLQT db = new QLQT())
{
lst2.AddRange(db.tblDuToanPhanBos.Skip(RowsTaken).Take(RowsToTake).ToList());
}
RowsTaken = lst2.Count;
bs.DataSource = lst2;
dataGridView1.DataSource = bs;
m_oWorker.RunWorkerAsync();
}
BackgroundWorker to take one portion of data:
void m_oWorker_DoWork(object sender, DoWorkEventArgs e)
{
// Load data
using (QLQT db = new QLQT())
{
lst2.AddRange(db.tblDuToanPhanBos.Skip(RowsTaken).Take(RowsToTake).ToList());
}
// Update number of rows loaded
RowsTaken = lst2.Count;
if (((BackgroundWorker)sender).CancellationPending)
{
e.Cancel = true;
return;
}
}
When BackgroundWorker is completed, update BindingSource, run BackgroundWorker against until all data loaded:
void m_oWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Loading Cancelled.");
}
else if (e.Error != null)
{
MessageBox.Show("Error while performing background operation.");
}
else
{
if (lst2.Count < totalData)
{
bs.ResetBindings(false);
m_oWorker.RunWorkerAsync();
}
else
{
bs.ResetBindings(false);
}
}
}
Hope this help :)
So i successfully dynamiclly add button on DataGridView (DataGridViewButtonColumn) based on tables count in my database with :
//'cmd' type is MySqlCommand and 'cnnct' is MySqlConnection
//this is inside the Main()
string cmdString = "SHOW TABLES";
cmd.CommandText = cmdString;
cmd.Connection = cnnct;
Reader = cmd.ExecuteReader();
while (Reader.Read())
{
classSelect.Rows.Add(Reader.GetString(0));
}
Reader.Close();
then i've prepared a method that will be called when the button is clicked.
protected internal void FormCaller (object sender, EventArgs e)
{
//There's should be unique ID for the 'sender', but i don't know how
if(aioForm == null)
{
//'AIO_Form' is a Form
splash.ShowSplashScreen();
aioForm = new AIO_Form(this);
splash.CloseForm();
this.Visible = false;
aioForm.Visible = true;
}
//and some other code, handling if aioForm isn't null
}
The problem is, how i can add an EventHandler to the dynamiclly generated button, and it is based on a remote database ?
i've read from Here and Here (both are from StackOverflow) but no help.
Thanks in advance everybody.
After days do this and that, i can provide a workaround. Despite add the EventHandler to the Cell i add it to the DataGrid itself, then the Method which Handle the event should identify the cell that raise the event
add the EventHandler
while (Reader.Read())
{
var Name = UppercaseWords(Reader.GetString(0));
kelasSelect.Rows.Add(Name);
}
Reader.Close();
classSelect.CellMouseUp += FormCaller;
Then FormCaller which handle the event
protected internal void FormCaller(object sender, EventArgs e)
{
DataGridView Sender = (DataGridView)sender;
string FormName = Sender.CurrentCell.Value.ToString(); //This is how i identify the cell who raise the event
NForm = new myForm(this);
NForm.Text = FormName;
}
I have a WINFORM app with a DataGridView control , hooked into a ContextMenuStrip control.
The ContextMenuStrip fires events to essentially perform copy / paste between the DataGridView and the Clipboard.
private void copyToolStripMenuItem_Click(object sender, EventArgs e)
{
CopyClipboard();
}
private void CopyClipboard()
{
DataObject d = myGrid.GetClipboardContent();
Clipboard.SetDataObject(d);
}
private void pasteCtrlVToolStripMenuItem_Click(object sender, EventArgs e)
{
PasteClipboard();
}
I've added another DataGridView to my application and wish to share ContextMenuStrip between them since, per my code above, it's hardcoded to my grid, myGrid.
I believed it would simply be an easy exercise to modify my code to cast a new DataGridView control from the sender:
private void copyToolStripMenuItem_Click(object sender, EventArgs e)
{
CopyClipboard(sender);
}
private void CopyClipboard(object sender)
{
var grid = (DataGridView)sender;
DataObject d = grid.GetClipboardContent();
Clipboard.SetDataObject(d);
}
private void pasteCtrlVToolStripMenuItem_Click(object sender, EventArgs e)
{
var grid = (DataGridView)sender;
PasteClipboard(grid);
}
but of course, I found that the sender was instead the ToolStripMenuItem.
Is there a way to reference the original DataViewGrid via the sender, or EventArgs e ?
And, thank you for reading :)
Ahh, think I got it!
private void copyToolStripMenuItem_Click(object sender, EventArgs e)
{
CopyClipboard(sender);
}
private void CopyClipboard(object sender)
{
var grid = (DataGridView)sender;
DataObject d = grid.GetClipboardContent();
Clipboard.SetDataObject(d);
}
private void pasteCtrlVToolStripMenuItem_Click(object sender, EventArgs e)
{
var item = (ToolStripMenuItem)sender;
ToolStripMenuItem t = (ToolStripMenuItem)sender;
ContextMenuStrip s = (ContextMenuStrip)t.Owner;
var grid = (DataGridView)s.SourceControl;
// Pulling the backend datatable just to enhance the example.
// Going Live, the consumer of the "grid" will do the extraction.
BindingSource bs = (BindingSource)grid.DataSource;
DataTable dt = (DataTable)bs.DataSource;
PasteClipboard(grid, dt);
}
I found the solution here: http://discuss.joelonsoftware.com/default.asp?dotnet.12.474610.5
Finally per this thread, I wanted to add ToolStripMenuItem as a thread tag but i don't have the rep.
Appreciate someone with the rep adding it to the tag cache so that I can update this tread;
hopefully making somone else's life, with same issue, a lil' easier finding this thread! :)
Try
var grid = CType(sender, DataGridView)
or
var grid = CType(sender.parent, DataGridView)