PageRenderTime 47ms CodeModel.GetById 10ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://bitbucket.org/jeremejevs/milk-manager
C# | 1372 lines | 975 code | 165 blank | 232 comment | 260 complexity | dc952efb07a8b919b04ba1e279079b5c MD5 | raw file

Large files files are truncated, but you can click here to view the full 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.Generic;
  8. using System.Collections.Specialized;
  9. using System.ComponentModel;
  10. using System.Diagnostics.CodeAnalysis;
  11. using System.Windows;
  12. using System.Windows.Controls;
  13. using System.Windows.Controls.Primitives;
  14. using System.Windows.Data;
  15. using System.Windows.Input;
  16. using System.Windows.Media;
  17. using System.Windows.Media.Animation;
  18. using System.Windows.Navigation;
  19. namespace Microsoft.Phone.Controls
  20. {
  21. /// <summary>
  22. /// Class that implements a flexible list-picking experience with a custom interface for few/many items.
  23. /// </summary>
  24. /// <QualityBand>Preview</QualityBand>
  25. [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "This is a complicated control.")]
  26. [TemplatePart(Name = ItemsPresenterPartName, Type = typeof(ItemsPresenter))]
  27. [TemplatePart(Name = ItemsPresenterTranslateTransformPartName, Type = typeof(TranslateTransform))]
  28. [TemplatePart(Name = ItemsPresenterHostPartName, Type = typeof(Canvas))]
  29. [TemplatePart(Name = MultipleSelectionModeSummaryPartName, Type = typeof(TextBlock))]
  30. [TemplateVisualState(GroupName = PickerStatesGroupName, Name = PickerStatesNormalStateName)]
  31. [TemplateVisualState(GroupName = PickerStatesGroupName, Name = PickerStatesHighlightedStateName)]
  32. [TemplateVisualState(GroupName = PickerStatesGroupName, Name = PickerStatesDisabledStateName)]
  33. public class ListPicker : ItemsControl
  34. {
  35. private const string ItemsPresenterPartName = "ItemsPresenter";
  36. private const string ItemsPresenterTranslateTransformPartName = "ItemsPresenterTranslateTransform";
  37. private const string ItemsPresenterHostPartName = "ItemsPresenterHost";
  38. private const string MultipleSelectionModeSummaryPartName = "MultipleSelectionModeSummary";
  39. private const string BorderPartName = "Border";
  40. private const string PickerStatesGroupName = "PickerStates";
  41. private const string PickerStatesNormalStateName = "Normal";
  42. private const string PickerStatesHighlightedStateName = "Highlighted";
  43. private const string PickerStatesDisabledStateName = "Disabled";
  44. /// <summary>
  45. /// In Mango, the size of list pickers in expanded mode was given extra offset.
  46. /// </summary>
  47. private const double NormalModeOffset = 4;
  48. private readonly DoubleAnimation _heightAnimation = new DoubleAnimation();
  49. private readonly DoubleAnimation _translateAnimation = new DoubleAnimation();
  50. private readonly Storyboard _storyboard = new Storyboard();
  51. private PhoneApplicationFrame _frame;
  52. private PhoneApplicationPage _page;
  53. private FrameworkElement _itemsPresenterHostParent;
  54. private Canvas _itemsPresenterHostPart;
  55. private ItemsPresenter _itemsPresenterPart;
  56. private TranslateTransform _itemsPresenterTranslateTransformPart;
  57. private bool _updatingSelection;
  58. private int _deferredSelectedIndex = -1;
  59. private object _deferredSelectedItem = null;
  60. private object _frameContentWhenOpened;
  61. private NavigationInTransition _savedNavigationInTransition;
  62. private NavigationOutTransition _savedNavigationOutTransition;
  63. private ListPickerPage _listPickerPage;
  64. private TextBlock _multipleSelectionModeSummary;
  65. private Border _border;
  66. /// <summary>
  67. /// Whether this list picker has the picker page opened.
  68. /// </summary>
  69. private bool _hasPickerPageOpen;
  70. /// <summary>
  71. /// Event that is raised when the selection changes.
  72. /// </summary>
  73. public event SelectionChangedEventHandler SelectionChanged;
  74. /// <summary>
  75. /// Gets or sets the delegate, which is called to summarize a list of selections into a string.
  76. /// If not implemented, the default summarizing behavior will be used.
  77. /// If this delegate is implemented, default summarizing behavior can be achieved by returning
  78. /// null instead of a string.
  79. /// </summary>
  80. public Func<IList, string> SummaryForSelectedItemsDelegate
  81. {
  82. get { return (Func<IList, string>)GetValue(SummaryForSelectedItemsDelegateProperty); }
  83. set { SetValue(SummaryForSelectedItemsDelegateProperty, value); }
  84. }
  85. /// <summary>
  86. /// Identifies the SummaryForSelectedItemsDelegate DependencyProperty.
  87. /// </summary>
  88. public static readonly DependencyProperty SummaryForSelectedItemsDelegateProperty =
  89. DependencyProperty.Register("SummaryForSelectedItemsDelegate", typeof(Func<IList, string>), typeof(ListPicker), null);
  90. /// <summary>
  91. /// Gets or sets the ListPickerMode (ex: Normal/Expanded/Full).
  92. /// </summary>
  93. public ListPickerMode ListPickerMode
  94. {
  95. get { return (ListPickerMode)GetValue(ListPickerModeProperty); }
  96. private set { SetValue(ListPickerModeProperty, value); }
  97. }
  98. /// <summary>
  99. /// Identifies the ListPickerMode DependencyProperty.
  100. /// </summary>
  101. public static readonly DependencyProperty ListPickerModeProperty =
  102. DependencyProperty.Register("ListPickerMode", typeof(ListPickerMode), typeof(ListPicker), new PropertyMetadata(ListPickerMode.Normal, OnListPickerModeChanged));
  103. private static void OnListPickerModeChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  104. {
  105. ((ListPicker)o).OnListPickerModeChanged((ListPickerMode)e.OldValue, (ListPickerMode)e.NewValue);
  106. }
  107. private void OnListPickerModeChanged(ListPickerMode oldValue, ListPickerMode newValue)
  108. {
  109. if ((ListPickerMode.Expanded == oldValue))
  110. {
  111. if (null != _page)
  112. {
  113. _page.BackKeyPress -= OnPageBackKeyPress;
  114. _page = null;
  115. }
  116. if (null != _frame)
  117. {
  118. _frame.ManipulationStarted -= OnFrameManipulationStarted;
  119. _frame = null;
  120. }
  121. }
  122. if (ListPickerMode.Expanded == newValue)
  123. {
  124. // Hook up to frame if not already done
  125. if (null == _frame)
  126. {
  127. _frame = Application.Current.RootVisual as PhoneApplicationFrame;
  128. if (null != _frame)
  129. {
  130. _frame.AddHandler(ManipulationStartedEvent, new EventHandler<ManipulationStartedEventArgs>(OnFrameManipulationStarted), true);
  131. }
  132. }
  133. if (null != _frame)
  134. {
  135. _page = _frame.Content as PhoneApplicationPage;
  136. if (null != _page)
  137. {
  138. _page.BackKeyPress += OnPageBackKeyPress;
  139. }
  140. }
  141. }
  142. if (ListPickerMode.Full == oldValue)
  143. {
  144. ClosePickerPage();
  145. }
  146. if (ListPickerMode.Full == newValue)
  147. {
  148. OpenPickerPage();
  149. }
  150. SizeForAppropriateView(ListPickerMode.Full != oldValue);
  151. IsHighlighted = (ListPickerMode.Expanded == newValue);
  152. }
  153. /// <summary>
  154. /// Whether the list picker is highlighted.
  155. /// This occurs when the user is manipulating the box or when in expanded mode.
  156. /// </summary>
  157. private bool IsHighlighted
  158. {
  159. get { return (bool)GetValue(IsHighlightedProperty); }
  160. set { SetValue(IsHighlightedProperty, value); }
  161. }
  162. private static readonly DependencyProperty IsHighlightedProperty =
  163. DependencyProperty.Register("IsHighlighted",
  164. typeof(bool),
  165. typeof(ListPicker),
  166. new PropertyMetadata(false, new PropertyChangedCallback(OnIsHighlightedChanged)));
  167. /// <summary>
  168. /// Highlight property changed
  169. /// </summary>
  170. private static void OnIsHighlightedChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  171. {
  172. (o as ListPicker).OnIsHighlightedChanged();
  173. }
  174. /// <summary>
  175. /// Highlight property changed
  176. /// </summary>
  177. private void OnIsHighlightedChanged()
  178. {
  179. UpdateVisualStates(true);
  180. }
  181. /// <summary>
  182. /// Enabled property changed
  183. /// </summary>
  184. private static void OnIsEnabledChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  185. {
  186. (o as ListPicker).OnIsEnabledChanged();
  187. }
  188. /// <summary>
  189. /// Enabled property changed
  190. /// </summary>
  191. private void OnIsEnabledChanged()
  192. {
  193. UpdateVisualStates(true);
  194. }
  195. /// <summary>
  196. /// Gets or sets the index of the selected item.
  197. /// </summary>
  198. public int SelectedIndex
  199. {
  200. get { return (int)GetValue(SelectedIndexProperty); }
  201. set { SetValue(SelectedIndexProperty, value); }
  202. }
  203. /// <summary>
  204. /// Identifies the SelectedIndex DependencyProperty.
  205. /// </summary>
  206. public static readonly DependencyProperty SelectedIndexProperty =
  207. DependencyProperty.Register("SelectedIndex", typeof(int), typeof(ListPicker), new PropertyMetadata(-1, OnSelectedIndexChanged));
  208. private static void OnSelectedIndexChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  209. {
  210. ((ListPicker)o).OnSelectedIndexChanged((int)e.OldValue, (int)e.NewValue);
  211. }
  212. [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "SelectedIndex", Justification = "Property name.")]
  213. private void OnSelectedIndexChanged(int oldValue, int newValue)
  214. {
  215. // Validate new value
  216. if ((Items.Count <= newValue) ||
  217. ((0 < Items.Count) && (newValue < 0)) ||
  218. ((0 == Items.Count) && (newValue != -1)))
  219. {
  220. if ((null == Template) && (0 <= newValue))
  221. {
  222. // Can't set the value now; remember it for later
  223. _deferredSelectedIndex = newValue;
  224. return;
  225. }
  226. throw new InvalidOperationException(Properties.Resources.InvalidSelectedIndex);
  227. }
  228. // Synchronize SelectedItem property
  229. if (!_updatingSelection)
  230. {
  231. _updatingSelection = true;
  232. SelectedItem = (-1 != newValue) ? Items[newValue] : null;
  233. _updatingSelection = false;
  234. }
  235. if (-1 != oldValue)
  236. {
  237. // Toggle container selection
  238. ListPickerItem oldContainer = (ListPickerItem)ItemContainerGenerator.ContainerFromIndex(oldValue);
  239. if (null != oldContainer)
  240. {
  241. oldContainer.IsSelected = false;
  242. }
  243. }
  244. }
  245. /// <summary>
  246. /// Gets or sets the selected item.
  247. /// </summary>
  248. public object SelectedItem
  249. {
  250. get { return (object)GetValue(SelectedItemProperty); }
  251. set { SetValue(SelectedItemProperty, value); }
  252. }
  253. /// <summary>
  254. /// Identifies the SelectedItem DependencyProperty.
  255. /// </summary>
  256. public static readonly DependencyProperty SelectedItemProperty =
  257. DependencyProperty.Register("SelectedItem", typeof(object), typeof(ListPicker), new PropertyMetadata(null, OnSelectedItemChanged));
  258. private static void OnSelectedItemChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  259. {
  260. ((ListPicker)o).OnSelectedItemChanged(e.OldValue, e.NewValue);
  261. }
  262. [SuppressMessage("Microsoft.Naming", "CA2204:Literals should be spelled correctly", MessageId = "SelectedItem", Justification = "Property name.")]
  263. private void OnSelectedItemChanged(object oldValue, object newValue)
  264. {
  265. if (newValue != null && (null == Items || Items.Count == 0))
  266. {
  267. if (null == Template)
  268. {
  269. // Can't set the value now; remember it for later
  270. _deferredSelectedItem = newValue;
  271. return;
  272. }
  273. else
  274. {
  275. throw new InvalidOperationException(Properties.Resources.InvalidSelectedItem);
  276. }
  277. }
  278. // Validate new value
  279. int newValueIndex = (null != newValue) ? Items.IndexOf(newValue) : -1;
  280. if ((-1 == newValueIndex) && (0 < Items.Count))
  281. {
  282. throw new InvalidOperationException(Properties.Resources.InvalidSelectedItem);
  283. }
  284. // Synchronize SelectedIndex property
  285. if (!_updatingSelection)
  286. {
  287. _updatingSelection = true;
  288. SelectedIndex = newValueIndex;
  289. _updatingSelection = false;
  290. }
  291. // Switch to Normal mode or size for current item
  292. if (ListPickerMode.Normal != ListPickerMode)
  293. {
  294. ListPickerMode = ListPickerMode.Normal;
  295. }
  296. else
  297. {
  298. SizeForAppropriateView(false);
  299. }
  300. // Fire SelectionChanged event
  301. var handler = SelectionChanged;
  302. if (null != handler)
  303. {
  304. IList removedItems = (null == oldValue) ? new object[0] : new object[] { oldValue };
  305. IList addedItems = (null == newValue) ? new object[0] : new object[] { newValue };
  306. handler(this, new SelectionChangedEventArgs(removedItems, addedItems));
  307. }
  308. }
  309. private static readonly DependencyProperty ShadowItemTemplateProperty =
  310. DependencyProperty.Register("ShadowItemTemplate", typeof(DataTemplate), typeof(ListPicker), new PropertyMetadata(null, OnShadowOrFullModeItemTemplateChanged));
  311. /// <summary>
  312. /// Gets or sets the DataTemplate used to display each item when ListPickerMode is set to Full.
  313. /// </summary>
  314. public DataTemplate FullModeItemTemplate
  315. {
  316. get { return (DataTemplate)GetValue(FullModeItemTemplateProperty); }
  317. set { SetValue(FullModeItemTemplateProperty, value); }
  318. }
  319. /// <summary>
  320. /// Identifies the FullModeItemTemplate DependencyProperty.
  321. /// </summary>
  322. public static readonly DependencyProperty FullModeItemTemplateProperty =
  323. DependencyProperty.Register("FullModeItemTemplate", typeof(DataTemplate), typeof(ListPicker), new PropertyMetadata(null, OnShadowOrFullModeItemTemplateChanged));
  324. private static void OnShadowOrFullModeItemTemplateChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  325. {
  326. ((ListPicker)o).OnShadowOrFullModeItemTemplateChanged(/*(DataTemplate)e.OldValue, (DataTemplate)e.NewValue*/);
  327. }
  328. private void OnShadowOrFullModeItemTemplateChanged(/*DataTemplate oldValue, DataTemplate newValue*/)
  329. {
  330. // Set ActualFullModeItemTemplate accordingly
  331. SetValue(ActualFullModeItemTemplateProperty, FullModeItemTemplate ?? ItemTemplate);
  332. }
  333. private static readonly DependencyProperty ActualFullModeItemTemplateProperty =
  334. DependencyProperty.Register("ActualFullModeItemTemplate", typeof(DataTemplate), typeof(ListPicker), null);
  335. /// <summary>
  336. /// Gets or sets the header of the control.
  337. /// </summary>
  338. public object Header
  339. {
  340. get { return (object)GetValue(HeaderProperty); }
  341. set { SetValue(HeaderProperty, value); }
  342. }
  343. /// <summary>
  344. /// Identifies the Header DependencyProperty.
  345. /// </summary>
  346. public static readonly DependencyProperty HeaderProperty =
  347. DependencyProperty.Register("Header", typeof(object), typeof(ListPicker), null);
  348. /// <summary>
  349. /// Gets or sets the template used to display the control's header.
  350. /// </summary>
  351. public DataTemplate HeaderTemplate
  352. {
  353. get { return (DataTemplate)GetValue(HeaderTemplateProperty); }
  354. set { SetValue(HeaderTemplateProperty, value); }
  355. }
  356. /// <summary>
  357. /// Identifies the HeaderTemplate DependencyProperty.
  358. /// </summary>
  359. public static readonly DependencyProperty HeaderTemplateProperty =
  360. DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(ListPicker), null);
  361. /// <summary>
  362. /// Gets or sets the header to use when ListPickerMode is set to Full.
  363. /// </summary>
  364. public object FullModeHeader
  365. {
  366. get { return (object)GetValue(FullModeHeaderProperty); }
  367. set { SetValue(FullModeHeaderProperty, value); }
  368. }
  369. /// <summary>
  370. /// Identifies the FullModeHeader DependencyProperty.
  371. /// </summary>
  372. public static readonly DependencyProperty FullModeHeaderProperty =
  373. DependencyProperty.Register("FullModeHeader", typeof(object), typeof(ListPicker), null);
  374. /// <summary>
  375. /// Gets the maximum number of items for which Expanded mode will be used, 5.
  376. /// </summary>
  377. public int ItemCountThreshold
  378. {
  379. get { return (int)GetValue(ItemCountThresholdProperty); }
  380. private set { SetValue(ItemCountThresholdProperty, value); }
  381. }
  382. /// <summary>
  383. /// Identifies the ItemCountThreshold DependencyProperty.
  384. /// </summary>
  385. public static readonly DependencyProperty ItemCountThresholdProperty =
  386. DependencyProperty.Register("ItemCountThreshold", typeof(int), typeof(ListPicker), new PropertyMetadata(5, OnItemCountThresholdChanged));
  387. private static void OnItemCountThresholdChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  388. {
  389. ((ListPicker)o).OnItemCountThresholdChanged(/*(int)e.OldValue,*/ (int)e.NewValue);
  390. }
  391. [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Following DependencyProperty property changed handler convention.")]
  392. [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "Providing the DependencyProperty name is preferred here.")]
  393. private void OnItemCountThresholdChanged(/*int oldValue,*/ int newValue)
  394. {
  395. if (newValue < 0)
  396. {
  397. throw new ArgumentOutOfRangeException("ItemCountThreshold");
  398. }
  399. }
  400. /// <summary>
  401. /// Gets or sets the Uri to use for loading the ListPickerPage instance when the control is tapped.
  402. /// </summary>
  403. public Uri PickerPageUri
  404. {
  405. get { return (Uri)GetValue(PickerPageUriProperty); }
  406. set { SetValue(PickerPageUriProperty, value); }
  407. }
  408. /// <summary>
  409. /// Identifies the PickerPageUri DependencyProperty.
  410. /// </summary>
  411. public static readonly DependencyProperty PickerPageUriProperty = DependencyProperty.Register(
  412. "PickerPageUri", typeof(Uri), typeof(ListPicker), null);
  413. /// <summary>
  414. /// Gets or sets how the list picker expands when tapped.
  415. /// This property has an effect only when SelectionMode is Single.
  416. /// When SelectionMode is Multiple, the ExpansionMode will be treated as FullScreenOnly.
  417. /// ExpansionAllowed will only expand when the number of items is less than or equalt to ItemCountThreshold
  418. /// Single by default.
  419. /// </summary>
  420. public ExpansionMode ExpansionMode
  421. {
  422. get { return (ExpansionMode)GetValue(ExpansionModeProperty); }
  423. set { SetValue(ExpansionModeProperty, value); }
  424. }
  425. /// <summary>
  426. /// Identifies the ExpansionMode DependencyProperty.
  427. /// </summary>
  428. public static readonly DependencyProperty ExpansionModeProperty = DependencyProperty.Register(
  429. "ExpansionMode",
  430. typeof(ExpansionMode),
  431. typeof(ListPicker),
  432. new PropertyMetadata(ExpansionMode.ExpansionAllowed, null)
  433. );
  434. /// <summary>
  435. /// Gets or sets the SelectionMode. Extended is treated as Multiple.
  436. /// Single by default.
  437. /// </summary>
  438. public SelectionMode SelectionMode
  439. {
  440. get { return (SelectionMode)GetValue(SelectionModeProperty); }
  441. set { SetValue(SelectionModeProperty, value); }
  442. }
  443. /// <summary>
  444. /// Identifies the SelectionMode DependencyProperty.
  445. /// </summary>
  446. public static readonly DependencyProperty SelectionModeProperty = DependencyProperty.Register(
  447. "SelectionMode",
  448. typeof(SelectionMode),
  449. typeof(ListPicker),
  450. new PropertyMetadata(SelectionMode.Single, OnSelectionModeChanged)
  451. );
  452. private static void OnSelectionModeChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  453. {
  454. ((ListPicker)o).OnSelectionModeChanged((SelectionMode)e.NewValue);
  455. }
  456. private void OnSelectionModeChanged(SelectionMode newValue)
  457. {
  458. // Show/Hide the multiple selection mode summary text block or the items presenter depending on which selection mode chosen
  459. if (newValue == SelectionMode.Multiple || newValue == SelectionMode.Extended)
  460. {
  461. if (_multipleSelectionModeSummary != null && _itemsPresenterHostPart != null)
  462. {
  463. _multipleSelectionModeSummary.Visibility = Visibility.Visible;
  464. _itemsPresenterHostPart.Visibility = Visibility.Collapsed;
  465. }
  466. }
  467. else
  468. {
  469. if (_multipleSelectionModeSummary != null && _itemsPresenterHostPart != null)
  470. {
  471. _multipleSelectionModeSummary.Visibility = Visibility.Collapsed;
  472. _itemsPresenterHostPart.Visibility = Visibility.Visible;
  473. }
  474. }
  475. }
  476. /// <summary>
  477. /// Gets the selected items.
  478. /// </summary>
  479. [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification="Want to allow this to be bound to.")]
  480. public IList SelectedItems
  481. {
  482. get { return (IList)GetValue(SelectedItemsProperty); }
  483. set { SetValue(SelectedItemsProperty, value); }
  484. }
  485. /// <summary>
  486. /// Identifies the SelectedItems DependencyProperty.
  487. /// </summary>
  488. public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register(
  489. "SelectedItems",
  490. typeof(IList),
  491. typeof(ListPicker),
  492. new PropertyMetadata(OnSelectedItemsChanged)
  493. );
  494. private static void OnSelectedItemsChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  495. {
  496. ((ListPicker)o).OnSelectedItemsChanged((IList)e.OldValue, (IList)e.NewValue);
  497. }
  498. private void OnSelectedItemsChanged(IList oldValue, IList newValue)
  499. {
  500. UpdateSummary(newValue);
  501. // Fire SelectionChanged event
  502. var handler = SelectionChanged;
  503. if (null != handler)
  504. {
  505. IList removedItems = new List<object>();
  506. if (null != oldValue)
  507. {
  508. foreach (object o in oldValue)
  509. {
  510. if (null == newValue || !newValue.Contains(o))
  511. {
  512. removedItems.Add(o);
  513. }
  514. }
  515. }
  516. IList addedItems = new List<object>();
  517. if (null != newValue)
  518. {
  519. foreach (object o in newValue)
  520. {
  521. if (null == oldValue || !oldValue.Contains(o))
  522. {
  523. addedItems.Add(o);
  524. }
  525. }
  526. }
  527. handler(this, new SelectionChangedEventArgs(removedItems, addedItems));
  528. }
  529. }
  530. /// <summary>
  531. /// Initializes a new instance of the ListPicker class.
  532. /// </summary>
  533. public ListPicker()
  534. {
  535. DefaultStyleKey = typeof(ListPicker);
  536. Storyboard.SetTargetProperty(_heightAnimation, new PropertyPath(FrameworkElement.HeightProperty));
  537. Storyboard.SetTargetProperty(_translateAnimation, new PropertyPath(TranslateTransform.YProperty));
  538. // Would be nice if these values were customizable (ex: as DependencyProperties or in Template as VSM states)
  539. Duration duration = TimeSpan.FromSeconds(0.2);
  540. _heightAnimation.Duration = duration;
  541. _translateAnimation.Duration = duration;
  542. IEasingFunction easingFunction = new ExponentialEase { EasingMode = EasingMode.EaseInOut, Exponent = 4 };
  543. _heightAnimation.EasingFunction = easingFunction;
  544. _translateAnimation.EasingFunction = easingFunction;
  545. this.RegisterNotification("IsEnabled", OnIsEnabledChanged);
  546. Loaded += OnLoaded;
  547. Unloaded += OnUnloaded;
  548. }
  549. private void OnLoaded(object sender, RoutedEventArgs e)
  550. {
  551. UpdateVisualStates (true);
  552. }
  553. private void OnUnloaded(object sender, RoutedEventArgs e)
  554. {
  555. // Unhook any remaining event handlers
  556. if (null != _frame)
  557. {
  558. _frame.ManipulationStarted -= OnFrameManipulationStarted;
  559. _frame = null;
  560. }
  561. }
  562. /// <summary>
  563. /// Builds the visual tree for the control when a new template is applied.
  564. /// </summary>
  565. public override void OnApplyTemplate()
  566. {
  567. // Unhook from old elements
  568. if (null != _itemsPresenterHostParent)
  569. {
  570. _itemsPresenterHostParent.SizeChanged -= OnItemsPresenterHostParentSizeChanged;
  571. }
  572. _storyboard.Stop();
  573. base.OnApplyTemplate();
  574. // Hook up to new elements
  575. _itemsPresenterPart = GetTemplateChild(ItemsPresenterPartName) as ItemsPresenter;
  576. _itemsPresenterTranslateTransformPart = GetTemplateChild(ItemsPresenterTranslateTransformPartName) as TranslateTransform;
  577. _itemsPresenterHostPart = GetTemplateChild(ItemsPresenterHostPartName) as Canvas;
  578. _itemsPresenterHostParent = (null != _itemsPresenterHostPart) ? _itemsPresenterHostPart.Parent as FrameworkElement : null;
  579. _multipleSelectionModeSummary = GetTemplateChild(MultipleSelectionModeSummaryPartName) as TextBlock;
  580. _border = GetTemplateChild(BorderPartName) as Border;
  581. if (null != _itemsPresenterHostParent)
  582. {
  583. _itemsPresenterHostParent.SizeChanged += OnItemsPresenterHostParentSizeChanged;
  584. }
  585. if (null != _itemsPresenterHostPart)
  586. {
  587. Storyboard.SetTarget(_heightAnimation, _itemsPresenterHostPart);
  588. if (!_storyboard.Children.Contains(_heightAnimation))
  589. {
  590. _storyboard.Children.Add(_heightAnimation);
  591. }
  592. }
  593. else
  594. {
  595. if (_storyboard.Children.Contains(_heightAnimation))
  596. {
  597. _storyboard.Children.Remove(_heightAnimation);
  598. }
  599. }
  600. if (null != _itemsPresenterTranslateTransformPart)
  601. {
  602. Storyboard.SetTarget(_translateAnimation, _itemsPresenterTranslateTransformPart);
  603. if (!_storyboard.Children.Contains(_translateAnimation))
  604. {
  605. _storyboard.Children.Add(_translateAnimation);
  606. }
  607. }
  608. else
  609. {
  610. if (_storyboard.Children.Contains(_translateAnimation))
  611. {
  612. _storyboard.Children.Remove(_translateAnimation);
  613. }
  614. }
  615. SetBinding(ShadowItemTemplateProperty, new Binding("ItemTemplate") { Source = this });
  616. // Commit deferred SelectedIndex (if any)
  617. if (-1 != _deferredSelectedIndex)
  618. {
  619. SelectedIndex = _deferredSelectedIndex;
  620. _deferredSelectedIndex = -1;
  621. }
  622. if (null != _deferredSelectedItem)
  623. {
  624. SelectedItem = _deferredSelectedItem;
  625. _deferredSelectedItem = null;
  626. }
  627. OnSelectionModeChanged(SelectionMode);
  628. OnSelectedItemsChanged(SelectedItems, SelectedItems);
  629. }
  630. /// <summary>
  631. /// Determines if the specified item is (or is eligible to be) its own item container.
  632. /// </summary>
  633. /// <param name="item">The specified item.</param>
  634. /// <returns>True if the item is its own item container; otherwise, false.</returns>
  635. protected override bool IsItemItsOwnContainerOverride(object item)
  636. {
  637. return item is ListPickerItem;
  638. }
  639. /// <summary>
  640. /// Creates or identifies the element used to display a specified item.
  641. /// </summary>
  642. /// <returns>A container corresponding to a specified item.</returns>
  643. protected override DependencyObject GetContainerForItemOverride()
  644. {
  645. return new ListPickerItem();
  646. }
  647. /// <summary>
  648. /// Prepares the specified element to display the specified item.
  649. /// </summary>
  650. /// <param name="element">The element used to display the specified item.</param>
  651. /// <param name="item">The item to display.</param>
  652. protected override void PrepareContainerForItemOverride(DependencyObject element, object item)
  653. {
  654. base.PrepareContainerForItemOverride(element, item);
  655. // Hook up to interesting events
  656. ContentControl container = (ContentControl)element;
  657. container.Tap += OnContainerTap;
  658. container.SizeChanged += OnListPickerItemSizeChanged;
  659. // Size for selected item if it's this one
  660. if (object.Equals(item, SelectedItem))
  661. {
  662. SizeForAppropriateView(false);
  663. }
  664. }
  665. /// <summary>
  666. /// Undoes the effects of the PrepareContainerForItemOverride method.
  667. /// </summary>
  668. /// <param name="element">The container element.</param>
  669. /// <param name="item">The item.</param>
  670. protected override void ClearContainerForItemOverride(DependencyObject element, object item)
  671. {
  672. base.ClearContainerForItemOverride(element, item);
  673. // Unhook from events
  674. ContentControl container = (ContentControl)element;
  675. container.Tap -= OnContainerTap;
  676. container.SizeChanged -= OnListPickerItemSizeChanged;
  677. }
  678. /// <summary>
  679. /// Provides handling for the ItemContainerGenerator.ItemsChanged event.
  680. /// </summary>
  681. /// <param name="e">A NotifyCollectionChangedEventArgs that contains the event data.</param>
  682. protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e)
  683. {
  684. base.OnItemsChanged(e);
  685. if ((0 < Items.Count) && (null == SelectedItem))
  686. {
  687. // Nothing selected (and no pending Binding); select the first item
  688. if ((null == GetBindingExpression(SelectedIndexProperty)) &&
  689. (null == GetBindingExpression(SelectedItemProperty)))
  690. {
  691. SelectedIndex = 0;
  692. }
  693. }
  694. else if (0 == Items.Count)
  695. {
  696. // No items; select nothing
  697. SelectedIndex = -1;
  698. ListPickerMode = ListPickerMode.Normal;
  699. }
  700. else if (Items.Count <= SelectedIndex)
  701. {
  702. // Selected item no longer present; select the last item
  703. SelectedIndex = Items.Count - 1;
  704. }
  705. else
  706. {
  707. // Re-synchronize SelectedIndex with SelectedItem if necessary
  708. if (!object.Equals(Items[SelectedIndex], SelectedItem))
  709. {
  710. int selectedItemIndex = Items.IndexOf(SelectedItem);
  711. if (-1 == selectedItemIndex)
  712. {
  713. SelectedItem = Items[0];
  714. }
  715. else
  716. {
  717. SelectedIndex = selectedItemIndex;
  718. }
  719. }
  720. }
  721. // Translate it into view once layout has been updated for the added/removed item(s)
  722. Dispatcher.BeginInvoke(() => SizeForAppropriateView(false));
  723. }
  724. private bool IsValidManipulation(object OriginalSource, Point p)
  725. {
  726. DependencyObject element = OriginalSource as DependencyObject;
  727. while (null != element)
  728. {
  729. if (_itemsPresenterHostPart == element || _multipleSelectionModeSummary == element || _border == element)
  730. {
  731. double Padding = 11.0;
  732. return (p.X > 0 && p.Y > 0 - Padding && p.X < _border.RenderSize.Width && p.Y < _border.RenderSize.Height + Padding);
  733. }
  734. element = VisualTreeHelper.GetParent(element);
  735. }
  736. return false;
  737. }
  738. /// <summary>
  739. /// Handles the tap event.
  740. /// </summary>
  741. /// <param name="e">Event args</param>
  742. protected override void OnTap(System.Windows.Input.GestureEventArgs e)
  743. {
  744. if (null == e)
  745. {
  746. throw new ArgumentNullException("e");
  747. }
  748. if (ListPickerMode == ListPickerMode.Normal)
  749. {
  750. if (!IsEnabled)
  751. {
  752. e.Handled = true;
  753. return;
  754. }
  755. Point p = e.GetPosition((UIElement)e.OriginalSource);
  756. if (IsValidManipulation(e.OriginalSource, p))
  757. {
  758. if (Open())
  759. {
  760. e.Handled = true;
  761. }
  762. }
  763. }
  764. }
  765. /// <summary>
  766. /// Called when the ManipulationStarted event occurs.
  767. /// </summary>
  768. /// <param name="e">Event data for the event.</param>
  769. protected override void OnManipulationStarted(ManipulationStartedEventArgs e)
  770. {
  771. if (null == e)
  772. {
  773. throw new ArgumentNullException("e");
  774. }
  775. base.OnManipulationStarted(e);
  776. if (ListPickerMode == ListPickerMode.Normal)
  777. {
  778. if (!IsEnabled)
  779. {
  780. e.Complete();
  781. return;
  782. }
  783. Point p = e.ManipulationOrigin;
  784. if (e.OriginalSource != e.ManipulationContainer)
  785. {
  786. p = e.ManipulationContainer.TransformToVisual((UIElement)e.OriginalSource).Transform(p);
  787. }
  788. if (IsValidManipulation(e.OriginalSource, p))
  789. {
  790. IsHighlighted = true;
  791. }
  792. }
  793. }
  794. /// <summary>
  795. /// Called when the ManipulationDelta event occurs.
  796. /// </summary>
  797. /// <param name="e">Event data for the event.</param>
  798. protected override void OnManipulationDelta(ManipulationDeltaEventArgs e)
  799. {
  800. if (null == e)
  801. {
  802. throw new ArgumentNullException("e");
  803. }
  804. base.OnManipulationDelta(e);
  805. if (ListPickerMode == ListPickerMode.Normal)
  806. {
  807. if (!IsEnabled)
  808. {
  809. e.Complete();
  810. return;
  811. }
  812. Point p = e.ManipulationOrigin;
  813. if (e.OriginalSource != e.ManipulationContainer)
  814. {
  815. p = e.ManipulationContainer.TransformToVisual((UIElement)e.OriginalSource).Transform(p);
  816. }
  817. if (!IsValidManipulation(e.OriginalSource, p))
  818. {
  819. IsHighlighted = false;
  820. e.Complete();
  821. }
  822. }
  823. }
  824. /// <summary>
  825. /// Called when the ManipulationCompleted event occurs.
  826. /// </summary>
  827. /// <param name="e">Event data for the event.</param>
  828. protected override void OnManipulationCompleted(ManipulationCompletedEventArgs e)
  829. {
  830. if (null == e)
  831. {
  832. throw new ArgumentNullException("e");
  833. }
  834. base.OnManipulationCompleted(e);
  835. if (!IsEnabled)
  836. {
  837. return;
  838. }
  839. if (ListPickerMode == ListPickerMode.Normal)
  840. {
  841. // Style box to look unselected
  842. IsHighlighted = false;
  843. }
  844. }
  845. /// <summary>
  846. /// Opens the picker for selection either into Expanded or Full mode depending on the picker's state.
  847. /// </summary>
  848. /// <returns>Whether the picker was succesfully opened.</returns>
  849. public bool Open()
  850. {
  851. if (SelectionMode == SelectionMode.Single)
  852. {
  853. // On interaction, switch to Expanded/Full mode
  854. if ((ListPickerMode.Normal == ListPickerMode))
  855. {
  856. if (ExpansionMode == ExpansionMode.ExpansionAllowed && Items.Count <= ItemCountThreshold)
  857. {
  858. ListPickerMode = ListPickerMode.Expanded;
  859. }
  860. else
  861. {
  862. ListPickerMode = ListPickerMode.Full;
  863. }
  864. return true;
  865. }
  866. }
  867. else
  868. {
  869. ListPickerMode = ListPickerMode.Full;
  870. return true;
  871. }
  872. return false;
  873. }
  874. private void OnItemsPresenterHostParentSizeChanged(object sender, SizeChangedEventArgs e)
  875. {
  876. if (null != _itemsPresenterPart && null != _itemsPresenterHostPart && (e.NewSize.Width != e.PreviousSize.Width || e.NewSize.Width == 0))
  877. {
  878. // The control size has changed and we need to update the items presenter's size as well
  879. // as its host's size (the canvas).
  880. UpdateItemsPresenterWidth(e.NewSize.Width);
  881. }
  882. // Update clip to show only the selected item in Normal mode
  883. _itemsPresenterHostParent.Clip = new RectangleGeometry { Rect = new Rect(new Point(), e.NewSize) };
  884. }
  885. private void UpdateItemsPresenterWidth(double availableWidth)
  886. {
  887. // First, we clear everthing and we measure the items presenter desired size.
  888. _itemsPresenterPart.Width = _itemsPresenterHostPart.Width = double.NaN;
  889. _itemsPresenterPart.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
  890. // We set the host's width to the presenter's desired width only if no explicit width is set and
  891. // the horizontal alignment isn't stretch (when the horizontal alignment is stretch, the canvas is
  892. // automatically stretched).
  893. if (double.IsNaN(Width) && HorizontalAlignment != HorizontalAlignment.Stretch)
  894. {
  895. _itemsPresenterHostPart.Width = _itemsPresenterPart.DesiredSize.Width;
  896. }
  897. if (availableWidth > _itemsPresenterPart.DesiredSize.Width)
  898. _itemsPresenterPart.Width = availableWidth;
  899. }
  900. private void OnListPickerItemSizeChanged(object sender, SizeChangedEventArgs e)
  901. {
  902. // Update size accordingly
  903. ContentControl container = (ContentControl)sender;
  904. if (object.Equals(ItemContainerGenerator.ItemFromContainer(container), SelectedItem))
  905. {
  906. SizeForAppropriateView(false);
  907. }
  908. // Updates the host's width to reflect the items presenter desired width.
  909. if (double.IsNaN(Width) && HorizontalAlignment != HorizontalAlignment.Stretch)
  910. {
  911. _itemsPresenterHostPart.Width = _itemsPresenterPart.DesiredSize.Width;
  912. }
  913. }
  914. private void OnPageBackKeyPress(object sender, CancelEventArgs e)
  915. {
  916. // Revert to Normal mode
  917. ListPickerMode = ListPickerMode.Normal;
  918. e.Cancel = true;
  919. }
  920. private void SizeForAppropriateView(bool animate)
  921. {
  922. switch (ListPickerMode)
  923. {
  924. case ListPickerMode.Normal:
  925. SizeForNormalMode(animate);
  926. break;
  927. case ListPickerMode.Expanded:
  928. SizeForExpandedMode();
  929. break;
  930. case ListPickerMode.Full:
  931. // Nothing to do
  932. return;
  933. }
  934. // Play the height/translation animations
  935. _storyboard.Begin();
  936. if (!animate)
  937. {
  938. _storyboard.SkipToFill();
  939. }
  940. }
  941. private void SizeForNormalMode(bool animate)
  942. {
  943. ContentControl container = (ContentControl)ItemContainerGenerator.ContainerFromItem(SelectedItem);
  944. if (null != container)
  945. {
  946. // Set height/translation to show just the selected item
  947. if (0 < container.ActualHeight)
  948. {
  949. SetContentHeight(container.ActualHeight + container.Margin.Top + container.Margin.Bottom - (NormalModeOffset * 2));
  950. }
  951. if (null != _itemsPresenterTranslateTransformPart)
  952. {
  953. if (!animate)
  954. {
  955. _itemsPresenterTranslateTransformPart.Y = -NormalModeOffset;
  956. }
  957. _translateAnimation.To = container.Margin.Top - LayoutInformation.GetLayoutSlot(container).Top - NormalModeOffset;
  958. _translateAnimation.From = animate ? null : _translateAnimation.To;
  959. }
  960. }
  961. else
  962. {
  963. // Resize to minimum height
  964. SetContentHeight(0);
  965. }
  966. // Clear highlight of previously selected container
  967. ListPickerItem oldContainer = (ListPickerItem)ItemContainerGenerator.ContainerFromIndex(SelectedIndex);
  968. if (null != oldContainer)
  969. {
  970. oldContainer.IsSelected = false;
  971. }
  972. }
  973. private void SizeForExpandedMode()
  974. {
  975. // Set height and align first element at top
  976. if (null != _itemsPresenterPart)
  977. {
  978. SetContentHeight(_itemsPresenterPart.ActualHeight);
  979. }
  980. if (null != _itemsPresenterTranslateTransformPart)
  981. {
  982. _translateAnimation.To = 0;
  983. }
  984. // Highlight selected container
  985. ListPickerItem container = (ListPickerItem)ItemContainerGenerator.ContainerFromIndex(SelectedIndex);
  986. if (null != container)
  987. {
  988. container.IsSelected = true;
  989. }
  990. }
  991. private void SetContentHeight(double height)
  992. {
  993. if ((null != _itemsPresenterHostPart) && !double.IsNaN(height))
  994. {
  995. double canvasHeight = _itemsPresenterHostPart.Height;
  996. _heightAnimation.From = double.IsNaN(canvasHeight) ? height : canvasHeight;
  997. _heightAnimation.To = height;
  998. }
  999. }
  1000. private void OnFrameManipulationStarted(object sender, ManipulationStartedEventArgs e)
  1001. {
  1002. if (ListPickerMode.Expanded == ListPickerMode)
  1003. {
  1004. // Manipulation outside an Expanded ListPicker reverts to Normal mode
  1005. DependencyObject element = e.OriginalSource as DependencyObject;
  1006. DependencyObject cancelElement = (DependencyObject)_itemsPresenterHostPart ?? (DependencyObject)this;
  1007. while (null != element)
  1008. {
  1009. if (cancelElement == element)
  1010. {
  1011. return;
  1012. }
  1013. element = VisualTreeHelper.GetParent(element);
  1014. }
  1015. ListPickerMode = ListPickerMode.Normal;
  1016. }
  1017. }
  1018. private void OnContainerTap(object sender, System.Windows.Input.GestureEventArgs e)
  1019. {
  1020. if (ListPickerMode.Expanded == ListPickerMode)
  1021. {
  1022. // Manipulation of a container selects the item and reverts to Normal mode
  1023. ContentControl container = (ContentControl)sender;
  1024. SelectedItem = ItemContainerGenerator.ItemFromContainer(container);
  1025. ListPickerMode = ListPickerMode.Normal;
  1026. e.Handled = true;
  1027. }
  1028. }
  1029. private void UpdateVisualStates(bool useTransitions)
  1030. {
  1031. if (!IsEnabled)
  1032. {
  1033. VisualStateManager.GoToState(this, PickerStatesDisabledStateName, useTransitions);
  1034. }
  1035. else if (IsHighlighted)
  1036. {
  1037. VisualStateManager.GoToState(this, PickerStatesHighlightedStateName, useTransitions);
  1038. }
  1039. else
  1040. {
  1041. VisualStateManager.GoToState(this, PickerStatesNormalStateName, useTransitions);
  1042. }
  1043. }
  1044. /// <summary>
  1045. /// Updates the summary of the selected items to be displayed in the ListPicker.
  1046. /// </summary>
  1047. /// <param name="newValue">The list selected items</param>
  1048. [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Windows.Controls.TextBlock.set_Text(System.String)", Justification = "By design.")]
  1049. private void UpdateSummary(IList newValue)
  1050. {
  1051. const string space = " ";
  1052. string summary = null;
  1053. if (null != SummaryForSelectedItemsDelegate)
  1054. {
  1055. // Ask the delegate to sumarize the selected items.
  1056. summary = SummaryForSelectedItemsDelegate(newValue);
  1057. }
  1058. if (summary == null)
  1059. {
  1060. // No summary was provided, so by default, show only the first item in the selection list.
  1061. if (null == newValue || newValue.Count == 0)
  1062. {
  1063. // In the case that there were no selected items, show the empty string.
  1064. summary = space;
  1065. }
  1066. else
  1067. {
  1068. summary = newValue[0].ToString();
  1069. }
  1070. }
  1071. // The display does not size correctly if the empty string is used.
  1072. if (String.IsNullOrEmpty(summary))
  1073. {
  1074. summary = space;
  1075. }
  1076. if (null != _multipleSelectionModeSummary)
  1077. {
  1078. _multipleSelectionModeSummary.Text = summary;
  1079. }
  1080. }
  1081. private void OpenPickerPage()
  1082. {
  1083. if (null == PickerPageUri)
  1084. {
  1085. throw new ArgumentException("PickerPageUri");
  1086. }
  1087. if (null == _frame)
  1088. {
  1089. // Hook up to necessary events and navigate
  1090. _frame = Ap

Large files files are truncated, but you can click here to view the full file