WPF DataGrid:滚动时减小列宽以适合其内容

Posted

技术标签:

【中文标题】WPF DataGrid:滚动时减小列宽以适合其内容【英文标题】:WPF DataGrid: reduce column width to fit its content while scrolling 【发布时间】:2019-05-10 14:03:55 【问题描述】:

当我滚动垂直滚动条时,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>

【问题讨论】:

能否请您也发布您的 xaml。 -) @SatishPai,请看一下。 【参考方案1】:

将LoadingRow 属性添加到您的Datagrid:

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

这绝对不是最干净的解决方案,但它会在滚动时调整视图中的列的大小。

希望这会有所帮助。


你能把它包装成附加的行为吗?

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) 或者,行为提供了一种更加封装的方式来扩展功能(需要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?)。

【讨论】:

提示,还有一个e.Row.UpdateLayout() @TimvZon,你能把它包装成附加的行为吗? @Funk,一半的赏金是你的 =) 谢谢。 感谢@Funk 的帮助【参考方案2】:

对于延迟回答您的问题,我们深表歉意。

我这里的方法是捕获屏幕中的可见行并获取平均宽度并分配给列宽。

首先订阅了 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;
    

我还创建了一个静态类来获取 Visual 子级。

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

【讨论】:

@Sathish,您的解决方案效果很好,感谢您的关注。但我更喜欢 TimvZon 提供的简短的。 我同意你的看法 :-) @AsValeO

以上是关于WPF DataGrid:滚动时减小列宽以适合其内容的主要内容,如果未能解决你的问题,请参考以下文章

增加 Silverlight DataGrid 中的列宽以填充整个 DG 宽度

XAML WPF DataGrid:在滚动时减小列宽度以适应其内容,除了一个

谷歌图表 API:设置表格列宽以适合数据

在 FLEX 中动态改变 Datagrid 列的宽度

调整列宽以适应 QTableWidget pyqt

WPF DataGrid 同步列宽