DataGrid: динамический DataTemplate для динамического DataGridTemplateColumn - PullRequest
3 голосов
/ 11 января 2011

Я хочу показать данные в сетке данных, где данные являются коллекцией

public class Thing
{
    public string Foo { get; set; }
    public string Bar { get; set; }
    public List<Candidate> Candidates { get; set; }
}

public class Candidate
{
    public string FirstName { get; set; }
    public string LastName { get; set; }
    ...
}

, где число кандидатов в списке кандидатов изменяется во время выполнения.

Желаемая сетка выглядит следующим образом

Foo | Bar | Candidate 1 | Candidate 2 | ... | Candidate N

Я хотел бы иметь DataTemplate для каждого Кандидата , так как я планирую изменить его во время выполнения - пользователь может выбрать, какая информация о кандидате будет отображаться в разных столбцах (кандидат - всего лишь пример, я есть другой объект). Это означает, что я также хочу изменить шаблоны столбцов во время выполнения, хотя этого можно достичь с помощью одного большого шаблона и сворачивания его частей.

Я знаю о двух способах достижения моих целей (оба очень похожи):

  1. Используйте AutoGeneratingColumn событие и создайте Кандидаты столбцы
  2. Добавить столбцы вручную

В обоих случаях мне нужно загрузить DataTemplate из строки с XamlReader. Перед этим мне нужно отредактировать строку, чтобы изменить привязку на требуемый Кандидат .

Есть ли лучший способ создать DataGrid с неизвестным номером DataGridTemplateColumn?

Примечание: Этот вопрос основан на динамической табличке данных с преобразователем значений

Редактировать: Поскольку мне нужно поддерживать как WPF, так и Silverlight, я создал свой собственный DataGrid компонент, который имеет DependencyProperty для привязки коллекции столбцов. Когда коллекция изменится, я обновлю столбцы.

Ответы [ 3 ]

2 голосов
/ 11 января 2011

Например, мы создаем 2 DataTemplates и ContentControl:

<DataTemplate DataType="{x:Type viewModel:VariantA}"> <dataGrid...> </DataTemplate>
<DataTemplate DataType="{x:Type viewModel:VariantB}"> <dataGrid...> </DataTemplate>

<ContentControl Content="{Binding Path=GridModel}" />

Теперь, если вы установите свойство GridModel (например, тип объекта) в VariantA или VariantB, оно переключит шаблон данных.

Пример VariantA & B Реализация:

public class VariantA
{
    public ObservableCollection<ViewModel1> DataList { get; set; }
}

public class VariantB
{
    public ObservableCollection<ViewModel2> DataList { get; set; }
}

Надеюсь, это поможет.

0 голосов
/ 22 ноября 2011

Я смотрел на похожую проблему и нашел только несколько полезных шаблонов.Вся проблема с «динамическими столбцами» интересна в свете Silverlight.

Вчера я нашел эту страницу Сетка данных Silverlight с динамическими столбцами на сайте Трэвиса Петтижона во время моих поисков.

Раньше я использовал шаблон «индексный конвертер», обозначенный Колин Эберхардт , который работает фантастически хорошо ... до тех пор, пока вы используете DataGridTextColumn.Все может быть сделано в коде, и у меня не было проблем с применением стилей во время выполнения.Однако мое требование теперь состоит в том, чтобы применить некоторое форматирование на уровне ячейки - изменить фон для ячейки и т. Д., Что означает DataGridTemplateColumn.

Большая проблема с DataGridTemplateColumn для меня заключалась в том, чтоЯ не могу установить привязку в коде.Я знаю, что мы можем построить его, анализируя xaml, но, как и все остальные, это похоже на массивный хак и не поддерживается nth.

Шаблон, описанный Трэвисом (первая ссылка выше), совершенно другой.Во время выполнения (т.е. время загрузки страницы) создайте нужные столбцы в вашей сетке.Это означает, что нужно выполнить итерацию по вашей коллекции и добавить столбец для каждого элемента с соответствующим заголовком и т. Д. Затем реализовать обработчик для события RowLoaded, а при загрузке каждой строки просто установить DataContext для каждой ячейки к соответствующему индексу свойства / свойства родительского объекта.

