WPF DataGrid:调整列的大小

时间:2011-09-19 08:21:08

标签: .net wpf datagrid wpfdatagrid resize

我有一个System.Windows.Controls.DataGrid,其属性CanUserResizeColumns分配给True。现在,我可以通过在两个列标题之间单击鼠标左键来调整列的宽度。

但我也希望能够在dataGrid的任何行中更改列的宽度,而不仅仅是在列标题中。有可能吗?

3 个答案:

答案 0 :(得分:5)

在您的dataGrid中,您可以使用DataGridTemplate列与GridSplitter对齐来实现此目的。

 <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>

然后在你的代码后面......做这个......

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

这样,单个单元级别的GridSplitter可以调整其整个列的大小。

如果您使用MVVM,则应将上述事件处理程序置于附加行为

答案 1 :(得分:2)

继WPF之后 - 这是一个很好的答案,以下是如何在附加行为中实现相同的结果:

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);
    }
}

为了使所有网格分割器在DataGrid中显示为一个,我将DataGridCell的BorderThickness调整为0,否则所有网格分割器都显示为破折号(至少在Windows 8上)。

窗口的XAML如下所示:

<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>

剩下的工作非常明显,但为了完整起见,Windows DataContext被设置为以下ViewModel代码的实例:

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

答案 2 :(得分:0)

这是一种不会污染数据网格内容的替代解决方案。将Canvas层叠在DataGrid的顶部,并在该Canvas中有一条可以左右拖动的Line。拖动时,它会更新所需的列宽。

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#代码隐藏:

#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

注意我选择使线条透明,以便最终用户不会真正看到它。这是因为我已经依赖数据网格本身来显示列之间的垂直网格线。 此外,您可以选择线条粗细到您发现的任何用户友好,而不会影响网格单元格的布局。我选择了4,因为即使数据网格将垂直网格线渲染为1像素宽,也可以轻松拾取。

示例代码来自我的自定义PropertyGrid代码库,它只有两列,因此是硬编码的列0.为了更加通用化,我将其转换为附加行为,支持所需的列数,或子类DataGrid本身。

与之前的解决方案相比,这个解决方案只添加了一些WPF元素来支持行为,无论您拥有多少数据网格行,因此它可能在大型数据集上更有效和可扩展。