Click here to Skip to main content
Click here to Skip to main content

Cell Blink for DataGridView

By , 4 May 2008
 
Screenshot - DataGridViewCellBlink.jpg

Introduction

After reading many articles on The Code Project, I realized that it's my time to contribute. Few months ago, I came across a requirement for cells in a DataGridView control to blink when the cell value changed. The code presented here can be applied to any other grid.

The blinking of the grid cell is achieved in the following manner. When we update the value of a cell, we also change the background color of that cell to a blink color. To restore the cell background color to its original value, we run a background thread that iterates through a list of cells that are blinking and resets them to their original non blinking state.

The Code

The sample project has two functions. The first function DataInputThreadFunc() is used to generate random values to be filled / updated in the grid. The second function GridBlinkThreadFunc() is used to restore the cells to the non blink state.

Let's take a look at the first function DataInputThreadFunc():

private void DataInputThreadFunc()
{
    Random rand = new Random();
    while (true)
    {
        if (dataGridView1.IsDisposed)
            break;

        CellData data = new CellData();
        data.Row = rand.Next(0, 7);
        data.Col = rand.Next(0, 3);
        data.Time = DateTime.Now;

        int value = rand.Next(0, 101);

        dataGridView1.Invoke((MethodInvoker)delegate()
        {
            dataGridView1.Rows[data.Row].Cells[data.Col].Value = value;
            dataGridView1.Rows[data.Row].Cells[data.Col].Style
              .BackColor = Color.Salmon;
        });

        lock (_blinkData)
        {
            _blinkData.Add(data);
        }

        Thread.Sleep(1000);
    }
}

The function uses a while (true) loop as it's a background thread and will be shutdown automatically when the application is closed. if (dataGridView1.IsDisposed) check is done to make sure we do not call dataGridView1.Invoke() on a disposed object. This can happen when the user closes the application.

Next, we initialize an object of the class CellData to store the blink data:

class CellData
{
     public int Row;
     public int Col;
     public DateTime Time;
}

This class is used to store the row number, column number and the time when the value changed.

Next we use dataGridView1.Invoke() to make a call to the user interface thread and set the grid properties. We save the blink data in a generic list to be used later by the blink thread function. Since the list is altered by more than one thread, we synchronize access by locking the list on each access.

Now let's take a look at the blink thread function:

private void GridBlinkThreadFunc()
{
    while (true)
    {
        // Make a copy to avoid invalid operation exception
        // while iterating through the map
        List<CellData> tempBlinkData;
        lock (_blinkData)
        {
            tempBlinkData = new List<CellData>(_blinkData);
        }

        foreach (CellData data in tempBlinkData)
        {
            TimeSpan elapsed = DateTime.Now - data.Time;
            if (elapsed.TotalMilliseconds > 500) // 500 is the Blink delay
            {
                if (dataGridView1.IsDisposed)
                    return;

                dataGridView1.BeginInvoke((MethodInvoker)delegate()
                {
                    dataGridView1.Rows[data.Row].Cells[data.Col]
                      .Style.BackColor = dataGridView1.Columns[data.Col]
                      .DefaultCellStyle.BackColor;
                });

                lock (_blinkData)
                {
                    _blinkData.Remove(data);
                }
            }
        }

        Thread.Sleep(250); // Blink frequency
    }
}

At the very beginning, we make a copy of the _blinkData list. This helps us to modify the list while we iterate through the contents of the temporary copy. For each cell we find in the list, we check to make sure whether the blink time has elapsed or not. In this case, the blink time is 500 milliseconds. Any cell that has elapsed the blink time gets its background color reset to the default cell style background color and is removed from the list.

Again we make sure that we set the grid property only in the user interface thread. In addition, we lock the _blinkData list before altering it. Thread.Sleep(250) is the frequency with which we go through the list to turn off the cells. Ideally, it should be half the value of blink delay.

Points of Interest

