Windows Phone中的几种集合控件

时间:2023-03-08 19:46:46
  • 前言

Windows Phone开发过程中不可避免的就是和集合数据打交道,如果之前做过WP App的开发的话,相信你已经看过了各种集合控件的使用、扩展和自定义。这些个内容在这篇博客里都没有,那么我们今天说点儿什么呢。当然也还是围绕WP的集合控件,要不然就和本文的题目不相符了,这篇博客主要讲集合控件的一些基础知识和在使用它们的过程中遇到的种种问题。WP中总共有三种集合控件,分别是ItemsControl、ListBox、LongListSelector。虽然都是集合控件,但它们的出场率绝对有着天壤之别,那么就一一说说它们的异同吧。

  1. ItemsControl

      相信ItemsControl应该算是出场率最低的集合控件了,以至于很多人都不知道有这么个控件的存在。这也不足为其,因为在各大官方非官方的Data Binding的例子中都见不到他的身影,大大降低了直接Copy Paste的概率。当然不能说那些个写Demo的人偏心,确实是由于ItemsControl本身的轻量级和功能简单造成的。

先来看看ItemsControl的代码。

 // Summary:
// Represents a control that can be used to present a collection of items.
[ContentProperty("Items", true)]
public class ItemsControl : Control
{
// Summary:
// Identifies the System.Windows.Controls.ItemsControl.DisplayMemberPath dependency
// property.
//
// Returns:
// The identifier for the System.Windows.Controls.ItemsControl.DisplayMemberPath
// dependency property.
public static readonly DependencyProperty DisplayMemberPathProperty;
//
// Summary:
// Identifies the System.Windows.Controls.ItemsControl.ItemsPanel dependency
// property.
//
// Returns:
// The identifier for the System.Windows.Controls.ItemsControl.ItemsPanel dependency
// property.
public static readonly DependencyProperty ItemsPanelProperty;
//
// Summary:
// Identifies the System.Windows.Controls.ItemsControl.ItemsSource dependency
// property.
//
// Returns:
// The identifier for the System.Windows.Controls.ItemsControl.ItemsSource dependency
// property.
public static readonly DependencyProperty ItemsSourceProperty;
//
// Summary:
// Identifies the System.Windows.Controls.ItemsControl.ItemTemplate dependency
// property.
//
// Returns:
// The identifier for the System.Windows.Controls.ItemsControl.ItemTemplate
// dependency property.
public static readonly DependencyProperty ItemTemplateProperty; // Summary:
// Initializes a new instance of the System.Windows.Controls.ItemsControl class.
public ItemsControl(); // Summary:
// Gets or sets the name or path of the property that is displayed for each
// data item.
//
// Returns:
// The name or path of the property that is displayed for each the data item
// in the control. The default is an empty string ("").
public string DisplayMemberPath { get; set; }
//
// Summary:
// Gets the System.Windows.Controls.ItemContainerGenerator associated with this
// System.Windows.Controls.ItemsControl.
//
// Returns:
// The System.Windows.Controls.ItemContainerGenerator associated with this System.Windows.Controls.ItemsControl.
public ItemContainerGenerator ItemContainerGenerator { get; }
//
// Summary:
// Gets the collection used to generate the content of the control.
//
// Returns:
// The collection that is used to generate the content of the control, if it
// exists; otherwise, null. The default is an empty collection.
public ItemCollection Items { get; }
//
// Summary:
// Gets or sets the template that defines the panel that controls the layout
// of items.
//
// Returns:
// An System.Windows.Controls.ItemsPanelTemplate that defines the panel to use
// for the layout of the items. The default value for the System.Windows.Controls.ItemsControl
// is an System.Windows.Controls.ItemsPanelTemplate that specifies a System.Windows.Controls.StackPanel.
public ItemsPanelTemplate ItemsPanel { get; set; }
//
// Summary:
// Gets or sets a collection used to generate the content of the System.Windows.Controls.ItemsControl.
//
// Returns:
// The object that is used to generate the content of the System.Windows.Controls.ItemsControl.
// The default is null.
public IEnumerable ItemsSource { get; set; }
//
// Summary:
// Gets or sets the System.Windows.DataTemplate used to display each item.
//
// Returns:
// The template that specifies the visualization of the data objects. The default
// is null.
public DataTemplate ItemTemplate { get; set; } // Summary:
// Undoes the effects of the System.Windows.Controls.ItemsControl.PrepareContainerForItemOverride(System.Windows.DependencyObject,System.Object)
// method.
//
// Parameters:
// element:
// The container element.
//
// item:
// The item.
protected virtual void ClearContainerForItemOverride(DependencyObject element, object item);
//
// Summary:
// Creates or identifies the element that is used to display the given item.
//
// Returns:
// The element that is used to display the given item.
protected virtual DependencyObject GetContainerForItemOverride();
//
// Summary:
// Returns the System.Windows.Controls.ItemsControl that the specified element
// hosts items for.
//
// Parameters:
// element:
// The host element.
//
// Returns:
// The System.Windows.Controls.ItemsControl that the specified element hosts
// items for, or null.
public static ItemsControl GetItemsOwner(DependencyObject element);
//
// Summary:
// Determines if the specified item is (or is eligible to be) its own container.
//
// Parameters:
// item:
// The item to check.
//
// Returns:
// true if the item is (or is eligible to be) its own container; otherwise,
// false.
protected virtual bool IsItemItsOwnContainerOverride(object item);
//
// Summary:
// Returns the System.Windows.Controls.ItemsControl that owns the specified
// container element.
//
// Parameters:
// container:
// The container element to return the System.Windows.Controls.ItemsControl
// for.
//
// Returns:
// The System.Windows.Controls.ItemsControl that owns the specified container
// element; otherwise, null. The System.Windows.Controls.ItemsControl.ItemsControlFromItemContainer(System.Windows.DependencyObject)
// method returns null if container is not a System.Windows.UIElement or the
// parent is not an System.Windows.Controls.ItemsControl.
public static ItemsControl ItemsControlFromItemContainer(DependencyObject container);
//
// Summary:
// Called when the value of the System.Windows.Controls.ItemsControl.Items property
// changes.
//
// Parameters:
// e:
// A System.Collections.Specialized.NotifyCollectionChangedEventArgs that contains
// the event data
protected virtual void OnItemsChanged(NotifyCollectionChangedEventArgs e);
//
// Summary:
// Prepares the specified element to display the specified item.
//
// Parameters:
// element:
// The element used to display the specified item.
//
// item:
// The item to display.
protected virtual void PrepareContainerForItemOverride(DependencyObject element, object item);
}

