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();
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.
i make a code in C# where i extract some records from an Access database , but i need the while going to the next iteration to depend on the click of a button. i tried with some Thread or Tasks , but it didn't worked because it blocked the UI which i need it to be seen and clickable.
Here's the code:
bool nextClick = false ;
while (readerSelect.Read())
{
// show the correct panel
if (string.Compare(readerSelect[2].ToString(), "P1") == 0)
{
// panel with type 1
textBoxP1Text1.Text = readerSelect[3].ToString();
textBoxP1Text2.Text = readerSelect[4].ToString();
pictureBoxP1Image.ImageLocation = readerSelect[6].ToString();
}
else
{
// panel with type 2
textBoxP1Text2.Text = readerSelect[5].ToString();
}
//this while need to be kind of infinite so the interation can't be processed and
//so when i need to change iteration i click the buttonNext
while (!nextClick) {
startWhile:;
MethodInvoker mi = delegate () {
if (nextClick)
{
Application.DoEvents();
// System.Windows.Forms.Application.Run();
}
};
this.Invoke(mi);
//break;
goto startWhile;
}
private void buttonNext_Click(object sender, EventArgs e)
{
// click on the next button
nextClick = true;
}
You can use a semaphore within an async task, have the button Release it during each click, and have the while loop await it each time through. Here's a quick example, using a form that has a button1 and a label1 added to it:
public partial class Form1 : Form
{
private readonly SemaphoreSlim signal = new SemaphoreSlim(0, int.MaxValue);
public Form1()
{
this.InitializeComponent();
this.RunLoop();
}
private async void RunLoop()
{
var i = 0;
while (true)
{
this.label2.Text = $"Enqueued: {this.signal.CurrentCount}";
await this.signal.WaitAsync(); // Wait button click async
await Task.Delay(1000); // Simulate work
this.label1.Text = $"Completed: {++i}";
}
}
private void button1_Click(object sender, EventArgs e)
{
this.signal.Release();
this.label2.Text = $"Enqueued: {this.signal.CurrentCount + 1}";
// Or if you want to limit the # people can queue up, then put this whole
// thing in an `if (signal.CurrentCount < myLimit)` block, and optionally
// disable the button once limit has been reached, and re-enable it right
// before the `WaitAsync` call above.
}
}
While Dax Fohl's answer works, it seems like you've got a problem in your design. I think you're violating the Single Responsibility Principle by having too much business logic going on in the Form class.
I'd recommend factoring out the business logic into its own class. Then rather than running through everything in a loop, you simply have the button click event process the next record and display the result. Here's an example of what I mean:
public partial class Form1 : Form
{
private readonly DataProcessor dataProcessor = new DataProcessor();
public Form1()
{
this.InitializeComponent();
}
private void button1Next_Click(object sender, EventArgs e)
{
this.buttonNext.Enabled = false;
this.ProcessNext();
}
private async void ProcessNext()
{
string s = await this.dataProcessor.ProcessNext();
this.textBoxP1Text1.Text = s;
this.buttonNext.Enabled = true;
}
}
public class DataProcessor
{
private readonly Random r = new Random(); // Or reader or whatever.
public async Task<string> ProcessNext() // Just using `string` as an example.
{
await Task.Delay(1000);
return this.r.Next().ToString();
}
}
I think this will be easier to understand and more maintainable in the future. When a new team member looks at semaphore stuff (or your future self), it'll be hard to understand/remember what the point of all that was. Here, you just have a local function that does one thing and is easy to follow.
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);
}
}
I am struggling to workout how to create something that essentially pauses my while loop until my button1 is pressed, I know about the event handler button1_Click but I don't think that will work in this situation as I have lots of loops nested in each other on my form_load.
Any help would be highly appreciated!
This is a snipped of my code where I want the loop to be 'paused' with the notes:
while (reader2.Read())
{
QuestionSpace = Convert.ToString(reader2["Question Space"]);
label1.Text = QuestionSpace;
if (button1.Click = true) // if the button is clicked)
{
// continue with the while loop (I am going to add an INSERT SQL query in here later)
}
else
{
// pause until the button is pressed
}
}
My whole code for the form:
public partial class CurrentlySetTestForm : Form
{
private int QuestionID { get; set; }
private string QuestionSpace { get; set; }
public CurrentlySetTestForm()
{
InitializeComponent();
}
private void CurrentlySetTestForm_Load(object sender, EventArgs e)
{
string y = GlobalVariableClass.Signedinteacher;
MessageBox.Show(y);
Convert.ToInt32(y);
string connectionString = ConfigurationManager.ConnectionStrings["myconnectionstring"].ConnectionString;
SqlConnection connect = new SqlConnection(connectionString);
connect.Open();
SqlCommand command18 = new SqlCommand("SELECT [QuestionID] FROM QuestionStudentAssociation WHERE ( [StudentID]=#Signedinstudent)", connect);
command18.Parameters.AddWithValue("#Signedinstudent", y);
var reader = command18.ExecuteReader();
while (reader.Read())
{
QuestionID = Convert.ToInt32(reader["QuestionID"]);
SqlCommand command19 = new SqlCommand(#"SELECT [Question Space] FROM Questions WHERE ( [QuestionID] = #currentQID )", connect);
command19.Parameters.AddWithValue("#currentQID", QuestionID);
try
{
var reader2 = command19.ExecuteReader();
while (reader2.Read())
{
QuestionSpace = Convert.ToString(reader2["Question Space"]);
label1.Text = QuestionSpace;
if (button1.Click = true) // if the button is clicked)
{
// continue with the while loop (I am going to add an INSERT SQL query in here later)
}
else
{
// pause until the button is pressed
}
}
}
catch (SyntaxErrorException ex)
{
MessageBox.Show(ex.Message);
}
finally
{
MessageBox.Show("Done one loop");
}
}
}
}
Sounds like your not ready to learn TPL
So maybe a BackgroundWorker , you can paint it on the form
To make the click cancel the background worker have a look at Cancel backgroundworker
I would some time to learn TPL as its going to create a simpler and more elegant solution.
As for pausing I would refactor the code, you should not keep the reader open waiting on the user.
You do want event-driven response to UI events, always. However, I guess that you don't want to split your logic into a state machine by hand (where each event triggers progress to the next state). Well, you're in luck, the C# compiler has some keywords to build state machines automagically so you don't have to manage the details.
There are actually two different mechanisms for continuation-passing style implemented in C#. The old one, yield return, works great if your UI events are pretty much interchangeable (or you're only interested in one). Works like this:
IEnumerator<int> Coroutine;
// this could be a Form_Load, but don't you need to get the user information before making the database connection?
void BeginQuiz_Click( object sender, EventArgs unused )
{
Coroutine = RunQA();
}
IEnumerator<int> RunQA()
{
// connect to DB
// show first question on UI
return ContinueQA();
}
IEnumerator<int> ContinueQA()
{
// you can use a while loop instead if you really want
for( int question = 0; question < questionCount; ++question )
{
// check answer
if (/* too many wrong answers*/) {
// report failure in DB
yield break;
}
// get next question from DB
// show new question on the UI
// wait for UI action
yield return question;
}
// report score in DB
// update UI with completion certificate
}
void AnswerButton_Click( object sender, EventArgs unused )
{
answer = sender;
Coroutine.MoveNext(); // MAGIC HAPPENS HERE
}
void TimeoutTimer_Tick( object sender, EventArgs unused )
{
answer = TimeoutTimer;
Coroutine.MoveNext();
}
The magic comes from yield return. Every time the function reaches yield return, the compiler saves what you were doing. When the button click event comes and calls MoveNext, the compiler generates code that starts where yield return paused everything, and keeps going from there until the next yield return.
Important note, the code inside ContinueQA doesn't start when RunQA() does return ContinueQA(); It actually starts on the first MoveNext(). So split your code between RunQA() and ContinueQA accordingly.
If you need different pause reasons at different places in your code, then async/await will be more helpful.
A better way to handle this would be the use of a timer. This would allow the form to draw it's controls and handle all input, such as clicking the button.
Adjust the timer interval (ms) to your needs.
Another way of doing this would be, as Mehrzad Chehraz said, to use multi-threading.
On a side note, I would strongly recommend condition checks over the try/catch checks if possible.
Enable/Disable the timer using the button and call the loop when the timer ticks.
Example:
Timer loopTimer = new Timer();
private void Form1_Load(object sender, EventArgs e)
{
loopTimer.Interval = 100;
loopTimer.Tick += loopTimer_Tick;
loopTimer.Enabled = true;
}
void loopTimer_Tick(object sender, EventArgs e)
{
//perform the loop here at the set interval
}
private void button1_Click(object sender, EventArgs e)
{
//pause/play the loop
loopTimer.Enabled = !loopTimer.Enabled;
}
I am working in Visual Studio 2010 .NET 4.0 in C# using WinForms.
The form has a single DataGridView that is data bound to a DataSet.
The DataSet is being populated from a Thread that is processing data being read from a ConcurrentQueue.
The code is also using a semaphore to serialize access to the DataSet as it can be accessed from the "worker" Thread and the UI main thread ( when updating the DataGridView ).
The UI/Form has a System.Windows.Forms.Timer that fires every 1/4 second to call a function that causes the DataGridView to be updated with the current contents of the DataSet.
The code runs correctly until the point at which the DataGridView data scrolls and the scroll bar becomes visible. At this point the entire form becomes unresponsive - title caption says "Program Not Responding".
The interesting thing is that this DOESN'T happen while running under the debugger. Only when the program is deployed. And no exceptions are ever raised.
I've tried using both Invoke and BeginInvoke with no change in behavior.
Questions:
1- What might be wrong with my code below?
2- Since I can't observe this behavior while running under the debugger, how do I find out what is causing the problem?
The wall of code is provided below. I know it's a lot of code, but I cut out as much as I could.
// deleted using statements for brevity
namespace namcom
{
public partial class wndXMLtrans : Form
{
private DataSet dsRecords = new DataSet();
private Thread threadConsumeXML = null;
private static Semaphore ResourceLock;
public wndXMLtrans(String ip)
{
InitializeComponent();
ResourceLock = new Semaphore(1, 1);
curSize = this.Size;
m_ip = ip;
}
private Boolean AddRowToDataSet(String[] columns, String xml)
{
Boolean retCode = true;
String value = String.Empty;
Int64 id = -1;
DataRow row;
ResourceLock.WaitOne();
row = dsRecords.Tables[0].NewRow();
// prepare row code omitted - brevity
// add new data row to DataSet
dsRecords.Tables[0].Rows.Add(row);
ResourceLock.Release();
// SQL inserts into DB removed - brevity
return (retCode);
}
private Boolean HandleSingleXMLMessage(String[] columns, String xml, out String exceptionMessage)
{
Boolean boolRet = true;
exceptionMessage = String.Empty;
// store data in dataset and database
if ( closeRequested == false )
{
AddRowToDataSet(columns, xml);
}
return (boolRet);
}
private Boolean StoreG2SMessages(String message)
{
// code removed - brevity
// removed code just parses out string and in a loop calls HandleSingleXMLMessage
// until all XML in message have been processed
HandleSingleXMLMessage(columns,xml, out exceptionMessage) // call in loop
return (ret);
}
// pull XML out of mainwnd.msgQueue and update database
private void G2SParseThread()
{
String Data;
String exceptionMsg = String.Empty;
while ( /* thread is to be active - code removed for brevity */)
{
Data = String.Empty;
if (mainwnd.msgQueue.TryDequeue(out Data) == true)
{
this.StoreG2SMessages(Data);
}
Thread.Sleep(20);
}
// thread ended cleanup code removed - brevity
}
private void StartThreads()
{
// start XML packet processing thread
threadConsumeXML = new Thread(G2SParseThread);
threadConsumeXML.SetApartmentState(ApartmentState.STA);
threadConsumeXML.Start();
threadMonitor = new Thread(() => threadHandleStatusMsg(ref mainwnd.statusQueue));
threadMonitor.Start();
}
private void wndXMLtrans_Shown(object sender, EventArgs e)
{
// remove SQL code - brevity
// fill dsRecords ( DataSet )
try
{
var adapter = new SQLiteDataAdapter(selectSQL, dbConnection);
adapter.Fill(dsRecords);
dataGrid.DataSource = dsRecords.Tables[0].DefaultView;
adapter.Dispose();
}
catch { } // catch code removed - brevity
StartThreads();
}
private void gridUpdateTimer_Tick(object sender, EventArgs e)
{
ResourceLock.WaitOne();
try
{
if (dataGrid != null && numRows < dsRecords.Tables[0].Rows.Count)
{
if (dataGrid.RowCount > 0)
{
dataGrid.FirstDisplayedScrollingRowIndex = dataGrid.RowCount - 1;
}
dataGrid.Refresh();
numRows = dsRecords.Tables[0].Rows.Count;
}
}
catch { }
ResourceLock.Release();
}
}
}
EDIT: Hans provided the answer which is that you can't update a bound DataSet from a worker thread. So I was able to fix the problem by adding the following:
These are called by delegate functions using Invoke. Unbind called before DataSet update and Bind after DataSet is updated. This works, but causes the DataGridView to appear to flicker or redraw multiple times. Any way to remedy this?
public void UnbindDataGridView(DataGridView grid)
{
grid.DataSource = null;
}
public void BindDataGridView(DataGridView grid)
{
grid.DataSource = dsRecords.Tables[0].DefaultView;
}