如何获得 ListView GridViewColumn 来填充网格中的剩余空间?

Posted

技术标签:

【中文标题】如何获得 ListView GridViewColumn 来填充网格中的剩余空间?【英文标题】:How can I get a ListView GridViewColumn to fill the remaining space in my grid? 【发布时间】:2012-01-10 01:01:42 【问题描述】:

我想创建一个 ListView,它有两列固定宽度和第三列来填充剩余空间。所以是这样的:

<ListView>
    <ListView.View>
        <GridView>
            <GridViewColumn Header="Name" Width="*" />
            <GridViewColumn Header="Age" Width="50" />
            <GridViewColumn Header="Gender" Width="50" />
        </GridView>
    </ListView.View>
</ListView>

问题是我找不到让Name 列填充剩余空间的方法,因为将宽度设置为* 不起作用。看起来有一种方法可以使用value converter 来做到这一点,但似乎应该有一种更简单的方法。与 DataGrid 控件一样,您可以使用 *s 指定列的宽度。

【问题讨论】:

【参考方案1】:

我试图实现相同的目标,但后来决定我希望我的 ListView 列消耗 ListView 的一部分,结果是所有列都消耗一部分空间,并且所有空间都在 ListView 中消耗。您可以将其设置为在最后一列具有任何您喜欢的百分比,以直接实现“填充最后一列的剩余空间”目标。

我发现这个方法相当健壮和可靠(即使在调整大小时!)所以想我可以分享一下。

对于这个示例,我的 ListView 中有四列。您只需使用以下事件处理程序在您的 ListView 中注册 SizeChanged 事件:

private void ProductsListView_SizeChanged(object sender, SizeChangedEventArgs e)

    ListView listView = sender as ListView;
    GridView gView = listView.View as GridView;

    var workingWidth = listView.ActualWidth - SystemParameters.VerticalScrollBarWidth; // take into account vertical scrollbar
    var col1 = 0.50;
    var col2 = 0.20;
    var col3 = 0.15;
    var col4 = 0.15;

    gView.Columns[0].Width = workingWidth*col1;
    gView.Columns[1].Width = workingWidth*col2;
    gView.Columns[2].Width = workingWidth*col3;
    gView.Columns[3].Width = workingWidth*col4;

【讨论】:

我建议使用SystemParameters.VerticalScrollBarWidth 而不是硬编码的35。一些用户(比如我)自定义他们的滚动条宽度。地雷可能是 70 左右:) +1 无论如何都是一个好的答案 如果您创建了某种子 GridView 和列,您将获得加分,因此您可以将其应用于所有 GridView,而不是为每个视图自定义实现。 谢谢。修改列的简单方法。 @KonradMorawski 不错的提示,但在我的环境中是 SystemParameters.VerticalScrollBarWidth = 17,我需要使用 25 左右的值。当我使用 17 时,VerticalScrollBar 可见。 @honzakuzel1989 是的,SystemParameters.VerticalScrollBarWidth 只是默认宽度,而不是当前宽度 - 我必须采用样式的自定义宽度。【参考方案2】:

在研究类似问题时遇到了这个问题,我的问题是我希望所有列都是“自动”,除了第一个,这只会填补额外的空间,所以我扩展了 GONeale 的解决方案。

private void ListView_SizeChanged(object sender, SizeChangedEventArgs e)

    ListView _ListView = sender as ListView;
    GridView _GridView = _ListView.View as GridView;
    var _ActualWidth = _ListView.ActualWidth - SystemParameters.VerticalScrollBarWidth;
    for (Int32 i = 1; i < _GridView.Columns.Count; i++)
    
        _ActualWidth = _ActualWidth - _GridView.Columns[i].ActualWidth;
    
    _GridView.Columns[0].Width = _ActualWidth;

那么 XAML 就是:

...
<ListView.View>
    <GridView>
        <GridViewColumn Header="Title" />
        <GridViewColumn Header="Artist" Width="Auto" />
        <GridViewColumn Header="Album" Width="Auto" />
        <GridViewColumn Header="Genre" Width="Auto" />
    </GridView>