没有眼花缭乱的Template,也没有千变万化的VisualStateGroup,感觉完全就是一个基类的命,确实他也是后面要提到的ListBox的基类。但存在即合理嘛,虽然他没有UI虚拟化的功能,但已其轻量的优势完全可以胜任既定的小规模的数据绑定的工作,如果在配合ScrollViewer使用,效果完全不输给后面的两种集合控件。

Note: ItemsControl 是不具有UI虚拟化功能的,这就意味着您绑定的Data会一次性的全Load出来,您可以在ItemTemplate的任意控件上加个Loaded事件,看看output就会看到您所绑定的数据一个一个的被加载出来,即使他们没有显示在屏幕上也是预先加载到了内存中,这绝对不是一个小的开销,所以说ItemsControl只适合做一些小量的数据绑定工作。

2.  ListBox

      ListBox绝对算是WP中的集合控件的主角,在无数的下拉刷新、滑动刷新、控件扩展中绝对是少补了ListBox,在大数据量的数据绑定中ListBox绝对是出尽了风头,配合VirtualizingStackPanel使用,UI虚拟化绝对让你的应用变成360°无死角顺畅。但这些都不是我们要说的内容。

还是先来看一下ListBox的代码。

 // Summary:
// Contains a list of selectable items.
[TemplatePart(Name = "ScrollViewer", Type = typeof(ScrollViewer))]
[TemplateVisualState(Name = "InvalidFocused", GroupName = "ValidationStates")]
[TemplateVisualState(Name = "InvalidUnfocused", GroupName = "ValidationStates")]
[TemplateVisualState(Name = "Valid", GroupName = "ValidationStates")]
public class ListBox : Selector
{
// Summary:
// Identifies the IsSelectionActive dependency property.
//
// Returns:
// The identifier for the IsSelectionActive dependency property.
public static readonly DependencyProperty IsSelectionActiveProperty;
//
// Summary:
// Identifies the System.Windows.Controls.ListBox.ItemContainerStyle dependency
// property.
//
// Returns:
// The identifier for the System.Windows.Controls.ListBox.ItemContainerStyle
// dependency property.
public static readonly DependencyProperty ItemContainerStyleProperty;
//
// Summary:
// Identifies the System.Windows.Controls.ListBox.SelectionMode dependency property.
//
// Returns:
// The identifier for the System.Windows.Controls.ListBox.SelectionMode dependency
// property.
public static readonly DependencyProperty SelectionModeProperty; // Summary:
// Initializes a new instance of the System.Windows.Controls.ListBox class.
public ListBox(); // Summary:
// Gets or sets the style that is used when rendering the item containers.
//
// Returns:
// The style applied to the item containers. The default is null.
public Style ItemContainerStyle { get; set; }
//
// Summary:
// Gets the list of currently selected items for the System.Windows.Controls.ListBox
// control.
//
// Returns:
// The list of currently selected items for the System.Windows.Controls.ListBox.
public IList SelectedItems { get; }
//
// Summary:
// Gets or sets the selection behavior for the System.Windows.Controls.ListBox
// control.
//
// Returns:
// One of the System.Windows.Controls.SelectionMode values.
public SelectionMode SelectionMode { get; set; } // Summary:
// Creates or identifies the element used to display a specified item.
//
// Returns:
// A System.Windows.Controls.ListBoxItem corresponding to a specified item.
protected override DependencyObject GetContainerForItemOverride();
//
// Summary:
// Determines if the specified item is (or is eligible to be) its own item container.
//
// Parameters:
// item:
// The specified item.
//
// Returns:
// true if the item is its own item container; otherwise, false.
protected override bool IsItemItsOwnContainerOverride(object item);
//
// Summary:
// Builds the visual tree for the System.Windows.Controls.ListBox control when
// a new template is applied.
public override void OnApplyTemplate();
//
// Summary:
// Returns a System.Windows.Automation.Peers.ListBoxAutomationPeer for the Windows Phone
// automation infrastructure.
//
// Returns:
// A System.Windows.Automation.Peers.ListBoxAutomationPeer for the System.Windows.Controls.ListBox
// object.
protected override AutomationPeer OnCreateAutomationPeer();
//
// Summary:
// Provides handling for the System.Windows.UIElement.GotFocus event.
//
// Parameters:
// e:
// The event data.
protected override void OnGotFocus(RoutedEventArgs e);
//
// Summary:
// Provides handling for the System.Windows.Controls.ItemContainerGenerator.ItemsChanged
// event.
//
// Parameters:
// e:
// A System.Collections.Specialized.NotifyCollectionChangedEventArgs that contains
// the event data.
protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e);
//
// Summary:
// Provides handling for the System.Windows.UIElement.KeyDown event that occurs
// when a key is pressed while the control has focus.
//
// Parameters:
// e:
// The event data.
[SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Straightforward switch-based key handling method that barely triggers the warning")]
protected override void OnKeyDown(KeyEventArgs e);
//
// Summary:
// Provides handling for the System.Windows.UIElement.LostFocus event.
//
// Parameters:
// e:
// The event data.
protected override void OnLostFocus(RoutedEventArgs e);
//
// Summary:
// Causes the object to scroll into view.
//
// Parameters:
// item:
// The object to scroll.
public void ScrollIntoView(object item);
//
// Summary:
// Selects all the items in the System.Windows.Controls.ListBox.
//
// Exceptions:
// System.NotSupportedException:
// System.Windows.Controls.ListBox.SelectionMode is set to System.Windows.Controls.SelectionMode.Single
public void SelectAll();
}

