I would like to have a WPF DataGrid with a horizontal orientation, d开发者_开发问答oes anyone know a solution?
I've done this earlier since we wanted to be able to use the same control for a DataGrid
and a PropertyGrid
. Alot of things have to be changed (like alignment, scrolling, positioning of sort-arrows etc.). There is way to much code to post the whole solution but this should get you started. This is an example with Autogenerated TextColumns but you can easily modify it to use other column types.
<ScrollViewer Name="c_dataGridScrollViewer"
Loaded="c_dataGridScrollViewer_Loaded"
VerticalScrollBarVisibility="Auto"
HorizontalScrollBarVisibility="Auto">
<DataGrid x:Name="c_dataGrid"
HorizontalAlignment="Left"
VerticalAlignment="Top"
AutoGeneratedColumns="c_dataGrid_AutoGeneratedColumns"
HorizontalScrollBarVisibility="Hidden"
VerticalScrollBarVisibility="Hidden">
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}">
<Setter Property="LayoutTransform">
<Setter.Value>
<TransformGroup>
<RotateTransform Angle="90"/>
</TransformGroup>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="-90"/>
</TransformGroup>
</DataGrid.LayoutTransform>
</DataGrid>
</ScrollViewer>
And when the Columns are generated, we reverse their positions and rotates the TextBlocks and TextBoxes (This is better than rotating the DataGridCell
in terms of alignment, blur etc.)
private void c_dataGridScrollViewer_Loaded(object sender, RoutedEventArgs e)
{
// Add MouseWheel support for the datagrid scrollviewer.
c_dataGrid.AddHandler(MouseWheelEvent, new RoutedEventHandler(DataGridMouseWheelHorizontal), true);
}
private void DataGridMouseWheelHorizontal(object sender, RoutedEventArgs e)
{
MouseWheelEventArgs eargs = (MouseWheelEventArgs)e;
double x = (double)eargs.Delta;
double y = c_dataGridScrollViewer.VerticalOffset;
c_dataGridScrollViewer.ScrollToVerticalOffset(y - x);
}
private void c_dataGrid_AutoGeneratedColumns(object sender, EventArgs e)
{
TransformGroup transformGroup = new TransformGroup();
transformGroup.Children.Add(new RotateTransform(90));
foreach (DataGridColumn dataGridColumn in c_dataGrid.Columns)
{
if (dataGridColumn is DataGridTextColumn)
{
DataGridTextColumn dataGridTextColumn = dataGridColumn as DataGridTextColumn;
Style style = new Style(dataGridTextColumn.ElementStyle.TargetType, dataGridTextColumn.ElementStyle.BasedOn);
style.Setters.Add(new Setter(TextBlock.MarginProperty, new Thickness(0, 2, 0, 2)));
style.Setters.Add(new Setter(TextBlock.LayoutTransformProperty, transformGroup));
style.Setters.Add(new Setter(TextBlock.HorizontalAlignmentProperty, HorizontalAlignment.Center));
Style editingStyle = new Style(dataGridTextColumn.EditingElementStyle.TargetType, dataGridTextColumn.EditingElementStyle.BasedOn);
editingStyle.Setters.Add(new Setter(TextBox.MarginProperty, new Thickness(0, 2, 0, 2)));
editingStyle.Setters.Add(new Setter(TextBox.LayoutTransformProperty, transformGroup));
editingStyle.Setters.Add(new Setter(TextBox.HorizontalAlignmentProperty, HorizontalAlignment.Center));
dataGridTextColumn.ElementStyle = style;
dataGridTextColumn.EditingElementStyle = editingStyle;
}
}
List<DataGridColumn> dataGridColumns = new List<DataGridColumn>();
foreach (DataGridColumn dataGridColumn in c_dataGrid.Columns)
{
dataGridColumns.Add(dataGridColumn);
}
c_dataGrid.Columns.Clear();
dataGridColumns.Reverse();
foreach (DataGridColumn dataGridColumn in dataGridColumns)
{
c_dataGrid.Columns.Add(dataGridColumn);
}
}
I am truly standing on the shoulders of giants here :-) but, I have an additional enhancement.
@dimaKudr suggested a way to transform predefined columns without code behind, and @FrankE refined the order of the columns. What i am adding is a way to transform, automatically generated columns (AutoGenerateColumns="True"
), by using the DataGrid.CellStyle
template. So the complete (and quite elegant) solution is:
<DataGrid ItemsSource="{Binding YourObservableCollection}"
AutoGenerateColumns="True"
AutoGeneratingColumn="OnAutoGeneratingColumn">
<DataGrid.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="90"/>
<MatrixTransform Matrix="-1,0,0,1,0,0"/>
</TransformGroup>
</DataGrid.LayoutTransform>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}"
BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
<Setter Property="LayoutTransform">
<Setter.Value>
<TransformGroup>
<RotateTransform Angle="-90"/>
<ScaleTransform ScaleX="1" ScaleY="-1" />
</TransformGroup>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
<DataGrid.CellStyle>
<Style TargetType="DataGridCell">
<Setter Property="LayoutTransform">
<Setter.Value>
<TransformGroup>
<RotateTransform Angle="-90"/>
<ScaleTransform ScaleX="1" ScaleY="-1" />
</TransformGroup>
</Setter.Value>
</Setter>
</Style>
</DataGrid.CellStyle>
</DataGrid>
I've simplified a little bit previous solution. I do not like black magic with additional scrollviewer, so I do not use it. But instead I use additional scale transformation.
<DataGrid.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="-90"/>
<ScaleTransform ScaleX="1" ScaleY="-1" />
</TransformGroup>
</DataGrid.LayoutTransform>
<DataGrid.ColumnHeaderStyle>
<Style TargetType="{x:Type DataGridColumnHeader}"
BasedOn="{StaticResource {x:Type DataGridColumnHeader}}">
<Setter Property="LayoutTransform">
<Setter.Value>
<TransformGroup>
<RotateTransform Angle="-90"/>
<ScaleTransform ScaleX="1" ScaleY="-1" />
</TransformGroup>
</Setter.Value>
</Setter>
</Style>
</DataGrid.ColumnHeaderStyle>
In case of predefined list of colums it is possible to transfom cells content directly in XAML:
<Style x:Key="TextCellStyle" TargetType="{x:Type TextBlock}">
<Setter Property="LayoutTransform">
<Setter.Value>
<TransformGroup>
<RotateTransform Angle="-90"/>
<ScaleTransform ScaleX="1" ScaleY="-1" />
</TransformGroup>
</Setter.Value>
</Setter>
</Style>
This allows you completely escape codebehind.
I found this approach very useful, I however did a rotation and a mirroring:
TransformGroup transformGroup = new TransformGroup();
transformGroup.Children.Add(new RotateTransform(90));
transformGroup.Children.Add(new MatrixTransform(-1, 0, 0, 1, 0, 0));
or in Xaml:
<!-- we rotate the whole DataGrid by -90 degree and then mirror via y-Axis so that it is docked vertically to the left side-->
<DataGrid.LayoutTransform>
<TransformGroup>
<RotateTransform Angle="90"/>
<MatrixTransform Matrix="-1,0,0,1,0,0"/>
</TransformGroup>
</DataGrid.LayoutTransform>
By using the mirroring I have the field at the end of the column list at the bottom instead of at the top.
Wow, great solution, thanks! It also requires modifying row / column selection switch between up/down and left/right buttons. I tried to cover this in the DataGrid's keydown or keyup events, but that behaves kind of weird, flipping the selection back and forth. Anyone with a better solution than the one below? This is just the code for shift tabbing left and tabbing right through wat are originally the rows.
private void c_dataGrid_KeyUp(object sender, KeyEventArgs e)
{
switch (e.Key)
{
case Key.Tab:
if (Keyboard.IsKeyDown(Key.LeftShift))
{
if (c_dataGrid.SelectedIndex > 0)
{
c_dataGrid.SelectedIndex--;
}
}
else
{
if (c_dataGrid.SelectedIndex < c_dataGrid.Items.Count - 1)
{
c_dataGrid.SelectedIndex++;
}
}
e.Handled = true;
break;
}
}
}
精彩评论