【问题标题】:How can I get a ListView GridViewColumn to fill the remaining space in my grid?如何获得 ListView GridViewColumn 来填充网格中的剩余空间?
【发布时间】: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 指定列的宽度。

【问题讨论】:

    标签: .net wpf listview


    【解决方案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://stackoverflow.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 比宽度转换器更干净。总体结果也比已经发布的更灵活。

              【讨论】:

                猜你喜欢
                • 2020-05-14
                • 2013-11-04
                • 2017-10-18
                • 1970-01-01
                • 2020-08-25
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 2021-10-29
                相关资源
                最近更新 更多