提问人:greenoldman 提问时间:12/9/2010 最后编辑:Communitygreenoldman 更新时间:11/13/2023 访问量:37287
如何使WPF数据网格中的最后一列始终占用所有剩余空间?
How to make the last column in WPF datagrid take all the left space, always?
问:
标准 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 数据网格的两个问题:
- 误导性命名
- 特征矛盾
我仍然全神贯注于如何真正解决这个问题。
答:
将数据网格的宽度设置为“自动”。您允许列在网格本身中正确调整大小,但已将宽度硬连线为 200。
更新:根据@micas的评论,我可能看错了。如果是这种情况,请尝试将 100 用于左列的宽度,将 100* 用于右列(注意星号)。这会将右列的宽度默认为 100,但允许其调整大小以填充网格。
评论
您可以将列宽设置为在代码上加星标。 在构造函数中,添加:
Loaded += (s, e) => dataGrid1.Columns[3].Width =
new DataGridLength(1, DataGridLengthUnitType.Star);
评论
我刚刚将其实现为附加行为。问题是当您将 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));
}
}
}
预先警告:这是一个黑客......
我在 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);
}
这是一个非常简单的答案,全部在代码隐藏中执行。:-) 所有列都将自动调整大小;最后一列将填充所有剩余空间。
// 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);
我可能有点晚了,但你可以从这个问题中试试我的代码。我扩展了原始网格,并添加了最后一列拉伸的方法:
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
LayoutUpdated
DependencyProperty
根据 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"
评论
对于最后一列,设置并覆盖 ,例如 to 。Width="*"
MaxWidth
MaxWidth="2000"
评论
MaxWidth
如果你愿意使用 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);
}
}
}
用法:
- 添加命名空间
xmlns:i="http://schemas.microsoft.com/xaml/behaviors"
- 将此添加到您的中:
DataGrid
<i:Interaction.Behaviors>
<view:DataGridLastColumnFill />
</i:Interaction.Behaviors>
评论