![](/img/trans.png)
[英]How can I change the Background color of a GridViewColumn dynamically?
[英]How can I get a ListView GridViewColumn to fill the remaining space in my grid?
我想創建一個具有固定寬度的兩列和第三列以填充剩余空間的 ListView。 所以是這樣的:
<ListView>
<ListView.View>
<GridView>
<GridViewColumn Header="Name" Width="*" />
<GridViewColumn Header="Age" Width="50" />
<GridViewColumn Header="Gender" Width="50" />
</GridView>
</ListView.View>
</ListView>
問題是我找不到讓Name
列填充剩余空間的方法,因為將寬度設置為*
不起作用。 看起來有一種方法可以使用值轉換器來做到這一點,但似乎應該有一種更簡單的方法。 與 DataGrid 控件一樣,您可以使用*
s 指定列的寬度。
我試圖實現相同的目標,但后來決定我希望我的 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;
}
在研究類似問題時遇到了這個問題,我的問題是我希望所有列都是“自動”,期望第一個,這只會填補額外的空間,所以我擴展了 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>
...
此代碼也可以更通用地使用,因為列的數量不是硬編碼的,並且通過一些調整,您可能可以通過某種邏輯來定義“填充列”。
希望它可以幫助某人:)
問題是 GridViewColumn 的列寬是雙倍的,而不是 GridLength 對象,並且沒有適當的轉換來處理 *. 不確定這是否是 WPF 團隊的疏忽。 你會認為它應該被支持。
除了轉換器,我見過的唯一其他方法是在這里: http ://www.ontheblog.net/CMS/Default.aspx?tabid=36&EntryID=37。
兩者都是不需要的額外工作。 我在 ListView 和 GridView 組合中發現了其他“奇怪”的東西,所以我停止使用它們。 如果我需要一個數據網格,我使用我們許可的第 3 方,如果我需要一個復雜的 ListBox 樣式菜單,我只使用一個模板化的 ListBox。
另一個答案中提到的 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">
我的需要是讓所有列的寬度相同。 上述解決方案很好,但我更喜歡將這樣的東西包裝在附加屬性中(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 方法中調整您的大小需求。
我意識到這是一個老問題,但我在嘗試為ListView
/ GridView
中的多個列獲取基於星的解決方案時發現了這篇文章。 所以我想我可以幫助一些未來的人,因為它也會回答這個問題。
我最初實現了一個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
的 defaultMetadata 默認為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 比寬度轉換器更干凈。 總體結果也比已經發布的更靈活。
我的問題很相似,但我想修復第一列的寬度,我也不希望它在我添加或刪除列時中斷,即使在運行時也是如此。 感謝@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;
}
}
這是一個解決方案,它允許多個 ListViews 利用通用的“調整大小”事件處理程序。
//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);
}
}
我舉了上面的例子(這很好)並稍微改進它以防止調整大小時出現運行時異常:
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;
}
}
基於其他一些答案(例如 Timores 的答案),這是我通常使用的答案。 與那些不同的是,此解決方案允許相對大小和自動大小共存:
public class GridViewColumnRelativeSize {
public static readonly DependencyProperty IsEnabledProperty = DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(GridViewColumnRelativeSize), new FrameworkPropertyMetadata(IsEnabledPropertyChanged));
public static bool GetIsEnabled(DependencyObject d) => (bool)d.GetValue(IsEnabledProperty);
public static void SetIsEnabled(ListView d, bool value) => d.SetValue(IsEnabledProperty, value);
public static void IsEnabledPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) {
if (d is ListView list) {
if (!KnownLists.Contains(list)) {
KnownLists.Add(list);
list.Unloaded += OnUnloaded;
list.SizeChanged += OnSizeChanged;
}
}
else
throw new Exception("ListView expected");
}
public static readonly DependencyProperty RelativeWidthProperty = DependencyProperty.RegisterAttached("RelativeWidth", typeof(double), typeof(GridViewColumnRelativeSize), new FrameworkPropertyMetadata(double.NaN));
public static double GetWidth(DependencyObject d) => (double)d.GetValue(RelativeWidthProperty);
public static void SetWidth(GridViewColumn d, double value) => d.SetValue(RelativeWidthProperty, value);
private static readonly List<FrameworkElement> KnownLists = new();
private static void OnUnloaded(object sender, RoutedEventArgs e) {
var element = (FrameworkElement)sender;
_ = KnownLists.Remove(element);
element.Unloaded -= OnUnloaded;
element.SizeChanged -= OnSizeChanged;
}
private static void OnSizeChanged(object sender, SizeChangedEventArgs e) {
if (sender is ListView listView) {
bool isEnabled = listView.GetValue(IsEnabledProperty) is bool enabled && enabled;
if (isEnabled && listView.View is GridView gridView) {
double TotalUnits = gridView.Columns.Sum(column => {
double width = (double)column.GetValue(RelativeWidthProperty);
return double.IsNaN(width) ? 1 : width;
});
double ActualWidth = listView.ActualWidth - SystemParameters.VerticalScrollBarWidth;
double UnitWidth = Math.Floor(ActualWidth / TotalUnits);
foreach (var column in gridView.Columns) {
double unit = (double)column.GetValue(RelativeWidthProperty);
if (!double.IsNaN(unit))
column.Width = unit * UnitWidth;
}
}
}
}
}
<ListView ns:GridViewColumnRelativeSize.IsEnabled="True">
<ListView.View>
<GridView>
<GridViewColumn />
<GridViewColumn Width="50" />
<GridViewColumn ns:GridViewColumnRelativeSize.Width="2" />
</GridView>
</ListView.View>
具有特定Width
的列遵循大小,沒有Width
的列將自動調整大小,具有GridViewColumnRelativeSize.Width
的列將使用該值作為因子按比例調整大小。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.