相比ItemsControl的极简,ListBox就要热闹多了。首先在Template中您就能看到大大的ScrollViewer的存在,原来ListBox的上下滑动就是通过ScrollViewer实现的。之前我们有个功能是要在用户上下滑动的时候触发一个事件,但如何判断ListBox控件是否在滑动呢?我们可以先来看看ScrollViewer的模板。

 // Summary:
// Represents a scrollable area that can contain other visible elements.
[TemplatePart(Name = "HorizontalScrollBar", Type = typeof(ScrollBar))]
[TemplatePart(Name = "ScrollContentPresenter", Type = typeof(ScrollContentPresenter))]
[TemplatePart(Name = "VerticalScrollBar", Type = typeof(ScrollBar))]
[TemplateVisualState(Name = "CompressionBottom", GroupName = "VerticalCompressionStates")]
[TemplateVisualState(Name = "CompressionLeft", GroupName = "HorizontalCompressionStates")]
[TemplateVisualState(Name = "CompressionRight", GroupName = "HorizontalCompressionStates")]
[TemplateVisualState(Name = "CompressionTop", GroupName = "VerticalCompressionStates")]
[TemplateVisualState(Name = "NoHorizontalCompression", GroupName = "HorizontalCompressionStates")]
[TemplateVisualState(Name = "NotScrolling", GroupName = "ScrollStates")]
[TemplateVisualState(Name = "NoVerticalCompression", GroupName = "VerticalCompressionStates")]
[TemplateVisualState(Name = "Scrolling", GroupName = "ScrollStates")]
public sealed class ScrollViewer : ContentControl

