I have a Crystal Report which generates from various DataTables from a button on a Form with a TreeView. I wanted to run a BackGroundWorker so I can add a ProgressBar, since the Crystal Report generated takes some time. I've read that in the first place I needed to add a BackGroundWorker to the control and put all the logic code that generates que long-running process on the DoWork event of the BackGroundWorker. I did it like this:
//bgwBackThread is the name of the BackGroundWorkerObject
private void bgwBackThread_DoWork(object sender, DoWorkEventArgs e)
{
DataTable reporte = preReportDouble(Ot, Oth);
DataTable hh = reporteHH(Ot, Oth);
DataTable otNoCosto = otNoCost(Ot, Oth);
DataTable dethh = detalleHH(Ot, Oth);
//cryrepo is a Form which holds a CrystalReportViewer
InformeMaquina cryrepo = new InformeMaquina();
cryrepo.Informe = reporte;
cryrepo.Hh = hh;
cryrepo.SinCosto = otNoCosto;
cryrepo.DetHh = dethh;
cryrepo.Show();
}
and after I assigned the method RunWorkerAsync() to the button which generated the Form
before
private void btnReporte_Click(object sender, EventArgs e)
{
bgwBackThread.RunWorkerAsync();
//Below its commented because before of trying BackGroundWorker I just used the code here.
/*DataTable reporte = preReportDouble(Ot, Oth);
DataTable hh = reporteHH(Ot, Oth);
DataTable otNoCosto = otNoCost(Ot, Oth);
DataTable dethh = detalleHH(Ot, Oth);
InformeMaquina cryrepo = new InformeMaquina();
cryrepo.Informe = reporte;
cryrepo.Hh = hh;
cryrepo.SinCosto = otNoCosto;
cryrepo.DetHh = dethh;
cryrepo.Show();
*/
}
The problem is when I press the report button with the code as above. It loads the Form which holds que Crystal Report, but this Forms hangs (even in Debug). Without using BackGroundWorker it works fine, but with delay. I've read that its maybe because I'm loading the Form from a non-UI Thread, and that I have to unbind from the UI and then rebind. Is that the Problem?? If it were, how can I unbind and then rebind??
Your help is very apreciated.
Try creating a private class in your form to hold the DataTable information (which I assume is the time consuming part);
private class ReportTables {
public DataTable reporte;
public DataTable hh;
public DataTable otNoCosto;
public DataTable dethh;
}
Create the DataTables and update the results in the e.Result property:
private void bgwBackThread_DoWork(object sender, DoWorkEventArgs e)
{
ReportTables rt = new ReportTables();
rt.reporte = preReportDouble(Ot, Oth);
rt.hh = reporteHH(Ot, Oth);
rt.otNoCosto = otNoCost(Ot, Oth);
rt.dethh = detalleHH(Ot, Oth);
e.Result = rt;
}
Then in the Completed event, show the form:
void bgwBackThread_RunWorkerCompleted(object sender,
RunWorkerCompletedEventArgs e) {
if (e.Error != null) {
MessageBox.Show(e.Error.Message);
} else {
ReportsTables rt = e.Result as ReportTables;
//cryrepo is a Form which holds a CrystalReportViewer
InformeMaquina cryrepo = new InformeMaquina();
cryrepo.Informe = rt.reporte;
cryrepo.Hh = rt.hh;
cryrepo.SinCosto = rt.otNoCosto;
cryrepo.DetHh = rt.dethh;
cryrepo.Show();
}
}
Related
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
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
loadtest();
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
//Progress Bar Window close
pop.Close();
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pop.prgTest.Value = e.ProgressPercentage;
}
private void Window_Loaded(object sender, RoutedEventArgs e)
{
//Background Worker code///
bw.WorkerReportsProgress = true;
bw.DoWork += bw_DoWork;
bw.ProgressChanged += bw_ProgressChanged;
bw.RunWorkerCompleted += bw_RunWorkerCompleted;
bw.RunWorkerAsync();
//Progress Bar Window
pop.Show();
}
The load test here is...a method that picks up few images from database and displays them. The method works fine if the run the method on page initialisation but its not giving any output when i load them like this in background worker.....here is the method loadtest.
public void loadtest()
{
string query = "select*from question where id='" + 1 + "'";
MySqlConnection conDataBase = new MySqlConnection(constring);
MySqlCommand cmdDataBase = new MySqlCommand(query, conDataBase);
MySqlDataReader myReader;
try
{
conDataBase.Open();
myReader = cmdDataBase.ExecuteReader();
while (myReader.Read())
{
string qid = myReader.GetInt32("id").ToString();
byte[] imgg1q1 = (byte[])(myReader["question"]);
byte[] imgg2q1 = (byte[])(myReader["opt1"]);
byte[] imgg3q1 = (byte[])(myReader["opt2"]);
byte[] imgg4q1 = (byte[])(myReader["opt3"]);
byte[] imgg5q1 = (byte[])(myReader["opt4"]);
MemoryStream mstreamq1 = new MemoryStream(imgg1q1);
MemoryStream mstream1q1 = new MemoryStream(imgg2q1);
MemoryStream mstream2q1 = new MemoryStream(imgg3q1);
MemoryStream mstream3q1 = new MemoryStream(imgg4q1);
MemoryStream mstream4q1 = new MemoryStream(imgg5q1);
q1.BeginInit();
q1.StreamSource = mstreamq1;
q1.CacheOption = BitmapCacheOption.OnLoad;
q1.EndInit();
// Assign the Source property of your image
q_image.Source = q1;
q1opt1.BeginInit();
q1opt1.StreamSource = mstream1q1;
q1opt1.CacheOption = BitmapCacheOption.OnLoad;
q1opt1.EndInit();
option_1.Source = q1opt1;
q1opt2.BeginInit();
q1opt2.StreamSource = mstream2q1;
q1opt2.CacheOption = BitmapCacheOption.OnLoad;
q1opt2.EndInit();
option_2.Source = q1opt2;
q1opt3.BeginInit();
q1opt3.StreamSource = mstream3q1;
q1opt3.CacheOption = BitmapCacheOption.OnLoad;
q1opt3.EndInit();
option_3.Source = q1opt3;
q1opt4.BeginInit();
q1opt4.StreamSource = mstream4q1;
q1opt4.CacheOption = BitmapCacheOption.OnLoad;
q1opt4.EndInit();
option_4.Source = q1opt4;
}
conDataBase.Close();
}
catch
{
}
}
i am using a background worker to display a popup of loading progressbar until the page loads and popup is a function that will display a new popup window which says loading....
the loadtest method works properly everywhere but its not working with the backgroundworker...the funtion loadtest picks some images from database which takes time and i am displaying a popup until the images are getting loaded and the dispalying in option_2.source,option_2.source,option_3.source and option_4.source..........
Don't change control properties directly from a background thread. Instead send them over to the UI thread in order to use them. One way to do this would be via BackgroundWorker.ReportProgress Method (Int32, Object) see also Updating UI with BackgroundWorker in WPF
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
loadtest(sender as BackgroundWorker);
}
and
public void loadtest(BackgroundWorker bw)
{
//... your code
q1.BeginInit();
q1.StreamSource = mstreamq1;
q1.CacheOption = BitmapCacheOption.OnLoad;
q1.EndInit();
// by freezing the image, it will become available to the UI thread
q1.Freeze();
// Don't directly assign the Source property of your image
// q_image.Source = q1;
// Instead, report progress to the UI thread:
bw.ReportProgress.ReportProgress(25, new Tuple<Image, ImageSource>(q_image, q1));
//... your code
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pop.prgTest.Value = e.ProgressPercentage;
// assign the image source on the UI thread
var data = e.UserState as Tuple<Image, ImageSource>;
data.Item1.Source = data.Item2;
}
Edit: added the Freeze() call to image after initialization. This is necessary in order to allow access to the image outside the worker thread where it was created.
As a side note: Please replace your empty catch { } block with some actual error checking... I suppose you just suppressed the relevant exceptions there that would help in identifying the issues.
The backgroundworker has the ReportProgress method with which you can report periodic progress of the background to the UI.
Set the WorkerReportsProgress to true, as this defaults to false.
bw.WorkerReportsProgress = true;
Then hook up the Progress report event to your UI updating method, bw_ProgressChanged, which you are already doing.
bw.ProgressChanged += bw_ProgressChanged;
Then call in the ReportProgress method wherever you want to update the UI in the Do_Work method. Get the worker from the sender object and pass into the loadtest method and call like below.
bw.ReportProgress(progressPercentage); // This would be % completed you want to display.
This should work.
I have this situation I want to synchronize informations in my dataGridView when I insert it on my add form like you can see on this picture.
In my Insert form on insert button I call Add form to pop up like this
private void button1_Click(object sender, EventArgs e)
{
if (addForm==null)
{
addForm = new AddForm();
}
addForm.MdiParent = this.ParentForm;
addForm.FormClosed += AddForm_FormClosed;
addForm.Show();
}
private void AddForm_FormClosed(object sender, FormClosedEventArgs e)
{
addForm = null;
}
On Add form in Accept click I insert informations and call fillDataGrid() method from Insert form to do data sync but nothing is shown data is shown just when I close Insert form and call it again does someone has susggestion how can I do this this is the first time I work with MdiContainer ?
private void buttonAccept_Click(object sender, EventArgs e)
{
if (validation())
{
Proizvod product = new Proizvod();
product.NazivProizvoda = textBoxName.Text;
product.Opis = textBoxDescription.Text;
product.SerijskiBroj = textBoxNumber.Text;
product.ZemljaPorijekla = textBoxCountry.Text;
if (pDal.insertProduct(product)==0)
{
MessageBox.Show("Informations are successfully inserted","Message");
InsertForm inForm = new InsertForm();
inForm.fillDataGrid();
}
}
}
My fillDataGrid() method and Load event of InsertForm:
public void fillDataGrid()
{
dataGridViewProducts.DataSource = null;
dataGridViewProducts.AutoGenerateColumns = false;
dataGridViewProducts.AutoSizeColumnsMode = DataGridViewAutoSizeColumnsMode.Fill;
dataGridViewProducts.ColumnCount = 3;
dataGridViewProducts.Columns[0].Name = "Product name";
dataGridViewProducts.Columns[0].DataPropertyName = "NazivProizvoda";
dataGridViewProducts.Columns[1].Name = "Country";
dataGridViewProducts.Columns[1].DataPropertyName = "ZemljaPorijekla";
dataGridViewProducts.Columns[2].Name = "Product number";
dataGridViewProducts.Columns[2].DataPropertyName = "SerijskiBroj";
dataGridViewProducts.DataSource = pDal.getAllProducts();
}
private void InsertForm_Load(object sender, EventArgs e)
{
fillDataGrid();
}
private void InsertForm_Shown(object sender, EventArgs e)
{
dataGridViewProducts.CurrentCell = null;
dataGridViewProducts.ClearSelection();
}
Currently in buttonAccept_Click code, you have created a new instance of the list form and called its FillGrid. This way you are manipulating another instance of the list form which is different from the instance which is open and you can see. You are filling a different form which you didn't show it.
Instead of creating a new instance, create a constructor for your second form which accepts a parameter of first form type. Then when you want to create a new instance of seccod form, pass the instance of the first form (this) to second Form. Then in your save button call the FillGrid method of the passed instance.
For more information about how to manipulate another form, read this post. It contains some useful options about:
Pass data to second Form when creating
Manipulate second Form after showing
Manipulate first Form from second Form
Here is some code which belong to the ListForm:
private void ShowAddForm_Click(object sender, EventArgs e)
{
if (addForm == null)
{
addForm = new AddForm(this);
addForm.MdiParent = this.ParentForm;
addForm.FormClosed += AddForm_FormClosed;
}
addForm.Show();
}
private void AddForm_FormClosed(object sender, FormClosedEventArgs e)
{
addForm = null;
}
And here is the code for AddForm
public class AddForm
{
MyListForm listForm;
public AddForm(MyListForm f)
{
InitializeComponent();
listForm = f;
}
private void SaveVutton_Click(object sender, EventArgs e)
{
//perform validation and save data
f.FillGrid();
}
}
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 :)
I want to use a ProgressBar control in C# when my DataGridView is loading data, but when I run my program I get an error:
Cross-thread operation not valid: Control 'dataGridView1' accessed from a thread other than the thread it was created on.
Code:
private readonly BackgroundWorker _bw = new BackgroundWorker();
public workerbloks()
{
InitializeComponent();
progressBar1.MarqueeAnimationSpeed = 30;
// set Visible false before you start long running task
progressBar1.Visible = false;
_bw.DoWork += show_worked_bloks;
_bw.RunWorkerCompleted += BW_RunWorkerCompleted;
}
private void button1_Click(object sender, EventArgs e)
{
progressBar1.Show();
_bw.RunWorkerAsync();
}
private void BW_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Hide();
}
public void show_worked_bloks(object sender, DoWorkEventArgs doWorkEventArgs)
{
MYDBMS md = new MYDBMS();
DataTable dt = new DataTable();
clsWorkBloks clswb = new clsWorkBloks();
try
{
dt = md.ExecuteSelectSQL(clswb.show_all_worked_bloks());
dataGridView1.DataSource = dt;
}
catch
{
}
}
Execute the specified delegate asynchronously on the thread that the dataGridView1 underlying handle was created on.
dataGridView1.BeginInvoke((MethodInvoker)delegate
{
this.dataGridView1.DataSource = dt;
});
As the progressbar is just marquee display. Simply bring it to front before running background worker.
progressBar1.BringToFront();
progressBar1.Show();
_bw.RunWorkerAsync();