我知道这是一篇旧帖子,但对于任何遇到它的人来说,如果他们从他们的 ViewModel 绑定到 ListView ItemsSource 属性,也许 MultiValue 转换器可以提供帮助。我知道还有其他解决问题的方法,但我喜欢使用这种方法可以保持的控制水平,它同样适用于ListView 和GridViewColumn。
我还喜欢这样一个事实,即使用转换器,我的 ICollection 对象可以包含字符串/原始类型或可以通过反射访问其属性的类对象。
简而言之:传入转换器的值数组是创建字体和实例化FormattedText 对象、UIElement 的最小和最大宽度以及绑定的ICollection 对象本身所需的值。参数参数实际上是一个数组,其中包含一个类对象的属性名称(如果不需要,则为 null)和一个 Thickness 对象,用于为计算的宽度添加填充。 Thickness 对象允许我满足构成我的ListView 一部分的任何填充/边距设计。
注意:ListView ItemsPane 上的默认填充是 {12,0,12,0} 和 GridViewColumn {6,0,6,0}。这可能是 BK 提到的 12-20 像素错误的原因。
转换器本身如下所示:
/// <summary>
/// Iterates a collection of items to calculate the maximum text width of those items.
/// Items can either be primitive types and strings or objects with a property that is
/// a primitive type or string.
/// </summary>
public sealed class ItemsToWidthConverter : IMultiValueConverter
{
//Constants for array indexes.
private const int FONTFAMILY_ID = 0;
private const int FONTSTYLE_ID = 1;
private const int FONTWEIGHT_ID = 2;
private const int FONTSTRETCH_ID = 3;
private const int FONTSIZE_ID = 4;
private const int FOREGROUND_ID = 5;
private const int MINWIDTH_ID = 6;
private const int MAXWIDTH_ID = 7;
private const int ICOLLECTION_ID = 8;
private const int PARAMETERPROPERTY_ID = 0;
private const int PARAMETERGAP_ID = 1;
/// <summary>
/// Converts collection items to a width.
/// Parameter[0] is the property name of an object. If no property name is needed, pass in null.
/// Parameter[1] is the padding to be added to the calculated width. If no padding is needed, pass in a Thickness of 0.
/// Note: a ListViewItem has default padding of {12,0,12,0}. A GridViewColumn has default padding of {6,0,6,0}.
/// </summary>
/// <param name="values">Array of 9 objects {FontFamily, FontStyle, FontWeight, FontStretch, double [FontSize], Brush, double [MinWidth], double [MaxWidth], ICollection}</param>
/// <param name="targetType">Double</param>
/// <param name="parameter">Array of 2 objects {string [Property Name], Thickness}</param>
/// <param name="culture">Desired CultureInfo</param>
/// <returns>Width of widest item including padding or Nan if none is calculated.</returns>
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
// Throw error if passed parameters are incorrect.
if (values.Length != 9) throw new Exception("Incorrect number of items passed in 'values'.");
if (!(parameter.GetType().IsArray)) throw new Exception("'Parameter' must be an array.");
var prm = (object[])parameter;
if (prm.Length !=2) throw new Exception("Incorrect number of items passed in 'parameter'.");
if (prm[PARAMETERPROPERTY_ID] != null && !(prm[PARAMETERPROPERTY_ID] is string property)) throw new Exception("'Parameter['" + PARAMETERPROPERTY_ID + "]' is neither null nor of type 'string'.");
if (!(prm[PARAMETERGAP_ID] is Thickness margin)) throw new Exception("'Parameter['" + PARAMETERGAP_ID + "]' is not of type 'Thickness'.");
if (values[ICOLLECTION_ID] == null) return double.NaN;
if (!(values[FONTFAMILY_ID] is FontFamily family)) throw new Exception("'Value['" + FONTFAMILY_ID + "]' is not of type 'FontFamily'.");
if (!(values[FONTSTYLE_ID] is FontStyle style)) throw new Exception("'Value['" + FONTSTYLE_ID + "]' is not of type 'FontStyle'.");
if (!(values[FONTWEIGHT_ID] is FontWeight weight)) throw new Exception("'Value['" + FONTWEIGHT_ID + "]' is not of type 'FontWeight'.");
if (!(values[FONTSTRETCH_ID] is FontStretch stretch)) throw new Exception("'Value['" + FONTSTRETCH_ID + "]' is not of type 'FontStretch'.");
if (!(values[FONTSIZE_ID] is double size)) throw new Exception("'Value['" + FONTSIZE_ID + "]' is not of type 'double'.");
if (!(values[FOREGROUND_ID] is Brush foreground)) throw new Exception("'Value['" + FOREGROUND_ID + "]' is not of type 'Brush'.");
if (!(values[MINWIDTH_ID] is double minWidth)) throw new Exception("'Value['" + MINWIDTH_ID + "]' is not of type 'double'.");
if (!(values[MAXWIDTH_ID] is double maxWidth)) throw new Exception("'Value['" + MAXWIDTH_ID + "]' is not of type 'double'.");
if (!(values[ICOLLECTION_ID] is ICollection col)) throw new Exception("'Value['" + ICOLLECTION_ID + "]' is not of type 'ICollection'.");
// Conver font properties to a typeface.
var typeFace = new Typeface(family, style, weight, stretch);
// Initialise the max_width variable at 0.
var widest = 0.0;
foreach (var item in col)
{
// If property parameter is null, assume the ICollection contains primitives or strings.
if (prm[PARAMETERPROPERTY_ID] == null)
{
if (item.GetType().IsPrimitive || item is string)
{
var text = new FormattedText(item.ToString(),
culture,
FlowDirection.LeftToRight,
typeFace,
size,
foreground,
null,
TextFormattingMode.Ideal);
if (text.WidthIncludingTrailingWhitespace > widest)
widest = text.WidthIncludingTrailingWhitespace;
}
}
else
// Property parameter contains a string, so assume ICollection is an object
// and use reflection to get property value.
{
if (item.GetType().GetProperty(prm[PARAMETERPROPERTY_ID].ToString()) != null)
{
var propertyValue = item.GetType().GetProperty(prm[PARAMETERPROPERTY_ID].ToString()).GetValue(item);
if (propertyValue.GetType().IsPrimitive || propertyValue is string)
{
var text = new FormattedText(propertyValue.ToString(),
culture,
FlowDirection.LeftToRight,
typeFace,
size,
foreground,
null,
TextFormattingMode.Display);
if (text.WidthIncludingTrailingWhitespace > widest)
widest = text.WidthIncludingTrailingWhitespace;
}
}
}
}
// If no width could be calculated, return Nan which sets the width to 'Automatic'
if (widest == 0) return double.NaN;
// Add the left and right thickness values to the calculated width and
// check result is within min and max values.
{
widest += ((Thickness)prm[PARAMETERGAP_ID]).Left + ((Thickness)prm[PARAMETERGAP_ID]).Right;
if (widest < minWidth || widest > maxWidth) return double.NaN;
return widest;
}
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
{
throw new NotImplementedException();
}
}
它可以在 XAML 中实现,如下所示。
例如 1 - 模板中的简单 ListView:
<ListView ItemsSource="{Binding MyStrings}">
<ListView.Width>
<MultiBinding Converter="{StaticResource ItemsToWidthConverter}">
<MultiBinding.ConverterParameter>
<x:Array Type="sys:Object">
<x:Null />
<Thickness>12,0,12,0</Thickness>
</x:Array>
</MultiBinding.ConverterParameter>
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="FontFamily" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="FontStyle" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="FontWeight" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="FontStretch" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="FontSize" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="Foreground" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="MinWidth" />
<Binding RelativeSource="{RelativeSource TemplatedParent}" Path="MaxWidth" />
<Binding Path="MyStrings" />
</MultiBinding>
</ListView.Width>
</ListView>
例如 2 - ListView 和 GridView
<ListView ItemsSource="{Binding Employees}">
<ListView.View>
<GridView>
<GridViewColumn DisplayMemberBinding="{Binding EmployeeName}">
<GridViewColumn.Width>
<MultiBinding Converter="{StaticResource ItemsToWidthConverter}">
<MultiBinding.ConverterParameter>
<x:Array Type="sys:Object">
<sys:String>EmployeeName</sys:String>
<Thickness>6,0,6,0</Thickness>
</x:Array>
</MultiBinding.ConverterParameter>
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListView}" Path="FontFamily" />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListView}" Path="FontStyle" />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListView}" Path="FontWeight" />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListView}" Path="FontStretch" />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListView}" Path="FontSize" />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListView}" Path="Foreground" />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListView}" Path="MinWidth" />
<Binding RelativeSource="{RelativeSource Mode=FindAncestor, AncestorType=ListView}" Path="MaxWidth" />
<Binding Path="Employees" />
</MultiBinding>
</GridViewColumn.Width>
</GridViewColumn>
</GridView>
</ListView.View>
</ListView>
当然,还有很多其他的绑定变体/组合。
这种方法的唯一问题是,如果 ObservableCollection 发生更改(例如通过添加项目),则不会通知 MultiBinding,因此可能需要额外的通知代码。我没有遇到过这个问题,因为我主要将此技术用于替换整个集合的条件列表(因此会触发 OnPropertyChanged,它由 MultiBinding 使用),但 SO 给出了如何编写此类通知的示例。