有誰知道啓用分組時支持UI虛擬化的ListView實現嗎?默認情況下,當設置分組時,VirtualizingStackPanel被禁用。WPF ListView虛擬化分組
看來微軟不會在.NET框架的4.0版本中實現這個功能,所以我正在尋找替代解決方案。
有誰知道啓用分組時支持UI虛擬化的ListView實現嗎?默認情況下,當設置分組時,VirtualizingStackPanel被禁用。WPF ListView虛擬化分組
看來微軟不會在.NET框架的4.0版本中實現這個功能,所以我正在尋找替代解決方案。
我找到了一個示例Grouping and Virtualization MSDN Code Sample,它將組合的ListView轉換爲支持虛擬化的平面列表。但是我無法解決如何模仿頭文件的擴展動作。
您是否有幸找到如何切換分組項目的可見性?讓他們表現得像是將分組項目作爲內容的擴展器? – bigfoot 2011-11-09 22:23:14
WPF/.NET 4.5現在支持這種https://msdn.microsoft.com/en-us/library/system.windows.controls.virtualizingpanel.isvirtualizingwhengrouping(v=vs.110).aspx
如果你還在瞄準4.0你可以用反射設置這樣至少一些用戶可以得到好處。
我希望它沒有太多的話題,但我最近有一個類似的問題。如上所述,它只是.NET 4.0的問題。我甚至會同意,在大多數情況下,組合框通常不需要虛擬化,因爲它不應該有那麼多項目,並且如果需要分組,那麼應該實施某種主 - 細節解決方案。但可能有一些灰色地帶。
盧克關於MSDN上的分組和虛擬化提供的鏈接幫助我了很多。就我而言,這是我能夠在任何需要的方向上找到或找到的唯一方法。它不支持來自ListViewCollection的所有功能。我不得不重寫一些方法,否則選擇項目將無法正常工作。顯然還有更多的工作要做。
因此,這裏是FlatGroupListCollectionView從here更新的解決方案:
/// <summary>
/// Provides a view that flattens groups into a list
/// This is used to avoid limitation that ListCollectionView has in .NET 4.0, if grouping is used then Virtialuzation would not work
/// It assumes some appropriate impelmentation in view(XAML) in order to support this way of grouping
/// Note: As implemented, it does not support nested grouping
/// Note: Only overriden properties and method behaves correctly, some of methods and properties related to selection of item might not work as expected and would require new implementation
/// </summary>
public class FlatGroupListCollectionView : ListCollectionView
{
/// <summary>
/// Initializes a new instance of the <see cref="FlatGroupListCollectionView"/> class.
/// </summary>
/// <param name="list">A list used in this collection</param>
public FlatGroupListCollectionView(IList list)
: base(list)
{
}
/// <summary>
/// This currently only supports one level of grouping
/// Returns CollectionViewGroups if the index matches a header
/// Otherwise, maps the index into the base range to get the actual item
/// </summary>
/// <param name="index">Index from which get an item</param>
/// <returns>Item that was found on given index</returns>
public override object GetItemAt(int index)
{
int delta = 0;
ReadOnlyObservableCollection<object> groups = this.BaseGroups;
if (groups != null)
{
int totalCount = 0;
for (int i = 0; i < groups.Count; i++)
{
CollectionViewGroup group = groups[i] as CollectionViewGroup;
if (group != null)
{
if (index == totalCount)
{
return group;
}
delta++;
int numInGroup = group.ItemCount;
totalCount += numInGroup + 1;
if (index < totalCount)
{
break;
}
}
}
}
object item = base.GetItemAt(index - delta);
return item;
}
/// <summary>
/// In the flat list, the base count is incremented by the number of groups since there are that many headers
/// To support nested groups, the nested groups must also be counted and added to the count
/// </summary>
public override int Count
{
get
{
int count = base.Count;
if (this.BaseGroups != null)
{
count += this.BaseGroups.Count;
}
return count;
}
}
/// <summary>
/// By returning null, we trick the generator into thinking that we are not grouping
/// Thus, we avoid the default grouping code
/// </summary>
public override ReadOnlyObservableCollection<object> Groups
{
get
{
return null;
}
}
/// <summary>
/// Gets the Groups collection from the base class
/// </summary>
private ReadOnlyObservableCollection<object> BaseGroups
{
get
{
return base.Groups;
}
}
/// <summary>
/// DetectGroupHeaders is a way to get access to the containers by setting the value to true in the container style
/// That way, the change handler can hook up to the container and provide a value for IsHeader
/// </summary>
public static readonly DependencyProperty DetectGroupHeadersProperty =
DependencyProperty.RegisterAttached("DetectGroupHeaders", typeof(bool), typeof(FlatGroupListCollectionView), new FrameworkPropertyMetadata(false, OnDetectGroupHeaders));
/// <summary>
/// Gets the Detect Group Headers property
/// </summary>
/// <param name="obj">Dependency Object from which the property is get</param>
/// <returns>Value of Detect Group Headers property</returns>
public static bool GetDetectGroupHeaders(DependencyObject obj)
{
return (bool)obj.GetValue(DetectGroupHeadersProperty);
}
/// <summary>
/// Sets the Detect Group Headers property
/// </summary>
/// <param name="obj">Dependency Object on which the property is set</param>
/// <param name="value">Value to set to property</param>
public static void SetDetectGroupHeaders(DependencyObject obj, bool value)
{
obj.SetValue(DetectGroupHeadersProperty, value);
}
/// <summary>
/// IsHeader can be used to style the container differently when it is a header
/// For instance, it can be disabled to prevent selection
/// </summary>
public static readonly DependencyProperty IsHeaderProperty =
DependencyProperty.RegisterAttached("IsHeader", typeof(bool), typeof(FlatGroupListCollectionView), new FrameworkPropertyMetadata(false));
/// <summary>
/// Gets the Is Header property
/// </summary>
/// <param name="obj">Dependency Object from which the property is get</param>
/// <returns>Value of Is Header property</returns>
public static bool GetIsHeader(DependencyObject obj)
{
return (bool)obj.GetValue(IsHeaderProperty);
}
/// <summary>
/// Sets the Is Header property
/// </summary>
/// <param name="obj">Dependency Object on which the property is set</param>
/// <param name="value">Value to set to property</param>
public static void SetIsHeader(DependencyObject obj, bool value)
{
obj.SetValue(IsHeaderProperty, value);
}
/// <summary>
/// Raises the System.Windows.Data.CollectionView.CollectionChanged event.
/// </summary>
/// <param name="args">The System.Collections.Specialized.NotifyCollectionChangedEventArgs object to pass to the event handler</param>
protected override void OnCollectionChanged(NotifyCollectionChangedEventArgs args)
{
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
{
int flatIndex = this.ConvertFromItemToFlat(args.NewStartingIndex, false);
int headerIndex = Math.Max(0, flatIndex - 1);
object o = this.GetItemAt(headerIndex);
CollectionViewGroup group = o as CollectionViewGroup;
if ((group != null) && (group.ItemCount == args.NewItems.Count))
{
// Notify that a header was added
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, new object[] { group }, headerIndex));
}
base.OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, args.NewItems, flatIndex));
}
break;
case NotifyCollectionChangedAction.Remove:
// TODO: Implement this action
break;
case NotifyCollectionChangedAction.Move:
// TODO: Implement this action
break;
case NotifyCollectionChangedAction.Replace:
// TODO: Implement this action
break;
default:
base.OnCollectionChanged(args);
break;
}
}
/// <summary>
/// Sets the specified item to be the System.Windows.Data.CollectionView.CurrentItem in the view
/// This is an override of base method, an item index is get first and its needed to convert that index to flat version which includes groups
/// Then adjusted version of MoveCurrentToPosition base method is called
/// </summary>
/// <param name="item">The item to set as the System.Windows.Data.CollectionView.CurrentItem</param>
/// <returns>true if the resulting System.Windows.Data.CollectionView.CurrentItem is within the view; otherwise, false</returns>
public override bool MoveCurrentTo(object item)
{
int index = this.IndexOf(item);
int newIndex = this.ConvertFromItemToFlat(index, false);
return this.MoveCurrentToPositionBase(newIndex);
}
/// <summary>
/// Sets the item at the specified index to be the System.Windows.Data.CollectionView.CurrentItem in the view
/// This is an override of base method, Its called when user selects new item from this collection
/// A delta is get of which is the possition shifted because of groups and we shift this position by this delta and then base method is called
/// </summary>
/// <param name="position">The index to set the System.Windows.Data.CollectionView.CurrentItem to</param>
/// <returns>true if the resulting System.Windows.Data.CollectionView.CurrentItem is an item within the view; otherwise, false</returns>
public override bool MoveCurrentToPosition(int position)
{
int delta = this.GetDelta(position);
int newPosition = position - delta;
return base.MoveCurrentToPosition(newPosition);
}
private static void OnDetectGroupHeaders(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
// This assumes that a container will not change between being a header and not
// If using ContainerRecycling this may not be the case
((FrameworkElement)d).Loaded += OnContainerLoaded;
}
private static void OnContainerLoaded(object sender, RoutedEventArgs e)
{
FrameworkElement element = (FrameworkElement)sender;
element.Loaded -= OnContainerLoaded; // If recycling, remove this line
// CollectionViewGroup is the type of the header in this sample
// Add more types or change the type as necessary
if (element.DataContext is CollectionViewGroup)
{
SetIsHeader(element, true);
}
}
private int ConvertFromItemToFlat(int index, bool removed)
{
ReadOnlyObservableCollection<object> groups = this.BaseGroups;
if (groups != null)
{
int start = 1;
for (int i = 0; i < groups.Count; i++)
{
CollectionViewGroup group = groups[i] as CollectionViewGroup;
if (group != null)
{
index++;
int end = start + group.ItemCount;
if ((start <= index) && ((!removed && index < end) || (removed && index <= end)))
{
break;
}
start = end + 1;
}
}
}
return index;
}
/// <summary>
/// Move <seealso cref="CollectionView.CurrentItem"/> to the item at the given index.
/// This is a replacement for base method
/// </summary>
/// <param name="position">Move CurrentItem to this index</param>
/// <returns>true if <seealso cref="CollectionView.CurrentItem"/> points to an item within the view.</returns>
private bool MoveCurrentToPositionBase(int position)
{
// VerifyRefreshNotDeferred was removed
bool result = false;
// Instead of property InternalCount we use Count property
if (position < -1 || position > this.Count)
{
throw new ArgumentOutOfRangeException("position");
}
if (position != this.CurrentPosition || !this.IsCurrentInSync)
{
// Instead of property InternalCount we use Count property from this class
// Instead of InternalItemAt we use GetItemAt from this class
object proposedCurrentItem = (0 <= position && position < this.Count) ? this.GetItemAt(position) : null;
// ignore moves to the placeholder
if (proposedCurrentItem != CollectionView.NewItemPlaceholder)
{
if (this.OKToChangeCurrent())
{
bool oldIsCurrentAfterLast = this.IsCurrentAfterLast;
bool oldIsCurrentBeforeFirst = this.IsCurrentBeforeFirst;
this.SetCurrent(proposedCurrentItem, position);
this.OnCurrentChanged();
// notify that the properties have changed.
if (this.IsCurrentAfterLast != oldIsCurrentAfterLast)
{
this.OnPropertyChanged(PropertySupport.ExtractPropertyName(() => this.IsCurrentAfterLast));
}
if (this.IsCurrentBeforeFirst != oldIsCurrentBeforeFirst)
{
this.OnPropertyChanged(PropertySupport.ExtractPropertyName(() => this.IsCurrentBeforeFirst));
}
this.OnPropertyChanged(PropertySupport.ExtractPropertyName(() => this.CurrentPosition));
this.OnPropertyChanged(PropertySupport.ExtractPropertyName(() => this.CurrentItem));
result = true;
}
}
}
// Instead of IsCurrentInView we return result
return result;
}
private int GetDelta(int index)
{
int delta = 0;
ReadOnlyObservableCollection<object> groups = this.BaseGroups;
if (groups != null)
{
int totalCount = 0;
for (int i = 0; i < groups.Count; i++)
{
CollectionViewGroup group = groups[i] as CollectionViewGroup;
if (group != null)
{
if (index == totalCount)
{
break;
}
delta++;
int numInGroup = group.ItemCount;
totalCount += numInGroup + 1;
if (index < totalCount)
{
break;
}
}
}
}
return delta;
}
/// <summary>
/// Helper to raise a PropertyChanged event
/// </summary>
/// <param name="propertyName">Name of the property</param>
private void OnPropertyChanged(string propertyName)
{
base.OnPropertyChanged(new PropertyChangedEventArgs(propertyName));
}
}
XAML一部分留,因爲它在示例代碼。視圖模型保持原樣,這意味着使用FlatGroupListCollectionView並設置GroupDescriptions。
我更喜歡這個解決方案,因爲它將視圖模型中的數據列表中的分組邏輯與我的列表分開。其他解決方案是實現對視圖模型中原始項目列表的分組支持,這意味着以某種方式識別標題。對於一次性使用應該沒問題,但可能需要重新創建集合以用於不同或不分組的目的,這是不太好的。
謝謝!我查看了第3部分中的示例代碼。我遇到的主要困難是如何使用GroupDescriptions添加分組項目 – Luke 2009-07-09 07:16:59