</ListView.View>
...

此代码也可以更通用地使用,因为列数不是硬编码的,稍微调整一下,您就可以通过某种逻辑来定义“填充列”。

希望它可以帮助某人:)

【讨论】:

确实如此,就在我更改了 GridView 中的列数并破坏了硬编码的列数之后。我修改了这种方法来满足这一点(我固定了第一列的宽度)。谢谢。【参考方案3】:

问题是 GridViewColumn 的列宽是双倍的,而不是 GridLength 对象,并且没有适当的转换来处理 *.不确定这是否是 WPF 团队的疏忽。你会认为它应该被支持。

除了转换器,我见过的唯一其他方法是:http://www.ontheblog.net/CMS/Default.aspx?tabid=36&EntryID=37。

两者都是不需要的额外工作。我在 ListView 和 GridView 组合中发现了其他“奇怪”的东西,所以我停止使用它们。如果我需要一个数据网格,我使用我们许可的第 3 方,如果我需要一个复杂的 ListBox 样式菜单,我只使用一个模板化的 ListBox。

【讨论】:

啊,宽度是 double 而不是 GridLength 对象。知道这是否真的只是一个疏忽或者这背后是否有一些理由会很有趣。哦,好吧,对于我的场景,看起来最简单的解决方案就是使用 DataGrid。【参考方案4】:

在第一个答案之一中提到的 David Hanson-Greville 的 OnTheBlog 的解决方案不再可用,即使该博客仍然存在。我可以在 Wayback Machine 上找到它,并且经过一些审核,它是:

诀窍是您在 ListView 上设置 Stretch=true,它会平均拉伸宽度不同的列。

using System;
using System.Collections.Generic;
using System.Windows;
using System.Windows.Controls;

namespace Demo.Extension.Properties

    ///
    /// ListViewColumnStretch
    ///
    public class ListViewColumns : DependencyObject
    
        ///
        /// IsStretched Dependancy property which can be attached to gridview columns.
        ///
        public static readonly DependencyProperty StretchProperty =
            DependencyProperty.RegisterAttached("Stretch",
            typeof(bool),
            typeof(ListViewColumns),
            new UIPropertyMetadata(true, null, OnCoerceStretch));

        ///
        /// Gets the stretch.
        ///
        /// The obj.
        ///
        public static bool GetStretch(DependencyObject obj)
        
            return (bool)obj.GetValue(StretchProperty);
        

        ///
        /// Sets the stretch.
        ///
        /// The obj.
        /// if set to true [value].
        public static void SetStretch(DependencyObject obj, bool value)
        
            obj.SetValue(StretchProperty, value);
        

        ///
        /// Called when [coerce stretch].
        ///
        ///If this callback seems unfamilar then please read
        /// the great blog post by Paul Jackson found here.
        /// http://compilewith.net/2007/08/wpf-dependency-properties.html
        /// The source.
        /// The value.
        ///
        public static object OnCoerceStretch(DependencyObject source, object value)
        
            ListView lv = (source as ListView);

            //Ensure we dont have an invalid dependancy object of type ListView.
            if (lv == null)
            
                throw new ArgumentException("This property may only be used on ListViews");
            

            //Setup our event handlers for this list view.
            lv.Loaded += new RoutedEventHandler(lv_Loaded);
            lv.SizeChanged += new SizeChangedEventHandler(lv_SizeChanged);
            return value;
        

        ///
        /// Handles the SizeChanged event of the lv control.
        ///
        /// The source of the event.
        /// The instance containing the event data.
        private static void lv_SizeChanged(object sender, SizeChangedEventArgs e)
        
            ListView lv = (sender as ListView);
            if (lv.IsLoaded)
            
                //Set our initial widths.
                SetColumnWidths(lv);
            
        

        ///
        /// Handles the Loaded event of the lv control.
        ///
        /// The source of the event.
        /// The instance containing the event data.
        private static void lv_Loaded(object sender, RoutedEventArgs e)
        
            ListView lv = (sender as ListView);
            //Set our initial widths.
            SetColumnWidths(lv);
        

        ///
        /// Sets the column widths.
        ///
        private static void SetColumnWidths(ListView listView)
        
            //Pull the stretch columns fromt the tag property.
            List<GridViewColumn> columns = (listView.Tag as List<GridViewColumn>);
            double specifiedWidth = 0;
            GridView gridView = listView.View as GridView;
            if (gridView != null)
            
                if (columns == null)
                
                    //Instance if its our first run.
                    columns = new List<GridViewColumn>();
                    // Get all columns with no width having been set.
                    foreach (GridViewColumn column in gridView.Columns)
                    
                        if (!(column.Width >= 0))
                        
                            columns.Add(column);
                        
                        else
                        
                            specifiedWidth += column.ActualWidth;
                        
                    
                
                else
                
                    // Get all columns with no width having been set.
                    foreach (GridViewColumn column in gridView.Columns)
                    
                        if (!columns.Contains(column))
                        
                            specifiedWidth += column.ActualWidth;
                        
                    
                

                // Allocate remaining space equally.
                foreach (GridViewColumn column in columns)
                
                    double newWidth = (listView.ActualWidth - specifiedWidth) / columns.Count;
                    if (newWidth >= 10)
                    
                        column.Width = newWidth - 10;
                    
                

                //Store the columns in the TAG property for later use.
                listView.Tag = columns;
            
        
    