似乎我们看到了Scrolling和NotScrolling这两个VisualState,既然有这两个状态那我们就可以通过hook VisualStateGroup的CurrentStateChanging事件来实现这个功能。

 // Summary:
// Contains mutually exclusive System.Windows.VisualState objects and System.Windows.VisualTransition
// objects that are used to go from one state to another.
[ContentProperty("States", true)]
public sealed class VisualStateGroup : DependencyObject
{
// Summary:
// Initializes a new instance of the System.Windows.VisualStateGroup class.
public VisualStateGroup(); // Summary:
// Gets the most recently set System.Windows.VisualState from a successful call
// to the System.Windows.VisualStateManager.GoToState(System.Windows.Controls.Control,System.String,System.Boolean)
// method.
//
// Returns:
// The most recently set System.Windows.VisualState from a successful call to
// the System.Windows.VisualStateManager.GoToState(System.Windows.Controls.Control,System.String,System.Boolean)
// method.
public VisualState CurrentState { get; }
//
// Summary:
// Gets the name of the System.Windows.VisualStateGroup.
//
// Returns:
// The name of the System.Windows.VisualStateGroup.
public string Name { get; }
//
// Summary:
// Gets the collection of mutually exclusive System.Windows.VisualState objects.
//
// Returns:
// The collection of mutually exclusive System.Windows.VisualState objects.
public IList States { get; }
//
// Summary:
// Gets the collection of System.Windows.VisualTransition objects.
//
// Returns:
// The collection of System.Windows.VisualTransition objects.
public IList Transitions { get; } // Summary:
// Occurs after a control transitions into a different state.
public event EventHandler<VisualStateChangedEventArgs> CurrentStateChanged;
//
// Summary:
// Occurs when a control begins transitioning into a different state. public event EventHandler<VisualStateChangedEventArgs> CurrentStateChanging;
}
public static void HookScrollingEvents(DependencyObject listBox)
{
if (DesignerProperties.IsInDesignTool) return;
var scrollViewerVisualStateGroup = GetScrollStates(listBox);
if (scrollViewerVisualStateGroup != null)
{
scrollViewerVisualStateGroup.CurrentStateChanging += ScrollingStateChanging;
}
} static VisualStateGroup GetScrollStates(DependencyObject root)
{
try
{
var scrollViewer = FindItem<ScrollViewer>(root, elt => elt.Name == "ScrollViewer");
if (scrollViewer == null) return null;
var element = VisualTreeHelper.GetChild(scrollViewer, ) as FrameworkElement;
return element == null ? null : VisualStateManager.GetVisualStateGroups(element).Cast<VisualStateGroup>().SingleOrDefault(elt => elt.Name == "ScrollStates");
}
catch { }
return null;
} public static T FindItem<T>(DependencyObject parent, Func<T, bool> filter) where T : DependencyObject
{
var childCount = VisualTreeHelper.GetChildrenCount(parent);
for (var i = ; i < childCount; i++)
{
var elt = VisualTreeHelper.GetChild(parent, i);
if (elt is T && filter((T)elt)) return (T)elt;
var result = FindItem(elt, filter);
if (result != null) return result;
}
return null;
} private static void ScrollingStateChanging(object sender, VisualStateChangedEventArgs e)
{
if (e.NewState.Name == "Scrolling")
{
//your logic
}
else
{
//your logic
}
}

