WPF Datagrid / Datatable: большое количество строк - PullRequest
2 голосов
/ 23 сентября 2010

У меня есть Datagrid, подключенный к Datatable, который должен загружать очень большое количество строк.

Чтобы ускорить процесс, я загружаю 10% строк и отображаю форму.Большую часть времени пользователю нужны только эти 10% (это самые последние записи).В фоновом потоке я загружаю оставшиеся 90% строк в другую таблицу данных (SecondData).Затем я объединяю обе таблицы данных:

FirstData.BeginLoadData()
FirstData.Merge(SecondData, False)
FirstData.EndLoadData()

Это прекрасно работает, но операция объединения занимает очень много времени.Если я переверну операцию (слияние SecondData с FirstData), это займет гораздо меньше времени.Но затем мне нужно повторно назначить источник данных (SecondData) для Datagrid, и пользователь теряет текущую позицию прокрутки, выбранную строку и т. Д.

Я также попытался добавить строки непосредственно в FirstData из фонового потокаи, похоже, работает просто отлично.Но когда я прокручиваю Datagrid после этого, я замираю, и после этого «внутренний индекс DataTable поврежден».

Как правильно это сделать?

Ответы [ 2 ]

2 голосов
/ 24 сентября 2010

Вот несколько взломанная дополнительная версия, которая показывает, как загрузить DataGrid при привязке к DataView с использованием still BeginInvoke. Код по-прежнему загружает одну строку за раз в DataGrid. Вам нужно будет изменить по мере необходимости; Я загружаю из образца AdventureWorks, используя событие Loaded.

Вот как работает ViewModel:

  1. Сначала загрузите столбцы с помощью оператора SQL с предложением Where 1 = 0
  2. Вызовите Dispatcher.BeginInvoke, чтобы сначала загрузить подмножество данных
  3. Затем снова вызовите Dispatcher.BeginInvoke для загрузки оставшихся данных

Вот окно:

<Window x:Class="DatagridBackgroundWorker.Views.MainView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:WpfToolkit="clr-namespace:Microsoft.Windows.Controls;assembly=WPFToolkit" 
    Loaded="Window_Loaded"
    Title="Main Window" Height="400" Width="800">
  <DockPanel>
    <Grid>
        <WpfToolkit:DataGrid  
            Grid.Column="1"
            SelectedItem="{Binding Path=SelectedGroup, Mode=TwoWay}"
            ItemsSource="{Binding Path=GridData, Mode=OneWay}" >
        </WpfToolkit:DataGrid>
    </Grid>
  </DockPanel>
</Window>

Вот код окна с событием Loaded:

public partial class MainView : Window
{
  ViewModels.MainViewModel _mvm = new MainViewModel();

  public MainView()
  {
     InitializeComponent();
     this.DataContext = _mvm;
  }

  private void Window_Loaded(object sender, RoutedEventArgs e)
  {
     Dispatcher d = this.Dispatcher;
     _mvm.LoadData(d);
  }
}

Здесь ViewModel:

public class MainViewModel : ViewModelBase
{
  public MainViewModel()
  {
     // load the connection string from the configuration files
     _connectionString = ConfigurationManager.ConnectionStrings["AdventureWorks"].ConnectionString;

     using (SqlConnection conn = new SqlConnection(ConnectionString))
     {
        conn.Open();

        // load no data 1=0, but get the columns...
        string query =
           "SELECT [BusinessEntityID],[Name],[SalesPersonID],[Demographics],[rowguid],[ModifiedDate] FROM [Sales].[Store] Where 1=0";
        SqlCommand cmd = conn.CreateCommand();
        cmd.CommandType = CommandType.Text;
        cmd.CommandText = query;

        SqlDataAdapter da = new SqlDataAdapter(cmd);
        da.Fill(_ds);
     }
  }

  // only show grid data after button pressed...
  private DataSet _ds = new DataSet("MyDataSet");
  public DataView GridData
  {
     get
     {
        return _ds.Tables[0].DefaultView;
     }
  }

  private void AddRow(SqlDataReader reader)
  {
     DataRow row = _ds.Tables[0].NewRow();
     for (int i = 0; i < reader.FieldCount; i++)
     {
        row[i] = reader[i];
     }
     _ds.Tables[0].Rows.Add(row);
  }

  public void LoadData(Dispatcher dispatcher)
  {
     // Execute a delegate to load the first number on the UI thread, with a priority of Background.
     dispatcher.BeginInvoke(DispatcherPriority.Background, new LoadNumberDelegate(LoadNumber), dispatcher, true, 1);
  }

