2008-12-10 7 views
11

Je souhaite utiliser le mode GridView d'un ListView pour afficher un ensemble de données que mon programme recevra d'une source externe. Les données seront constituées de deux tableaux, l'un des noms de colonnes et l'autre des valeurs de chaînes pour remplir le contrôle.WPF GridView avec une définition dynamique

Je ne vois pas comment créer une classe appropriée que je peux utiliser comme Item dans un ListView. Le seul moyen que je connaisse pour peupler les Items est de le mettre dans une classe avec des propriétés qui représentent les colonnes, mais je n'ai aucune connaissance des colonnes avant l'exécution.

Je pourrais créer un ItemTemplate dynamiquement comme décrit dans: Create WPF ItemTemplate DYNAMICALLY at runtime mais il me laisse toujours une perte quant à la façon de décrire les données réelles.

Toute aide reçue avec gratitude.

Répondre

11

Vous pouvez ajouter GridViewColumns au GridView dynamique étant donné la première matrice en utilisant une méthode comme ceci:

private void AddColumns(GridView gv, string[] columnNames) 
{ 
    for (int i = 0; i < columnNames.Length; i++) 
    { 
     gv.Columns.Add(new GridViewColumn 
     { 
      Header = columnNames[i], 
      DisplayMemberBinding = new Binding(String.Format("[{0}]", i)) 
     }); 
    } 
} 

Je suppose que le second tableau contenant les valeurs seront de ranges * longueur COLONNES. Dans ce cas, vos éléments peuvent être des tableaux de chaînes de longueur COLUMNS. Vous pouvez utiliser Array.Copy ou LINQ pour diviser le tableau. Le principe est démontré ici:

<Grid> 
    <Grid.Resources> 
     <x:Array x:Key="data" Type="{x:Type sys:String[]}"> 
      <x:Array Type="{x:Type sys:String}"> 
       <sys:String>a</sys:String> 
       <sys:String>b</sys:String> 
       <sys:String>c</sys:String> 
      </x:Array> 
      <x:Array Type="{x:Type sys:String}"> 
       <sys:String>do</sys:String> 
       <sys:String>re</sys:String> 
       <sys:String>mi</sys:String> 
      </x:Array> 
     </x:Array> 
    </Grid.Resources> 
    <ListView ItemsSource="{StaticResource data}"> 
     <ListView.View> 
      <GridView> 
       <GridViewColumn DisplayMemberBinding="{Binding Path=[0]}" Header="column1"/> 
       <GridViewColumn DisplayMemberBinding="{Binding Path=[1]}" Header="column2"/> 
       <GridViewColumn DisplayMemberBinding="{Binding Path=[2]}" Header="column3"/> 
      </GridView> 
     </ListView.View> 
    </ListView> 
</Grid> 
5

Merci, c'est très utile. Je l'ai utilisé pour créer une version dynamique comme suit. J'ai créé les têtes de colonne comme vous le suggérez:

private void AddColumns(List<String> myColumns) 
{ 
    GridView viewLayout = new GridView(); 
    for (int i = 0; i < myColumns.Count; i++) 
    { 
     viewLayout.Columns.Add(new GridViewColumn 
     { 
      Header = myColumns[i], 
      DisplayMemberBinding = new Binding(String.Format("[{0}]", i)) 
     }); 
    } 
    myListview.View = viewLayout; 
} 

Mettre en place ListView très simplement en XAML:

<ListView Name="myListview" DockPanel.Dock="Left"/> 

Création d'une classe wrapper pour ObservableCollection de tenir mes données:

public class MyCollection : ObservableCollection<List<String>> 
{ 
    public MyCollection() 
     : base() 
    { 
    } 
} 

Et lié mon ListView à elle:

results = new MyCollection(); 

Binding binding = new Binding(); 
binding.Source = results; 
myListview.SetBinding(ListView.ItemsSourceProperty, binding); 

ensuite pour le remplir, il était juste un cas de compensation sur les anciennes données et l'ajout de la nouvelle:

results.Clear(); 
List<String> details = new List<string>(); 
for (int ii=0; ii < externalDataCollection.Length; ii++) 
{ 
    details.Add(externalDataCollection[ii]); 
} 
results.Add(details); 

Il y a probablement des moyens plus nets de le faire, mais cela est très utile pour mon application. Merci encore.

3

