【问题标题】:WPF DataGrid: resizing columnsWPF DataGrid:调整列大小
【发布时间】:2011-11-20 01:17:25
【问题描述】:

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

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

【问题讨论】:

  • 戴安娜,你真的应该接受答案,否则社区不会有动力帮助你。
  • 不。这太神秘了。 :(。
  • @AngelWPF,我认为她的意思是 - 我可以通过拖动其单元格的左/右边框来调整列宽(如列标题的行为)。有趣的问题,Excel 中也没有这样的行为。
  • @Michael,我之前误解了。显然有一个解决方案。 :-)

标签: .net wpf datagrid wpfdatagrid resize


【解决方案1】:

这是一种不会污染数据网格内容的替代解决方案。在 DataGrid 顶部放置一个 Canvas,在该 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 元素来支持该行为,而不管您有多少数据网格行,因此它在大型数据集上可能更加高效和可扩展。

【讨论】:

    【解决方案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 上)。

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

    【讨论】:

      【解决方案3】:

      在您的 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 来自我 - 效果很好 - 为了好玩,我决定在附加行为中使用您的代码并使用它发布答案。
      猜你喜欢
      • 2016-01-22
      • 2011-08-20
      • 2011-04-30
      • 1970-01-01
      • 1970-01-01
      • 2011-03-05
      • 1970-01-01
      • 2011-10-20
      • 2011-01-07
      相关资源
      最近更新 更多