2010-09-22 5 views
2

J'ai une Datagrid connectée à Datatable, qui doit charger une très grande quantité de lignes.WPF Datagrid/Datatable: grand nombre de lignes

Pour accélérer les choses, je charge 10% des lignes et affiche le formulaire. La plupart du temps, l'utilisateur n'a besoin que de ces 10% (ce sont les entrées les plus récentes). Dans un thread d'arrière-plan, je charge les 90% restants des lignes dans un autre datatable (SecondData). Ensuite, je fusionne les deux datatables:

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

Cela fonctionne très bien, mais l'opération de fusion prend beaucoup de temps. Si j'inverse l'opération (fusion de SecondData avec FirstData), cela prend beaucoup moins de temps. Mais ensuite, je dois réattribuer une datasource (SecondData) à la Datagrid, et l'utilisateur perd la position de défilement courante, la ligne sélectionnée, etc.

J'ai également essayé d'ajouter les lignes directement à FirstData à partir du thread d'arrière-plan, et il semble fonctionner très bien. Mais quand je fais défiler la Datagrid après cela, je me bloque, et "l'index interne de DataTable est corrompu", après cela.

Quelle serait la bonne façon de procéder?

Répondre

2

Voici une version supplémentaire quelque peu piratée qui montre comment charger un DataGrid lors de la liaison à un DataView en utilisant encore BeginInvoke. Le code charge toujours une ligne à la fois dans le DataGrid. Vous devrez modifier au besoin; Je charge à partir de l'exemple AdventureWorks en utilisant l'événement Loaded.

Voici comment le ViewModel fonctionne:

  1. charge d'abord les colonnes à l'aide d'une instruction SQL avec une clause Where de 1 = 0
  2. Call Dispatcher.BeginInvoke d'abord charger un sous-ensemble de données
  3. appellent ensuite Dispatcher.BeginInvoke à nouveau pour charger les données restantes

Voici la fenêtre:

<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> 

Voici le code-behind fenêtre avec le Loaded événement:

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); 
    } 
} 

Voici le 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

Si vous utilisez la méthode BeginInvoke d'une propriété Dispatcher d'une fenêtre ou d'un contrôle, ajoute le délégué à la file d'attente d'événements de Dispatcher; Cependant, vous avez la possibilité de lui attribuer une priorité inférieure . En exécutant une méthode qui ne charge qu'un seul élément à la fois, la fenêtre a la possibilité d'exécuter d'autres événements de priorité supérieure entre les éléments. Cela permet au contrôle ou à la fenêtre d'être affiché et rendu immédiatement et de charger chaque élément un à la fois.

Voici un exemple de code qui charge un ListBox.
Vous pouvez adapter ceci à votre DataGrid.
Dans cet exemple, j'ai utilisé un ViewModel qui contient un ObservableCollection qui contient un objet.
Si vous rencontrez des problèmes lors de la conversion vers votre DataGrid, je le retravaille.

Voici la fenêtre 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> 

Voici la fenêtre code-behind avec l'événement 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); 
    } 
    } 
} 

Voici le ViewModel:

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"); 
    } 
    } 
} 

Et la défintion de Personne pour complétude:

public class Person 
{ 
    public string Name { get; set; } 
    public string Description { get; set; } 
} 
+0

Merci pour votre réponse très détaillée! Cependant, je dois réécrire presque tout mon application complète pour implémenter toute la logique ObservableCollection, parce que je travaille avec Datatables maintenant. La raison pour laquelle ce code fonctionne pour vous est que la seule chose que vous modifiez à l'intérieur du thread est '_mwvm' (le viewmodel). J'ai besoin de modifier le Datatable dans le thread, et cela corrompt le viewmodel (qui n'est pas modifié), provoquant les erreurs et les exceptions. Mais merci pour votre réponse! – Muis

Questions connexes