Solved

DataGridView in Virtual Mode flickers when adding rows.

Posted on 2010-08-26
23
2,791 Views
Last Modified: 2012-05-10
Hello Community,

I'm fighting against a .NET 2.0 DataGridView in Virtual Mode. I'm handling its CellValueNeeded event providing data it has to show. Everything is working fine, but there is an anoying thing... When I periodically add additional rows to the DataGridView (by calling its Rows.Add() method) the entire view flickers and the cpu load rises about 10%. It flickers even if the view does not show any freshly added data!

In my oppinion DataGridView's internal row collection is changed when a row is added to the collection which results in the entire view to repaint.

Can anyone tell me how I can eliminate repainting the view in this case?

Thank you in advance!

Metacrawler
0
Comment
Question by:Metacrawler
  • 11
  • 10
  • 2
23 Comments
 
LVL 16

Expert Comment

by:Vikram Singh Saini
Comment Utility
.NET 2.0  DataGridView in Virtual Mode  - What it exactly means ?

Share your code with us and let us know what you means to say and what you are trying to do?
0
 
LVL 16

Expert Comment

by:kris_per
Comment Utility

CellValueNeeded event is called when the control needs to paint a cell...i.e. it gets called only for the cells visible on screen. So you need to keep the code in that event efficient and not to do any bit-a-long tasks...Check what is in this handler. if you can post that code, I can see if I could find anything...

Another thing is why do you need to do Rows.Add()?.....
Generally in virtual mode, it is enough to set dataGridView1.RowCount value. for example, if set it to 10 rows, the grid will get prepared for 10 rows...so when user scrolls it will keep calling CellValueNeeded event to value for the cells....

This msdn link has a good example on implementing virtual mode for datagridview; you can take a look to understand what makes your code different => http://msdn.microsoft.com/en-us/library/2b177d6d.aspx

0
 

Author Comment

by:Metacrawler
Comment Utility
Hello vs00saini,

Normally a DataGridView operates in Non-Virtual-Mode which means that every piece of data you add to the view is managed by the view itself. If you have a really large set of data (say 50.000 records with 5 values each) populating a DataGridView takes VERY long (about 40 seconds on my machine (Intel P4)). This is because each and every piece of data is formatted at this point. Due to the fact that you cannot view 50.000 records at once, processing only records that can be seen is a good idea... This is where the Virtual Mode comes into play. Setting the VirtualMode property of the DataGridView to true tells the DataGridView to call its CellValueNeeded event when it needs some piece of data. You can keep the data shown in the view under your control, so the view does not need to keep its own copy of them. This saves memory and cpu time, because the view prepares the view only for the records you are about to see.

The stupid thing is that you must tell the view how much rows and columns it has to show. Ok, columns are quite easy, because they do not change. But the number of rows grows while the application is running. The application I'm talking about is a log viewer that receives a lot of new log messages over the time. Each time I add a log message to the view the entire view flickers even if the added log message is not shown in the current view section. I cannot see a reason why the view initiates a repaint when nothing happened to the rows shown...

I hope this clarified what I'm looking for :)

Metacrawler
0
 

Author Comment

by:Metacrawler
Comment Utility
Hello kris_per,

here is the CellValueNeeded handler code. There is nothing that lasts that long...

And I need the Rows.Add() method to let the GataGridView grow over time.
private void DataGridView_CellValueNeeded(object sender, DataGridViewCellValueEventArgs e)

		{

			LogMessage message = (LogMessage )mShownMessagesList[e.RowIndex];

						

			switch (e.ColumnIndex)

			{

				case 0:

					e.Value = message.Timestamp;

					break;

				

				case 1:

					e.Value = message.ApplicationName;

					break;



				case 2:

					e.Value = message.ProcessId;

					break;



				case 3:

					e.Value = message.SourceName;

					break;



				case 4:

					e.Value = message.LogLevelName;

					break;



				case 5:

					e.Value = message.MessageText;

					break;

			}

		}

Open in new window

0
 
LVL 16

Expert Comment

by:Vikram Singh Saini
Comment Utility
Hello Metacrawler,

The information you shared with us is very nice. And thanks for sharing that with me. I was not aware of the same before.

And as far concerned about flickering issue check the below links:

(1) http://www.experts-exchange.com/Programming/Languages/C_Sharp/Q_22534845.html

(2) http://www.experts-exchange.com/Programming/Languages/.NET/Visual_Studio_.NET_2005/Q_22779088.html#a19745921

Might they help you to avoid flickering.

Regards,
VSS
0
 
LVL 16

Expert Comment

by:kris_per
Comment Utility

As mentioned in one of the links provided by vs00saini, you can try using the Suspend/Resume layout calls around the Rows.Add method like.

dataGridView1.SuspendLayout();

// Rows.Add method here

dataGridView1.ResumeLayout();

This seems to be a good option to try first...
0
 

Author Comment

by:Metacrawler
Comment Utility
Thank you for your answers!

