This example demonstrates how to create a GridControl class descendant with horizontally-oriented columns (similar to the WinForms Vertical Grid Control).
This functionality is not available out of the box, so we used unbound columns to get/set cell values and customized row appearance (Modify Theme Resources).
If you want to use this custom solution in your real application, first check if you can implement one of the following solutions instead:
- Change the structure of your
ItemsSource
collection to use the regular Data Grid. - If you wish to show properties of a single object, use the Property Grid.
Files to Review
- VerticalGridControl.cs (VB: VerticalGridControl.vb)
- VerticalGridControlResources.xaml (VB: VerticalGridControlResources.xaml)
- MainWindow.xaml (VB: MainWindow.xaml)
- MainWindow.xaml.cs (VB: MainWindow.xaml.vb)
- SampleDataRow.cs (VB: SampleDataRow.vb)
Documentation
Does this example address your development requirements/objectives?
(you will be redirected to DevExpress.com to submit your response)
Example Code
C#using DevExpress.Data;
using DevExpress.Xpf.Grid;
using System;
using System.Collections;
using System.Collections.ObjectModel;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Linq;
using System.Reflection;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
namespace dxExample.VGrid {
public class VerticalGridControl : GridControl {
public VerticalRowsCollection Rows { get; set; }
public bool AutoPopulateRows {
get { return (bool)GetValue(AutoPopulateRowsProperty); }
set { SetValue(AutoPopulateRowsProperty, value); }
}
public new object ItemsSource {
get { return (object)GetValue(ItemsSourceProperty); }
set { SetValue(ItemsSourceProperty, value); }
}
public VerticalGridControl() {
CustomUnboundColumnData += OnCustomUnboundColumnData;
InitializeRowsCollection();
}
void InitializeRowsCollection() {
Rows = new VerticalRowsCollection();
Rows.CollectionChanged += OnRowsCollectionChanged;
}
void UpdateGridColumns() {
ICollection itemsSource = ItemsSource as ICollection;
if (itemsSource == null) {
Columns.Clear();
return;
}
int columnsCount = itemsSource.Count;
if (Columns.Count == columnsCount) return;
Columns.BeginUpdate();
int delta = columnsCount - Columns.Count;
if (columnsCount > Columns.Count) {
for (int i = Columns.Count; i < columnsCount; i++) {
Columns.Add(new GridColumn() { FieldName = i.ToString(), UnboundType = UnboundColumnType.Object });
}
} else {
for (int i = Columns.Count - 1; i >= columnsCount; i--) {
Columns.RemoveAt(i);
}
}
Columns.EndUpdate();
}
void UpdateItemsSourceCollectionChangedSubscription(object oldSource, object newSource) {
if (oldSource is INotifyCollectionChanged) {
((INotifyCollectionChanged)oldSource).CollectionChanged -= OnItemsSourceCollectionChanged;
}
if (newSource is INotifyCollectionChanged) {
((INotifyCollectionChanged)newSource).CollectionChanged += OnItemsSourceCollectionChanged;
}
}
void PopulateRows() {
if (!(ItemsSource is IEnumerable)) return;
var firstItem = ((IEnumerable)ItemsSource).Cast<object>().FirstOrDefault();
if (firstItem == null) return;
var newRows = TypeDescriptor.GetProperties(firstItem)
.Cast<PropertyDescriptor>()
.Select(p => new VerticalRow() { RowName = p.Name });
foreach (var row in newRows) {
Rows.Add(row);
}
}
void OnRowsCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
base.ItemsSource = Rows;
}
static void OnItemsSourceChanged(object sender, DependencyPropertyChangedEventArgs e) {
var grid = (VerticalGridControl)sender;
grid.UpdateGridColumns();
if (grid.AutoPopulateRows) {
grid.PopulateRows();
}
grid.UpdateItemsSourceCollectionChangedSubscription(e.OldValue, e.NewValue);
}
void OnItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) {
if (e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Remove) {
UpdateGridColumns();
}
}
void OnCustomUnboundColumnData(object sender, GridColumnDataEventArgs e) {
var itemsSource = ItemsSource as IList;
if (itemsSource == null) return;
var row = Rows[e.ListSourceRowIndex];
var item = itemsSource[Convert.ToInt32(e.Column.FieldName)];
var targetProperty = TypeDescriptor.GetProperties(item).Cast<PropertyDescriptor>().FirstOrDefault(p => p.Name == row.RowName);
if (targetProperty == null) return;
if (e.IsGetData) {
e.Value = targetProperty.GetValue(item);
}
if (e.IsSetData) {
targetProperty.SetValue(item, e.Value);
}
}
public static readonly DependencyProperty AutoPopulateRowsProperty = DependencyProperty.Register("AutoPopulateRows", typeof(bool), typeof(VerticalGridControl), new PropertyMetadata(false));
public static new readonly DependencyProperty ItemsSourceProperty = DependencyProperty.Register("ItemsSource", typeof(object), typeof(VerticalGridControl), new PropertyMetadata(null, OnItemsSourceChanged));
}
public class BottomIndicatorRowVisibilityConverter : IMultiValueConverter {
public object Convert(object[] values, Type targetType, object parameter, System.Globalization.CultureInfo culture) {
if (values.Count() < 2)
return Visibility.Collapsed;
if (!((values[0] is int) && (values[1] is int)))
return Visibility.Collapsed;
return ((int)values[0]) > ((int)values[1]) ? Visibility.Visible : Visibility.Collapsed;
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, System.Globalization.CultureInfo culture) {
throw new NotImplementedException();
}
}
public class DefaultCellTemplateSelector : DataTemplateSelector {
public override DataTemplate SelectTemplate(object item, DependencyObject container) {
var row = (VerticalRow)((EditGridCellData)item).RowData.Row;
return (row.CellTemplate != null) ? row.CellTemplate : base.SelectTemplate(item, container);
}
}
public class VerticalRow : DependencyObject {
public string RowName { get; set; }
public DataTemplate CellTemplate {
get { return (DataTemplate)GetValue(CellTemplateProperty); }
set { SetValue(CellTemplateProperty, value); }
}
public static readonly DependencyProperty CellTemplateProperty = DependencyProperty.Register("CellTemplate", typeof(DataTemplate), typeof(VerticalRow), new PropertyMetadata(null));
public static VerticalRow FromPropertyInfo(PropertyInfo info) {
return new VerticalRow() { RowName = info.Name };
}
}
public class VerticalRowsCollection : ObservableCollection<VerticalRow> {
protected override void InsertItem(int index, VerticalRow item) {
int existsIndex = IndexOf(item.RowName);
if (existsIndex > -1) {
if (Items[existsIndex].CellTemplate != null) return;
Items[existsIndex].CellTemplate = item.CellTemplate;
return;
}
base.InsertItem(index, item);
}
int IndexOf(string rowName) {
for (int i = 0; i < Items.Count; i++) {
if (Items[i].RowName == rowName) return i;
}
return -1;
}
}
}
XAML<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:dxg="http://schemas.devexpress.com/winfx/2008/xaml/grid"
xmlns:dxgt="http://schemas.devexpress.com/winfx/2008/xaml/grid/themekeys"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:dxExample.VGrid"
xmlns:sys="clr-namespace:System;assembly=mscorlib">
<sys:Double x:Key="{dxgt:TableViewThemeKey ResourceKey=IndicatorWidth, ThemeName=Office2016White}">180</sys:Double>
<!-- only for optimized mode -->
<ControlTemplate x:Key="{dxgt:RowIndicatorThemeKey ResourceKey=RowIndicatorTemplate, ThemeName=Office2016White}" TargetType="{x:Type dxg:RowIndicator}">
<Grid>
<Border x:Name="PART_ContentBorder" Opacity="0" />
<ContentPresenter Content="{Binding}" ContentTemplate="{DynamicResource {dxgt:RowIndicatorThemeKey ResourceKey=RowTemplate, ThemeName=Office2016White}}" />
</Grid>
</ControlTemplate>
<DataTemplate x:Key="{dxgt:RowIndicatorThemeKey ResourceKey=RowTemplate, ThemeName=Office2016White}">
<Grid>
<Border Background="White" BorderBrush="DarkGray" BorderThickness="0,0,1,1">
<dxg:GridColumnHeader />
</Border>
<Border x:Name="BottomRowBorder" BorderBrush="Black" BorderThickness="0,0,0,1">
<Border.Visibility>
<MultiBinding>
<Binding Path="Level"/>
<Binding Path="NextRowLevel"/>
<MultiBinding.Converter>
<local:BottomIndicatorRowVisibilityConverter/>
</MultiBinding.Converter>
</MultiBinding>
</Border.Visibility>
</Border>
<TextBlock Text="{Binding DataContext.Row.RowName, RelativeSource={RelativeSource TemplatedParent}}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Grid>
</DataTemplate>
<Style TargetType="{x:Type dxg:TableView}">
<Style.Setters>
<Setter Property="ShowTotalSummary" Value="False"/>
<Setter Property="AllowColumnMoving" Value="False"/>
<Setter Property="ShowGroupPanel" Value="False"/>
<Setter Property="AllowColumnFiltering" Value="False"/>
<Setter Property="MultiSelectMode" Value="Cell"/>
<Setter Property="CellTemplateSelector" Value="{DynamicResource defaultCellTemplateSelector}"/>
<Setter Property="UseLightweightTemplates" Value="All" />
</Style.Setters>
</Style>
<Style TargetType="{x:Type dxg:GridControl}">
<Style.Setters>
<Setter Property="AutoPopulateColumns" Value="False"/>
</Style.Setters>
</Style>
<local:DefaultCellTemplateSelector x:Key="defaultCellTemplateSelector"/>
</ResourceDictionary>
XAML<Window x:Class="dxExample.MainWindow"
Title="MainWindow" Height="350" Width="850"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:dxe="http://schemas.devexpress.com/winfx/2008/xaml/editors"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:VGrid="clr-namespace:dxExample.VGrid">
<Window.Resources>
<ResourceDictionary>
<DataTemplate x:Key="dateCellTemplate">
<dxe:DateEdit Name="PART_Editor"/>
</DataTemplate>
<DataTemplate x:Key="boolCellTemplate">
<dxe:CheckEdit Name="PART_Editor"/>
</DataTemplate>
<DataTemplate x:Key="intCellTemplate">
<dxe:TextEdit Name="PART_Editor" MaskType="Numeric" Mask="d"/>
</DataTemplate>
<ResourceDictionary.MergedDictionaries>
<ResourceDictionary Source="VGrid/VerticalGridControlResources.xaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Window.Resources>
<Grid>
<VGrid:VerticalGridControl x:Name="grid" Margin="2"
HorizontalAlignment="Stretch" VerticalAlignment="Stretch"
ItemsSource="{Binding Orders}"
AutoPopulateRows="True">
<VGrid:VerticalGridControl.Rows>
<VGrid:VerticalRow RowName="Date" CellTemplate="{StaticResource dateCellTemplate}"/>
<VGrid:VerticalRow RowName="HasFlag" CellTemplate="{StaticResource boolCellTemplate}"/>
<VGrid:VerticalRow RowName="Id" CellTemplate="{StaticResource intCellTemplate}"/>
</VGrid:VerticalGridControl.Rows>
</VGrid:VerticalGridControl>
</Grid>
</Window>
C#using System;
using System.Collections.Generic;
namespace dxExample {
class SampleDataRow {
public int Id { get; set; }
public string Name {get; set;}
public DateTime Date { get; set; }
public bool HasFlag {get; set;}
public static IList<SampleDataRow> CreateRows(int rowCount) {
IList<SampleDataRow> result = new List<SampleDataRow>();
for(int i = 0; i < rowCount; i++) {
result.Add(new SampleDataRow() {
Id = i,
Name = String.Format("name {0}", i % 3),
Date = DateTime.Now.AddDays(i),
HasFlag = (i % 3 == 0),
});
}
return result;
}
}
}