I'm generating report in C# by using background worker but I'm getting this error.
Source code as follows:
I have to access my datagridview Records to access it's data.
A small window opens up in my datagridview which asks user to enter date from and to generate report, then i access back my datagridview convert into data table write in XML file and generate report.
Global Variables
// This is the form where the data lies, I'm accessing it's instance.
Records TR = new Records();
// This is the form where report will be displayed.
TReportDisplay TRD = new TReportDisplay();
// This is the report.
Treport treport1 = new Treport();
private void button1_Click(object sender, EventArgs e)
{
// FIXED HERE - 1
// FIXED - 2 IN THE ANSWER BELOW.
// Accessing my DataGridView Form Instance.
TR = Application.OpenForms.OfType<Records>().ElementAt(0);
treport1.SetDataSource(TR.ds);
TRD.crystalReportViewer2.ReportSource = treport1;
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
try
{
TRD.crystalReportViewer2.ReportSource = treport1;
ParameterFieldDefinitions Parameters;
ParameterFieldDefinition Parameter;
ParameterValues Values = new ParameterValues();
ParameterDiscreteValue DiscreteValue = new ParameterDiscreteValue();
DiscreteValue.Value = dateTimePicker1.Text;
Parameters = treport1.DataDefinition.ParameterFields;
Parameter = Parameters["fromdate"];
Values = Parameter.CurrentValues;
Values.Clear();
Values.Add(DiscreteValue);
Parameter.ApplyCurrentValues(Values);
DiscreteValue.Value = dateTimePicker2.Text;
Parameters = treport1.DataDefinition.ParameterFields;
Parameter = Parameters["todate"];
Values = Parameter.CurrentValues;
Values.Add(DiscreteValue);
Parameter.ApplyCurrentValues(Values);
}
}
catch (Exception ex) { MessageBox.Show(ex.Message.ToString(), "Message"); };
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
TRD.ShowDialog();
}
There were two issues, first of all updating progress bar from a different thread for which I got answer, another calling form instance after background worker was causing the issue, just put the variable before background worker start async and fixed.
The error message is telling you that the only way to update the controls is via the thread that the controls are running on. You are currently running on a different thread (the one for the back ground worker).
Take a look at the example in this link for another question on SO Invoke(Delegate) . You basically should have a method that you can call, to update the UI, which can check if it is on the correct thread and if it is not gets the correct thread to call it.
This is a snippet of code that was on the link parvee gave above that shows how you can do this.
public void UpdateProgress(int percentComplete)
{
if (!InvokeRequired)
{
ProgressBar.Value = percentComplete;
}
else
{
Invoke(new Action<int>(UpdateProgress), percentComplete);
}
}
Related
I need some help on adding a progress bar while the datagridview is being load it. I already have my code that loads the datagridview but as we know the loading takes time to finish loading depending of the records. So, I would like to add a progress bar loading and a label having a the count from 1 to 100 to complete.
I know there is a way using the background work handle event, but not sure how that make it work. I would like something simple but can do the work I need.
my code works great fills the datagridview as I want. but I need to add the progress bar while loading the datagridview.
change the code please review and let me know if I missed something.
So I made the changes and seems to work now, but there is an issue the progress bar does not work immediately takes a few seconds and then I can see the progress bar to move to 100%. Why it does that?
second issue after loading the datagridview the progress bar color goes away after I click the message MessageBox.Show("Successful Completion.");
here is a test image after my combo box select the value we want and display the datagridview
here I made the new changes to the program, but for some reason after I select the combobox the datagridview populates correctly but then I try again sometimes it fails and gives me this error
namespace DatagridViewProgressBar
{
public partial class Form1 : Form
{
//datagridview, bindingsource, data_apapter global objects variables
private DataGridView dataGridView = new DataGridView();
private BindingSource bindingSource = new BindingSource();
private SqlDataAdapter dataAdapter = new SqlDataAdapter();
DataTable dt = new DataTable();
//class objects
Databases lemars = new Databases();
Databases schuyler = new Databases();
Databases detroitlakeskc = new Databases();
public Form1()
{
InitializeComponent();
// To report progress from the background worker we set this property
dbWorker = new BackgroundWorker();
dbWorker.DoWork += new DoWorkEventHandler(dbWorker_DoWork);
dbWorker.ProgressChanged += new ProgressChangedEventHandler(dbWorker_ProgressChanged);
dbWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(dbWorker_RunWorkerCompleted);
dbWorker.WorkerReportsProgress = true;
dbWorker.WorkerSupportsCancellation = true;
}
private void btn_Exit_Click(object sender, EventArgs e)
{
this.Close();
}
private void comboBox_Database_SelectedIndexChanged(object sender, EventArgs e)
{
if (comboBox_Database.SelectedItem.ToString() == "LeMars21St")
{
if (dbWorker.IsBusy != true)
{
dbWorker.RunWorkerAsync();
}
}
}
private void GetTableToDataGridView()
{
//prgBar_DataGridViewLoading
DatabaseColumns Obj = new DatabaseColumns();
String SqlcmdString = #"SELECT invoice, shipment, Project, invoiceDateTB, CreatedDate, typeName, exportedDate, statusName, total, import_status, Time_Completed, ERROR_DESCRIPTION FROM dbo.AllInvoicesInReadyStatus";
SqlDataReader reader;
int progress;
using (SqlConnection conn = new SqlConnection(lemars._LeMarsConnectionString))
{
reader = null;
SqlCommand Sqlcmd = new SqlCommand(SqlcmdString, conn);
conn.Open();
reader = Sqlcmd.ExecuteReader();
if (reader.HasRows)
{
try
{
dt.Load(reader);
for (int i = 0; i < dt.Rows.Count; i++)
{
Obj.Invoice = dt.Rows[i]["invoice"].ToString();
Obj.Shipment = dt.Rows[i]["shipment"].ToString();
Obj.Project = dt.Rows[i]["Project"].ToString();
Obj.InvoiceDateTB = Convert.ToDateTime(dt.Rows[i]["invoiceDateTB"]);
Obj.CreatedDate = Convert.ToDateTime(dt.Rows[i]["CreatedDate"]);
Obj.TypeName = dt.Rows[i]["typeName"].ToString();
Obj.ExportedDate = Convert.ToDateTime(dt.Rows[i]["exportedDate"]);
Obj.StatusName = dt.Rows[i]["statusName"].ToString();
Obj.Total = Convert.ToDecimal(dt.Rows[i]["total"]);
Obj.ImportStatus = dt.Rows[i]["import_status"].ToString();
if (!Convert.IsDBNull(dt.Rows[i]["Time_Completed"]))
{
Obj.TimeCompleted = Convert.ToDateTime(dt.Rows[i]["Time_Completed"]);
}
Obj.ErrorDescription = dt.Rows[i]["ERROR_DESCRIPTION"].ToString();
progress = i * 100 / dt.Rows.Count;
dbWorker.ReportProgress(progress);
Thread.Sleep(500);
}
}
finally
{
conn.Close();
}
}
}
}
private void dbWorker_DoWork(object sender, DoWorkEventArgs e)
{
GetTableToDataGridView();
dbWorker.ReportProgress(100);
}
private void dbWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar_GetTasks.Value = e.ProgressPercentage;
// eg: Set your label text to the current value of the progress bar
lbl_PercentageCount.Text = (progressBar_GetTasks.Value.ToString() + "%");
}
private void dbWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
dataGridView_ShowAllData.DataSource = dt;
if (e.Cancelled)
{
MessageBox.Show("Process Cancelled.");
}
else if (e.Error != null)
{
MessageBox.Show("Error occurred: " + e.Error.Message);
}
else
{
MessageBox.Show("Successful Completion.");
}
//progressBar_GetTasks.Value = 0;
}
private void btn_CancelOperation_Click(object sender, EventArgs e)
{
if (dbWorker.IsBusy)
{
dbWorker.CancelAsync();
}
}
}
}
Firstly, SELECT * is a bad idea, regardless of how many columns you have, or need. Explicitly stating which columns you want opens up possibilities for using indices and reduces maintainability issues with your code.
Secondly, your main question. I have done something similar recently, and can give some pointers. I am going not going to immediately apply this to your code-snippet, because I think that will complicate things.
EDIT
For thread safety purposes, the code inside dbWorker_DoWork() should not try to access form elements which were created in the main thread. There are obviously ways around this, and once you get to the dbWorker_RunWorkerCompleted() function, you are back in the main thread and you have full access to the necessary form elements.
END EDIT
1) Your form. You need a backgroundworker to do the work, as well as three callback functions to handle what is going on. A progress bar is assumed to be on the form as well (System.Windows.Forms.ProgressBar).
...
using System.ComponentModel;
...
public partial class YourForm : Form
{
BackgroundWorker dbWorker;
...
public YourForm()
{
...
dbWorker = new BackgroundWorker();
dbWorker.DoWork += new DoWorkEventHandler(dbWorker_DoWork);
dbWorker.ProgressChanged += new ProgressChangedEventHandler(dbWorker_ProgressChanged);
dbWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(dbWorker_RunWorkerCompleted);
dbWorker.WorkerReportsProgress = true;
dbWorker.WorkerSupportsCancellation = true;
...
}
...
public void dbWorker_DoWork(object sender, DoWorkEventArgs e)
{
// This is where you put your GetTableToDataGridView() code.
// Add a line inside the loop, for reporting on progress:
// dbWorker.ReportProgress((int)(currentIteration * 100 / totalIterations));
// At the end of the process, set the progress bar to 100% (optional)
dbWorker.ReportProgress(100);
}
public void dbWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar.Value = e.ProgressPercentage;
// Here you can also do other things, which depend on the progress.
// eg: Set your label text to the current value of the progress bar.
}
public void dbWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if (e.Cancelled)
{
MessageBox.Show("Process Cancelled.");
}
else if (e.Error != null)
{
MessageBox.Show("Error occurred: "+ e.Error.Message.");
}
else
{
MessageBox.Show("Successful Completion.");
}
progressBar.Value = 0;
}
}
2) Starting the process (this could go in your form load function, or if you start the process manually, in an OnClick event):
...
if (!dbWorker.IsBusy)
{
dbWorker.RunWorkerAsync();
}
...
3) Cancelling the process (if you have a button for cancelling, then this would go in the OnClick event code):
...
if (dbWorker.IsBusy)
{
dbWorker.CancelAsync();
}
...
A couple obvious problems:
Since you have the connection inside a using block, the explicit conn.Close() is unnecessary. The using mechanism will automatically close it even if an exception occurs. In fact, unless you have a need to handle exceptions at this level, you can remove the try block altogether.
Never use SELECT *. You are retrieving a ton of data you don't need and this is probably why it's going slow in the first place.
To the meat of your question: BackgroundWorker is your friend!
I'm more familiar with doing data binding with WPF, so you'll need to do some legwork on your own. But the basic idea is that you have your progressbar's visibility bound to a variable, then you update that variable:
when you launch the thread, to make it visible (using the IsIndeterminate property is useful since there's no real way to do percentages for a SQL query)
when the thread finishes, to hide the progress bar.
In this way, you get the animated progress bar while the query is running.
You can set the mouse cursor to busy/arrow in the same way.
In my Silverlight application, I put the WCF call in my ViewModel class.
DateTime CurrentDateTime;
internal void GetDateTime()
{
var client = new WcfClient();
client.GetCurrentDateTimeCompleted += GetCurrentDateTimeCompleted;
client.GetCurrentDateTimeAsync();
}
private void GetCurrentDateTimeCompleted(object sender, GetCurrentDateTimeCompletedEventArgs args)
{
try
{
CurrentDateTime = args.Result;
}
Then in my code behind code some.xaml.cs file. I have a checkbox clicked event.
private void CheckBox_Clicked(object sender, RoutedEventArgs e)
{
var msgBoxControl = new MessageBoxControl();
msgBoxControl.Closed -= MessageBoxYesNo_Closed;
msgBoxControl.Closed += MessageBoxYesNo_Closed;
Inside the method MessageBoxYesNo_Closed, I call the method in the ViewModel class.
private void MessageBoxYesNo_Closed(object sender, EventArgs e)
{
try
{
this.ViewModel.GetDateTime();
curDateTime = this.ViewModel.CurrentDateTime;
My question is that sometimes the line curDateTime = this.ViewModel.CurrentDateTime; is executed before wcf call completed method, so I can't get the right value.
I guess that it may be there are two threads, one is in UI, the other one is in service call? Please don't use async/await as I have to use Visual Studio 2010.
Thanks
Get the solution, just add a while loop:
this.ViewModel.GetDateTime();
while (true)
{
this.ViewModel.CurrentDateTime = DateTime.Now;
if (this.ViewModel.CurrentDateTime != DateTime.MinValue)
break;
}
curDateTime = this.ViewModel.CurrentDateTime;
I am trying to make a program that, at the same time, displays text that the user inputted and writes to a text file of that same user input data. I've tried wrapping the the code with Task.Run:
private void button_Click(object sender, RoutedEventArgs e)
{
show.Text = inputText.Text;
//Debug.WriteLine(check1_cont.ToString());
//Debug.WriteLine(check2_cont.ToString());
if (check1_cont && check2_cont == true )
{
show2.Text = inputText.Text;
Task.Run(() => File.WriteAllText(#"A:\temp\name.txt", inputText.Text));
}
}
But I get an exception error after the second text (the one in the if statement) when I press the button:
An exception of type 'System.Exception' occurred in normieap.exe but
was not handled in user code
Additional information: The application called an interface that was
marshalled for a different thread. (Exception from HRESULT: 0x8001010E
(RPC_E_WRONG_THREAD))
I try using StreamWriter:
private void button_Click(object sender, RoutedEventArgs e)
{
show.Text = inputText.Text;
//Debug.WriteLine(check1_cont.ToString());
//Debug.WriteLine(check2_cont.ToString());
if (check1_cont && check2_cont == true )
{
show2.Text = inputText.Text;
using (StreamWriter writer = new StreamWriter(#"A:\temp\name.txt"))
{
writer.WriteLine(inputText.Text);
}
}
}
But I get an error on the line:
using (StreamWriter writer = new StreamWriter(#"A:\temp\name.txt"))
Because '#"A:\temp\name.txt"' cannot convert from 'string' to 'System.IO.Stream'
And when I try just the normal way without any wrappers I get a synchronous error. Any solutions to this problem would be much appreciated.
When you run a task asyncrounously, it isn't guaranteed to run on the UI thread. Take your first example and try this:
private void button_Click(object sender, RoutedEventArgs e)
{
show.Text = inputText.Text;
//Debug.WriteLine(check1_cont.ToString());
//Debug.WriteLine(check2_cont.ToString());
if (check1_cont && check2_cont == true )
{
show2.Text = inputText.Text;
// Copy the text to output
string outputToWrite = inputText.Text;
// use the copied text
Task.Run(() => File.WriteAllText(#"A:\temp\name.txt", outputToWrite));
}
}
What's going on here is that a background thread is trying to access a GUI element. That's generally not allowed in singled threaded UI libraries like Windows Forms, so you need to copy the data out of the control before sending it back to the background thread, otherwise the code will fail as you have seen.
So, I'm trying to develop a simple application in visual C# which gets data from serial port and displays it in a textbox (to monitor temperature). I'm acquiring and displaying the data successfully, using the DataReceived event to update a global string variable and a timer to update the text field on my text box, as shown:
private void port_DataReceived_1(object sender, SerialDataReceivedEventArgs e)
{
try
{
globalVar.updateTemp = port.ReadLine(); //This is my global string
}
catch (IOException)
{
}
catch (InvalidOperationException)
{
}
catch (TimeoutException)
{
}
}
private void timer1_Tick(object sender, EventArgs e)
{
tempDisplayBox.Text = globalVar.updateTemp; //This is my textbox updating
}
The only issue I have is that the value shown in the textbox keeps flashing, making it hard to read. My timer is set to trigger every 10 ms (which should be fast enough, right?). Is there any way to make it more stable? I realize this may be a newb question, but to be fair I am a newb :) Any help is appreciated! Thanks!
Do you really need it updating every 10ms? What about every 500 ms or if not that then 100ms. 100ms will require your update method run 10 times less and therefore update 10 times less. The flickering you are expiriencing is due to the refresh speed. You could create custom method which will only update the temp only when target Label or textBox value is different than source port. But that will only sort the flickering when temp is steady, when temp will start vary it will bring back the flickering. Good luck ;-)
UPDATE
Hi I tried to reproduce the conditions and could not make my textbox nor Label flash. The way I tested it was by assigning int ntick = 0; and then increment the ++ntick; inside of the timer_tick method. The results didn't make any of the controls flash and were updated even every milisecond at some point. I also tried string.Format to put some load on the method. Is your app responsive?
The trick is to use double buffering. This way the operating system will redraw the Control off-screen, and only show the control when it is fully redrawn.
I have had the same problem, and solved it by extending the TextBox control like this:
public FastLogBox()
{
InitializeComponent();
_logBoxText = new StringBuilder(150000);
timer1.Interval = 20;
timer1.Tick += timer1_Tick;
timer1.Start();
SetStyle(ControlStyles.DoubleBuffer, true);
}
void timer1_Tick(object sender, EventArgs e)
{
if (_timeToClear)
{
_logBoxText.Clear();
_timeToClear = false;
}
if (_logQueue.Count <= 0) return;
while (!_logQueue.IsEmpty)
{
string element;
if (!_logQueue.TryDequeue(out element)) continue;
{
_logBoxText.Insert(0, element + "\r\n");
}
}
if (_logBoxText.Length > 150000)
{
_logBoxText.Remove(150000, _logBoxText.Length - 150001);
}
Text = _logBoxText.ToString();
}
public new void Clear()
{
_timeToClear = true;
while (!_logQueue.IsEmpty)
{
string element;
_logQueue.TryDequeue(out element);
}
}
public void AddToQueue(string message)
{
_logQueue.Enqueue(message);
}
}
I also use a timer and a concurrentQueue to avoid using Invoke to update the control from another thread. I also use a StringBuilder to prepare the string before putting it into the TextBox. StringBuilder is faster when building larger strings.
You can use ReadExisting() to read the whole data at a time.
You need to handle DataReceived Event of SerialPort
serialPort1.ReadExisting();
Sample:
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
String myData=serialPort1.ReadExisting();
}
Example Code: Here i would like to show you the code to Read Data(RFID Tag Code which is basically of length 12)
String macid = "";
private void DoWork()
{
Invoke(
new SetTextDeleg(machineExe ),
new object[] { macid });
macid = "";
}
private void serialPort1_DataReceived(object sender, System.IO.Ports.SerialDataReceivedEventArgs e)
{
string str1;
macid += serialPort1.ReadExisting();
if (macid.Length == 12)
{
macid = macid.Substring(0, 10);
Thread t = new Thread(new ThreadStart(DoWork));
t.Start();
}
}
public void machineExe(string text)
{
TextBox1.Text=text;
}
Thank you so much for the answers! I found a way to work around this issue:
Instead of replacing the contents of my textbox by rewriting the TextBox.Text property - which, as HenningNT implied, refreshes the control and causes the flickering - I'm now using the TextBox.AppendText method. Though, as I want to display only one line of data at a time, I use the textbox in multiline mode and the Environment.NewLine to jump to a new line before appending the text. As for the method of updating, I've gone back to using the timer because with the invoke method was crashing my application when I close the form, for some reason. Also, enabling double buffering didn't do me much good, although I guess I was doing it wrong... It still flickers a bit, but it's much better now :) I know this is not really a perfect solution (much more of a workaround), so I'll keep looking for it. If I find it, I'll be sure to update it here ;) My code:
private void timer1_Tick(object sender, EventArgs e) //Timer to update textbox
{
if (tempDisplayBox.Text != globalVar.updateTemp) //Only update if temperature is different
{
try
{
tempDisplayBox.AppendText(Environment.NewLine);
tempDisplayBox.AppendText(globalVar.updateTemp);
}
catch (NullReferenceException)
{
}
}
}
I have designed a progress bar that I'd like to use when I load a grid (I load a datagridview from a stored procedure). However the process that calls the stored procedure has a few different items it calls (see below). I'm early on in getting the progress bar to work (it doesn't in the code below, hence why Im here), but my question is this.
Can the progress bar properly wok when the progress of what I'm tracking is multiple different methods. The "LoadGrid" method is the one I'd really like to track progress of, as that is the processing of the stored procedure and loading of datagridview (i.e. the time consuimng processes). I guess I'm more asking what's the proper technique as opposed to the exact code to use, but I'm limited in knowedge on progress bars. I know I could use a just a random icon that says "busy" but I'd rather have the progress bar if its possible to do legitimately.
public void btnLoadGrid_Click(object sender, EventArgs e)
{
frmProgress progressForm = new frmProgress();
try
{
progressForm.MdiParent = this;
progressForm.Text = "Importing DSC_0";
progressForm.Top = this.Height / 3 - progressForm.Height / 2;
progressForm.Left = this.Width / 2 - progressForm.Width / 2;
//ofd1.Title = "Import legacy DSC balances";
//this.ofd1.ShowDialog(this);
//Need code to empty grid before loading
grd1.Rows.Clear();
grd1.Refresh();
//Load grid based on new selections
GetUserSelections();
GetUserRelatedInfo();
LoadLabelForecastType();
LoadGrid();
}
catch (Exception ex)
{
util.LogError(ex);
MessageBox.Show(ex.Message, "Error", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
}
Cursor.Current = Cursors.Default;
progressForm.Close();
}
And the progress bar itself:
namespace AmortClient
{
public partial class frmProgress : Form
{
public frmProgress()
{
InitializeComponent();
}
public ProgressBar Pbar
{
get { return this.pb1; }
}
}
}
We have a progress bar that uses Background thread and event callbacks. It also uses the "params" parameter and delegates so that it can be generalized into any process in the code.
Here are a few little snippits...
private delegate T Method(ProgressBarCallBackInterface callingform, params object[] argsobject);
private void frmProgress_Load(object sender, EventArgs e)
{
if (!string.IsNullOrEmpty(FormTitle))
{
lblSampleTitle.Text = FormTitle;
this.Text = FormTitle;
}
else
{
lblSampleTitle.Text = string.Empty;
this.Text = string.Empty;
}
bgWorkerThread.RunWorkerAsync();
}
private void bgWorkerThread_DoWork(object sender, DoWorkEventArgs e)
{
Delegate method = Delegate.CreateDelegate(typeof(Method), instanceOfClassHavingTheFunction, FullFunctionName, true, true);
if (method != null)
{
ReturnValue = ((Method)method)(this, Parameters);
}
}
public void ReportProgress(int percentage, string statusText)
{
lblProgress.SetPropertyThreadSafe(() => lblProgress.Text, statusText);
bgWorkerThread.ReportProgress(percentage);
}
then in whatever code is using it, I can call
if (progressBar != null)
progressBar .ReportProgress(6, "Verifying Journal Integrity");
my code is slightly more complicated because T cannot be void, so I had to add some switches and a secondary method to allow this to run on processes that have a void return. But the basic shape is there.
Here is a sample of an entry point into the progress bar:
//note: FunctionName, Class Having Function, Params....
frmProgress _frmProgress = new frmProgress("UpdateRigX", RigFacade.Instance, rigX, dRigVersion, ModuleVersion);
_frmProgress.FormTitle = "Updating RigX...";
_frmProgress.ShowDialog();