I'm using the SuspendLayout() / ResumeLayout() pair already, but that does not change anything :-(
0
 

Author Comment

by:Metacrawler
Comment Utility
As already mentioned in my first message I think that this flickering is a result of the resizing of the internal row collection. The DataGridView does not seem to distinguish between a "real" change to the collection and resizing that does not effect rows that are already in the collection.

Hmmm, but how can we fix that little thing ?!
0
 
LVL 16

Expert Comment

by:kris_per
Comment Utility

Can you check - instead of using Add method, see just incrementing dataGridView1.RowCount like dataGridView1.RowCount++ will make any difference, as it doesn't deal with Rows collection atleast directly...

Are you using dataGridView1.Rows.Add() method or any other overload of that method? It can be checked if using other overloads will make any difference.

Is the flickering happens when one row is added or a bulk of rows...?

0
 
LVL 16

Expert Comment

by:kris_per
Comment Utility

Also if auto resizing of rows or cols or header is enabled, you can check if disabling them will make any difference (not as a solution; but to understand what exacltly causes this), as when an update occurs grid may try to resize the row/col/header....
0
 
LVL 16

Expert Comment

by:kris_per
Comment Utility
Generally .net provides double-buffering feature to avoid flickering of the controls when they are resized or updated...but it is hidden by datagridview...the following link discusses about this (probably about a way to enable double-buffering)...=> http://social.msdn.microsoft.com/Forums/en-US/winformsdatacontrols/thread/e4885e94-b5d1-480c-95c3-3c2981596741
0
What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

 
LVL 16

Expert Comment

by:kris_per
Comment Utility

This link mentions about various things as workaround to avoid flickering => http://social.msdn.microsoft.com/Forums/en-US/winformsdatacontrols/thread/c76eb3da-2d47-4fc9-bbb9-616be44a1265
0
 

Author Comment

by:Metacrawler
Comment Utility
Thank you for your answers, kris_per!


"Can you check - instead of using Add method, see just incrementing dataGridView1.RowCount like dataGridView1.RowCount++ will make any difference, as it doesn't deal with Rows collection atleast directly...

Are you using dataGridView1.Rows.Add() method or any other overload of that method? It can be checked if using other overloads will make any difference."

=> Yes, I'm using the Rows.Add() method to add rows, but setting the RowCount property causes the same problem.

// --------------------------------------------------

"Is the flickering happens when one row is added or a bulk of rows...?"

=> I've traced the calls to the CellValueNeeded event and every time new row is added, it is called for the new row (even if it is not visible) and for all visible rows (even if they are not changed).

// --------------------------------------------------

"Also if auto resizing of rows or cols or header is enabled, you can check if disabling them will make any difference (not as a solution; but to understand what exacltly causes this), as when an update occurs grid may try to resize the row/col/header...."

=> I've disabled any kind of autosizing due to performance issues.

// --------------------------------------------------

"Generally .net provides double-buffering feature to avoid flickering of the controls when they are resized or updated...but it is hidden by datagridview...the following link discusses about this (probably about a way to enable double-buffering)...=> http://social.msdn.microsoft.com/Forums/en-US/winformsdatacontrols/thread/e4885e94-b5d1-480c-95c3-3c2981596741"

=> Ok, double buffering would probably solve the flickering issue but add a delay when scrolling the view. Furthermore it would not stop the view from consuming 10% cpu just for nothing other than repainting rows that do not need to be repainted.

0
 

Author Comment

by:Metacrawler
Comment Utility
Sorry, I've said the following...

"I've traced the calls to the CellValueNeeded event and every time new row is added, it is called for the new row (even if it is not visible) and for all visible rows (even if they are not changed)."

That is wrong... Only the visible rows are repainted, NOT the freshly added row!
0
 
LVL 16

Expert Comment

by:kris_per
Comment Utility

I tried this in a test app. In a button click I added 1000 rows and in CellValueNeeded event I put a Debug.WriteLine(e.RowIndex + "-" + e.ColumnIndex); When I clicked the button, 1000 rows are added and I can see the debug lines for all the 1000 rows....that means CellValueNeeded event gets called for the all the new rows (though I am not sure why it is not called for new rows in your case. Anyway...)

Actually CellValueNeeded is called only when VirtualMode is true...so I tried setting VirtualMode = false before adding and set it back to true after like:

private void button1_Click(object sender, EventArgs e)
    {
        dataGridView1.VirtualMode = false;
        dataGridView1.SuspendLayout();
        for (int n = 0; n < 1000; n++)
        {
            this.customers.Add(new Customer("Bottom-Dollar Markets", "Elizabeth Lincoln"));
            this.dataGridView1.RowCount++;
        }
        dataGridView1.ResumeLayout();
        dataGridView1.VirtualMode = true;
    }

When I did this, CellValueNeeded event is not called for the newly added rows which would definitely avoid a big overload...

See if this (disabling VirtualMode when adding) makes any difference for you...

I am just throwing some ideas to see if any would lead to a solution....


0
 
LVL 16