然后您只需将命名空间添加到 XAML 文件:

xmlns:Extensions="clr-namespace:Demo.Extension.Properties"

并在您的列表视图中使用它:

<ListView ItemsSource="Binding Path=Items" DisplayMemberPath="Name"
                          ScrollViewer.VerticalScrollBarVisibility="Auto"
                          Grid.Column="0" Margin="8" Extensions:ListViewColumns.Stretch="true">

【讨论】:

【参考方案5】:

我的需要是让所有列的宽度都相同。上述解决方案很好,但我更喜欢将这样的东西包装在附加属性(MVVM、可重用性等)中。这是我的代码,如果有帮助的话。

    public class StarSizeHelper 

    private static readonly List<FrameworkElement> s_knownElements = new List<FrameworkElement>();

    public static bool GetIsEnabled(DependencyObject d) 
        return (bool) d.GetValue(IsEnabledProperty);
    

    public static void SetIsEnabled(ListView d, bool value) 
        d.SetValue(IsEnabledProperty, value);
    

    public static readonly DependencyProperty IsEnabledProperty =
        DependencyProperty.RegisterAttached("IsEnabled", 
                                            typeof(bool), 
                                            typeof(StarSizeHelper),
                                            new FrameworkPropertyMetadata(IsEnabledChanged));

    public static void IsEnabledChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) 

        var ctl = d as ListView;
        if (ctl == null) 
            throw new Exception("IsEnabled attached property only works on a ListView type");
        

        RememberElement(ctl);
    

    private static void RememberElement(ListView ctl) 

        if (! s_knownElements.Contains(ctl)) 
            s_knownElements.Add(ctl);

            RegisterEvents(ctl);
         
        // nothing to do if elt is known
    

    private static void OnUnloaded(object sender, RoutedEventArgs e) 

        FrameworkElement ctl = (FrameworkElement) sender;
        ForgetControl(ctl);
    

    private static void ForgetControl(FrameworkElement fe) 

        s_knownElements.Remove(fe);
        UnregisterEvents(fe);
    

    private static void RegisterEvents(FrameworkElement fe) 
        fe.Unloaded += OnUnloaded;
        fe.SizeChanged += OnSizeChanged;
    

    private static void UnregisterEvents(FrameworkElement fe) 
        fe.Unloaded -= OnUnloaded;
        fe.SizeChanged -= OnSizeChanged;
    

    private static void OnSizeChanged(object sender, SizeChangedEventArgs e) 

        ListView listView = sender as ListView;
        if (listView == null) 
            return; // should not happen
        
        GridView gView = listView.View as GridView;
        if (gView == null) 
            return; // should not happen
        

        var workingWidth = listView.ActualWidth - SystemParameters.VerticalScrollBarWidth -10; // take into account vertical scrollbar
        var colWidth = workingWidth / gView.Columns.Count;
        foreach (GridViewColumn column in gView.Columns) 
            column.Width = colWidth;
        
    

