WPF DataGrid:滚动时减小列宽以适合其内容
WPF DataGrid: reduce column width to fit its content while scrolling
当我滚动垂直滚动条时,DataGrid
如果新可见行中的内容更大并且超过了先前的列宽,则自动扩展列宽。没关系。
但是如果所有较大的行都被滚动并且新的可见行的内容宽度较小,DataGrid
不会减小列宽。有办法实现吗?
附加行为的实现会很棒。
代码:
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
var persons = new List<Person>();
for (var i = 0; i < 20; i++)
persons.Add(new Person() {Name = "Coooooooooooooool", Surname = "Super"});
for (var i = 0; i < 20; i++)
persons.Add(new Person() {Name = "Cool", Surname = "Suuuuuuuuuuuuuuper"});
for (var i = 0; i < 20; i++)
persons.Add(new Person() {Name = "Coooooooooooooool", Surname = "Super"});
DG.ItemsSource = persons;
}
public class Person
{
public string Name { get; set; }
public string Surname { get; set; }
}
}
XAML:
<Window
x:Class="WpfApp4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="400"
Height="200"
mc:Ignorable="d">
<Grid>
<DataGrid
x:Name="DG"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserResizeRows="False"
CanUserSortColumns="False" />
</Grid>
</Window>
将 LoadingRow 属性 添加到您的数据网格:
<DataGrid x:Name="DG"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserResizeRows="False"
CanUserSortColumns="False" LoadingRow="DG_LoadingRow">
</DataGrid>
然后添加这段代码
在后面的代码中:
private void DG_LoadingRow(object sender, DataGridRowEventArgs e)
{
foreach (DataGridColumn c in DG.Columns)
c.Width = 0;
DG.UpdateLayout();
foreach (DataGridColumn c in DG.Columns)
c.Width = DataGridLength.Auto;
}
这绝对不是最干净的解决方案,但它会在滚动时调整可见列的大小。
希望这对您有所帮助。
Can you please wrap this into attached behaviour?
1) 第一个选项是使用 附加 属性.
public class DataGridHelper : DependencyObject
{
public static readonly DependencyProperty SyncedColumnWidthsProperty =
DependencyProperty.RegisterAttached(
"SyncedColumnWidths",
typeof(Boolean),
typeof(DataGridHelper),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnSyncColumnsChanged)
));
private static void OnSyncColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is DataGrid dataGrid)
{
dataGrid.LoadingRow += SyncColumnWidths;
}
}
private static void SyncColumnWidths(object sender, DataGridRowEventArgs e)
{
var dataGrid = (DataGrid)sender;
foreach (DataGridColumn c in dataGrid.Columns)
c.Width = 0;
e.Row.UpdateLayout();
foreach (DataGridColumn c in dataGrid.Columns)
c.Width = DataGridLength.Auto;
}
public static void SetSyncedColumnWidths(UIElement element, Boolean value)
{
element.SetValue(SyncedColumnWidthsProperty, value);
}
}
用法
<DataGrid
ext:DataGridHelper.SyncedColumnWidths="True"
... />
2) 或者,Behaviors 提供了一种更加封装的方式来扩展功能(需要 System.Windows.Interactivity)。
using System.Windows.Interactivity;
...
public class SyncedColumnWidthsBehavior : Behavior<DataGrid>
{
protected override void OnAttached()
{
this.AssociatedObject.LoadingRow += this.SyncColumnWidths;
}
protected override void OnDetaching()
{
this.AssociatedObject.LoadingRow -= this.SyncColumnWidths;
}
private void SyncColumnWidths(object sender, DataGridRowEventArgs e)
{
var dataGrid = this.AssociatedObject;
foreach (DataGridColumn c in dataGrid.Columns)
c.Width = 0;
e.Row.UpdateLayout();
foreach (DataGridColumn c in dataGrid.Columns)
c.Width = DataGridLength.Auto;
}
}
用法
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
...
<DataGrid
... >
<i:Interaction.Behaviors>
<ext:SyncedColumnWidthsBehavior />
</i:Interaction.Behaviors>
</DataGrid>
行为提供了一种干净的方式来释放事件处理程序。虽然,在这种情况下,即使我们不取消订阅附加的 属性,我们也不会造成内存泄漏(参考 Is it bad to not unregister event handlers?)。
抱歉延迟回答您的问题。
我这里的方法是捕获屏幕中的可见行并获取平均宽度并分配给列宽。
首先订阅了 ScrollViewer 的 ScrollChanged 事件。
<DataGrid
ScrollViewer.ScrollChanged="DG_ScrollChanged"
x:Name="DG"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserResizeRows="False"
CanUserSortColumns="False" />
</Grid>
通过使用 ScrollChanged
和 FindVisualChildren
我可以获得垂直偏移。
我们可以从垂直偏移中获取行索引,最后一行的索引是使用 (int)scroll.VerticalOffset + (int)scroll.ViewportHeight - 1
计算的
ScrollViewer scroll = null;
private void DG_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
// get the control once and then use its offset to get the row index
if (scroll == null)
scroll = MethodRepo.FindVisualChildren<ScrollViewer>((DependencyObject)sender).First();
int firstRow = (int)scroll.VerticalOffset;
int lastRow = (int)scroll.VerticalOffset + (int)scroll.ViewportHeight + 1;
List<int> FirstColumnLength = new List<int>();
List<int> SecondColumnLength = new List<int>();
for (int i = firstRow; i < lastRow-1; i++)
{
FirstColumnLength.Add(Convert.ToString(persons[i].Name).Length);
SecondColumnLength.Add(Convert.ToString(persons[i].Surname).Length);
}
DG.Columns[0].Width = FirstColumnLength.Max()*10;
DG.Columns[1].Width = SecondColumnLength.Max() * 10;
}
我还创建了一个静态 class 来获取视觉子项。
public static class MethodRepo
{
public static IEnumerable<T> FindVisualChildren<T>([NotNull] this DependencyObject parent) where T : DependencyObject
{
if (parent == null)
throw new ArgumentNullException(nameof(parent));
var queue = new Queue<DependencyObject>(new[] { parent });
while (queue.Any())
{
var reference = queue.Dequeue();
var count = VisualTreeHelper.GetChildrenCount(reference);
for (var i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(reference, i);
if (child is T children)
yield return children;
queue.Enqueue(child);
}
}
}
}
当我滚动垂直滚动条时,DataGrid
如果新可见行中的内容更大并且超过了先前的列宽,则自动扩展列宽。没关系。
但是如果所有较大的行都被滚动并且新的可见行的内容宽度较小,DataGrid
不会减小列宽。有办法实现吗?
附加行为的实现会很棒。
代码:
public partial class MainWindow
{
public MainWindow()
{
InitializeComponent();
var persons = new List<Person>();
for (var i = 0; i < 20; i++)
persons.Add(new Person() {Name = "Coooooooooooooool", Surname = "Super"});
for (var i = 0; i < 20; i++)
persons.Add(new Person() {Name = "Cool", Surname = "Suuuuuuuuuuuuuuper"});
for (var i = 0; i < 20; i++)
persons.Add(new Person() {Name = "Coooooooooooooool", Surname = "Super"});
DG.ItemsSource = persons;
}
public class Person
{
public string Name { get; set; }
public string Surname { get; set; }
}
}
XAML:
<Window
x:Class="WpfApp4.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
Title="MainWindow"
Width="400"
Height="200"
mc:Ignorable="d">
<Grid>
<DataGrid
x:Name="DG"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserResizeRows="False"
CanUserSortColumns="False" />
</Grid>
</Window>
将 LoadingRow 属性 添加到您的数据网格:
<DataGrid x:Name="DG"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserResizeRows="False"
CanUserSortColumns="False" LoadingRow="DG_LoadingRow">
</DataGrid>
然后添加这段代码 在后面的代码中:
private void DG_LoadingRow(object sender, DataGridRowEventArgs e)
{
foreach (DataGridColumn c in DG.Columns)
c.Width = 0;
DG.UpdateLayout();
foreach (DataGridColumn c in DG.Columns)
c.Width = DataGridLength.Auto;
}
这绝对不是最干净的解决方案,但它会在滚动时调整可见列的大小。
希望这对您有所帮助。
Can you please wrap this into attached behaviour?
1) 第一个选项是使用 附加 属性.
public class DataGridHelper : DependencyObject
{
public static readonly DependencyProperty SyncedColumnWidthsProperty =
DependencyProperty.RegisterAttached(
"SyncedColumnWidths",
typeof(Boolean),
typeof(DataGridHelper),
new FrameworkPropertyMetadata(false,
FrameworkPropertyMetadataOptions.AffectsRender,
new PropertyChangedCallback(OnSyncColumnsChanged)
));
private static void OnSyncColumnsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is DataGrid dataGrid)
{
dataGrid.LoadingRow += SyncColumnWidths;
}
}
private static void SyncColumnWidths(object sender, DataGridRowEventArgs e)
{
var dataGrid = (DataGrid)sender;
foreach (DataGridColumn c in dataGrid.Columns)
c.Width = 0;
e.Row.UpdateLayout();
foreach (DataGridColumn c in dataGrid.Columns)
c.Width = DataGridLength.Auto;
}
public static void SetSyncedColumnWidths(UIElement element, Boolean value)
{
element.SetValue(SyncedColumnWidthsProperty, value);
}
}
用法
<DataGrid
ext:DataGridHelper.SyncedColumnWidths="True"
... />
2) 或者,Behaviors 提供了一种更加封装的方式来扩展功能(需要 System.Windows.Interactivity)。
using System.Windows.Interactivity;
...
public class SyncedColumnWidthsBehavior : Behavior<DataGrid>
{
protected override void OnAttached()
{
this.AssociatedObject.LoadingRow += this.SyncColumnWidths;
}
protected override void OnDetaching()
{
this.AssociatedObject.LoadingRow -= this.SyncColumnWidths;
}
private void SyncColumnWidths(object sender, DataGridRowEventArgs e)
{
var dataGrid = this.AssociatedObject;
foreach (DataGridColumn c in dataGrid.Columns)
c.Width = 0;
e.Row.UpdateLayout();
foreach (DataGridColumn c in dataGrid.Columns)
c.Width = DataGridLength.Auto;
}
}
用法
xmlns:i="http://schemas.microsoft.com/expression/2010/interactivity"
...
<DataGrid
... >
<i:Interaction.Behaviors>
<ext:SyncedColumnWidthsBehavior />
</i:Interaction.Behaviors>
</DataGrid>
行为提供了一种干净的方式来释放事件处理程序。虽然,在这种情况下,即使我们不取消订阅附加的 属性,我们也不会造成内存泄漏(参考 Is it bad to not unregister event handlers?)。
抱歉延迟回答您的问题。
我这里的方法是捕获屏幕中的可见行并获取平均宽度并分配给列宽。
首先订阅了 ScrollViewer 的 ScrollChanged 事件。
<DataGrid
ScrollViewer.ScrollChanged="DG_ScrollChanged"
x:Name="DG"
CanUserAddRows="False"
CanUserDeleteRows="False"
CanUserReorderColumns="False"
CanUserResizeColumns="False"
CanUserResizeRows="False"
CanUserSortColumns="False" />
</Grid>
通过使用 ScrollChanged
和 FindVisualChildren
我可以获得垂直偏移。
我们可以从垂直偏移中获取行索引,最后一行的索引是使用 (int)scroll.VerticalOffset + (int)scroll.ViewportHeight - 1
计算的 ScrollViewer scroll = null;
private void DG_ScrollChanged(object sender, ScrollChangedEventArgs e)
{
// get the control once and then use its offset to get the row index
if (scroll == null)
scroll = MethodRepo.FindVisualChildren<ScrollViewer>((DependencyObject)sender).First();
int firstRow = (int)scroll.VerticalOffset;
int lastRow = (int)scroll.VerticalOffset + (int)scroll.ViewportHeight + 1;
List<int> FirstColumnLength = new List<int>();
List<int> SecondColumnLength = new List<int>();
for (int i = firstRow; i < lastRow-1; i++)
{
FirstColumnLength.Add(Convert.ToString(persons[i].Name).Length);
SecondColumnLength.Add(Convert.ToString(persons[i].Surname).Length);
}
DG.Columns[0].Width = FirstColumnLength.Max()*10;
DG.Columns[1].Width = SecondColumnLength.Max() * 10;
}
我还创建了一个静态 class 来获取视觉子项。
public static class MethodRepo
{
public static IEnumerable<T> FindVisualChildren<T>([NotNull] this DependencyObject parent) where T : DependencyObject
{
if (parent == null)
throw new ArgumentNullException(nameof(parent));
var queue = new Queue<DependencyObject>(new[] { parent });
while (queue.Any())
{
var reference = queue.Dequeue();
var count = VisualTreeHelper.GetChildrenCount(reference);
for (var i = 0; i < count; i++)
{
var child = VisualTreeHelper.GetChild(reference, i);
if (child is T children)
yield return children;
queue.Enqueue(child);
}
}
}
}