// (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.Generic; using System.ComponentModel; using System.Globalization; using System.Windows; using System.Windows.Controls; using System.Windows.Media; using System.Windows.Media.Animation; using System.Windows.Navigation; using Microsoft.Phone.Shell; namespace Microsoft.Phone.Controls { /// /// Displays the list of items and allows single or multiple selection. /// public partial class ListPickerPage : PhoneApplicationPage { private const string StateKey_Value = "ListPickerPage_State_Value"; private PageOrientation lastOrientation; /// /// Gets or sets the string of text to display as the header of the page. /// public string HeaderText { get; set; } /// /// Gets or sets the list of items to display. /// public IList Items { get; private set; } /// /// Gets or sets the selection mode. /// public SelectionMode SelectionMode { get; set; } /// /// Gets or sets the selected item. /// public object SelectedItem { get; set; } /// /// Gets or sets the list of items to select. /// public IList SelectedItems { get; private set; } /// /// Gets or sets the item template /// public DataTemplate FullModeItemTemplate { get; set; } /// /// Whether the picker page is open or not. /// private bool IsOpen { get { return (bool)GetValue(IsOpenProperty); } set { SetValue(IsOpenProperty, value); } } private static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register("isOIsOpenpen", typeof(bool), typeof(ListPickerPage), new PropertyMetadata(false, new PropertyChangedCallback(OnIsOpenChanged))); private static void OnIsOpenChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) { (o as ListPickerPage).OnIsOpenChanged(); } private void OnIsOpenChanged() { UpdateVisualState(true); } /// /// Creates a list picker page. /// public ListPickerPage() { InitializeComponent(); Items = new List(); SelectedItems = new List(); Loaded += OnLoaded; Unloaded += OnUnloaded; } private void OnLoaded(object sender, RoutedEventArgs e) { OrientationChanged += OnOrientationChanged; lastOrientation = Orientation; // Customize the ApplicationBar Buttons by providing the right text if (null != ApplicationBar) { foreach (object obj in ApplicationBar.Buttons) { IApplicationBarIconButton button = obj as IApplicationBarIconButton; if (null != button) { if ("DONE" == button.Text) { button.Text = LocalizedResources.ControlResources.DateTimePickerDoneText; button.Click += OnDoneButtonClick; } else if ("CANCEL" == button.Text) { button.Text = LocalizedResources.ControlResources.DateTimePickerCancelText; button.Click += OnCancelButtonClick; } } } } SetupListItems(-90); PlaneProjection headerProjection = (PlaneProjection)HeaderTitle.Projection; if (null == headerProjection) { headerProjection = new PlaneProjection(); HeaderTitle.Projection = headerProjection; } headerProjection.RotationX = -90; Picker.Opacity = 1; Dispatcher.BeginInvoke(() => { IsOpen = true; }); } private void OnUnloaded(object sender, RoutedEventArgs e) { OrientationChanged -= OnOrientationChanged; } private void SetupListItems(double degree) { // Add a projection for each list item and turn it to -90 (rotationX) so it is hidden. for (int x = 0; x < Picker.Items.Count; x++) { FrameworkElement item = (FrameworkElement)Picker.ItemContainerGenerator.ContainerFromIndex(x); if (null != item) { PlaneProjection p = (PlaneProjection)item.Projection; if (null == p) { p = new PlaneProjection(); item.Projection = p; } p.RotationX = degree; } } } /// /// Called when a page becomes the active page in a frame. /// /// An object that contains the event data. protected override void OnNavigatedTo(NavigationEventArgs e) { if (null == e) { throw new ArgumentNullException("e"); } base.OnNavigatedTo(e); // Restore Value if returning to application (to avoid inconsistent state) if (State.ContainsKey(StateKey_Value)) { State.Remove(StateKey_Value); // Back out from picker page for consistency with behavior of core pickers in this scenario if (NavigationService.CanGoBack) { NavigationService.GoBack(); return; } } // Automatically uppercase the text for the header. if (null != HeaderText) { HeaderTitle.Text = HeaderText.ToUpper(CultureInfo.CurrentCulture); } Picker.DataContext = Items; Picker.SelectionMode = SelectionMode; if (null != FullModeItemTemplate) { Picker.ItemTemplate = FullModeItemTemplate; } if (SelectionMode == SelectionMode.Single) { ApplicationBar.IsVisible = false; Picker.SelectedItem = SelectedItem; } else { ApplicationBar.IsVisible = true; Picker.ItemContainerStyle = (Style)Resources["ListBoxItemCheckedStyle"]; foreach (object item in Items) { if (null != SelectedItems && SelectedItems.Contains(item)) { Picker.SelectedItems.Add(item); } } } } private void OnDoneButtonClick(object sender, EventArgs e) { // Commit the value and close SelectedItem = Picker.SelectedItem; SelectedItems = Picker.SelectedItems; ClosePickerPage(); } private void OnCancelButtonClick(object sender, EventArgs e) { // Close without committing a value SelectedItem = null; SelectedItems = null; ClosePickerPage(); } /// /// Called when the Back key is pressed. /// /// Event arguments. protected override void OnBackKeyPress(CancelEventArgs e) { if (null == e) { throw new ArgumentNullException("e"); } // Cancel back action so we can play the Close state animation (then go back) e.Cancel = true; SelectedItem = null; SelectedItems = null; ClosePickerPage(); } private void ClosePickerPage() { IsOpen = false; } private void OnClosedStoryboardCompleted(object sender, EventArgs e) { // Close the picker page NavigationService.GoBack(); } /// /// Called when a page is no longer the active page in a frame. /// /// An object that contains the event data. protected override void OnNavigatedFrom(NavigationEventArgs e) { if (null == e) { throw new ArgumentNullException("e"); } base.OnNavigatedFrom(e); // Save Value if navigating away from application if (e.Uri.IsExternalNavigation()) { State[StateKey_Value] = StateKey_Value; } } private void OnOrientationChanged(object sender, OrientationChangedEventArgs e) { PageOrientation newOrientation = e.Orientation; RotateTransition transitionElement = new RotateTransition(); // Adjust padding if possible if (null != MainGrid) { switch (newOrientation) { case PageOrientation.Portrait: case PageOrientation.PortraitUp: HeaderTitle.Margin = new Thickness(20, 12, 12, 12); Picker.Margin = new Thickness(8, 12, 0, 0); transitionElement.Mode = (lastOrientation == PageOrientation.LandscapeLeft) ? RotateTransitionMode.In90Counterclockwise : RotateTransitionMode.In90Clockwise; break; case PageOrientation.Landscape: case PageOrientation.LandscapeLeft: HeaderTitle.Margin = new Thickness(72, 0, 0, 0); Picker.Margin = new Thickness(60, 0, 0, 0); transitionElement.Mode = (lastOrientation == PageOrientation.LandscapeRight) ? RotateTransitionMode.In180Counterclockwise : RotateTransitionMode.In90Clockwise; break; case PageOrientation.LandscapeRight: HeaderTitle.Margin = new Thickness(20, 0, 0, 0); Picker.Margin = new Thickness(8, 0, 0, 0); transitionElement.Mode = (lastOrientation == PageOrientation.PortraitUp) ? RotateTransitionMode.In90Counterclockwise : RotateTransitionMode.In180Clockwise; break; } } PhoneApplicationPage phoneApplicationPage = (PhoneApplicationPage)(((PhoneApplicationFrame)Application.Current.RootVisual)).Content; ITransition transition = transitionElement.GetTransition(phoneApplicationPage); transition.Completed += delegate { transition.Stop(); }; transition.Begin(); lastOrientation = newOrientation; } private void UpdateVisualState(bool useTransitions) { if (useTransitions) { if (!IsOpen) { SetupListItems(0); } Storyboard mainBoard = new Storyboard(); Storyboard headerBoard = AnimationForElement(HeaderTitle, 0); mainBoard.Children.Add(headerBoard); IList itemsInView = ItemsControlExtensions.GetItemsInViewPort(Picker); for (int i = 0; i < itemsInView.Count; i++) { FrameworkElement element = (FrameworkElement)itemsInView[i].Target; Storyboard board = AnimationForElement(element, i + 1); mainBoard.Children.Add(board); } Dispatcher.BeginInvoke(new Action(() => UpdateOutOfViewItems(itemsInView))); if (!IsOpen) { mainBoard.Completed += OnClosedStoryboardCompleted; } mainBoard.Begin(); } else if (!IsOpen) { OnClosedStoryboardCompleted(null, null); } } private Storyboard AnimationForElement(FrameworkElement element, int index) { double delay = 30; double duration = (IsOpen) ? 350 : 250; double from = (IsOpen) ? -45 : 0; double to = (IsOpen) ? 0 : 90; ExponentialEase ee = new ExponentialEase() { EasingMode = (IsOpen) ? EasingMode.EaseOut : EasingMode.EaseIn, Exponent = 5, }; DoubleAnimation anim = new DoubleAnimation() { Duration = new Duration(TimeSpan.FromMilliseconds(duration)), From = from, To = to, EasingFunction = ee, }; Storyboard.SetTarget(anim, element); Storyboard.SetTargetProperty(anim, new PropertyPath("(UIElement.Projection).(PlaneProjection.RotationX)")); Storyboard board = new Storyboard(); board.BeginTime = TimeSpan.FromMilliseconds(delay * index); board.Children.Add(anim); return board; } /// /// Go through all the items that were not visible on the page and set their properties accordingly without animation. /// private void UpdateOutOfViewItems(IList itemsInView) { // IList itemsInView = ItemsControlExtensions.GetItemsInViewPort(Picker); for (int k = 0; k < Picker.Items.Count; k++) { FrameworkElement item = (FrameworkElement)Picker.ItemContainerGenerator.ContainerFromIndex(k); if (item != null) { bool found = false; foreach (WeakReference refr in itemsInView) { if (refr.Target == item) { found = true; } } if (!found) { item.Opacity = (IsOpen) ? 1 : 0; PlaneProjection p = item.Projection as PlaneProjection; if (null != p) { p.RotationX = 0; } } } } } private void OnPickerTapped(object sender, System.Windows.Input.GestureEventArgs e) { // We listen to the tap event because SelectionChanged does not fire if the user picks the already selected item. // Only close the page in Single Selection mode. if (SelectionMode == SelectionMode.Single) { // Commit the value and close SelectedItem = Picker.SelectedItem; ClosePickerPage(); } } } }