3

I have a DataGrid that I fill with a DataView (using DataContext). I have tried to do that in a separate thread but the UI still freezes. I want to prevent the UI from freezing when populating the DataGrid.

Here is the code I made so far:

private void btnOK_Click(object sender, RoutedEventArgs e)
    {
        GetFieldsBLL getFieldsBLL = new GetFieldsBLL();
        DataView dv = getFieldsBLL.GetWholeView(ViewName);
        Task task = new Task(() => ucDataExtracViewControl.PopulateGrid(dv));
        task.Start();
    }

public void PopulateGrid(DataView dv)
    {
        dgView.Dispatcher.BeginInvoke(DispatcherPriority.Normal, new Action(delegate{

            dgView.Columns.Clear();
            dgView.AutoGenerateColumns = false;
            foreach (DataColumn column in dv.Table.Columns)
            {
                var gridColumn = new DataGridTextColumn()
                {
                    Header = column.ColumnName,
                    Binding = new Binding("[" + column.ColumnName + "]")
                };

                dgView.Columns.Add(gridColumn);
            }
            dgView.DataContext = dv;

            DataView = dv;
        }));
    }

Thanks in advance!

Edit: The reason why I recreate the columns is because some of the column names have a dot in their name. For instance “Job No.” doesn’t yield any data when using binding. Read more here: What is it about DataTable Column Names with dots that makes them unsuitable for WPF's DataGrid control? It is not an option to make changes to the database. –

Community
  • 1
  • 1
user1471467
  • 33
  • 1
  • 5
  • The main UI thread creates UI objects and is the only the only thread that can access UI objects. You can GetFieldsBLL() on a background tread. If you don't need edit then use ListView GridView as it is lighter and faster. – paparazzo Jun 21 '12 at 14:19
  • It's generally polite to mark answer as accepted. Otherwise people won't help you again another time. – jjrdk Jun 22 '12 at 08:48

5 Answers5

4

I've worked quite a lot with the WPF DataGrid, and rendering is a problem. In the end rendering time comes down to the amount of columns and rows that you will be showing. When the DataGrid renders it has to draw each one, which means loading and measuring the size of the content.

I've found that you can get some good speed improvements by setting fixed column widths and row heights. When used in combination with the DelayedDataGridTextColumn below, there is very little blockage of the UI thread, because each cell is rendered separately, and thus allows for other things to happen on the UI thread with a higher priority.

public class DelayedDataGridTextColumn : DataGridTextColumn
{
    protected override FrameworkElement GenerateElement(DataGridCell cell, object dataItem)
    {
        var textBlock = new TextBlock();
        textBlock.SetValue(FrameworkElement.StyleProperty, ElementStyle);

        Dispatcher.BeginInvoke(
            DispatcherPriority.Loaded,
            new Action<TextBlock>(x => x.SetBinding(TextBlock.TextProperty, Binding)),
        textBlock);
        return textBlock;
    }
}

Note that you can tweak the DispatcherPriority to fit the rendering speed you want. The lower the priority the more of a curtain effect you get. The higher the priority the less other items will be handled while rendering.

jjrdk
  • 1,882
  • 15
  • 18
  • Thanks for the quick answer! Setting fixed column widths and row heights definitely made it render faster. I looked through your code but I don’t understand what “c” and “x” represents. Could you please elaborate? – user1471467 Jun 21 '12 at 09:12
  • I remove the c, it was a custom namespace alias in my code. Sorry. The x is simply the parameter that gets passed into the lambda delegate. It represents the textblock content of the cell, which is what gets passed in as the arg parameter to the BeginInvoke method. – jjrdk Jun 21 '12 at 10:47
1

Change your DispatcherPriority to Background.

dgView.Dispatcher.BeginInvoke(DispatcherPriority.Background, new Action(delegate{
// your code
};
Barracoder
  • 3,696
  • 2
  • 28
  • 31
0

You don't actually run this in a seperate thread as the first thing you do is dispatching it back to the UI-Thread

The question is why you recreate the columns yourself? Does this really yield different results than setting AutoGenerateColumns to true?

I think you could improve on this if you would properly use the binding-features of WPF but there is just to few code to make a good suggestion on this.

Random Dev
  • 51,810
  • 9
  • 92
  • 119
  • The reason why I recreate the columns is because some of the column names have a dot in their name. For instance “Job No.” doesn’t yield any data when using binding. Read more here: http://stackoverflow.com/questions/2940618/what-is-it-about-datatable-column-names-with-dots-that-makes-them-unsuitable-for It is not an option to make changes to the database. – user1471467 Jun 21 '12 at 08:50
0

You can use BackgroundWorker class to perform the background process means:

Assign the below task to DoWork callback

  1. Call the getFieldsBLL.GetWholeView(ViewName);
  2. Poulate the DataView the way you want to populate

Move the UI update code (dgView.DataContext = dv;) to RunWorkerCompleted callback.

Note : You need to create a class variable for DataView so that it can be used for DoWork and RunWorkerCompleted callback.

pchajer
  • 1,584
  • 2
  • 13
  • 25
0

I got the issue resolved. The C# code was not the reason for UI freeze but the rendering of records on the DataGrid. Set EnableRowVirtualization="True" EnableColumnVirtualization="True" ScrollViewer.CanContentScroll="True" in the data grid

Actually DataGrid freezes the UI on large data. So enabling virtualization resolves the issue

Basharat Hussain
  • 209
  • 1
  • 3
  • 8