Je ne sais pas si c'est encore pertinent, mais j'ai trouvé un moyen de styliser les cellules individuelles avec un sélecteur de cellule. Il est un peu hacky parce que vous avez à grignoter autour avec le contenu du ContentPresenter pour obtenir le DataContext approprié pour la cellule (de sorte que vous pouvez lier à l'élément de cellule réelle dans le modèle cellulaire):

public class DataMatrixCellTemplateSelectorWrapper : DataTemplateSelector 
    { 
     private readonly DataTemplateSelector _ActualSelector; 
     private readonly string _ColumnName; 
     private Dictionary<string, object> _OriginalRow; 

     public DataMatrixCellTemplateSelectorWrapper(DataTemplateSelector actualSelector, string columnName) 
     { 
      _ActualSelector = actualSelector; 
      _ColumnName = columnName; 
     } 

     public override DataTemplate SelectTemplate(object item, DependencyObject container) 
     { 
      // The item is basically the Content of the ContentPresenter. 
      // In the DataMatrix binding case that is the dictionary containing the cell objects. 
      // In order to be able to select a template based on the actual cell object and also 
      // be able to bind to that object within the template we need to set the DataContext 
      // of the template to the actual cell object. However after the template is selected 
      // the ContentPresenter will set the DataContext of the template to the presenters 
      // content. 
      // So in order to achieve what we want, we remember the original DataContext and then 
      // change the ContentPresenter content to the actual cell object. 
      // Therefor we need to remember the orginal DataContext otherwise in subsequent calls 
      // we would get the first cell object. 

      // remember old data context 
      if (item is Dictionary<string, object>) 
      { 
       _OriginalRow = item as Dictionary<string, object>; 
      } 

      if (_OriginalRow == null) 
       return null; 

      // get the actual cell object 
      var obj = _OriginalRow[_ColumnName]; 

      // select the template based on the cell object 
      var template = _ActualSelector.SelectTemplate(obj, container); 

      // find the presenter and change the content to the cell object so that it will become 
      // the data context of the template 
      var presenter = WpfUtils.GetFirstParentForChild<ContentPresenter>(container); 
      if (presenter != null) 
      { 
       presenter.Content = obj; 
      } 

      return template; 
     } 
    } 

Note: I a changé le DataMatrix à partir de l'article CodeProject afin que les lignes soient des Dictionnaires (ColumnName -> Cell Object).

Je ne peux pas garantir que cette solution ne casse pas quelque chose ou ne se casse pas dans le futur.Sortie nette. Il repose sur le fait que ContentPresenter définit DataContext après avoir sélectionné le modèle dans son propre contenu. (Réflecteur aide beaucoup dans ces cas :))

Lors de la création des GridColumns, je fais quelque chose comme ça:

  var column = new GridViewColumn 
          { 
           Header = col.Name, 
           HeaderTemplate = gridView.ColumnHeaderTemplate 
          }; 
      if (listView.CellTemplateSelector != null) 
      { 
       column.CellTemplateSelector = new DataMatrixCellTemplateSelectorWrapper(listView.CellTemplateSelector, col.Name); 
      } 
      else 
      { 
       column.DisplayMemberBinding = new Binding(string.Format("[{0}]", col.Name)); 
      } 
      gridView.Columns.Add(column); 

Note: J'étendu ListView de sorte qu'il possède une propriété CellTemplateSelector vous pouvez lier à en XAML

@EDIT 15/03/2011: J'ai écrit un petit article qui a un petit projet de démonstration ci-joint: http://codesilence.wordpress.com/2011/03/15/listview-with-dynamic-columns/

1

Version totalement PROGmatic:

 var view = grid.View as GridView; 
     view.Columns.Clear(); 
     int count=0; 
     foreach (var column in ViewModel.GridData.Columns) 
     { 
      //Create Column 
      var nc = new GridViewColumn(); 
      nc.Header = column.Field; 
      nc.Width = column.Width; 
      //Create template 
      nc.CellTemplate = new DataTemplate(); 
      var factory = new FrameworkElementFactory(typeof(System.Windows.Controls.Border)); 
      var tbf = new FrameworkElementFactory(typeof(System.Windows.Controls.TextBlock)); 

      factory.AppendChild(tbf); 
      factory.SetValue(System.Windows.Controls.Border.BorderThicknessProperty, new Thickness(0,0,1,1)); 
      factory.SetValue(System.Windows.Controls.Border.MarginProperty, new Thickness(-7,0,-7,0)); 
      factory.SetValue(System.Windows.Controls.Border.BorderBrushProperty, Brushes.LightGray); 
      tbf.SetValue(System.Windows.Controls.TextBlock.MarginProperty, new Thickness(6,2,6,2)); 
      tbf.SetValue(System.Windows.Controls.TextBlock.HorizontalAlignmentProperty, column.Alignment); 

      //Bind field 
      tbf.SetBinding(System.Windows.Controls.TextBlock.TextProperty, new Binding(){Converter = new GridCellConverter(), ConverterParameter=column.BindingField}); 
      nc.CellTemplate.VisualTree = factory; 

      view.Columns.Add(nc); 
      count++; 
     } 
1

J'irais à faire cela en ajoutant un AttachedProperty au GridView où ma demande de MVVM pouvez spécifier les colonnes (et peut-être quelques métadonnées supplémentaires.) Le code de comportement peut alors travailler de façon dynamique directement avec l'objet GridView pour créer la colonnes. De cette façon, vous adhérez à MVVM et ViewModel peut spécifier les colonnes à la volée.

+0

C'est comme ça que je veux faire mais pas clair comment. Les Props attachés et les PDD sont toujours un mystère pour moi. Tout exemple serait apprécié. A eu un coup d'oeil à ceci (https://rachel53461.wordpress.com/2011/09/17/wpf-grids-rowcolumn-count-properties/). Est-ce ainsi? – VivekDev

Questions connexes