如何使WPF数据网格中的最后一列占用所有左侧空格,总是?

时间:2010-12-08 16:12:09

标签: wpf datagrid

标准WPF 4数据网格。

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

最初我为第1列设置宽度为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 = * < / p>

  • datagrid,用户无法调整最后一列的大小,并且相应地显示,最初没有显示右边的空格,但是当用户调整datagrid的任何元素时,可以显示它 - 对于最后一列:CanUserResize = false ,宽度= *

到目前为止,我可以看到WPF datagrid的两个问题:

  • 误导性命名
  • 功能的矛盾

我仍然很清楚如何真正解决这个问题。

7 个答案:

答案 0 :(得分:21)

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

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

答案 1 :(得分:5)

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

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

答案 2 :(得分:4)

我刚刚将此作为附加行为实现。问题是当你将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));
        }
    }
}

答案 3 :(得分:0)

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

我注册了&#34; AutoGeneratedColumns&#34; &#34; OnLastColumnFillChanged&#34;中的事件Dr. ABT的类的方法并将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"

<强>更新:   我说里程会有所不同!我当然做到了。

整个&#34; autofit列&#34; bit导致我的一些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);

    }

答案 4 :(得分:0)

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

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

答案 5 :(得分:0)

我可能会迟到一些,但是您可以尝试从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,以指示是否应自动拉伸最后一列。

答案 6 :(得分:0)

根据 pennyraveDR.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 顶部的 window 标记中添加类似 xmlns:Util="clr-namespace:MyProject.Util" 的内容,然后您可以在 DataGrid 标记中使用 Util:DataGridExtensions.LastColumnFill="True"