为了使用它:

<ListView ... StarSizeHelper.IsEnabled="true" ... />

(当然,你还是要修复 XAML 中的命名空间声明)

您可以在 OnSizeChanged 方法中调整您的大小需求。

【讨论】:

【参考方案6】:

我的问题类似,但我想修复第一列的宽度,我也不希望它在我添加或删除列时中断,即使在运行时也是如此。感谢@Gary 在https://***.com/a/14674830/492 的提示

private void ResultsListView_SizeChanged(object sender, SizeChangedEventArgs e)
    
      double newWidthForColumnsExceptFirstColumn = ResultsListView.ActualWidth - SystemParameters.VerticalScrollBarWidth - ResultsGridView.Columns[0].Width;
      int columnsCount = ResultsGridView.Columns.Count;
      Double newColumnWidth = newWidthForColumnsExceptFirstColumn / (columnsCount -1);

      for ( int col = 1; col < columnsCount; col++ ) // skip column [0]
      
        ResultsGridView.Columns[col].Width = newColumnWidth;
      
    

【讨论】:

【参考方案7】:

这是一种解决方案,它允许多个 ListView 使用通用的“调整大小”事件处理程序。

    //Using dictionarys as trackers allows us to have multiple ListViews use the same code
    private Dictionary<string, double> _fixedWidthTracker = new Dictionary<string, double>();
    private Dictionary<string, List<GridViewColumn>> _varWidthColTracker = new Dictionary<string, List<GridViewColumn>>();
    private void ListView_SizeChanged(object sender, SizeChangedEventArgs e)
    
        ListView lv = sender as ListView;
        if (lv != null)
        
            //For validation during Debug
            VerifyName(lv);

            GridView gv = lv.View as GridView;
            if (gv != null)
            
                if (!_varWidthColTracker.ContainsKey(lv.Name))
                
                    _varWidthColTracker[lv.Name] = new List<GridViewColumn>();
                    _fixedWidthTracker[lv.Name] = 0;
                    foreach (GridViewColumn gvc in gv.Columns)
                    
                        if (!double.IsNaN(gvc.Width)) _fixedWidthTracker[lv.Name] += gvc.Width; else _varWidthColTracker[lv.Name].Add(gvc);
                    
                
                double newWidthForColumns = e.NewSize.Width - SystemParameters.VerticalScrollBarWidth - _fixedWidthTracker[lv.Name];
                int columnsCount = gv.Columns.Count;
                int numberOfFixedWithColumns = columnsCount - _varWidthColTracker[lv.Name].Count;
                Double newColumnWidth = newWidthForColumns / (columnsCount - numberOfFixedWithColumns);

                foreach (GridViewColumn gvc in _varWidthColTracker[lv.Name])
                
                    gvc.Width = newColumnWidth;
                
            
        
    

    /// <summary>
    /// Warns the developer if this object does not have
    /// a public property with the specified name. This 
    /// method does not exist in a Release build.
    /// </summary>
    [Conditional("DEBUG")]
    [DebuggerStepThrough]
    public void VerifyName(ListView listView)
    
        if (String.IsNullOrEmpty(listView.Name))
        
            string msg = "The Name attribute is required to be set on the ListView in order to Bind to this method";
            Debug.Fail(msg);
        
    

【讨论】:

【参考方案8】:

我拿了上面的例子(这很好)并稍微改进了它以防止调整大小时出现运行时异常:

