我已经构建了自己的控件来模仿Windows 8中的VariableSizedGridView。由于该控件不支持UI虚拟化,我使用虚拟化来重建它。控制基本上测量所有元素并计算它们的位置,但只绘制那些在视图中的元素。这使得Surface RT具有巨大的性能提升。Windows 8.1中的内存泄漏
虽然有一个问题,控件泄漏内存。我确信它是控件(或与之相关的东西),因为我已经能够在孤立的应用程序中重现泄漏。控件的源代码分为两类。 VirtualizedList
类是控件的基础,并定义了几个DependencyProperties。 VirtualizedVariableSizedWrapGrid
类是魔法发生的地方,以及屏幕上绘制物品的位置。
我不知道我该如何解决这个问题,因为它已经发生了好几个月了,而且我没有帮上什么忙。我检查了我的事件,试图处理UI元素(这看起来不可能)。我猜发生了泄漏,因为DataTemplate的一些子元素仍然绑定到数据对象,因此仍然引用它们,这就是UI对象仍然留在内存中的原因。但我可能完全错了。正如你在代码中看到的那样,当我更新视图时,我注释掉了RemoveChild(old);
。这是因为当我重新使用旧的FrameworkElement(从DataTemplate创建)时,泄漏速度会变慢。当我一直重新创建新项目时,每个滚动操作的泄漏大约为5MB。
对此的任何帮助将非常多赞赏!谢谢。
PS:好像我无法附上所有源代码。 VirtualizedList类只定义了一些DependencyProperties,所以我会留下一个。
来源 VirtualizedVariableSizedWrapGrid
[TemplatePart(Name="Root", Type=typeof(Grid))]
[TemplatePart(Name="Scroll", Type=typeof(ScrollViewer))]
[TemplatePart(Name="LayoutArea", Type=typeof(Grid))]
[TemplatePart(Name="SnappedView", Type=typeof(ListView))]
public class VirtualizedVariableSizedWrapGrid : VirtualizedList
{
public static readonly DependencyProperty SnappedItemContainerStyleProperty = DependencyProperty.Register("SnappedItemContainerStyle", typeof(Style), typeof(VirtualizedVariableSizedWrapGrid), new PropertyMetadata(null, SnappedItemContainerStyleChanged));
public static readonly DependencyProperty ListStateProperty = DependencyProperty.Register("ListState", typeof(ViewState), typeof(VirtualizedVariableSizedWrapGrid), new PropertyMetadata(ViewState.Full, ListStateChanged));
private readonly List<object> _currentDataView;
private Grid _root;
private Panel _panel;
private ScrollViewer _scrollViewer;
private ListView _snappedView;
private const double COLUMNS_PRELOADED = 2;
private const double CARD_MARGIN = 6.0;
private int _maxRows = 0;
private object _selectedItem;
private bool _moreDataRequested = false;
public delegate void CalculatingItemSizeEventHandler(ItemContainer item);
public event CalculatingItemSizeEventHandler OnCalculatingItemSize;
public event SelectionChangedEventHandler SelectionChanged;
public event EventHandler DataRequested;
public enum ViewState
{
Full,
Snapped
}
public VirtualizedVariableSizedWrapGrid()
{
_currentDataView = new List<object>();
DefaultStyleKey = typeof(VirtualizedVariableSizedWrapGrid);
this.Style = (Style) XamlReader.Load(
@"<Style xmlns=""http://schemas.microsoft.com/winfx/2006/xaml/presentation"" xmlns:x=""http://schemas.microsoft.com/winfx/2006/xaml"" xmlns:controls=""using:MeTweets.Controls"" TargetType=""controls:VirtualizedVariableSizedWrapGrid"">
<Setter Property=""Template"">
<Setter.Value>
<ControlTemplate TargetType=""controls:VirtualizedVariableSizedWrapGrid"">
<Grid x:Name=""Root"" VerticalAlignment=""Stretch"" HorizontalAlignment=""Stretch"">
<ScrollViewer x:Name=""Scroll"" ZoomMode=""Disabled"" HorizontalScrollMode=""Auto"" VerticalScrollMode=""Disabled"" HorizontalScrollBarVisibility=""Hidden"" VerticalScrollBarVisibility=""Hidden"" HorizontalAlignment=""Stretch"" VerticalAlignment=""Stretch"">
<Grid x:Name=""LayoutArea"" />
</ScrollViewer>
<ListView x:Name=""SnappedView"" HorizontalContentAlignment=""Stretch"" HorizontalAlignment=""Stretch"" />
</Grid>
</ControlTemplate>
</Setter.Value>
</Setter>
</Style>");
this.Loaded += VirtualizedVariableSizedWrapGrid_Loaded;
}
~VirtualizedVariableSizedWrapGrid()
{
this.Loaded -= VirtualizedVariableSizedWrapGrid_Loaded;
}
#region Dependency Property Changed
private static void SnappedItemContainerStyleChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
VirtualizedVariableSizedWrapGrid varSizeGrid = sender as VirtualizedVariableSizedWrapGrid;
if (varSizeGrid == null || !(e.NewValue is Style))
return;
varSizeGrid.SetupSnappedView();
}
private static void ListStateChanged(DependencyObject sender, DependencyPropertyChangedEventArgs e)
{
VirtualizedVariableSizedWrapGrid varSizeGrid = sender as VirtualizedVariableSizedWrapGrid;
if (varSizeGrid == null || e.NewValue.Equals(e.OldValue))
return;
if (varSizeGrid.ListState == ViewState.Full)
{
varSizeGrid._scrollViewer.Visibility = Visibility.Visible;
varSizeGrid._snappedView.Visibility = Visibility.Collapsed;
}
else if (varSizeGrid.ListState == ViewState.Snapped)
{
varSizeGrid._scrollViewer.Visibility = Visibility.Collapsed;
varSizeGrid._snappedView.Visibility = Visibility.Visible;
}
if (varSizeGrid.ListState == ViewState.Full)
{
var snapScroll = varSizeGrid._snappedView.FindFirstChildOfType<ScrollViewer>();
if (snapScroll != null)
{
int index = (int)snapScroll.VerticalOffset;
if (index >= 0 && index < varSizeGrid.ItemsSource.Count)
varSizeGrid.BringIntoView(varSizeGrid.ItemsSource[index]);
}
}
else if (varSizeGrid.ListState == ViewState.Snapped)
{
if (varSizeGrid._itemContainer == null)
return;
var item = (from a in varSizeGrid._itemContainer
where (a.Column * varSizeGrid.ItemWidth) + varSizeGrid.ItemWidth > varSizeGrid._scrollViewer.HorizontalOffset + varSizeGrid.Padding.Left
select a).FirstOrDefault();
if (item == null)
return;
varSizeGrid.BringIntoView(varSizeGrid.ItemsSource[item.Index]);
}
}
#endregion
#region Virtual Voids
protected override void OnItemTemplateChanged(DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("Item template changed");
SetupSnappedView();
GenerateItemContainers();
}
protected override void OnItemsSourceChanged(DependencyPropertyChangedEventArgs e)
{
Debug.WriteLine("Items source changed");
SetupSnappedView();
if (e.OldValue != null)
{
if (e.OldValue is INotifyCollectionChanged)
{
INotifyCollectionChanged collection = (INotifyCollectionChanged)e.OldValue;
collection.CollectionChanged -= collection_CollectionChanged;
}
ClearItems();
}
if (e.NewValue != null)
{
GenerateItemContainers();
UpdateView();
if (e.NewValue is INotifyCollectionChanged)
{
INotifyCollectionChanged collection = (INotifyCollectionChanged)e.NewValue;
collection.CollectionChanged += collection_CollectionChanged;
}
}
}
protected virtual DataTemplate GetDataTemplateForItem(object item)
{
return ItemTemplate;
}
#endregion
#region Properties
public Style SnappedItemContainerStyle
{
get { return (Style)GetValue(SnappedItemContainerStyleProperty); }
set { SetValue(SnappedItemContainerStyleProperty, value); }
}
public ViewState ListState
{
get { return (ViewState)GetValue(ListStateProperty); }
set { SetValue(ListStateProperty, value); }
}
public object SelectedValue
{
get { return _selectedItem; }
set
{
if (value == _selectedItem)
return;
if (value == null)
{
_selectedItem = null;
return;
}
var oldValue = _selectedItem;
if (BringIntoView(value))
{
_selectedItem = value;
if (SelectionChanged != null)
SelectionChanged(this, new SelectionChangedEventArgs(new List<object>() { oldValue }, new List<object>() { value }));
}
}
}
#endregion
#region Overrides
protected override void OnApplyTemplate()
{
base.OnApplyTemplate();
Debug.WriteLine("OnApplyTemplate");
_root = this.GetTemplateChild("Root") as Grid;
_panel = this.GetTemplateChild("LayoutArea") as Panel;
_scrollViewer = this.GetTemplateChild("Scroll") as ScrollViewer;
_snappedView = this.GetTemplateChild("SnappedView") as ListView;
_snappedView.Loaded += _snappedView_Loaded;
_scrollViewer.ViewChanging += _scrollViewer_ViewChanging;
_scrollViewer.ViewChanged += _scrollViewer_ViewChanged;
_panel.SizeChanged += _panel_SizeChanged;
_snappedView.SelectionChanged += _snappedView_SelectionChanged;
App.RootFrame.SizeChanged += RootFrame_SizeChanged;
if (ItemsSource != null)
{
GenerateItemContainers();
UpdateView();
}
SetupSnappedView();
}
#endregion
#region Private voids
protected override void ClearItems()
{
if (_panel == null)
return;
foreach (var item in _panel.Children.Cast<FrameworkElement>())
{
RemoveChild(item);
}
_currentDataView.Clear();
_panel.Children.Clear();
}
protected override void GenerateItemContainers()
{
if (_panel == null || _panel.Visibility == Visibility.Collapsed || _panel.ActualHeight == 0 || _panel.ActualWidth == 0)
return;
_itemContainer.Clear();
if (ItemsSource == null)
return;
int _currentRow = 0, _currentColumn = 0;
int _currentColWidth = 1;
_maxRows = (int)((_panel.ActualHeight-this.Padding.Bottom-this.Padding.Top)/this.ItemHeight);
for (int ix = 0; ix < ItemsSource.Count; ix++)
{
var item = ItemsSource[ix];
var container = new ItemContainer { Index = ix };
if (_currentDataView.Contains(item))
container.IsRealized = true;
if (OnCalculatingItemSize != null)
OnCalculatingItemSize(container);
if (container.RowSpan < 0)
container.RowSpan = 0;
if (container.ColumnSpan < 0)
container.ColumnSpan = 0;
container.Column = _currentColumn;
container.Row = _currentRow;
if (container.RowSpan + _currentRow > _maxRows) //Not enough rows --> new column
{
if (_itemContainer.Count > 0)
{
// Equally split the rest of the space in this column over the elements that are currently in it
var additionalrows = _maxRows - _currentRow;
var c = _itemContainer.Where(x => x.Column == _currentColumn).ToList();
var ccount = c.Count();
var i = 0;
foreach (var t in c)
{
t.Row += (additionalrows/ccount)*i;
t.RowSpan += additionalrows/ccount;
i++;
}
// Couldn't equally divide, give the rest of the space to the last element
if (additionalrows%ccount > 0)
{
c.Last().RowSpan += additionalrows%ccount;
}
}
_currentColumn++;
container.Column = _currentColumn;
_currentRow = 0;
container.Row = _currentRow;
_currentColWidth = container.ColumnSpan;
_itemContainer.Add(container);
_currentRow += container.RowSpan;
continue;
}
//Make column as wide as widest element
if (container.ColumnSpan > _currentColWidth)
_currentColWidth = container.ColumnSpan;
if (_currentColWidth > container.ColumnSpan)
container.ColumnSpan = _currentColWidth;
_currentRow += container.RowSpan;
if (_currentRow >= _maxRows)
{
//Make sure all elements in column are as wide as widest element
foreach (var wider in
_itemContainer.Where(x => x.ColumnSpan < _currentColWidth && x.Column == _currentColumn))
{
wider.ColumnSpan = _currentColWidth;
}
_currentRow = 0;
_currentColumn += _currentColWidth;
_currentColWidth = 1;
}
_itemContainer.Add(container);
}
}
protected override void UpdateView()
{
if (_panel == null || _scrollViewer == null || _itemContainer == null || _itemContainer.Count == 0)
return;
_panel.Width = ((_itemContainer.Last().Column + 1) * this.ItemWidth) + this.Padding.Left + this.Padding.Right;
var margin = this.ItemWidth * COLUMNS_PRELOADED;
double minLeft = (_scrollViewer.HorizontalOffset - margin);
double maxLeft = (_scrollViewer.HorizontalOffset + _scrollViewer.ActualWidth + margin);
int minColumn = (int)(minLeft/this.ItemWidth)-1;
int maxColumn = (int)(maxLeft/this.ItemWidth)+1;
minLeft = (minColumn * this.ItemWidth) + this.Padding.Left;
maxLeft = (maxColumn * this.ItemWidth) + this.Padding.Left;
_currentDataView.Clear();
var shown = _itemContainer.Where(x => x.Column >= minColumn && x.Column <= maxColumn);
_currentDataView.AddRange(shown.Select(x => ItemsSource[x.Index]));
List<FrameworkElement> recycle = new List<FrameworkElement>();
foreach (var old in _panel.Children.Cast<FrameworkElement>().Where(x => x.Margin.Left < minLeft || x.Margin.Left > maxLeft ||
!(_currentDataView.Contains(x.DataContext))).ToList())
{
foreach (var recycled in _itemContainer.Where(x => ItemsSource[x.Index] == old.DataContext))
recycled.IsRealized = false;
//RemoveChild(old);
recycle.Add(old);
}
foreach (var item in shown)
{
FrameworkElement obj = recycle.FirstOrDefault();
double left, top;
if (item.IsRealized)
{
//Rearrange item if needed
obj = _panel.Children.FirstOrDefault(x => x is FrameworkElement && ((FrameworkElement)x).DataContext == ItemsSource[item.Index]) as FrameworkElement;
if (obj == null)
continue;
obj.Height = (this.ItemHeight * item.RowSpan) - (2 * CARD_MARGIN);
obj.Width = (this.ItemWidth * item.ColumnSpan) - (2 * CARD_MARGIN);
left = (item.Column * this.ItemWidth) + this.Padding.Left;
top = (item.Row * this.ItemHeight) + this.Padding.Top;
obj.Margin = new Thickness(left, top, 0, 0);
continue;
}
item.IsRealized = true;
if (obj == null)
{
obj = this.GetDataTemplateForItem(item).LoadContent() as FrameworkElement;
obj.Tapped += Item_Tapped;
obj.PointerReleased += Item_PointerReleased;
obj.PointerEntered += Item_PointerEntered;
obj.PointerExited += Item_PointerExited;
_panel.Children.Add(obj);
}
else
recycle.Remove(obj);
obj.DataContext = null;
obj.DataContext = ItemsSource[item.Index];
obj.Height = (this.ItemHeight * item.RowSpan) - (2 * CARD_MARGIN);
obj.Width = (this.ItemWidth * item.ColumnSpan) - (2 * CARD_MARGIN);
obj.VerticalAlignment = VerticalAlignment.Top;
obj.HorizontalAlignment = HorizontalAlignment.Left;
obj.Visibility = Visibility.Visible;
left = (item.Column * this.ItemWidth) + this.Padding.Left;
top = (item.Row * this.ItemHeight) + this.Padding.Top;
obj.Margin = new Thickness(left, top, 0, 0);
}
foreach (var notRecycled in recycle) //Remove not recycled items
RemoveChild(notRecycled);
Debug.WriteLine("Children count "+_panel.Children.Count);
}
private void RemoveChild(FrameworkElement item)
{
item.Tapped -= Item_Tapped;
item.PointerReleased -= Item_PointerReleased;
item.PointerEntered -= Item_PointerEntered;
item.PointerExited -= Item_PointerExited;
_panel.Children.Remove(item);
item.DataContext = null;
item.ClearValue(DataContextProperty);
}
public static IEnumerable<PropertyInfo> GetAllProperties(TypeInfo type)
{
var list = type.DeclaredProperties.ToList();
var subtype = type.BaseType;
if (subtype != null)
list.AddRange(GetAllProperties(subtype.GetTypeInfo()));
return list.ToArray();
}
public List<DependencyObject> AllChildren(DependencyObject parent)
{
var list = new List<DependencyObject>();
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
{
var child = VisualTreeHelper.GetChild(parent, i);
if (child != null)
list.Add(child);
list.AddRange(AllChildren(child));
}
return list;
}
private void SetupSnappedView()
{
if (_snappedView == null)
return;
_snappedView.ItemTemplate = this.ItemTemplate;
_snappedView.ItemContainerStyle = this.SnappedItemContainerStyle;
_snappedView.ItemsSource = this.ItemsSource;
var snapScroll = this._snappedView.FindFirstChildOfType<ScrollViewer>();
if (snapScroll != null)
{
snapScroll.ViewChanged -= snapped_ViewChanged;
snapScroll.ViewChanged += snapped_ViewChanged;
}
}
private void Item_PointerExited(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
{
}
private void Item_PointerEntered(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
{
}
private bool _pointerPressed = false;
private object _pointerOriginalSource = null;
private void Item_PointerReleased(object sender, Windows.UI.Xaml.Input.PointerRoutedEventArgs e)
{
if (e.Handled)
return;
_pointerPressed = true;
_pointerOriginalSource = e.OriginalSource;
}
private void Item_Tapped(object sender, Windows.UI.Xaml.Input.TappedRoutedEventArgs e)
{
if (!_pointerPressed || e.OriginalSource != _pointerOriginalSource)
{
_pointerPressed = false;
return;
}
object previousObject = _selectedItem;
object dataObject = ((FrameworkElement)sender).DataContext;
if (dataObject != _selectedItem)
{
_selectedItem = dataObject;
if (SelectionChanged != null)
SelectionChanged(this, new SelectionChangedEventArgs(new List<object>() { previousObject }, new List<object>() { dataObject }));
}
_pointerPressed = false;
}
void collection_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
object item = null;
if (_itemContainer != null && _itemContainer.Count > 0 && ListState == ViewState.Snapped)
{
var snapScroll = _snappedView.FindFirstChildOfType<ScrollViewer>();
if (snapScroll != null)
{
int index = (int)snapScroll.VerticalOffset;
if (index >= 0 && index < ItemsSource.Count)
item = ItemsSource[index];
}
}
else if (_itemContainer != null && _itemContainer.Count > 0 && ListState == ViewState.Full)
{
var i = (from a in _itemContainer
where (a.Column * ItemWidth) + ItemWidth > _scrollViewer.HorizontalOffset + Padding.Left
select a).FirstOrDefault();
if (i == null)
return;
item = ItemsSource[i.Index];
}
if (e.Action == NotifyCollectionChangedAction.Add || e.Action == NotifyCollectionChangedAction.Reset)
_moreDataRequested = false;
GenerateItemContainers();
UpdateView();
//if (item != null)
// BringIntoView(item);
//else if (this.ItemsSource is IList && (this.ItemsSource as IList).Count > 0)
// BringIntoView((this.ItemsSource as IList)[0]);
}
void VirtualizedVariableSizedWrapGrid_Loaded(object sender, RoutedEventArgs e)
{
if (this.ListState == ViewState.Full)
{
this._scrollViewer.Visibility = Visibility.Visible;
this._snappedView.Visibility = Visibility.Collapsed;
}
else if (this.ListState == ViewState.Snapped)
{
this._scrollViewer.Visibility = Visibility.Collapsed;
this._snappedView.Visibility = Visibility.Visible;
}
Debug.WriteLine("VirtualizedVariableSizedWrapGrid Loaded");
if (this.ItemsSource != null && (_itemContainer == null || _itemContainer.Count == 0))
{
GenerateItemContainers();
UpdateView();
}
}
void snapped_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
if (!(sender is ScrollViewer))
return;
var scroll = (sender as ScrollViewer);
var maxOffset = scroll.ExtentHeight - scroll.ViewportHeight;
if (!_moreDataRequested && (maxOffset <= 0 || scroll.VerticalOffset >= maxOffset))
{
_moreDataRequested = true;
if (DataRequested != null)
DataRequested(this, EventArgs.Empty);
}
}
void _snappedView_Loaded(object sender, RoutedEventArgs e)
{
var snapScroll = this._snappedView.FindFirstChildOfType<ScrollViewer>();
if (snapScroll != null)
{
snapScroll.ViewChanged += snapped_ViewChanged;
}
}
private double _lastOffsetUpdate = 0;
void _scrollViewer_ViewChanging(object sender, ScrollViewerViewChangingEventArgs e)
{
if (e.NextView.HorizontalOffset == e.FinalView.HorizontalOffset)
{
UpdateView();
return;
}
if (Math.Abs(e.NextView.HorizontalOffset - _lastOffsetUpdate) > 100)
{
_lastOffsetUpdate = e.NextView.HorizontalOffset;
UpdateView();
}
}
void _scrollViewer_ViewChanged(object sender, ScrollViewerViewChangedEventArgs e)
{
if (!_moreDataRequested && this._scrollViewer.HorizontalOffset + this._scrollViewer.ActualWidth >= (this._panel.ActualWidth))
{
_moreDataRequested = true;
if (DataRequested != null)
DataRequested(this, EventArgs.Empty);
}
}
void _panel_SizeChanged(object sender, SizeChangedEventArgs e)
{
int currentMaxRows = _maxRows;
_maxRows = (int)((_panel.ActualHeight - this.Padding.Bottom - this.Padding.Top)/this.ItemHeight);
if (_maxRows != currentMaxRows) //Orientation probably changed, control got higher
{
Debug.WriteLine("Panel size changed");
ClearItems();
GenerateItemContainers();
UpdateView();
}
}
void _snappedView_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (e.AddedItems.Count > 0)
this.SelectedValue = e.AddedItems[0];
_snappedView.SelectedItem = null;
}
void RootFrame_SizeChanged(object sender, SizeChangedEventArgs e)
{
if (_itemContainer != null && _itemContainer.Count > 0 && _panel.ActualWidth > 0)
UpdateView();
}
#endregion
#region Public voids
public override bool BringIntoView(object obj, bool animate = true)
{
try
{
if (_itemContainer == null || _itemContainer.Count == 0)
return false;
var item = (from a in _itemContainer
where ItemsSource[a.Index] == obj
select a).FirstOrDefault();
if (item == null)
return false;
//Calculate position
double offset = (item.Column * this.ItemWidth) + CARD_MARGIN;
this._scrollViewer.ChangeView(offset, null, null, !animate);
_snappedView.ScrollIntoView(obj, ScrollIntoViewAlignment.Leading);
UpdateView();
return true;
}
catch { return false; }
}
#endregion
}
欢迎来到Stack Overflow!如果你将代码编写成[最小,完整,测试和可读](http://stackoverflow.com/help/mcve),我们很容易找到问题。在很多情况下,锻炼甚至可以帮助你自己找到答案! – mhlester