这招是通过大名鼎鼎的LazyListBox学来的,作者也给出了解释为什么要这样做。

        There are two reasons for doing this. The first is that you want to avoid doing any work on the UI thread while the list is being scrolled, otherwise it won't be responsive to the user's gestures or you might see blank items in your list if the UI thread (which is creating the content for the items) can't keep up with the render thread (which is animating them). The other is that you want to avoid doing any work for items that are not visible to the user (see the next section) but the computation for what is visible and what is not visible is expensive, and per the previous sentence we don't want to do that expensive work while the list is scrolling. So we need to wait for the list to stop scrolling before we can compute the visible items.

3. LongListSelector

这里要讲的LongListSelector的是SDK中原生的控件,区别于toolkit中的LongListSelector。

LongListSelector不仅具有ListBox的UI虚拟化功能,更增加了DataTemplate的重用。光从感觉上就比ListBox要瞬间高大上了许多,msdn也在不遗余力的宣传用LLS替换ListBox。替换工作很容易就能做到,之前用的也很顺手,下面我们要讲的是我在使用LLS的过程中遇到的一个bug,迫使不得不换回了原先的ListBox。

还是先来看看LLS的代码。

 // Summary:
// Displays a list of selectable items with a mechanism for users to jump to
// a specific section of the list.
[StyleTypedProperty(Property = "JumpListStyle", StyleTargetType = typeof(LongListSelector))]
[TemplatePart(Name = "VerticalScrollBar", Type = typeof(ScrollBar))]
[TemplatePart(Name = "ViewportControl", Type = typeof(ViewportControl))]
[TemplateVisualState(Name = "NotScrolling", GroupName = "ScrollStates")]
[TemplateVisualState(Name = "Scrolling", GroupName = "ScrollStates")]
public class LongListSelector : Control, INotifyPropertyChanged
{
// Summary:
// Identifies the Microsoft.Phone.Controls.LongListSelector.GridCellSize dependency
// property.
//
// Returns:
// The identifier for the Microsoft.Phone.Controls.LongListSelector.GridCellSize
// dependency property.
public static readonly DependencyProperty GridCellSizeProperty;
//
// Summary:
// Identifies the Microsoft.Phone.Controls.LongListSelector.GroupFooterTemplate
// dependency property.
//
// Returns:
// The identifier for the Microsoft.Phone.Controls.LongListSelector.GroupFooterTemplate
// dependency property.
public static readonly DependencyProperty GroupFooterTemplateProperty;
//
// Summary:
// Identifies the Microsoft.Phone.Controls.LongListSelector.GroupHeaderTemplate
// dependency property.
//
// Returns:
// The identifier for the Microsoft.Phone.Controls.LongListSelector.GroupHeaderTemplate
// dependency property.
public static readonly DependencyProperty GroupHeaderTemplateProperty;
//
// Summary:
// Identifies the Microsoft.Phone.Controls.LongListSelector.HideEmptyGroups
// dependency property.
//
// Returns:
// The identifier for the Microsoft.Phone.Controls.LongListSelector.HideEmptyGroups
// dependency property.
public static readonly DependencyProperty HideEmptyGroupsProperty;
//
// Summary:
// Identifies the Microsoft.Phone.Controls.LongListSelector.IsGroupingEnabled
// dependency property.
//
// Returns:
// The identifier for the Microsoft.Phone.Controls.LongListSelector.IsGroupingEnabled
// dependency property.
public static readonly DependencyProperty IsGroupingEnabledProperty;
//
// Summary:
// Identifies the Microsoft.Phone.Controls.LongListSelector.ItemsSource dependency
// property.
//
// Returns:
// The identifier for the Microsoft.Phone.Controls.LongListSelector.ItemsSource
// dependency property.
public static readonly DependencyProperty ItemsSourceProperty;
//
// Summary:
// Identifies the Microsoft.Phone.Controls.LongListSelector.ItemTemplate dependency
// property.
//
// Returns:
// The identifier for the Microsoft.Phone.Controls.LongListSelector.ItemTemplate
// dependency property.
public static readonly DependencyProperty ItemTemplateProperty;
//
// Summary:
// Identifies the Microsoft.Phone.Controls.LongListSelector.JumpListStyle dependency
// property.
//
// Returns:
// The identifier for the Microsoft.Phone.Controls.LongListSelector.JumpListStyle
// dependency property.
public static readonly DependencyProperty JumpListStyleProperty;
//
// Summary:
// Identifies the Microsoft.Phone.Controls.LongListSelector.ListFooter dependency
// property.
//
// Returns:
// The identifier for the Microsoft.Phone.Controls.LongListSelector.ListFooter
// dependency property.
public static readonly DependencyProperty ListFooterProperty;
//
// Summary:
// Identifies the Microsoft.Phone.Controls.LongListSelector.ListFooterTemplate
// dependency property.
//
// Returns:
// The identifier for the Microsoft.Phone.Controls.LongListSelector.ListFooterTemplate
// dependency property.
public static readonly DependencyProperty ListFooterTemplateProperty;
//
// Summary:
// Identifies the Microsoft.Phone.Controls.LongListSelector.ListHeader dependency
// property.
//
// Returns:
// The identifier for the Microsoft.Phone.Controls.LongListSelector.ListHeader
// dependency property.
public static readonly DependencyProperty ListHeaderProperty;
//
// Summary:
// Identifies the Microsoft.Phone.Controls.LongListSelector.ListHeaderTemplate
// dependency property.
//
// Returns:
// The identifier for the Microsoft.Phone.Controls.LongListSelector.ListHeaderTemplate
// dependency property.
public static readonly DependencyProperty ListHeaderTemplateProperty; // Summary:
// Initializes a new instance of the Microsoft.Phone.Controls.LongListSelector
// class.
public LongListSelector(); // Summary:
// Gets or sets the size used when displaying an item in the Microsoft.Phone.Controls.LongListSelector.
//
// Returns:
// The size used when displaying an item.
public Size GridCellSize { get; set; }
//
// Summary:
// Gets or sets the template for the group footer in the Microsoft.Phone.Controls.LongListSelector.
//
// Returns:
// The System.Windows.DataTemplate that provides the templates for the group
// footer in the Microsoft.Phone.Controls.LongListSelector.
public DataTemplate GroupFooterTemplate { get; set; }
//
// Summary:
// Gets or sets the template for the group header in the Microsoft.Phone.Controls.LongListSelector.
//
// Returns:
// The System.Windows.DataTemplate for the group header in the Microsoft.Phone.Controls.LongListSelector.
public DataTemplate GroupHeaderTemplate { get; set; }
//
// Summary:
// Gets or sets a value that indicates whether to hide empty groups in the Microsoft.Phone.Controls.LongListSelector.
//
// Returns:
// true if empty groups are hidden; otherwise false.Default is false.
public bool HideEmptyGroups { get; set; }
//
// Summary:
// Gets or sets a value that indicates whether grouping is enabled in the Microsoft.Phone.Controls.LongListSelector.
//
// Returns:
// true if grouping is enabled; otherwise false.
public bool IsGroupingEnabled { get; set; }
//
// Summary:
// Gets or sets a collection used to generate the content of the Microsoft.Phone.Controls.LongListSelector.
//
// Returns:
// A collection that is used to generate the content of the Microsoft.Phone.Controls.LongListSelector.
public IList ItemsSource { get; set; }
//
// Summary:
// Gets or sets the template for the items in the items view
//
// Returns:
// The System.Windows.DataTemplate for the items in the items view.
public DataTemplate ItemTemplate { get; set; }
//
// Summary:
// Gets or sets the System.Windows.Style for jump list in the Microsoft.Phone.Controls.LongListSelector.
//
// Returns:
// The System.Windows.Style for the jump list in the Microsoft.Phone.Controls.LongListSelector.
public Style JumpListStyle { get; set; }
//
// Summary:
// Gets or sets a value that specifies if the Microsoft.Phone.Controls.LongListSelector
// is in a list mode or grid mode from the Microsoft.Phone.Controls.LongListSelectorLayoutMode
// enum.
//
// Returns:
// A Microsoft.Phone.Controls.LongListSelectorLayoutMode value that specifies
// if the Microsoft.Phone.Controls.LongListSelector is in a list mode or grid
// mode.
public LongListSelectorLayoutMode LayoutMode { get; set; }
//
// Summary:
// Gets or sets the object that is displayed at the foot of the Microsoft.Phone.Controls.LongListSelector.
//
// Returns:
// The System.Object that is displayed at the foot of the Microsoft.Phone.Controls.LongListSelector.
public object ListFooter { get; set; }
//
// Summary:
// Gets or sets the System.Windows.DataTemplatefor an item to display at the
// foot of the Microsoft.Phone.Controls.LongListSelector.
//
// Returns:
// The System.Windows.DataTemplate for an item to display at the foot of the
// Microsoft.Phone.Controls.LongListSelector.
public DataTemplate ListFooterTemplate { get; set; }
//
// Summary:
// Gets or sets the object to display at the head of the Microsoft.Phone.Controls.LongListSelector.
//
// Returns:
// The System.Object that is displayed at the head of the Microsoft.Phone.Controls.LongListSelector.
public object ListHeader { get; set; }
//
// Summary:
// Gets or sets the System.Windows.DataTemplatefor an item to display at the
// head of the Microsoft.Phone.Controls.LongListSelector.
//
// Returns:
// The System.Windows.DataTemplate for an item to display at the head of the
// Microsoft.Phone.Controls.LongListSelector.
public DataTemplate ListHeaderTemplate { get; set; }
//
// Summary:
// Gets the state of manipulation handling on the Microsoft.Phone.Controls.LongListSelector
// control.
//
// Returns:
// The state of manipulation handling on the Microsoft.Phone.Controls.LongListSelector
// control.
public ManipulationState ManipulationState { get; }
//
// Summary:
// Gets the currently selected item in the Microsoft.Phone.Controls.LongListSelector.
//
// Returns:
// The System.Object that represents the currently selected item in the Microsoft.Phone.Controls.LongListSelector.
public object SelectedItem { get; set; } // Summary:
// Occurs when a new item is realized.
public event EventHandler<ItemRealizationEventArgs> ItemRealized;
//
// Summary:
// Occurs when an item in the Microsoft.Phone.Controls.LongListSelector is unrealized.
public event EventHandler<ItemRealizationEventArgs> ItemUnrealized;
//
// Summary:
// Occurs when the jump list is closed.
public event EventHandler JumpListClosed;
//
// Summary:
// Occurs when a jump list is opened.
public event EventHandler JumpListOpening;
//
// Summary:
// Occurs when Microsoft.Phone.Controls.ManipulationState changes.
public event EventHandler ManipulationStateChanged;
//
// Summary:
// Occurs when a property value changes.
public event PropertyChangedEventHandler PropertyChanged;
//
// Summary:
// Occurs when the currently selected item changes.
public event SelectionChangedEventHandler SelectionChanged; // Summary:
// Provides the behavior for the Measure pass of layout.
//
// Parameters:
// availableSize:
// The available size that this object can give to child objects. Infinity (System.Double.PositiveInfinity)
// can be specified as a value to indicate that the object will size to whatever
// content is available.
//
// Returns:
// The size that this object determines it needs during layout, based on its
// calculations of the allocated sizes for child objects; or based on other
// considerations, such as a fixed container size.
protected override Size MeasureOverride(Size availableSize);
//
// Summary:
// Builds the visual tree for the Microsoft.Phone.Controls.LongListSelector
// control when a new template is applied.
public override void OnApplyTemplate();
//
// Summary:
// Scrolls to a specified item in the Microsoft.Phone.Controls.LongListSelector.
//
// Parameters:
// item:
// The list item to scroll to.
public void ScrollTo(object item);
}

