4

I'm having trouble refreshing my DataGridView in a reasonable time in C# (which I am new to btw, I'm used to java...).

I'm getting data over a network with 20 packages sent per second. I'd like to parse the data and put it in a DataGridView. I would also like to adjust the interval in which the DataGridView is updated, from 0.1 seconds to 1 minute.

So I created an extra thread, which reads the packages and parses them to an Array. I also have a Timer, which I use to change the Interval. On every timer tick, I reassign the DataSource to the DataGridView.

Interestingly, when I do, even if I set the timer to 0.1 seconds, it is only triggered about once a second. If I do not refresh the DataGridView, it gets triggered 10 times a second, as it is supposed to.

So I am assuming that my method of updating the DataGridView is too time consuming. But what do I have to do to make it more efficient, so I can update it 10 times a second without any problems?

Here is the code I use:

public MyForm()
    {
        InitializeComponent();

        timer = new System.Windows.Forms.Timer();
        timer.Interval = (1 * 1000); // 1 secs
        timer.Tick += new EventHandler(timer_Tick);
        timer.Start();

        readNetworkValues = true;
        networkReader = new Thread(() =>
        {
            Thread.CurrentThread.IsBackground = true;
            byte[] data = new byte[1024];
            IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 49003);
            UdpClient newsock = new UdpClient(ipep);
            IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);

            while (readNetworkValues)
            {
                data = newsock.Receive(ref sender);
                dataSet = parseData(data); //Decrypts the data
            }
        newsock.Close();
        });
        networkReader.Start();
    }

    private void timer_Tick(object sender, EventArgs e)
    {
        if (dataSet != null)
        {
            lock (dataSet)
            {
                int currentRow = dataGrid.FirstDisplayedScrollingRowIndex;
                dataGrid.DataSource = dataSet;
                dataGrid.FirstDisplayedScrollingRowIndex = currentRow;
            }
        }
    }
TaW
  • 48,779
  • 8
  • 56
  • 89
Maverick283
  • 1,009
  • 1
  • 11
  • 29
  • _timer.Interval = (1 * 1000); // 10 secs_ nope. - Also: __Do not__ call a `DataGridView`a `GridView` or a `DataGrid` and vice versa!! This is wrong and confusing as those are different controls. Always call things by their __right__ name! - Also: How many data do you want to display? 20 fps is faster than a movie; no human could read anything at that speed! – TaW May 25 '17 at 17:25
  • I'm sorry, I corrected my question, the comment was a left over of some older code. I didn't know there was such a confusion, couldn't find a GridView in the first place.As mentioned, I'm completely new to C#. 20fps is not my goal, but as written in the question I'd like to have at least 10fps. I'm aware that this is fast, what is needed is the data change to be visible in real time.1 sec would be too slow in most cases. I would also, independent from my current problem, like to know what exactly is causing the "lag", just to learn and get to know C# better. – Maverick283 May 25 '17 at 18:08
  • GridView and DataGrid are controls from other/older frameworks (.Net 1.0, ASP, WPF). - The lag may be caused by the size of the data. How many data do you display. Or by the Lock.. – TaW May 25 '17 at 18:14
  • Ahh, good to know.... I have so far tested it with 20 rows and 26 columns, each containing a rather short string or double. I'd like to scale that up to about 150 rows, same amount of columns. I've read that C# was quite thread safe... Is the lock redundant? – Maverick283 May 25 '17 at 18:18
  • Um, no c# per se is not thread-safe. You need to check each type for this. But once several thread compete a lock will on the one habd sort it out but otoh make one more more threads wait.. _-Maybe__ (untested idea) you should separate the DataTable updates and the binding. The latter at some reasonable (and maybe variable) speed form 3-15 fps and othe former at the speed the data come in. – TaW May 25 '17 at 18:23
  • Hum removing the lock doesn't seem to make a difference... Is the approach for setting the data to the DataGridView correct? What do you mean by binding? `dataGrid.DataSource = dataSet;`? If so, I put that in the while loop that gets the data, just for the heck of it, assigned it back to the main thread (`dataGrid.Invoke(new Action(() => dataGrid.DataSource = parseData(data)));`) but that caused an even greater delay. It affected the loop as it only picked up data every 2 seconds, so that reassigning of DataSource seems to be the problem. `parseData` takes less then 1ms according to debugger. – Maverick283 May 25 '17 at 18:32
  • OK. Just did a test, no threading involved, just trying to change data fast. I found the results for larger data numbers quite strong. Here is what made all the difference for me: I turned on double-buffering for the DGV. You need to use a subclass like [this one](https://stackoverflow.com/questions/41893708/how-to-prevent-datagridview-from-flickering-when-scrolling-horizontally/41894210?s=1|0.6969#41894210) but it makes not only scrolling but also the updates really smooth here.. – TaW May 25 '17 at 19:06
  • 1
    Wow, that is genius. I ended up using this weird something: `typeof(DataGridView).InvokeMember("DoubleBuffered", BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty, null, dataGrid, new object[] { true });` since I'm not sure how to make the dataGrid of a custom Class... If you know more about this, I'd appreciate if you can elaborate why this works in an answer. If not, post this as an answer anyways so you can earn your well deserved points. Thanks a lot! – Maverick283 May 25 '17 at 19:16

1 Answers1

9

The number of cells you want to update and also the update rate you want are high enough to cause flicker and lagging.

To avoid it you can turn on DoubleBuffering for the DataGridView.

This property is not exposed by default. So have a have a choice of either

  • creating a subclass or
  • accessing it via reflection

Here is a post that demonstrates the former. It was written for a case of scrolling flicker but will help avoid update lags as well. The class can maybe look like this:

public class DBDataGridView : DataGridView
{
    public new bool DoubleBuffered
    {
        get { return base.DoubleBuffered; }
        set { base.DoubleBuffered = value; }
    }

    public DBDataGridView()
    {
        DoubleBuffered = true;
    }
}

You can add this class to the project or simply to the form class (before the very last curly.) Compile and it will show up in the ToolBox.

The other option uses reflection; here is a general-purpose function that should work for for any type of control:

using System.Reflection;

static void SetDoubleBuffer(Control ctl, bool DoubleBuffered)
{
    typeof(Control).InvokeMember("DoubleBuffered", 
        BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.SetProperty, 
        null, ctl, new object[] { DoubleBuffered });
}

Both ways let you turn DoubleBuffering on and off at will; the former via the now exposed property, the latter by the bool param of the method.

TaW
  • 48,779
  • 8
  • 56
  • 89