开发者

WPF DataGrid: resizing columns

开发者 https://www.devze.com 2023-04-06 00:13 出处:网络
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.

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?


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


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


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消