直接暴露了Scrolling和NoteScrolling这两个状态在外面,可滑动区域的控件也从ScrollViewer换成了ViewportControl,一切看起来都是这么的自然,以至于感觉和ListBox没有太大的区别,下面我们就来看看最近遇到的这个bug。先看下面的代码。

 <Grid x:Name="ContentPanel" Grid.Row="" Margin="12,0,12,0">
<phone:LongListSelector x:Name="list" SelectionChanged="list_SelectionChanged" />
</Grid> using System;
using System.Windows;
using System.Windows.Controls;
using System.Collections.ObjectModel;
using Microsoft.Phone.Controls; namespace ListCheck
{
public partial class MainPage : PhoneApplicationPage
{
ObservableCollection<string> clist; // Constructor
public MainPage()
{
InitializeComponent(); clist = new ObservableCollection<string>(); clist.Add("");
clist.Add("");
clist.Add("");
clist.Add("");
clist.Add("");
clist.Add("");
clist.Add("");
clist.Add(""); list.ItemsSource = clist;
} private void list_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
clist.Remove((string)e.AddedItems[]);
}
}
}

代码应该很好理解,就是在页面放一个LLS控件,然后通过后台绑定数据,然后通过SelectionChanged事件来删除Item。如果这个时候我们从上面一个一个往下删除的时候就会发生一个意想不到的Exception,System.ArgumentException: Value does not fall within the expected range. 如果你按照上面的步骤做了,相信会很好复现。

