我意识到这是一个老问题,但我在尝试为 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 比宽度转换器更干净。总体结果也比已经发布的更灵活。