【问题标题】:How to make the last column in WPF datagrid take all the left space, always?如何使 WPF 数据网格中的最后一列始终占用所有剩余空间?
【发布时间】:2011-05-22 08:05:21
【问题描述】:

标准 WPF 4 数据网格。

假设我有 200 像素宽和 2 列的数据网格。我希望这些列始终占据整个空间,这意味着如果用户将第一列的大小调整为 50 像素,则最后一列将是 150。

最初我将第一列的宽度设置为 100 像素,将最后一列设置为 *(在 XAML 中)。

我认为问题在于删除虚拟的第 3 列,如下所述:

http://wpf.codeplex.com/Thread/View.aspx?ThreadId=58939

但实际上并没有什么区别——不过,在调整列大小时,我在右边得到了一些额外的空间——如果有虚拟列,它是一个虚拟列(默认情况下为白色),没有它,它是空白空间(默认为灰色)。

问题:如何强制执行约束,即无论用户如何调整列的大小,

sum(columns width)==datagrid width

?

编辑

是的,我使用 WPF 4。

解决方法

我将其中一个答案标记为解决方案,但实际上由于 WPF 设计,它不是解决方案。这只是 WPF 最多可以做的事情,而且它不是很好——首先,列的选项 CanUserResize 意味着真的是 IsResizeable,并且这个选项在打开时与 Width 设置为 * 相矛盾。因此,如果没有一些真正聪明的技巧,你最终会得到:

  • datagrid 最后一列表面上可调整大小,但实际上并非如此,并且右侧显示的空间很小(即虚拟列不可调整大小)- 最后一列:CanUserResize=true, Width=*

  • 最后一列不能被用户调整大小的数据网格并相应地显示,最初右侧没有显示空间,但是当用户调整数据网格的任何元素大小时可以显示它——对于最后一列:CanUserResize=false, Width= *

到目前为止,我可以看到 WPF 数据网格存在两个问题:

  • 误导性命名
  • 特征矛盾

对于如何真正解决这个问题,我仍然全神贯注。

