BackgroundWorker only reporting progress when finished - c#

I have a very simple app that loads an excel file with several thousand rows into a DataGridView component. This table is then scanned for duplicates and various other issues by pressing the Scan button. I chose to have this intensive task run in a BackgroundWorker so the UI remains responsive and so I can report its progress. The code for my Scan button simply calls the RunWorkerAsync() method on the background worker component, which then does this :
Scanner scanner = new Scanner(this, dgvScancodes, dgvErroredScancodes, BgwScanner);
This calls another class to do the actual work, passing the scanner as a parameter. The Scanner class then does this :
foreach (DataGridViewRow row in table.Rows)
{
//Long computations on each row
worker.ReportProgress(row.Index / table.RowCount * 100);
}
The scan executes perfectly fine and produces the expected output but my ProgressBar never updates with the following code on the BackgroundWorker's ProgressChanged event :
private void BgwScanner_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
pgbProgress.Value = e.ProgressPercentage;
}
Thinking this could be an issue with the way I calculate the percentage, I replaced my call to ReportProgress and just sent a value of 50 to see what would happen. The progress bar does update, but only when the entire scan is completed! No exception is raised and it's behaving like the UI thread is too busy doing other things (which, aside from looping over every row in a table, it is not to the best of my knowledge). Any idea why I'm seeing this behavior?
******EDIT******
I found my culprit. I forgot that, during the scan, the table's rows can be updated with a tooltip and a background color. I commented these 2 lines and sure enough the progress bar works perfectly fine now. This proves my theory that indeed the UI thread is being overwhelmed. Is there potentially a way around this? Some sort of higher priority for the progress bar updates?

For integer division the value rounds towards zero to the next nearest integer.
In your case it will always round towards exactly zero before being multiplied by 100, since row.index is presumably between 0 and table.RowCount - 1.
Being totally verbose:
row.Index / table.RowCount * 100
Could become:
(int)(((double)row.Index / (double)table.RowCount) * 100)