You will notice that this code can be applied to any grid. This code can also be hidden in a class that extends a DataGridView control.

One thing I love about .NET 2.0 is dataGridView1.Invoke((MethodInvoker)delegate(). This statement lets you get away from writing a function and declaring a delegate.

A good point was made by "Kristof Verbiest" about the use of BeginInvoke() instead of Invoke(). The GridBlinkThreadFunc() uses BeginInvoke() to avoid unnecessary context switch.

History

  • 09/06/2007: First published
  • 09/11/2007: Changed the GridBlinkThreadFunc() to use BeginInvoke() instead of Invoke()
  • 05/02/2008: Edited the "Points of Interest" section

License

This article, along with any associated source code and files, is licensed under The zlib/libpng License

About the Author

Rammohan Raja
Architect
India India
Member
Nothing to brag about! Just another passionate software developer!Smile | :)
 
Work to make a living, don't live to work!

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board.
Search this forum  
    Spacing  Noise  Layout  Per page   
GeneralPerformance issuememberHoa Le10 Nov '08 - 15:19 
Your code works well but on my PC(with Intel 2 core 2.67Ghz CPU and 2Gb RAM) the CPU usage is ALWAYS about 10%. I think that's an issue especially when it comes to real world app with much more cells and much more processing.
 
Any suggestion will be appreciated
GeneralRe: Performance issuememberRaghavan Ram Raja14 Nov '08 - 3:21 
Okay so you are having performance issues. First let us clarify how you are measuring your performance metrics and in what environment? If you are using the windows task manager then keep in mind that 10% usage could be anything in the application including the blink logic. Second to check performance, please make sure you are running a “release” build outside the IDE. I have seen tons of developers in my old organization who ran the application in the IDE debugger and claimed poor performance!Smile | :) Again you should be using a profiler to identify the function that takes the most CPU time.
 
Let us assume it is the blink logic that takes 10% CPU. Then it could be because the grid size is very big, let’s say more than 1000 rows and 50 columns. In this case trying to blink the entire table makes no sense. You have to blink only the cells that are visible to the user. Every grid comes with a property or function to tell you what the top visible row is and the size of the client area. Using this information you can find out how many rows are visible and blink only those rows.
 
The second cause could be you are updating your grid very fast and hence a lot of blink action. Updates faster than say 500 ms will be nothing more than Christmas lights!Smile | :) You will need to increase the blink frequency.
 
Ideally updating only the visible cells will bring down the CPU usage drastically and you can ignore the second cause. Let me know how it goes. All the best!
GeneralRe: Performance issuememberHoa Le20 Nov '08 - 17:27 
Thank you very much for your suggestions.
 
Actually, our datagrid has only some 10 rows * 4 columns to blink. And you're right, our datafeed pushes data to the grid too fast (about 4 times/sec/cell). That maybe the reason for the poor performance. Moreover, we have to push data from ThreadPool.QueueUserWorkItem. I think that will cause a lot of switch contexts to the UI thread to update the cells which seriously hit the performance.
 
I've come up with a little trick that slightly improve the performance (on my PC, the CPU usage drop from 10% to 7-8%). When a cell need to be updated, I don't set the color right away but insert that cell into another list. Then in the grid blink thread I set the color of each cell in this list and remove it and then insert it into the blink list.
 
That trick only helps to reduce switch contexts so I think we need a better approach to the issue.
GeneralVB.NET Version Here (Visual Studio 2005)membergratro14 Oct '08 - 2:26 
A great Article.. (I have converted the code to VB for those VB coders amongst us)
 
Use this in the Form1.vb code.....
 
Imports System
Imports System.Collections.Generic
Imports System.ComponentModel
Imports System.Data
Imports System.Drawing
Imports System.Text
Imports System.Windows.Forms
Imports System.Threading
 
Public Class Form1
 
Private _dataInputThread As Thread = Nothing
Private _gridBlinkThread As Thread = Nothing
Private _blinkData As List(Of CellData) = Nothing
 
