开发者

How to update DataGridView correctly, so GUI doesn't freeze

开发者 https://www.devze.com 2023-02-27 06:14 出处:网络
I have a backgroundworker, which does some calculations and reports progress result as string. This string needs to be inserted into dataGridView. But while inserting values, GUI freezes.

I have a backgroundworker, which does some calculations and reports progress result as string. This string needs to be inserted into dataGridView. But while inserting values, GUI freezes.

private void bckgrSorter_DoWork(object sender, DoWorkEventArgs e)
{
    for (int i=0; i<1000; i++)
    {
        // doing some calculations
        bckgrSorter.ReportProgress(i, someString);
    }
}    

 private void bckgrSorter_ProgressChanged(object sender, ProgressChangedEventArgs e)
 {
     string results = (string)e.UserState;
     dataGridView1.Rows.Add(results);
 }

So even though I do all the heavy calculations on a background thread, GUI still freezes, because of DataGridView.

Edit for code:

private void bckgrSorter_DoWork(object sender, DoWorkEventArgs e)
    {
        string[] folders = // some folders to get File List from

        bckgrFileScanner.RunWorkerAsync(folders);            
    }

private void bckgrFileScanner_DoWork(object sender, DoWorkEventArgs e)
    {
        string[] folders = (string[])e.Argument;
        foreach (string f in folders)
        {
            GetFileList(ref scannedFiles, f, bckgrFileScanner);
            bckgrFileScanner.ReportProgress(1);
        }
    }

public void GetFileList(ref List<FileInfo> fList, string fPath, BackgroundWorker scanner)
        {
            DirectoryInfo di = new DirectoryInfo(fPath);
            FileInfo[] fi = di.GetFiles();

            foreach (FileInfo fiTemp in fi)
            {
                //ar ~$ saakas nevajadzīgie temp faili, tos izlaižam
                if (f开发者_C百科iTemp.Name.StartsWith("~$") == false)
                {
                    fList.Add(fiTemp);
                    scanner.ReportProgress(0, fiTemp.Name);
                }
            }
            DirectoryInfo[] dFolders = di.GetDirectories();

            //katrai apakšmapei rekursīvi izsaucam šo funkciju
            foreach (DirectoryInfo d in dFolders)
            {
                GetFileList(ref fList, d.FullName, scanner);
            }
        }

private void bckgrFileScanner_ProgressChanged(object sender, ProgressChangedEventArgs e)
    {
        if (e.ProgressPercentage == 0)
        {
            filesDataGrid.Rows.Add(e.UserState);
        }

        else progressBar1.PerformStep();
    }


It looks to me like you need to batch up the results from your worker thread. Maybe you could make the ProgressChanged event return a collection of progress results or something? Or only have the event fire at most every half a second with the latest data. I suspect the problem is that you're just trying to add data too fast. You need to do less units of work with more work in each unit (add lots of rows in one go).

Babar is right. Something like this would probably do what you want (I haven't tried to compile it):

    public void GetFileList(ref List<FileInfo> fList, string fPath, BackgroundWorker scanner)
    {
        DirectoryInfo di = new DirectoryInfo(fPath);
        FileInfo[] fi = di.GetFiles();

        List<string> progressData = new List<string>();

        foreach (FileInfo fiTemp in fi)
        {
            //ar ~$ saakas nevajadzigie temp faili, tos izlaižam
            if (fiTemp.Name.StartsWith("~$") == false)
            {
                fList.Add(fiTemp);
                progressData.Add(fiTemp.Name);
                if (progressData.Count > 50){
                    scanner.ReportProgress(0, progressData.ToArray());
                    progressData.Clear();//You've just copied the data to an array and sent it to the GUI, clear the list and start counting up again
                }
            }
        }

        if (progressData.Count > 0){
            scanner.ReportProgress(0, progressData.ToArray());
        }

        DirectoryInfo[] dFolders = di.GetDirectories();

        //katrai apakšmapei rekursivi izsaucam šo funkciju
        foreach (DirectoryInfo d in dFolders)
        {
            GetFileList(ref fList, d.FullName, scanner);
        }
    }

It's not really a very good solution but it's a start...

You will need to cast e.UserState to a string array in your callback as well...


You are adding file names to the grid as soon as those are discovered. On a reasonably sized directory this will easily overload UI thread with the requests to adding rows in grid. As a result your UI freezes.

Here is how you should update the code. Instead of calling ReportProgress() for individual file, the code should maintain an list of files discovered so far. Once the list gets to a threshold size say 100, then you should call the ReportProgress() and pass that list as UserState.

This will significantly reduce the number of calls to UI thread, making your application responsive.


Isn't there a control property that temporarily stops the control from updating? Sadly I can't recall the name.

You could also try to create the control in background and add it to the form once it is completed. Be careful to avoid threading issues though.

Another idea would be to hide it during the update.

An ugly solution that definitely "works" would be to have your background thread add one item at a time, wait for a few milliseconds (during which the GUI thread can actually perform the change), and add the next item. It will take a long time, but the GUI won't freeze during the update.

0

精彩评论

暂无评论...
验证码 换一张
取 消

关注公众号