XAML WPF DataGrid:滚动时减小列宽以适合其内容,只有一个除外

XAML WPF DataGrid: reduce columns width to fit its content while scrolling except one

是在滚动期间自动减小 DataGrid 列宽度的解决方案。 我需要一个稍微修改的版本,其中最后一列填充其他列之后剩下的所有行宽。

旧的解决方案:

private void OnLoadingRow(object sender, DataGridRowEventArgs e)
{
    if (sender is not DataGrid dg) return;
    foreach (var c in dg.Columns) c.Width = 0;
    e.Row.UpdateLayout();
    foreach (var c in dg.Columns) c.Width = DataGridLength.Auto;
}

如果在方法末尾添加 dg.Columns.Last().Width = new DataGridLength(1, DataGridLengthUnitType.Star); 最后一列不考虑其他列 Auto 大小并强制其大小为 20px(参见图片)。

更新:

我要求描述行为的原因是 select 当鼠标单击行的任何位置时编辑该行。当我单击该行的右侧时,该行没有 select - 没有单元格。我通过添加 MouseLeftButtonDown 描述的事件处理程序实现了这一点 .

UpdateLayout 调用完全是多余的。修改 Width 将自动触发布局过程。此外,为了提高性能,应该将两个迭代合并为一个迭代。
就用户体验而言,完整的实现是非常值得怀疑的:如果用户调整列的宽度,您将覆盖并重置他的自定义,这提供了糟糕的体验。此外,网格可能会不断调整自身大小,这看起来和感觉起来也很奇怪。您至少应该将 DataGrid.CanUserResizeColumns 设置为 false
我相信添加一个列过滤器,允许用户隐藏特定列以压缩视图,具有更高的价值。

根据您发布的代码,无法准确判断发生了什么或可能导致问题的原因。 DataGrid 控件及其上下文的实际布局配置显然未公开。

但是,问题可能与 DataGrid 如何计算每列的宽度有关,以防您给 DataGrid 一个固定的 Width 而这个值会导致超出可用视口宽度的内容(在这种情况下会出现 ScrollViewer):

a) 如果每列宽度设置为 AutoDataGrid 将简单地为每列提供所需的最小宽度。

b) 如果列的宽度设置为星号 * 大小,则分配的宽度是相对于可用的剩余 space.
计算的 为了计算出合理的值,该算法将使用 DataGrid.
的有效大小 关键是,当使用 ScrollViewer 时,虚拟宽度将是无限的。现在,如果算法将最后一列的宽度设置为无限大,您将得到一个非常小的滚动条。因此,无限不是一个合理的值。实际上,无法计算最终宽度。这就是为什么算法回退使用 DataGrid 的有效宽度,即 ScrollViewer.ViewportWidth。结果是所有列都必须压缩到可用视口 space 中,导致列剪切其 headers 和单元格内容。

我可以提供两种解决方案。

第一个解决方案是对最后一列使用固定的 Width,例如 1000 DIPs。
该解决方案的扩展性非常差。最后一列的宽度要么太小,要么宽度太大,这没有多大意义(这就是为什么无法计算出最后一列的合理宽度的原因 - 在这种情况下什么是合理的?)。

第二个更好的解决方案是尝试检测 ScrollViewer 是否可以滚动:在这种情况下,最后一列至少会部分不在视图中。这导致 Auto 最后一列的合理 Width:所需的最小值 space.
另一种情况,当 ScrollViewer 无法滚动时,所有列都在视口中可见。这导致 * 的合理 Width 以允许最后一列填充剩余的 space:


private void OnLoadingRow(object sender, DataGridRowEventArgs e)
{
  var dataGrid = sender as DataGrid;

  foreach (var dataGridColumn in dataGrid.Columns)
  {
    dataGridColumn.Width = 0;
    dataGridColumn.Width = DataGridLength.Auto;
  }

  if (TryFindVisualChildElementByName(dataGrid, string.Empty, out ScrollViewer scrollViewer) 
    && scrollViewer.ScrollableWidth == 0)
  {
    dataGrid.Columns.Last().Width = new DataGridLength(1, DataGridLengthUnitType.Star);
  }
}

public static bool TryFindVisualChildElementByName<TChild>(
  DependencyObject parent,
  string childElementName,
  out TChild resultElement) where TChild : FrameworkElement
{
  resultElement = null;

  if (parent is Popup popup)
  {
    parent = popup.Child;
    if (parent == null)
    {
      return false;
    }
  }

  for (var childIndex = 0; childIndex < VisualTreeHelper.GetChildrenCount(parent); childIndex++)
  {
    DependencyObject childElement = VisualTreeHelper.GetChild(parent, childIndex);

    if (childElement is TChild frameworkElement)
    {
      if (string.IsNullOrWhiteSpace(childElementName) 
        || frameworkElement.Name.Equals(childElementName, StringComparison.OrdinalIgnoreCase))
      {
        resultElement = frameworkElement;
        return true;
      }
    }

    if (TryFindVisualChildElementByName(childElement, childElementName, out resultElement))
    {
      return true;
    }
  }

  return false;
}

似乎 DataGrid 无法一次性处理所有更改,但您可以“错开”更新并通过将最后一部分放在事件循环中来执行“中间”布局,例如,像这样:

private void OnLoadingRow(object sender, DataGridRowEventArgs e)
{
    if (sender is not DataGrid dg) return;
    foreach (var c in dg.Columns) c.Width = 0;
    foreach (var c in dg.Columns.SkipLast(1)) c.Width = DataGridLength.Auto;            
    Dispatcher.BeginInvoke((DataGridColumn lc) => lc.Width = new DataGridLength(1, DataGridLengthUnitType.Star), dg.Columns.Last());
}

这不是一个很好的解决方案,并且由于列宽的可见变化,网格有点闪烁。也许可以改进它,例如,通过检测何时实际需要“重置”宽度。你决定它是否足够好。

编辑 - 通过在中间布局期间将最后一列设置为相同的宽度,这是一个稍微不那么闪烁的版本:

private void OnLoadingRow(object sender, DataGridRowEventArgs e)
{
    if (sender is not DataGrid dg) return;
    foreach (var c in dg.Columns.SkipLast(1)) c.Width = 0;
    var lastColumn = dg.Columns.Last();
    lastColumn.Width = lastColumn.ActualWidth;
    foreach (var c in dg.Columns.SkipLast(1)) c.Width = DataGridLength.Auto;            
    Dispatcher.BeginInvoke((DataGridColumn lc) => 
        lc.Width = new DataGridLength(1, DataGridLengthUnitType.Star), lastColumn);
}

编辑 2 - 重构以使其更清晰:

private void OnLoadingRow(object sender, DataGridRowEventArgs e)
{
    if (sender is not DataGrid dg) return;
    foreach (var c in dg.Columns.SkipLast(1))
    {
        c.Width = 0;
        c.Width = DataGridLength.Auto;
    }
    var lastColumn = dg.Columns.Last();
    lastColumn.Width = lastColumn.ActualWidth;            
    Dispatcher.BeginInvoke(static (DataGridColumn lc) => 
        lc.Width = new DataGridLength(1, DataGridLengthUnitType.Star), lastColumn);
}