Public Class CellData
Public Row As Integer
Public Col As Integer
Public Time As DateTime
End Class
 
Public Sub New()
InitializeComponent()
_blinkData = New List(Of CellData)()
DataGridView1.RowCount = 7
End Sub
 
Private Sub Form1_Load(ByVal sender As Object, ByVal e As EventArgs) Handles Me.Load
_dataInputThread = New Thread(New ThreadStart(AddressOf DataInputThreadFunc))
_gridBlinkThread = New Thread(New ThreadStart(AddressOf GridBlinkThreadFunc))
 
_dataInputThread.IsBackground = True
_gridBlinkThread.IsBackground = True
 
_dataInputThread.Start()
_gridBlinkThread.Start()
End Sub
 
Private Sub DataInputThreadFunc()
Dim rand As New Random()
While True
If DataGridView1.IsDisposed Then
Exit While
End If
 
Dim data As New CellData()
data.Row = rand.[Next](0, 7)
data.Col = rand.[Next](0, 3)
data.Time = DateTime.Now
 
Dim value As Integer = rand.[Next](0, 101)
 
If InvokeRequired Then
DataGridView1.BeginInvoke(SetCell(data, value))
End If
 
SyncLock _blinkData
_blinkData.Add(data)
End SyncLock
 
Thread.Sleep(1000)
End While
End Sub
 
Private Function SetCell(ByRef Data As CellData, ByVal value As Integer)
DataGridView1.Rows(Data.Row).Cells(Data.Col).Value = value
DataGridView1.Rows(Data.Row).Cells(Data.Col).Style.BackColor = Color.Salmon
Return Nothing
End Function
 

Private Sub GridBlinkThreadFunc()
While True
' Make a copy to avoid invalid operation exception
' while iterating through the map
Dim tempBlinkData As List(Of CellData)
SyncLock _blinkData
tempBlinkData = New List(Of CellData)(_blinkData)
End SyncLock
 
For Each data As CellData In tempBlinkData
Dim elapsed As TimeSpan = DateTime.Now - data.Time
If elapsed.TotalMilliseconds > 500 Then
' 500 is the Blink delay
If DataGridView1.IsDisposed Then
Return
End If
 
If InvokeRequired Then
DataGridView1.BeginInvoke(ClearCell(data))
End If
 
SyncLock _blinkData
_blinkData.Remove(data)
End SyncLock
End If
Next
 
' Blink frequency
Thread.Sleep(250)
End While
End Sub
 
Private Function ClearCell(ByVal Data As CellData)
DataGridView1.Rows(Data.Row).Cells(Data.Col).Style.BackColor = DataGridView1.Columns(Data.Col).DefaultCellStyle.BackColor
Return Nothing
End Function
 
End Class
GeneralRe: VB.NET Version Here (Visual Studio 2005)memberRaghavan Ram Raja14 Oct '08 - 3:07 
Thanks gratro. I am honored!
GeneralBlinkmembertxALI29 Jan '08 - 4:16 
I think it is smarter to add some animated .GIF "bullet" instead of such sleeping/etc
GeneralSome commentsmemberKristof Verbiest10 Sep '07 - 20:16 
1) It would be better to use BeginInvoke instead of Invoke.
For more info look here: http://kristofverbiest.blogspot.com/2007/02/avoid-invoke-prefer-begininvoke.html[^]
 
2) Why don't you use a System.Windows.Forms.Timer? This will automatically be executed on the GUI-thread so there won't be any threading issues (so it won't be necessary to take a copy of the map).
GeneralRe: Some commentsmemberRam Mohan Raja11 Sep '07 - 4:42 
1) I do agree that in the GridBlinkThreadFunc() I should use BeginInvoke() instead of Invoke() to avoid unnecessary context switch. I will update the code in the article. I disagree with your suggestion of avoiding Invoke() completely. Lets take a look at my other thread function DataInputThreadFunc(). Here I would insist on using Invoke(). The reason begin BeginInvoke() would cause this worker thread to run out of control and queue up UI updates on the GUI thread. This can happen if the GUI thread is blocked by the OS, to complete some high priority operation. Our worker thread would run uncontrolled. I generally do not prefer a runaway thread.
 
