DataGrid.SetBinding ItemsSource - Зависание UI

208
25 июня 2018, 10:50
Dispatcher.BeginInvoke(new Action(() => { 
    dataGrid.SetBinding(ItemsControl.ItemsSourceProperty, new Binding() { Source = dataTable 
});

Сначала я заполняю DataTable нужными мне значениями, а затем делаю привязку DataGrid к этой таблице. Однако, я встретился с такой проблемой как зависание интерфейса в момент вызова SetBinding.

DataTable заполняется в отдельном потоке, из которого вызывается Dispatcher.BeginInvoke. IsAsync=True в Binding и различные приоритеты BeginInvoke не помогают. Что можно сделать?

Answer 1

Смог повторить ваш эксперимент - действительно медленно отрабатывает. Но медленно отрабатывает не привязка, а код, который выполняет датагрид ПОСЛЕ привязки.

Например:

public class MyWnd : Window
{
    DataGrid dg;
    public MyWnd()
    {
        dg = new System.Windows.Controls.DataGrid() {AutoGenerateColumns = true};
        var bt = new Button() { Content = "Load" };
        bt.Click += Load;
        var sp = new StackPanel() { Orientation = Orientation.Vertical};
        sp.Children.Add(bt);
        sp.Children.Add(dg);
        this.Content = sp;
    }
    public async void Load(object sender, EventArgs e)
    {
        DataTable dt = null;
        await Task.Run(() =>
        {
            dt = new DataTable();
            for (var i = 0; i < 10; i++)
            {
                dt.Columns.Add($"Column{i}");
            }
            for (var i = 0; i < 1000; i++)
            {
                var row = dt.NewRow();
                for (var j = 0; j < 10; j++)
                {
                    var col = $"Column{j}";
                    row[col] = i + j;
                }
                dt.Rows.Add(row);
            }
        });
        var sw = new Stopwatch();
        sw.Start();     
        dg.SetBinding(ItemsControl.ItemsSourceProperty, new Binding()
        {
            Source = dt, Mode = BindingMode.OneWay
        });     
        sw.Stop();
        MessageBox.Show($"Elapsed: {sw.Elapsed}");
    }
}

Этот код при нажатии кнопки Load сначала создаст таблицу, забиндит её, остановит часы и уже после этого (но перед отображением мессаджбокса) зависнет. Потому у меня часики всегда показывают очень маленький интервал времени

Я думаю, это связано с самим контролом DataGrid. Он либо не умеет в виртуализацию и потому пытается рендерить все строки сразу (при 10 строчках работает оч быстро), либо и без этого у него проблемы с производительностью, например раз, два. Хотя скорее всего и то и то.

UPD

Чтобы побороть это надо правильно включить виртуализацию и положить грид в контрол, который ограничит его размеры. Выглядит это так:

public class MyWnd : Window
{
    DataGrid dg;
    public MyWnd()
    {
        dg = new System.Windows.Controls.DataGrid()
        {
            AutoGenerateColumns = true,
            EnableRowVirtualization = true,
            EnableColumnVirtualization = true
        };
        VirtualizingPanel.SetIsVirtualizing(dg, true);
        ScrollViewer.SetCanContentScroll(dg, true);
        Grid.SetRow(dg, 1);
        var bt = new Button() { Content = "Load" };
        bt.Click += Load;
        var grid = new Grid();
        grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
        grid.RowDefinitions.Add(new RowDefinition() { });
        grid.Children.Add(bt);
        grid.Children.Add(dg);
        this.Content = grid;    
    }
    public async void Load(object sender, EventArgs e)
    {
        DataTable dt = null;
        await Task.Run(() =>
        {
            dt = new DataTable();
            for (var i = 0; i < 10; i++)
            {
                dt.Columns.Add($"Column{i}");
            }
            for (var i = 0; i < 1000; i++)
            {
                var row = dt.NewRow();
                for (var j = 0; j < 10; j++)
                {
                    var col = $"Column{j}";
                    row[col] = i + j;
                }
                dt.Rows.Add(row);
            }
        });
        var sw = new Stopwatch();
        sw.Start();
        dg.SetBinding(ItemsControl.ItemsSourceProperty, new Binding()
        {
            Source = dt
        });
        sw.Stop();
        MessageBox.Show($"Elapsed: {sw.Elapsed}");
    }
}

Так как размеры контрола ограниченны и включена виртуализация, то грид не будет пытаться все сразу отрисовать, а будет отрисовывать только видимую часть

УПД 2

Добавление строки

public class MyWnd : Window
{
    DataGrid dg;
    public MyWnd()
    {
        dg = new System.Windows.Controls.DataGrid()
        {
            AutoGenerateColumns = true,
            EnableRowVirtualization = true,
            EnableColumnVirtualization = true
        };
        VirtualizingPanel.SetIsVirtualizing(dg, true);
        ScrollViewer.SetCanContentScroll(dg, true);
        Grid.SetRow(dg, 1);
        var bt = new Button() { Content = "Load" };
        bt.Click += Load;
        var btrow = new Button() { Content = "AddRow" };
        btrow.Click += AddRow;
        var sp = new StackPanel() { Orientation = Orientation.Horizontal };
        sp.Children.Add(bt);
        sp.Children.Add(btrow);
        var grid = new Grid();
        grid.RowDefinitions.Add(new RowDefinition() { Height = GridLength.Auto });
        grid.RowDefinitions.Add(new RowDefinition() { });
        grid.Children.Add(sp);
        grid.Children.Add(dg);
        this.Content = grid;
    }
    DataTable dt = null;
    Random r = new Random();
    public async void AddRow(object sender, EventArgs e)
    {
        var newRow = dt.NewRow();       
        for (var j = 0; j < 10; j++)
        {
            var col = $"Column{j}";
            newRow[col] = r.Next(10);
        }
        dt.Rows.Add(newRow);
    }
    public async void Load(object sender, EventArgs e)
    {
        await Task.Run(() =>
        {
            dt = new DataTable();
            for (var i = 0; i < 10; i++)
            {
                dt.Columns.Add($"Column{i}");
            }
            for (var i = 0; i < 10; i++)
            {
                var row = dt.NewRow();
                for (var j = 0; j < 10; j++)
                {
                    var col = $"Column{j}";
                    row[col] = i + j;
                }
                dt.Rows.Add(row);
            }
        });
        var sw = new Stopwatch();
        sw.Start();
        dg.SetBinding(ItemsControl.ItemsSourceProperty, new Binding()
        {
            Source = dt
        });
        sw.Stop();
        MessageBox.Show($"Elapsed: {sw.Elapsed}");
    }
}

Результат

READ ALSO
C# импорт данных из excel таблицы и поиск по ней

C# импорт данных из excel таблицы и поиск по ней

Есть база поставщиков, она в табличке excelОдин из столбцов - тег, в нем через запятую указаны теги поставщика

166
Photon Unity Проблема с управлением персонажей

Photon Unity Проблема с управлением персонажей

столкнулся с проблемой разрабатывая онлайн игру на Unity с помощь сервиса Photon PunПолучается в чем проблема сама, когда я запуская окна с игрой(окно...

161
Как обработать событие показа ( Show() ) окна WPF?

Как обработать событие показа ( Show() ) окна WPF?

Открываю владельца окна через thisOwner

142