private void tpList_SizeChanged(object sender, SizeChangedEventArgs e)
        
            ListView listView = sender as ListView;
            GridView gView = listView.View as GridView;

            var workingWidth = listView.ActualWidth - (SystemParameters.VerticalScrollBarWidth + 20); // take into account vertical scrollbar
            var col1 = 0.50;
            var col2 = 0.50;

            var t1 = workingWidth * col1;
            var t2 = workingWidth * col2;
            gView.Columns[0].Width = t1 > 0 ? t1 : 1;
            gView.Columns[1].Width = t2 > 0 ? t2 : 1;

        
    

【讨论】:

【参考方案9】:

我意识到这是一个老问题,但我在尝试为 ListView/GridView 中的多个列获取基于 star 的解决方案时发现了这篇文章。所以我想我可以帮助一些未来的人,因为它也会回答这个问题。

我最初实现了WidthConverter,但这有一个明显的限制,即最后一列不会“填充”,并且从未保证该行会适合它的空间,但这里是为那些好奇的人准备的:

public class WidthConverter : IValueConverter
    
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        
            var actualWidth = (double)value;
            var desiredPercentage = SafeConvert(parameter);

            if (actualWidth == 0.0 || desiredPercentage == 0.0) return double.NaN;

            var result = actualWidth * (desiredPercentage / 100);

            return result;
        

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        
            throw new NotImplementedException();
        

        private double SafeConvert(object pInput)
        
            if (pInput == null) return default;

            if (double.TryParse(pInput.ToString(), out var result))
            
                return result;
            

            return default;
        
    
<GridViewColumn Header="My Header"
                DisplayMemberBinding="Binding MyColumnData"
                Width="Binding Path=ActualWidth, ElementName=MyListView, Converter=StaticResource WidthConverter, ConverterParameter='10.5'" />

这是可行的,但它非常依赖程序员对参数值进行硬编码,并且在某种意义上说绑定需要来自ListView 元素的ActualWidth 属性。最终,它的实际功能相当有限,尤其是考虑到大多数 WPF 人员习惯于使用 GridLength 进行星级调整。

我从这里和其他地方发布的解决方案中获取了各种零碎的信息,并开发了一个基于附加属性和行为的 MVVM 友好解决方案。

我使用GridLength 创建了一个附加属性,以利用现有的绝对/自动/星形逻辑匹配我们都习惯的 XAML 宽度模式。

public class ColumnAttachedProperties : DependencyObject

    public static readonly DependencyProperty GridLength_WidthProperty = DependencyProperty.RegisterAttached(
        name: "GridLength_Width",
        propertyType: typeof(GridLength),
        ownerType: typeof(ColumnAttachedProperties),
        defaultMetadata: new FrameworkPropertyMetadata(GridLength.Auto));

    public static GridLength GetGridLength_Width(DependencyObject dependencyObject) 
        => (GridLength)dependencyObject.GetValue(GridLength_WidthProperty);

    public static void SetGridLength_Width(DependencyObject dependencyObject, string value) 
        => dependencyObject.SetValue(GridLength_WidthProperty, value);

附加行为挂钩到 Loaded 和 SizeChanged 事件,并执行一些共享逻辑来调整列的大小。

基本上,在第一次遍历列时,它会计算星值(但尚未为星列设置宽度),然后在第二次遍历时以星列为目标,将宽度设置为百分比可用的剩余宽度。我相信这可以通过某种方式进行优化。

