如何获得 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
需要实现附加行为,并且每个列都实现附加属性。但是,如果您将附加属性保留在列之外,它将按照 DependencyProperty
s 默认元数据默认 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 来填充网格中的剩余空间?