【问题讨论】:

    标签: wpf datagrid


    【解决方案1】:

    将数据网格的宽度设置为“自动”。您允许列在网格本身内正确调整大小,但您已将宽度硬连线为 200。

    更新:根据@micas 的评论,我可能看错了。如果是这种情况,请尝试使用 100 作为左列的宽度,使用 100* 作为右列的宽度(注意星号)。这会将右列的宽度默认为 100,但允许它调整大小以填充网格。

    【讨论】:

    • 我认为您的意思是 Auto 用于最后一列,而不是整个网格,对吧?自动意味着适合内容,这让事情变得更糟——它在一开始就显示了额外的虚拟列。如果您的意思是整个网格的真正自动 - 它没有任何区别。
    • 评论更新:它不是那样工作的。最后一列最初会填满整个左侧空间 (!),但是一旦您调整了第一列的大小,最后一列的大小不会改变,并且空白区域会出现在右侧。
    【解决方案2】:

    您可以将列宽设置为在代码上加注星标。 在您的构造函数中,添加:

        Loaded += (s, e) => dataGrid1.Columns[3].Width =
    new DataGridLength(1, DataGridLengthUnitType.Star);
    

    【讨论】:

    • 我在代码或 XAML 中设置它没有区别——它已经存在并且它仅在最初工作。一旦用户调整最后一列旁边的列的大小,就会出现空白。
    【解决方案3】:

    我刚刚将其实现为附加行为。问题是,当您将 DataGrid 的最后一列设置为 * 时,它确实会调整大小以适应,但随后其他单元格的所有自动拟合都会混乱。为了解决这个问题,附加的行为会手动自动调整其他(非最后一个)单元格。

    这也适用于调整其他列的大小 - 加载后,您可以调整大小,最后一列将始终填满。请注意,此行为在 Loaded 事件上有效

    // Behavior usage: <DataGrid DataGridExtensions.LastColumnFill="True"/>
    public class DataGridExtensions
    {
        public static readonly DependencyProperty LastColumnFillProperty = DependencyProperty.RegisterAttached("LastColumnFill", typeof(bool), typeof(DataGridExtensions), new PropertyMetadata(default(bool), OnLastColumnFillChanged));
    
        public static void SetLastColumnFill(DataGrid element, bool value)
        {
            element.SetValue(LastColumnFillProperty, value);
        }
    
        public static bool GetLastColumnFill(DataGrid element)
        {
            return (bool)element.GetValue(LastColumnFillProperty);
        }
    
        private static void OnLastColumnFillChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            var dataGrid = d as DataGrid;
            if (dataGrid == null) return;
    
            dataGrid.Loaded -= OnDataGridLoaded;
            dataGrid.Loaded += OnDataGridLoaded;
        }        
    
        private static void OnDataGridLoaded(object sender, RoutedEventArgs e)
        {
            var dataGrid = sender as DataGrid;
            if (dataGrid == null) return;
    
            var lastColumn = dataGrid.Columns.LastOrDefault();
            if(lastColumn != null)
                lastColumn.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
    
            // Autofit all other columns
            foreach (var column in dataGrid.Columns)
            {
                if (column == lastColumn) break;
    
                double beforeWidth = column.ActualWidth;
                column.Width = new DataGridLength(1, DataGridLengthUnitType.SizeToCells);
                double sizeCellsWidth = column.ActualWidth;
                column.Width = new DataGridLength(1, DataGridLengthUnitType.SizeToHeader);
                double sizeHeaderWidth = column.ActualWidth;
                column.MinWidth = Math.Max(beforeWidth, Math.Max(sizeCellsWidth, sizeHeaderWidth));
            }
        }
    }
    

    【讨论】:

      【解决方案4】:

      预先警告:这是一个 hack....

      我在Dr. ABT的类的“OnLastColumnFillChanged”方法中注册了“AutoGeneratedColumns”事件,并将Loaded方法复制到其中,它可以工作。我还没有真正彻底测试过,所以 YMMV。

      我的改变:

          private static void OnLastColumnFillChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
          {
              var dataGrid = d as DataGrid;
              if (dataGrid == null) return;
      
              dataGrid.Loaded -= OnDataGridLoaded;
              dataGrid.Loaded += OnDataGridLoaded;
      
              dataGrid.AutoGeneratedColumns -= OnDataGrid_AutoGeneratedColumns;
              dataGrid.AutoGeneratedColumns += OnDataGrid_AutoGeneratedColumns;
          }
      
          private static void OnDataGrid_AutoGeneratedColumns(object sender, EventArgs e)
          {
              var dataGrid = sender as DataGrid;
              if (dataGrid == null) return;
      
              var lastColumn = dataGrid.Columns.LastOrDefault();
              if (lastColumn != null)
                  lastColumn.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
      
              // Autofit all other columns
              foreach (var column in dataGrid.Columns)
              {
                  if (column == lastColumn) break;
      
                  double beforeWidth = column.ActualWidth;
                  column.Width = new DataGridLength(1, DataGridLengthUnitType.SizeToCells);
                  double sizeCellsWidth = column.ActualWidth;
                  column.Width = new DataGridLength(1, DataGridLengthUnitType.SizeToHeader);
                  double sizeHeaderWidth = column.ActualWidth;
                  column.MinWidth = Math.Max(beforeWidth, Math.Max(sizeCellsWidth, sizeHeaderWidth));
              }
          }
      

      哦,别忘了将命名空间添加到 XAML 声明中! :)

      顶部:

      xmlns:ext="clr-namespace:TestProject.Extensions"
      

      然后在DataGrid声明中:

      ext:DataGridExtensions.LastColumnFill="True"
      

      更新: 我说里程会有所不同!我的当然有。

      整个“自动调整列”位导致我在 DataGrid 中具有可变列数的某些列仅与列标题一样宽。我删除了那部分,现在它似乎在应用程序中的所有 DataGrids 上工作。

      现在我有:

          private static void OnDataGrid_AutoGeneratedColumns(object sender, EventArgs e)
          {
              var dataGrid = sender as DataGrid;
              if (dataGrid == null) return;
      
              UpdateColumnWidths(dataGrid);
      
          }
      
          private static void OnDataGridLoaded(object sender, RoutedEventArgs e)
          {
              var dataGrid = sender as DataGrid;
              if (dataGrid == null) return;
      
              UpdateColumnWidths(dataGrid);
          }
      
          private static void UpdateColumnWidths(DataGrid dataGrid)
          {
              var lastColumn = dataGrid.Columns.LastOrDefault();
              if (lastColumn == null) return;
      
              lastColumn.Width = new DataGridLength(1.0d, DataGridLengthUnitType.Star);
      
          }
      

      【讨论】:

        【解决方案5】:

        这是一个非常简单的答案,全部在后面的代码中执行。 :-) 所有列都将自动调整大小;最后一列将填满所有剩余空间。

        // build your datasource, e.g. perhaps you have a:
        List<Person> people = ...
        
        // create your grid and set the datasource
        var dataGrid = new DataGrid();
        dataGrid.ItemsSource = people;
        
        // set AutoGenerateColumns to false and manually define your columns
        // this is the price for using my solution :-)
        dataGrid.AutoGenerateColumns = false;
        
        // example of creating the columns manually.
        // there are obviously more clever ways to do this 
        var col0 = new DataGridTextColumn();
        col0.Binding = new Binding("LastName");
        var col1 = new DataGridTextColumn();
        col1.Binding = new Binding("FirstName");
        var col2 = new DataGridTextColumn();
        col2.Binding = new Binding("MiddleName");
        dataGrid.Columns.Add(col0);
        dataGrid.Columns.Add(col1);
        dataGrid.Columns.Add(col2);
        
        // Set the width to * for the last column
        col2.Width = new DataGridLength(1, DataGridLengthUnitType.Star);
        
        

        【讨论】:

          【解决方案6】:

          我可能有点晚了,但你可以试试我来自this question 的代码。我扩展了原始网格并添加了最后一列拉伸的方法:

          private void StretchLastColumnToTheBorder()
          {
              if (ViewPortWidth.HasValue)
              {
                  var widthSum = 0d;
                  for (int i = 0; i < Columns.Count; i++)
                  {
                      if (i == Columns.Count - 1 && ViewPortWidth > widthSum + Columns[i].MinWidth)
                      {
                          var newWidth = Math.Floor(ViewPortWidth.Value - widthSum);
                          Columns[i].Width = new DataGridLength(newWidth, DataGridLengthUnitType.Pixel);
                          return;
                      }
                      widthSum += Columns[i].ActualWidth;
                  }
              }
          }
          

          ViewPortWidth 在哪里:

          public double? ViewPortWidth 
          { 
              get 
              {
                  return FindChild<DataGridColumnHeadersPresenter>(this, "PART_ColumnHeadersPresenter")?.ActualWidth;
              } 
          }
          

          因此,您必须找到 DataGridColumnHeadersPresenter 类型的可视子项(来自 here 的答案),它具有视口的宽度并计算最后一列的宽度。要自动执行此操作,您可以在 LayoutUpdated 事件上触发此方法。此外,您可以添加DependencyProperty,指示是否应自动拉伸最后一列。

          【讨论】:

            【解决方案7】:

            基于pennyrave 提供给DR.ABT's answer 的更新,我进行了进一步的更新以使其更好地工作。它仍然是一个 hack,但是当我不断更新 DataGrid 的 ItemsSource 属性时,它似乎比他们的任何一个答案都好。如果我尝试在任何地方使用星号或自动宽度,WPF 坚持所有列只有 20 像素宽,所以我根据它们设置的自动值对它们进行硬编码。

            我已经向 AutoGeneratedColumns 事件添加了一个调用,以使其延迟一点。没有这个延迟,所有的列都坚持它们只有 20 像素宽。他们有时仍然这样做,但我对此进行了检查,它似乎有效,(但列呈现错误,然后在一毫秒后更正。)

            理想情况下,我们会在 WPF 计算出自动大小是多少之后并在呈现 DataGrid 之前应用列大小,但我找不到任何方法让我的代码在那里运行。要么太早,要么太晚。

            public class DataGridExtensions
            {
                public static readonly DependencyProperty LastColumnFillProperty = DependencyProperty.RegisterAttached("LastColumnFill", typeof(bool), typeof(DataGridExtensions), new PropertyMetadata(default(bool), OnLastColumnFillChanged));
            
                public static void SetLastColumnFill(DataGrid element, bool value)
                {
                    element.SetValue(LastColumnFillProperty, value);
                }
            
                public static bool GetLastColumnFill(DataGrid element)
                {
                    return (bool)element.GetValue(LastColumnFillProperty);
                }
            
                private static void OnLastColumnFillChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
                {
                    var dataGrid = d as DataGrid;
                    if (dataGrid == null) return;
            
                    dataGrid.Loaded -= OnDataGridLoaded;
                    dataGrid.Loaded += OnDataGridLoaded;
            
                    dataGrid.AutoGeneratedColumns -= OnDataGrid_AutoGeneratedColumns;
                    dataGrid.AutoGeneratedColumns += OnDataGrid_AutoGeneratedColumns;
                }
            
                private static void OnDataGrid_AutoGeneratedColumns(object sender, EventArgs e)
                {
                    var dataGrid = sender as DataGrid;
                    if (dataGrid == null) return;
            
                    Application.Current.Dispatcher.BeginInvoke(DispatcherPriority.Render, new Action(() => afterInvoke(dataGrid)));
                }
            
                private static void afterInvoke(DataGrid dataGrid)
                {
                    bool nonMin = false;
                    foreach (var col in dataGrid.Columns)
                    {
                        if (col.ActualWidth != col.MinWidth)
                        {
                            nonMin = true;
                        }
                    }
                    if(nonMin)
                    {
                        OnDataGridLoaded(dataGrid, null);
                    }
                }
            
                public static void OnDataGridLoaded(object sender, RoutedEventArgs e)
                {
                    var dataGrid = sender as DataGrid;
                    if (dataGrid == null) return;
            
                    // set size of columns
                    double sizeSoFar = 0;
                    for(int i =0; i < dataGrid.Columns.Count; i++)
                    {
                        var column = dataGrid.Columns[i];
            
                        //if last column
                        if (i == dataGrid.Columns.Count-1)
                        {
                            sizeSoFar = dataGrid.ActualWidth - sizeSoFar - 2;//2 pixels of padding
                            if(column.ActualWidth != sizeSoFar)
                            {
                                column.MinWidth = sizeSoFar;
                                column.Width = new DataGridLength(sizeSoFar);
                            }
                        }
                        else //not last column
                        {
                            double beforeWidth = column.ActualWidth;
                            column.Width = new DataGridLength(1, DataGridLengthUnitType.SizeToCells);
                            double sizeCellsWidth = column.ActualWidth;
                            column.Width = new DataGridLength(1, DataGridLengthUnitType.SizeToHeader);
                            double sizeHeaderWidth = column.ActualWidth;
                            column.MinWidth = Math.Max(beforeWidth, Math.Max(sizeCellsWidth, sizeHeaderWidth));
            
                            sizeSoFar += column.MinWidth; //2 pixels of padding and 1 of border
                        }
                    }
                }
            }
            

            记得在 xaml 顶部的窗口标签中添加 xmlns:Util="clr-namespace:MyProject.Util" 之类的内容,然后您可以在 DataGrid 标签中使用 Util:DataGridExtensions.LastColumnFill="True"

            【讨论】:

            • 如果您的 DataGrid 有滚动条,这不考虑滚动条的大小。
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2019-04-22
            • 2012-09-27
            • 2019-09-23
            • 2021-01-19
            • 1970-01-01
            • 1970-01-01
            • 2010-11-06
            相关资源
            最近更新 更多