I addressed this issue by doing the processing on the underlying DataSource for my tables until the very last minute when it needs to be displayed. This way there are no UI updates until the table is displayed. I still need to iterate over the table itself to set the background color and tooltip for certain cells at the end (since you obviously can't do this on the DataSource itself) but that takes a relatively short time compared to the processing that is being done on the DataSource beforehand. My ProgressBar now works properly.
******EDIT******
You can actually use the DataGridView's CellFormatting event to color cells, no need to iterate over the rows and this has the bonus effect of keeping the tooltip and the background color when re-ordering the table :
private void dgvErroredScancodes_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
{
DataGridViewCell scancodeCell = dgvErroredScancodes.Rows[e.RowIndex].Cells[1];
if (e.Value.Equals((int) ErrorType.DUPLICATE))
{
scancodeCell.Style.BackColor = ErrorType.DUPLICATE.BackgroundColor();
scancodeCell.ToolTipText = ErrorType.DUPLICATE.ErrorMessage();
}
...
}

Related

Winform numericUpDown have different delay for up and down value change

I have just started studying .Net Winform with C# (.Net 5). I did a simple winform with a NumericUpDown and a ProgressBar, basically, the ProgressBar will update its value when the NumericUpDown's value changes via the ValueChanged event.
Here is my code:
private void numericUpDown1_ValueChanged(object sender, EventArgs e)
{
progressBar2.Value = (int)numericUpDown1.Value;
}
I notice that the ProgressBar update took about half a second to detect the NumericUpDown ValueChanged when the value is increasing (from 0 to 1, 1 to 2, 2 to 3, etc.) no matter whether I was holding the Up key to increase it continuously or press one at a time.
However, when the value is decreasing, the progress bar update instantly, even when I hold down the Down button to decrease it continuously. Which is pretty weird.
Even when I enter the value directly, it still does the same thing: enter 80 when it is at 20 took a bit to fill up while enter 20 when it is 80 update instantly
The most obvious way to see it is holding the Up button to continuously increasing the value and then press the Down key once. The Progress Bar fill up slowly and then suddenly jump to the correct value when the Down key is pressed
My question is: Is this the default behavior? Does Visual Studio had different algorithm to detect increase or decrease value change? Or am I missing something?
I am pretty sure what you are seeing is the default behavior of the ProgressBar Control. When the progress bar increases, there is a small “delay” to show each step. I may be mistaken; however, I could not find any particular property in the ProgressBar control that would remove or shorten this “delay.”
Fortunately, I did find a fairly simple solution that appears to work. In this solution the idea is to “increase” the current value from the NUD by one (1). Then set the progress bars value to that value, then immediately change the progress bars value back to the true value.
The only issue is if the NUDS value is at the maximum of the progress bar. If we try and increase the NUDS value by 1, then we would go out of bounds on the progress bar. So, in that case we simply need to increase the progress bars maximum value by 1, set the value, then immediately set it back to the original maximum value.
See if the code below works as you are wanting. Please let me know as I will remove it if it does not work as you are wanting.
private void numericUpDown1_ValueChanged(object sender, EventArgs e) {
//progressBar1.Value = (int)numericUpDown1.Value;
if ((int)numericUpDown1.Value == progressBar1.Maximum) {
progressBar1.Maximum = progressBar1.Maximum + 1;
progressBar1.Value = (int)numericUpDown1.Value + 1;
progressBar1.Value = (int)numericUpDown1.Value;
progressBar1.Maximum = progressBar1.Maximum - 1;
}
else {
progressBar1.Value = (int)numericUpDown1.Value + 1;
progressBar1.Value = (int)numericUpDown1.Value;
}
}

Timer MinValue, MaxValue, and Value in C#

Ok I know this sounds odd, but to be honest, I do remember solving an example with timers in c# where I set the timer's minvalue, maxvalue, tick, interval etc... yet I don't remember/don't know how to do that it again, MVS 2019 won't show such parameters.
Any help would be really appreciated.
I am trying to start a row based on the value of the 7th index AFTER the datagridview (dg) is fully loaded from the database but, it is not working properly, either some rows are not loaded or it loaded but the O Complexity is VERY LOW (Program response is very BAD).
private void Dg_CellPainting(object sender, DataGridViewCellPaintingEventArgs e)
{
OleDbConnection con = GetConnection();
OleDbCommand cmd = new OleDbCommand("SELECT * FROM Items", con);
try
{
OleDbDataReader reader = cmd.ExecuteReader();
while (reader.Read())
{
string PartName = reader.GetValue(0).ToString();
string SN = reader.GetValue(1).ToString();
string ModNum = reader.GetValue(2).ToString();
string TS = reader.GetValue(3).ToString();
string Man = reader.GetValue(4).ToString();
string Brand = reader.GetValue(5).ToString();
string Price = reader.GetValue(6).ToString();
string Quan = reader.GetValue(7).ToString();
string IPP = reader.GetValue(8).ToString();
string HeadMat = reader.GetValue(9).ToString();
string Descibtion = reader.GetValue(10).ToString();
string MinQuan = reader.GetValue(11).ToString();
string MaxQuan = reader.GetValue(12).ToString();
string PricePI = reader.GetValue(13).ToString();
string ExpiryDate = reader.GetValue(14).ToString();
string USDRate = reader.GetValue(15).ToString();
}
reader.Close();
con.Close();
}
catch (OleDbException ex)
{
MessageBox.Show(ex.Message.ToString(), "Error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
if (GetConnection().State == ConnectionState.Closed)
{
timer1.Start();
Thread thread = new Thread(Blink);
thread.Start();
timer1.Stop();
}
}
Without this piece of code, the program runs just fine no problems what so ever
Here is the Blink method to handle the rows colour change and blinking.
private void Blink(object o)
{
if (dg.Rows.Count != 0)
{
while (dg.Rows.Count >= 0 && go)
{
foreach (DataGridViewRow row in dg.Rows)
{
int value = int.Parse(row.Cells[7].Value.ToString());
if (value == 0 || value == 1)
{
row.DefaultCellStyle.BackColor = Color.Red;
}
else if (value > 1 || value < 4)
{
row.DefaultCellStyle.BackColor = Color.Yellow;
}
else
row.DefaultCellStyle.BackColor = Color.Green;
}
go = true;
Thread.Sleep(100);
while (dg.Rows.Count != 0 && !go)
{
foreach (DataGridViewRow row in dg.Rows)
{
int value1 = int.Parse(row.Cells[7].Value.ToString());
if (value1 == 0 || value1 == 1)
{
row.DefaultCellStyle.BackColor = Color.White;
}
else if (value1 > 1 || value1 < 4)
{
row.DefaultCellStyle.BackColor = Color.White;
}
else
row.DefaultCellStyle.BackColor = Color.White;
}
go = false;
Thread.Sleep(1000);
}
}
}
}
I do not want to dissuade you from doing this, however, if you will indulge me for a moment as I am having a difficult time trying to understand “why” you would want the rows to “blink” for a short amount of time. This seems odd to me as explained below.
I am somewhat leery of the “blinking” rows. I am not sure how this
“blinking” of the rows for 5 seconds “HELPS” the user considering the
row color is changed to a particular color in the end. I personally
think the “blinking” is distracting/annoying and serves no purpose.
Granted, possibly a “few” rows “blinking” when the data is loaded to
indicate a problem with those rows. But even then, it is easy to
imagine the user looking away at that moment and miss the “blinking”
rows and what about the rows that are below the visible grid size?
“Blinking” those rows makes no sense. So, I am not understanding “what
purpose” this “blinking” rows serves? I would think that it would make
more sense if the “blinking” never stopped until the user corrects the
offending data.
With that said, I suggest a closer look at what you currently have code wise. This code approach may well work, however in my tests, I am confident that the UI is going to act sluggish and will be slow to respond. Example, if you scroll the grid in either direction, you will notice how the display is sluggish and jumpy and not smooth as expected. Also, if you edit a cell and press the “Enter” key, you will notice a lag… it will take a second or two to drop down to the next cell.
This is mainly coming from the fact that the Blink code is executed FROM the grid’s CellPainting event. The grid’s CellPainting event gets fired OFTEN. It will fire when none of the cells have changed. Example, if the user “scrolls” the grid. Because of this, the Blink code will run unnecessarily since no cells have changed. In other words, the Blink method may run and change the rows back color when the rows are already the proper color. This is causing the sluggishness. The code is running the Blink method many more times than is needed.
You may be saying… “well I run the Blink method under another thread.” … with…
Thread thread = new Thread(Blink);
thread.Start();
And you would be correct… HOWEVER, this is actually “creating” even more problems in relation to sluggishness of the UI. If you step back and look at this, you should ask yourself… “How many threads do I need?” … This code is literally “creating” hundreds of threads, uses them ONCE and never disposes of them.
Example, let’s say the data has three (3) columns with twenty (20) rows. While loading the data into the grid, the grid’s CellPainting event will fire at least sixty (60) times (once for each cell). (I am confident it will actually fire more than this), however, even at 60 times, this will mean the code is creating at least 60 “separate” threads that never get disposed. Unabated, a crash should not be surprising.
You may somewhat alleviate this, if somewhere, the code properly disposes of the threads, however, in the codes current state, this is not possible since the Thread is “created”, started, and is never disposed in the same if statement. The variable thread will go out of scope (without properly being disposed) as soon as the if statement is exited.
Granted, once out of scope, the GC MAY dispose of this, however, I wouldn’t count on it to be quick about it. The main point here, is that starting multiple Threads for this just seems unnecessary and is problematic. In addition, my understanding is that when using win forms, it is better to use a BackGroundWorker instead of a Thread.
I am simply alluding to the fact that… if you DO use a thread/background worker for this, then only ONE background worker is really needed. We could use the same background worker over and over. Since it takes milliseconds for the code to execute, the worker will be done quickly. I could be mistaken, but I just do not see the need for a background worker here. In some cases, it may be needed and a whole different approach would be required.
Blink code …
In reference to the Blink code… it may well work, however, the user will never see this “changing” of the rows color. The user will not see the color changes as the code does not “Refresh” the grid. I assume this is why the sleep statement is there. The grid will not “automatically” refresh until the code exits the Blink method. The user will only see the final product. The user will not see the row colors change and the rows will appear to remain white.
You may consider simply calling the grids Refresh method, then sleep execution for a few milliseconds to allow the user to “see” the row color change. Unfortunately, this is not going to work. Blink is being called from the grids CellPainting event. If we call the grids Refresh method from Blink… that is going to “re-fire” the grids CellPainting event. A re-re-entrant error is almost guaranteed. This same idea would apply when the Blink method sets a rows color. This may re-fire the grid’s cell painting event again. All the more reason to avoid using this event.
Given all this and emphasizing my first comment, is should be clear that using the grids CellPainting event for this is probably not the best approach for numerous reasons. Most grid “painting” events fire often and for what the code is doing, we end up running the code more times than needed.
In this case, if the current code did execute, it is going to continue the “blinking” of the rows even after 5 seconds. Every time the grids CellPainting event fires, the code will call the Blink method and rows will start blinking. Eventually, the grid will start flashing like a Christmas tree and the UI will start to stall.
The main thing here, is that when using the “GRIDS” painting events, they are firing many more times than we need and we CAN get around this, however, considering that you only want the rows to “blink” for 5 seconds after the data loads, then after 5 seconds, why have the grid worry about it? This is something that should run for 5 seconds, then stop. So, bringing in the grids events seems like overkill. It can be done, but why. After the data loads, start a 5 second timer, start blinking the rows… then, after five seconds, stop the rows from blinking… done.
Given this, my approach does not use any of the grid’s events (yet). Instead of using the grids CellPainting event to call the Blink code… Let us go with your initial suggestion of using Timers. We could use two (2) timers. One timer will have an interval of 5000 and will keep track of the 5 seconds we want the rows to “blink” after the data is loaded into the grid. The second timer would have an interval of say 500 or 1/2 of a second. With each tick of this timer, we would call the Blink method to “toggle/blink” the row colors.
A small trace of the current Blink code…
If we start at the first foreach loop through the grid rows, if the cell at column 7 is a zero (0) or a one (1), then the code will color that row red. On the else portion we have… if (value > 1 || value < 4) … which I am confident you mean && to check for the numbers 2 and 3. Otherwise, you need to explain how this would ever be false. Assuming we want to use &&, this will color the row yellow when cell 7 values are 2 or 3.
Then if the cells value is not 0, 1, 2 or 3, then set that row color to green.
Continuing, the code needlessly freezes the UI and “sleeps” for 100 milliseconds. Then the code again starts a foreach loop through the rows in the grid. Here, I do not follow the logic, if ALL the rows are being set to white, then why check the value of cell 7? Checking for the value in cell 7 is unnecessary since the code is setting the rows to the same color (white).
It would appear, that a simpler approach would be to either “color” ALL the rows based on the cells value in column 7… OR … color ALL the rows white. This should simplify the code and we can use the timer to tell the code “which” way to color the rows… i.e., color the rows based on column 7 or color the rows white.
To help, we can create a global bool variable and call it ColorOn. We can use this variable to help us “toggle” the color state of the rows. If ColorOn is true and we run the Blink code it will color the rows based on the cell value at column 7. If ColorOn is false, then Blink will color the rows white.
Since we may be looping through the grids rows several times to color the rows, it may help if we create a method called SetRowColor that takes a DataGridViewRow and a Boolean value to indicate if we should make the row color “white” or color the row based on column 7. This method will come in handy when the code loops through the grid rows and changes the row color.
The SetRowColor method is below. First the code checks for the “new” row… we will ignore the grids “new” row if it exists. Next, a check is made on the bool white parameter. If it is true, then color the row white. If it is false, then color the row based on the value in column 7.
If we want to color the row based on the cell value in column (7) “TargetColumn.” A TryParse is used to safely validate the cells value as a number and place that number in the out variable value. Next a switch statement is used on the value variable to determine which color to set the row. 0 or 1, red; 2 or 3 yellow and any other number green.
private void SetRowColor(DataGridViewRow row, bool white) {
if (row.IsNewRow) {
return;
}
if (white) {
row.DefaultCellStyle.BackColor = Color.White;
}
else {
if (int.TryParse(row.Cells["TargetColumn"].Value.ToString(), out int value)) {
switch (value) {
case 0:
case 1:
row.DefaultCellStyle.BackColor = Color.Red;
break;
case 2:
case 3:
row.DefaultCellStyle.BackColor = Color.Yellow;
break;
default:
row.DefaultCellStyle.BackColor = Color.LightGreen;
break;
}
}
}
}
We can use the SetRowColor method in the new Blink method and it may look like below. Following the code, a check is made on the ColorOn variable...
If true, then loop through all the rows and set each row color based on the cells value in column 7.
If false, then loop through all the rows and set each row color to white.
We will call this Blink method in the MilliSecondTimer_Tick event.
private void Blink() {
if (ColorOn) {
foreach (DataGridViewRow row in dg.Rows) {
SetRowColor(row, false);
}
}
else {
foreach (DataGridViewRow row in dg.Rows) {
SetRowColor(row, true);
}
}
}
This looks cleaner and it will take milliseconds to execute.
Now that we have the Blink method set up, next we need a Timer to call it. This timer’s interval needs to be set such that it defines how “often” we want the rows color to toggle. Example, if we set the interval to 1 second, then the rows will change color every second. With each tick, we call Blink, then toggle the ColorOn variable to change the color next time around. This MilliSecondTimer_Tick event may look like…
private void MilliSecondTimer_Tick(object sender, EventArgs e) {
Blink();
ColorOn = !ColorOn;
}
If we run this, the timers tick event will fire at every interval and toggle the row colors but it does not stop after five seconds. It is possible to “check” the amount of time that has elapsed since the timer was first started, and if it is greater than 5 seconds, then stop the timer. This is doable; however, I am going the lazy route and use a second timer with an interval of 5 seconds.
Start this five second timer at the same time as the “blink” timer. Then, after 5 seconds has elapsed, its tick event will fire where we can turn off the “blink” timer and also stop its own timer as it is no longer needed. Since we want the rows to “remain” in a colored state after the “blinking” we need to set the ColorOn to true and call the Blink method one last time. Simply stopping the timers may leave the grid in a state such that ALL the rows are colored white. This FiveSecondTimer_Tick event may look something like…
private void FiveSecondTimer_Tick(object sender, EventArgs e) {
MilliSecondTimer.Stop();
FiveSecondTimer.Stop();
ColorOn = true;
Blink();
}
A helper method is used to set up and start the global Timers. We set the interval, subscribe (wire up) to the timer’s tick event and start the timers. We will call this method right after the data is loaded. To note, if you decrease the MilliSecondTimer interval the rows will “blink” faster and obviously, a larger number will “blink” slower. The five second timer’s interval is set to 5 seconds. Increasing its interval will allow the rows to continue “blinking” longer than 5 seconds.
private void StartTimers() {
FiveSecondTimer.Interval = 5000;
MilliSecondTimer.Interval = 500;
FiveSecondTimer.Tick += new EventHandler(FiveSecondTimer_Tick);
MilliSecondTimer.Tick += new EventHandler(MillSecondTimer_Tick);
MilliSecondTimer.Start();
FiveSecondTimer.Start();
}
A method to get some test data GetDT is created. A DataTable is used as a data source to the grid. It has three (3) columns, such that the “target” column we want to check the values with is in column 2. Its name is ”TargetColumn” and this name is used in the SetRowColor method instead of the column index number. Then the table is filled with thirty (30) rows, such that the TargetColumn values will come from a random number generator with random numbers from 0 to 6.
private DataTable GetDT() {
DataTable dt = new DataTable();
dt.Columns.Add("Col0", typeof(string));
dt.Columns.Add("TargetColumn", typeof(int));
dt.Columns.Add("Col2", typeof(string));
Random rand = new Random();
for (int i = 0; i < 30; i++) {
dt.Rows.Add("COR" + i, rand.Next(0, 7), "C2R" + i);
}
return dt;
}
Putting all this together…
To put all this together and test, the code below should demonstrate what is described above. Create a new win forms project, drop a DataGridView onto the form as shown above and re-name the grid to dg using the OPs grid name… dg. Running the code should “blink” the rows in the grid for 5 seconds and after the 5 seconds will leave the rows in a colored state.
DataTable GridDT;
bool ColorOn = true;
System.Windows.Forms.Timer FiveSecondTimer = new System.Windows.Forms.Timer();
System.Windows.Forms.Timer MilliSecondTimer = new System.Windows.Forms.Timer();
private void Form1_Load(object sender, EventArgs e) {
GridDT = GetDT();
dg.DataSource = GridDT;
StartTimers();
}
What happens if the user “changes” the value in one of the target column’s cells?
Now that we have the coloring and the blinking of the rows done, you may ask yourself, ”What happens if the user (or code) changes one of the cells values that is in the “target” column?” The color will not change and we may have to run the Blink code again.
This is where one of the grids events will come in handy. The grids, CellValueChanged event will fire when the user changes a cells value and tries to “leave” the cell. If we wire up (subscribe) to this event, we could run the Blink code again. But this seems like overkill. In other words, only ONE (1) cell has changed, it seems unnecessary to loop through ALL the rows. And we already know which row it is.
In this case, we only want to change the row color when a “TargetColumn” cell changes value. If the other column value’s change, we can ignore those. We are only concerned with the “TargetColumn” cells changing. Below is the identical code as the Blink code however it does not loop through all the grid rows… it only re-colors the row that changed. Using the SetRowColor method makes things a little easier.
private void dg_CellValueChanged(object sender, DataGridViewCellEventArgs e) {
if (dg.Columns[e.ColumnIndex].Name == "TargetColumn") {
if (dg.Rows[e.RowIndex].Cells["TargetColumn"].Value != null) {
SetRowColor(dg.Rows[e.RowIndex], false);
}
}
}
This should complete the example. Please forgive my long-winded rant. I hope it makes sense and helps.

DataGridView DataSource: Slow Assignment

I have inherited a form and have been tasked with speeding it up. Basically, it is a form which makes a WCF call to a service for some data, and then displays the data in a DataGridView.
I originally thought the bottleneck was the WCF call, so I moved that to a BackgroundWorker, to stop the UI from freezing up.
That helped a little bit, but it turns out that the bottleneck is actually this line:
DumpInfoGrid.DataSource = dumpGridBinding;
where the data retrieved from the WCF is assigned to the DataSource property.
The code basically boils down to this:
private BackgroundWorker dataGatherer;
dataGatherer.DoWork += dataGatherer_DoWork;
dataGatherer.RunWorkerCompleted += dataGatherer_RunWorkerCompleted;
private void dataGatherer_DoWork(object sender, DoWorkEventArgs e)
{
dumpGridBinding = WCF.GetDataFromService();
}
private void dataGatherer_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
DumpInfoGrid.DataSource = dumpGridBinding;
}
Is there anything I can do to speed this up? Or any part I can move to the BackgroundWorker ? The assignment freezes the UI for far too long.
One possible solution for this would be to use FastDataGridView (or FastDataListView) which is found in ObjectListView, which greatly improves the speed of the DataGridView control by not attempting to process all the records at one time.
Another solution would be to page the DataGridView you currently have, spreading out the load time across the pages as you use them as seen here
You could use virtual mode -
MSDN: http://msdn.microsoft.com/en-us/library/15a31akc.aspx
This mode shows just the rows the user is looking at through the viewable region - you only load the rows actually being viewed - which is fast.
You handle the CellValueNeeded event and provide data from your list when the user scrolls through the list. Tex example shows you how to do that, but it's easy to write a linq statement to skip, take whatever is requested from your datasource (returned from the WCF service).

Struggling to get ProgressBar updated (without threading)

i have a program(winform) that runs through a lot of files(in the area of 400 some times) in a folder and to show its progressing i use a progress bar. the info get posted into a listview if that makes a difference. The problem i am having is that when the pc is slow, im working with folders over a network or if it is a really big amount of files the ProgressBar just stops updating. if n leave the program it will continue and finish, but the progress bar gets stuck at some stage. And then only shows that it is complete (additional note: i have noticed that isnt just the progress bar. sometimes it is the whole form. but again, when the program is done it is fine. and while it is working you can move the form, just not interact with it)
Now here is where it gets tricky. I know it can be solved by using threads. i have however been told not to use it(let us not go into the why, lets just except it, whether it is stupid or not). i have also tried to refresh the form, and to refresh the bar itself. none of it seems to work. it isn't a good idea to use it, but even tried .DoEvent, it also doesn't work
How can i get the progress bar to update? any ideas?
here is some of the code i use for the progress bar
//before the loop
progress = iCount1;
progressvalue = 0;
double increment = 100 / (double)progress;
//this is at the end of the loop
progressvalue = progressvalue + increment;
then there is a bit of code. some of it to stop the value from going out of bound. the rest not related to the progress bar at all
edit: o, i have that 1 variable. and it basically runs over 3 loops. so that we have one continuous bar. not the bar running 3 times
edit: it seems to work fine for folder on my pc. but when it is run on a slow pc or over a network, that is when it does this
EDIT:Im getting answers about backgroundworker and threads. so is there no way to do it without threads?
Update: i finally convinced my boss to do it with backgroundworker. so ill be doing it in that. but for this question i guess the .DoEvent answer is the best
When the form-thread is busy, it will be very unresponsive and not draw the form at all. Even if you dont want threading i think you ought to reconsider..
I would use a backgroundworker as in this example: here it is a quite simple setup.
You could add an Application.DoEvents() then.
For example:
while(!end)
{
//You processing logic here
Application.DoEvents();
}
Note that it is generally discouraged since it will break the normal flow of events and may have unpredicted results.
http://msdn.microsoft.com/en-us/library/system.windows.forms.application.doevents.aspx
http://www.codinghorror.com/blog/2005/08/is-doevents-evil-revisited.html

c# DatagridView Control (VirtualMode) and Threading

I have a weird issue with the DataGridView control in virtual mode and threading. I use a Cache class to stored 2 pages of data. When I scroll down and I have to load a new page in cache, there is a little delay before data is display (it's ok). But, instead of having
the next row displayed(199th,200th,201th), the control skip many rows (CellValueNeeded). By example, I cached 1 to 200 rows, when I scrolling at 199, I'm loading a page to 200 à 299. However, in the datagrid it's display the row 320.. instead of 300. (I kept the mouse button pressed 2-3 secondes, before releasing it). I notice I have the problem only when I load the cache in a particular thread. Does anyone have a idea how I can fix this?
private void datagridView_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)
{
e.Value = m_rfiCache.RetrieveElement(e.RowIndex, e.ColumnIndex);
}
....
public DataTable SupplyPageOfData(int lowerPageBoundary, int rowsPerPage)
{
e : " + lowerPageBoundary);
DataTable dt = new DataTable();
if (m_useThreading) //THIS DOESN'T WORK WELL
{
Thread MulThread = new Thread(delegate()
{
dt = this.LoadData(lowerPageBoundary, rowsPerPage);
});
MulThread.Start();
MulThread.Join();
}
else //OK.
{
dt = this.LoadData(lowerPageBoundary, rowsPerPage);
}
return dt;
}
P.S. : This is only a snippet, I used an external API to extract data from ODB. This API used thread to load data.
Does anyone have a idea or I can fix this?
You can load the data in a background thread, but you can't add it to your main DataTable in the background thread itself.
One potential approach would be to load the data as you're doing, but into a separate DataTable. You could then, on the UI thread, call DataSet.Merge to merge this data in with your bound data correctly (which will be reasonable fast).
Finally, I found the solution. When the tread is processing and the scroll down button is pressed, my event for scrolling aren't sent to the datagrid unless my thread is finished. So I received a bunch of calls for drawing cells and the index was wrong. To avoid this, I removed the vs scrollbar from the datagridview and added a VSscrollbar control. When I'm loading data in my thread, I set to false the enable property on my scrollbar. When the thread is completed, I enable the scrollbar. Is not the BEST solution, but at least, there is now row skipped.
Note : Virtual Mode works very well if you don't load your data in a separate thread.

Categories