- 前言
Windows Phone开发过程中不可避免的就是和集合数据打交道,如果之前做过WP App的开发的话,相信你已经看过了各种集合控件的使用、扩展和自定义。这些个内容在这篇博客里都没有,那么我们今天说点儿什么呢。当然也还是围绕WP的集合控件,要不然就和本文的题目不相符了,这篇博客主要讲集合控件的一些基础知识和在使用它们的过程中遇到的种种问题。WP中总共有三种集合控件,分别是ItemsControl、ListBox、LongListSelector。虽然都是集合控件,但它们的出场率绝对有着天壤之别,那么就一一说说它们的异同吧。
- ItemsControl
相信ItemsControl应该算是出场率最低的集合控件了,以至于很多人都不知道有这么个控件的存在。这也不足为其,因为在各大官方非官方的Data Binding的例子中都见不到他的身影,大大降低了直接Copy
Paste的概率。当然不能说那些个写Demo的人偏心,确实是由于ItemsControl本身的轻量级和功能简单造成的。
先来看看ItemsControl的代码。
1 // Summary: 2 // Represents a control that can be used to present a collection of items. 3 [ContentProperty("Items", true)] 4 public class ItemsControl : Control 5 { 6 // Summary: 7 // Identifies the System.Windows.Controls.ItemsControl.DisplayMemberPath dependency 8 // property. 9 // 10 // Returns: 11 // The identifier for the System.Windows.Controls.ItemsControl.DisplayMemberPath 12 // dependency property. 13 public static readonly DependencyProperty DisplayMemberPathProperty; 14 // 15 // Summary: 16 // Identifies the System.Windows.Controls.ItemsControl.ItemsPanel dependency 17 // property. 18 // 19 // Returns: 20 // The identifier for the System.Windows.Controls.ItemsControl.ItemsPanel dependency 21 // property. 22 public static readonly DependencyProperty ItemsPanelProperty; 23 // 24 // Summary: 25 // Identifies the System.Windows.Controls.ItemsControl.ItemsSource dependency 26 // property. 27 // 28 // Returns: 29 // The identifier for the System.Windows.Controls.ItemsControl.ItemsSource dependency 30 // property. 31 public static readonly DependencyProperty ItemsSourceProperty; 32 // 33 // Summary: 34 // Identifies the System.Windows.Controls.ItemsControl.ItemTemplate dependency 35 // property. 36 // 37 // Returns: 38 // The identifier for the System.Windows.Controls.ItemsControl.ItemTemplate 39 // dependency property. 40 public static readonly DependencyProperty ItemTemplateProperty; 41 42 // Summary: 43 // Initializes a new instance of the System.Windows.Controls.ItemsControl class. 44 public ItemsControl(); 45 46 // Summary: 47 // Gets or sets the name or path of the property that is displayed for each 48 // data item. 49 // 50 // Returns: 51 // The name or path of the property that is displayed for each the data item 52 // in the control. The default is an empty string (""). 53 public string DisplayMemberPath { get; set; } 54 // 55 // Summary: 56 // Gets the System.Windows.Controls.ItemContainerGenerator associated with this 57 // System.Windows.Controls.ItemsControl. 58 // 59 // Returns: 60 // The System.Windows.Controls.ItemContainerGenerator associated with this System.Windows.Controls.ItemsControl. 61 public ItemContainerGenerator ItemContainerGenerator { get; } 62 // 63 // Summary: 64 // Gets the collection used to generate the content of the control. 65 // 66 // Returns: 67 // The collection that is used to generate the content of the control, if it 68 // exists; otherwise, null. The default is an empty collection. 69 public ItemCollection Items { get; } 70 // 71 // Summary: 72 // Gets or sets the template that defines the panel that controls the layout 73 // of items. 74 // 75 // Returns: 76 // An System.Windows.Controls.ItemsPanelTemplate that defines the panel to use 77 // for the layout of the items. The default value for the System.Windows.Controls.ItemsControl 78 // is an System.Windows.Controls.ItemsPanelTemplate that specifies a System.Windows.Controls.StackPanel. 79 public ItemsPanelTemplate ItemsPanel { get; set; } 80 // 81 // Summary: 82 // Gets or sets a collection used to generate the content of the System.Windows.Controls.ItemsControl. 83 // 84 // Returns: 85 // The object that is used to generate the content of the System.Windows.Controls.ItemsControl. 86 // The default is null. 87 public IEnumerable ItemsSource { get; set; } 88 // 89 // Summary: 90 // Gets or sets the System.Windows.DataTemplate used to display each item. 91 // 92 // Returns: 93 // The template that specifies the visualization of the data objects. The default 94 // is null. 95 public DataTemplate ItemTemplate { get; set; } 96 97 // Summary: 98 // Undoes the effects of the System.Windows.Controls.ItemsControl.PrepareContainerForItemOverride(System.Windows.DependencyObject,System.Object) 99 // method. 100 // 101 // Parameters: 102 // element: 103 // The container element. 104 // 105 // item: 106 // The item. 107 protected virtual void ClearContainerForItemOverride(DependencyObject element, object item); 108 // 109 // Summary: 110 // Creates or identifies the element that is used to display the given item. 111 // 112 // Returns: 113 // The element that is used to display the given item. 114 protected virtual DependencyObject GetContainerForItemOverride(); 115 // 116 // Summary: 117 // Returns the System.Windows.Controls.ItemsControl that the specified element 118 // hosts items for. 119 // 120 // Parameters: 121 // element: 122 // The host element. 123 // 124 // Returns: 125 // The System.Windows.Controls.ItemsControl that the specified element hosts 126 // items for, or null. 127 public static ItemsControl GetItemsOwner(DependencyObject element); 128 // 129 // Summary: 130 // Determines if the specified item is (or is eligible to be) its own container. 131 // 132 // Parameters: 133 // item: 134 // The item to check. 135 // 136 // Returns: 137 // true if the item is (or is eligible to be) its own container; otherwise, 138 // false. 139 protected virtual bool IsItemItsOwnContainerOverride(object item); 140 // 141 // Summary: 142 // Returns the System.Windows.Controls.ItemsControl that owns the specified 143 // container element. 144 // 145 // Parameters: 146 // container: 147 // The container element to return the System.Windows.Controls.ItemsControl 148 // for. 149 // 150 // Returns: 151 // The System.Windows.Controls.ItemsControl that owns the specified container 152 // element; otherwise, null. The System.Windows.Controls.ItemsControl.ItemsControlFromItemContainer(System.Windows.DependencyObject) 153 // method returns null if container is not a System.Windows.UIElement or the 154 // parent is not an System.Windows.Controls.ItemsControl. 155 public static ItemsControl ItemsControlFromItemContainer(DependencyObject container); 156 // 157 // Summary: 158 // Called when the value of the System.Windows.Controls.ItemsControl.Items property 159 // changes. 160 // 161 // Parameters: 162 // e: 163 // A System.Collections.Specialized.NotifyCollectionChangedEventArgs that contains 164 // the event data 165 protected virtual void OnItemsChanged(NotifyCollectionChangedEventArgs e); 166 // 167 // Summary: 168 // Prepares the specified element to display the specified item. 169 // 170 // Parameters: 171 // element: 172 // The element used to display the specified item. 173 // 174 // item: 175 // The item to display. 176 protected virtual void PrepareContainerForItemOverride(DependencyObject element, object item); 177 }
没有眼花缭乱的Template,也没有千变万化的VisualStateGroup,感觉完全就是一个基类的命,确实他也是后面要提到的ListBox的基类。但存在即合理嘛,虽然他没有UI虚拟化的功能,但已其轻量的优势完全可以胜任既定的小规模的数据绑定的工作,如果在配合ScrollViewer使用,效果完全不输给后面的两种集合控件。
Note: ItemsControl 是不具有UI虚拟化功能的,这就意味着您绑定的Data会一次性的全Load出来,您可以在ItemTemplate的任意控件上加个Loaded事件,看看output就会看到您所绑定的数据一个一个的被加载出来,即使他们没有显示在屏幕上也是预先加载到了内存中,这绝对不是一个小的开销,所以说ItemsControl只适合做一些小量的数据绑定工作。
2. ListBox
ListBox绝对算是WP中的集合控件的主角,在无数的下拉刷新、滑动刷新、控件扩展中绝对是少补了ListBox,在大数据量的数据绑定中ListBox绝对是出尽了风头,配合VirtualizingStackPanel使用,UI虚拟化绝对让你的应用变成360°无死角顺畅。但这些都不是我们要说的内容。
还是先来看一下ListBox的代码。
1 // Summary: 2 // Contains a list of selectable items. 3 [TemplatePart(Name = "ScrollViewer", Type = typeof(ScrollViewer))] 4 [TemplateVisualState(Name = "InvalidFocused", GroupName = "ValidationStates")] 5 [TemplateVisualState(Name = "InvalidUnfocused", GroupName = "ValidationStates")] 6 [TemplateVisualState(Name = "Valid", GroupName = "ValidationStates")] 7 public class ListBox : Selector 8 { 9 // Summary: 10 // Identifies the IsSelectionActive dependency property. 11 // 12 // Returns: 13 // The identifier for the IsSelectionActive dependency property. 14 public static readonly DependencyProperty IsSelectionActiveProperty; 15 // 16 // Summary: 17 // Identifies the System.Windows.Controls.ListBox.ItemContainerStyle dependency 18 // property. 19 // 20 // Returns: 21 // The identifier for the System.Windows.Controls.ListBox.ItemContainerStyle 22 // dependency property. 23 public static readonly DependencyProperty ItemContainerStyleProperty; 24 // 25 // Summary: 26 // Identifies the System.Windows.Controls.ListBox.SelectionMode dependency property. 27 // 28 // Returns: 29 // The identifier for the System.Windows.Controls.ListBox.SelectionMode dependency 30 // property. 31 public static readonly DependencyProperty SelectionModeProperty; 32 33 // Summary: 34 // Initializes a new instance of the System.Windows.Controls.ListBox class. 35 public ListBox(); 36 37 // Summary: 38 // Gets or sets the style that is used when rendering the item containers. 39 // 40 // Returns: 41 // The style applied to the item containers. The default is null. 42 public Style ItemContainerStyle { get; set; } 43 // 44 // Summary: 45 // Gets the list of currently selected items for the System.Windows.Controls.ListBox 46 // control. 47 // 48 // Returns: 49 // The list of currently selected items for the System.Windows.Controls.ListBox. 50 public IList SelectedItems { get; } 51 // 52 // Summary: 53 // Gets or sets the selection behavior for the System.Windows.Controls.ListBox 54 // control. 55 // 56 // Returns: 57 // One of the System.Windows.Controls.SelectionMode values. 58 public SelectionMode SelectionMode { get; set; } 59 60 // Summary: 61 // Creates or identifies the element used to display a specified item. 62 // 63 // Returns: 64 // A System.Windows.Controls.ListBoxItem corresponding to a specified item. 65 protected override DependencyObject GetContainerForItemOverride(); 66 // 67 // Summary: 68 // Determines if the specified item is (or is eligible to be) its own item container. 69 // 70 // Parameters: 71 // item: 72 // The specified item. 73 // 74 // Returns: 75 // true if the item is its own item container; otherwise, false. 76 protected override bool IsItemItsOwnContainerOverride(object item); 77 // 78 // Summary: 79 // Builds the visual tree for the System.Windows.Controls.ListBox control when 80 // a new template is applied. 81 public override void OnApplyTemplate(); 82 // 83 // Summary: 84 // Returns a System.Windows.Automation.Peers.ListBoxAutomationPeer for the Windows Phone 85 // automation infrastructure. 86 // 87 // Returns: 88 // A System.Windows.Automation.Peers.ListBoxAutomationPeer for the System.Windows.Controls.ListBox 89 // object. 90 protected override AutomationPeer OnCreateAutomationPeer(); 91 // 92 // Summary: 93 // Provides handling for the System.Windows.UIElement.GotFocus event. 94 // 95 // Parameters: 96 // e: 97 // The event data. 98 protected override void OnGotFocus(RoutedEventArgs e); 99 // 100 // Summary: 101 // Provides handling for the System.Windows.Controls.ItemContainerGenerator.ItemsChanged 102 // event. 103 // 104 // Parameters: 105 // e: 106 // A System.Collections.Specialized.NotifyCollectionChangedEventArgs that contains 107 // the event data. 108 protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e); 109 // 110 // Summary: 111 // Provides handling for the System.Windows.UIElement.KeyDown event that occurs 112 // when a key is pressed while the control has focus. 113 // 114 // Parameters: 115 // e: 116 // The event data. 117 [SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity", Justification = "Straightforward switch-based key handling method that barely triggers the warning")] 118 protected override void OnKeyDown(KeyEventArgs e); 119 // 120 // Summary: 121 // Provides handling for the System.Windows.UIElement.LostFocus event. 122 // 123 // Parameters: 124 // e: 125 // The event data. 126 protected override void OnLostFocus(RoutedEventArgs e); 127 // 128 // Summary: 129 // Causes the object to scroll into view. 130 // 131 // Parameters: 132 // item: 133 // The object to scroll. 134 public void ScrollIntoView(object item); 135 // 136 // Summary: 137 // Selects all the items in the System.Windows.Controls.ListBox. 138 // 139 // Exceptions: 140 // System.NotSupportedException: 141 // System.Windows.Controls.ListBox.SelectionMode is set to System.Windows.Controls.SelectionMode.Single 142 public void SelectAll(); 143 }
相比ItemsControl的极简,ListBox就要热闹多了。首先在Template中您就能看到大大的ScrollViewer的存在,原来ListBox的上下滑动就是通过ScrollViewer实现的。之前我们有个功能是要在用户上下滑动的时候触发一个事件,但如何判断ListBox控件是否在滑动呢?我们可以先来看看ScrollViewer的模板。
1 // Summary: 2 // Represents a scrollable area that can contain other visible elements. 3 [TemplatePart(Name = "HorizontalScrollBar", Type = typeof(ScrollBar))] 4 [TemplatePart(Name = "ScrollContentPresenter", Type = typeof(ScrollContentPresenter))] 5 [TemplatePart(Name = "VerticalScrollBar", Type = typeof(ScrollBar))] 6 [TemplateVisualState(Name = "CompressionBottom", GroupName = "VerticalCompressionStates")] 7 [TemplateVisualState(Name = "CompressionLeft", GroupName = "HorizontalCompressionStates")] 8 [TemplateVisualState(Name = "CompressionRight", GroupName = "HorizontalCompressionStates")] 9 [TemplateVisualState(Name = "CompressionTop", GroupName = "VerticalCompressionStates")] 10 [TemplateVisualState(Name = "NoHorizontalCompression", GroupName = "HorizontalCompressionStates")] 11 [TemplateVisualState(Name = "NotScrolling", GroupName = "ScrollStates")] 12 [TemplateVisualState(Name = "NoVerticalCompression", GroupName = "VerticalCompressionStates")] 13 [TemplateVisualState(Name = "Scrolling", GroupName = "ScrollStates")] 14 public sealed class ScrollViewer : ContentControl
似乎我们看到了Scrolling和NotScrolling这两个VisualState,既然有这两个状态那我们就可以通过hook VisualStateGroup的CurrentStateChanging事件来实现这个功能。
1 // Summary: 2 // Contains mutually exclusive System.Windows.VisualState objects and System.Windows.VisualTransition 3 // objects that are used to go from one state to another. 4 [ContentProperty("States", true)] 5 public sealed class VisualStateGroup : DependencyObject 6 { 7 // Summary: 8 // Initializes a new instance of the System.Windows.VisualStateGroup class. 9 public VisualStateGroup(); 10 11 // Summary: 12 // Gets the most recently set System.Windows.VisualState from a successful call 13 // to the System.Windows.VisualStateManager.GoToState(System.Windows.Controls.Control,System.String,System.Boolean) 14 // method. 15 // 16 // Returns: 17 // The most recently set System.Windows.VisualState from a successful call to 18 // the System.Windows.VisualStateManager.GoToState(System.Windows.Controls.Control,System.String,System.Boolean) 19 // method. 20 public VisualState CurrentState { get; } 21 // 22 // Summary: 23 // Gets the name of the System.Windows.VisualStateGroup. 24 // 25 // Returns: 26 // The name of the System.Windows.VisualStateGroup. 27 public string Name { get; } 28 // 29 // Summary: 30 // Gets the collection of mutually exclusive System.Windows.VisualState objects. 31 // 32 // Returns: 33 // The collection of mutually exclusive System.Windows.VisualState objects. 34 public IList States { get; } 35 // 36 // Summary: 37 // Gets the collection of System.Windows.VisualTransition objects. 38 // 39 // Returns: 40 // The collection of System.Windows.VisualTransition objects. 41 public IList Transitions { get; } 42 43 // Summary: 44 // Occurs after a control transitions into a different state. 45 public event EventHandler<VisualStateChangedEventArgs> CurrentStateChanged; 46 // 47 // Summary: 48 // Occurs when a control begins transitioning into a different state. 49 50 public event EventHandler<VisualStateChangedEventArgs> CurrentStateChanging; 51 } 52 public static void HookScrollingEvents(DependencyObject listBox) 53 { 54 if (DesignerProperties.IsInDesignTool) return; 55 var scrollViewerVisualStateGroup = GetScrollStates(listBox); 56 if (scrollViewerVisualStateGroup != null) 57 { 58 scrollViewerVisualStateGroup.CurrentStateChanging += ScrollingStateChanging; 59 } 60 } 61 62 static VisualStateGroup GetScrollStates(DependencyObject root) 63 { 64 try 65 { 66 var scrollViewer = FindItem<ScrollViewer>(root, elt => elt.Name == "ScrollViewer"); 67 if (scrollViewer == null) return null; 68 var element = VisualTreeHelper.GetChild(scrollViewer, 0) as FrameworkElement; 69 return element == null ? null : VisualStateManager.GetVisualStateGroups(element).Cast<VisualStateGroup>().SingleOrDefault(elt => elt.Name == "ScrollStates"); 70 } 71 catch { } 72 return null; 73 } 74 75 public static T FindItem<T>(DependencyObject parent, Func<T, bool> filter) where T : DependencyObject 76 { 77 var childCount = VisualTreeHelper.GetChildrenCount(parent); 78 for (var i = 0; i < childCount; i++) 79 { 80 var elt = VisualTreeHelper.GetChild(parent, i); 81 if (elt is T && filter((T)elt)) return (T)elt; 82 var result = FindItem(elt, filter); 83 if (result != null) return result; 84 } 85 return null; 86 } 87 88 private static void ScrollingStateChanging(object sender, VisualStateChangedEventArgs e) 89 { 90 if (e.NewState.Name == "Scrolling") 91 { 92 //your logic 93 } 94 else 95 { 96 //your logic 97 } 98 }
这招是通过大名鼎鼎的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的代码。
1 // Summary: 2 // Displays a list of selectable items with a mechanism for users to jump to 3 // a specific section of the list. 4 [StyleTypedProperty(Property = "JumpListStyle", StyleTargetType = typeof(LongListSelector))] 5 [TemplatePart(Name = "VerticalScrollBar", Type = typeof(ScrollBar))] 6 [TemplatePart(Name = "ViewportControl", Type = typeof(ViewportControl))] 7 [TemplateVisualState(Name = "NotScrolling", GroupName = "ScrollStates")] 8 [TemplateVisualState(Name = "Scrolling", GroupName = "ScrollStates")] 9 public class LongListSelector : Control, INotifyPropertyChanged 10 { 11 // Summary: 12 // Identifies the Microsoft.Phone.Controls.LongListSelector.GridCellSize dependency 13 // property. 14 // 15 // Returns: 16 // The identifier for the Microsoft.Phone.Controls.LongListSelector.GridCellSize 17 // dependency property. 18 public static readonly DependencyProperty GridCellSizeProperty; 19 // 20 // Summary: 21 // Identifies the Microsoft.Phone.Controls.LongListSelector.GroupFooterTemplate 22 // dependency property. 23 // 24 // Returns: 25 // The identifier for the Microsoft.Phone.Controls.LongListSelector.GroupFooterTemplate 26 // dependency property. 27 public static readonly DependencyProperty GroupFooterTemplateProperty; 28 // 29 // Summary: 30 // Identifies the Microsoft.Phone.Controls.LongListSelector.GroupHeaderTemplate 31 // dependency property. 32 // 33 // Returns: 34 // The identifier for the Microsoft.Phone.Controls.LongListSelector.GroupHeaderTemplate 35 // dependency property. 36 public static readonly DependencyProperty GroupHeaderTemplateProperty; 37 // 38 // Summary: 39 // Identifies the Microsoft.Phone.Controls.LongListSelector.HideEmptyGroups 40 // dependency property. 41 // 42 // Returns: 43 // The identifier for the Microsoft.Phone.Controls.LongListSelector.HideEmptyGroups 44 // dependency property. 45 public static readonly DependencyProperty HideEmptyGroupsProperty; 46 // 47 // Summary: 48 // Identifies the Microsoft.Phone.Controls.LongListSelector.IsGroupingEnabled 49 // dependency property. 50 // 51 // Returns: 52 // The identifier for the Microsoft.Phone.Controls.LongListSelector.IsGroupingEnabled 53 // dependency property. 54 public static readonly DependencyProperty IsGroupingEnabledProperty; 55 // 56 // Summary: 57 // Identifies the Microsoft.Phone.Controls.LongListSelector.ItemsSource dependency 58 // property. 59 // 60 // Returns: 61 // The identifier for the Microsoft.Phone.Controls.LongListSelector.ItemsSource 62 // dependency property. 63 public static readonly DependencyProperty ItemsSourceProperty; 64 // 65 // Summary: 66 // Identifies the Microsoft.Phone.Controls.LongListSelector.ItemTemplate dependency 67 // property. 68 // 69 // Returns: 70 // The identifier for the Microsoft.Phone.Controls.LongListSelector.ItemTemplate 71 // dependency property. 72 public static readonly DependencyProperty ItemTemplateProperty; 73 // 74 // Summary: 75 // Identifies the Microsoft.Phone.Controls.LongListSelector.JumpListStyle dependency 76 // property. 77 // 78 // Returns: 79 // The identifier for the Microsoft.Phone.Controls.LongListSelector.JumpListStyle 80 // dependency property. 81 public static readonly DependencyProperty JumpListStyleProperty; 82 // 83 // Summary: 84 // Identifies the Microsoft.Phone.Controls.LongListSelector.ListFooter dependency 85 // property. 86 // 87 // Returns: 88 // The identifier for the Microsoft.Phone.Controls.LongListSelector.ListFooter 89 // dependency property. 90 public static readonly DependencyProperty ListFooterProperty; 91 // 92 // Summary: 93 // Identifies the Microsoft.Phone.Controls.LongListSelector.ListFooterTemplate 94 // dependency property. 95 // 96 // Returns: 97 // The identifier for the Microsoft.Phone.Controls.LongListSelector.ListFooterTemplate 98 // dependency property. 99 public static readonly DependencyProperty ListFooterTemplateProperty; 100 // 101 // Summary: 102 // Identifies the Microsoft.Phone.Controls.LongListSelector.ListHeader dependency 103 // property. 104 // 105 // Returns: 106 // The identifier for the Microsoft.Phone.Controls.LongListSelector.ListHeader 107 // dependency property. 108 public static readonly DependencyProperty ListHeaderProperty; 109 // 110 // Summary: 111 // Identifies the Microsoft.Phone.Controls.LongListSelector.ListHeaderTemplate 112 // dependency property. 113 // 114 // Returns: 115 // The identifier for the Microsoft.Phone.Controls.LongListSelector.ListHeaderTemplate 116 // dependency property. 117 public static readonly DependencyProperty ListHeaderTemplateProperty; 118 119 // Summary: 120 // Initializes a new instance of the Microsoft.Phone.Controls.LongListSelector 121 // class. 122 public LongListSelector(); 123 124 // Summary: 125 // Gets or sets the size used when displaying an item in the Microsoft.Phone.Controls.LongListSelector. 126 // 127 // Returns: 128 // The size used when displaying an item. 129 public Size GridCellSize { get; set; } 130 // 131 // Summary: 132 // Gets or sets the template for the group footer in the Microsoft.Phone.Controls.LongListSelector. 133 // 134 // Returns: 135 // The System.Windows.DataTemplate that provides the templates for the group 136 // footer in the Microsoft.Phone.Controls.LongListSelector. 137 public DataTemplate GroupFooterTemplate { get; set; } 138 // 139 // Summary: 140 // Gets or sets the template for the group header in the Microsoft.Phone.Controls.LongListSelector. 141 // 142 // Returns: 143 // The System.Windows.DataTemplate for the group header in the Microsoft.Phone.Controls.LongListSelector. 144 public DataTemplate GroupHeaderTemplate { get; set; } 145 // 146 // Summary: 147 // Gets or sets a value that indicates whether to hide empty groups in the Microsoft.Phone.Controls.LongListSelector. 148 // 149 // Returns: 150 // true if empty groups are hidden; otherwise false.Default is false. 151 public bool HideEmptyGroups { get; set; } 152 // 153 // Summary: 154 // Gets or sets a value that indicates whether grouping is enabled in the Microsoft.Phone.Controls.LongListSelector. 155 // 156 // Returns: 157 // true if grouping is enabled; otherwise false. 158 public bool IsGroupingEnabled { get; set; } 159 // 160 // Summary: 161 // Gets or sets a collection used to generate the content of the Microsoft.Phone.Controls.LongListSelector. 162 // 163 // Returns: 164 // A collection that is used to generate the content of the Microsoft.Phone.Controls.LongListSelector. 165 public IList ItemsSource { get; set; } 166 // 167 // Summary: 168 // Gets or sets the template for the items in the items view 169 // 170 // Returns: 171 // The System.Windows.DataTemplate for the items in the items view. 172 public DataTemplate ItemTemplate { get; set; } 173 // 174 // Summary: 175 // Gets or sets the System.Windows.Style for jump list in the Microsoft.Phone.Controls.LongListSelector. 176 // 177 // Returns: 178 // The System.Windows.Style for the jump list in the Microsoft.Phone.Controls.LongListSelector. 179 public Style JumpListStyle { get; set; } 180 // 181 // Summary: 182 // Gets or sets a value that specifies if the Microsoft.Phone.Controls.LongListSelector 183 // is in a list mode or grid mode from the Microsoft.Phone.Controls.LongListSelectorLayoutMode 184 // enum. 185 // 186 // Returns: 187 // A Microsoft.Phone.Controls.LongListSelectorLayoutMode value that specifies 188 // if the Microsoft.Phone.Controls.LongListSelector is in a list mode or grid 189 // mode. 190 public LongListSelectorLayoutMode LayoutMode { get; set; } 191 // 192 // Summary: 193 // Gets or sets the object that is displayed at the foot of the Microsoft.Phone.Controls.LongListSelector. 194 // 195 // Returns: 196 // The System.Object that is displayed at the foot of the Microsoft.Phone.Controls.LongListSelector. 197 public object ListFooter { get; set; } 198 // 199 // Summary: 200 // Gets or sets the System.Windows.DataTemplatefor an item to display at the 201 // foot of the Microsoft.Phone.Controls.LongListSelector. 202 // 203 // Returns: 204 // The System.Windows.DataTemplate for an item to display at the foot of the 205 // Microsoft.Phone.Controls.LongListSelector. 206 public DataTemplate ListFooterTemplate { get; set; } 207 // 208 // Summary: 209 // Gets or sets the object to display at the head of the Microsoft.Phone.Controls.LongListSelector. 210 // 211 // Returns: 212 // The System.Object that is displayed at the head of the Microsoft.Phone.Controls.LongListSelector. 213 public object ListHeader { get; set; } 214 // 215 // Summary: 216 // Gets or sets the System.Windows.DataTemplatefor an item to display at the 217 // head of the Microsoft.Phone.Controls.LongListSelector. 218 // 219 // Returns: 220 // The System.Windows.DataTemplate for an item to display at the head of the 221 // Microsoft.Phone.Controls.LongListSelector. 222 public DataTemplate ListHeaderTemplate { get; set; } 223 // 224 // Summary: 225 // Gets the state of manipulation handling on the Microsoft.Phone.Controls.LongListSelector 226 // control. 227 // 228 // Returns: 229 // The state of manipulation handling on the Microsoft.Phone.Controls.LongListSelector 230 // control. 231 public ManipulationState ManipulationState { get; } 232 // 233 // Summary: 234 // Gets the currently selected item in the Microsoft.Phone.Controls.LongListSelector. 235 // 236 // Returns: 237 // The System.Object that represents the currently selected item in the Microsoft.Phone.Controls.LongListSelector. 238 public object SelectedItem { get; set; } 239 240 // Summary: 241 // Occurs when a new item is realized. 242 public event EventHandler<ItemRealizationEventArgs> ItemRealized; 243 // 244 // Summary: 245 // Occurs when an item in the Microsoft.Phone.Controls.LongListSelector is unrealized. 246 public event EventHandler<ItemRealizationEventArgs> ItemUnrealized; 247 // 248 // Summary: 249 // Occurs when the jump list is closed. 250 public event EventHandler JumpListClosed; 251 // 252 // Summary: 253 // Occurs when a jump list is opened. 254 public event EventHandler JumpListOpening; 255 // 256 // Summary: 257 // Occurs when Microsoft.Phone.Controls.ManipulationState changes. 258 public event EventHandler ManipulationStateChanged; 259 // 260 // Summary: 261 // Occurs when a property value changes. 262 public event PropertyChangedEventHandler PropertyChanged; 263 // 264 // Summary: 265 // Occurs when the currently selected item changes. 266 public event SelectionChangedEventHandler SelectionChanged; 267 268 // Summary: 269 // Provides the behavior for the Measure pass of layout. 270 // 271 // Parameters: 272 // availableSize: 273 // The available size that this object can give to child objects. Infinity (System.Double.PositiveInfinity) 274 // can be specified as a value to indicate that the object will size to whatever 275 // content is available. 276 // 277 // Returns: 278 // The size that this object determines it needs during layout, based on its 279 // calculations of the allocated sizes for child objects; or based on other 280 // considerations, such as a fixed container size. 281 protected override Size MeasureOverride(Size availableSize); 282 // 283 // Summary: 284 // Builds the visual tree for the Microsoft.Phone.Controls.LongListSelector 285 // control when a new template is applied. 286 public override void OnApplyTemplate(); 287 // 288 // Summary: 289 // Scrolls to a specified item in the Microsoft.Phone.Controls.LongListSelector. 290 // 291 // Parameters: 292 // item: 293 // The list item to scroll to. 294 public void ScrollTo(object item); 295 }
直接暴露了Scrolling和NoteScrolling这两个状态在外面,可滑动区域的控件也从ScrollViewer换成了ViewportControl,一切看起来都是这么的自然,以至于感觉和ListBox没有太大的区别,下面我们就来看看最近遇到的这个bug。先看下面的代码。
1 <Grid x:Name="ContentPanel" Grid.Row="1" Margin="12,0,12,0"> 2 <phone:LongListSelector x:Name="list" SelectionChanged="list_SelectionChanged" /> 3 </Grid> 4 5 using System; 6 using System.Windows; 7 using System.Windows.Controls; 8 using System.Collections.ObjectModel; 9 using Microsoft.Phone.Controls; 10 11 namespace ListCheck 12 { 13 public partial class MainPage : PhoneApplicationPage 14 { 15 ObservableCollection<string> clist; 16 17 // Constructor 18 public MainPage() 19 { 20 InitializeComponent(); 21 22 clist = new ObservableCollection<string>(); 23 24 clist.Add("1"); 25 clist.Add("2"); 26 clist.Add("3"); 27 clist.Add("4"); 28 clist.Add("5"); 29 clist.Add("6"); 30 clist.Add("7"); 31 clist.Add("8"); 32 33 list.ItemsSource = clist; 34 } 35 36 private void list_SelectionChanged(object sender, SelectionChangedEventArgs e) 37 { 38 clist.Remove((string)e.AddedItems[0]); 39 } 40 } 41 }
代码应该很好理解,就是在页面放一个LLS控件,然后通过后台绑定数据,然后通过SelectionChanged事件来删除Item。如果这个时候我们从上面一个一个往下删除的时候就会发生一个意想不到的Exception,System.ArgumentException: Value does not fall within the expected range. 如果你按照上面的步骤做了,相信会很好复现。
通过Google了这个问题之后,发现这个bug不光是我一个人碰到,很多人也有同样的问题。Exception是通过LLS的MeasureOverride方法抛出来的,解决办法是通过重写MeasureOverride方法,并try/catch来规避原有的问题。看似是可以解决这个问题,但在实际应用过程中try/catch之后的界面很大几率被打乱,所以这个方法行不通。
1 public class LongListSelectorEx : LongListSelector 2 { 3 4 protected override System.Windows.Size MeasureOverride(System.Windows.Size availableSize) 5 { 6 try 7 { 8 return base.MeasureOverride(availableSize); 9 } 10 catch (ArgumentException) 11 { 12 return this.DesiredSize; 13 } 14 } 15 }
还有一种解决办法是通过设置LLS的ItemsSource,在删除Item之前先把ItemsSource设成null,删除之后再重新赋值,这个方法可以解决这个问题,但在数据量很大的时候这种做法显然很鸡肋。后来无奈还是换回到ListBox。
1 list.ItemsSource = null; 2 clist.Remove((string)e.AddedItems[0]); 3 list.ItemsSource = clist;
- 总结
WP提供了三种集合控件,存在即合理嘛,总有一种适合您的需求。希望您看完之后能像耐心挑选数据结构一样,耐心选择使用哪种集合控件。
这篇博客讲的东西都很基础,其实我觉得基础很重要。相比之下见到了不少人上来就是,大数据,多少多少万用户量。这些都是不切实际的做法,基础的东西是最容易被忽略的,我相信大部分问题的产生都是因为基础不扎实。只有积跬步才能至千里。