Reputation: 229
I have a System.Windows.Controls.DataGrid with property CanUserResizeColumns assigned to True. Now I can adjust the width of the columns by using the mouse left button click between 2 column headers.
But I also want to be able to change the width of the columns in any row of the dataGrid, not only in the column headers. Is it possible?
Upvotes: 3
Views: 8412
Reputation: 145
Here is an alternative solution that does not pollute your data grid contents. Layer a Canvas on top of the DataGrid, and within that Canvas have a Line that can be dragged left and right. When dragged, it updates the desired column width.
XAML:
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*" />
</Grid.RowDefinitions>
<DataGrid x:Name="grid" Grid.Row="0" /> <!-- This is your data grid -->
<Canvas Grid.Row="0"> <!-- Canvas layerd over data grid -->
<Line StrokeThickness="4" Stroke="Transparent" Cursor="SizeWE"
X1="{Binding Columns[0].ActualWidth, ElementName=grid}"
X2="{Binding X1, RelativeSource={RelativeSource Self}}"
Y2="{Binding ActualHeight, RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Canvas}}}"
MouseLeftButtonDown="OnSplitLineMouseLeftButtonDown"
MouseLeftButtonUp="OnSplitLineMouseLeftButtonUp"
MouseMove="OnSplitLineMouseMove"/>
</Canvas>
</Grid>
C# code-behind:
#region SplitBarHandling
bool splitBarDragging = false;
double splitBarMouseLastX = 0;
private void OnSplitLineMouseLeftButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
e.Handled = true;
splitBarDragging = true;
splitBarMouseLastX = e.GetPosition(null).X;
((UIElement)sender).CaptureMouse();
}
private void OnSplitLineMouseLeftButtonUp(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
e.Handled = true;
splitBarDragging = false;
((UIElement)sender).ReleaseMouseCapture();
}
private void OnSplitLineMouseMove(object sender, System.Windows.Input.MouseEventArgs e)
{
if (splitBarDragging)
{
e.Handled = true;
double newX = e.GetPosition(null).X;
grid.Columns[0].Width = grid.Columns[0].ActualWidth + (newX - splitBarMouseLastX);
splitBarMouseLastX = newX;
}
}
#endregion
Note I chose to make the line transparent so the final user will not actually see it. This is because I already rely on the data grid itself to show the vertical grid lines between columns. Also, you may choose the line thickness to whatever you find to be user-friendly without affecting the layout of the grid cells. I chose 4 because it makes it easy to pickup even though the datagrid renders the vertical grid line as 1-pixel wide.
The example code comes from my custom PropertyGrid code-base, which has only two columns, hence the hard-coded column 0. For more generalization, I'd turn this into an attached behavior with support for as many columns needed, or sub-class DataGrid itself.
Compared to the previous solution, this one only adds a few WPF elements to support the behavior regardless of how many data grid rows you have, so it might be more efficient and scalable on large data sets.
Upvotes: 0
Reputation: 29157
Following on from WPF-its excellent answer, here's how to achieve the same result with at attached behavior:
public static class SplitterOnGridCellBehaviour
{
public static readonly DependencyProperty ChangeGridCellSizeOnDragProperty =
DependencyProperty.RegisterAttached("ChangeGridCellSizeOnDrag", typeof (bool),
typeof (SplitterOnGridCellBehaviour),
new PropertyMetadata(false, OnChangeGridCellSizeOnDrag));
private static void OnChangeGridCellSizeOnDrag(DependencyObject dependencyObject, DependencyPropertyChangedEventArgs args)
{
GridSplitter splitter = dependencyObject as GridSplitter;
if(splitter == null)
{
throw new NotSupportedException("SplitterOnGridCellBehaviour can only be on a GridSplitter");
}
if((bool)args.NewValue)
{
splitter.DragDelta += SplitterOnDragDelta;
}
else
{
splitter.DragDelta -= SplitterOnDragDelta;
}
}
private static void SplitterOnDragDelta(object sender, DragDeltaEventArgs args)
{
GridSplitter splitter = (GridSplitter)sender;
var containerCell = splitter.FindParent<DataGridCell>();
containerCell.Column.Width = containerCell.Column.ActualWidth + args.HorizontalChange;
}
public static void SetChangeGridCellSizeOnDrag(UIElement element, bool value)
{
element.SetValue(ChangeGridCellSizeOnDragProperty, value);
}
public static bool GetChangeGridCellSizeOnDrag(UIElement element)
{
return (bool) element.GetValue(ChangeGridCellSizeOnDragProperty);
}
public static T FindParent<T>(this DependencyObject child)
where T : DependencyObject
{
DependencyObject parentObject = VisualTreeHelper.GetParent(child);
if (parentObject == null) return null;
var parent = parentObject as T;
if (parent != null)
{
return parent;
}
return FindParent<T>(parentObject);
}
}
To make all of the grid splitters appear as one in the DataGrid, I adjusted the BorderThickness of the DataGridCell to 0, otherwise all of the grid splitters appeared as dashes (on Windows 8 at least).
The XAML for the Window looks like this:
<Window x:Class="DataGridWithSplitter.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="clr-namespace:DataGridWithSplitter" Title="MainWindow" Height="350" Width="525">
<Window.Resources>
<DataTemplate x:Key="CellWithSplitterTemplate">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Column1}"/>
<GridSplitter Grid.Column="1" Width="3" Background="Black" local:SplitterOnGridCellBehaviour.ChangeGridCellSizeOnDrag="True" />
</Grid>
</DataTemplate>
</Window.Resources>
<Grid>
<DataGrid ItemsSource="{Binding SampleData}" GridLinesVisibility="None" HeadersVisibility="None" AutoGenerateColumns="False">
<DataGrid.Resources>
<!-- Makes the GridSplitters Solid -->
<Style TargetType="DataGridCell">
<Setter Property="BorderThickness" Value="0" />
</Style>
</DataGrid.Resources>
<DataGrid.Columns>
<DataGridTemplateColumn Header="First Column" CellTemplate="{StaticResource CellWithSplitterTemplate}" />
<DataGridTextColumn Header="Other column" Binding="{Binding Column2}" />
</DataGrid.Columns>
</DataGrid>
</Grid>
</Window>
The rest of it is fairly obvious to work out, but for completeness the Windows DataContext was set to an instance of the following ViewModel code:
public class SampleData
{
public string Column1 { get; set; }
public string Column2 { get; set; }
}
public class MainWindowViewModel
{
public IEnumerable<SampleData> SampleData
{
get
{
return new List<SampleData>()
{
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
new SampleData() {Column1 = "Hello", Column2 = "World"},
};
}
}
}
Upvotes: 2
Reputation: 19895
In your dataGrid you can use a DataGridTemplate
column alogn with a GridSplitter
to achieve this..
<toolkit:DataGridTemplateColumn Header="Text" >
<toolkit:DataGridTemplateColumn.CellTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<TextBlock Text="{Binding Text}"/>
<GridSplitter Grid.Column="1" Width="3"
DragIncrement="1"
DragDelta="GridSplitter_DragDelta"
Tag="{Binding BindsDirectlyToSource=True,
RelativeSource={RelativeSource
AncestorType={x:Type toolkit:DataGridCell}}}"/>
</Grid>
</DataTemplate>
</toolkit:DataGridTemplateColumn.CellTemplate>
</toolkit:DataGridTemplateColumn>
Then in your code behind... do this...
private void GridSplitter_DragDelta(
object sender,
System.Windows.Controls.Primitives.DragDeltaEventArgs e)
{
var gridSplitter = sender as GridSplitter;
if (gridSplitter != null)
{
((DataGridCell) gridSplitter.Tag).Column.Width
= ((DataGridCell) gridSplitter.Tag).Column.ActualWidth +
e.HorizontalChange;
}
}
This way a GridSplitter at individual cell level can resize its entire column.
If you are using MVVM then the above event handler should be put in an Attached Behavior
Upvotes: 5