Expert Comment

by:kris_per
Comment Utility

Also note after I added the line Debug.WriteLine(e.RowIndex + "-" + e.ColumnIndex); in CellValueNeeded event, the whole execution has got slowed down becasue of writing text messages in the debug output inside VS....so in your final testing, you may want to remove any debug/trace calls in that event for better performance....
0
 
LVL 16

Expert Comment

by:kris_per
Comment Utility

In my further testing...definitely double-buffering gives better result and I can see the difference in flickering with and without double-buffering...but as you said, there is a delay when scrolling the view.
0
 

Author Comment

by:Metacrawler
Comment Utility
Hello kris_per,

thank you for your answers. I tried to disable the virtual mode and re-enable it after adding some rows again. This does not keep the CellValueNeeded event from being fired for every shown cell. The problem still persists :-/

0
 

Author Comment

by:Metacrawler
Comment Utility
I've made a little repro-application which shows the behavior I'm experiencing...

All you have to do is...
1) Repeatedly click the button to add rows to the view until the view shows the scrollbar.
2) Push the window to a place where it is not covered by other windows for the rest of the test, because any repainting causes the CellValueNeeded event to be fired.
3) Clear the output log in Visual Studio.
4) Push the "Add row" button again.
5) Have a look at the output log in Visual Studio.

The application will repaint all shown rows every time a row is added to the view - not depending on whether virtual mode is enabled or not.

I wonder what I'm doing wrong while you got it working without repainting in your application...


Greetings,
Metacrawler
WindowsApplication1.zip
0
 
LVL 16

Expert Comment

by:kris_per
Comment Utility

You are right. I also got the same results (which is grid gets repainted after an update). What I meant above is when disabling/enabling virtual mode, the event is not called for new rows; but it is called for visible rows.

Also note that in my testing it gets repainted only once for all the 1000 rows; I guess this is because of suspend/resume layout calls where repaint is stopped when suspended and when resumed it gets repainted once.

If your app adds one row per batch continuously, then 'pagination' will make it lot better. I.e. when a new row comes up, first it will be added to an interim cache and when the cache is filled, the rows will be added to datagrid (so only one repainting for this set of rows); plus time-based behaviour can also be added to this like rows will be added to grid when say 30 seconds have passed since last add.

It looks to me this 'repainting of grid when an update occurs' behaviour is the way the grid internally does this...so the solution for this is it to workaround this behaviour in a way that frequent repaint doesn't occur. One way is to use the 'pagination' mentioned above based on app's flow.

Also as I mentioned earlier, 'double-buffering' can definitely be considered.





0
 

Author Comment

by:Metacrawler
Comment Utility
I see... now I understand what you intended to tell me :)

Double buffering is a nice thing to eliminate flickering, but it does not solve the problem that unnecessary repainting consumes a lot of cpu power. Depending on the number of visible rows that might be noticeable (about 10% for 50 visible rows).
0
 
LVL 16

Accepted Solution

by:
kris_per earned 500 total points
Comment Utility

With double-buffering, an enhancement you can try is to enable it only when adding, after adding you can disable, so that scrolling and other user operations will not delay....this is just a thought..you can try and see if it works..

Moreover on cpu usage, I tried switching between 3 browser windows and 2 VS windows by clicking on them in the task bar so that they will repaint themselves and I watched the cpu usage...it comes between 8-11% (once it came to 20% but only once and in another case 5% once among several rounds of clicking)...Try this and see how it shows for you...to get some understanding.

0
 

Author Comment

by:Metacrawler
Comment Utility
I know that repainting costs a lot of cpu time. That is why I'm so frustrated that the DataGridView repaints itself even if it is not necessary ;-)

I think I'll do as you say and enable double buffering when adding rows only. After this discussion with you I'm almost sure that the repainting issue is a bug in the control we cannot circumvent, but beautify its appearance.

Thank you for your help!
0

Featured Post

Do You Know the 4 Main Threat Actor Types?

Do you know the main threat actor types? Most attackers fall into one of four categories, each with their own favored tactics, techniques, and procedures.

Join & Write a Comment

Recently while returning home from work my wife (another .NET developer) was murmuring something. On further poking she said that she has been assigned a task where she has to serialize and deserialize objects and she is afraid of serialization. Wha…
Introduction Hi all and welcome to my first article on Experts Exchange. A while ago, someone asked me if i could do some tutorials on object oriented programming. I decided to do them on C#. Now you may ask me, why's that? Well, one of the re…
Here's a very brief overview of the methods PRTG Network Monitor (https://www.paessler.com/prtg) offers for monitoring bandwidth, to help you decide which methods you´d like to investigate in more detail.  The methods are covered in more detail in o…
This video shows how to remove a single email address from the Outlook 2010 Auto Suggestion memory. NOTE: For Outlook 2016 and 2013 perform the exact same steps. Open a new email: Click the New email button in Outlook. Start typing the address: …

771 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

13 Experts available now in Live!

Get 1:1 Help Now