private void MyGrid_RowLoaded(object sender, EventArgs e)
{
    var grid = sender as DataGrid;
    var myItem = grid.SelectedItem as MyClass;
    foreach (int i = 0; i < myItem.ColumnObjects.Count; i++)
    { 
        var column = grid.Columns[i]; 
        var cell = column.GetCellContent(e.Row)
        cell.DataContext = myItem.ColumnObjects[i];
    }
}

Это избавило меня от необходимости использовать конвертер индекса.Вы, вероятно, можете использовать Binding при установке cell.DataContext, но для меня проще, чтобы шаблон просто привязывался непосредственно к базовому объекту.

Теперь я планирую иметь несколько шаблонов (где каждый может связываться сте же свойства на моем объекте ячейки) и переключение между ними при загрузке страницы.Очень аккуратное решение.

0 голосов
/ 07 февраля 2011

Я не знаю, является ли это "лучшим" способом, так как он остается довольно уродливым, но я лично сделал это так:

  • сделать шаблон в xaml
  • использовать мультибинд, который принимает текущую привязку + привязку к столбцу, чтобы получить «правильный» dataContext (т.е. ячейку вместо строки)
  • используйте конвертер для этой привязки, чтобы получить значение свойства, которое вам нравится, при необходимости добавьте параметр, если у вас есть много свойств для извлечения.

например: (извините, я не адаптировал свой код в соответствии с вашим проектом, но вы сможете сделать это самостоятельно)

вот мой шаблон данных:

<DataTemplate x:Key="TreeCellTemplate">
    <Grid>
        <TextBlock HorizontalAlignment="Left" VerticalAlignment="Center" Margin="5,0,0,0">
            <TextBlock.Text>
                <MultiBinding Converter="{StaticResource RowColumnToCellConverter}" ConverterParameter="Text">
                    <Binding />
                    <Binding RelativeSource="{RelativeSource AncestorType=DataGridCell}" Path="Column" />
                </MultiBinding>
            </TextBlock.Text>
        </TextBlock>
    </Grid>
</DataTemplate>

и вот мой конвертер:

   public class RowColumnToCellConverter : MarkupExtension, IMultiValueConverter
   {
      public RowColumnToCellConverter() { }

      public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
      {
         XwpfRow row = values[0] as XwpfRow;
         XwpfTreeColumn column = values[1] as XwpfTreeColumn;

         if (row == null || column == null) return DependencyProperty.UnsetValue;

         TreeCell treeCell = (TreeCell)row[column.DataGrid.Columns.IndexOf(column)];
         switch ((string)parameter)
         {
            case "Text": return treeCell.Text;
            case "Expanded": return treeCell.Expanded;
            case "ShowExpandSymbol": return treeCell.ShowExpandSymbol;
            case "CurrentLevel": return new GridLength(treeCell.CurrentLevel * 14);

            default:
               throw new MissingMemberException("the property " + parameter.ToString() + " is not defined for the TreeCell object");
         }
      }

      public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
      {
         throw new NotSupportedException();
      }

      public override object ProvideValue(IServiceProvider serviceProvider)
      {
         return new RowColumnToCellConverter();
      }
   }

это сохраняет модель MVVM, и я предпочитаю такой способ работы, потому что я действительно не люблю использовать парсеры xaml для создания "динамических" шаблонов данных, но это все еще уродливый хак с моей точки зрения.

Хотелось бы, чтобы ребята из MS дали нам способ получать ячейки вместо строк в качестве dataContexts, чтобы иметь возможность генерировать шаблонные столбцы на лету ...

надеюсь, это поможет

РЕДАКТИРОВАТЬ: В вашем случае конвертер должен быть намного проще на самом деле (вы можете вернуть экземпляр ячейки напрямую, если я не ошибаюсь, и вам не нужны какие-либо параметры), но Тем не менее, я оставил более сложную версию, на случай, если у кого-то еще возникнет похожая проблема

...