The use of Invoke() makes sure the worker thread is blocked if the UI thread is busy. This is a desired effect and I would use Invoke() in this case. Also there are many occasions when the UI needs to be updated immediately before the worker thread can proceed further.
 
Agreed that Invoke() is a double edged sword. If used incorrectly it will cause pains. A complete avoidance is not the solution either!
 
2) I come from a Visual C++, MFC/Win32 background. Those days system timers were precious resources! And if all the timers were used up then your application was doomed! Back then the good folks at MSDN suggested using worker threads as an alternative. It has been so ingrained in me that even though the timer limitations are gone in .NET, I still do not like them!
 
One more thing to note is that when the timer tick event is called all the code inside that event handler is getting executed in the UI thread. Using the precious UI thread to perform any lengthy operation is definitely a no-no. If you are writing code for a financial application which updates 20 times a second you will realize how busy the UI thread can get. If you add any lengthy operation on the timer tick event you can easily bring down the financial application!
 
The reason I make a copy of the List is because I am using the foreach() statement to iterate through the list. And I also modify the list while iterating. This will cause an InvalidOperationException if I do not make the copy.
 
Thanks for your comments!Smile | :)

 
Ram
GeneralRe: Some commentsmemberKristof Verbiest11 Sep '07 - 21:11 
1) W.r.t. the worker-thread running out of control: this is not likely to happen if the OS is performing a high priority operation, because both the GUI-thread AND the worker-thread will not be scheduled.
But problems are possible if the timer's period is too short (which you should avoid) or if the GUI-thread is blocked for long operations (which should never happen of course). And of course 'Invoke()' is still necessary if your method has a return-value or [out] parameters.
 
2) W.r.t. expensive timers: in your example I would make the timer static, to avoid each instance of your class having it's own timer.
The same should be applied to your workerthread: it seems a waste to have a seperate thread for each instance. A thread certainly has it's cost too.
If you're really convinced that you don't want to use a System.Windows.Forms.Timer, you could still use a Systems.Threading.Timer. This will schedule your delegate on a threadpool-thread (so you still need to use (Begin)Invoke).
 
regards,
 
Kristof Verbiest[^]
GeneralRe: Some commentsmemberRam Mohan Raja12 Sep '07 - 4:46 
1) Is there a guarantee that both the GUI thread and the worker thread will not be scheduled? What if the user machine has dual CPUs? What if the GUI thread is blocked on the first CPU while the worker thread gets scheduled on the second CPU? Can you point me to some document from Microsoft that confirms the actual behavior?
In financial applications the real time messaging systems like RV or EMS start bombarding the GUI thread with update messages even before the GUI has time to load! For example when the user clicks on a tab page, in a tab control, for the first time the controls in the tab page take a moment to initialize. In that time there are already messages to update the controls! In general we do not update the controls directly from messaging events. The update messages are queued and a second thread is used to pickup updates from this queue to update the controls. Here we need the second thread to be blocked till the GUI thread is ready to update the controls.
 
2) Timers are expensive because they use the GUI thread to execute the code in the timer tick event. This is a waste of time for the GUI thread. As for each class having a separate thread that is not required. You can modify the CellData class to hold the reference to the grid control. This way you can use the same thread to blink all the grids in your form.
 
Regards,
Ram

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Permalink | Advertise | Privacy | Mobile
Web03 | 2.6.130523.1 | Last Updated 4 May 2008
Article Copyright 2007 by Rammohan Raja
Everything else Copyright © CodeProject, 1999-2013
Terms of Use
Layout: fixed | fluid