Hans Billiet
Hans Billiet

Reputation: 135

Scrolling DataGridView seems to bring down performance drastically

I'm writing an application in C# using Microsoft Visual Studio 2019. The application communicates with several Arduino boards. Sending and receiving works asynchronously using the TAP model, and works fine.

The application is based on a Windows Form App, using .NET Framework 4.7.2. I added a DataGridView to the form, using a DataTable as DataSource. The intention is to use this DataGridView as a data logger, showing 5 columns: TimeStamp, DeviceID, Direction, Command and ErrorStatus.

If I disable the DataGridView, I reach up to 500 commands per second on a Chinese Arduino clone. On a real Arduino I seem only to get up to 244 commands per second - see other question on stackoverflow below - but this is not the question now:

Communication speed over USB (PC/Arduino) using SerialPort in C# seems to “clip”

When I enable my DataGridView, I see communication speed dropping to about 25 commands/second, and this is purely because of the updating of the lines in the DataGridView. But that seems only the case when the DataGridView starts scrolling.

See below code snippet:

dt.Rows.Add(new string[] 
{
    DateTime.Now.ToString("HH:mm:ss:fff"),
    device._FMGSDevice,
    action,
    notifyData.Command,
    notifyData.notifyError.ToString()
});
if (dt.Rows.Count > maxLines)
    dt.Rows.RemoveAt(0);

dt is the DataTable that is used as DataSource for the DataGridView. For every communication, a line is added at the end of the DataTable, which then automatically updates in the DataGridView.

maxLines is a constant currently set at 500. To avoid that my DataGridView gets too much lines, I limit it to 500 lines. If the limit is reached, I remove the first line with "RemoveAt(0)" after having added the new line, to keep it at maximum 500 lines.

I now see that once the DataGridView starts scrolling (the "RemoveAt(0)" causes all the lines to move up with one line), the speed goes dramatically down.

Does anyone has an idea how to speed up the scrolling? Or is there another item I could use to log? (although, I need filtering as well).

Upvotes: 0

Views: 760

Answers (3)

Harald Coppoolse
Harald Coppoolse

Reputation: 30474

Consider not to update the data automatically. People will be reading the data, maybe selecting parts of it. It is fairly annoying if the data that you just selected scrolls away.

It is similar to editing a document will parts that you are reading, or just selected suddenly disappear.

Add a "Refresh" button, which will refresh the data.

If you think that live updates of the data is important, so operators can see which data changes, consider to use a timer. If the timer elapses, refresh the data.

Something like this:

// the data in one Row:
class ArduinoLogData
{
    ... // properties that will be shown in the columns
}

BindingList<ArduinoLogData> DisplayedArduinoData
{
    get => (BindingList<LoggedArduinoRow>)this.DataGridView1.DataSource;
    set => this.DataGridView1.DataSource = value;
}

// Fetch ArduinoData
IEnumerable<ArduinoLogData> FetchArduinoLogdata()
{
    ...
}

Consider to create an overload in which you state which arduinos must be fetched, so you can update only the arduinos that are currently visible. Use DataGridViewRow.Displayed to get the ArduinoLogData that is currently displayed:

IEnumerable<ArduinoLogData> DisplayedData => this.DataGridView1.Rows
    .Where(row => row.Displayed)
    .Select(row => row.DataBoundItem)
    .Cast<ArduinoLogData>();

And a method:

void RefreshArduinoLogData(ICollection<ArduinoLogData> itemsToRefresh)
{
    ... // update only these items.
}

void RefreshDisplayedArduinoLogData()
{
    this.RefreshArduinoLogData(this.DisplayedData.ToList());
}

This will update only the visible items, about 40 rows or so.

Do this once a second:

Timer refreshTime = new Timer
{
    AutoReset = true,
    Interval = TimeSpan.FromSeconds(1).TotalMilliseconds,
};
timer.Elapsed += OnTimerTick;
timer.Enabled = true;

void OnTimerTick(object sender, ...)
{
    this.RefreshDisplayedArduinoLogData();
}

Don't forget to Dispose your Timer when your Form closes, or at last when the Form is disposed.

Upvotes: 0

Hans Billiet
Hans Billiet

Reputation: 135

The suggestion from @TaW worked best so far. At least, it was the easiest to implement. I have not yet experimented with Virtual Mode nor have I tried ListView, because these require quite some time to implement.

I created a derived class from DataGridView as below (see link where I found this):

Horrible redraw performance of the DataGridView on one of my two screens

class CustomDataGridView : DataGridView
{
    public CustomDataGridView()
    {
        // if not remote desktop session then enable double-buffering optimization
        if (!System.Windows.Forms.SystemInformation.TerminalServerSession)
            DoubleBuffered = true;
    }
}

Because I'm using Designer, I manually changed the code there to use the CustomDataGridView class instead.

this.dgLogView = new HW_Hub.CustomDataGridView();

Although, what I'm not sure, is it allowed to change code in "...designer.cs"? I have the feeling that Visual Studio "owns" this file, and make changes automatically. Do I need another way to overrule the standard implementation?

Upvotes: 0

alby98
alby98

Reputation: 47

I don't know exactly your application but you can try the delete method instead of RemoveAt and see if the performances improve. it should be:

DataRow dr = dtName.Rows[0];
dr.Delete();
dtName.AcceptChanges();

Alternatively you can increase the number of record to remove, for example remove the first 10 or 20 records instead only the first.

Upvotes: 0

Related Questions