PageRenderTime 132ms CodeModel.GetById 26ms RepoModel.GetById 2ms app.codeStats 0ms

/toolkit/Microsoft.Phone.Controls.Toolkit/ListPicker/ListPicker.cs

https://bitbucket.org/jeremejevs/word-steps
C# | 941 lines | 710 code | 85 blank | 146 comment | 166 complexity | 484cd1e146f289ab70334345de18c101 MD5 | raw file
  1. // (c) Copyright Microsoft Corporation.
  2. // This source is subject to the Microsoft Public License (Ms-PL).
  3. // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
  4. // All other rights reserved.
  5. using System;
  6. using System.Collections;
  7. using System.Collections.Specialized;
  8. using System.ComponentModel;
  9. using System.Diagnostics.CodeAnalysis;
  10. using System.Windows;
  11. using System.Windows.Controls;
  12. using System.Windows.Controls.Primitives;
  13. using System.Windows.Data;
  14. using System.Windows.Input;
  15. using System.Windows.Media;
  16. using System.Windows.Media.Animation;
  17. using Microsoft.Phone.Shell;
  18. namespace Microsoft.Phone.Controls
  19. {
  20. /// <summary>
  21. /// Class that implements a flexible list-picking experience with a custom interface for few/many items.
  22. /// </summary>
  23. [TemplatePart(Name = ItemsPresenterPartName, Type = typeof(ItemsPresenter))]
  24. [TemplatePart(Name = ItemsPresenterTranslateTransformPartName, Type = typeof(TranslateTransform))]
  25. [TemplatePart(Name = ItemsPresenterHostPartName, Type = typeof(Canvas))]
  26. [TemplatePart(Name = FullModePopupPartName, Type = typeof(Popup))]
  27. [TemplatePart(Name = FullModeSelectorPartName, Type = typeof(Selector))]
  28. [TemplateVisualState(GroupName = PickerStatesGroupName, Name = PickerStatesNormalStateName)]
  29. [TemplateVisualState(GroupName = PickerStatesGroupName, Name = PickerStatesExpandedStateName)]
  30. public class ListPicker : ItemsControl
  31. {
  32. private const string ItemsPresenterPartName = "ItemsPresenter";
  33. private const string ItemsPresenterTranslateTransformPartName = "ItemsPresenterTranslateTransform";
  34. private const string ItemsPresenterHostPartName = "ItemsPresenterHost";
  35. private const string FullModePopupPartName = "FullModePopup";
  36. private const string FullModeSelectorPartName = "FullModeSelector";
  37. private const string PickerStatesGroupName = "PickerStates";
  38. private const string PickerStatesNormalStateName = "Normal";
  39. private const string PickerStatesExpandedStateName = "Expanded";
  40. private readonly DoubleAnimation _heightAnimation = new DoubleAnimation();
  41. private readonly DoubleAnimation _translateAnimation = new DoubleAnimation();
  42. private readonly Storyboard _storyboard = new Storyboard();
  43. private PhoneApplicationFrame _frame;
  44. private PhoneApplicationPage _page;
  45. private FrameworkElement _itemsPresenterHostParent;
  46. private Canvas _itemsPresenterHostPart;
  47. private ItemsPresenter _itemsPresenterPart;
  48. private Popup _fullModePopupPart;
  49. private Selector _fullModeSelectorPart;
  50. private TranslateTransform _itemsPresenterTranslateTransformPart;
  51. private bool _updatingSelection;
  52. private bool _savedSystemTrayIsVisible;
  53. private bool _savedApplicationBarIsVisible;
  54. private int _deferredSelectedIndex = -1;
  55. /// <summary>
  56. /// Event that is raised when the selection changes.
  57. /// </summary>
  58. public event SelectionChangedEventHandler SelectionChanged;
  59. /// <summary>
  60. /// Gets or sets the ListPickerMode (ex: Normal/Expanded/Full).
  61. /// </summary>
  62. public ListPickerMode ListPickerMode
  63. {
  64. get { return (ListPickerMode)GetValue(ListPickerModeProperty); }
  65. set { SetValue(ListPickerModeProperty, value); }
  66. }
  67. /// <summary>
  68. /// Identifies the ListPickerMode DependencyProperty.
  69. /// </summary>
  70. public static readonly DependencyProperty ListPickerModeProperty =
  71. DependencyProperty.Register("ListPickerMode", typeof(ListPickerMode), typeof(ListPicker), new PropertyMetadata(ListPickerMode.Normal, OnListPickerModeChanged));
  72. private static void OnListPickerModeChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  73. {
  74. ((ListPicker)o).OnListPickerModeChanged((ListPickerMode)e.OldValue, (ListPickerMode)e.NewValue);
  75. }
  76. private void OnListPickerModeChanged(ListPickerMode oldValue, ListPickerMode newValue)
  77. {
  78. // Hook up to frame if not already done
  79. if (null == _frame)
  80. {
  81. _frame = Application.Current.RootVisual as PhoneApplicationFrame;
  82. if (null != _frame)
  83. {
  84. _frame.AddHandler(ManipulationCompletedEvent, new EventHandler<ManipulationCompletedEventArgs>(HandleFrameManipulationCompleted), true);
  85. }
  86. }
  87. // Restore state
  88. if ((ListPickerMode.Full == oldValue) && !DesignerProperties.IsInDesignTool)
  89. {
  90. if (null != _fullModePopupPart)
  91. {
  92. _fullModePopupPart.IsOpen = false;
  93. }
  94. if (null != _fullModeSelectorPart)
  95. {
  96. _fullModeSelectorPart.SelectionChanged -= HandleFullModeSelectorPartSelectionChanged;
  97. _fullModeSelectorPart.Loaded -= HandleFullModeSelectorPartLoaded;
  98. _fullModeSelectorPart.ItemsSource = null;
  99. }
  100. Action restoreSystemTray = () =>
  101. {
  102. try
  103. {
  104. SystemTray.IsVisible = _savedSystemTrayIsVisible;
  105. }
  106. catch (InvalidOperationException)
  107. {
  108. }
  109. };
  110. try
  111. {
  112. restoreSystemTray();
  113. }
  114. catch (InvalidOperationException)
  115. {
  116. Dispatcher.BeginInvoke(restoreSystemTray);
  117. }
  118. if (null != _page)
  119. {
  120. if (null != _page.ApplicationBar)
  121. {
  122. _page.ApplicationBar.IsVisible = _savedApplicationBarIsVisible;
  123. }
  124. }
  125. if (null != _frame)
  126. {
  127. _frame.OrientationChanged -= HandleFrameOrientationChanged;
  128. }
  129. }
  130. if ((ListPickerMode.Expanded == oldValue) || (ListPickerMode.Full == oldValue))
  131. {
  132. if (null != _page)
  133. {
  134. _page.BackKeyPress -= HandlePageBackKeyPress;
  135. _page = null;
  136. }
  137. }
  138. // Hook up to relevant events
  139. if ((ListPickerMode.Expanded == newValue) || (ListPickerMode.Full == newValue))
  140. {
  141. if (null != _frame)
  142. {
  143. _page = _frame.Content as PhoneApplicationPage;
  144. if (null != _page)
  145. {
  146. _page.BackKeyPress += HandlePageBackKeyPress;
  147. }
  148. }
  149. }
  150. if ((ListPickerMode.Full == newValue) && !DesignerProperties.IsInDesignTool)
  151. {
  152. Action saveTrayAndHide = () =>
  153. {
  154. try
  155. {
  156. _savedSystemTrayIsVisible = SystemTray.IsVisible;
  157. SystemTray.IsVisible = false;
  158. }
  159. catch (InvalidOperationException)
  160. {
  161. }
  162. };
  163. try
  164. {
  165. saveTrayAndHide();
  166. }
  167. catch (InvalidOperationException)
  168. {
  169. Dispatcher.BeginInvoke(saveTrayAndHide);
  170. }
  171. if (null != _frame)
  172. {
  173. AdjustPopupChildForCurrentOrientation(_frame);
  174. _frame.OrientationChanged += HandleFrameOrientationChanged;
  175. if (null != _page)
  176. {
  177. if (null != _page.ApplicationBar)
  178. {
  179. _savedApplicationBarIsVisible = _page.ApplicationBar.IsVisible;
  180. _page.ApplicationBar.IsVisible = false;
  181. }
  182. }
  183. }
  184. if (null != _fullModeSelectorPart)
  185. {
  186. _fullModeSelectorPart.ItemsSource = Items;
  187. _fullModeSelectorPart.SelectionChanged += HandleFullModeSelectorPartSelectionChanged;
  188. _fullModeSelectorPart.Loaded += HandleFullModeSelectorPartLoaded;
  189. }
  190. if (null != _fullModePopupPart)
  191. {
  192. _fullModePopupPart.IsOpen = true;
  193. }
  194. }
  195. // Resize for new view and go to relevant visual state(s)
  196. SizeForAppropriateView(ListPickerMode.Full != oldValue);
  197. GoToStates(true);
  198. }
  199. /// <summary>
  200. /// Gets or sets the index of the selected item.
  201. /// </summary>
  202. public int SelectedIndex
  203. {
  204. get { return (int)GetValue(SelectedIndexProperty); }
  205. set { SetValue(SelectedIndexProperty, value); }
  206. }
  207. /// <summary>
  208. /// Identifies the SelectedIndex DependencyProperty.
  209. /// </summary>
  210. public static readonly DependencyProperty SelectedIndexProperty =
  211. DependencyProperty.Register("SelectedIndex", typeof(int), typeof(ListPicker), new PropertyMetadata(-1, OnSelectedIndexChanged));
  212. private static void OnSelectedIndexChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  213. {
  214. ((ListPicker)o).OnSelectedIndexChanged((int)e.OldValue, (int)e.NewValue);
  215. }
  216. [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "SelectedIndex", Justification = "Property name.")]
  217. private void OnSelectedIndexChanged(int oldValue, int newValue)
  218. {
  219. // Validate new value
  220. if ((Items.Count <= newValue) ||
  221. ((0 < Items.Count) && (newValue < 0)) ||
  222. ((0 == Items.Count) && (newValue != -1)))
  223. {
  224. if ((null == Template) && (0 <= newValue))
  225. {
  226. // Can't set the value now; remember it for later
  227. _deferredSelectedIndex = newValue;
  228. return;
  229. }
  230. throw new InvalidOperationException(Properties.Resources.InvalidSelectedIndex);
  231. }
  232. // Synchronize SelectedItem property
  233. if (!_updatingSelection)
  234. {
  235. _updatingSelection = true;
  236. SelectedItem = (-1 != newValue) ? Items[newValue] : null;
  237. _updatingSelection = false;
  238. }
  239. if (-1 != oldValue)
  240. {
  241. // Toggle container selection
  242. ListPickerItem oldContainer = (ListPickerItem)ItemContainerGenerator.ContainerFromIndex(oldValue);
  243. if (null != oldContainer)
  244. {
  245. oldContainer.IsSelected = false;
  246. }
  247. }
  248. }
  249. /// <summary>
  250. /// Gets or sets the selected item.
  251. /// </summary>
  252. public object SelectedItem
  253. {
  254. get { return (object)GetValue(SelectedItemProperty); }
  255. set { SetValue(SelectedItemProperty, value); }
  256. }
  257. /// <summary>
  258. /// Identifies the SelectedItem DependencyProperty.
  259. /// </summary>
  260. public static readonly DependencyProperty SelectedItemProperty =
  261. DependencyProperty.Register("SelectedItem", typeof(object), typeof(ListPicker), new PropertyMetadata(null, OnSelectedItemChanged));
  262. private static void OnSelectedItemChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  263. {
  264. ((ListPicker)o).OnSelectedItemChanged(e.OldValue, e.NewValue);
  265. }
  266. [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "SelectedItem", Justification = "Property name.")]
  267. private void OnSelectedItemChanged(object oldValue, object newValue)
  268. {
  269. // Validate new value
  270. int newValueIndex = newValueIndex = (null != newValue) ? Items.IndexOf(newValue) : -1;
  271. if ((-1 == newValueIndex) && (0 < Items.Count))
  272. {
  273. throw new InvalidOperationException(Properties.Resources.InvalidSelectedItem);
  274. }
  275. // Synchronize SelectedIndex property
  276. if (!_updatingSelection)
  277. {
  278. _updatingSelection = true;
  279. SelectedIndex = newValueIndex;
  280. _updatingSelection = false;
  281. }
  282. // Switch to Normal mode or size for current item
  283. if (ListPickerMode.Normal != ListPickerMode)
  284. {
  285. ListPickerMode = ListPickerMode.Normal;
  286. }
  287. else
  288. {
  289. SizeForAppropriateView(false);
  290. }
  291. // Fire SelectionChanged event
  292. SelectionChangedEventHandler handler = SelectionChanged;
  293. if (null != handler)
  294. {
  295. IList removedItems = (null == oldValue) ? new object[0] : new object[] { oldValue };
  296. IList addedItems = (null == newValue) ? new object[0] : new object[] { newValue };
  297. handler(this, new SelectionChangedEventArgs(removedItems, addedItems));
  298. }
  299. }
  300. private static readonly DependencyProperty ShadowItemTemplateProperty =
  301. DependencyProperty.Register("ShadowItemTemplate", typeof(DataTemplate), typeof(ListPicker), new PropertyMetadata(null, OnShadowOrFullModeItemTemplateChanged));
  302. /// <summary>
  303. /// Gets or sets the DataTemplate used to display each item when ListPickerMode is set to Full.
  304. /// </summary>
  305. public DataTemplate FullModeItemTemplate
  306. {
  307. get { return (DataTemplate)GetValue(FullModeItemTemplateProperty); }
  308. set { SetValue(FullModeItemTemplateProperty, value); }
  309. }
  310. /// <summary>
  311. /// Identifies the FullModeItemTemplate DependencyProperty.
  312. /// </summary>
  313. public static readonly DependencyProperty FullModeItemTemplateProperty =
  314. DependencyProperty.Register("FullModeItemTemplate", typeof(DataTemplate), typeof(ListPicker), new PropertyMetadata(null, OnShadowOrFullModeItemTemplateChanged));
  315. private static void OnShadowOrFullModeItemTemplateChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  316. {
  317. ((ListPicker)o).OnShadowOrFullModeItemTemplateChanged(/*(DataTemplate)e.OldValue, (DataTemplate)e.NewValue*/);
  318. }
  319. private void OnShadowOrFullModeItemTemplateChanged(/*DataTemplate oldValue, DataTemplate newValue*/)
  320. {
  321. // Set ActualFullModeItemTemplate accordingly
  322. SetValue(ActualFullModeItemTemplateProperty, FullModeItemTemplate ?? ItemTemplate);
  323. }
  324. private static readonly DependencyProperty ActualFullModeItemTemplateProperty =
  325. DependencyProperty.Register("ActualFullModeItemTemplate", typeof(DataTemplate), typeof(ListPicker), null);
  326. /// <summary>
  327. /// Gets or sets the header of the control.
  328. /// </summary>
  329. public object Header
  330. {
  331. get { return (object)GetValue(HeaderProperty); }
  332. set { SetValue(HeaderProperty, value); }
  333. }
  334. /// <summary>
  335. /// Identifies the Header DependencyProperty.
  336. /// </summary>
  337. public static readonly DependencyProperty HeaderProperty =
  338. DependencyProperty.Register("Header", typeof(object), typeof(ListPicker), null);
  339. /// <summary>
  340. /// Gets or sets the template used to display the control's header.
  341. /// </summary>
  342. public DataTemplate HeaderTemplate
  343. {
  344. get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
  345. set { SetValue(HeaderTemplateProperty, value); }
  346. }
  347. /// <summary>
  348. /// Identifies the HeaderTemplate DependencyProperty.
  349. /// </summary>
  350. public static readonly DependencyProperty HeaderTemplateProperty =
  351. DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(ListPicker), null);
  352. /// <summary>
  353. /// Gets or sets the header to use when ListPickerMode is set to Full.
  354. /// </summary>
  355. public object FullModeHeader
  356. {
  357. get { return (object)GetValue(FullModeHeaderProperty); }
  358. set { SetValue(FullModeHeaderProperty, value); }
  359. }
  360. /// <summary>
  361. /// Identifies the FullModeHeader DependencyProperty.
  362. /// </summary>
  363. public static readonly DependencyProperty FullModeHeaderProperty =
  364. DependencyProperty.Register("FullModeHeader", typeof(object), typeof(ListPicker), null);
  365. /// <summary>
  366. /// Gets or sets the maximum number of items for which Expanded mode will be used (default: 5).
  367. /// </summary>
  368. public int ItemCountThreshold
  369. {
  370. get { return (int)GetValue(ItemCountThresholdProperty); }
  371. set { SetValue(ItemCountThresholdProperty, value); }
  372. }
  373. /// <summary>
  374. /// Identifies the ItemCountThreshold DependencyProperty.
  375. /// </summary>
  376. public static readonly DependencyProperty ItemCountThresholdProperty =
  377. DependencyProperty.Register("ItemCountThreshold", typeof(int), typeof(ListPicker), new PropertyMetadata(5, OnItemCountThresholdChanged));
  378. private static void OnItemCountThresholdChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  379. {
  380. ((ListPicker)o).OnItemCountThresholdChanged(/*(int)e.OldValue,*/ (int)e.NewValue);
  381. }
  382. [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Following DependencyProperty property changed handler convention.")]
  383. [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "Providing the DependencyProperty name is preferred here.")]
  384. private void OnItemCountThresholdChanged(/*int oldValue,*/ int newValue)
  385. {
  386. if (newValue < 0)
  387. {
  388. throw new ArgumentOutOfRangeException("ItemCountThreshold");
  389. }
  390. }
  391. /// <summary>
  392. /// Initializes a new instance of the ListPicker class.
  393. /// </summary>
  394. public ListPicker()
  395. {
  396. DefaultStyleKey = typeof(ListPicker);
  397. Storyboard.SetTargetProperty(_heightAnimation, new PropertyPath(FrameworkElement.HeightProperty));
  398. Storyboard.SetTargetProperty(_translateAnimation, new PropertyPath(TranslateTransform.YProperty));
  399. // Would be nice if these values were customizable (ex: as DependencyProperties or in Template as VSM states)
  400. Duration duration = TimeSpan.FromSeconds(0.2);
  401. _heightAnimation.Duration = duration;
  402. _translateAnimation.Duration = duration;
  403. IEasingFunction easingFunction = new ExponentialEase { EasingMode = EasingMode.EaseInOut, Exponent = 4 };
  404. _heightAnimation.EasingFunction = easingFunction;
  405. _translateAnimation.EasingFunction = easingFunction;
  406. Unloaded += delegate
  407. {
  408. // Unhook any remaining event handlers
  409. if (null != _frame)
  410. {
  411. _frame.ManipulationCompleted -= new EventHandler<ManipulationCompletedEventArgs>(HandleFrameManipulationCompleted);
  412. _frame = null;
  413. }
  414. };
  415. }
  416. /// <summary>
  417. /// Builds the visual tree for the control when a new template is applied.
  418. /// </summary>
  419. public override void OnApplyTemplate()
  420. {
  421. // Unhook from old elements
  422. if (null != _itemsPresenterHostParent)
  423. {
  424. _itemsPresenterHostParent.SizeChanged -= HandleItemsPresenterHostParentSizeChanged;
  425. }
  426. _storyboard.Stop();
  427. base.OnApplyTemplate();
  428. // Hook up to new elements
  429. _itemsPresenterPart = GetTemplateChild(ItemsPresenterPartName) as ItemsPresenter;
  430. _itemsPresenterTranslateTransformPart = GetTemplateChild(ItemsPresenterTranslateTransformPartName) as TranslateTransform;
  431. _itemsPresenterHostPart = GetTemplateChild(ItemsPresenterHostPartName) as Canvas;
  432. _fullModePopupPart = GetTemplateChild(FullModePopupPartName) as Popup;
  433. _fullModeSelectorPart = GetTemplateChild(FullModeSelectorPartName) as Selector;
  434. _itemsPresenterHostParent = (null != _itemsPresenterHostPart) ? _itemsPresenterHostPart.Parent as FrameworkElement : null;
  435. if (null != _itemsPresenterHostParent)
  436. {
  437. _itemsPresenterHostParent.SizeChanged += HandleItemsPresenterHostParentSizeChanged;
  438. }
  439. if (null != _itemsPresenterHostPart)
  440. {
  441. Storyboard.SetTarget(_heightAnimation, _itemsPresenterHostPart);
  442. if (!_storyboard.Children.Contains(_heightAnimation))
  443. {
  444. _storyboard.Children.Add(_heightAnimation);
  445. }
  446. }
  447. else
  448. {
  449. if (_storyboard.Children.Contains(_heightAnimation))
  450. {
  451. _storyboard.Children.Remove(_heightAnimation);
  452. }
  453. }
  454. if (null != _itemsPresenterTranslateTransformPart)
  455. {
  456. Storyboard.SetTarget(_translateAnimation, _itemsPresenterTranslateTransformPart);
  457. if (!_storyboard.Children.Contains(_translateAnimation))
  458. {
  459. _storyboard.Children.Add(_translateAnimation);
  460. }
  461. }
  462. else
  463. {
  464. if (_storyboard.Children.Contains(_translateAnimation))
  465. {
  466. _storyboard.Children.Remove(_translateAnimation);
  467. }
  468. }
  469. if (null != _fullModePopupPart)
  470. {
  471. UIElement child = _fullModePopupPart.Child;
  472. _fullModePopupPart.Child = null;
  473. _fullModePopupPart = new Popup();
  474. _fullModePopupPart.Child = child;
  475. }
  476. SetBinding(ShadowItemTemplateProperty, new Binding("ItemTemplate") { Source = this });
  477. // Commit deferred SelectedIndex (if any)
  478. if (-1 != _deferredSelectedIndex)
  479. {
  480. SelectedIndex = _deferredSelectedIndex;
  481. _deferredSelectedIndex = -1;
  482. }
  483. // Go to current state(s)
  484. GoToStates(false);
  485. }
  486. /// <summary>
  487. /// Determines if the specified item is (or is eligible to be) its own item container.
  488. /// </summary>
  489. /// <param name="item">The specified item.</param>
  490. /// <returns>True if the item is its own item container; otherwise, false.</returns>
  491. protected override bool IsItemItsOwnContainerOverride(object item)
  492. {
  493. return item is ListPickerItem;
  494. }
  495. /// <summary>
  496. /// Creates or identifies the element used to display a specified item.
  497. /// </summary>
  498. /// <returns>A container corresponding to a specified item.</returns>
  499. protected override DependencyObject GetContainerForItemOverride()
  500. {
  501. return new ListPickerItem();
  502. }
  503. /// <summary>
  504. /// Prepares the specified element to display the specified item.
  505. /// </summary>
  506. /// <param name="element">The element used to display the specified item.</param>
  507. /// <param name="item">The item to display.</param>
  508. protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
  509. {
  510. base.PrepareContainerForItemOverride(element, item);
  511. // Hook up to interesting events
  512. ContentControl container = (ContentControl)element;
  513. container.ManipulationCompleted += HandleContainerManipulationCompleted;
  514. container.SizeChanged += HandleListPickerItemSizeChanged;
  515. // Size for selected item if it's this one
  516. if (object.Equals(item, SelectedItem))
  517. {
  518. SizeForAppropriateView(false);
  519. }
  520. }
  521. /// <summary>
  522. /// Undoes the effects of the PrepareContainerForItemOverride method.
  523. /// </summary>
  524. /// <param name="element">The container element.</param>
  525. /// <param name="item">The item.</param>
  526. protected override void ClearContainerForItemOverride(DependencyObject element, object item)
  527. {
  528. base.ClearContainerForItemOverride(element, item);
  529. // Unhook from events
  530. ContentControl container = (ContentControl)element;
  531. container.ManipulationCompleted -= HandleContainerManipulationCompleted;
  532. container.SizeChanged -= HandleListPickerItemSizeChanged;
  533. }
  534. /// <summary>
  535. /// Provides handling for the ItemContainerGenerator.ItemsChanged event.
  536. /// </summary>
  537. /// <param name="e">A NotifyCollectionChangedEventArgs that contains the event data.</param>
  538. protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
  539. {
  540. base.OnItemsChanged(e);
  541. if ((0 < Items.Count) && (null == SelectedItem))
  542. {
  543. // Nothing selected (and no pending Binding); select the first item
  544. if ((null == GetBindingExpression(SelectedIndexProperty)) &&
  545. (null == GetBindingExpression(SelectedItemProperty)))
  546. {
  547. SelectedIndex = 0;
  548. }
  549. }
  550. else if (0 == Items.Count)
  551. {
  552. // No items; select nothing
  553. SelectedIndex = -1;
  554. ListPickerMode = ListPickerMode.Normal;
  555. }
  556. else if (Items.Count <= SelectedIndex)
  557. {
  558. // Selected item no longer present; select the last item
  559. SelectedIndex = Items.Count - 1;
  560. }
  561. else
  562. {
  563. // Re-synchronize SelectedIndex with SelectedItem if necessary
  564. if (!object.Equals(Items[SelectedIndex], SelectedItem))
  565. {
  566. int selectedItemIndex = Items.IndexOf(SelectedItem);
  567. if (-1 == selectedItemIndex)
  568. {
  569. SelectedItem = Items[0];
  570. }
  571. else
  572. {
  573. SelectedIndex = selectedItemIndex;
  574. }
  575. }
  576. }
  577. // Translate it into view once layout has been updated for the added/removed item(s)
  578. Dispatcher.BeginInvoke(() => SizeForAppropriateView(false));
  579. }
  580. /// <summary>
  581. /// Called when the ManipulationCompleted event occurs.
  582. /// </summary>
  583. /// <param name="e">Event data for the event.</param>
  584. protected override void OnManipulationCompleted(ManipulationCompletedEventArgs e)
  585. {
  586. if (null == e)
  587. {
  588. throw new ArgumentNullException("e");
  589. }
  590. base.OnManipulationCompleted(e);
  591. // Interaction needs to be on the _itemsPresenterHostPart or its children
  592. DependencyObject element = e.OriginalSource as DependencyObject;
  593. while (null != element)
  594. {
  595. if (_itemsPresenterHostPart == element)
  596. {
  597. // On interaction, switch to Expanded/Full mode
  598. if ((ListPickerMode.Normal == ListPickerMode) && (0 < Items.Count))
  599. {
  600. ListPickerMode = (Items.Count <= ItemCountThreshold) ? ListPickerMode.Expanded : ListPickerMode.Full;
  601. e.Handled = true;
  602. }
  603. break;
  604. }
  605. element = VisualTreeHelper.GetParent(element);
  606. }
  607. }
  608. private void HandleItemsPresenterHostParentSizeChanged(object sender, SizeChangedEventArgs e)
  609. {
  610. // Pass width through the Canvas
  611. if (null != _itemsPresenterPart)
  612. {
  613. _itemsPresenterPart.Width = e.NewSize.Width;
  614. }
  615. // Update clip to show only the selected item in Normal mode
  616. _itemsPresenterHostParent.Clip = new RectangleGeometry { Rect = new Rect(new Point(), e.NewSize) };
  617. }
  618. private void HandleListPickerItemSizeChanged(object sender, SizeChangedEventArgs e)
  619. {
  620. // Update size accordingly
  621. ContentControl container = (ContentControl)sender;
  622. if (object.Equals(ItemContainerGenerator.ItemFromContainer(container), SelectedItem))
  623. {
  624. SizeForAppropriateView(false);
  625. }
  626. }
  627. private void HandleFullModeSelectorPartSelectionChanged(object sender, SelectionChangedEventArgs e)
  628. {
  629. if (null != _fullModeSelectorPart)
  630. {
  631. // Commit selected item
  632. if (SelectedItem != _fullModeSelectorPart.SelectedItem)
  633. {
  634. SelectedItem = _fullModeSelectorPart.SelectedItem;
  635. }
  636. else
  637. {
  638. // User selected the already-selected item; just switch back to Normal view
  639. ListPickerMode = ListPickerMode.Normal;
  640. }
  641. }
  642. }
  643. private void HandleFullModeSelectorPartLoaded(object sender, RoutedEventArgs e)
  644. {
  645. if (null != _fullModeSelectorPart)
  646. {
  647. // Find the relevant container and make it look selected
  648. // Note: Selector.SelectedItem is left null so *any* selection will trigger the SelectionChanged event.
  649. // However, this doesn't highlight the "currently selected" item; the following technique fakes that.
  650. ContentControl container = _fullModeSelectorPart.ItemContainerGenerator.ContainerFromItem(SelectedItem) as ContentControl;
  651. if (null == container)
  652. {
  653. // Container isn't always available; defer until it is
  654. // Note: Assumes the container eventually WILL be available (which is why
  655. // the default Template replaces VirtualizingStackPanel with StackPanel)
  656. Dispatcher.BeginInvoke(() => HandleFullModeSelectorPartLoaded(sender, e));
  657. }
  658. else
  659. {
  660. Brush phoneAccentBrush = Application.Current.Resources["PhoneAccentBrush"] as Brush;
  661. if (null != phoneAccentBrush)
  662. {
  663. container.Foreground = phoneAccentBrush;
  664. }
  665. }
  666. // Scroll item into view if possible
  667. ListBox listBox = _fullModeSelectorPart as ListBox;
  668. if (null != listBox)
  669. {
  670. listBox.ScrollIntoView(SelectedItem);
  671. }
  672. }
  673. }
  674. private void HandlePageBackKeyPress(object sender, CancelEventArgs e)
  675. {
  676. // Revert to Normal mode
  677. ListPickerMode = ListPickerMode.Normal;
  678. e.Cancel = true;
  679. }
  680. private void HandleFrameOrientationChanged(object sender, OrientationChangedEventArgs e)
  681. {
  682. AdjustPopupChildForCurrentOrientation((PhoneApplicationFrame)sender);
  683. }
  684. private void AdjustPopupChildForCurrentOrientation(PhoneApplicationFrame frame)
  685. {
  686. if (null != _fullModePopupPart)
  687. {
  688. FrameworkElement child = _fullModePopupPart.Child as FrameworkElement;
  689. if (null != child)
  690. {
  691. // Transform child according to current orientation
  692. double actualWidth = frame.ActualWidth;
  693. double actualHeight = frame.ActualHeight;
  694. bool portrait = PageOrientation.Portrait == (PageOrientation.Portrait & frame.Orientation);
  695. TransformGroup transformGroup = new TransformGroup();
  696. switch (frame.Orientation)
  697. {
  698. case PageOrientation.LandscapeLeft:
  699. transformGroup.Children.Add(new RotateTransform { Angle = 90 });
  700. transformGroup.Children.Add(new TranslateTransform { X = actualWidth });
  701. break;
  702. case PageOrientation.LandscapeRight:
  703. transformGroup.Children.Add(new RotateTransform { Angle = -90 });
  704. transformGroup.Children.Add(new TranslateTransform { Y = actualHeight });
  705. break;
  706. }
  707. child.RenderTransform = transformGroup;
  708. // Size child to frame
  709. child.Width = portrait ? actualWidth : actualHeight;
  710. child.Height = portrait ? actualHeight : actualWidth;
  711. // Adjust padding if possible
  712. Border border = child as Border;
  713. if (null != border)
  714. {
  715. switch (frame.Orientation)
  716. {
  717. case PageOrientation.PortraitUp:
  718. border.Padding = new Thickness(0, 32, 0, 0);
  719. break;
  720. case PageOrientation.LandscapeLeft:
  721. border.Padding = new Thickness(72, 0, 0, 0);
  722. break;
  723. case PageOrientation.LandscapeRight:
  724. border.Padding = new Thickness(0, 0, 72, 0);
  725. break;
  726. }
  727. }
  728. }
  729. }
  730. }
  731. private void SizeForAppropriateView(bool animate)
  732. {
  733. switch (ListPickerMode)
  734. {
  735. case ListPickerMode.Normal:
  736. SizeForNormalMode(animate);
  737. break;
  738. case ListPickerMode.Expanded:
  739. SizeForExpandedMode();
  740. break;
  741. case ListPickerMode.Full:
  742. // Nothing to do
  743. break;
  744. }
  745. // Play the height/translation animations
  746. _storyboard.Begin();
  747. if (!animate)
  748. {
  749. _storyboard.SkipToFill();
  750. }
  751. }
  752. private void SizeForNormalMode(bool animate)
  753. {
  754. ContentControl container = (ContentControl)ItemContainerGenerator.ContainerFromItem(SelectedItem);
  755. if (null != container)
  756. {
  757. // Set height/translation to show just the selected item
  758. if (0 < container.ActualHeight)
  759. {
  760. SetContentHeight(container.ActualHeight + container.Margin.Top + container.Margin.Bottom);
  761. }
  762. if (null != _itemsPresenterTranslateTransformPart)
  763. {
  764. if (!animate)
  765. {
  766. _itemsPresenterTranslateTransformPart.Y = 0;
  767. }
  768. _translateAnimation.To = container.Margin.Top - LayoutInformation.GetLayoutSlot(container).Top;
  769. _translateAnimation.From = animate ? null : _translateAnimation.To;
  770. }
  771. }
  772. else
  773. {
  774. // Resize to minimum height
  775. SetContentHeight(0);
  776. }
  777. // Clear highlight of previously selected container
  778. ListPickerItem oldContainer = (ListPickerItem)ItemContainerGenerator.ContainerFromIndex(SelectedIndex);
  779. if (null != oldContainer)
  780. {
  781. oldContainer.IsSelected = false;
  782. }
  783. }
  784. private void SizeForExpandedMode()
  785. {
  786. // Set height and align first element at top
  787. if (null != _itemsPresenterPart)
  788. {
  789. SetContentHeight(_itemsPresenterPart.ActualHeight);
  790. }
  791. if (null != _itemsPresenterTranslateTransformPart)
  792. {
  793. _translateAnimation.To = 0;
  794. }
  795. // Highlight selected container
  796. ListPickerItem container = (ListPickerItem)ItemContainerGenerator.ContainerFromIndex(SelectedIndex);
  797. if (null != container)
  798. {
  799. container.IsSelected = true;
  800. }
  801. }
  802. private void SetContentHeight(double height)
  803. {
  804. if ((null != _itemsPresenterHostPart) && !double.IsNaN(height))
  805. {
  806. double canvasHeight = _itemsPresenterHostPart.Height;
  807. _heightAnimation.From = double.IsNaN(canvasHeight) ? height : canvasHeight;
  808. _heightAnimation.To = height;
  809. }
  810. }
  811. private void HandleFrameManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
  812. {
  813. if (ListPickerMode.Expanded == ListPickerMode)
  814. {
  815. // Manipulation outside an Expanded ListPicker reverts to Normal mode
  816. DependencyObject element = e.OriginalSource as DependencyObject;
  817. DependencyObject cancelElement = (DependencyObject)_itemsPresenterHostPart ?? (DependencyObject)this;
  818. while (null != element)
  819. {
  820. if (cancelElement == element)
  821. {
  822. return;
  823. }
  824. element = VisualTreeHelper.GetParent(element);
  825. }
  826. ListPickerMode = ListPickerMode.Normal;
  827. }
  828. }
  829. private void HandleContainerManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
  830. {
  831. if (ListPickerMode.Expanded == ListPickerMode)
  832. {
  833. // Manipulation of a container selects the item and reverts to Normal mode
  834. ContentControl container = (ContentControl)sender;
  835. SelectedItem = ItemContainerGenerator.ItemFromContainer(container);
  836. ListPickerMode = ListPickerMode.Normal;
  837. e.Handled = true;
  838. }
  839. }
  840. private void GoToStates(bool useTransitions)
  841. {
  842. switch (ListPickerMode)
  843. {
  844. case ListPickerMode.Normal:
  845. VisualStateManager.GoToState(this, PickerStatesNormalStateName, useTransitions);
  846. break;
  847. case ListPickerMode.Expanded:
  848. VisualStateManager.GoToState(this, PickerStatesExpandedStateName, useTransitions);
  849. break;
  850. case ListPickerMode.Full:
  851. // Nothing to do
  852. break;
  853. }
  854. }
  855. }
  856. }