2008-12-10 3 views
11

Я хочу использовать режим GridView ListView для отображения набора данных, которые моя программа будет получать от внешнего источника. Данные будут состоять из двух массивов, одного из имен столбцов и одного из значений строк для заполнения элемента управления.WPF GridView с динамическим определением

Я не вижу, как создать подходящий класс, который я могу использовать в качестве элемента в ListView. Единственный способ, которым я знаю, чтобы заполнить Элементы, - установить его в класс со свойствами, которые представляют столбцы, но я не знаю столбцов перед временем выполнения.

Я мог бы создать ItemTemplate динамически, как описано в: Create WPF ItemTemplate DYNAMICALLY at runtime, но все равно оставляет меня в недоумении относительно того, как описывать фактические данные.

Любая помощь с благодарностью получена.

ответ

11

Вы можете добавить GridViewColumns к GridView динамически дали первый массив, используя метод, как это:

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

Я полагаю, второй массив, содержащие значения будут рядки * длина колонн. В этом случае ваши элементы могут быть строковыми массивами длины COLUMNS. Вы можете использовать Array.Copy или LINQ для разделения массива. Принцип демонстрируется здесь:

<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

Спасибо, это очень полезно.

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

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

Настройка ListView очень просто в XAML:

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

создал класс-оболочку для ObservableCollection держать мои данные:

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

И связали мой ListView с ним:

results = new MyCollection(); 

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

Затем заполнить его, это был просто случай очистки любых старых данных и добавление новых:

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

Есть, вероятно, аккуратнее способы сделать это, но это очень полезно для моего приложения. Еще раз спасибо.

3

Не уверен, что это все еще актуально, но я нашел способ стилизовать отдельные ячейки с помощью селектора элементов. Это немного Hacky, потому что вы должны жевания вокруг с содержанием ContentPresenter, чтобы получить надлежащее DataContext для ячейки (так что вы можете связать с самим элементом ячейки в шаблоне ячейки):

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

Примечания: Я изменил DataMatrix из статьи CodeProject, чтобы строки были Словари (ColumnName -> Cell Object).

Я не могу гарантировать, что это решение не сломает что-то или не сломается в будущем.Чистая версия. Он полагается на то, что ContentPresenter устанавливает DataContext после того, как он выбрал шаблон для своего собственного Контента. (Отражатель помогает в таких случаях :))

При создании GridColumns, я что-то вроде этого:

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

Примечание: я продлил ListView так, что он имеет свойство CellTemplateSelector вы можете связывать в XAML

@Edit 15/03/2011: я написал небольшую статью, которая имеет небольшой демонстрационный проект прилагается: http://codesilence.wordpress.com/2011/03/15/listview-with-dynamic-columns/

1

Полностью 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

Я бы об этом, добавив AttachedProperty в GridView, где мой MVVM приложение может указать столбцы (и, возможно, некоторые дополнительные метаданные.) Код Поведение может динамически работать непосредственно с GridView объекта для создания колонны. Таким образом, вы придерживаетесь MVVM, и ViewModel может указывать столбцы «на лету».

+0

Вот как я хочу это сделать, но не ясно, как это сделать. Прикрепленные реквизиты и DP все еще остаются загадкой для меня. Любой пример будет оценен по достоинству. Посмотрите на это (https://rachel53461.wordpress.com/2011/09/17/wpf-grids-rowcolumn-count-properties/). Так оно и есть? – VivekDev 2016-05-02 16:27:54