通过Google了这个问题之后,发现这个bug不光是我一个人碰到,很多人也有同样的问题。Exception是通过LLS的MeasureOverride方法抛出来的,解决办法是通过重写MeasureOverride方法,并try/catch来规避原有的问题。看似是可以解决这个问题,但在实际应用过程中try/catch之后的界面很大几率被打乱,所以这个方法行不通。

 public class LongListSelectorEx : LongListSelector
{ protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize)
{
try
{
return base.MeasureOverride(availableSize);
}
catch (ArgumentException)
{
return this.DesiredSize;
}
}
}

还有一种解决办法是通过设置LLS的ItemsSource,在删除Item之前先把ItemsSource设成null,删除之后再重新赋值,这个方法可以解决这个问题,但在数据量很大的时候这种做法显然很鸡肋。后来无奈还是换回到ListBox。

 list.ItemsSource = null;
clist.Remove((string)e.AddedItems[]);
list.ItemsSource = clist;
  • 总结

WP提供了三种集合控件,存在即合理嘛,总有一种适合您的需求。希望您看完之后能像耐心挑选数据结构一样,耐心选择使用哪种集合控件。

这篇博客讲的东西都很基础,其实我觉得基础很重要。相比之下见到了不少人上来就是,大数据,多少多少万用户量。这些都是不切实际的做法,基础的东西是最容易被忽略的,我相信大部分问题的产生都是因为基础不扎实。只有积跬步才能至千里。