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>

通过使用 ScrollChangedFindVisualChildren 我可以获得垂直偏移。

我们可以从垂直偏移中获取行索引,最后一行的索引是使用 (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);
            }
        }
    }
}