如何使WPF数据网格中的最后一列始终占用所有剩余空间?

How to make the last column in WPF datagrid take all the left space, always?

提问人:greenoldman 提问时间:12/9/2010 最后编辑:Communitygreenoldman 更新时间:11/13/2023 访问量:37287

问:

标准 WPF 4 数据网格。

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

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

我认为问题出在删除虚拟的第三列时,如下所述:

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

但实际上没有区别 - 仍然,在调整列大小时,我在右侧获得了一些额外的空间 - 使用虚拟列,它是一个虚拟列(默认为白色),没有它,它是空白区域(默认为灰色)。

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

sum(columns width)==datagrid width

?

编辑

是的,我使用 WPF 4。

解决方法

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

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

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

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

  • 误导性命名
  • 特征矛盾

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

WPF 数据网格

评论


答:

22赞 Babak Naffas 12/9/2010 #1

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

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

评论

0赞 greenoldman 12/9/2010
我想你的意思是最后一列的自动,而不是整个网格,对吧?Auto 表示适合内容,这让情况变得更糟 - 它在开始时显示额外的虚拟列。如果你的意思是整个网格真的是自动的——它没有任何区别。
0赞 greenoldman 12/9/2010
评论更新:它不是那样工作的。最后一列最初会填满整个左边空格 (!),但一旦调整了第一列的大小,最后一列的大小就不会改变,并且空白区域会出现在右侧。
8赞 biju 12/9/2010 #2

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

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

评论

0赞 greenoldman 12/9/2010
如果我在代码或 XAML 中设置它,则没有区别 - 它已经存在并且仅在最初起作用。一旦用户调整最后一个列旁边的列的大小,就会出现空白区域。
4赞 Dr. Andrew Burnett-Thompson 7/17/2013 #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));
        }
    }
}
0赞 pennyrave 6/11/2015 #4

预先警告:这是一个黑客......

我在 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 中具有可变列数的一些列仅与列标题一样宽。我删除了该部分,现在它似乎正在处理应用程序中的所有 DataGrid。

现在我有:

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

    }
0赞 Jay 7/22/2019 #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);

0赞 Arli Chokoev 10/21/2020 #6

我可能有点晚了,但你可以从这个问题中试试我的代码。我扩展了原始网格,并添加了最后一列拉伸的方法:

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

因此,您必须找到 类型的可视子项(从此处回答),该子项具有视口的宽度并计算最后一列的宽度。若要自动执行此操作,可以在事件时触发此方法。此外,还可以添加一个 ,指示是否应执行最后一列的自动拉伸。DataGridColumnHeadersPresenterLayoutUpdatedDependencyProperty

0赞 Brian Heward 12/24/2020 #7

根据 pennyrave 提供给 DR 的更新。ABT 的回答:我做了进一步的更新,让它更好地工作。这仍然是一个黑客,但当我不断更新 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 顶部的窗口标记中添加类似内容的内容,然后可以在 DataGrid 标记中使用。xmlns:Util="clr-namespace:MyProject.Util"Util:DataGridExtensions.LastColumnFill="True"

评论

0赞 Brian Heward 1/1/2021
如果 DataGrid 有滚动条,则这不考虑滚动条的大小。
1赞 DiMaAgalakov 6/26/2023 #8

对于最后一列,设置并覆盖 ,例如 to 。Width="*"MaxWidthMaxWidth="2000"

评论

0赞 Scover 11/13/2023
设置有什么帮助?它似乎对我无能为力。MaxWidth
0赞 Scover 11/13/2023 #9

如果你愿意使用 Microsoft.Xaml.Behaviors 包,下面是一个无样板解决方案,它还可以响应数据网格大小的变化:

using System.Windows;
using System.Windows.Controls;

using Microsoft.Xaml.Behaviors;

namespace YourNamespaceHere;

public sealed class DataGridLastColumnFill : Behavior<DataGrid>
{
    protected override void OnAttached()
    {
        AssociatedObject.Loaded += ResizeColumns;
        AssociatedObject.SizeChanged += ResizeColumns;
        base.OnAttached();
    }

    protected override void OnDetaching()
    {
        AssociatedObject.Loaded -= ResizeColumns;
        AssociatedObject.SizeChanged -= ResizeColumns;
        base.OnDetaching();
    }

    private void ResizeColumns(object sender, RoutedEventArgs e)
    {
        if (AssociatedObject.Columns.LastOrDefault() is { } lastColumn)
        {
            lastColumn.Width = new(1, DataGridLengthUnitType.Star);
        }
    }
}

用法:

  1. 添加命名空间xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
  2. 将此添加到您的中:DataGrid
<i:Interaction.Behaviors>
    <view:DataGridLastColumnFill />
</i:Interaction.Behaviors>