  // Declare a delegate to wrap the LoadNumber method
  private delegate void LoadNumberDelegate(Dispatcher dispatcher, bool first, int id); 
  private void LoadNumber(Dispatcher dispatcher, bool first, int id)
  {
     try
     {
        using (SqlConnection conn = new SqlConnection(ConnectionString))
        {
           conn.Open();

           // load first 10 rows...
           String query = string.Empty;
           if (first)
           {
              // load first 10 rows
              query =
                 "SELECT TOP 10 [BusinessEntityID],[Name],[SalesPersonID],[Demographics],[rowguid],[ModifiedDate] FROM [AdventureWorks2008].[Sales].[Store] ORDER By [BusinessEntityID]";
              SqlCommand cmd = conn.CreateCommand();
              cmd.CommandType = CommandType.Text;
              cmd.CommandText = query;
              int lastId = -1;
              SqlDataReader reader = cmd.ExecuteReader();
              if (reader != null)
              {
                 if (reader.HasRows)
                 {
                    while (reader.Read())
                    {
                       lastId = (int)reader["BusinessEntityID"];
                       AddRow(reader);
                    }
                 }
                 reader.Close();
              }

              // Load the remaining, by executing this method recursively on 
              // the dispatcher queue, with a priority of Background.
              dispatcher.BeginInvoke(DispatcherPriority.Background,
                 new LoadNumberDelegate(LoadNumber), dispatcher, false, lastId);
           }
           else
           {
              // load the remaining rows...

              // SIMULATE DELAY....
              Thread.Sleep(5000);

              query = string.Format(
                    "SELECT [BusinessEntityID],[Name],[SalesPersonID],[Demographics],[rowguid],[ModifiedDate] FROM [Sales].[Store] Where [BusinessEntityID] > {0} ORDER By [BusinessEntityID]",
                    id);
              SqlCommand cmd = conn.CreateCommand();
              cmd.CommandType = CommandType.Text;
              cmd.CommandText = query;
              SqlDataReader reader = cmd.ExecuteReader();
              if (reader != null)
              {
                 if (reader.HasRows)
                 {
                    while (reader.Read())
                    {
                       AddRow(reader);
                    }
                 }
                 reader.Close();
              }
           }
        }
     }
     catch (SqlException ex)
     {
     }
  }

  private string _connectionString = string.Empty;
  public string ConnectionString
  {
     get { return _connectionString; }
     set
     {
        _connectionString = value;
        OnPropertyChanged("ConnectionString");
     }
  }
}
2 голосов
/ 23 сентября 2010

Если вы используете метод BeginInvoke окна или свойства Dispatcher элемента управления, он добавляет делегата в очередь событий Dispatcher;однако вы получаете возможность указать для него более низкий приоритет.Выполняя метод, который загружает только один элемент за раз, окно получает возможность выполнить любые другие события с более высоким приоритетом между элементами.Это позволяет немедленно отображать и отображать элемент управления или окно и загружать каждый элемент по одному.

Вот пример кода, который загружает ListBox.
Вы можете адаптировать его к своей DataGrid.
В этом примере я использовал ViewModel, которая содержит ObservableCollection, которая содержит объект.
Если у вас возникнут проблемы с преобразованием в вашу DataGrid, я переделаю.

Вот окно XAML:

<Window x:Class="ListBoxDragDrop.Views.MainView"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:Models="clr-namespace:ListBoxDragDrop.Models" 
    Loaded="Window_Loaded"
    Title="Main Window" Height="400" Width="800">
  <DockPanel>
    <Grid>
        <Grid.ColumnDefinitions>
            <ColumnDefinition/>
        </Grid.ColumnDefinitions>
        <ListBox Grid.Column="0" ItemsSource="{Binding Path=MyData}">
            <ListBox.ItemTemplate>
                <DataTemplate DataType="{x:Type Models:Person}">
                    <StackPanel>
                        <TextBlock Text="{Binding Name}" ></TextBlock>
                        <TextBlock Text="{Binding Description}" ></TextBlock>
                    </StackPanel>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
  </DockPanel>
</Window>

Вот код окна с событием Loaded:

public partial class MainView : Window
{
  MainViewModel _mwvm = new ViewModels.MainViewModel();
  ObservableCollection<Person> _myData = new ObservableCollection<Person>();

  public MainView()
  {
     InitializeComponent();
     this.DataContext = _mwvm;
  }

  private void Window_Loaded(object sender, RoutedEventArgs e)
  {
     // Execute a delegate to load
     // the first number on the UI thread, with
     // a priority of Background.
     this.Dispatcher.BeginInvoke(DispatcherPriority.Background, new LoadNumberDelegate(LoadNumber), 1);
  }

  // Declare a delegate to wrap the LoadNumber method
  private delegate void LoadNumberDelegate(int number);
  private void LoadNumber(int number)
  {
     // Add the number to the observable collection
     // bound to the ListBox
     Person p = new Person { Name = "Jeff - " + number.ToString(), Description = "not used for now"};
     _mwvm.MyData.Add(p);
     if (number < 10000)
     {
        // Load the next number, by executing this method
        // recursively on the dispatcher queue, with
        // a priority of Background.
        //
        this.Dispatcher.BeginInvoke(
        DispatcherPriority.Background,
        new LoadNumberDelegate(LoadNumber), ++number);
     }
  }
}

Вот модель представления:

public class MainViewModel : ViewModelBase
{
  public MainViewModel()
  {
  }

  private ObservableCollection<Person> _myData = new ObservableCollection<Person>();
  public ObservableCollection<Person> MyData
  {
     get
     {
        return _myData;
     }
     set
     {
        _myData = value;
        OnPropertyChanged("MyData");
     }
  }
}

И определение Person для полноты:

public class Person
{
  public string Name { get; set; }
  public string Description { get; set; }
}
...