public static class ListViewStarSizingAttachedBehavior

    public static readonly DependencyProperty UseGridLength_WidthProperty = DependencyProperty.RegisterAttached(
        name: "UseGridLength_Width",
        propertyType: typeof(bool),
        ownerType: typeof(ListViewStarSizingAttachedBehavior),
        new UIPropertyMetadata(false, RegisterEventHandlers));

    public static bool GetUseGridLength_Width(DependencyObject dependencyObject) 
        => (bool)dependencyObject.GetValue(UseGridLength_WidthProperty);
    public static void SetUseGridLength_Width(DependencyObject dependencyObject, bool value) 
        => dependencyObject.SetValue(UseGridLength_WidthProperty, value);

    private static void RegisterEventHandlers(DependencyObject d, DependencyPropertyChangedEventArgs e)
    
        if (d is ListView listView)
        
            if (e.NewValue is bool booleanValue && booleanValue == true)
            
                listView.SizeChanged += ListView_SizeChanged;
                listView.Loaded += ListView_Loaded;
            
            else
            
                listView.SizeChanged -= ListView_SizeChanged;
                listView.Loaded -= ListView_Loaded;
            
        
    

    private static void ListView_Loaded(object sender, RoutedEventArgs e)
    
        CalculateGridColumnWidths(sender);
    

    private static void ListView_SizeChanged(object sender, SizeChangedEventArgs e)
    
        if (sender is ListView listView && !listView.IsLoaded) return;

        CalculateGridColumnWidths(sender);
    

    private static void CalculateGridColumnWidths(object sender)
    
        if (sender is ListView listView && listView.View is GridView gridView)
        
            // the extra offset may need to be altered per your application.
            var scrollOffset = SystemParameters.VerticalScrollBarWidth - 7;

            var remainingWidth = listView.ActualWidth - scrollOffset;
            var starTotal = 0.0;
                
            foreach (var column in gridView.Columns)
            
                var gridLength = ColumnAttachedProperties.GetGridLength_Width(column);

                if (gridLength.IsStar)
                
                    // Get the cumlative star value while passing over the columns
                    // but don't set their width until absolute and auto have been set.
                    starTotal += gridLength.Value;
                    continue;
                

                if (gridLength.IsAbsolute)
                
                    column.Width = gridLength.Value;
                
                else
                
                    column.Width = double.NaN;
                

                remainingWidth -= column.ActualWidth;
            

            if (starTotal == 0.0) return;

            // now eval each star column
            foreach (var column in gridView.Columns)
            
                var gridLength = ColumnAttachedProperties.GetGridLength_Width(column);

                if (!gridLength.IsStar) continue;

                var starPercent = (gridLength.Value / starTotal);
                column.Width = remainingWidth * starPercent;
            
        
    

最后要在 XAML 中使用它,ListView 需要实现附加行为,并且每个列都实现附加属性。但是,如果您将附加属性保留在列之外,它将按照 DependencyPropertys 默认元数据默认 Auto

<ListView local:ListViewStarSizingAttachedBehavior.UseGridLength_Width="True"
          ItemsSource="Binding MyItems" >
    <ListView.View>
        <GridView>
            <GridView.Columns>
                <!-- Absolute -->
                <GridViewColumn local:ColumnAttachedProperties.GridLength_Width="250"
                                Header="Column One"
                                DisplayMemberBinding="Binding Item1" />
                
                <!-- Star -->               
                <GridViewColumn local:ColumnAttachedProperties.GridLength_Width="2*"
                                Header="Column Two"
                                DisplayMemberBinding="Binding Item2" />
                
                <!-- Auto -->               
                <GridViewColumn local:ColumnAttachedProperties.GridLength_Width="Auto"
                                Header="Column Three"
                                DisplayMemberBinding="Binding Item3" />

                <!-- Star -->
                <GridViewColumn local:ColumnAttachedProperties.GridLength_Width="*"
                                Header="Column Four"
                                DisplayMemberBinding="Binding Item4" />
            </GridView.Columns>
        </GridView>
    </ListView.View>
</ListView>

目前看来运行良好,但我确信有一些边缘情况需要考虑。 XAML 比宽度转换器更干净。总体结果也比已经发布的更灵活。

【讨论】:

以上是关于如何获得 ListView GridViewColumn 来填充网格中的剩余空间?的主要内容,如果未能解决你的问题,请参考以下文章

如何使用 Flutter 从 Firestore 中的 ListView 获得无限滚动

如何获得 ListView GridViewColumn 来填充网格中的剩余空间?

我如何获得要提升的ListView项?

如何获得telerik ListView特定列的总和?

android中如何获得listview中的checkbox的值?

WPF如何获得ListView内各单元格控件