// (c) Copyright Microsoft Corporation. // This source is subject to the Microsoft Public License (Ms-PL). // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details. // All other rights reserved. using System; using System.Collections; using System.Collections.Specialized; using System.ComponentModel; using System.Diagnostics.CodeAnalysis; using System.Windows; using System.Windows.Controls; using System.Windows.Controls.Primitives; using System.Windows.Data; using System.Windows.Input; using System.Windows.Media; using System.Windows.Media.Animation; using Microsoft.Phone.Shell; namespace Microsoft.Phone.Controls { /// /// Class that implements a flexible list-picking experience with a custom interface for few/many items. /// [TemplatePart(Name = ItemsPresenterPartName, Type = typeof(ItemsPresenter))] [TemplatePart(Name = ItemsPresenterTranslateTransformPartName, Type = typeof(TranslateTransform))] [TemplatePart(Name = ItemsPresenterHostPartName, Type = typeof(Canvas))] [TemplatePart(Name = FullModePopupPartName, Type = typeof(Popup))] [TemplatePart(Name = FullModeSelectorPartName, Type = typeof(Selector))] [TemplateVisualState(GroupName = PickerStatesGroupName, Name = PickerStatesNormalStateName)] [TemplateVisualState(GroupName = PickerStatesGroupName, Name = PickerStatesExpandedStateName)] public class ListPicker : ItemsControl { private const string ItemsPresenterPartName = "ItemsPresenter"; private const string ItemsPresenterTranslateTransformPartName = "ItemsPresenterTranslateTransform"; private const string ItemsPresenterHostPartName = "ItemsPresenterHost"; private const string FullModePopupPartName = "FullModePopup"; private const string FullModeSelectorPartName = "FullModeSelector"; private const string PickerStatesGroupName = "PickerStates"; private const string PickerStatesNormalStateName = "Normal"; private const string PickerStatesExpandedStateName = "Expanded"; private readonly DoubleAnimation _heightAnimation = new DoubleAnimation(); private readonly DoubleAnimation _translateAnimation = new DoubleAnimation(); private readonly Storyboard _storyboard = new Storyboard(); private PhoneApplicationFrame _frame; private PhoneApplicationPage _page; private FrameworkElement _itemsPresenterHostParent; private Canvas _itemsPresenterHostPart; private ItemsPresenter _itemsPresenterPart; private Popup _fullModePopupPart; private Selector _fullModeSelectorPart; private TranslateTransform _itemsPresenterTranslateTransformPart; private bool _updatingSelection; private bool _savedSystemTrayIsVisible; private bool _savedApplicationBarIsVisible; private int _deferredSelectedIndex = -1; /// /// Event that is raised when the selection changes. /// public event SelectionChangedEventHandler SelectionChanged; /// /// Gets or sets the ListPickerMode (ex: Normal/Expanded/Full). /// public ListPickerMode ListPickerMode { get { return (ListPickerMode)GetValue(ListPickerModeProperty); } set { SetValue(ListPickerModeProperty, value); } } /// /// Identifies the ListPickerMode DependencyProperty. /// public static readonly DependencyProperty ListPickerModeProperty = DependencyProperty.Register("ListPickerMode", typeof(ListPickerMode), typeof(ListPicker), new PropertyMetadata(ListPickerMode.Normal, OnListPickerModeChanged)); private static void OnListPickerModeChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { ((ListPicker)o).OnListPickerModeChanged((ListPickerMode)e.OldValue, (ListPickerMode)e.NewValue); } private void OnListPickerModeChanged(ListPickerMode oldValue, ListPickerMode newValue) { // Hook up to frame if not already done if (null == _frame) { _frame = Application.Current.RootVisual as PhoneApplicationFrame; if (null != _frame) { _frame.AddHandler(ManipulationCompletedEvent, new EventHandler(HandleFrameManipulationCompleted), true); } } // Restore state if ((ListPickerMode.Full == oldValue) && !DesignerProperties.IsInDesignTool) { if (null != _fullModePopupPart) { _fullModePopupPart.IsOpen = false; } if (null != _fullModeSelectorPart) { _fullModeSelectorPart.SelectionChanged -= HandleFullModeSelectorPartSelectionChanged; _fullModeSelectorPart.Loaded -= HandleFullModeSelectorPartLoaded; _fullModeSelectorPart.ItemsSource = null; } SystemTray.IsVisible = _savedSystemTrayIsVisible; if (null != _page) { if (null != _page.ApplicationBar) { _page.ApplicationBar.IsVisible = _savedApplicationBarIsVisible; } _page.BackKeyPress -= HandlePageBackKeyPress; _page = null; } if (null != _frame) { _frame.OrientationChanged -= HandleFrameOrientationChanged; } } else if ((ListPickerMode.Expanded == oldValue) && !DesignerProperties.IsInDesignTool) { _page = _frame.Content as PhoneApplicationPage; if (null != _page) { _page.BackKeyPress -= HandlePageBackKeyPress; } } // Hook up to relevant events if ((ListPickerMode.Full == newValue) && !DesignerProperties.IsInDesignTool) { _savedSystemTrayIsVisible = SystemTray.IsVisible; SystemTray.IsVisible = false; if (null != _frame) { AdjustPopupChildForCurrentOrientation(_frame); _frame.OrientationChanged += HandleFrameOrientationChanged; _page = _frame.Content as PhoneApplicationPage; if (null != _page) { if (null != _page.ApplicationBar) { _savedApplicationBarIsVisible = _page.ApplicationBar.IsVisible; _page.ApplicationBar.IsVisible = false; } _page.BackKeyPress += HandlePageBackKeyPress; } } if (null != _fullModeSelectorPart) { _fullModeSelectorPart.ItemsSource = Items; _fullModeSelectorPart.SelectionChanged += HandleFullModeSelectorPartSelectionChanged; _fullModeSelectorPart.Loaded += HandleFullModeSelectorPartLoaded; } if (null != _fullModePopupPart) { _fullModePopupPart.IsOpen = true; } } else if ((ListPickerMode.Expanded == newValue) && !DesignerProperties.IsInDesignTool) { _page = _frame.Content as PhoneApplicationPage; if (null != _page) { _page.BackKeyPress += HandlePageBackKeyPress; } } // Resize for new view and go to relevant visual state(s) SizeForAppropriateView(ListPickerMode.Full != oldValue); GoToStates(true); } /// /// Gets or sets the index of the selected item. /// public int SelectedIndex { get { return (int)GetValue(SelectedIndexProperty); } set { SetValue(SelectedIndexProperty, value); } } /// /// Identifies the SelectedIndex DependencyProperty. /// public static readonly DependencyProperty SelectedIndexProperty = DependencyProperty.Register("SelectedIndex", typeof(int), typeof(ListPicker), new PropertyMetadata(-1, OnSelectedIndexChanged)); private static void OnSelectedIndexChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { ((ListPicker)o).OnSelectedIndexChanged((int)e.OldValue, (int)e.NewValue); } [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "SelectedIndex", Justification = "Property name.")] private void OnSelectedIndexChanged(int oldValue, int newValue) { // Validate new value if ((Items.Count <= newValue) || ((0 < Items.Count) && (newValue < 0)) || ((0 == Items.Count) && (newValue != -1))) { if ((null == Template) && (0 <= newValue)) { // Can't set the value now; remember it for later _deferredSelectedIndex = newValue; return; } throw new InvalidOperationException(Properties.Resources.InvalidSelectedIndex); } // Synchronize SelectedItem property if (!_updatingSelection) { _updatingSelection = true; SelectedItem = (-1 != newValue) ? Items[newValue] : null; _updatingSelection = false; } if (-1 != oldValue) { // Toggle container selection ListPickerItem oldContainer = (ListPickerItem)ItemContainerGenerator.ContainerFromIndex(oldValue); if (null != oldContainer) { oldContainer.IsSelected = false; } } } /// /// Gets or sets the selected item. /// public object SelectedItem { get { return (object)GetValue(SelectedItemProperty); } set { SetValue(SelectedItemProperty, value); } } /// /// Identifies the SelectedItem DependencyProperty. /// public static readonly DependencyProperty SelectedItemProperty = DependencyProperty.Register("SelectedItem", typeof(object), typeof(ListPicker), new PropertyMetadata(null, OnSelectedItemChanged)); private static void OnSelectedItemChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { ((ListPicker)o).OnSelectedItemChanged(e.OldValue, e.NewValue); } [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "SelectedItem", Justification = "Property name.")] private void OnSelectedItemChanged(object oldValue, object newValue) { // Validate new value int newValueIndex = newValueIndex = (null != newValue) ? Items.IndexOf(newValue) : -1; if ((-1 == newValueIndex) && (0 < Items.Count)) { throw new InvalidOperationException(Properties.Resources.InvalidSelectedItem); } // Synchronize SelectedIndex property if (!_updatingSelection) { _updatingSelection = true; SelectedIndex = newValueIndex; _updatingSelection = false; } // Switch to Normal mode or size for current item if (ListPickerMode.Normal != ListPickerMode) { ListPickerMode = ListPickerMode.Normal; } else { SizeForAppropriateView(false); } // Fire SelectionChanged event SelectionChangedEventHandler handler = SelectionChanged; if (null != handler) { IList removedItems = (null == oldValue) ? new object[0] : new object[] { oldValue }; IList addedItems = (null == newValue) ? new object[0] : new object[] { newValue }; handler(this, new SelectionChangedEventArgs(removedItems, addedItems)); } } private static readonly DependencyProperty ShadowItemTemplateProperty = DependencyProperty.Register("ShadowItemTemplate", typeof(DataTemplate), typeof(ListPicker), new PropertyMetadata(null, OnShadowOrFullModeItemTemplateChanged)); /// /// Gets or sets the DataTemplate used to display each item when ListPickerMode is set to Full. /// public DataTemplate FullModeItemTemplate { get { return (DataTemplate)GetValue(FullModeItemTemplateProperty); } set { SetValue(FullModeItemTemplateProperty, value); } } /// /// Identifies the FullModeItemTemplate DependencyProperty. /// public static readonly DependencyProperty FullModeItemTemplateProperty = DependencyProperty.Register("FullModeItemTemplate", typeof(DataTemplate), typeof(ListPicker), new PropertyMetadata(null, OnShadowOrFullModeItemTemplateChanged)); private static void OnShadowOrFullModeItemTemplateChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { ((ListPicker)o).OnShadowOrFullModeItemTemplateChanged(/*(DataTemplate)e.OldValue, (DataTemplate)e.NewValue*/); } private void OnShadowOrFullModeItemTemplateChanged(/*DataTemplate oldValue, DataTemplate newValue*/) { // Set ActualFullModeItemTemplate accordingly SetValue(ActualFullModeItemTemplateProperty, FullModeItemTemplate ?? ItemTemplate); } private static readonly DependencyProperty ActualFullModeItemTemplateProperty = DependencyProperty.Register("ActualFullModeItemTemplate", typeof(DataTemplate), typeof(ListPicker), null); /// /// Gets or sets the header of the control. /// public object Header { get { return (object)GetValue(HeaderProperty); } set { SetValue(HeaderProperty, value); } } /// /// Identifies the Header DependencyProperty. /// public static readonly DependencyProperty HeaderProperty = DependencyProperty.Register("Header", typeof(object), typeof(ListPicker), null); /// /// Gets or sets the template used to display the control's header. /// public DataTemplate HeaderTemplate { get { return (DataTemplate)GetValue(HeaderTemplateProperty); } set { SetValue(HeaderTemplateProperty, value); } } /// /// Identifies the HeaderTemplate DependencyProperty. /// public static readonly DependencyProperty HeaderTemplateProperty = DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(ListPicker), null); /// /// Gets or sets the header to use when ListPickerMode is set to Full. /// public object FullModeHeader { get { return (object)GetValue(FullModeHeaderProperty); } set { SetValue(FullModeHeaderProperty, value); } } /// /// Identifies the FullModeHeader DependencyProperty. /// public static readonly DependencyProperty FullModeHeaderProperty = DependencyProperty.Register("FullModeHeader", typeof(object), typeof(ListPicker), null); /// /// Gets or sets the maximum number of items for which Expanded mode will be used (default: 5). /// public int ItemCountThreshold { get { return (int)GetValue(ItemCountThresholdProperty); } set { SetValue(ItemCountThresholdProperty, value); } } /// /// Identifies the ItemCountThreshold DependencyProperty. /// public static readonly DependencyProperty ItemCountThresholdProperty = DependencyProperty.Register("ItemCountThreshold", typeof(int), typeof(ListPicker), new PropertyMetadata(5, OnItemCountThresholdChanged)); private static void OnItemCountThresholdChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { ((ListPicker)o).OnItemCountThresholdChanged(/*(int)e.OldValue,*/ (int)e.NewValue); } [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Following DependencyProperty property changed handler convention.")] [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "Providing the DependencyProperty name is preferred here.")] private void OnItemCountThresholdChanged(/*int oldValue,*/ int newValue) { if (newValue < 0) { throw new ArgumentOutOfRangeException("ItemCountThreshold"); } } /// /// Initializes a new instance of the ListPicker class. /// public ListPicker() { DefaultStyleKey = typeof(ListPicker); Storyboard.SetTargetProperty(_heightAnimation, new PropertyPath(FrameworkElement.HeightProperty)); Storyboard.SetTargetProperty(_translateAnimation, new PropertyPath(TranslateTransform.YProperty)); // Would be nice if these values were customizable (ex: as DependencyProperties or in Template as VSM states) Duration duration = TimeSpan.FromSeconds(0.2); _heightAnimation.Duration = duration; _translateAnimation.Duration = duration; IEasingFunction easingFunction = new ExponentialEase { EasingMode = EasingMode.EaseInOut, Exponent = 4 }; _heightAnimation.EasingFunction = easingFunction; _translateAnimation.EasingFunction = easingFunction; Unloaded += delegate { // Unhook any remaining event handlers if (null != _frame) { _frame.ManipulationCompleted -= new EventHandler(HandleFrameManipulationCompleted); _frame = null; } }; } /// /// Builds the visual tree for the control when a new template is applied. /// public override void OnApplyTemplate() { // Unhook from old elements if (null != _itemsPresenterHostParent) { _itemsPresenterHostParent.SizeChanged -= HandleItemsPresenterHostParentSizeChanged; } _storyboard.Stop(); base.OnApplyTemplate(); // Hook up to new elements _itemsPresenterPart = GetTemplateChild(ItemsPresenterPartName) as ItemsPresenter; _itemsPresenterTranslateTransformPart = GetTemplateChild(ItemsPresenterTranslateTransformPartName) as TranslateTransform; _itemsPresenterHostPart = GetTemplateChild(ItemsPresenterHostPartName) as Canvas; _fullModePopupPart = GetTemplateChild(FullModePopupPartName) as Popup; _fullModeSelectorPart = GetTemplateChild(FullModeSelectorPartName) as Selector; _itemsPresenterHostParent = (null != _itemsPresenterHostPart) ? _itemsPresenterHostPart.Parent as FrameworkElement : null; if (null != _itemsPresenterHostParent) { _itemsPresenterHostParent.SizeChanged += HandleItemsPresenterHostParentSizeChanged; } if (null != _itemsPresenterHostPart) { Storyboard.SetTarget(_heightAnimation, _itemsPresenterHostPart); if (!_storyboard.Children.Contains(_heightAnimation)) { _storyboard.Children.Add(_heightAnimation); } } else { if (_storyboard.Children.Contains(_heightAnimation)) { _storyboard.Children.Remove(_heightAnimation); } } if (null != _itemsPresenterTranslateTransformPart) { Storyboard.SetTarget(_translateAnimation, _itemsPresenterTranslateTransformPart); if (!_storyboard.Children.Contains(_translateAnimation)) { _storyboard.Children.Add(_translateAnimation); } } else { if (_storyboard.Children.Contains(_translateAnimation)) { _storyboard.Children.Remove(_translateAnimation); } } if (null != _fullModePopupPart) { UIElement child = _fullModePopupPart.Child; _fullModePopupPart.Child = null; _fullModePopupPart = new Popup(); _fullModePopupPart.Child = child; } SetBinding(ShadowItemTemplateProperty, new Binding("ItemTemplate") { Source = this }); // Commit deferred SelectedIndex (if any) if (-1 != _deferredSelectedIndex) { SelectedIndex = _deferredSelectedIndex; _deferredSelectedIndex = -1; } // Go to current state(s) GoToStates(false); } /// /// Determines if the specified item is (or is eligible to be) its own item container. /// /// The specified item. /// True if the item is its own item container; otherwise, false. protected override bool IsItemItsOwnContainerOverride(object item) { return item is ListPickerItem; } /// /// Creates or identifies the element used to display a specified item. /// /// A container corresponding to a specified item. protected override DependencyObject GetContainerForItemOverride() { return new ListPickerItem(); } /// /// Prepares the specified element to display the specified item. /// /// The element used to display the specified item. /// The item to display. protected override void PrepareContainerForItemOverride(DependencyObject element, object item) { base.PrepareContainerForItemOverride(element, item); // Hook up to interesting events ContentControl container = (ContentControl)element; container.ManipulationCompleted += HandleContainerManipulationCompleted; container.SizeChanged += HandleListPickerItemSizeChanged; // Size for selected item if it's this one if (object.Equals(item, SelectedItem)) { SizeForAppropriateView(false); } } /// /// Undoes the effects of the PrepareContainerForItemOverride method. /// /// The container element. /// The item. protected override void ClearContainerForItemOverride(DependencyObject element, object item) { base.ClearContainerForItemOverride(element, item); // Unhook from events ContentControl container = (ContentControl)element; container.ManipulationCompleted -= HandleContainerManipulationCompleted; container.SizeChanged -= HandleListPickerItemSizeChanged; } /// /// Provides handling for the ItemContainerGenerator.ItemsChanged event. /// /// A NotifyCollectionChangedEventArgs that contains the event data. protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) { base.OnItemsChanged(e); if ((0 < Items.Count) && (null == SelectedItem)) { // Nothing selected (and no pending Binding); select the first item if ((null == GetBindingExpression(SelectedIndexProperty)) && (null == GetBindingExpression(SelectedItemProperty))) { SelectedIndex = 0; } } else if (0 == Items.Count) { // No items; select nothing SelectedIndex = -1; ListPickerMode = ListPickerMode.Normal; } else if (Items.Count <= SelectedIndex) { // Selected item no longer present; select the last item SelectedIndex = Items.Count - 1; } else { // Re-synchronize SelectedIndex with SelectedItem if necessary if (!object.Equals(Items[SelectedIndex], SelectedItem)) { int selectedItemIndex = Items.IndexOf(SelectedItem); if (-1 == selectedItemIndex) { SelectedItem = Items[0]; } else { SelectedIndex = selectedItemIndex; } } } // Translate it into view once layout has been updated for the added/removed item(s) Dispatcher.BeginInvoke(() => SizeForAppropriateView(false)); } /// /// Called when the ManipulationCompleted event occurs. /// /// Event data for the event. protected override void OnManipulationCompleted(ManipulationCompletedEventArgs e) { if (null == e) { throw new ArgumentNullException("e"); } base.OnManipulationCompleted(e); // Interaction needs to be on the _itemsPresenterHostPart or its children DependencyObject element = e.OriginalSource as DependencyObject; while (null != element) { if (_itemsPresenterHostPart == element) { // On interaction, switch to Expanded/Full mode if ((ListPickerMode.Normal == ListPickerMode) && (0 < Items.Count)) { ListPickerMode = (Items.Count <= ItemCountThreshold) ? ListPickerMode.Expanded : ListPickerMode.Full; e.Handled = true; } break; } element = VisualTreeHelper.GetParent(element); } } private void HandleItemsPresenterHostParentSizeChanged(object sender, SizeChangedEventArgs e) { // Pass width through the Canvas if (null != _itemsPresenterPart) { _itemsPresenterPart.Width = e.NewSize.Width; } // Update clip to show only the selected item in Normal mode _itemsPresenterHostParent.Clip = new RectangleGeometry { Rect = new Rect(new Point(), e.NewSize) }; } private void HandleListPickerItemSizeChanged(object sender, SizeChangedEventArgs e) { // Update size accordingly ContentControl container = (ContentControl)sender; if (object.Equals(ItemContainerGenerator.ItemFromContainer(container), SelectedItem)) { SizeForAppropriateView(false); } } private void HandleFullModeSelectorPartSelectionChanged(object sender, SelectionChangedEventArgs e) { if (null != _fullModeSelectorPart) { // Commit selected item if (SelectedItem != _fullModeSelectorPart.SelectedItem) { SelectedItem = _fullModeSelectorPart.SelectedItem; } else { // User selected the already-selected item; just switch back to Normal view ListPickerMode = ListPickerMode.Normal; } } } private void HandleFullModeSelectorPartLoaded(object sender, RoutedEventArgs e) { if (null != _fullModeSelectorPart) { // Find the relevant container and make it look selected // Note: Selector.SelectedItem is left null so *any* selection will trigger the SelectionChanged event. // However, this doesn't highlight the "currently selected" item; the following technique fakes that. ContentControl container = _fullModeSelectorPart.ItemContainerGenerator.ContainerFromItem(SelectedItem) as ContentControl; if (null == container) { // Container isn't always available; defer until it is // Note: Assumes the container eventually WILL be available (which is why // the default Template replaces VirtualizingStackPanel with StackPanel) Dispatcher.BeginInvoke(() => HandleFullModeSelectorPartLoaded(sender, e)); } else { Brush phoneAccentBrush = Application.Current.Resources["PhoneAccentBrush"] as Brush; if (null != phoneAccentBrush) { container.Foreground = phoneAccentBrush; } } // Scroll item into view if possible ListBox listBox = _fullModeSelectorPart as ListBox; if (null != listBox) { listBox.ScrollIntoView(SelectedItem); } } } private void HandlePageBackKeyPress(object sender, CancelEventArgs e) { // Revert to Normal mode ListPickerMode = ListPickerMode.Normal; e.Cancel = true; } private void HandleFrameOrientationChanged(object sender, OrientationChangedEventArgs e) { AdjustPopupChildForCurrentOrientation((PhoneApplicationFrame)sender); } private void AdjustPopupChildForCurrentOrientation(PhoneApplicationFrame frame) { if (null != _fullModePopupPart) { FrameworkElement child = _fullModePopupPart.Child as FrameworkElement; if (null != child) { // Transform child according to current orientation double actualWidth = frame.ActualWidth; double actualHeight = frame.ActualHeight; bool portrait = PageOrientation.Portrait == (PageOrientation.Portrait & frame.Orientation); TransformGroup transformGroup = new TransformGroup(); switch (frame.Orientation) { case PageOrientation.LandscapeLeft: transformGroup.Children.Add(new RotateTransform { Angle = 90 }); transformGroup.Children.Add(new TranslateTransform { X = actualWidth }); break; case PageOrientation.LandscapeRight: transformGroup.Children.Add(new RotateTransform { Angle = -90 }); transformGroup.Children.Add(new TranslateTransform { Y = actualHeight }); break; } child.RenderTransform = transformGroup; // Size child to frame child.Width = portrait ? actualWidth : actualHeight; child.Height = portrait ? actualHeight : actualWidth; // Adjust padding if possible Border border = child as Border; if (null != border) { switch (frame.Orientation) { case PageOrientation.PortraitUp: border.Padding = new Thickness(0, 32, 0, 0); break; case PageOrientation.LandscapeLeft: border.Padding = new Thickness(72, 0, 0, 0); break; case PageOrientation.LandscapeRight: border.Padding = new Thickness(0, 0, 72, 0); break; } } } } } private void SizeForAppropriateView(bool animate) { switch (ListPickerMode) { case ListPickerMode.Normal: SizeForNormalMode(animate); break; case ListPickerMode.Expanded: SizeForExpandedMode(); break; case ListPickerMode.Full: // Nothing to do break; } // Play the height/translation animations _storyboard.Begin(); if (!animate) { _storyboard.SkipToFill(); } } private void SizeForNormalMode(bool animate) { ContentControl container = (ContentControl)ItemContainerGenerator.ContainerFromItem(SelectedItem); if (null != container) { // Set height/translation to show just the selected item if (0 < container.ActualHeight) { SetContentHeight(container.ActualHeight + container.Margin.Top + container.Margin.Bottom); } if (null != _itemsPresenterTranslateTransformPart) { if (!animate) { _itemsPresenterTranslateTransformPart.Y = 0; } _translateAnimation.To = container.Margin.Top - LayoutInformation.GetLayoutSlot(container).Top; _translateAnimation.From = animate ? null : _translateAnimation.To; } } else { // Resize to minimum height SetContentHeight(0); } // Clear highlight of previously selected container ListPickerItem oldContainer = (ListPickerItem)ItemContainerGenerator.ContainerFromIndex(SelectedIndex); if (null != oldContainer) { oldContainer.IsSelected = false; } } private void SizeForExpandedMode() { // Set height and align first element at top if (null != _itemsPresenterPart) { SetContentHeight(_itemsPresenterPart.ActualHeight); } if (null != _itemsPresenterTranslateTransformPart) { _translateAnimation.To = 0; } // Highlight selected container ListPickerItem container = (ListPickerItem)ItemContainerGenerator.ContainerFromIndex(SelectedIndex); if (null != container) { container.IsSelected = true; } } private void SetContentHeight(double height) { if ((null != _itemsPresenterHostPart) && !double.IsNaN(height)) { double canvasHeight = _itemsPresenterHostPart.Height; _heightAnimation.From = double.IsNaN(canvasHeight) ? height : canvasHeight; _heightAnimation.To = height; } } private void HandleFrameManipulationCompleted(object sender, ManipulationCompletedEventArgs e) { if (ListPickerMode.Expanded == ListPickerMode) { // Manipulation outside an Expanded ListPicker reverts to Normal mode DependencyObject element = e.OriginalSource as DependencyObject; DependencyObject cancelElement = (DependencyObject)_itemsPresenterHostPart ?? (DependencyObject)this; while (null != element) { if (cancelElement == element) { return; } element = VisualTreeHelper.GetParent(element); } ListPickerMode = ListPickerMode.Normal; } } private void HandleContainerManipulationCompleted(object sender, ManipulationCompletedEventArgs e) { if (ListPickerMode.Expanded == ListPickerMode) { // Manipulation of a container selects the item and reverts to Normal mode ContentControl container = (ContentControl)sender; SelectedItem = ItemContainerGenerator.ItemFromContainer(container); ListPickerMode = ListPickerMode.Normal; e.Handled = true; } } private void GoToStates(bool useTransitions) { switch (ListPickerMode) { case ListPickerMode.Normal: VisualStateManager.GoToState(this, PickerStatesNormalStateName, useTransitions); break; case ListPickerMode.Expanded: VisualStateManager.GoToState(this, PickerStatesExpandedStateName, useTransitions); break; case ListPickerMode.Full: // Nothing to do break; } } } }