From 24f1139cdfb0aed5639424cdc382cfc6c8c64729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Attila=20Kisk=C3=B3?= Date: Sun, 12 Feb 2012 02:04:10 +0100 Subject: [PATCH] Initial commit of 71382 --- .gitignore | 8 + .../AutoCompleteBoxMetadata.cs | 132 + .../ExpanderViewDesign.cs | 33 + .../ExpanderViewMetadata.cs | 44 + .../GlobalSuppressions.cs | 16 + .../HubTileDesign.cs | 33 + .../HubTileMetadata.cs | 46 + ...trols.AutoCompleteBox.Expression.Large.png | Bin 0 -> 557 bytes ...trols.AutoCompleteBox.Expression.Small.png | Bin 0 -> 220 bytes ....Controls.AutoCompleteBox.VisualStudio.bmp | Bin 0 -> 824 bytes ....Controls.ContextMenu.Expression.Large.png | Bin 0 -> 904 bytes ....Controls.ContextMenu.Expression.Small.png | Bin 0 -> 439 bytes ...hone.Controls.ContextMenu.VisualStudio.bmp | Bin 0 -> 824 bytes ...e.Controls.DatePicker.Expression.Large.png | Bin 0 -> 3387 bytes ...e.Controls.DatePicker.Expression.Small.png | Bin 0 -> 3013 bytes ...Phone.Controls.DatePicker.VisualStudio.bmp | Bin 0 -> 824 bytes ...e.Controls.ListPicker.Expression.Large.png | Bin 0 -> 669 bytes ...e.Controls.ListPicker.Expression.Small.png | Bin 0 -> 339 bytes ...Phone.Controls.ListPicker.VisualStudio.bmp | Bin 0 -> 824 bytes ...rols.LongListSelector.Expression.Large.png | Bin 0 -> 3281 bytes ...rols.LongListSelector.Expression.Small.png | Bin 0 -> 3024 bytes ...Controls.LongListSelector.VisualStudio.bmp | Bin 0 -> 824 bytes ...one.Controls.MenuItem.Expression.Large.png | Bin 0 -> 722 bytes ...one.Controls.MenuItem.Expression.Small.png | Bin 0 -> 381 bytes ...t.Phone.Controls.MenuItem.VisualStudio.bmp | Bin 0 -> 824 bytes ...ne.Controls.Separator.Expression.Large.png | Bin 0 -> 428 bytes ...ne.Controls.Separator.Expression.Small.png | Bin 0 -> 209 bytes ....Phone.Controls.Separator.VisualStudio.bmp | Bin 0 -> 824 bytes ...e.Controls.TimePicker.Expression.Large.png | Bin 0 -> 3871 bytes ...e.Controls.TimePicker.Expression.Small.png | Bin 0 -> 3135 bytes ...Phone.Controls.TimePicker.VisualStudio.bmp | Bin 0 -> 824 bytes ...Controls.ToggleSwitch.Expression.Large.png | Bin 0 -> 526 bytes ...Controls.ToggleSwitch.Expression.Small.png | Bin 0 -> 259 bytes ...one.Controls.ToggleSwitch.VisualStudio.bmp | Bin 0 -> 824 bytes ...ne.Controls.WrapPanel.Expression.Large.png | Bin 0 -> 432 bytes ...ne.Controls.WrapPanel.Expression.Small.png | Bin 0 -> 296 bytes ....Phone.Controls.WrapPanel.VisualStudio.bmp | Bin 0 -> 824 bytes .../MenuItemDesign.cs | 50 + .../Metadata.cs | 167 + ...osoft.Phone.Controls.Toolkit.Design.csproj | 148 + .../MultiselectItemDesign.cs | 33 + .../MultiselectItemMetadata.cs | 36 + .../MultiselectListDesign.cs | 33 + .../MultiselectListMetadata.cs | 33 + .../PhoneTextBoxDesign.cs | 28 + .../PhoneTextBoxMetadata.cs | 43 + .../Properties/AssemblyInfo.cs | 33 + .../Properties/Resources.Designer.cs | 81 + .../Properties/Resources.resx | 126 + .../ToggleSwitchDesign.cs | 33 + .../ToggleSwitchMetadata.cs | 33 + .../TransitionsMetadata.cs | 57 + .../GlobalSuppressions.cs | 16 + .../Metadata.cs | 40 + ...ontrols.Toolkit.VisualStudio.Design.csproj | 96 + .../Properties/AssemblyInfo.cs | 30 + .../AutoCompleteBox/AutoCompleteBox.cs | 2677 +++++++++++++++++ .../AutoCompleteBox/AutoCompleteFilter.cs | 207 ++ .../AutoCompleteBox/AutoCompleteFilterMode.cs | 141 + .../AutoCompleteFilterPredicate.cs | 27 + .../AutoCompleteBox/ISelectionAdapter.cs | 77 + .../AutoCompleteBox/PopulatedEventArgs.cs | 44 + .../AutoCompleteBox/PopulatedEventHandler.cs | 27 + .../AutoCompleteBox/PopulatingEventArgs.cs | 53 + .../AutoCompleteBox/PopulatingEventHandler.cs | 27 + .../AutoCompleteBox/PopupHelper.cs | 713 +++++ .../SelectorSelectionAdapter.cs | 379 +++ .../Common/BindingEvaluator.cs | 121 + .../Common/CultureInfoExtensions.cs | 90 + .../Common/Extensions.cs | 113 + .../Common/IUpdateVisualState.cs | 30 + .../Common/InteractionHelper.cs | 528 ++++ .../Common/ItemsControlExtensions.cs | 120 + .../Common/ItemsControlHelper.cs | 235 ++ .../Common/LengthConverter.cs | 220 ++ .../Common/MathHelpers.cs | 55 + .../Common/NumericExtensions.cs | 127 + .../Common/OpacityAnimator.cs | 137 + .../Common/PhoneHelper.cs | 166 + .../Common/PhysicsConstants.cs | 158 + .../Common/RoutedPropertyChangingEventArgs.cs | 146 + .../RoutedPropertyChangingEventHandler.cs | 26 + .../Common/SafeRaise.cs | 93 + .../Common/TemplatedVisualTreeExtensions.cs | 107 + .../Common/TimeTypeConverter.cs | 192 ++ .../Common/TransformAnimator.cs | 217 ++ .../Common/Tuple.cs | 36 + .../Common/TypeConverters.cs | 142 + .../Common/VisualStates.cs | 414 +++ .../Common/VisualTreeExtensions.cs | 67 + .../Common/WeakEventListener.cs | 88 + .../ContextMenu/ContextMenu.cs | 1352 +++++++++ .../ContextMenu/ContextMenuService.cs | 83 + .../ContextMenu/MenuBase.cs | 120 + .../ContextMenu/MenuItem.cs | 337 +++ .../ContextMenu/Separator.cs | 28 + .../Data/HierarchicalDataTemplate.cs | 117 + .../DailyDateTimeConverter.cs | 80 + .../DateTimeFormatHelper.cs | 415 +++ .../FullViewDateTimeConverter.cs | 92 + .../HourlyDateTimeConverter.cs | 98 + .../ListViewDateTimeConverter.cs | 85 + .../RelativeTimeConverter.cs | 350 +++ .../ThreadDateTimeConverter.cs | 85 + .../DateTimePickers/DataSource.cs | 134 + .../DateTimePickers/DatePicker.cs | 25 + .../DateTimePickers/DatePickerPage.xaml | 189 ++ .../DateTimePickers/DatePickerPage.xaml.cs | 63 + .../DateTimePickers/DateTimePickerBase.cs | 299 ++ .../DateTimePickers/DateTimePickerPageBase.cs | 304 ++ .../DateTimePickerResources.cs | 26 + .../DateTimeValueChangedEventArgs.cs | 36 + .../DateTimePickers/DateTimeWrapper.cs | 81 + .../DateTimePickers/IDateTimePickerPage.cs | 20 + .../DateTimePickers/RecurringDaysPicker.cs | 180 ++ .../DateTimePickers/TimePicker.cs | 47 + .../DateTimePickers/TimePickerPage.xaml | 171 ++ .../DateTimePickers/TimePickerPage.xaml.cs | 65 + .../ExpanderView/ExpanderView.cs | 737 +++++ .../GlobalSuppressions.cs | 34 + .../HeaderedItemsControl.cs | 371 +++ .../HubTile/HubTile.cs | 454 +++ .../HubTile/HubTileConverters.cs | 54 + .../HubTile/HubTileService.cs | 379 +++ .../Input/GestureHelperEventArgs.cs | 322 ++ .../Input/GestureListener.cs | 88 + .../Input/GestureListenerStatic.cs | 372 +++ .../Input/GestureService.cs | 85 + .../ListPicker/ExpansionMode.cs | 23 + .../ListPicker/ListPicker.cs | 1340 +++++++++ .../ListPicker/ListPickerItem.cs | 51 + .../ListPicker/ListPickerMode.cs | 28 + .../ListPicker/ListPickerPage.xaml | 126 + .../ListPicker/ListPickerPage.xaml.cs | 461 +++ .../ControlResources.Designer.cs | 414 +++ .../ControlResources.cs.Designer.cs | 0 .../ControlResources.cs.resx | 276 ++ .../ControlResources.da.Designer.cs | 0 .../ControlResources.da.resx | 276 ++ .../ControlResources.de-DE.Designer.cs | 0 .../ControlResources.de-DE.resx | 276 ++ .../ControlResources.el.Designer.cs | 0 .../ControlResources.el.resx | 276 ++ .../ControlResources.en-GB.Designer.cs | 0 .../ControlResources.en-GB.resx | 276 ++ .../ControlResources.es-ES.Designer.cs | 0 .../ControlResources.es-ES.resx | 276 ++ .../ControlResources.fi.Designer.cs | 0 .../ControlResources.fi.resx | 276 ++ .../ControlResources.fr-FR.Designer.cs | 0 .../ControlResources.fr-FR.resx | 276 ++ .../ControlResources.hu.Designer.cs | 0 .../ControlResources.hu.resx | 276 ++ .../ControlResources.it-IT.Designer.cs | 0 .../ControlResources.it-IT.resx | 276 ++ .../ControlResources.ja.Designer.cs | 0 .../ControlResources.ja.resx | 276 ++ .../ControlResources.ko.Designer.cs | 0 .../ControlResources.ko.resx | 276 ++ .../ControlResources.nb-NO.Designer.cs | 0 .../ControlResources.nb-NO.resx | 276 ++ .../ControlResources.nl-NL.Designer.cs | 0 .../ControlResources.nl-NL.resx | 276 ++ .../ControlResources.pl.Designer.cs | 0 .../ControlResources.pl.resx | 276 ++ .../ControlResources.pt-BR.Designer.cs | 0 .../ControlResources.pt-BR.resx | 276 ++ .../ControlResources.pt-PT.Designer.cs | 0 .../ControlResources.pt-PT.resx | 276 ++ .../LocalizedResources/ControlResources.resx | 276 ++ .../ControlResources.ru.Designer.cs | 0 .../ControlResources.ru.resx | 276 ++ .../ControlResources.sv-SE.Designer.cs | 0 .../ControlResources.sv-SE.resx | 276 ++ .../ControlResources.zh-CN.Designer.cs | 0 .../ControlResources.zh-CN.resx | 276 ++ .../ControlResources.zh-TW.Designer.cs | 0 .../ControlResources.zh-TW.resx | 276 ++ .../LongListSelector/LongListSelector.cs | 1729 +++++++++++ .../LongListSelectorEventArgs.cs | 68 + .../LongListSelector/LongListSelectorGroup.cs | 262 ++ .../LongListSelector/LongListSelectorItem.cs | 43 + .../LongListSelectorItemType.cs | 43 + .../LongListSelectorItemsControl.cs | 62 + .../LongListSelector/TemplatedListBox.cs | 127 + .../LongListSelector/TemplatedListBoxItem.cs | 19 + .../Microsoft.Phone.Controls.Toolkit.csproj | 549 ++++ .../MultiselectList/MultiselectItem.cs | 639 ++++ .../MultiselectList/MultiselectList.cs | 330 ++ .../PhoneTextBox/PhoneTextBox.cs | 582 ++++ .../Pivot/LockablePivot.cs | 287 ++ .../Primitives/ClipToBounds.cs | 88 + .../Primitives/LoopingSelector.cs | 799 +++++ .../Primitives/LoopingSelectorDataSource.cs | 41 + .../Primitives/LoopingSelectorItem.cs | 186 ++ .../Primitives/ToggleSwitchButton.cs | 344 +++ .../ProgressBar/PerformanceProgressBar.cs | 243 ++ .../RelativeAnimatingContentControl.cs | 467 +++ .../Properties/AssemblyInfo.cs | 34 + .../Properties/Resources.Designer.cs | 234 ++ .../Properties/Resources.resx | 196 ++ .../Themes/Generic.xaml | 1778 +++++++++++ .../Tilt/TiltEffect.cs | 734 +++++ .../ToggleSwitch/OffOnConverter.cs | 57 + .../ToggleSwitch/ToggleSwitch.cs | 314 ++ .../Transitions/ITransition.cs | 87 + .../Transitions/NavigationInTransition.cs | 11 + .../Transitions/NavigationOutTransition.cs | 11 + .../Transitions/NavigationTransition.cs | 99 + .../Transitions/Storyboards/Roll.xaml | 24 + .../Storyboards/RotateIn180Clockwise.xaml | 27 + .../RotateIn180CounterClockwise.xaml | 27 + .../Storyboards/RotateIn90Clockwise.xaml | 27 + .../RotateIn90CounterClockwise.xaml | 27 + .../Storyboards/RotateOut180Clockwise.xaml | 27 + .../RotateOut180CounterClockwise.xaml | 27 + .../Storyboards/RotateOut90Clockwise.xaml | 27 + .../RotateOut90CounterClockwise.xaml | 27 + .../Storyboards/SlideDownFadeIn.xaml | 27 + .../Storyboards/SlideDownFadeOut.xaml | 27 + .../Storyboards/SlideLeftFadeIn.xaml | 27 + .../Storyboards/SlideLeftFadeOut.xaml | 27 + .../Storyboards/SlideRightFadeIn.xaml | 27 + .../Storyboards/SlideRightFadeOut.xaml | 27 + .../Storyboards/SlideUpFadeIn.xaml | 27 + .../Storyboards/SlideUpFadeOut.xaml | 27 + .../Storyboards/SwivelBackwardIn.xaml | 25 + .../Storyboards/SwivelBackwardOut.xaml | 24 + .../Storyboards/SwivelForwardIn.xaml | 22 + .../Storyboards/SwivelForwardOut.xaml | 24 + .../Storyboards/SwivelFullScreenIn.xaml | 22 + .../Storyboards/SwivelFullScreenOut.xaml | 24 + .../Storyboards/TurnstileBackwardIn.xaml | 23 + .../Storyboards/TurnstileBackwardOut.xaml | 24 + .../Storyboards/TurnstileForwardIn.xaml | 30 + .../Storyboards/TurnstileForwardOut.xaml | 24 + .../Transitions/TransitionElement.cs | 31 + .../Transitions/TransitionFrame.cs | 479 +++ .../Transitions/TransitionService.cs | 112 + .../Transitions/Transitions.cs | 202 ++ .../Transitions/Transitions/RollTransition.cs | 28 + .../Transitions/RotateTransition.cs | 55 + .../Transitions/RotateTransitionMode.cs | 46 + .../Transitions/SlideTransition.cs | 55 + .../Transitions/SlideTransitionMode.cs | 46 + .../Transitions/SwivelTransition.cs | 55 + .../Transitions/SwivelTransitionMode.cs | 38 + .../Transitions/Transitions/Transition.cs | 233 ++ .../Transitions/TurnstileTransition.cs | 55 + .../Transitions/TurnstileTransitionMode.cs | 30 + .../Viewbox/StretchDirection.cs | 34 + .../Viewbox/Viewbox.cs | 406 +++ .../WrapPanel/OrientedSize.cs | 142 + .../WrapPanel/WrapPanel.cs | 453 +++ PhoneToolkit.sln | 50 + PhoneToolkit.snk | Bin 0 -> 596 bytes 256 files changed, 39432 insertions(+) create mode 100644 .gitignore create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/AutoCompleteBoxMetadata.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/ExpanderViewDesign.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/ExpanderViewMetadata.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/GlobalSuppressions.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/HubTileDesign.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/HubTileMetadata.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.AutoCompleteBox.Expression.Large.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.AutoCompleteBox.Expression.Small.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.AutoCompleteBox.VisualStudio.bmp create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ContextMenu.Expression.Large.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ContextMenu.Expression.Small.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ContextMenu.VisualStudio.bmp create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.DatePicker.Expression.Large.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.DatePicker.Expression.Small.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.DatePicker.VisualStudio.bmp create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ListPicker.Expression.Large.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ListPicker.Expression.Small.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ListPicker.VisualStudio.bmp create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.LongListSelector.Expression.Large.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.LongListSelector.Expression.Small.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.LongListSelector.VisualStudio.bmp create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.MenuItem.Expression.Large.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.MenuItem.Expression.Small.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.MenuItem.VisualStudio.bmp create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.Separator.Expression.Large.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.Separator.Expression.Small.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.Separator.VisualStudio.bmp create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.TimePicker.Expression.Large.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.TimePicker.Expression.Small.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.TimePicker.VisualStudio.bmp create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ToggleSwitch.Expression.Large.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ToggleSwitch.Expression.Small.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ToggleSwitch.VisualStudio.bmp create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.WrapPanel.Expression.Large.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.WrapPanel.Expression.Small.png create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.WrapPanel.VisualStudio.bmp create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/MenuItemDesign.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Metadata.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Microsoft.Phone.Controls.Toolkit.Design.csproj create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/MultiselectItemDesign.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/MultiselectItemMetadata.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/MultiselectListDesign.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/MultiselectListMetadata.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/PhoneTextBoxDesign.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/PhoneTextBoxMetadata.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Properties/AssemblyInfo.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Properties/Resources.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/Properties/Resources.resx create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/ToggleSwitchDesign.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/ToggleSwitchMetadata.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.Design/TransitionsMetadata.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.VisualStudio.Design/GlobalSuppressions.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.VisualStudio.Design/Metadata.cs create mode 100644 Microsoft.Phone.Controls.Toolkit.VisualStudio.Design/Microsoft.Phone.Controls.Toolkit.VisualStudio.Design.csproj create mode 100644 Microsoft.Phone.Controls.Toolkit.VisualStudio.Design/Properties/AssemblyInfo.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/AutoCompleteBox.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/AutoCompleteFilter.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/AutoCompleteFilterMode.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/AutoCompleteFilterPredicate.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/ISelectionAdapter.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopulatedEventArgs.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopulatedEventHandler.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopulatingEventArgs.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopulatingEventHandler.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopupHelper.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/SelectorSelectionAdapter.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/BindingEvaluator.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/CultureInfoExtensions.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/Extensions.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/IUpdateVisualState.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/InteractionHelper.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/ItemsControlExtensions.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/ItemsControlHelper.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/LengthConverter.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/MathHelpers.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/NumericExtensions.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/OpacityAnimator.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/PhoneHelper.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/PhysicsConstants.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/RoutedPropertyChangingEventArgs.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/RoutedPropertyChangingEventHandler.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/SafeRaise.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/TemplatedVisualTreeExtensions.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/TimeTypeConverter.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/TransformAnimator.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/Tuple.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/TypeConverters.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/VisualStates.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/VisualTreeExtensions.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Common/WeakEventListener.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/ContextMenu/ContextMenu.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/ContextMenu/ContextMenuService.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/ContextMenu/MenuBase.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/ContextMenu/MenuItem.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/ContextMenu/Separator.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Data/HierarchicalDataTemplate.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimeConverters/DailyDateTimeConverter.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimeConverters/DateTimeFormatHelper.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimeConverters/FullViewDateTimeConverter.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimeConverters/HourlyDateTimeConverter.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimeConverters/ListViewDateTimeConverter.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimeConverters/RelativeTimeConverter.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimeConverters/ThreadDateTimeConverter.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimePickers/DataSource.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimePickers/DatePicker.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimePickers/DatePickerPage.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimePickers/DatePickerPage.xaml.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimePickerBase.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimePickerPageBase.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimePickerResources.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimeValueChangedEventArgs.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimeWrapper.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimePickers/IDateTimePickerPage.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimePickers/RecurringDaysPicker.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimePickers/TimePicker.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimePickers/TimePickerPage.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/DateTimePickers/TimePickerPage.xaml.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/ExpanderView/ExpanderView.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/GlobalSuppressions.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/HeaderedItemsControl/HeaderedItemsControl.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/HubTile/HubTile.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/HubTile/HubTileConverters.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/HubTile/HubTileService.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Input/GestureHelperEventArgs.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Input/GestureListener.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Input/GestureListenerStatic.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Input/GestureService.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/ListPicker/ExpansionMode.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/ListPicker/ListPicker.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/ListPicker/ListPickerItem.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/ListPicker/ListPickerMode.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/ListPicker/ListPickerPage.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/ListPicker/ListPickerPage.xaml.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.cs.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.cs.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.da.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.da.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.de-DE.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.de-DE.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.el.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.el.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.en-GB.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.en-GB.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.es-ES.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.es-ES.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.fi.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.fi.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.fr-FR.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.fr-FR.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.hu.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.hu.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.it-IT.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.it-IT.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ja.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ja.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ko.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ko.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.nb-NO.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.nb-NO.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.nl-NL.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.nl-NL.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pl.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pl.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pt-BR.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pt-BR.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pt-PT.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pt-PT.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ru.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ru.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.sv-SE.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.sv-SE.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.zh-CN.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.zh-CN.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.zh-TW.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.zh-TW.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelector.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorEventArgs.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorGroup.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorItem.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorItemType.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorItemsControl.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LongListSelector/TemplatedListBox.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/LongListSelector/TemplatedListBoxItem.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Microsoft.Phone.Controls.Toolkit.csproj create mode 100644 Microsoft.Phone.Controls.Toolkit/MultiselectList/MultiselectItem.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/MultiselectList/MultiselectList.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/PhoneTextBox/PhoneTextBox.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Pivot/LockablePivot.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Primitives/ClipToBounds.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Primitives/LoopingSelector.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Primitives/LoopingSelectorDataSource.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Primitives/LoopingSelectorItem.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Primitives/ToggleSwitchButton.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/ProgressBar/PerformanceProgressBar.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/ProgressBar/RelativeAnimatingContentControl.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Properties/AssemblyInfo.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Properties/Resources.Designer.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Properties/Resources.resx create mode 100644 Microsoft.Phone.Controls.Toolkit/Themes/Generic.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Tilt/TiltEffect.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/ToggleSwitch/OffOnConverter.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/ToggleSwitch/ToggleSwitch.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/ITransition.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/NavigationInTransition.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/NavigationOutTransition.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/NavigationTransition.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/Roll.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateIn180Clockwise.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateIn180CounterClockwise.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateIn90Clockwise.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateIn90CounterClockwise.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateOut180Clockwise.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateOut180CounterClockwise.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateOut90Clockwise.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateOut90CounterClockwise.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideDownFadeIn.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideDownFadeOut.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideLeftFadeIn.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideLeftFadeOut.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideRightFadeIn.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideRightFadeOut.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideUpFadeIn.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideUpFadeOut.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelBackwardIn.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelBackwardOut.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelForwardIn.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelForwardOut.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelFullScreenIn.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelFullScreenOut.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/TurnstileBackwardIn.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/TurnstileBackwardOut.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/TurnstileForwardIn.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/TurnstileForwardOut.xaml create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/TransitionElement.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/TransitionFrame.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/TransitionService.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Transitions.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/RollTransition.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/RotateTransition.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/RotateTransitionMode.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/SlideTransition.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/SlideTransitionMode.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/SwivelTransition.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/SwivelTransitionMode.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/Transition.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/TurnstileTransition.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/TurnstileTransitionMode.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Viewbox/StretchDirection.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/Viewbox/Viewbox.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/WrapPanel/OrientedSize.cs create mode 100644 Microsoft.Phone.Controls.Toolkit/WrapPanel/WrapPanel.cs create mode 100644 PhoneToolkit.sln create mode 100644 PhoneToolkit.snk diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ad5dde3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,8 @@ +.svn +bin +obj +debug +release +*.suo +*.user +packages diff --git a/Microsoft.Phone.Controls.Toolkit.Design/AutoCompleteBoxMetadata.cs b/Microsoft.Phone.Controls.Toolkit.Design/AutoCompleteBoxMetadata.cs new file mode 100644 index 0000000..1784a05 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/AutoCompleteBoxMetadata.cs @@ -0,0 +1,132 @@ +// (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. + +#if WINDOWS_PHONE_DESIGN +using Controls = Microsoft.Phone.Controls; +using DesignerProperties = Microsoft.Phone.Controls.Toolkit.Design.Properties; +#else +extern alias Silverlight; +using System.Windows.Controls.Design.Common; +using Controls = Silverlight::System.Windows.Controls; +using DesignerProperties = System.Windows.Controls.Properties; +#endif + +using System.ComponentModel; +using Microsoft.Windows.Design; +using Microsoft.Windows.Design.Metadata; +using Microsoft.Windows.Design.PropertyEditing; + +#if WINDOWS_PHONE_DESIGN +namespace Microsoft.Phone.Controls.Design +#else +namespace System.Windows.Controls.Input.Design +#endif +{ + /// + /// To register design time metadata for AutoCompleteBox. + /// + internal class AutoCompleteBoxMetadata : AttributeTableBuilder + { + /// + /// To register design time metadata for AutoCompleteBox. + /// + public AutoCompleteBoxMetadata() + : base() + { +#if WINDOWS_PHONE_DESIGN + AddCustomAttributes(typeof(AutoCompleteBox), "FilterMode", new CategoryAttribute(DesignerProperties.Resources.CommonProperties)); + AddCustomAttributes(typeof(AutoCompleteBox), "InputScope", new CategoryAttribute(DesignerProperties.Resources.AutoComplete)); + AddCustomAttributes(typeof(AutoCompleteBox), "InputScope", new DescriptionAttribute("Gets or sets the InputScope used by the Text template part.")); + AddCustomAttributes(typeof(AutoCompleteBox), "IsDropDownOpen", new CategoryAttribute(DesignerProperties.Resources.AutoComplete)); + AddCustomAttributes(typeof(AutoCompleteBox), "IsTextCompletionEnabled", new CategoryAttribute(DesignerProperties.Resources.CommonProperties)); + AddCustomAttributes(typeof(AutoCompleteBox), "ItemFilter", new BrowsableAttribute(false)); + AddCustomAttributes(typeof(AutoCompleteBox), "ItemsSource", new NewItemTypesAttribute(typeof(string))); + AddCustomAttributes(typeof(AutoCompleteBox), "MaxDropDownHeight", new CategoryAttribute(DesignerProperties.Resources.AutoComplete)); + AddCustomAttributes(typeof(AutoCompleteBox), "MinimumPopulateDelay", new CategoryAttribute(DesignerProperties.Resources.AutoComplete)); + AddCustomAttributes(typeof(AutoCompleteBox), "MinimumPrefixLength", new CategoryAttribute(DesignerProperties.Resources.AutoComplete)); + AddCustomAttributes(typeof(AutoCompleteBox), "Text", new CategoryAttribute(DesignerProperties.Resources.CommonProperties)); + AddCustomAttributes(typeof(AutoCompleteBox), "TextFilter", new BrowsableAttribute(false)); + AddCustomAttributes(typeof(AutoCompleteBox), "ValueMemberBinding", new CategoryAttribute(DesignerProperties.Resources.AutoComplete)); + AddCustomAttributes(typeof(AutoCompleteBox), "ValueMemberPath", new CategoryAttribute(DesignerProperties.Resources.AutoComplete)); + AddCustomAttributes(typeof(AutoCompleteBox), "Watermark", new CategoryAttribute(DesignerProperties.Resources.AutoComplete)); + AddCustomAttributes(typeof(AutoCompleteBox), "Watermark", new DescriptionAttribute("Gets or sets the string that appears in the TextBox when it has no input and does not have focus.")); +#else + AddCallback( + typeof(SSWC.AutoCompleteBox), + b => + { + b.AddCustomAttributes( + Extensions.GetMemberName(x => x.TextFilter), + new BrowsableAttribute(false)); + b.AddCustomAttributes( + Extensions.GetMemberName(x => x.ItemFilter), + new BrowsableAttribute(false)); + b.AddCustomAttributes( + "ValueMemberBinding", + new CategoryAttribute(Properties.Resources.AutoComplete)); + b.AddCustomAttributes( + Extensions.GetMemberName(x => x.ValueMemberPath), + new CategoryAttribute(Properties.Resources.AutoComplete)); + b.AddCustomAttributes( + Extensions.GetMemberName(x => x.MaxDropDownHeight), + new CategoryAttribute(Properties.Resources.AutoComplete)); + b.AddCustomAttributes( + Extensions.GetMemberName(x => x.MinimumPopulateDelay), + new CategoryAttribute(Properties.Resources.AutoComplete)); + b.AddCustomAttributes( + Extensions.GetMemberName(x => x.MinimumPrefixLength), + new CategoryAttribute(Properties.Resources.AutoComplete)); + b.AddCustomAttributes( + Extensions.GetMemberName(x => x.IsDropDownOpen), + new CategoryAttribute(Properties.Resources.AutoComplete)); + + b.AddCustomAttributes( + Extensions.GetMemberName(x => x.FilterMode), + new CategoryAttribute(Properties.Resources.CommonProperties)); + b.AddCustomAttributes( + Extensions.GetMemberName(x => x.Text), + new CategoryAttribute(Properties.Resources.CommonProperties)); + b.AddCustomAttributes( + Extensions.GetMemberName(x => x.IsTextCompletionEnabled), + new CategoryAttribute(Properties.Resources.CommonProperties)); + + b.AddCustomAttributes( + Extensions.GetMemberName(x => x.ItemsSource), + new NewItemTypesAttribute(typeof(string))); + +#if MWD40 + b.AddCustomAttributes(new ToolboxCategoryAttribute(ToolboxCategoryPaths.Controls, true)); + + b.AddCustomAttributes( + Extensions.GetMemberName(x => x.ItemTemplate), + new DataContextValueSourceAttribute( + Extensions.GetMemberName(x => x.ItemsSource), + true)); + b.AddCustomAttributes( + Extensions.GetMemberName(x => x.ValueMemberPath), + new DataContextValueSourceAttribute( + Extensions.GetMemberName(x => x.SelectedItem), + false)); + b.AddCustomAttributes( + Extensions.GetMemberName(x => x.SelectedItem), + new DataContextValueSourceAttribute( + Extensions.GetMemberName(x => x.ItemsSource), + true)); + b.AddCustomAttributes( + "ValueMemberBinding", + new DataContextValueSourceAttribute( + Extensions.GetMemberName(x => x.SelectedItem), + true)); + b.AddCustomAttributes( + Extensions.GetMemberName(x => x.ItemContainerStyle), + new DataContextValueSourceAttribute( + Extensions.GetMemberName(x => x.ItemsSource), + true)); +#endif + }); +#endif + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.Design/ExpanderViewDesign.cs b/Microsoft.Phone.Controls.Toolkit.Design/ExpanderViewDesign.cs new file mode 100644 index 0000000..8c57eb0 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/ExpanderViewDesign.cs @@ -0,0 +1,33 @@ +// (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 Microsoft.Windows.Design.Model; + +namespace Microsoft.Phone.Controls.Design +{ + /// + /// Initializes the default values for ExpanderView. + /// + internal class ExpanderViewInitializer : DefaultInitializer + { + /// + /// Initializes the default values for the ModelItem. + /// + /// The ModelItem. + public override void InitializeDefaults(ModelItem modelItem) + { + if (modelItem == null) + { + throw new ArgumentNullException("modelItem"); + } + +#if VISUAL_STUDIO_DESIGNER + modelItem.Properties["Width"].SetValue(456d); + modelItem.Properties["Height"].SetValue(111d); +#endif + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.Design/ExpanderViewMetadata.cs b/Microsoft.Phone.Controls.Toolkit.Design/ExpanderViewMetadata.cs new file mode 100644 index 0000000..4c4f657 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/ExpanderViewMetadata.cs @@ -0,0 +1,44 @@ +// (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.ComponentModel; +using System.Windows; +using Microsoft.Windows.Design.Features; +using Microsoft.Windows.Design.Metadata; + +namespace Microsoft.Phone.Controls.Design +{ + internal class ExpanderViewMetadata : AttributeTableBuilder + { + private const string TextProperties = "Text"; + private const string OtherProperties = "Other"; + + public ExpanderViewMetadata() + { + // Type attributes + AddCustomAttributes(typeof(ExpanderView), new DescriptionAttribute("Represents a collection of items that can be expanded and collapsed.")); + AddCustomAttributes(typeof(ExpanderView), new FeatureAttribute(typeof(ExpanderViewInitializer))); + + // Property attributes + AddCustomAttributes(typeof(ExpanderView), "Expander", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(ExpanderView), "Expander", new DescriptionAttribute("Gets or sets the expander object.")); + AddCustomAttributes(typeof(MultiselectItem), "ExpanderTemplate", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(MultiselectItem), "ExpanderTemplate", new DescriptionAttribute("Gets or sets the data template used to display the expander of an ExpanderView.")); + AddCustomAttributes(typeof(ExpanderView), "NonExpandableHeader", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(ExpanderView), "NonExpandableHeader", new DescriptionAttribute("Gets or sets the non-expandable header object.")); + AddCustomAttributes(typeof(ExpanderView), "NonExpandableHeaderTemplate", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(ExpanderView), "NonExpandableHeaderTemplate", new DescriptionAttribute("Gets or sets the data template used to display the non-expandable header of an ExpanderView.")); + AddCustomAttributes(typeof(ExpanderView), "IsExpanded", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(ExpanderView), "IsExpanded", new DescriptionAttribute("Gets or sets whether the ExpanderView is expanded.")); + AddCustomAttributes(typeof(ExpanderView), "IsExpanded", new TypeConverterAttribute(typeof(BooleanConverter))); + AddCustomAttributes(typeof(ExpanderView), "HasItems", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(ExpanderView), "HasItems", new DescriptionAttribute("Gets or sets whether the ExpanderView has items.")); + AddCustomAttributes(typeof(ExpanderView), "HasItems", new TypeConverterAttribute(typeof(BooleanConverter))); + AddCustomAttributes(typeof(ExpanderView), "IsNonExpandable", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(ExpanderView), "IsNonExpandable", new DescriptionAttribute("Gets or sets whether the ExpanderView is non-expandable.")); + AddCustomAttributes(typeof(ExpanderView), "IsNonExpandable", new TypeConverterAttribute(typeof(BooleanConverter))); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.Design/GlobalSuppressions.cs b/Microsoft.Phone.Controls.Toolkit.Design/GlobalSuppressions.cs new file mode 100644 index 0000000..e88a083 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/GlobalSuppressions.cs @@ -0,0 +1,16 @@ +// (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. + +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. +// +// To add a suppression to this file, right-click the message in the +// Error List, point to "Suppress Message(s)", and click +// "In Project Suppression File". +// You do not need to add suppressions to this file manually. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Satisfied for Release, not for Debug.")] \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.Design/HubTileDesign.cs b/Microsoft.Phone.Controls.Toolkit.Design/HubTileDesign.cs new file mode 100644 index 0000000..9934459 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/HubTileDesign.cs @@ -0,0 +1,33 @@ +// (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 Microsoft.Windows.Design.Model; + +namespace Microsoft.Phone.Controls.Design +{ + /// + /// Initializes the default values for HubTile. + /// + internal class HubTileInitializer : DefaultInitializer + { + /// + /// Initializes the default values for the ModelItem. + /// + /// The ModelItem. + public override void InitializeDefaults(ModelItem modelItem) + { + if (modelItem == null) + { + throw new ArgumentNullException("modelItem"); + } + +#if VISUAL_STUDIO_DESIGNER + modelItem.Properties["Width"].SetValue(456d); + modelItem.Properties["Height"].SetValue(111d); +#endif + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.Design/HubTileMetadata.cs b/Microsoft.Phone.Controls.Toolkit.Design/HubTileMetadata.cs new file mode 100644 index 0000000..fe6ee76 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/HubTileMetadata.cs @@ -0,0 +1,46 @@ +// (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.ComponentModel; +using System.Windows; +using Microsoft.Windows.Design.Features; +using Microsoft.Windows.Design.Metadata; + +namespace Microsoft.Phone.Controls.Design +{ + internal class HubTileMetadata : AttributeTableBuilder + { + private const string TextProperties = "Text"; + private const string OtherProperties = "Other"; + + public HubTileMetadata() + { + // Type attributes + AddCustomAttributes(typeof(HubTile), new DescriptionAttribute("Represents an animated tile that supports an image and a title associated with either a message or a notification.")); + AddCustomAttributes(typeof(HubTile), new FeatureAttribute(typeof(HubTileInitializer))); + + // Property attributes + AddCustomAttributes(typeof(HubTile), "Source", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(HubTile), "Source", new DescriptionAttribute("Gets or sets the source for the image.")); + AddCustomAttributes(typeof(HubTile), "Title", new CategoryAttribute(TextProperties)); + AddCustomAttributes(typeof(HubTile), "Title", new DescriptionAttribute("Gets or sets the title associated to the tile.")); + AddCustomAttributes(typeof(HubTile), "Title", new TypeConverterAttribute(typeof(StringConverter))); + AddCustomAttributes(typeof(HubTile), "Notification", new CategoryAttribute(TextProperties)); + AddCustomAttributes(typeof(HubTile), "Notification", new DescriptionAttribute("Gets or sets the notification displayed on the back of the tile.")); + AddCustomAttributes(typeof(HubTile), "Notification", new TypeConverterAttribute(typeof(StringConverter))); + AddCustomAttributes(typeof(HubTile), "Message", new CategoryAttribute(TextProperties)); + AddCustomAttributes(typeof(HubTile), "Message", new DescriptionAttribute("Gets or sets the message displayed on the back of the tile.")); + AddCustomAttributes(typeof(HubTile), "Message", new TypeConverterAttribute(typeof(StringConverter))); + AddCustomAttributes(typeof(HubTile), "DisplayNotification", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(HubTile), "DisplayNotification", new DescriptionAttribute("Gets or sets whether the notification should be displayed instead of the message.")); + AddCustomAttributes(typeof(HubTile), "DisplayNotification", new TypeConverterAttribute(typeof(BooleanConverter))); + AddCustomAttributes(typeof(HubTile), "IsFrozen", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(HubTile), "IsFrozen", new DescriptionAttribute("Gets or sets whether the HubTile can be animated.")); + AddCustomAttributes(typeof(HubTile), "IsFrozen", new TypeConverterAttribute(typeof(BooleanConverter))); + AddCustomAttributes(typeof(HubTile), "GroupTag", new CategoryAttribute(OtherProperties)); + AddCustomAttributes(typeof(HubTile), "GroupTag", new DescriptionAttribute("Gets or sets the group of HubTiles that this one is associated with.")); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.AutoCompleteBox.Expression.Large.png b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.AutoCompleteBox.Expression.Large.png new file mode 100644 index 0000000000000000000000000000000000000000..49e9afd0b90a17f0b1ce9b76adacb6ff8857ebe6 GIT binary patch literal 557 zcmV+|0@D47P)1%{1`eg7?*Z;;ZF%wY!TXe?+UcgQVZJnl9$(B z-~E2??)^z6$8jJ@k+>dYGMT3&z*SW}WZa^Hq9}g~p3P>_FtX*w*g_y-Vg8q5u^8#x zXf%LQ3cX$rCX9b{SN&N5IAB*6JbmxL{%UnN16BuQw1;Pm1e zUexYZiNsc&a^27D!p+qg*8lBBIoLuTW_G$naDREcF;c$#7`FVz^_A<0#cHR==b~41oe7->Eowi zz}PflJpK~3ZJJ{Ngn-y59sv=3foqWjbNLvJ;9b|j=JUaekS@UIO7I)=NPZ+Rd9>PK z01iLCh!Q)Lgo7_gXiNf}kkx9{-LP`EVStrN**k^LXDxwYn9yuC1Br`@*t;sX82vLS vKu{>;lPgpIa7yH_B7vcY!}S>-z6BTncq4}z-#MSx00000NkvXXu0mjfySVs; literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.AutoCompleteBox.Expression.Small.png b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.AutoCompleteBox.Expression.Small.png new file mode 100644 index 0000000000000000000000000000000000000000..3ee9d2826fbe9f08f9f0e8e86222d1b4fc7f54bf GIT binary patch literal 220 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&k$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1Gb)GJcAr-fhLV|;TpLbwNU=%dZzo+80an~*@W?{oFgB*s& zFJDS{jvA+*J7U84gguGTLzmkuo3qMlSBBl;rP1p*YF!8xX1(nFTSzkR?yjwKIvY(c zoZ%=E*NFK3?rwMR)XkfXZ`^c{mUy@@B_+jWkG#i66J-H?<%9fz42*0Ht0O&rM6k$- Q0$sx3>FVdQ&MBb@0H=;ic>n+a literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.AutoCompleteBox.VisualStudio.bmp b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.AutoCompleteBox.VisualStudio.bmp new file mode 100644 index 0000000000000000000000000000000000000000..50f94af451d05d6d04b21ad6d4120f05aa7fa32c GIT binary patch literal 824 zcmZ?rwP0od12Z700mK4O%*Y@C76%bR+z<>C_|Nd4rfBEfdg2k#B|ydhi31>&6sU&< z0MP&ck=YO~kVG~gr~u+92m>n@Sv{JW1l40z0Cx#Q7OVMiR{_;Pl)-hOsfVb9tHaP>e2k%)|x%)kHuX_X^7 literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ContextMenu.Expression.Large.png b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ContextMenu.Expression.Large.png new file mode 100644 index 0000000000000000000000000000000000000000..dac339b8b3b04e102c0973e1078f78f762f6807e GIT binary patch literal 904 zcmV;319$w1P)TNgY`D6*I@kjM0mOnVC@CokHcU@X zkD;=%lDECReF{u&@7}#c8}97v3}*ub5EHTl8yg#gu&^-0l`B^mbaZqW>gwuvLCN^~ z_3LEE0YCsTp$mwJh%hiQF)>`ed>QPM=H_OG*w|Qx$B!SAWdT3{v0xJv7Z+!E`t&Ko zrAwC>OifKe!NpKh*TkS?nZdxxFGbMMqj!$M*#H5A)dE99Lk1Zc8HOJ}elRdHGBS93 zdxPof+C~O#k0z3Q01!YJ7HDc}G5|9igOrpMLwR{Q!=_D}z=ne`IRGGln9vPBapD9+ zKtKQk3kwTFVPPRSKEkr6G3feqfibB7Ab?PO05Tlt>R*5V{{6ac-8#Xbpdg08z(59Z zrt1ujURrn!0tM%VHsj#Vi;b&e96Gf%nUXFM1x!o!su*d3jhKL+O=yL`1$!!T#D>sY-|_+2p}dLiQ+F1KLFzOurNmt0(_PrTL2J1 zEO=5LsPK`66@nHP7FdG=okq`;00G28OlI-*^(DJB1qdJ(B5I}OlvJ4j0Ys>PF(cNO eIKUr(00RKl@C^|q80Q)Q0000 literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ContextMenu.Expression.Small.png b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ContextMenu.Expression.Small.png new file mode 100644 index 0000000000000000000000000000000000000000..bf9327078e6fbf8bfeefd48a0d44ade99468fdaf GIT binary patch literal 439 zcmV;o0Z9IdP)3^{r{dld;ZUvGv~jtvGM6fnrj%7Tq`c6Nqa0T4heFowLmJOdjW8-t^xBbctO zZ)VW;XadU}y>kq%0U&_j8o0Q)81nP;8J<3Uir3Qs0R+~tZrwVDwzf8g&!0at+`M^{ zVaJXg3_yP|{QC8Sfr*KU!NNk8VQC*k13&B zX*@hU419cia2o&uhy@r~%c1NUApe0(c5rY22Lud*G{OKt05KxF4;t$@0ninU00G2^ hCbo<~(ue{GFaTY$dh!!lP5b}=002ovPDHLkV1m<)sVx8i literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ContextMenu.VisualStudio.bmp b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ContextMenu.VisualStudio.bmp new file mode 100644 index 0000000000000000000000000000000000000000..011fa7e6fbdecaada35526cbd2486e8817dfa0cd GIT binary patch literal 824 zcmZ?rwP0od12Z700mK4O%*Y@C76%bR+z<>C_|Nd44ru0zH3Si|Gk}W!!@xNhfC+$T zkOCx^!_}WVxEZ?1tAmTv9Kn9QmS^^Qlsy;iw2I301di>@C z)kB;CH=LmPaN8jof#mvyQ-S8-at~ZR#0q3Ckd5YFxH%BT`!+6X&WS9K^@NM#QV;Pj un&G(22P!^)aL3&CvWZn0E!mM}!0^ZIeq1I)?80;pPy|0hP7lPh85jWFw+pfW literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.DatePicker.Expression.Large.png b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.DatePicker.Expression.Large.png new file mode 100644 index 0000000000000000000000000000000000000000..9f38f76e93d6d84d3b6e7c241ab9d3dc4720cff2 GIT binary patch literal 3387 zcmV-B4aD+^P)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0007JNkl4^zCyiTI&`Rzg3!q%#)5-^6pVR8i(5OH zsRS{Q@}NVVj5t}O7*K3yD`pWClWZbQj0F{<5S(mEglM=fc_oQS`%>Z$Z#d`u=YIFT z%XtTyWgj0Oe<*}dnx-WHJMCrHG%azGPbu|JZbk^9wzs#H5JCYn9RQYmN-6pq;sL-3 z5C{a!+?h6YR($~2fH@(=>vk`N5O1)cMqi;&m<2M8fO7yhZ*Kzd@p_yx^cD{Pf z)_Q^?y)XO#U}tBiDek03-?TW}*3;jb7!_VtS63Mo-eVWH>a(>bT*8a%grdi5^W1&l zbib)7^aK`a)V z>^Z=MEH5v!va)hNnM_{o*azK6N=YCPpja&8@pu3j92^`D27^ySq0kJl-vsdcE?ZhS zolYaAWNT{+r_+h9>qMhbbX})ft)|Ro2lo4bpb+9&H#^T}vj7-|0f5`>1|XBku)e;& zP`7<& zuYf8*JRZL_H8te}a5x-me!o8k)Xb4-PAIF78@L2a)?*D=DwRs*a5$_gmCAQutkXKLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0002+NklH_Tb=t$t06WzR6@1fY$m2xCGWAgsXis0Hw7S0Pj7<7^bGP5P|}T z*s)lySS(lU69HIjF~)FmdPdjvbX|}4p0X^l*3JS)0Bzgy@c8s&Tb}16NisV%;tyGt z;hdvs8bsvy-@taer6>xVa{y}*xj<_zA|jhjKPwTDG)-j~2AO#Oe(NspJ>xh65XUjV z9ndJ?{C_|Nbkf_BcWCk7#_{|^S+7fr^+02+v_ z{`QI8K$SrCa0Z%2H1$B0_z{|VG}C~3^Lt+d5s(3qLQ@ZS2Aly`4-r9AkKZvsSv2*S z?m-j5r5^4cGzPNzSF5^TE^T=^zv}t)yk~t$Pus#C*LpoJb9hu_2}dWA^x@$TRR4GH ztUo)a{oXp^*M{z&t6P37ul=^D{OjDpuQRhgPfZ1)v#EwaD;ff2h({nN;RO(*4jecD aVGyGps2Bo(T7c^DBQ&SN{YxZ+fdK%3s}nf@ literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ListPicker.Expression.Large.png b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ListPicker.Expression.Large.png new file mode 100644 index 0000000000000000000000000000000000000000..826cd8accf4fbb32ca5d5581a2b064a0cae9d52c GIT binary patch literal 669 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEa{HEjtmUfZd~z?Faq)=OI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+AuIM>3h03hE&{2`t$$4J+m&O1A}1` zM`B{4WnJCBIx(?n3IhMn&$oYVSNA94$G5k)-KRt`2sr3C=o~qI+&tFH@%HIUwyZi8 zHP7cP`2T%<0fX*~@ZjLvzUv-t&%0~H&Y>`Ym21tKHF0U{JbC!zoK+mea_2<9Voh1c z_%Z+99?wk=U%h&jz|X)UaDs`?<`qjq&-eHDg>!c-SfDV6SHMAmTVh|`->Ual-c@>e zII@T_8|_!z*`@<@zyc35CbzsfJ9g~QH#grdbmq`>{rEgKw{NZr*QYjF%wq!SZE1+$ z@7UWIA*J^KY~&j}uTGge_pY4Eug}lVHy;q$^5yh&eQ^!n zWekige#;guTD103m(@3q!i4ijN|uUdFdjWAqR`Ocw@luyCV?T%uFZeGonb?fy|J+| zv#_u*8*j3;l~t21&}}ZNm$k#!HB8pr#P}m4VI_k(15lsWvV#W?rW^+b??s!LOe_Lt zCu_o(HZ#3WuDmTB!Oo!E)%ozy#@+)dRlQmu+a8>}a=>cB!?)rIS`5orjN1*`#QUQk zo-FzQ@2|yVWh>sk2sgKiOfCV34=JY(onkt2hUbjW8J;z3d^Apq%;1pNSNpro{B5%V z&xcQ+4&4NL%YWJQzT5z#@-Bv`14jyq{%cK1$;!_5E|UR<3X+4Ar!a1EVU&_zz`!!q qi{HU>#-$XX9X(s}wlT0sFzkHyLGVbB?jm6NV(@hJb6Mw<&;$TD<_A;& literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ListPicker.Expression.Small.png b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ListPicker.Expression.Small.png new file mode 100644 index 0000000000000000000000000000000000000000..fc683be4335b037b991144858bce33bae75acd06 GIT binary patch literal 339 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&kmUKs7M+Sy#H?H_c7ya+$4xLFwjY2F4>CM`k(1upGJL@JF;mocUum$XpJ| fPJzW73I`b0m&cykcQHH*=wSv=S3j3^P6C_|Nd44ru0zH3Si|Gk}W!!@xNhfC+$T zkOCx^!_}WVxEZ?1tAmTv9Kn9QmS^^Qlsy;iw2I301di>@C z)kB;CH=LmPaN8jof#mvyQ-S8-at~ZR#0q3Ckd5YFxH%BT`!+6X&WS9K^@NM#QV;Pj un&G(22P!^)aL3&CvWZn0E!mM}!0^ZIeq1I)?80;pPy|0hP7lPh85jWFw+pfW literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.LongListSelector.Expression.Large.png b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.LongListSelector.Expression.Large.png new file mode 100644 index 0000000000000000000000000000000000000000..b502f39f3834b7072e44ca377acc0920066aef50 GIT binary patch literal 3281 zcmV;?3@-DDP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0005}NklSuz*a6%m9mvC%MT@?tlLOod4W=Er3)ib<4Kx zm#TWe6+{pO?|k2X;(1@>MpQeORefux-_xN<%VpGK~*Uf3V_AwX(c3!TGeWGwIPpw|6&~c#4zHlUB{Xdh$y@F4dq7o^fDyT}lz}FAY+1k1`mnfo$YPj!yd^>Y) z&EN!)7=co$M8Dr(=&vz`h->Ja#CK9XmNBjAzo|9Ue7~R*xftC$b6F9 zuA?epg|}mdJi$eDpdzR!I`w&e27-u&dzE;cF=k#sR1p>2Pj6yr-WciwD=w88V P00000NkvXXu0mjfSDGUd literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.LongListSelector.Expression.Small.png b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.LongListSelector.Expression.Small.png new file mode 100644 index 0000000000000000000000000000000000000000..bc89b8d9c85fb0eb0f5222fd0a79a6f65fcbd5fd GIT binary patch literal 3024 zcmV;>3orDEP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0002{NklT`agb`T*&oc>qTjK^MVi z2q7;af$Qd&p{Y(5NxTlW!i84*!-ex-&i8RT#+WnU2I#UmZ@{Is_5oweJj*gy*Y(C% zmZdYs%$;*6pc}_Ad7iITr)f%26s#@?_$Nt{cwMcA>-*Q=&*`M^_2F<001)-|`Kx=t zbke738a@^aX0w`$(HD@Csd?F&8A0P2lydxrr2!lcB1J=HP09~XsvxfM7X^=SsK;S`eRp05d?t`jK?FkO$T_~cG)?1`aJ-T%U=w1 SNuh)Q0000O-{ow5QPma*&tRNffFFs?AdXko~LI*Nc^d^Y4g+6iT}-;aWP4x2wK_JX!n-As_<7I3j%&FX`i~*R;G=hRzfD;rYJ4%*)|TI8#K)K#&5X~(B z2p~ovwgSe%QEC{5Esg*J2p*<4Z{DQHaCLQctZ@VoKqvxx_wFUjaA#*{I9ozO!jqSm zcO@euBQMBD00BhRI1m;VRsk7q>5;+<^8i2qQPlz=A)zTC!v)1P`L7na3C| z-TFe7!JCh*0Mj7D1!YW75uC>JA1A_hbf;;ZI(EMv(^yAdFaF)9X)? z1v&;646@2b46F^%1Q6aJ`uFDt!|!iz8Ge6x3N-sY1Jl3n3@ku{Sy>nvSXo&a zSb&&cM25jLET7@T&bbWlpPXTk5$8oQ10aClKB!B#g6D(p-@h}wfB&B0*RNj;OiWBb zm-2vJ3&IdSGYU2}b!OPKq=Vtu_SwIzJQ>)8_z|fHAb>D}=qn zo8cfakU9`}_Uzdg!fZ!H9e*74038{vIXqW{+@CIbbojZ3v0NLyy0e}F4Tk!SkSF#L8vj7CH zfh>Xr3_t+EEdZwqv@j>ea1a}Wk-!}&1_OWqLb1TY!UDrUd=`K!6h=n884)0W;1+m# zdeWpu0|+1%Xic=7R#hfI0D&y{1H@CPWZoZi0e}Dl0MIn0%qFyo^#A|>07*qoM6N<$ Eg6DD>vH$=8 literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.MenuItem.Expression.Small.png b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.MenuItem.Expression.Small.png new file mode 100644 index 0000000000000000000000000000000000000000..1f42bb4ff9ff16a93024f9ebc77aa598a17e88a6 GIT binary patch literal 381 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&kmUKs7M+Sy#H?H_c7y}1g#-uxKHtEWcX!v( z6AjD^ECSpe9eT6)#UpM_KheMs)V`XElMm*bO>OM$O*Hc8^} z-fVZtBPpg54rUDu4h)T^M=Dnv#q9hfyiTA^SLcjG+7@F2nfr#%wF4y0J#;||3JMDi zZ``<%kdcs<#y0!ctE;Q67A#Y9b92jUU{ErXV{vR`JCeh|$kH_-m8Zo~q=!);fgyUU WQ;hZ``-8ynWAJqKb6Mw<&;$TTUW=0e literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.MenuItem.VisualStudio.bmp b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.MenuItem.VisualStudio.bmp new file mode 100644 index 0000000000000000000000000000000000000000..9ce84c6248a03a3e9fd4bb0451b89ec471b58666 GIT binary patch literal 824 zcmZ?rwP0od12Z700mK4O%*Y@C76%bR+z<>C_|Nd43^a4a8iEMfSfJwncmYTOl1t#~ zVa`OMfhOTnkINEF_3xT8@gQ94;pU_1#ibri8Jvr$9&R#L1}^m|;lcn6K`6kb9wv`Y uW2y&=;73^F3(Z_KW8qpr;=pu=1Rxsmt4A{&$i<@`D1{Zl-2h=QFaQ8mXB*}K literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.Separator.Expression.Large.png b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.Separator.Expression.Large.png new file mode 100644 index 0000000000000000000000000000000000000000..5a78e93866629edf0f4f6200dcbf8a8e974dcbcd GIT binary patch literal 428 zcmV;d0aN~oP)Q&kTwPtA0csY=%ug)X z6z$!+mn_4bot-hnSa2zxzU>Q1hG%;5U=?Q}RRgJjiGjv|<`#^`0YShBEh_kcx%eYB z4F=|BexTW?8BN!xizcD4tpkjQqt~uoV_3C%HN)>;zZsaBnHZRu znDNuhK$?jO$Yy3RF)?8ts^DY(+dSNM?#{(vfdpxJ*JY29oA2rvL= WAZSnr!2_-U0000k literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.Separator.VisualStudio.bmp b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.Separator.VisualStudio.bmp new file mode 100644 index 0000000000000000000000000000000000000000..e9d18236c83dc52fe199d7061d57c6bab543bfeb GIT binary patch literal 824 zcmZ?rwP0od12Z700mK4O%*Y@C7H0y=3voj*SO7@;XZSz-5FUT+oLi5EfZm6wM-vBf zfl?sy=ePggKK}po`v3c95J9;5|9^jjWWRp+|KSxv{lCBefB*Xb7RY@PsE;^+QOIfdK%A Cz6b08 literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.TimePicker.Expression.Large.png b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.TimePicker.Expression.Large.png new file mode 100644 index 0000000000000000000000000000000000000000..b90629d05a148d607d6115c03504ae83cfac13ac GIT binary patch literal 3871 zcmV+)58&{LP)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z000C^Nkl8g7IN0QKtlWQt1#SCQir0Vj6}@+jXT&TWGh&t?kWOJ zm142@Q7V-h1YXt5{*XJMPV`?q|xDKpuaD8utIW@r$QS0A$>i9uv5uM=9ik*H@BBLe6F^BVBI*nz5(#uV z-4gNQ+#Fpm9H1(Q419H(_dn<(5DYV4DiaDvF`741QVYw?b9*EJjYgw!e`B-DIIk9Q z*sZj*w2;f?$mMcO&txbT<}lTp*|DR8_Vyj*^97tPnWhIEM57Z-By-g0HxL>fn#^Xi zXMmfFGfMy-Z)$4#xI=lS!`38oBXE&{fdTgH*~5KA26$_1Z0vI#P?(;c{^|O);f^+C zKVw%e(*Mb)0E9xJ|5TD?nU~)_wd5>5?zjB>&4(v|K^-uqs_JjEv$LU*i~aH=PaMEE zcxH*fe}S%!Ep=Uo%Yz^YCg33vm;%D-bo%?7vCvF3IKu9y53T!6!b5%he*PW8Lw!q% zty^g8xTa|rVzHPFetk|Y=B%p;Wpg>^)p-hYDwV0_`n`ZAeJCQT-joX_VO9K68w=merDEeQW3ejA{pwZ(SlUI6}@oS;&vlmOF8 zUuHAV>~gt=|F1_Yl}ag_&3>0or{7x%P!F^N-uvi-8-Ne^bG1)@=klSo&d`7;kQVNd hRDU1h#l_9v0{}b1yJ((XNl5?z002ovPDHLkV1j!1SwsK; literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.TimePicker.Expression.Small.png b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.TimePicker.Expression.Small.png new file mode 100644 index 0000000000000000000000000000000000000000..222394377e99bdc8fa8343c61b64203afadbf021 GIT binary patch literal 3135 zcmV-F48Ze=P)KLZ*U+IBfRsybQWXdwQbLP>6pAqfylh#{fb6;Z(vMMVS~$e@S=j*ftg6;Uhf59&ghTmgWD0l;*T zI709Y^p6lP1rIRMx#05C~cW=H_Aw*bJ-5DT&Z2n+x)QHX^p z00esgV8|mQcmRZ%02D^@S3L16t`O%c004NIvOKvYIYoh62rY33S640`D9%Y2D-rV&neh&#Q1i z007~1e$oCcFS8neI|hJl{-P!B1ZZ9hpmq0)X0i`JwE&>$+E?>%_LC6RbVIkUx0b+_+BaR3cnT7Zv!AJxW zizFb)h!jyGOOZ85F;a?DAXP{m@;!0_IfqH8(HlgRxt7s3}k3K`kFu>>-2Q$QMFfPW!La{h336o>X zu_CMttHv6zR;&ZNiS=X8v3CR#fknUxHUxJ0uoBa_M6WNWeqIg~6QE69c9o#eyhGvpiOA@W-aonk<7r1(?fC{oI5N*U!4 zfg=2N-7=cNnjjOr{yriy6mMFgG#l znCF=fnQv8CDz++o6_Lscl}eQ+l^ZHARH>?_s@|##Rr6KLRFA1%Q+=*RRWnoLsR`7U zt5vFIcfW3@?wFpwUVxrVZ>QdQz32KIeJ}k~{cZZE^+ya? z2D1z#2HOnI7(B%_ac?{wFUQ;QQA1tBKtrWrm0_3Rgps+?Jfqb{jYbcQX~taRB;#$y zZN{S}1|}gUOHJxc?wV3fxuz+mJ4`!F$IZ;mqRrNsHJd##*D~ju=bP7?-?v~|cv>vB zsJ6IeNwVZxrdjT`yl#bBIa#GxRa#xMMy;K#CDyyGyQdMSxlWT#tDe?p!?5wT$+oGt z8L;Kp2HUQ-ZMJ=3XJQv;x5ci*?vuTfeY$;({XGW_huIFR9a(?@3)XSs8O^N5RyOM=TTmp(3=8^+zpz2r)C z^>JO{deZfso3oq3?Wo(Y?l$ge?uXo;%ru`Vo>?<<(8I_>;8Eq#KMS9gFl*neeosSB zfoHYnBQIkwkyowPu(zdms`p{<7e4kra-ZWq<2*OsGTvEV%s0Td$hXT+!*8Bnh2KMe zBmZRodjHV?r+_5^X9J0WL4jKW`}lf%A-|44I@@LTvf1rHjG(ze6+w@Jt%Bvjts!X0 z?2xS?_ve_-kiKB_KiJlZ$9G`c^=E@oNG)mWWaNo-3TIW8)$Hg0Ub-~8?KhvJ>$ z3*&nim@mj(aCxE5!t{lw7O5^0EIO7zOo&c6l<+|iDySBWCGrz@C5{St!X3hAA}`T4 z(TLbXTq+(;@<=L8dXnssyft|w#WSTW<++3>sgS%(4NTpeI-VAqb|7ssJvzNHgOZVu zaYCvgO_R1~>SyL=cFU|~g|hy|Zi}}s9+d~lYqOB71z9Z$wnC=pR9Yz4DhIM>Wmjgu z&56o6maCpC&F##y%G;1PobR9i?GnNg;gYtchD%p19a!eQtZF&3JaKv33gZ<8D~47E ztUS1iwkmDaPpj=$m#%)jCVEY4fnLGNg2A-`YwHVD3gv};>)hAvT~AmqS>Lr``i7kw zJ{5_It`yrBmlc25DBO7E8;5VoznR>Ww5hAaxn$2~(q`%A-YuS64wkBy=9dm`4cXeX z4c}I@?e+FW+b@^RDBHV(wnMq2zdX3SWv9u`%{xC-q*U}&`cyXV(%rRT*Z6MH?i+i& z_B8C(+grT%{XWUQ+f@NoP1R=AW&26{v-dx)iK^-Nmiuj8txj!m?Z*Ss1N{dh4z}01 z)YTo*JycSU)+_5r4#yw9{+;i4Ee$peRgIj+;v;ZGdF1K$3E%e~4LaI(jC-u%2h$&R z9cLXcYC@Xwnns&bn)_Q~Te?roKGD|d-g^8;+aC{{G(1^(O7m37Y1-+6)01cN&y1aw zoqc{T`P^XJqPBbIW6s}d4{z_f5Om?vMgNQEJG?v2T=KYd^0M3I6IZxbny)%vZR&LD zJpPl@Psh8QyPB@KTx+@RdcC!KX7}kEo;S|j^u2lU7XQ}Oo;f|;z4Ll+_r>@1-xl3| zawq-H%e&ckC+@AhPrP6BKT#_XdT7&;F71j}Joy zkC~6lh7E@6o;W@^IpRNZ{ptLtL(gQ-CY~4mqW;US7Zxvm_|@yz&e53Bp_lTPlfP|z zrTyx_>lv@x#=^!PzR7qqF<$gm`|ZJZ+;<)Cqu&ot2z=0000WV@Og>004R=004l4008;_004mL004C`008P>0026e000+nl3&F} z0004MNklA#G(GncAh?XSC>Rc=AV+n+YXo;rW99sGdB5G)h{)2{S zjGCe)sHR3~akCEzJC+$YT}s3T$$!IK2cW*zH7n@A#gkt{k3Yh&R2_d%U7835!dDeZeJFXDIeXl$2!D+^w zTS&MohsZ=E@@Nn5Nkwgm9amTQ$61 z??xyT+8BsTcNO!ONGX2_mSwTDw$JlJnv1iYs>gAhJ=?ZNl~OZ)zdy_$+~i&v?jHvT znn1bHXsnk?rCk7}6y3tyQH`NgHkTL;mOZ ZYXGeolpwz+&z%4O002ovPDHLkV1lz)*Qfvh literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.TimePicker.VisualStudio.bmp b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.TimePicker.VisualStudio.bmp new file mode 100644 index 0000000000000000000000000000000000000000..da37fd87b0a2dfd4661950f0f45db94b04104770 GIT binary patch literal 824 zcmZ?rwP0od12Z700mK4O%*Y@C7H0y=3voj*SO7@;XZSw|(cx*+*Oitn3=5kT5HKw+ zVN*{p+>xJ;Z$L&s@so4s9qMemzjghMHOtP;pL%iO+|@a$%c^T3S|Ey#EbN0YF3p;C zdivBSClCMs`1JqV2Y()4{dMomyG#35W~XhQI1#7?s0^qW7%~to*XGUxDgN~lr2ga6 zKQC_s(f=3MZf#xK7Z?Ol4^#})1tIVB^}WA;@Bhzl|Nngjs(*X!?2Ge9fe5H&s+TuJ zJ;Wst^3lwhcQ$SO4+c%%-Zf6ntLv-(yts8}Lg%c&K!_KR$tO#efXoMjzR1W=&+q;J z{PO?%M-PtgS)N~jY5wc&J08!P4bjr<=MPf<{t=Me-`aR^=1hosWcLF_-fZ9gXzDbe zmXlK_1IgD9@1C7GZBI)J#2IMe0}*__ZvCB!lQx!?t9{zA~ RTnyyE0kUx^qn0=W0|53a7Bc_< literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ToggleSwitch.Expression.Large.png b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ToggleSwitch.Expression.Large.png new file mode 100644 index 0000000000000000000000000000000000000000..9ba187720e93eb5cf9119cac9def780487139681 GIT binary patch literal 526 zcmV+p0`dKcP)GK=!0{hIN$&Y zf?STxa1a2ABm3;t>kkYo*Bl1Z*!WN&N_HggKX{oTJj51EW8*`?=fRaszkK;ZmIW;M zB!B$)!SEf3|Ni~U@b29^xVWReE;!6VG|?7-k|HRzfFcxR@Tbq88UFwK$H2kC!NAMU z&+zl-FSsT#Q2}t6!vKtpEQVsi*RNmi0n@osdwV+r6B84|hYuft2D5_=2jR~^%N8zN z$e^TUP0+=_d;m5NIVT$dT^=VcF7C_C&86o|KiJOi`m+EdU5G03E=)P=rK{ QT>t<807*qoM6N<$g5f{tUjP6A literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ToggleSwitch.Expression.Small.png b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ToggleSwitch.Expression.Small.png new file mode 100644 index 0000000000000000000000000000000000000000..1cf95aedb395f9e995bb260ad1b8a32418970f0c GIT binary patch literal 259 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&k$r9IylHmNblJdl&R0hYC{G?O` z&)mfH)S%SFl*+=BsWw1GOFdm2Ln>}1{rUgjo>{Y@v(d$26Ps9R>DLMcTgDDnjun3y zjl`22WQ%mvv4F FO#sNjW2FE9 literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ToggleSwitch.VisualStudio.bmp b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.ToggleSwitch.VisualStudio.bmp new file mode 100644 index 0000000000000000000000000000000000000000..d380aba8f716666ed0f11ef8005e81ecf5dd114a GIT binary patch literal 824 zcmZ?rwP0od12Z700mK4O%*Y@C7H0y=3voj*SO7@;XZSy)(T%f*_bi)0B!Vcm(2yb$ zL5u||UOKUMX+JswivRui>^~R)89**v{gS?##l6*NXaLod>VBY8i9taB-a3Dj$iRmv K9&Cw?fdK#u<;9u+ literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.WrapPanel.Expression.Large.png b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.WrapPanel.Expression.Large.png new file mode 100644 index 0000000000000000000000000000000000000000..9a50c38603da0e8b5eead59f400d363ce8c216a6 GIT binary patch literal 432 zcmeAS@N?(olHy`uVBq!ia0vp^5+KaM1|%Pp+x`GjEa{HEjtmUfZd~z?Faq)=OI#yL zg7ec#$`gxH85~pclTsBta}(23gHjVyDhp4h+AuIMI(fP{hE&{2`t$$4J+m&O1H;A! z4mUS9V^hnZxnna<+C@Ni2)X1tPTO$>uVL&OQ* z;-sq}BlcN1lxa%I+wS@Qz=#_tp~3N4+vCrAhnzlNMF)n7vg{5YIA`zs{r&yo4?ufb zFB!P8uUWH3qo#r3(9`CxEN6Z?uVH0iJmQohvAB8tkthZMhaXBmg-x3{6dGnotl+tz z%fO)^!adKn+O3qEfr&%4BW`~k@3O|J=L3s$6VlS$9x?+RvFq(@vs@ zL?x`{IQ9bQ%m5t@9-bvtb9n?DbgrC;`N04T9EH?5njvP=b0mSznRxB2SOWtygZ<3# Vg)Y{oUjc)h!PC{xWt~$(695AOn$Q3M literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.WrapPanel.Expression.Small.png b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.WrapPanel.Expression.Small.png new file mode 100644 index 0000000000000000000000000000000000000000..0d4628e783d113e7bda611e5476540ac0f659ab2 GIT binary patch literal 296 zcmeAS@N?(olHy`uVBq!ia0vp^JRr=$1|-8uW1a&kmUKs7M+Sy#H?H_c7y2Ef!RLg~Mc2<8FP&geXpnHMW0>i1f^CA$34R8~ zmg{ZN+w*kUFMfZ2zkM|$hk^*N_iv%V4229f!8vp06qqtFvV8lpJ^y}yj|3@L z0uhJr?k+zlv!Gt-qKV6*Yz7X64Qys+W^He8Z*R^F3k$Pg`xqG!Au(fyfF3)Cf?H6s n6wuPfFGfkGfByfs=TbPp!2Z|Dp+#g0$g2#Vu6{1-oD!M<$xUVk literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.WrapPanel.VisualStudio.bmp b/Microsoft.Phone.Controls.Toolkit.Design/Icons/Microsoft.Phone.Controls.WrapPanel.VisualStudio.bmp new file mode 100644 index 0000000000000000000000000000000000000000..20b6466350a7261eb9b0fc1c0c02fb443fb33ac6 GIT binary patch literal 824 zcmZ?rwP0od12Z700mK4O%*Y@C7H0y=ALfH#umF(w&+wnt2xv<~Ljy4+#3ew*|A+u^ z#{(HeDE|jEmJ;(@T3TvrYkQFZnx$ywXJlkR75w_~@9Red7p4u({XoOf%!gP75eI4_ l%KTqH;das4e4q#M08ID8f&nLu?0%r%fn0(l87Y~80RTSI0Du4h literal 0 HcmV?d00001 diff --git a/Microsoft.Phone.Controls.Toolkit.Design/MenuItemDesign.cs b/Microsoft.Phone.Controls.Toolkit.Design/MenuItemDesign.cs new file mode 100644 index 0000000..a08ecf4 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/MenuItemDesign.cs @@ -0,0 +1,50 @@ +// (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 Microsoft.Windows.Design.Model; +using Microsoft.Windows.Design.PropertyEditing; + +namespace Microsoft.Phone.Controls.Design +{ + /// + /// Class that implements the DefaultInitializer for MenuItem. + /// + class MenuItemInitializer : DefaultInitializer + { + /// + /// Initializes default for the specified ModelItem. + /// + /// Specified ModelItem. + public override void InitializeDefaults(ModelItem item) + { + if (item == null) + { + throw new ArgumentNullException("item"); + } + + // Set properties + item.Properties["Header"].SetValue(typeof(MenuItem).Name); + } + } + + /// + /// New item factory for MenuItems. + /// + class MenuItemFactory : NewItemFactory + { + /// + /// Creates an instance of the object. + /// + /// Type. + /// Instance. + public override object CreateInstance(Type type) + { + MenuItem item = new MenuItem(); + item.Header = typeof(MenuItem).Name; + return item; + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Metadata.cs b/Microsoft.Phone.Controls.Toolkit.Design/Metadata.cs new file mode 100644 index 0000000..defd924 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/Metadata.cs @@ -0,0 +1,167 @@ +// (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.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Windows.Controls; +using Microsoft.Phone.Controls.Primitives; +using Microsoft.Windows.Design; +using Microsoft.Windows.Design.Features; +using Microsoft.Windows.Design.Metadata; +using Microsoft.Windows.Design.PropertyEditing; + +namespace Microsoft.Phone.Controls.Design +{ + /// + /// Provides design metadata. + /// + public class MetadataStore : IProvideAttributeTable + { + /// + /// Stores the string used to refer to the "Common Properties" section. + /// + public static readonly string CommonProperties = "Common Properties"; + + /// + /// Gets the attribute table. + /// + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Simple method happens to be highly coupled.")] + public AttributeTable AttributeTable + { + get + { + AttributeTableBuilder attributeTableBuilder = new AttributeTableBuilder(); + + // Add attributes for ContextMenuService + attributeTableBuilder.AddCustomAttributes(typeof(ContextMenuService), "ContextMenu", new AttachedPropertyBrowsableForTypeAttribute(typeof(UIElement))); + attributeTableBuilder.AddCustomAttributes(typeof(ContextMenuService), "ContextMenu", new DisplayNameAttribute(typeof(ContextMenu).Name)); + attributeTableBuilder.AddCustomAttributes(typeof(ContextMenuService), "ContextMenu", new CategoryAttribute(CommonProperties)); + attributeTableBuilder.AddCustomAttributes(typeof(ContextMenuService), "ContextMenu", new EditorBrowsableAttribute(EditorBrowsableState.Advanced)); + + // Add attributes for ContextMenu + attributeTableBuilder.AddCustomAttributes(typeof(ContextMenu), new DescriptionAttribute("Represents a pop-up menu that enables a control to expose functionality that is specific to the context of the control.")); + + // Add attributes for ContextMenu properties + attributeTableBuilder.AddCustomAttributes(typeof(ContextMenu), "IsZoomEnabled", new CategoryAttribute(CommonProperties)); + attributeTableBuilder.AddCustomAttributes(typeof(ContextMenu), "VerticalOffset", new CategoryAttribute("Layout")); + attributeTableBuilder.AddCustomAttributes(typeof(ContextMenu), "IsOpen", new DescriptionAttribute("Gets or sets a value indicating whether the ContextMenu is visible.")); + attributeTableBuilder.AddCustomAttributes(typeof(ContextMenu), "IsZoomEnabled", new DescriptionAttribute(" Gets or sets a value indicating whether the background will zoom out when the ContextMenu is open.")); + attributeTableBuilder.AddCustomAttributes(typeof(ContextMenu), "ItemContainerStyle", new DescriptionAttribute("Gets or sets the Style that is applied to the container element generated for each item.")); + attributeTableBuilder.AddCustomAttributes(typeof(ContextMenu), "VerticalOffset", new DescriptionAttribute("Gets or sets the vertical distance between the target origin and the popup alignment point.")); + NewItemTypesAttribute menuItemType = new NewItemTypesAttribute(typeof(MenuItem)); + menuItemType.FactoryType = typeof(MenuItemFactory); + NewItemTypesAttribute separatorType = new NewItemTypesAttribute(typeof(Separator)); + attributeTableBuilder.AddCustomAttributes(typeof(ContextMenu), "Items", menuItemType, separatorType); + + // Add attributes for MenuItem + attributeTableBuilder.AddCustomAttributes(typeof(MenuItem), new DescriptionAttribute("Represents a selectable item inside a Menu or ContextMenu.")); + attributeTableBuilder.AddCustomAttributes(typeof(MenuItem), new FeatureAttribute(typeof(MenuItemInitializer))); + + // Add attributes for MenuItem properties + attributeTableBuilder.AddCustomAttributes(typeof(MenuItem), "Command", new CategoryAttribute(CommonProperties)); + attributeTableBuilder.AddCustomAttributes(typeof(MenuItem), "CommandParameter", new CategoryAttribute(CommonProperties)); + attributeTableBuilder.AddCustomAttributes(typeof(MenuItem), "Header", new CategoryAttribute(CommonProperties)); + attributeTableBuilder.AddCustomAttributes(typeof(MenuItem), "Command", new DescriptionAttribute("Gets or sets the command associated with the menu item.")); + attributeTableBuilder.AddCustomAttributes(typeof(MenuItem), "CommandParameter", new DescriptionAttribute("Gets or sets the parameter to pass to the Command property of a MenuItem.")); + attributeTableBuilder.AddCustomAttributes(typeof(MenuItem), "Header", new DescriptionAttribute("Gets or sets the item that labels the control.")); + attributeTableBuilder.AddCustomAttributes(typeof(MenuItem), "HeaderTemplate", new DescriptionAttribute("Gets or sets a data template that is used to display the contents of the control's header.")); + attributeTableBuilder.AddCustomAttributes(typeof(MenuItem), "ItemContainerStyle", new DescriptionAttribute("Gets or sets the Style that is applied to the container element generated for each item.")); + attributeTableBuilder.AddCustomAttributes(typeof(MenuItem), "Header", new TypeConverterAttribute(typeof(StringConverter))); + + // Add attributes for ListPicker + attributeTableBuilder.AddCustomAttributes(typeof(ListPicker), new DescriptionAttribute("Class that implements a flexible list-picking experience with a custom interface for few/many items.")); + + // Add attributes for ListPicker properties + attributeTableBuilder.AddCustomAttributes(typeof(ListPicker), "SelectedIndex", new CategoryAttribute(CommonProperties)); + attributeTableBuilder.AddCustomAttributes(typeof(ListPicker), "SelectedItem", new CategoryAttribute(CommonProperties)); + attributeTableBuilder.AddCustomAttributes(typeof(ListPicker), "FullModeItemTemplate", new CategoryAttribute(CommonProperties)); + attributeTableBuilder.AddCustomAttributes(typeof(ListPicker), "Header", new CategoryAttribute(CommonProperties)); + attributeTableBuilder.AddCustomAttributes(typeof(ListPicker), "FullModeHeader", new CategoryAttribute(CommonProperties)); + attributeTableBuilder.AddCustomAttributes(typeof(ListPicker), "ListPickerMode", new DescriptionAttribute("Gets or sets the ListPickerMode (ex: Normal/Expanded/Full).")); + attributeTableBuilder.AddCustomAttributes(typeof(ListPicker), "SelectedIndex", new DescriptionAttribute("Gets or sets the index of the selected item.")); + attributeTableBuilder.AddCustomAttributes(typeof(ListPicker), "SelectedItem", new DescriptionAttribute("Gets or sets the selected item.")); + attributeTableBuilder.AddCustomAttributes(typeof(ListPicker), "FullModeItemTemplate", new DescriptionAttribute("Gets or sets the DataTemplate used to display each item when ListPickerMode is set to Full.")); + attributeTableBuilder.AddCustomAttributes(typeof(ListPicker), "Header", new DescriptionAttribute("Gets or sets the header of the control.")); + attributeTableBuilder.AddCustomAttributes(typeof(ListPicker), "HeaderTemplate", new DescriptionAttribute("Gets or sets the template used to display the control's header.")); + attributeTableBuilder.AddCustomAttributes(typeof(ListPicker), "FullModeHeader", new DescriptionAttribute("Gets or sets the header to use when ListPickerMode is set to Full.")); + attributeTableBuilder.AddCustomAttributes(typeof(ListPicker), "ItemCountThreshold", new DescriptionAttribute("Gets or sets the maximum number of items for which Expanded mode will be used (default: 5).")); + attributeTableBuilder.AddCustomAttributes(typeof(ListPicker), "Header", new TypeConverterAttribute(typeof(StringConverter))); + attributeTableBuilder.AddCustomAttributes(typeof(ListPicker), "FullModeHeader", new TypeConverterAttribute(typeof(StringConverter))); + + // Add attributes for ListPickerItem + attributeTableBuilder.AddCustomAttributes(typeof(ListPickerItem), new DescriptionAttribute("Class that implements a container for the ListPicker control.")); + + // Add attributes for Separator + attributeTableBuilder.AddCustomAttributes(typeof(Separator), new DescriptionAttribute("Control that is used to separate items in items controls.")); + + // ToggleSwitch + attributeTableBuilder.AddTable(new ToggleSwitchMetadata().CreateTable()); + + // Add attributes for WrapPanel + attributeTableBuilder.AddCustomAttributes(typeof(WrapPanel), new DescriptionAttribute("Positions child elements sequentially from left to right or top to bottom. When elements extend beyond the panel edge, elements are positioned in the next row or column.")); + + // Add attributes for WrapPanel properties + attributeTableBuilder.AddCustomAttributes(typeof(WrapPanel), "Orientation", new CategoryAttribute(CommonProperties)); + attributeTableBuilder.AddCustomAttributes(typeof(WrapPanel), "Orientation", new DescriptionAttribute("Gets or sets the direction in which child elements are arranged.")); + attributeTableBuilder.AddCustomAttributes(typeof(WrapPanel), "ItemWidth", new CategoryAttribute(CommonProperties)); + attributeTableBuilder.AddCustomAttributes(typeof(WrapPanel), "ItemWidth", new DescriptionAttribute("Gets or sets the width of the layout area for each item.")); + attributeTableBuilder.AddCustomAttributes(typeof(WrapPanel), "ItemHeight", new CategoryAttribute(CommonProperties)); + attributeTableBuilder.AddCustomAttributes(typeof(WrapPanel), "ItemHeight", new DescriptionAttribute("Gets or sets the height of the layout area for each item.")); + + // Add attributes for DatePicker + attributeTableBuilder.AddCustomAttributes(typeof(DatePicker), new DescriptionAttribute("Represents a control that allows the user to choose a date (day/month/year).")); + + // Add attributes for TimePicker + attributeTableBuilder.AddCustomAttributes(typeof(TimePicker), new DescriptionAttribute("Represents a control that allows the user to choose a time (hour/minute/am/pm).")); + + // Add attributes for DatePicker/TimePicker properties + attributeTableBuilder.AddCustomAttributes(typeof(DateTimePickerBase), "Value", new CategoryAttribute(CommonProperties)); + attributeTableBuilder.AddCustomAttributes(typeof(DateTimePickerBase), "Header", new CategoryAttribute(CommonProperties)); + attributeTableBuilder.AddCustomAttributes(typeof(DateTimePickerBase), "Value", new DescriptionAttribute("Gets or sets the DateTime value.")); + attributeTableBuilder.AddCustomAttributes(typeof(DateTimePickerBase), "ValueChanged", new DescriptionAttribute("Event that is invoked when the Value property changes.")); + attributeTableBuilder.AddCustomAttributes(typeof(DateTimePickerBase), "ValueStringFormat", new DescriptionAttribute("Gets or sets the format string to use when converting the Value property to a string.")); + attributeTableBuilder.AddCustomAttributes(typeof(DateTimePickerBase), "Header", new DescriptionAttribute("Gets or sets the header of the control.")); + attributeTableBuilder.AddCustomAttributes(typeof(DateTimePickerBase), "HeaderTemplate", new DescriptionAttribute("Gets or sets the template used to display the control's header.")); + attributeTableBuilder.AddCustomAttributes(typeof(DateTimePickerBase), "PickerPageUri", new DescriptionAttribute("Gets or sets the Uri to use for loading the IDateTimePickerPage instance when the control is clicked.")); + attributeTableBuilder.AddCustomAttributes(typeof(DateTimePickerBase), "Header", new TypeConverterAttribute(typeof(StringConverter))); + + // Hide primitives + attributeTableBuilder.AddCustomAttributes(typeof(DateTimePickerBase), new ToolboxBrowsableAttribute(false)); + attributeTableBuilder.AddCustomAttributes(typeof(DatePickerPage), new ToolboxBrowsableAttribute(false)); + attributeTableBuilder.AddCustomAttributes(typeof(TimePickerPage), new ToolboxBrowsableAttribute(false)); + attributeTableBuilder.AddCustomAttributes(typeof(HeaderedItemsControl), new ToolboxBrowsableAttribute(false)); + attributeTableBuilder.AddCustomAttributes(typeof(LoopingSelector), new ToolboxBrowsableAttribute(false)); + attributeTableBuilder.AddCustomAttributes(typeof(LoopingSelectorItem), new ToolboxBrowsableAttribute(false)); + attributeTableBuilder.AddCustomAttributes(typeof(ToggleSwitchButton), new ToolboxBrowsableAttribute(false)); + + // Add back subclasses + attributeTableBuilder.AddCustomAttributes(typeof(DatePicker), new ToolboxBrowsableAttribute(true)); + attributeTableBuilder.AddCustomAttributes(typeof(TimePicker), new ToolboxBrowsableAttribute(true)); + attributeTableBuilder.AddCustomAttributes(typeof(MenuItem), new ToolboxBrowsableAttribute(true)); + + // AutoCompleteBox + attributeTableBuilder.AddTable(new AutoCompleteBoxMetadata().CreateTable()); + + // Transitions + attributeTableBuilder.AddTable(new TransitionsMetadata().CreateTable()); + + // HubTile + attributeTableBuilder.AddTable(new HubTileMetadata().CreateTable()); + + // MultiselectItem + attributeTableBuilder.AddTable(new MultiselectItemMetadata().CreateTable()); + + // MultiselectList + attributeTableBuilder.AddTable(new MultiselectListMetadata().CreateTable()); + + // ExpanderView + attributeTableBuilder.AddTable(new ExpanderViewMetadata().CreateTable()); + + return attributeTableBuilder.CreateTable(); + } + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Microsoft.Phone.Controls.Toolkit.Design.csproj b/Microsoft.Phone.Controls.Toolkit.Design/Microsoft.Phone.Controls.Toolkit.Design.csproj new file mode 100644 index 0000000..0a05fef --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/Microsoft.Phone.Controls.Toolkit.Design.csproj @@ -0,0 +1,148 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {AF13D696-5FAC-4A05-BE77-B8A1C971C5E5} + Library + Properties + Microsoft.Phone.Controls.Toolkit.Design + Microsoft.Phone.Controls.Toolkit.Design + v4.0 + 512 + True + ..\PhoneToolkit.snk + SAK + SAK + SAK + SAK + + + true + full + false + ..\Bin\Debug\ + TRACE;DEBUG;CODE_ANALYSIS;WINDOWS_PHONE_DESIGN + prompt + 4 + true + AllRules.ruleset + + + pdbonly + true + ..\Bin\Release\ + TRACE;WINDOWS_PHONE_DESIGN + prompt + 4 + + + + False + $(ProgramFiles)\Microsoft SDKs\Windows Phone\v7.1\Libraries\Silverlight\Microsoft.Phone.Controls.dll + + + + + $(ProgramFiles)\Reference Assemblies\Microsoft\Framework\Silverlight\v4.0\Profile\WindowsPhone71\System.Windows.dll + + + + False + + + False + + + False + $(ProgramFiles)\Reference Assemblies\Microsoft\Framework\Silverlight\v4.0\Profile\WindowsPhone\Microsoft.Phone.dll + False + + + False + $(ProgramFiles)\Reference Assemblies\Microsoft\Framework\Silverlight\v4.0\Profile\WindowsPhone\Microsoft.Phone.Interop.dll + False + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + + + + {0754458A-7AFC-463A-B27D-2F6980522119} + Microsoft.Phone.Controls.Toolkit + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.Design/MultiselectItemDesign.cs b/Microsoft.Phone.Controls.Toolkit.Design/MultiselectItemDesign.cs new file mode 100644 index 0000000..1f78d89 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/MultiselectItemDesign.cs @@ -0,0 +1,33 @@ +// (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 Microsoft.Windows.Design.Model; + +namespace Microsoft.Phone.Controls.Design +{ + /// + /// Initializes the default values for MultiselectItem. + /// + internal class MultiselectItemInitializer : DefaultInitializer + { + /// + /// Initializes the default values for the ModelItem. + /// + /// The ModelItem. + public override void InitializeDefaults(ModelItem modelItem) + { + if (modelItem == null) + { + throw new ArgumentNullException("modelItem"); + } + +#if VISUAL_STUDIO_DESIGNER + modelItem.Properties["Width"].SetValue(456d); + modelItem.Properties["Height"].SetValue(111d); +#endif + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.Design/MultiselectItemMetadata.cs b/Microsoft.Phone.Controls.Toolkit.Design/MultiselectItemMetadata.cs new file mode 100644 index 0000000..b87d6d7 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/MultiselectItemMetadata.cs @@ -0,0 +1,36 @@ +// (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.ComponentModel; +using System.Windows; +using Microsoft.Windows.Design.Features; +using Microsoft.Windows.Design.Metadata; + +namespace Microsoft.Phone.Controls.Design +{ + internal class MultiselectItemMetadata : AttributeTableBuilder + { + private const string OtherProperties = "Other"; + + public MultiselectItemMetadata() + { + // Type attributes + AddCustomAttributes(typeof(MultiselectItem), new DescriptionAttribute("Represents a selectable item in a MultiselectList.")); + AddCustomAttributes(typeof(MultiselectItem), new FeatureAttribute(typeof(MultiselectItemInitializer))); + + // Property attributes + AddCustomAttributes(typeof(MultiselectItem), "IsSelected", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(MultiselectItem), "IsSelected", new DescriptionAttribute("Gets or sets a value that indicates whether the MultiselectItem is selected.")); + AddCustomAttributes(typeof(MultiselectItem), "IsSelected", new TypeConverterAttribute(typeof(BooleanConverter))); + AddCustomAttributes(typeof(MultiselectItem), "HintPanelHeight", new CategoryAttribute(OtherProperties)); + AddCustomAttributes(typeof(MultiselectItem), "HintPanelHeight", new DescriptionAttribute("Gets or sets the height of the hint panel.")); + AddCustomAttributes(typeof(MultiselectItem), "HintPanelHeight", new TypeConverterAttribute(typeof(DoubleConverter))); + AddCustomAttributes(typeof(MultiselectItem), "ContentInfo", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(MultiselectItem), "ContentInfo", new DescriptionAttribute("Gets or sets the content information of a MultiselectItem.")); + AddCustomAttributes(typeof(MultiselectItem), "ContentInfoTemplate", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(MultiselectItem), "ContentInfoTemplate", new DescriptionAttribute("Gets or sets the data template used to display the content information of a MultiselectItem.")); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.Design/MultiselectListDesign.cs b/Microsoft.Phone.Controls.Toolkit.Design/MultiselectListDesign.cs new file mode 100644 index 0000000..6b90017 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/MultiselectListDesign.cs @@ -0,0 +1,33 @@ +// (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 Microsoft.Windows.Design.Model; + +namespace Microsoft.Phone.Controls.Design +{ + /// + /// Initializes the default values for MultiselectList. + /// + internal class MultiselectListInitializer : DefaultInitializer + { + /// + /// Initializes the default values for the ModelItem. + /// + /// The ModelItem. + public override void InitializeDefaults(ModelItem modelItem) + { + if (modelItem == null) + { + throw new ArgumentNullException("modelItem"); + } + +#if VISUAL_STUDIO_DESIGNER + modelItem.Properties["Width"].SetValue(456d); + modelItem.Properties["Height"].SetValue(111d); +#endif + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.Design/MultiselectListMetadata.cs b/Microsoft.Phone.Controls.Toolkit.Design/MultiselectListMetadata.cs new file mode 100644 index 0000000..4351ab6 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/MultiselectListMetadata.cs @@ -0,0 +1,33 @@ +// (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.ComponentModel; +using System.Windows; +using Microsoft.Windows.Design.Features; +using Microsoft.Windows.Design.Metadata; + +namespace Microsoft.Phone.Controls.Design +{ + internal class MultiselectListMetadata : AttributeTableBuilder + { + private const string OtherProperties = "Other"; + + public MultiselectListMetadata() + { + // Type attributes + AddCustomAttributes(typeof(MultiselectList), new DescriptionAttribute("Represents a selectable item in a MultiselectList.")); + AddCustomAttributes(typeof(MultiselectList), new FeatureAttribute(typeof(MultiselectListInitializer))); + + // Property attributes + AddCustomAttributes(typeof(MultiselectList), "IsSelectionEnabled", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(MultiselectList), "IsSelectionEnabled", new DescriptionAttribute("Gets or sets a value that indicates whether the MultiselectList is selecting.")); + AddCustomAttributes(typeof(MultiselectList), "IsSelectionEnabled", new TypeConverterAttribute(typeof(BooleanConverter))); + AddCustomAttributes(typeof(MultiselectList), "ItemInfoTemplate", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(MultiselectList), "ItemInfoTemplate", new DescriptionAttribute("Gets or sets the data template used to display the content information of each MultiselectItem.")); + AddCustomAttributes(typeof(MultiselectList), "ItemContainerStyle", new CategoryAttribute(OtherProperties)); + AddCustomAttributes(typeof(MultiselectList), "ItemContainerStyle", new DescriptionAttribute("Gets or sets the style that is applied to the container element generated for each item.")); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.Design/PhoneTextBoxDesign.cs b/Microsoft.Phone.Controls.Toolkit.Design/PhoneTextBoxDesign.cs new file mode 100644 index 0000000..f79f43d --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/PhoneTextBoxDesign.cs @@ -0,0 +1,28 @@ +// (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 Microsoft.Windows.Design.Model; + +namespace Microsoft.Phone.Controls.Design +{ + /// + /// Initializes the default values for ExtendedTextBox. + /// + internal class PhoneTextBoxInitializer : DefaultInitializer + { + /// + /// Initializes the default values for the ModelItem. + /// + /// The ModelItem. + public override void InitializeDefaults(ModelItem modelItem) + { + if (modelItem == null) + { + throw new ArgumentNullException("modelItem"); + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.Design/PhoneTextBoxMetadata.cs b/Microsoft.Phone.Controls.Toolkit.Design/PhoneTextBoxMetadata.cs new file mode 100644 index 0000000..d051505 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/PhoneTextBoxMetadata.cs @@ -0,0 +1,43 @@ +// (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.ComponentModel; +using System.Windows; +using Microsoft.Windows.Design.Features; +using Microsoft.Windows.Design.Metadata; + +namespace Microsoft.Phone.Controls.Design +{ + internal class PhoneTextBoxMetadata : AttributeTableBuilder + { + private const string TextProperties = "Text"; + private const string OtherProperties = "Other"; + + public PhoneTextBoxMetadata() + { + // Type attributes + AddCustomAttributes(typeof(PhoneTextBox), new DescriptionAttribute("Represents a text box with additional features, including hint text, an action icon, and a length indicator.")); + AddCustomAttributes(typeof(PhoneTextBox), new FeatureAttribute(typeof(PhoneTextBoxInitializer))); + + // Property attributes + AddCustomAttributes(typeof(PhoneTextBox), "Hint", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(PhoneTextBox), "Hint", new DescriptionAttribute("Gets or sets the hint text associated with the control.")); + AddCustomAttributes(typeof(PhoneTextBox), "Hint", new TypeConverterAttribute(typeof(StringConverter))); + AddCustomAttributes(typeof(PhoneTextBox), "HintStyle", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(PhoneTextBox), "HintStyle", new DescriptionAttribute("Gets or sets the style of the Hint associated with the control.")); + AddCustomAttributes(typeof(PhoneTextBox), "LengthIndicatorVisible", new CategoryAttribute(OtherProperties)); + AddCustomAttributes(typeof(PhoneTextBox), "LengthIndicatorVisible", new DescriptionAttribute("Gets or sets whether the length indicator should be visible.")); + AddCustomAttributes(typeof(PhoneTextBox), "LengthIndicatorVisible", new TypeConverterAttribute(typeof(BooleanConverter))); + AddCustomAttributes(typeof(PhoneTextBox), "LengthIndicatorThreshold", new CategoryAttribute(OtherProperties)); + AddCustomAttributes(typeof(PhoneTextBox), "LengthIndicatorThreshold", new DescriptionAttribute("Gets or sets the threshold after which the length indicator appears.")); + AddCustomAttributes(typeof(PhoneTextBox), "LengthIndicatorThreshold", new TypeConverterAttribute(typeof(Int16Converter))); + AddCustomAttributes(typeof(PhoneTextBox), "DisplayedMaxLength", new CategoryAttribute(OtherProperties)); + AddCustomAttributes(typeof(PhoneTextBox), "DisplayedMaxLength", new DescriptionAttribute("Gets or sets the displayed max length for the length indicator")); + AddCustomAttributes(typeof(PhoneTextBox), "DisplayedMaxLength", new TypeConverterAttribute(typeof(StringConverter))); + AddCustomAttributes(typeof(PhoneTextBox), "ActionIcon", new CategoryAttribute(OtherProperties)); + AddCustomAttributes(typeof(PhoneTextBox), "ActionIcon", new DescriptionAttribute("Gets or sets the icon displayed on the right side of the control.")); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Properties/AssemblyInfo.cs b/Microsoft.Phone.Controls.Toolkit.Design/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..5ef8233 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/Properties/AssemblyInfo.cs @@ -0,0 +1,33 @@ +// (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.Reflection; +using System.Runtime.InteropServices; +using Microsoft.Phone.Controls.Design; +using Microsoft.Windows.Design.Metadata; +using System.Resources; + +[assembly: AssemblyTitle("Microsoft.Phone.Controls.Toolkit.Design")] +[assembly: AssemblyDescription("Windows Phone Toolkit Design")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft Corporation")] +[assembly: AssemblyProduct("Microsoft® Windows Phone")] +[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("31bde016-ae15-472c-9b9e-2d12148824fc")] + +[assembly: AssemblyVersion("7.0.0.0")] +[assembly: AssemblyFileVersion("4.0.0.0")] + +[assembly: CLSCompliant(true)] + +[assembly: ProvideMetadata(typeof(MetadataStore))] + +[assembly: NeutralResourcesLanguage("en-us")] \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Properties/Resources.Designer.cs b/Microsoft.Phone.Controls.Toolkit.Design/Properties/Resources.Designer.cs new file mode 100644 index 0000000..13e685b --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/Properties/Resources.Designer.cs @@ -0,0 +1,81 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.1 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Phone.Controls.Toolkit.Design.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Phone.Controls.Toolkit.Design.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Auto Complete. + /// + internal static string AutoComplete { + get { + return ResourceManager.GetString("AutoComplete", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Common Properties. + /// + internal static string CommonProperties { + get { + return ResourceManager.GetString("CommonProperties", resourceCulture); + } + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit.Design/Properties/Resources.resx b/Microsoft.Phone.Controls.Toolkit.Design/Properties/Resources.resx new file mode 100644 index 0000000..c9a77cf --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/Properties/Resources.resx @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Auto Complete + + + Common Properties + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.Design/ToggleSwitchDesign.cs b/Microsoft.Phone.Controls.Toolkit.Design/ToggleSwitchDesign.cs new file mode 100644 index 0000000..b3d0d2a --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/ToggleSwitchDesign.cs @@ -0,0 +1,33 @@ +// (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 Microsoft.Windows.Design.Model; + +namespace Microsoft.Phone.Controls.Design +{ + /// + /// Initializes the default values for ToggleSwitch. + /// + internal class ToggleSwitchInitializer : DefaultInitializer + { + /// + /// Initializes the default values for the ModelItem. + /// + /// The ModelItem. + public override void InitializeDefaults(ModelItem modelItem) + { + if (modelItem == null) + { + throw new ArgumentNullException("modelItem"); + } + modelItem.Properties["Header"].SetValue(typeof(ToggleSwitch).Name); +#if VISUAL_STUDIO_DESIGNER + modelItem.Properties["Width"].SetValue(456d); + modelItem.Properties["Height"].SetValue(111d); +#endif + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.Design/ToggleSwitchMetadata.cs b/Microsoft.Phone.Controls.Toolkit.Design/ToggleSwitchMetadata.cs new file mode 100644 index 0000000..af9890c --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/ToggleSwitchMetadata.cs @@ -0,0 +1,33 @@ +// (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.ComponentModel; +using System.Windows; +using Microsoft.Windows.Design.Features; +using Microsoft.Windows.Design.Metadata; + +namespace Microsoft.Phone.Controls.Design +{ + internal class ToggleSwitchMetadata : AttributeTableBuilder + { + public ToggleSwitchMetadata() + { + // Type attributes + AddCustomAttributes(typeof(ToggleSwitch), new DescriptionAttribute("Represents a switch that can be toggled between two states.")); + AddCustomAttributes(typeof(ToggleSwitch), new FeatureAttribute(typeof(ToggleSwitchInitializer))); + + // Property attributes + AddCustomAttributes(typeof(ToggleSwitch), "IsChecked", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(ToggleSwitch), "IsChecked", new DescriptionAttribute("Gets or sets whether the ToggleSwitch is checked.")); + AddCustomAttributes(typeof(ToggleSwitch), "IsChecked", new TypeConverterAttribute(typeof(NullableBoolConverter))); + AddCustomAttributes(typeof(ToggleSwitch), "Header", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(ToggleSwitch), "Header", new DescriptionAttribute("Gets or sets the header.")); + AddCustomAttributes(typeof(ToggleSwitch), "Header", new TypeConverterAttribute(typeof(StringConverter))); + AddCustomAttributes(typeof(ToggleSwitch), "HeaderTemplate", new DescriptionAttribute("Gets or sets the template used to display the control's header.")); + AddCustomAttributes(typeof(ToggleSwitch), "Checked", new DescriptionAttribute("Occurs when a ToggleSwitch is checked.")); + AddCustomAttributes(typeof(ToggleSwitch), "Unchecked", new DescriptionAttribute("Occurs when a ToggleSwitch is unchecked.")); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.Design/TransitionsMetadata.cs b/Microsoft.Phone.Controls.Toolkit.Design/TransitionsMetadata.cs new file mode 100644 index 0000000..c7d3f53 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.Design/TransitionsMetadata.cs @@ -0,0 +1,57 @@ +// (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.ComponentModel; +using Microsoft.Windows.Design; +using Microsoft.Windows.Design.Metadata; + +namespace Microsoft.Phone.Controls.Design +{ + internal class TransitionsMetadata : AttributeTableBuilder + { + public TransitionsMetadata() + { + // Type attributes + + AddCustomAttributes(typeof(ITransition), new DescriptionAttribute("Controls the behavior of transitions.")); + AddCustomAttributes(typeof(ITransition), new ToolboxBrowsableAttribute(false)); + + AddCustomAttributes(typeof(NavigationTransition), new DescriptionAttribute("Has TransitionElements for the designer experiences.")); + AddCustomAttributes(typeof(NavigationTransition), new ToolboxBrowsableAttribute(false)); + + AddCustomAttributes(typeof(NavigationInTransition), new DescriptionAttribute("Has navigation-in TransitionElements for the designer experiences.")); + AddCustomAttributes(typeof(NavigationInTransition), new ToolboxBrowsableAttribute(false)); + + AddCustomAttributes(typeof(NavigationOutTransition), new DescriptionAttribute("Has navigation-out TransitionElements for the designer experiences.")); + AddCustomAttributes(typeof(NavigationOutTransition), new ToolboxBrowsableAttribute(false)); + + AddCustomAttributes(typeof(TransitionElement), new DescriptionAttribute("Transition factory for a particular transition family.")); + AddCustomAttributes(typeof(TransitionElement), new ToolboxBrowsableAttribute(false)); + + AddCustomAttributes(typeof(TransitionFrame), new DescriptionAttribute("Enables navigation transitions for PhoneApplicationPages.")); + AddCustomAttributes(typeof(TransitionFrame), new ToolboxBrowsableAttribute(false)); + + AddCustomAttributes(typeof(TransitionService), new DescriptionAttribute("Provides attached properties for navigation ITransitions.")); + AddCustomAttributes(typeof(TransitionService), new ToolboxBrowsableAttribute(false)); + + // Property attributes + + AddCustomAttributes(typeof(NavigationTransition), "Backward", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(NavigationTransition), "Backward", new DescriptionAttribute("Gets or sets the backward NavigationTransition.")); + AddCustomAttributes(typeof(NavigationTransition), "Forward", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(NavigationTransition), "Forward", new DescriptionAttribute("Gets or sets the forward NavigationTransition.")); + + AddCustomAttributes(typeof(NavigationInTransition), "Backward", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(NavigationInTransition), "Backward", new DescriptionAttribute("Gets or sets the backward NavigationTransition.")); + AddCustomAttributes(typeof(NavigationInTransition), "Forward", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(NavigationInTransition), "Forward", new DescriptionAttribute("Gets or sets the forward NavigationTransition.")); + + AddCustomAttributes(typeof(NavigationOutTransition), "Backward", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(NavigationOutTransition), "Backward", new DescriptionAttribute("Gets or sets the backward NavigationTransition.")); + AddCustomAttributes(typeof(NavigationOutTransition), "Forward", new CategoryAttribute(MetadataStore.CommonProperties)); + AddCustomAttributes(typeof(NavigationOutTransition), "Forward", new DescriptionAttribute("Gets or sets the forward NavigationTransition.")); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.VisualStudio.Design/GlobalSuppressions.cs b/Microsoft.Phone.Controls.Toolkit.VisualStudio.Design/GlobalSuppressions.cs new file mode 100644 index 0000000..e88a083 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.VisualStudio.Design/GlobalSuppressions.cs @@ -0,0 +1,16 @@ +// (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. + +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. +// +// To add a suppression to this file, right-click the message in the +// Error List, point to "Suppress Message(s)", and click +// "In Project Suppression File". +// You do not need to add suppressions to this file manually. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Satisfied for Release, not for Debug.")] \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.VisualStudio.Design/Metadata.cs b/Microsoft.Phone.Controls.Toolkit.VisualStudio.Design/Metadata.cs new file mode 100644 index 0000000..a5fc426 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.VisualStudio.Design/Metadata.cs @@ -0,0 +1,40 @@ +// (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.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Windows.Controls; +using Microsoft.Phone.Controls.Primitives; +using Microsoft.Windows.Design; +using Microsoft.Windows.Design.Features; +using Microsoft.Windows.Design.Metadata; +using Microsoft.Windows.Design.PropertyEditing; + +namespace Microsoft.Phone.Controls.Design +{ + /// + /// Provides design metadata. + /// + public class MetadataStore : IProvideAttributeTable + { + /// + /// Stores the string used to refer to the "Common Properties" section. + /// + public static readonly string CommonProperties = "Common Properties"; + + /// + /// Gets the attribute table. + /// + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Simple method happens to be highly coupled.")] + public AttributeTable AttributeTable + { + get + { + return new ToggleSwitchMetadata().CreateTable(); + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.VisualStudio.Design/Microsoft.Phone.Controls.Toolkit.VisualStudio.Design.csproj b/Microsoft.Phone.Controls.Toolkit.VisualStudio.Design/Microsoft.Phone.Controls.Toolkit.VisualStudio.Design.csproj new file mode 100644 index 0000000..041b603 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.VisualStudio.Design/Microsoft.Phone.Controls.Toolkit.VisualStudio.Design.csproj @@ -0,0 +1,96 @@ + + + + Debug + AnyCPU + 8.0.30703 + 2.0 + {BF8E3967-F531-440F-9AD9-C8330E96577B} + Library + Properties + Microsoft.Phone.Controls.Toolkit.Design + Microsoft.Phone.Controls.Toolkit.VisualStudio.Design + v4.0 + 512 + True + ..\PhoneToolkit.snk + %24/AgilityTeam/PhoneToolkit/Public/Microsoft.Phone.Controls.Toolkit.VisualStudio.Design + . + http://vstfdevdiv:8080/devdiv + {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C} + + + true + full + false + ..\Bin\Debug\ + TRACE;DEBUG;CODE_ANALYSIS;VISUAL_STUDIO_DESIGNER + prompt + 4 + true + AllRules.ruleset + + + pdbonly + true + ..\Bin\Release\ + TRACE;VISUAL_STUDIO_DESIGNER + prompt + 4 + + + + False + $(ProgramFiles)\Microsoft SDKs\Windows Phone\v7.1\Libraries\Silverlight\Microsoft.Phone.Controls.dll + + + + + + False + + + False + + + False + $(ProgramFiles)\Reference Assemblies\Microsoft\Framework\Silverlight\v4.0\Profile\WindowsPhone\System.Windows.dll + False + + + False + $(ProgramFiles)\Reference Assemblies\Microsoft\Framework\Silverlight\v4.0\Profile\WindowsPhone\Microsoft.Phone.dll + False + + + False + $(ProgramFiles)\Reference Assemblies\Microsoft\Framework\Silverlight\v4.0\Profile\WindowsPhone\Microsoft.Phone.Interop.dll + False + + + + + ToggleSwitchMetadata.cs + + + + + + ToggleSwitchDesign.cs + + + + + {0754458A-7AFC-463A-B27D-2F6980522119} + Microsoft.Phone.Controls.Toolkit + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit.VisualStudio.Design/Properties/AssemblyInfo.cs b/Microsoft.Phone.Controls.Toolkit.VisualStudio.Design/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..3df288a --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit.VisualStudio.Design/Properties/AssemblyInfo.cs @@ -0,0 +1,30 @@ +// (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.Reflection; +using System.Runtime.InteropServices; +using Microsoft.Phone.Controls.Design; +using Microsoft.Windows.Design.Metadata; + +[assembly: AssemblyTitle("Microsoft.Phone.Controls.Toolkit.VisualStudio.Design")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Microsoft.Phone.Controls.Toolkit.VisualStudio.Design")] +[assembly: AssemblyCopyright("Copyright © 2010")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("f8dcdb60-9c8c-4e4e-8fdb-95e30ccd1374")] + +[assembly: AssemblyVersion("7.0.0.0")] +[assembly: AssemblyFileVersion("4.0.0.0")] + +[assembly: CLSCompliant(true)] + +[assembly: ProvideMetadata(typeof(MetadataStore))] \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/AutoCompleteBox.cs b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/AutoCompleteBox.cs new file mode 100644 index 0000000..2f9a2e8 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/AutoCompleteBox.cs @@ -0,0 +1,2677 @@ +// (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.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Markup; +using System.Windows.Media; +using System.Windows.Threading; + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// Represents a control that provides a text box for user input and a + /// drop-down that contains possible matches based on the input in the text + /// box. + /// + /// Stable + [TemplatePart(Name = AutoCompleteBox.ElementSelectionAdapter, Type = typeof(ISelectionAdapter))] + [TemplatePart(Name = AutoCompleteBox.ElementSelector, Type = typeof(Selector))] + [TemplatePart(Name = AutoCompleteBox.ElementTextBox, Type = typeof(TextBox))] + [TemplatePart(Name = AutoCompleteBox.ElementPopup, Type = typeof(Popup))] + [StyleTypedProperty(Property = AutoCompleteBox.ElementTextBoxStyle, StyleTargetType = typeof(TextBox))] + [StyleTypedProperty(Property = AutoCompleteBox.ElementItemContainerStyle, StyleTargetType = typeof(ListBox))] + [TemplateVisualState(Name = VisualStates.StateNormal, GroupName = VisualStates.GroupCommon)] + [TemplateVisualState(Name = VisualStates.StateMouseOver, GroupName = VisualStates.GroupCommon)] + [TemplateVisualState(Name = VisualStates.StatePressed, GroupName = VisualStates.GroupCommon)] + [TemplateVisualState(Name = VisualStates.StateDisabled, GroupName = VisualStates.GroupCommon)] + [TemplateVisualState(Name = VisualStates.StateFocused, GroupName = VisualStates.GroupFocus)] + [TemplateVisualState(Name = VisualStates.StateUnfocused, GroupName = VisualStates.GroupFocus)] + [TemplateVisualState(Name = VisualStates.StatePopupClosed, GroupName = VisualStates.GroupPopup)] + [TemplateVisualState(Name = VisualStates.StatePopupOpened, GroupName = VisualStates.GroupPopup)] + [TemplateVisualState(Name = VisualStates.StateValid, GroupName = VisualStates.GroupValidation)] + [TemplateVisualState(Name = VisualStates.StateInvalidFocused, GroupName = VisualStates.GroupValidation)] + [TemplateVisualState(Name = VisualStates.StateInvalidUnfocused, GroupName = VisualStates.GroupValidation)] + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Large implementation keeps the components contained.")] + [ContentProperty("ItemsSource")] + public partial class AutoCompleteBox : Control, IUpdateVisualState + { + #region Template part and style names + + /// + /// Specifies the name of the selection adapter TemplatePart. + /// + private const string ElementSelectionAdapter = "SelectionAdapter"; + + /// + /// Specifies the name of the Selector TemplatePart. + /// + private const string ElementSelector = "Selector"; + + /// + /// Specifies the name of the Popup TemplatePart. + /// + private const string ElementPopup = "Popup"; + + /// + /// The name for the text box part. + /// + private const string ElementTextBox = "Text"; + + /// + /// The name for the text box style. + /// + private const string ElementTextBoxStyle = "TextBoxStyle"; + + /// + /// The name for the adapter's item container style. + /// + private const string ElementItemContainerStyle = "ItemContainerStyle"; + + #endregion + + /// + /// Gets or sets a local cached copy of the items data. + /// + private List _items; + + /// + /// Gets or sets the observable collection that contains references to + /// all of the items in the generated view of data that is provided to + /// the selection-style control adapter. + /// + private ObservableCollection _view; + + /// + /// Gets or sets a value to ignore a number of pending change handlers. + /// The value is decremented after each use. This is used to reset the + /// value of properties without performing any of the actions in their + /// change handlers. + /// + /// The int is important as a value because the TextBox + /// TextChanged event does not immediately fire, and this will allow for + /// nested property changes to be ignored. + private int _ignoreTextPropertyChange; + + /// + /// Gets or sets a value indicating whether to ignore calling a pending + /// change handlers. + /// + private bool _ignorePropertyChange; + + /// + /// Gets or sets a value indicating whether to ignore the selection + /// changed event. + /// + private bool _ignoreTextSelectionChange; + + /// + /// Gets or sets a value indicating whether to skip the text update + /// processing when the selected item is updated. + /// + private bool _skipSelectedItemTextUpdate; + + /// + /// Gets or sets the last observed text box selection start location. + /// + private int _textSelectionStart; + + /// + /// Gets or sets a value indicating whether the user is in the process + /// of inputting text. This is used so that we do not update + /// _textSelectionStart while the user is using an IME. + /// + private bool _inputtingText; + + /// + /// Gets or sets a value indicating whether the user initiated the + /// current populate call. + /// + private bool _userCalledPopulate; + + /// + /// A value indicating whether the popup has been opened at least once. + /// + private bool _popupHasOpened; + + /// + /// Gets or sets the DispatcherTimer used for the MinimumPopulateDelay + /// condition for auto completion. + /// + private DispatcherTimer _delayTimer; + + /// + /// Gets or sets a value indicating whether a read-only dependency + /// property change handler should allow the value to be set. This is + /// used to ensure that read-only properties cannot be changed via + /// SetValue, etc. + /// + private bool _allowWrite; + + /// + /// Gets or sets the helper that provides all of the standard + /// interaction functionality. Making it internal for subclass access. + /// + internal InteractionHelper Interaction { get; set; } + + /// + /// Gets or sets the BindingEvaluator, a framework element that can + /// provide updated string values from a single binding. + /// + private BindingEvaluator _valueBindingEvaluator; + + /// + /// A weak event listener for the collection changed event. + /// + private WeakEventListener _collectionChangedWeakEventListener; + + #region public int MinimumPrefixLength + /// + /// Gets or sets the minimum number of characters required to be entered + /// in the text box before the + /// displays + /// possible matches. + /// matches. + /// + /// + /// The minimum number of characters to be entered in the text box + /// before the + /// displays possible matches. The default is 1. + /// + /// + /// If you set MinimumPrefixLength to -1, the AutoCompleteBox will + /// not provide possible matches. There is no maximum value, but + /// setting MinimumPrefixLength to value that is too large will + /// prevent the AutoCompleteBox from providing possible matches as well. + /// + public int MinimumPrefixLength + { + get { return (int)GetValue(MinimumPrefixLengthProperty); } + set { SetValue(MinimumPrefixLengthProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty MinimumPrefixLengthProperty = + DependencyProperty.Register( + "MinimumPrefixLength", + typeof(int), + typeof(AutoCompleteBox), + new PropertyMetadata(1, OnMinimumPrefixLengthPropertyChanged)); + + /// + /// MinimumPrefixLengthProperty property changed handler. + /// + /// AutoCompleteBox that changed its MinimumPrefixLength. + /// Event arguments. + [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "MinimumPrefixLength is the name of the actual dependency property.")] + private static void OnMinimumPrefixLengthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + int newValue = (int)e.NewValue; + + if (newValue < 0 && newValue != -1) + { + throw new ArgumentOutOfRangeException("MinimumPrefixLength"); + } + } + #endregion public int MinimumPrefixLength + + #region public int MinimumPopulateDelay + /// + /// Gets or sets the minimum delay, in milliseconds, after text is typed + /// in the text box before the + /// control + /// populates the list of possible matches in the drop-down. + /// + /// The minimum delay, in milliseconds, after text is typed in + /// the text box, but before the + /// populates + /// the list of possible matches in the drop-down. The default is 0. + /// The set value is less than 0. + public int MinimumPopulateDelay + { + get { return (int)GetValue(MinimumPopulateDelayProperty); } + set { SetValue(MinimumPopulateDelayProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty MinimumPopulateDelayProperty = + DependencyProperty.Register( + "MinimumPopulateDelay", + typeof(int), + typeof(AutoCompleteBox), + new PropertyMetadata(OnMinimumPopulateDelayPropertyChanged)); + + /// + /// MinimumPopulateDelayProperty property changed handler. Any current + /// dispatcher timer will be stopped. The timer will not be restarted + /// until the next TextUpdate call by the user. + /// + /// AutoCompleteTextBox that changed its + /// MinimumPopulateDelay. + /// Event arguments. + [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "The exception is most likely to be called through the CLR property setter.")] + private static void OnMinimumPopulateDelayPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + AutoCompleteBox source = d as AutoCompleteBox; + + if (source._ignorePropertyChange) + { + source._ignorePropertyChange = false; + return; + } + + int newValue = (int)e.NewValue; + if (newValue < 0) + { + source._ignorePropertyChange = true; + d.SetValue(e.Property, e.OldValue); + + //throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.AutoComplete_OnMinimumPopulateDelayPropertyChanged_InvalidValue, newValue), "value"); + } + + // Stop any existing timer + if (source._delayTimer != null) + { + source._delayTimer.Stop(); + + if (newValue == 0) + { + source._delayTimer = null; + } + } + + // Create or clear a dispatcher timer instance + if (newValue > 0 && source._delayTimer == null) + { + source._delayTimer = new DispatcherTimer(); + source._delayTimer.Tick += source.PopulateDropDown; + } + + // Set the new tick interval + if (newValue > 0 && source._delayTimer != null) + { + source._delayTimer.Interval = TimeSpan.FromMilliseconds(newValue); + } + } + #endregion public int MinimumPopulateDelay + + #region public bool IsTextCompletionEnabled + /// + /// Gets or sets a value indicating whether the first possible match + /// found during the filtering process will be displayed automatically + /// in the text box. + /// + /// + /// True if the first possible match found will be displayed + /// automatically in the text box; otherwise, false. The default is + /// false. + /// + public bool IsTextCompletionEnabled + { + get { return (bool)GetValue(IsTextCompletionEnabledProperty); } + set { SetValue(IsTextCompletionEnabledProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty IsTextCompletionEnabledProperty = + DependencyProperty.Register( + "IsTextCompletionEnabled", + typeof(bool), + typeof(AutoCompleteBox), + new PropertyMetadata(false, null)); + + #endregion public bool IsTextCompletionEnabled + + #region public DataTemplate ItemTemplate + /// + /// Gets or sets the used + /// to display each item in the drop-down portion of the control. + /// + /// The used to + /// display each item in the drop-down. The default is null. + /// + /// You use the ItemTemplate property to specify the visualization + /// of the data objects in the drop-down portion of the AutoCompleteBox + /// control. If your AutoCompleteBox is bound to a collection and you + /// do not provide specific display instructions by using a + /// DataTemplate, the resulting UI of each item is a string + /// representation of each object in the underlying collection. + /// + public DataTemplate ItemTemplate + { + get { return GetValue(ItemTemplateProperty) as DataTemplate; } + set { SetValue(ItemTemplateProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty ItemTemplateProperty = + DependencyProperty.Register( + "ItemTemplate", + typeof(DataTemplate), + typeof(AutoCompleteBox), + new PropertyMetadata(null)); + + #endregion public DataTemplate ItemTemplate + + #region public Style ItemContainerStyle + /// + /// Gets or sets the that is + /// applied to the selection adapter contained in the drop-down portion + /// of the + /// control. + /// + /// The applied to the + /// selection adapter contained in the drop-down portion of the + /// control. + /// The default is null. + /// + /// The default selection adapter contained in the drop-down is a + /// ListBox control. + /// + public Style ItemContainerStyle + { + get { return GetValue(ItemContainerStyleProperty) as Style; } + set { SetValue(ItemContainerStyleProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty ItemContainerStyleProperty = + DependencyProperty.Register( + ElementItemContainerStyle, + typeof(Style), + typeof(AutoCompleteBox), + new PropertyMetadata(null, null)); + + #endregion public Style ItemContainerStyle + + #region public Style TextBoxStyle + /// + /// Gets or sets the applied to + /// the text box portion of the + /// control. + /// + /// The applied to the text + /// box portion of the + /// control. + /// The default is null. + public Style TextBoxStyle + { + get { return GetValue(TextBoxStyleProperty) as Style; } + set { SetValue(TextBoxStyleProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty TextBoxStyleProperty = + DependencyProperty.Register( + ElementTextBoxStyle, + typeof(Style), + typeof(AutoCompleteBox), + new PropertyMetadata(null)); + + #endregion public Style TextBoxStyle + + #region public double MaxDropDownHeight + /// + /// Gets or sets the maximum height of the drop-down portion of the + /// control. + /// + /// The maximum height of the drop-down portion of the + /// control. + /// The default is . + /// The specified value is less than 0. + public double MaxDropDownHeight + { + get { return (double)GetValue(MaxDropDownHeightProperty); } + set { SetValue(MaxDropDownHeightProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty MaxDropDownHeightProperty = + DependencyProperty.Register( + "MaxDropDownHeight", + typeof(double), + typeof(AutoCompleteBox), + new PropertyMetadata(double.PositiveInfinity, OnMaxDropDownHeightPropertyChanged)); + + /// + /// MaxDropDownHeightProperty property changed handler. + /// + /// AutoCompleteTextBox that changed its MaxDropDownHeight. + /// Event arguments. + [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "The exception will be called through a CLR setter in most cases.")] + private static void OnMaxDropDownHeightPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + AutoCompleteBox source = d as AutoCompleteBox; + if (source._ignorePropertyChange) + { + source._ignorePropertyChange = false; + return; + } + + double newValue = (double)e.NewValue; + + // Revert to the old value if invalid (negative) + if (newValue < 0) + { + source._ignorePropertyChange = true; + source.SetValue(e.Property, e.OldValue); + + //throw new ArgumentException(string.Format(CultureInfo.InvariantCulture, Properties.Resources.AutoComplete_OnMaxDropDownHeightPropertyChanged_InvalidValue, e.NewValue), "value"); + } + + source.OnMaxDropDownHeightChanged(newValue); + } + #endregion public double MaxDropDownHeight + + #region public bool IsDropDownOpen + /// + /// Gets or sets a value indicating whether the drop-down portion of + /// the control is open. + /// + /// + /// True if the drop-down is open; otherwise, false. The default is + /// false. + /// + public bool IsDropDownOpen + { + get { return (bool)GetValue(IsDropDownOpenProperty); } + set { SetValue(IsDropDownOpenProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty IsDropDownOpenProperty = + DependencyProperty.Register( + "IsDropDownOpen", + typeof(bool), + typeof(AutoCompleteBox), + new PropertyMetadata(false, OnIsDropDownOpenPropertyChanged)); + + /// + /// IsDropDownOpenProperty property changed handler. + /// + /// AutoCompleteTextBox that changed its IsDropDownOpen. + /// Event arguments. + private static void OnIsDropDownOpenPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + AutoCompleteBox source = d as AutoCompleteBox; + + // Ignore the change if requested + if (source._ignorePropertyChange) + { + source._ignorePropertyChange = false; + return; + } + + bool oldValue = (bool)e.OldValue; + bool newValue = (bool)e.NewValue; + + if (newValue) + { + source.TextUpdated(source.Text, true); + } + else + { + source.ClosingDropDown(oldValue); + } + + source.UpdateVisualState(true); + } + #endregion public bool IsDropDownOpen + + #region public IEnumerable ItemsSource + /// + /// Gets or sets a collection that is used to generate the items for the + /// drop-down portion of the + /// control. + /// + /// The collection that is used to generate the items of the + /// drop-down portion of the + /// control. + public IEnumerable ItemsSource + { + get { return GetValue(ItemsSourceProperty) as IEnumerable; } + set { SetValue(ItemsSourceProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty ItemsSourceProperty = + DependencyProperty.Register( + "ItemsSource", + typeof(IEnumerable), + typeof(AutoCompleteBox), + new PropertyMetadata(OnItemsSourcePropertyChanged)); + + /// + /// ItemsSourceProperty property changed handler. + /// + /// AutoCompleteBox that changed its ItemsSource. + /// Event arguments. + private static void OnItemsSourcePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + AutoCompleteBox autoComplete = d as AutoCompleteBox; + autoComplete.OnItemsSourceChanged((IEnumerable)e.OldValue, (IEnumerable)e.NewValue); + } + + #endregion public IEnumerable ItemsSource + + #region public object SelectedItem + /// + /// Gets or sets the selected item in the drop-down. + /// + /// The selected item in the drop-down. + /// + /// If the IsTextCompletionEnabled property is true and text typed by + /// the user matches an item in the ItemsSource collection, which is + /// then displayed in the text box, the SelectedItem property will be + /// a null reference. + /// + public object SelectedItem + { + get { return GetValue(SelectedItemProperty) as object; } + set { SetValue(SelectedItemProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier the + /// + /// dependency property. + public static readonly DependencyProperty SelectedItemProperty = + DependencyProperty.Register( + "SelectedItem", + typeof(object), + typeof(AutoCompleteBox), + new PropertyMetadata(OnSelectedItemPropertyChanged)); + + /// + /// SelectedItemProperty property changed handler. Fires the + /// SelectionChanged event. The event data will contain any non-null + /// removed items and non-null additions. + /// + /// AutoCompleteBox that changed its SelectedItem. + /// Event arguments. + private static void OnSelectedItemPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + AutoCompleteBox source = d as AutoCompleteBox; + + if (source._ignorePropertyChange) + { + source._ignorePropertyChange = false; + return; + } + + // Update the text display + if (source._skipSelectedItemTextUpdate) + { + source._skipSelectedItemTextUpdate = false; + } + else + { + source.OnSelectedItemChanged(e.NewValue); + } + + // Fire the SelectionChanged event + List removed = new List(); + if (e.OldValue != null) + { + removed.Add(e.OldValue); + } + + List added = new List(); + if (e.NewValue != null) + { + added.Add(e.NewValue); + } + + source.OnSelectionChanged(new SelectionChangedEventArgs( +#if !SILVERLIGHT + SelectionChangedEvent, +#endif + removed, + added)); + } + +#if !SILVERLIGHT + /// + /// Declares the routed event for SelectionChanged. + /// + public static readonly RoutedEvent SelectionChangedEvent = EventManager.RegisterRoutedEvent( + "SelectionChanged", RoutingStrategy.Bubble, typeof(SelectionChangedEventHandler), typeof(AutoCompleteBox)); +#endif + + /// + /// Called when the selected item is changed, updates the text value + /// that is displayed in the text box part. + /// + /// The new item. + private void OnSelectedItemChanged(object newItem) + { + string text; + + if (newItem == null) + { + text = SearchText; + } + else + { + text = FormatValue(newItem, true); + } + + // Update the Text property and the TextBox values + UpdateTextValue(text); + + // Move the caret to the end of the text box + if (TextBox != null && Text != null) + { + TextBox.SelectionStart = Text.Length; + } + } + + #endregion public object SelectedItem + + #region public string Text + /// + /// Gets or sets the text in the text box portion of the + /// control. + /// + /// The text in the text box portion of the + /// control. + public string Text + { + get { return GetValue(TextProperty) as string; } + set { SetValue(TextProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty TextProperty = + DependencyProperty.Register( + "Text", + typeof(string), + typeof(AutoCompleteBox), + new PropertyMetadata(string.Empty, OnTextPropertyChanged)); + + /// + /// TextProperty property changed handler. + /// + /// AutoCompleteBox that changed its Text. + /// Event arguments. + private static void OnTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + AutoCompleteBox source = d as AutoCompleteBox; + source.TextUpdated((string)e.NewValue, false); + } + + #endregion public string Text + + #region public string SearchText + /// + /// Gets the text that is used to filter items in the + /// + /// item collection. + /// + /// The text that is used to filter items in the + /// + /// item collection. + /// + /// The SearchText value is typically the same as the + /// Text property, but is set after the TextChanged event occurs + /// and before the Populating event. + /// + public string SearchText + { + get { return (string)GetValue(SearchTextProperty); } + + private set + { + try + { + _allowWrite = true; + SetValue(SearchTextProperty, value); + } + finally + { + _allowWrite = false; + } + } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty SearchTextProperty = + DependencyProperty.Register( + "SearchText", + typeof(string), + typeof(AutoCompleteBox), + new PropertyMetadata(string.Empty, OnSearchTextPropertyChanged)); + + /// + /// OnSearchTextProperty property changed handler. + /// + /// AutoCompleteBox that changed its SearchText. + /// Event arguments. + private static void OnSearchTextPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + AutoCompleteBox source = d as AutoCompleteBox; + if (source._ignorePropertyChange) + { + source._ignorePropertyChange = false; + return; + } + + // Ensure the property is only written when expected + if (!source._allowWrite) + { + // Reset the old value before it was incorrectly written + source._ignorePropertyChange = true; + source.SetValue(e.Property, e.OldValue); + + //throw new InvalidOperationException(Properties.Resources.AutoComplete_OnSearchTextPropertyChanged_InvalidWrite); + } + } + #endregion public string SearchText + + #region public AutoCompleteFilterMode FilterMode + /// + /// Gets or sets how the text in the text box is used to filter items + /// specified by the + /// + /// property for display in the drop-down. + /// + /// One of the + /// + /// values The default is + /// . + /// The specified value is + /// not a valid + /// . + /// + /// Use the FilterMode property to specify how possible matches are + /// filtered. For example, possible matches can be filtered in a + /// predefined or custom way. The search mode is automatically set to + /// Custom if you set the ItemFilter property. + /// + public AutoCompleteFilterMode FilterMode + { + get { return (AutoCompleteFilterMode)GetValue(FilterModeProperty); } + set { SetValue(FilterModeProperty, value); } + } + + /// + /// Gets the identifier for the + /// + /// dependency property. + /// + public static readonly DependencyProperty FilterModeProperty = + DependencyProperty.Register( + "FilterMode", + typeof(AutoCompleteFilterMode), + typeof(AutoCompleteBox), + new PropertyMetadata(AutoCompleteFilterMode.StartsWith, OnFilterModePropertyChanged)); + + /// + /// FilterModeProperty property changed handler. + /// + /// AutoCompleteBox that changed its FilterMode. + /// Event arguments. + [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "The exception will be thrown when the CLR setter is used in most situations.")] + private static void OnFilterModePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + AutoCompleteBox source = d as AutoCompleteBox; + AutoCompleteFilterMode mode = (AutoCompleteFilterMode)e.NewValue; + + if (mode != AutoCompleteFilterMode.Contains && + mode != AutoCompleteFilterMode.ContainsCaseSensitive && + mode != AutoCompleteFilterMode.ContainsOrdinal && + mode != AutoCompleteFilterMode.ContainsOrdinalCaseSensitive && + mode != AutoCompleteFilterMode.Custom && + mode != AutoCompleteFilterMode.Equals && + mode != AutoCompleteFilterMode.EqualsCaseSensitive && + mode != AutoCompleteFilterMode.EqualsOrdinal && + mode != AutoCompleteFilterMode.EqualsOrdinalCaseSensitive && + mode != AutoCompleteFilterMode.None && + mode != AutoCompleteFilterMode.StartsWith && + mode != AutoCompleteFilterMode.StartsWithCaseSensitive && + mode != AutoCompleteFilterMode.StartsWithOrdinal && + mode != AutoCompleteFilterMode.StartsWithOrdinalCaseSensitive) + { + source.SetValue(e.Property, e.OldValue); + + //throw new ArgumentException(Properties.Resources.AutoComplete_OnFilterModePropertyChanged_InvalidValue, "value"); + } + + // Sets the filter predicate for the new value + AutoCompleteFilterMode newValue = (AutoCompleteFilterMode)e.NewValue; + source.TextFilter = AutoCompleteSearch.GetFilter(newValue); + } + #endregion public AutoCompleteFilterMode FilterMode + + #region public AutoCompleteFilterPredicate ItemFilter + /// + /// Gets or sets the custom method that uses user-entered text to filter + /// the items specified by the + /// + /// property for display in the drop-down. + /// + /// The custom method that uses the user-entered text to filter + /// the items specified by the + /// + /// property. The default is null. + /// + /// The filter mode is automatically set to Custom if you set the + /// ItemFilter property. + /// + public AutoCompleteFilterPredicate ItemFilter + { + get { return GetValue(ItemFilterProperty) as AutoCompleteFilterPredicate; } + set { SetValue(ItemFilterProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty ItemFilterProperty = + DependencyProperty.Register( + "ItemFilter", + typeof(AutoCompleteFilterPredicate), + typeof(AutoCompleteBox), + new PropertyMetadata(OnItemFilterPropertyChanged)); + + /// + /// ItemFilterProperty property changed handler. + /// + /// AutoCompleteBox that changed its ItemFilter. + /// Event arguments. + private static void OnItemFilterPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + AutoCompleteBox source = d as AutoCompleteBox; + AutoCompleteFilterPredicate value = e.NewValue as AutoCompleteFilterPredicate; + + // If null, revert to the "None" predicate + if (value == null) + { + source.FilterMode = AutoCompleteFilterMode.None; + } + else + { + source.FilterMode = AutoCompleteFilterMode.Custom; + source.TextFilter = null; + } + } + #endregion public AutoCompleteFilterPredicate ItemFilter + + #region public AutoCompleteStringFilterPredicate TextFilter + /// + /// Gets or sets the custom method that uses the user-entered text to + /// filter items specified by the + /// + /// property in a text-based way for display in the drop-down. + /// + /// The custom method that uses the user-entered text to filter + /// items specified by the + /// + /// property in a text-based way for display in the drop-down. + /// + /// The search mode is automatically set to Custom if you set the + /// TextFilter property. + /// + public AutoCompleteFilterPredicate TextFilter + { + get { return GetValue(TextFilterProperty) as AutoCompleteFilterPredicate; } + set { SetValue(TextFilterProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// The identifier for the + /// + /// dependency property. + public static readonly DependencyProperty TextFilterProperty = + DependencyProperty.Register( + "TextFilter", + typeof(AutoCompleteFilterPredicate), + typeof(AutoCompleteBox), + new PropertyMetadata(AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith))); + #endregion public AutoCompleteStringFilterPredicate TextFilter + +#if WINDOWS_PHONE + #region public InputScope InputScope + /// + /// Gets or sets the + /// + /// used by the Text template part. + /// + public InputScope InputScope + { + get { return (InputScope)GetValue(InputScopeProperty); } + set { SetValue(InputScopeProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + public static readonly DependencyProperty InputScopeProperty = + DependencyProperty.Register( + "InputScope", + typeof(InputScope), + typeof(AutoCompleteBox), + null); + #endregion public InputScope InputScope +#endif + + #region Template parts + + /// + /// Gets or sets the drop down popup control. + /// + private PopupHelper DropDownPopup { get; set; } + + /// + /// Determines whether text completion should be done. + /// + private static bool IsCompletionEnabled + { + get + { + PhoneApplicationFrame frame; + return PhoneHelper.TryGetPhoneApplicationFrame(out frame) && frame.IsPortrait(); + } + } + + /// + /// The TextBox template part. + /// + private TextBox _text; + + /// + /// The SelectionAdapter. + /// + private ISelectionAdapter _adapter; + + /// + /// Gets or sets the Text template part. + /// + internal TextBox TextBox + { + get { return _text; } + set + { + // Detach existing handlers + if (_text != null) + { + _text.SelectionChanged -= OnTextBoxSelectionChanged; + _text.TextChanged -= OnTextBoxTextChanged; + } + + _text = value; + + // Attach handlers + if (_text != null) + { + _text.SelectionChanged += OnTextBoxSelectionChanged; + _text.TextChanged += OnTextBoxTextChanged; + + if (Text != null) + { + UpdateTextValue(Text); + } + } + } + } + + /// + /// Gets or sets the selection adapter used to populate the drop-down + /// with a list of selectable items. + /// + /// The selection adapter used to populate the drop-down with a + /// list of selectable items. + /// + /// You can use this property when you create an automation peer to + /// use with AutoCompleteBox or deriving from AutoCompleteBox to + /// create a custom control. + /// + protected internal ISelectionAdapter SelectionAdapter + { + get { return _adapter; } + set + { + if (_adapter != null) + { + _adapter.SelectionChanged -= OnAdapterSelectionChanged; + _adapter.Commit -= OnAdapterSelectionComplete; + _adapter.Cancel -= OnAdapterSelectionCanceled; + _adapter.Cancel -= OnAdapterSelectionComplete; + _adapter.ItemsSource = null; + } + + _adapter = value; + + if (_adapter != null) + { + _adapter.SelectionChanged += OnAdapterSelectionChanged; + _adapter.Commit += OnAdapterSelectionComplete; + _adapter.Cancel += OnAdapterSelectionCanceled; + _adapter.Cancel += OnAdapterSelectionComplete; + _adapter.ItemsSource = _view; + } + } + } + + #endregion + + /// + /// Occurs when the text in the text box portion of the + /// changes. + /// + public event RoutedEventHandler TextChanged; + + /// + /// Occurs when the + /// is + /// populating the drop-down with possible matches based on the + /// + /// property. + /// + /// + /// If the event is canceled, by setting the PopulatingEventArgs.Cancel + /// property to true, the AutoCompleteBox will not automatically + /// populate the selection adapter contained in the drop-down. + /// In this case, if you want possible matches to appear, you must + /// provide the logic for populating the selection adapter. + /// + public event PopulatingEventHandler Populating; + + /// + /// Occurs when the + /// has + /// populated the drop-down with possible matches based on the + /// + /// property. + /// + public event PopulatedEventHandler Populated; + + /// + /// Occurs when the value of the + /// + /// property is changing from false to true. + /// + public event RoutedPropertyChangingEventHandler DropDownOpening; + + /// + /// Occurs when the value of the + /// + /// property has changed from false to true and the drop-down is open. + /// + public event RoutedPropertyChangedEventHandler DropDownOpened; + + /// + /// Occurs when the + /// + /// property is changing from true to false. + /// + public event RoutedPropertyChangingEventHandler DropDownClosing; + + /// + /// Occurs when the + /// + /// property was changed from true to false and the drop-down is open. + /// + public event RoutedPropertyChangedEventHandler DropDownClosed; + + /// + /// Occurs when the selected item in the drop-down portion of the + /// has + /// changed. + /// + public event SelectionChangedEventHandler SelectionChanged; + + /// + /// Gets or sets the that + /// is used to get the values for display in the text portion of + /// the + /// control. + /// + /// The object used + /// when binding to a collection property. + public Binding ValueMemberBinding + { + get + { + return _valueBindingEvaluator != null ? _valueBindingEvaluator.ValueBinding : null; + } + set + { + _valueBindingEvaluator = new BindingEvaluator(value); + } + } + + /// + /// Gets or sets the property path that is used to get values for + /// display in the text portion of the + /// control. + /// + /// The property path that is used to get values for display in + /// the text portion of the + /// control. + public string ValueMemberPath + { + get + { + return (ValueMemberBinding != null) ? ValueMemberBinding.Path.Path : null; + } + set + { + ValueMemberBinding = value == null ? null : new Binding(value); + } + } + + /// + /// Initializes a new instance of the + /// class. + /// + public AutoCompleteBox() + { + DefaultStyleKey = typeof(AutoCompleteBox); + + Loaded += (sender, e) => ApplyTemplate(); +#if WINDOWS_PHONE + Loaded += delegate + { + PhoneApplicationFrame frame; + if (PhoneHelper.TryGetPhoneApplicationFrame(out frame)) + { + frame.OrientationChanged += delegate + { + IsDropDownOpen = false; + }; + } + }; +#endif + IsEnabledChanged += ControlIsEnabledChanged; + + Interaction = new InteractionHelper(this); + + // Creating the view here ensures that View is always != null + ClearView(); + } + + /// + /// Arranges and sizes the + /// + /// control and its contents. + /// + /// The size allowed for the + /// control. + /// The , unchanged. + protected override Size ArrangeOverride(Size finalSize) + { + Size r = base.ArrangeOverride(finalSize); + if (DropDownPopup != null) + { +#if WINDOWS_PHONE + DropDownPopup.Arrange(finalSize); +#else + DropDownPopup.Arrange(); +#endif + } + return r; + } + + /// + /// Builds the visual tree for the + /// control + /// when a new template is applied. + /// + public override void OnApplyTemplate() + { + if (TextBox != null) + { +#if SILVERLIGHT +#if WINDOWS_PHONE + TextBox.RemoveHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnUIElementKeyDown)); + TextBox.RemoveHandler(UIElement.KeyUpEvent, new KeyEventHandler(OnUIElementKeyUp)); +#else + TextBox.RemoveHandler(TextBox.TextInputStartEvent, new TextCompositionEventHandler(OnTextBoxTextInputStart)); + TextBox.RemoveHandler(TextBox.TextInputEvent, new TextCompositionEventHandler(OnTextBoxTextInput)); +#endif +#else + TextBox.PreviewKeyDown -= OnTextBoxPreviewKeyDown; +#endif + } + + if (DropDownPopup != null) + { + DropDownPopup.Closed -= DropDownPopup_Closed; + DropDownPopup.FocusChanged -= OnDropDownFocusChanged; + DropDownPopup.UpdateVisualStates -= OnDropDownPopupUpdateVisualStates; + DropDownPopup.BeforeOnApplyTemplate(); + DropDownPopup = null; + } + + base.OnApplyTemplate(); + + // Set the template parts. Individual part setters remove and add + // any event handlers. + Popup popup = GetTemplateChild(ElementPopup) as Popup; + if (popup != null) + { + DropDownPopup = new PopupHelper(this, popup); + DropDownPopup.MaxDropDownHeight = MaxDropDownHeight; + DropDownPopup.AfterOnApplyTemplate(); + DropDownPopup.Closed += DropDownPopup_Closed; + DropDownPopup.FocusChanged += OnDropDownFocusChanged; + DropDownPopup.UpdateVisualStates += OnDropDownPopupUpdateVisualStates; + } + SelectionAdapter = GetSelectionAdapterPart(); + TextBox = GetTemplateChild(AutoCompleteBox.ElementTextBox) as TextBox; + if (TextBox != null) + { +#if SILVERLIGHT +#if WINDOWS_PHONE + TextBox.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(OnUIElementKeyDown), true); + TextBox.AddHandler(UIElement.KeyUpEvent, new KeyEventHandler(OnUIElementKeyUp), true); +#else + TextBox.AddHandler(TextBox.TextInputStartEvent, new TextCompositionEventHandler(OnTextBoxTextInputStart), true); + TextBox.AddHandler(TextBox.TextInputEvent, new TextCompositionEventHandler(OnTextBoxTextInput), true); +#endif +#else + TextBox.PreviewKeyDown += OnTextBoxPreviewKeyDown; +#endif + } + + Interaction.OnApplyTemplateBase(); + + // If the drop down property indicates that the popup is open, + // flip its value to invoke the changed handler. + if (IsDropDownOpen && DropDownPopup != null && !DropDownPopup.IsOpen) + { + OpeningDropDown(false); + } + } + + /// + /// Allows the popup wrapper to fire visual state change events. + /// + /// The source object. + /// The event data. + private void OnDropDownPopupUpdateVisualStates(object sender, EventArgs e) + { + UpdateVisualState(true); + } + + /// + /// Allows the popup wrapper to fire the FocusChanged event. + /// + /// The source object. + /// The event data. + private void OnDropDownFocusChanged(object sender, EventArgs e) + { + FocusChanged(HasFocus()); + } + + /// + /// Begin closing the drop-down. + /// + /// The original value. + private void ClosingDropDown(bool oldValue) + { + bool delayedClosingVisual = false; + if (DropDownPopup != null) + { + delayedClosingVisual = DropDownPopup.UsesClosingVisualState; + } + + RoutedPropertyChangingEventArgs args = new RoutedPropertyChangingEventArgs(IsDropDownOpenProperty, oldValue, false, true); + + OnDropDownClosing(args); + + if (_view == null || _view.Count == 0) + { + delayedClosingVisual = false; + } + + if (args.Cancel) + { + _ignorePropertyChange = true; + SetValue(IsDropDownOpenProperty, oldValue); + } + else + { + // Immediately close the drop down window: + // When a popup closed visual state is present, the code path is + // slightly different and the actual call to CloseDropDown will + // be called only after the visual state's transition is done +#if !WINDOWS_PHONE + RaiseExpandCollapseAutomationEvent(oldValue, false); +#endif + if (!delayedClosingVisual) + { + CloseDropDown(oldValue, false); + } + } + + UpdateVisualState(true); + } + + /// + /// Begin opening the drop down by firing cancelable events, opening the + /// drop-down or reverting, depending on the event argument values. + /// + /// The original value, if needed for a revert. + private void OpeningDropDown(bool oldValue) + { + if (!IsCompletionEnabled) + { + return; + } + + RoutedPropertyChangingEventArgs args = new RoutedPropertyChangingEventArgs(IsDropDownOpenProperty, oldValue, true, true); + + // Opening + OnDropDownOpening(args); + + if (args.Cancel) + { + _ignorePropertyChange = true; + SetValue(IsDropDownOpenProperty, oldValue); + } + else + { +#if !WINDOWS_PHONE + RaiseExpandCollapseAutomationEvent(oldValue, true); +#endif + OpenDropDown(oldValue, true); + } + + UpdateVisualState(true); + } + +#if !WINDOWS_PHONE + /// + /// Raise an expand/collapse event through the automation peer. + /// + /// The old value. + /// The new value. + private void RaiseExpandCollapseAutomationEvent(bool oldValue, bool newValue) + { +#if SILVERLIGHT + AutoCompleteBoxAutomationPeer peer = FrameworkElementAutomationPeer.FromElement(this) as AutoCompleteBoxAutomationPeer; + if (peer != null) + { + peer.RaiseExpandCollapseAutomationEvent(oldValue, newValue); + } +#endif + } +#endif + +#if !SILVERLIGHT + /// + /// Handles the PreviewKeyDown event on the TextBox for WPF. This method + /// is not implemented for Silverlight. + /// + /// The source object. + /// The event data. + private void OnTextBoxPreviewKeyDown(object sender, KeyEventArgs e) + { + OnKeyDown(e); + } +#endif + + /// + /// Connects to the DropDownPopup Closed event. + /// + /// The source object. + /// The event data. + private void DropDownPopup_Closed(object sender, EventArgs e) + { + // Force the drop down dependency property to be false. + if (IsDropDownOpen) + { + IsDropDownOpen = false; + } + + // Fire the DropDownClosed event + if (_popupHasOpened) + { + OnDropDownClosed(new RoutedPropertyChangedEventArgs(true, false)); + } + } + +#if !WINDOWS_PHONE + /// + /// Returns a + /// + /// for use by the Silverlight automation infrastructure. + /// + /// A + /// + /// for the + /// object. + protected override AutomationPeer OnCreateAutomationPeer() + { +#if SILVERLIGHT + return new AutoCompleteBoxAutomationPeer(this); +#else + return null; +#endif + } +#endif + + #region Focus + + /// + /// Handles the FocusChanged event. + /// + /// A value indicating whether the control + /// currently has the focus. + private void FocusChanged(bool hasFocus) + { + // The OnGotFocus & OnLostFocus are asynchronously and cannot + // reliably tell you that have the focus. All they do is let you + // know that the focus changed sometime in the past. To determine + // if you currently have the focus you need to do consult the + // FocusManager (see HasFocus()). + + if (hasFocus) + { + if (TextBox != null && TextBox.SelectionLength == 0) + { +#if !WINDOWS_PHONE + TextBox.SelectAll(); +#endif +#if WINDOWS_PHONE + TextBox.Focus(); +#endif + } + } + else + { + IsDropDownOpen = false; + _userCalledPopulate = false; +#if !WINDOWS_PHONE + if (TextBox != null) + { + TextBox.Select(TextBox.Text.Length, 0); + } +#endif + } + } + + /// + /// Determines whether the text box or drop-down portion of the + /// control has + /// focus. + /// + /// true to indicate the + /// has focus; + /// otherwise, false. + protected bool HasFocus() + { + DependencyObject focused = +#if SILVERLIGHT + FocusManager.GetFocusedElement() as DependencyObject; +#else + FocusManager.GetFocusedElement(this) as DependencyObject; +#endif + while (focused != null) + { + if (object.ReferenceEquals(focused, this)) + { + return true; + } + + // This helps deal with popups that may not be in the same + // visual tree + DependencyObject parent = VisualTreeHelper.GetParent(focused); + if (parent == null) + { + // Try the logical parent. + FrameworkElement element = focused as FrameworkElement; + if (element != null) + { + parent = element.Parent; + } + } + focused = parent; + } + return false; + } + + /// + /// Provides handling for the + /// event. + /// + /// A + /// that contains the event data. + protected override void OnGotFocus(RoutedEventArgs e) + { + base.OnGotFocus(e); + FocusChanged(HasFocus()); + } + + /// + /// Provides handling for the + /// event. + /// + /// A + /// that contains the event data. + protected override void OnLostFocus(RoutedEventArgs e) + { + base.OnLostFocus(e); + FocusChanged(HasFocus()); + } + + #endregion + + /// + /// Handle the change of the IsEnabled property. + /// + /// The source object. + /// The event data. + private void ControlIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) + { + bool isEnabled = (bool)e.NewValue; + if (!isEnabled) + { + IsDropDownOpen = false; + } + } + + /// + /// Returns the + /// part, if + /// possible. + /// + /// + /// A object, + /// if possible. Otherwise, null. + /// + [SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Justification = "Following the GetTemplateChild pattern for the method.")] + protected virtual ISelectionAdapter GetSelectionAdapterPart() + { + ISelectionAdapter adapter = null; + Selector selector = GetTemplateChild(ElementSelector) as Selector; + if (selector != null) + { + // Check if it is already an IItemsSelector + adapter = selector as ISelectionAdapter; + if (adapter == null) + { + // Built in support for wrapping a Selector control + adapter = new SelectorSelectionAdapter(selector); + } + } + if (adapter == null) + { + adapter = GetTemplateChild(ElementSelectionAdapter) as ISelectionAdapter; + } + return adapter; + } + + /// + /// Handles the timer tick when using a populate delay. + /// + /// The source object. + /// The event arguments. + private void PopulateDropDown(object sender, EventArgs e) + { + if (_delayTimer != null) + { + _delayTimer.Stop(); + } + + // Update the prefix/search text. + SearchText = Text; + + // The Populated event enables advanced, custom filtering. The + // client needs to directly update the ItemsSource collection or + // call the Populate method on the control to continue the + // display process if Cancel is set to true. + PopulatingEventArgs populating = new PopulatingEventArgs(SearchText); + OnPopulating(populating); + if (!populating.Cancel) + { + PopulateComplete(); + } + } + + /// + /// Raises the + /// + /// event. + /// + /// A + /// that + /// contains the event data. + protected virtual void OnPopulating(PopulatingEventArgs e) + { + var handler = Populating; + if (handler != null) + { + handler(this, e); + } + } + + /// + /// Raises the + /// + /// event. + /// + /// A + /// + /// that contains the event data. + protected virtual void OnPopulated(PopulatedEventArgs e) + { + var handler = Populated; + if (handler != null) + { + handler(this, e); + } + } + + /// + /// Raises the + /// + /// event. + /// + /// A + /// + /// that contains the event data. + protected virtual void OnSelectionChanged(SelectionChangedEventArgs e) + { + var handler = SelectionChanged; + if (handler != null) + { + handler(this, e); + } + } + + /// + /// Raises the + /// + /// event. + /// + /// A + /// + /// that contains the event data. + protected virtual void OnDropDownOpening(RoutedPropertyChangingEventArgs e) + { + var handler = DropDownOpening; + if (handler != null) + { + handler(this, e); + } + } + + /// + /// Raises the + /// + /// event. + /// + /// A + /// + /// that contains the event data. + protected virtual void OnDropDownOpened(RoutedPropertyChangedEventArgs e) + { + var handler = DropDownOpened; + if (handler != null) + { + handler(this, e); + } + } + + /// + /// Raises the + /// + /// event. + /// + /// A + /// + /// that contains the event data. + protected virtual void OnDropDownClosing(RoutedPropertyChangingEventArgs e) + { + var handler = DropDownClosing; + if (handler != null) + { + handler(this, e); + } + } + + /// + /// Raises the + /// + /// event. + /// + /// A + /// + /// which contains the event data. + protected virtual void OnDropDownClosed(RoutedPropertyChangedEventArgs e) + { + var handler = DropDownClosed; + if (handler != null) + { + handler(this, e); + } + } + + /// + /// Formats an Item for text comparisons based on Converter + /// and ConverterCulture properties. + /// + /// The object to format. + /// A value indicating whether to clear + /// the data context after the lookup is performed. + /// Formatted Value. + private string FormatValue(object value, bool clearDataContext) + { + string str = FormatValue(value); + if (clearDataContext && _valueBindingEvaluator != null) + { + _valueBindingEvaluator.ClearDataContext(); + } + return str; + } + + /// + /// Converts the specified object to a string by using the + /// and + /// values + /// of the binding object specified by the + /// + /// property. + /// + /// The object to format as a string. + /// The string representation of the specified object. + /// + /// Override this method to provide a custom string conversion. + /// + protected virtual string FormatValue(object value) + { + if (_valueBindingEvaluator != null) + { + return _valueBindingEvaluator.GetDynamicValue(value) ?? string.Empty; + } + + return value == null ? string.Empty : value.ToString(); + } + + /// + /// Raises the + /// + /// event. + /// + /// A + /// that contains the event data. + protected virtual void OnTextChanged(RoutedEventArgs e) + { + var handler = TextChanged; + if (handler != null) + { + handler(this, e); + } + } + + /// + /// Handle the TextChanged event that is directly attached to the + /// TextBox part. This ensures that only user initiated actions will + /// result in an AutoCompleteBox suggestion and operation. + /// + /// The source TextBox object. + /// The TextChanged event data. + private void OnTextBoxTextChanged(object sender, TextChangedEventArgs e) + { + // Call the central updated text method as a user-initiated action + TextUpdated(_text.Text, true); + } + + /// + /// When selection changes, save the location of the selection start. + /// + /// The source object. + /// The event data. + private void OnTextBoxSelectionChanged(object sender, RoutedEventArgs e) + { + // If ignoring updates. This happens after text is updated, and + // before the PopulateComplete method is called. Required for the + // IsTextCompletionEnabled feature. + // Also, do not update if the user is in the middle of using an IME + if (_ignoreTextSelectionChange || _inputtingText) + { + return; + } + + _textSelectionStart = _text.SelectionStart; + } + +#if WINDOWS_PHONE + /// + /// Handles KeyDown to set a flag that indicates that the user is inputting + /// text. This is important for IME input. + /// + /// The source UIElement object. + /// The KeyDown event data. + private void OnUIElementKeyDown(object sender, KeyEventArgs e) + { + _inputtingText = true; + } + + /// + /// Handles KeyUp to turn off the flag that indicates that the user is inputting + /// text. This is important for IME input. + /// + /// The source UIElement object. + /// The KeyUp event data. + private void OnUIElementKeyUp(object sender, KeyEventArgs e) + { + _inputtingText = false; + } +#else + /// + /// Handles TextInputStart to set a flag that indicates that the user is inputting + /// text. This is important for IME input. + /// + /// The source TextBox object. + /// The TextInputStart event data. + private void OnTextBoxTextInputStart(object sender, KeyEventArgs e) + { + _inputtingText = true; + } + + /// + /// Handles TextInput to turn off the flag that indicates that the user is inputting + /// text. This is important for IME input. + /// + /// The source TextBox object. + /// The TextInput event data. + private void OnTextBoxTextInput(object sender, KeyEventArgs e) + { + _inputtingText = false; + } +#endif + + /// + /// Updates both the text box value and underlying text dependency + /// property value if and when they change. Automatically fires the + /// text changed events when there is a change. + /// + /// The new string value. + private void UpdateTextValue(string value) + { + UpdateTextValue(value, null); + } + + /// + /// Updates both the text box value and underlying text dependency + /// property value if and when they change. Automatically fires the + /// text changed events when there is a change. + /// + /// The new string value. + /// A nullable bool value indicating whether + /// the action was user initiated. In a user initiated mode, the + /// underlying text dependency property is updated. In a non-user + /// interaction, the text box value is updated. When user initiated is + /// null, all values are updated. + private void UpdateTextValue(string value, bool? userInitiated) + { + // Update the Text dependency property + if ((userInitiated == null || userInitiated == true) && Text != value) + { + _ignoreTextPropertyChange++; + Text = value; + OnTextChanged(new RoutedEventArgs()); + } + + // Update the TextBox's Text dependency property + if ((userInitiated == null || userInitiated == false) && TextBox != null && TextBox.Text != value) + { + _ignoreTextPropertyChange++; + TextBox.Text = value ?? string.Empty; + + // Text dependency property value was set, fire event + if (Text == value || Text == null) + { + OnTextChanged(new RoutedEventArgs()); + } + } + } + + /// + /// Handle the update of the text for the control from any source, + /// including the TextBox part and the Text dependency property. + /// + /// The new text. + /// A value indicating whether the update + /// is a user-initiated action. This should be a True value when the + /// TextUpdated method is called from a TextBox event handler. + private void TextUpdated(string newText, bool userInitiated) + { + // Only process this event if it is coming from someone outside + // setting the Text dependency property directly. + if (_ignoreTextPropertyChange > 0) + { + _ignoreTextPropertyChange--; + return; + } + + if (newText == null) + { + newText = string.Empty; + } + + // The TextBox.TextChanged event was not firing immediately and + // was causing an immediate update, even with wrapping. If there is + // a selection currently, no update should happen. + if (IsTextCompletionEnabled && TextBox != null && TextBox.SelectionLength > 0 && TextBox.SelectionStart != TextBox.Text.Length) + { + return; + } + + // Evaluate the conditions needed for completion. + // 1. Minimum prefix length + // 2. If a delay timer is in use, use it + bool populateReady = newText.Length >= MinimumPrefixLength && MinimumPrefixLength >= 0; + _userCalledPopulate = populateReady ? userInitiated : false; + + // Update the interface and values only as necessary + UpdateTextValue(newText, userInitiated); + + if (populateReady) + { + _ignoreTextSelectionChange = true; + + if (_delayTimer != null) + { + _delayTimer.Start(); + } + else + { + PopulateDropDown(this, EventArgs.Empty); + } + } + else + { + SearchText = string.Empty; + if (SelectedItem != null) + { + _skipSelectedItemTextUpdate = true; + } + SelectedItem = null; + if (IsDropDownOpen) + { + IsDropDownOpen = false; + } + } + } + + /// + /// Notifies the + /// that the + /// + /// property has been set and the data can be filtered to provide + /// possible matches in the drop-down. + /// + /// + /// Call this method when you are providing custom population of + /// the drop-down portion of the AutoCompleteBox, to signal the control + /// that you are done with the population process. + /// Typically, you use PopulateComplete when the population process + /// is a long-running process and you want to cancel built-in filtering + /// of the ItemsSource items. In this case, you can handle the + /// Populated event and set PopulatingEventArgs.Cancel to true. + /// When the long-running process has completed you call + /// PopulateComplete to indicate the drop-down is populated. + /// + public void PopulateComplete() + { + // Apply the search filter + RefreshView(); + + // Fire the Populated event containing the read-only view data. + PopulatedEventArgs populated = new PopulatedEventArgs(new ReadOnlyCollection(_view)); + OnPopulated(populated); + + if (SelectionAdapter != null && SelectionAdapter.ItemsSource != _view) + { + SelectionAdapter.ItemsSource = _view; + } + + bool isDropDownOpen = _userCalledPopulate && (_view.Count > 0); + if (isDropDownOpen != IsDropDownOpen) + { + _ignorePropertyChange = true; + IsDropDownOpen = isDropDownOpen; + } + if (IsDropDownOpen) + { + OpeningDropDown(false); + if (DropDownPopup != null) + { +#if WINDOWS_PHONE + DropDownPopup.Arrange(null); +#else + DropDownPopup.Arrange(); +#endif + } + } + else + { + ClosingDropDown(true); + } + + UpdateTextCompletion(_userCalledPopulate); + } + + /// + /// Performs text completion, if enabled, and a lookup on the underlying + /// item values for an exact match. Will update the SelectedItem value. + /// + /// A value indicating whether the operation + /// was user initiated. Text completion will not be performed when not + /// directly initiated by the user. + private void UpdateTextCompletion(bool userInitiated) + { + // By default this method will clear the selected value + object newSelectedItem = null; + string text = Text; + + // Text search is StartsWith explicit and only when enabled, in + // line with WPF's ComboBox lookup. When in use it will associate + // a Value with the Text if it is found in ItemsSource. This is + // only valid when there is data and the user initiated the action. + if (_view.Count > 0) + { + if (IsTextCompletionEnabled && TextBox != null && userInitiated) + { + int currentLength = TextBox.Text.Length; + int selectionStart = TextBox.SelectionStart; + if (selectionStart == text.Length && selectionStart > _textSelectionStart) + { + // When the FilterMode dependency property is set to + // either StartsWith or StartsWithCaseSensitive, the + // first item in the view is used. This will improve + // performance on the lookup. It assumes that the + // FilterMode the user has selected is an acceptable + // case sensitive matching function for their scenario. + object top = FilterMode == AutoCompleteFilterMode.StartsWith || FilterMode == AutoCompleteFilterMode.StartsWithCaseSensitive + ? _view[0] + : TryGetMatch(text, _view, AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.StartsWith)); + + // If the search was successful, update SelectedItem + if (top != null) + { + newSelectedItem = top; + string topString = FormatValue(top, true); + + // Only replace partially when the two words being the same + int minLength = Math.Min(topString.Length, Text.Length); + if (AutoCompleteSearch.Equals(Text.Substring(0, minLength), topString.Substring(0, minLength))) + { + // Update the text + UpdateTextValue(topString); + + // Select the text past the user's caret + TextBox.SelectionStart = currentLength; + TextBox.SelectionLength = topString.Length - currentLength; + } + } + } + } + else + { + // Perform an exact string lookup for the text. This is a + // design change from the original Toolkit release when the + // IsTextCompletionEnabled property behaved just like the + // WPF ComboBox's IsTextSearchEnabled property. + // + // This change provides the behavior that most people expect + // to find: a lookup for the value is always performed. + newSelectedItem = TryGetMatch(text, _view, AutoCompleteSearch.GetFilter(AutoCompleteFilterMode.EqualsCaseSensitive)); + } + } + + // Update the selected item property + + if (SelectedItem != newSelectedItem) + { + _skipSelectedItemTextUpdate = true; + } + SelectedItem = newSelectedItem; + + // Restore updates for TextSelection + if (_ignoreTextSelectionChange) + { + _ignoreTextSelectionChange = false; + if (TextBox != null && !_inputtingText) + { + _textSelectionStart = TextBox.SelectionStart; + } + } + } + + /// + /// Attempts to look through the view and locate the specific exact + /// text match. + /// + /// The search text. + /// The view reference. + /// The predicate to use for the partial or + /// exact match. + /// Returns the object or null. + private object TryGetMatch(string searchText, ObservableCollection view, AutoCompleteFilterPredicate predicate) + { + if (view != null && view.Count > 0) + { + foreach (object o in view) + { + if (predicate(searchText, FormatValue(o))) + { + return o; + } + } + } + + return null; + } + + /// + /// A simple helper method to clear the view and ensure that a view + /// object is always present and not null. + /// + private void ClearView() + { + if (_view == null) + { + _view = new ObservableCollection(); + } + else + { + _view.Clear(); + } + } + + /// + /// Walks through the items enumeration. Performance is not going to be + /// perfect with the current implementation. + /// + private void RefreshView() + { + if (_items == null) + { + ClearView(); + return; + } + + // Cache the current text value + string text = Text ?? string.Empty; + + // Determine if any filtering mode is on + bool stringFiltering = TextFilter != null; + bool objectFiltering = FilterMode == AutoCompleteFilterMode.Custom && TextFilter == null; + + int view_index = 0; + int view_count = _view.Count; + List items = _items; + foreach (object item in items) + { + bool inResults = !(stringFiltering || objectFiltering); + if (!inResults) + { + inResults = stringFiltering ? TextFilter(text, FormatValue(item)) : ItemFilter(text, item); + } + + if (view_count > view_index && inResults && _view[view_index] == item) + { + // Item is still in the view + view_index++; + } + else if (inResults) + { + // Insert the item + if (view_count > view_index && _view[view_index] != item) + { + // Replace item + // Unfortunately replacing via index throws a fatal + // exception: View[view_index] = item; + // Cost: O(n) vs O(1) + _view.RemoveAt(view_index); + _view.Insert(view_index, item); + view_index++; + } + else + { + // Add the item + if (view_index == view_count) + { + // Constant time is preferred (Add). + _view.Add(item); + } + else + { + _view.Insert(view_index, item); + } + view_index++; + view_count++; + } + } + else if (view_count > view_index && _view[view_index] == item) + { + // Remove the item + _view.RemoveAt(view_index); + view_count--; + } + } + + // Clear the evaluator to discard a reference to the last item + if (_valueBindingEvaluator != null) + { + _valueBindingEvaluator.ClearDataContext(); + } + } + + /// + /// Handle any change to the ItemsSource dependency property, update + /// the underlying ObservableCollection view, and set the selection + /// adapter's ItemsSource to the view if appropriate. + /// + /// The old enumerable reference. + /// The new enumerable reference. + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "oldValue", Justification = "This makes it easy to add validation or other changes in the future.")] + private void OnItemsSourceChanged(IEnumerable oldValue, IEnumerable newValue) + { + // Remove handler for oldValue.CollectionChanged (if present) + INotifyCollectionChanged oldValueINotifyCollectionChanged = oldValue as INotifyCollectionChanged; + if (null != oldValueINotifyCollectionChanged && null != _collectionChangedWeakEventListener) + { + _collectionChangedWeakEventListener.Detach(); + _collectionChangedWeakEventListener = null; + } + + // Add handler for newValue.CollectionChanged (if possible) + INotifyCollectionChanged newValueINotifyCollectionChanged = newValue as INotifyCollectionChanged; + if (null != newValueINotifyCollectionChanged) + { + _collectionChangedWeakEventListener = new WeakEventListener(this); + _collectionChangedWeakEventListener.OnEventAction = (instance, source, eventArgs) => instance.ItemsSourceCollectionChanged(source, eventArgs); + _collectionChangedWeakEventListener.OnDetachAction = (weakEventListener) => newValueINotifyCollectionChanged.CollectionChanged -= weakEventListener.OnEvent; + newValueINotifyCollectionChanged.CollectionChanged += _collectionChangedWeakEventListener.OnEvent; + } + + // Store a local cached copy of the data + _items = newValue == null ? null : new List(newValue.Cast().ToList()); + + // Clear and set the view on the selection adapter + ClearView(); + if (SelectionAdapter != null && SelectionAdapter.ItemsSource != _view) + { + SelectionAdapter.ItemsSource = _view; + } + if (IsDropDownOpen) + { + RefreshView(); + } + } + + /// + /// Method that handles the ObservableCollection.CollectionChanged event for the ItemsSource property. + /// + /// The object that raised the event. + /// The event data. + private void ItemsSourceCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + // Update the cache + if (e.Action == NotifyCollectionChangedAction.Remove && e.OldItems != null) + { + for (int index = 0; index < e.OldItems.Count; index++) + { + _items.RemoveAt(e.OldStartingIndex); + } + } + if (e.Action == NotifyCollectionChangedAction.Add && e.NewItems != null && _items.Count >= e.NewStartingIndex) + { + for (int index = 0; index < e.NewItems.Count; index++) + { + _items.Insert(e.NewStartingIndex + index, e.NewItems[index]); + } + } + if (e.Action == NotifyCollectionChangedAction.Replace && e.NewItems != null && e.OldItems != null) + { + for (int index = 0; index < e.NewItems.Count; index++) + { + _items[e.NewStartingIndex] = e.NewItems[index]; + } + } + + // Update the view + if (e.Action == NotifyCollectionChangedAction.Remove || e.Action == NotifyCollectionChangedAction.Replace) + { + for (int index = 0; index < e.OldItems.Count; index++) + { + _view.Remove(e.OldItems[index]); + } + } + + if (e.Action == NotifyCollectionChangedAction.Reset) + { + // Significant changes to the underlying data. + ClearView(); + if (ItemsSource != null) + { + _items = new List(ItemsSource.Cast().ToList()); + } + } + + // Refresh the observable collection used in the selection adapter. + RefreshView(); + } + + #region Selection Adapter + + /// + /// Handles the SelectionChanged event of the selection adapter. + /// + /// The source object. + /// The selection changed event data. + private void OnAdapterSelectionChanged(object sender, SelectionChangedEventArgs e) + { + SelectedItem = _adapter.SelectedItem; + } + + /// + /// Handles the Commit event on the selection adapter. + /// + /// The source object. + /// The event data. + private void OnAdapterSelectionComplete(object sender, RoutedEventArgs e) + { + IsDropDownOpen = false; + + // Completion will update the selected value + UpdateTextCompletion(false); + + // Text should not be selected + if (TextBox != null) + { + TextBox.Select(TextBox.Text.Length, 0); + TextBox.Focus(); + } + } + + /// + /// Handles the Cancel event on the selection adapter. + /// + /// The source object. + /// The event data. + private void OnAdapterSelectionCanceled(object sender, RoutedEventArgs e) + { + UpdateTextValue(SearchText); + + // Completion will update the selected value + UpdateTextCompletion(false); + } + + #endregion + + #region Popup + + /// + /// Handles MaxDropDownHeightChanged by re-arranging and updating the + /// popup arrangement. + /// + /// The new value. + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "newValue", Justification = "This makes it easy to add validation or other changes in the future.")] + private void OnMaxDropDownHeightChanged(double newValue) + { + if (DropDownPopup != null) + { + DropDownPopup.MaxDropDownHeight = newValue; +#if WINDOWS_PHONE + DropDownPopup.Arrange(null); +#else + DropDownPopup.Arrange(); +#endif + } + UpdateVisualState(true); + } + + /// + /// Private method that directly opens the popup, checks the expander + /// button, and then fires the Opened event. + /// + /// The old value. + /// The new value. + private void OpenDropDown(bool oldValue, bool newValue) + { + if (DropDownPopup != null) + { + DropDownPopup.IsOpen = true; + } + _popupHasOpened = true; + OnDropDownOpened(new RoutedPropertyChangedEventArgs(oldValue, newValue)); + } + + /// + /// Private method that directly closes the popup, flips the Checked + /// value, and then fires the Closed event. + /// + /// The old value. + /// The new value. + private void CloseDropDown(bool oldValue, bool newValue) + { + if (_popupHasOpened) + { + if (SelectionAdapter != null) + { + SelectionAdapter.SelectedItem = null; + } + if (DropDownPopup != null) + { + DropDownPopup.IsOpen = false; + } + OnDropDownClosed(new RoutedPropertyChangedEventArgs(oldValue, newValue)); + } + } + + #endregion + + /// + /// Provides handling for the + /// event. + /// + /// A + /// that contains the event data. + protected override void OnKeyDown(KeyEventArgs e) + { + if (e == null) + { + throw new ArgumentNullException("e"); + } + + base.OnKeyDown(e); + + if (e.Handled || !IsEnabled) + { + return; + } + + // The drop down is open, pass along the key event arguments to the + // selection adapter. If it isn't handled by the adapter's logic, + // then we handle some simple navigation scenarios for controlling + // the drop down. + if (IsDropDownOpen) + { + if (SelectionAdapter != null) + { + SelectionAdapter.HandleKeyDown(e); + if (e.Handled) + { + return; + } + } + + if (e.Key == Key.Escape) + { + OnAdapterSelectionCanceled(this, new RoutedEventArgs()); + e.Handled = true; + } + } + else + { + // The drop down is not open, the Down key will toggle it open. + if (e.Key == Key.Down) + { + IsDropDownOpen = true; + e.Handled = true; + } + } + + // Standard drop down navigation + switch (e.Key) + { + case Key.F4: + IsDropDownOpen = !IsDropDownOpen; + e.Handled = true; + break; + + case Key.Enter: + OnAdapterSelectionComplete(this, new RoutedEventArgs()); + e.Handled = true; + break; + + default: + break; + } + } + + /// + /// Update the visual state of the control. + /// + /// + /// A value indicating whether to automatically generate transitions to + /// the new state, or instantly transition to the new state. + /// + void IUpdateVisualState.UpdateVisualState(bool useTransitions) + { + UpdateVisualState(useTransitions); + } + + /// + /// Update the current visual state of the button. + /// + /// + /// True to use transitions when updating the visual state, false to + /// snap directly to the new visual state. + /// + internal virtual void UpdateVisualState(bool useTransitions) + { + // Popup + VisualStateManager.GoToState(this, IsDropDownOpen ? VisualStates.StatePopupOpened : VisualStates.StatePopupClosed, useTransitions); + + // Handle the Common and Focused states + Interaction.UpdateVisualStateBase(useTransitions); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/AutoCompleteFilter.cs b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/AutoCompleteFilter.cs new file mode 100644 index 0000000..e037f87 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/AutoCompleteFilter.cs @@ -0,0 +1,207 @@ +// (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; + + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// A predefined set of filter functions for the known, built-in + /// AutoCompleteFilterMode enumeration values. + /// + internal static class AutoCompleteSearch + { + /// + /// Index function that retrieves the filter for the provided + /// AutoCompleteFilterMode. + /// + /// The built-in search mode. + /// Returns the string-based comparison function. + public static AutoCompleteFilterPredicate GetFilter(AutoCompleteFilterMode FilterMode) + { + switch (FilterMode) + { + case AutoCompleteFilterMode.Contains: + return Contains; + + case AutoCompleteFilterMode.ContainsCaseSensitive: + return ContainsCaseSensitive; + + case AutoCompleteFilterMode.ContainsOrdinal: + return ContainsOrdinal; + + case AutoCompleteFilterMode.ContainsOrdinalCaseSensitive: + return ContainsOrdinalCaseSensitive; + + case AutoCompleteFilterMode.Equals: + return Equals; + + case AutoCompleteFilterMode.EqualsCaseSensitive: + return EqualsCaseSensitive; + + case AutoCompleteFilterMode.EqualsOrdinal: + return EqualsOrdinal; + + case AutoCompleteFilterMode.EqualsOrdinalCaseSensitive: + return EqualsOrdinalCaseSensitive; + + case AutoCompleteFilterMode.StartsWith: + return StartsWith; + + case AutoCompleteFilterMode.StartsWithCaseSensitive: + return StartsWithCaseSensitive; + + case AutoCompleteFilterMode.StartsWithOrdinal: + return StartsWithOrdinal; + + case AutoCompleteFilterMode.StartsWithOrdinalCaseSensitive: + return StartsWithOrdinalCaseSensitive; + + case AutoCompleteFilterMode.None: + case AutoCompleteFilterMode.Custom: + default: + return null; + } + } + + /// + /// Check if the string value begins with the text. + /// + /// The AutoCompleteBox prefix text. + /// The item's string value. + /// Returns true if the condition is met. + public static bool StartsWith(string text, string value) + { + return value.StartsWith(text, StringComparison.CurrentCultureIgnoreCase); + } + + /// + /// Check if the string value begins with the text. + /// + /// The AutoCompleteBox prefix text. + /// The item's string value. + /// Returns true if the condition is met. + public static bool StartsWithCaseSensitive(string text, string value) + { + return value.StartsWith(text, StringComparison.CurrentCulture); + } + + /// + /// Check if the string value begins with the text. + /// + /// The AutoCompleteBox prefix text. + /// The item's string value. + /// Returns true if the condition is met. + public static bool StartsWithOrdinal(string text, string value) + { + return value.StartsWith(text, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Check if the string value begins with the text. + /// + /// The AutoCompleteBox prefix text. + /// The item's string value. + /// Returns true if the condition is met. + public static bool StartsWithOrdinalCaseSensitive(string text, string value) + { + return value.StartsWith(text, StringComparison.Ordinal); + } + + /// + /// Check if the prefix is contained in the string value. The current + /// culture's case insensitive string comparison operator is used. + /// + /// The AutoCompleteBox prefix text. + /// The item's string value. + /// Returns true if the condition is met. + public static bool Contains(string text, string value) + { + return value.Contains(text, StringComparison.CurrentCultureIgnoreCase); + } + + /// + /// Check if the prefix is contained in the string value. + /// + /// The AutoCompleteBox prefix text. + /// The item's string value. + /// Returns true if the condition is met. + public static bool ContainsCaseSensitive(string text, string value) + { + return value.Contains(text, StringComparison.CurrentCulture); + } + + /// + /// Check if the prefix is contained in the string value. + /// + /// The AutoCompleteBox prefix text. + /// The item's string value. + /// Returns true if the condition is met. + public static bool ContainsOrdinal(string text, string value) + { + return value.Contains(text, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Check if the prefix is contained in the string value. + /// + /// The AutoCompleteBox prefix text. + /// The item's string value. + /// Returns true if the condition is met. + public static bool ContainsOrdinalCaseSensitive(string text, string value) + { + return value.Contains(text, StringComparison.Ordinal); + } + + /// + /// Check if the string values are equal. + /// + /// The AutoCompleteBox prefix text. + /// The item's string value. + /// Returns true if the condition is met. + public static bool Equals(string text, string value) + { + return value.Equals(text, StringComparison.CurrentCultureIgnoreCase); + } + + /// + /// Check if the string values are equal. + /// + /// The AutoCompleteBox prefix text. + /// The item's string value. + /// Returns true if the condition is met. + public static bool EqualsCaseSensitive(string text, string value) + { + return value.Equals(text, StringComparison.CurrentCulture); + } + + /// + /// Check if the string values are equal. + /// + /// The AutoCompleteBox prefix text. + /// The item's string value. + /// Returns true if the condition is met. + public static bool EqualsOrdinal(string text, string value) + { + return value.Equals(text, StringComparison.OrdinalIgnoreCase); + } + + /// + /// Check if the string values are equal. + /// + /// The AutoCompleteBox prefix text. + /// The item's string value. + /// Returns true if the condition is met. + public static bool EqualsOrdinalCaseSensitive(string text, string value) + { + return value.Equals(text, StringComparison.Ordinal); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/AutoCompleteFilterMode.cs b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/AutoCompleteFilterMode.cs new file mode 100644 index 0000000..6a76353 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/AutoCompleteFilterMode.cs @@ -0,0 +1,141 @@ +// (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. + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + // When adding to this enum, please update the OnFilterModePropertyChanged + // in the AutoCompleteBox class that is used for validation. + + /// + /// Specifies how text in the text box portion of the + /// control is used + /// to filter items specified by the + /// + /// property for display in the drop-down. + /// + /// Stable + public enum AutoCompleteFilterMode + { + /// + /// Specifies that no filter is used. All items are returned. + /// + None = 0, + + /// + /// Specifies a culture-sensitive, case-insensitive filter where the + /// returned items start with the specified text. The filter uses the + /// + /// method, specifying + /// as + /// the string comparison criteria. + /// + StartsWith = 1, + + /// + /// Specifies a culture-sensitive, case-sensitive filter where the + /// returned items start with the specified text. The filter uses the + /// + /// method, specifying + /// as the string + /// comparison criteria. + /// + StartsWithCaseSensitive = 2, + + /// + /// Specifies an ordinal, case-insensitive filter where the returned + /// items start with the specified text. The filter uses the + /// + /// method, specifying + /// as the + /// string comparison criteria. + /// + StartsWithOrdinal = 3, + + /// + /// Specifies an ordinal, case-sensitive filter where the returned items + /// start with the specified text. The filter uses the + /// + /// method, specifying as + /// the string comparison criteria. + /// + StartsWithOrdinalCaseSensitive = 4, + + /// + /// Specifies a culture-sensitive, case-insensitive filter where the + /// returned items contain the specified text. + /// + Contains = 5, + + /// + /// Specifies a culture-sensitive, case-sensitive filter where the + /// returned items contain the specified text. + /// + ContainsCaseSensitive = 6, + + /// + /// Specifies an ordinal, case-insensitive filter where the returned + /// items contain the specified text. + /// + ContainsOrdinal = 7, + + /// + /// Specifies an ordinal, case-sensitive filter where the returned items + /// contain the specified text. + /// + ContainsOrdinalCaseSensitive = 8, + + /// + /// Specifies a culture-sensitive, case-insensitive filter where the + /// returned items equal the specified text. The filter uses the + /// + /// method, specifying + /// as + /// the search comparison criteria. + /// + Equals = 9, + + /// + /// Specifies a culture-sensitive, case-sensitive filter where the + /// returned items equal the specified text. The filter uses the + /// + /// method, specifying + /// as the string + /// comparison criteria. + /// + EqualsCaseSensitive = 10, + + /// + /// Specifies an ordinal, case-insensitive filter where the returned + /// items equal the specified text. The filter uses the + /// + /// method, specifying + /// as the + /// string comparison criteria. + /// + EqualsOrdinal = 11, + + /// + /// Specifies an ordinal, case-sensitive filter where the returned items + /// equal the specified text. The filter uses the + /// + /// method, specifying as + /// the string comparison criteria. + /// + EqualsOrdinalCaseSensitive = 12, + + /// + /// Specifies that a custom filter is used. This mode is used when the + /// + /// or + /// + /// properties are set. + /// + Custom = 13, + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/AutoCompleteFilterPredicate.cs b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/AutoCompleteFilterPredicate.cs new file mode 100644 index 0000000..b6dbaea --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/AutoCompleteFilterPredicate.cs @@ -0,0 +1,27 @@ +// (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. + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// Represents the filter used by the + /// control to + /// determine whether an item is a possible match for the specified text. + /// + /// true to indicate is a possible match + /// for ; otherwise false. + /// The string used as the basis for filtering. + /// The item that is compared with the + /// parameter. + /// The type used for filtering the + /// . This type can + /// be either a string or an object. + /// Stable + public delegate bool AutoCompleteFilterPredicate(string search, T item); +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/ISelectionAdapter.cs b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/ISelectionAdapter.cs new file mode 100644 index 0000000..470c6d2 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/ISelectionAdapter.cs @@ -0,0 +1,77 @@ +// (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.Collections; +using System.Windows; +using System.Windows.Automation.Peers; +using System.Windows.Controls; +using System.Windows.Input; + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// Defines an item collection, selection members, and key handling for the + /// selection adapter contained in the drop-down portion of an + /// control. + /// + /// Stable + public interface ISelectionAdapter + { + /// + /// Gets or sets the selected item. + /// + /// The currently selected item. + object SelectedItem { get; set; } + + /// + /// Occurs when the + /// + /// property value changes. + /// + event SelectionChangedEventHandler SelectionChanged; + + /// + /// Gets or sets a collection that is used to generate content for the + /// selection adapter. + /// + /// The collection that is used to generate content for the + /// selection adapter. + IEnumerable ItemsSource { get; set; } + + /// + /// Occurs when a selected item is not cancelled and is committed as the + /// selected item. + /// + event RoutedEventHandler Commit; + + /// + /// Occurs when a selection has been canceled. + /// + event RoutedEventHandler Cancel; + + /// + /// Provides handling for the + /// event that occurs + /// when a key is pressed while the drop-down portion of the + /// has focus. + /// + /// A + /// that contains data about the + /// event. + void HandleKeyDown(KeyEventArgs e); + + /// + /// Returns an automation peer for the selection adapter, for use by the + /// Silverlight automation infrastructure. + /// + /// An automation peer for the selection adapter, if one is + /// available; otherwise, null. + AutomationPeer CreateAutomationPeer(); + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopulatedEventArgs.cs b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopulatedEventArgs.cs new file mode 100644 index 0000000..5592372 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopulatedEventArgs.cs @@ -0,0 +1,44 @@ +// (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.Collections; +using System.Windows; + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// Provides data for the + /// + /// event. + /// + /// Stable + public class PopulatedEventArgs : RoutedEventArgs + { + /// + /// Gets the list of possible matches added to the drop-down portion of + /// the + /// control. + /// + /// The list of possible matches added to the + /// . + public IEnumerable Data { get; private set; } + + /// + /// Initializes a new instance of the + /// . + /// + /// The list of possible matches added to the + /// drop-down portion of the + /// control. + public PopulatedEventArgs(IEnumerable data) + { + Data = data; + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopulatedEventHandler.cs b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopulatedEventHandler.cs new file mode 100644 index 0000000..6e54675 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopulatedEventHandler.cs @@ -0,0 +1,27 @@ +// (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.Diagnostics.CodeAnalysis; + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// Represents the method that will handle the + /// + /// event of a + /// control. + /// + /// The source of the event. + /// A + /// that + /// contains the event data. + /// Stable + [SuppressMessage("Microsoft.Design", "CA1003:UseGenericEventHandlerInstances", Justification = "There is no generic RoutedEventHandler.")] + public delegate void PopulatedEventHandler(object sender, PopulatedEventArgs e); +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopulatingEventArgs.cs b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopulatingEventArgs.cs new file mode 100644 index 0000000..c0a9936 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopulatingEventArgs.cs @@ -0,0 +1,53 @@ +// (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.Windows; + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// Provides data for the + /// + /// event. + /// + /// Stable + public class PopulatingEventArgs : RoutedEventArgs + { + /// + /// Gets the text that is used to determine which items to display in + /// the + /// control. + /// + /// The text that is used to determine which items to display in + /// the . + public string Parameter { get; private set; } + + /// + /// Gets or sets a value indicating whether the + /// + /// event should be canceled. + /// + /// True to cancel the event, otherwise false. The default is + /// false. + public bool Cancel { get; set; } + + /// + /// Initializes a new instance of the + /// . + /// + /// The value of the + /// + /// property, which is used to filter items for the + /// control. + public PopulatingEventArgs(string parameter) + { + Parameter = parameter; + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopulatingEventHandler.cs b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopulatingEventHandler.cs new file mode 100644 index 0000000..3757e0b --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopulatingEventHandler.cs @@ -0,0 +1,27 @@ +// (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.Diagnostics.CodeAnalysis; + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// Represents the method that will handle the + /// + /// event of a + /// control. + /// + /// The source of the event. + /// A + /// that + /// contains the event data. + /// Stable + [SuppressMessage("Microsoft.Design", "CA1003:UseGenericEventHandlerInstances", Justification = "There is no generic RoutedEventHandler.")] + public delegate void PopulatingEventHandler(object sender, PopulatingEventArgs e); +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopupHelper.cs b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopupHelper.cs new file mode 100644 index 0000000..fed72db --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/PopupHelper.cs @@ -0,0 +1,713 @@ +// (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.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Media; + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// PopupHelper is a simple wrapper type that helps abstract platform + /// differences out of the Popup. + /// + internal class PopupHelper + { + /// + /// A value indicating whether Silverlight has loaded at least once, + /// so that the wrapping canvas is not recreated. + /// + private bool _hasControlLoaded; + + /// + /// The distance from the control to the child. + /// + private const double PopupOffset = 2; + + /// + /// Gets a value indicating whether a visual popup state is being used + /// in the current template for the Closed state. Setting this value to + /// true will delay the actual setting of Popup.IsOpen to false until + /// after the visual state's transition for Closed is complete. + /// + public bool UsesClosingVisualState { get; private set; } + + /// + /// Gets or sets the parent control. + /// + private Control Parent { get; set; } + +#if SILVERLIGHT + /// + /// Gets or sets the expansive area outside of the popup. + /// + private Canvas OutsidePopupCanvas { get; set; } + + /// + /// Gets or sets the canvas for the popup child. + /// + private Canvas PopupChildCanvas { get; set; } +#endif + + /// + /// Gets or sets the maximum drop down height value. + /// + public double MaxDropDownHeight { get; set; } + + /// + /// Gets the Popup control instance. + /// + public Popup Popup { get; private set; } + + /// + /// Gets or sets a value indicating whether the actual Popup is open. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Provided for completeness.")] + public bool IsOpen + { + get { return Popup.IsOpen; } + set { Popup.IsOpen = value; } + } + + /// + /// Gets or sets the popup child framework element. Can be used if an + /// assumption is made on the child type. + /// + private FrameworkElement PopupChild { get; set; } + + /// + /// The Closed event is fired after the Popup closes. + /// + public event EventHandler Closed; + + /// + /// Fired when the popup children have a focus event change, allows the + /// parent control to update visual states or react to the focus state. + /// + public event EventHandler FocusChanged; + + /// + /// Fired when the popup children intercept an event that may indicate + /// the need for a visual state update by the parent control. + /// + public event EventHandler UpdateVisualStates; + + /// + /// Initializes a new instance of the PopupHelper class. + /// + /// The parent control. + public PopupHelper(Control parent) + { + Debug.Assert(parent != null, "Parent should not be null."); + Parent = parent; + } + + /// + /// Initializes a new instance of the PopupHelper class. + /// + /// The parent control. + /// The Popup template part. + public PopupHelper(Control parent, Popup popup) + : this(parent) + { + Popup = popup; + } + +#if WINDOWS_PHONE + /// + /// Gets the for the control. + /// + /// The . + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This try-catch pattern is used by other popup controls to keep the runtime up.")] + private MatrixTransform GetControlMatrixTransform() + { + // Getting the transform will throw if the popup is no longer in the visual tree. + // This can happen if you first open the popup and then click on something else on the page that removes it from the live tree. + try + { + return Parent.TransformToVisual(null) as MatrixTransform; + } + catch + { + OnClosed(EventArgs.Empty); // IsDropDownOpen = false; + return null; + } + } + + /// + /// Makes a from a . + /// + /// + /// The control's margin is counted with the offset to make the . + /// This makes the refer to the visible part of the control. + /// + /// The . + /// The margin. + /// + private static Point MatrixTransformPoint(MatrixTransform matrixTransform, Thickness margin) + { + double x = matrixTransform.Matrix.OffsetX + margin.Left; + double y = matrixTransform.Matrix.OffsetY + margin.Top; + return new Point(x, y); + } + + /// + /// Gets the of the visible part of the control (minus the margin). + /// + /// + /// The Parent is wrong if the orientation changed, so use the ArrangeOverride if it's available. + /// + /// The margin. + /// The size. + /// The . + private Size GetControlSize(Thickness margin, Size? finalSize) + { + double width = (finalSize.HasValue ? finalSize.Value.Width : Parent.ActualWidth) - margin.Left - margin.Right; + double height = (finalSize.HasValue ? finalSize.Value.Height : Parent.ActualHeight) - margin.Top - margin.Bottom; + return new Size(width, height); + } + + /// + /// Gets the margin for the control. + /// + /// The margin. + private Thickness GetMargin() + { + Thickness? thickness = Popup.Resources["PhoneTouchTargetOverhang"] as Thickness?; + if (thickness.HasValue) + { + return thickness.Value; + } + return new Thickness(0); + } + + /// + /// Determines whether is displayed above the control. + /// + /// The not covered by the SIP. + /// The of the control. + /// The position of the control. + /// + private static bool IsChildAbove(Size displaySize, Size controlSize, Point controlOffset) + { + double above = controlOffset.Y; + double below = displaySize.Height - controlSize.Height - above; + return above > below; + } + + /// + /// Gets the minimum of three numbers and floors it at zero. + /// + /// The first number. + /// The second number. + /// The result. + private static double Min0(double x, double y) + { + return Math.Max(Math.Min(x, y), 0); + } + + /// + /// Computes the of if displayed above the control. + /// + /// The of the control. + /// The position of the control. + /// The . + private Size AboveChildSize(Size controlSize, Point controlPoint) + { + double width = controlSize.Width; + double availableHeight = controlPoint.Y - PopupOffset; + double customHeight = MaxDropDownHeight; + double height = Min0(availableHeight, customHeight); + return new Size(width, height); + } + + /// + /// Computes the of if displayed below the control. + /// + /// The not covered by the SIP. + /// The of the control. + /// The position of the control. + /// The . + private Size BelowChildSize(Size displaySize, Size controlSize, Point controlPoint) + { + double width = controlSize.Width; + double availableHeight = displaySize.Height - controlSize.Height - controlPoint.Y - PopupOffset; + double customHeight = MaxDropDownHeight; + double height = Min0(availableHeight, customHeight); + return new Size(width, height); + } + + /// + /// The position of if displayed above the control. + /// + /// The control's margin. + /// The position. + private Point AboveChildPoint(Thickness margin) + { + double x = margin.Left; + double y = margin.Top - PopupChild.ActualHeight - PopupOffset; + return new Point(x, y); + } + + /// + /// The position of if displayed below the control. + /// + /// The control's margin. + /// The of the control. + /// The position. + private static Point BelowChildPoint(Thickness margin, Size controlSize) + { + double x = margin.Left; + double y = margin.Top + controlSize.Height + PopupOffset; + return new Point(x, y); + } + + /// + /// Arrange the popup. + /// The from . + /// + public void Arrange(Size? finalSize) + { + if (Popup == null || + PopupChild == null || + Application.Current == null || + OutsidePopupCanvas == null || + Application.Current.Host == null || + Application.Current.Host.Content == null) + { + return; + } + PhoneApplicationFrame frame; + if (!PhoneHelper.TryGetPhoneApplicationFrame(out frame)) + { + return; + } + Size frameSize = frame.GetUsefulSize(); + Thickness margin = GetMargin(); + Size controlSize = GetControlSize(margin, finalSize); + MatrixTransform matrixTransform = GetControlMatrixTransform(); + if (matrixTransform == null) + { + return; + } + Point controlPoint = MatrixTransformPoint(matrixTransform, margin); + Size displaySize = frame.GetSipUncoveredSize(); + bool isChildAbove = IsChildAbove(displaySize, controlSize, controlPoint); + Size childSize = + isChildAbove ? + AboveChildSize(controlSize, controlPoint) : + BelowChildSize(displaySize, controlSize, controlPoint); + if (frameSize.Width == 0 || frameSize.Height == 0 || childSize.Height == 0) + { + return; + } + Point childPoint = isChildAbove ? AboveChildPoint(margin) : BelowChildPoint(margin, controlSize); + + Popup.HorizontalOffset = 0; + Popup.VerticalOffset = 0; + + PopupChild.Width = PopupChild.MaxWidth = PopupChild.MinWidth = controlSize.Width; + PopupChild.MinHeight = 0; + PopupChild.MaxHeight = childSize.Height; + PopupChild.HorizontalAlignment = HorizontalAlignment.Left; + PopupChild.VerticalAlignment = VerticalAlignment.Top; + Canvas.SetLeft(PopupChild, childPoint.X); + Canvas.SetTop(PopupChild, childPoint.Y); + + OutsidePopupCanvas.Width = controlSize.Width; + OutsidePopupCanvas.Height = frameSize.Height; + Matrix fromFrameToControl = matrixTransform.Matrix; + Matrix fromControlToFrame; + fromFrameToControl.Invert(out fromControlToFrame); + matrixTransform.Matrix = fromControlToFrame; + OutsidePopupCanvas.RenderTransform = matrixTransform; + } +#else // WINDOWS_PHONE + /// + /// Arrange the popup. + /// + [SuppressMessage("Microsoft.Design", "CA1031:DoNotCatchGeneralExceptionTypes", Justification = "This try-catch pattern is used by other popup controls to keep the runtime up.")] + public void Arrange() + { + if (Popup == null + || PopupChild == null || Application.Current == null +#if SILVERLIGHT + || OutsidePopupCanvas == null || Application.Current.Host == null || Application.Current.Host.Content == null +#endif + || false) + { + return; + } + +#if SILVERLIGHT + Content hostContent = Application.Current.Host.Content; + double rootWidth = hostContent.ActualWidth; + double rootHeight = hostContent.ActualHeight; +#else + UIElement u = Parent; + if (Application.Current.Windows.Count > 0) + { + u = Application.Current.Windows[0]; + } + while ((u as Window) == null && u != null) + { + u = VisualTreeHelper.GetParent(u) as UIElement; + } + Window w = u as Window; + if (w == null) + { + return; + } + + double rootWidth = w.ActualWidth; + double rootHeight = w.ActualHeight; +#endif + + double popupContentWidth = PopupChild.ActualWidth; + double popupContentHeight = PopupChild.ActualHeight; + + if (rootHeight == 0 || rootWidth == 0 || popupContentWidth == 0 || popupContentHeight == 0) + { + return; + } + + double rootOffsetX = 0; + double rootOffsetY = 0; + +#if SILVERLIGHT + // Getting the transform will throw if the popup is no longer in + // the visual tree. This can happen if you first open the popup + // and then click on something else on the page that removes it + // from the live tree. + MatrixTransform mt = null; + try + { + mt = Parent.TransformToVisual(null) as MatrixTransform; + } + catch + { + OnClosed(EventArgs.Empty); // IsDropDownOpen = false; + } + if (mt == null) + { + return; + } + + rootOffsetX = mt.Matrix.OffsetX; + rootOffsetY = mt.Matrix.OffsetY; + + if (Parent.FlowDirection == FlowDirection.RightToLeft) + { + // In RTL mode the coordinate system is flipped horizontally, + // right plugin edge corresponds to X==0, so we need to adjust + // the rootOffsetX to be mirrored too, for the below code to + // compute popup position correctly + rootOffsetX = rootWidth - rootOffsetX; + } +#endif + + double myControlHeight = Parent.ActualHeight; + double myControlWidth = Parent.ActualWidth; + + // Use or come up with a maximum popup height. + double popupMaxHeight = MaxDropDownHeight; + if (double.IsInfinity(popupMaxHeight) || double.IsNaN(popupMaxHeight)) + { + popupMaxHeight = (rootHeight - myControlHeight) * 3 / 5; + } + + popupContentWidth = Math.Min(popupContentWidth, rootWidth); + popupContentHeight = Math.Min(popupContentHeight, popupMaxHeight); + popupContentWidth = Math.Max(myControlWidth, popupContentWidth); + + // We prefer to align the popup box with the left edge of the + // control, if it will fit. + double popupX = rootOffsetX; + if (rootWidth < popupX + popupContentWidth) + { + // Since it doesn't fit when strictly left aligned, we shift it + // to the left until it does fit. + popupX = rootWidth - popupContentWidth; + popupX = Math.Max(0, popupX); + } + + // We prefer to put the popup below the combobox if it will fit. + bool below = true; + double popupY = rootOffsetY + myControlHeight; + if (rootHeight < popupY + popupContentHeight) + { + below = false; + // It doesn't fit below the combobox, lets try putting it above + // the combobox. + popupY = rootOffsetY - popupContentHeight; + if (popupY < 0) + { + // doesn't really fit below either. Now we just pick top + // or bottom based on wich area is bigger. + if (rootOffsetY < (rootHeight - myControlHeight) / 2) + { + below = true; + popupY = rootOffsetY + myControlHeight; + } + else + { + below = false; + popupY = rootOffsetY - popupContentHeight; + } + } + } + + // Now that we have positioned the popup we may need to truncate + // its size. + popupMaxHeight = below ? Math.Min(rootHeight - popupY, popupMaxHeight) : Math.Min(rootOffsetY, popupMaxHeight); + + Popup.HorizontalOffset = 0; + Popup.VerticalOffset = 0; + +#if SILVERLIGHT + OutsidePopupCanvas.Width = rootWidth; + OutsidePopupCanvas.Height = rootHeight; + + // Transform the transparent canvas to the plugin's coordinate + // space origin. + Matrix transformToRootMatrix = mt.Matrix; + Matrix newMatrix; + transformToRootMatrix.Invert(out newMatrix); + mt.Matrix = newMatrix; + + OutsidePopupCanvas.RenderTransform = mt; +#endif + PopupChild.MinWidth = myControlWidth; + PopupChild.MaxWidth = rootWidth; + PopupChild.MinHeight = 0; + PopupChild.MaxHeight = Math.Max(0, popupMaxHeight); + + PopupChild.Width = popupContentWidth; + // PopupChild.Height = popupContentHeight; + PopupChild.HorizontalAlignment = HorizontalAlignment.Left; + PopupChild.VerticalAlignment = VerticalAlignment.Top; + + // Set the top left corner for the actual drop down. + Canvas.SetLeft(PopupChild, popupX - rootOffsetX); + Canvas.SetTop(PopupChild, popupY - rootOffsetY); + } +#endif // WINDOWS_PHONE + + /// + /// Fires the Closed event. + /// + /// The event data. + private void OnClosed(EventArgs e) + { + var handler = Closed; + if (handler != null) + { + handler(this, e); + } + } + + /// + /// Actually closes the popup after the VSM state animation completes. + /// + /// Event source. + /// Event arguments. + private void OnPopupClosedStateChanged(object sender, VisualStateChangedEventArgs e) + { + // Delayed closing of the popup until now + if (e != null && e.NewState != null && e.NewState.Name == VisualStates.StatePopupClosed) + { + if (Popup != null) + { + Popup.IsOpen = false; + } + OnClosed(EventArgs.Empty); + } + } + + /// + /// Should be called by the parent control before the base + /// OnApplyTemplate method is called. + /// + public void BeforeOnApplyTemplate() + { + if (UsesClosingVisualState) + { + // Unhook the event handler for the popup closed visual state group. + // This code is used to enable visual state transitions before + // actually setting the underlying Popup.IsOpen property to false. + VisualStateGroup groupPopupClosed = VisualStates.TryGetVisualStateGroup(Parent, VisualStates.GroupPopup); + if (null != groupPopupClosed) + { + groupPopupClosed.CurrentStateChanged -= OnPopupClosedStateChanged; + UsesClosingVisualState = false; + } + } + + if (Popup != null) + { + Popup.Closed -= Popup_Closed; + } + } + + /// + /// Should be called by the parent control after the base + /// OnApplyTemplate method is called. + /// + public void AfterOnApplyTemplate() + { + if (Popup != null) + { + Popup.Closed += Popup_Closed; + } + + VisualStateGroup groupPopupClosed = VisualStates.TryGetVisualStateGroup(Parent, VisualStates.GroupPopup); + if (null != groupPopupClosed) + { + groupPopupClosed.CurrentStateChanged += OnPopupClosedStateChanged; + UsesClosingVisualState = true; + } + + if (Popup != null) + { + PopupChild = Popup.Child as FrameworkElement; + + // For Silverlight only, we just create the popup child with + // canvas a single time. + if (PopupChild != null && !_hasControlLoaded) + { +#if SILVERLIGHT + _hasControlLoaded = true; + + // Replace the popup child with a canvas + PopupChildCanvas = new Canvas(); + Popup.Child = PopupChildCanvas; + + OutsidePopupCanvas = new Canvas(); + OutsidePopupCanvas.Background = new SolidColorBrush(Colors.Transparent); + OutsidePopupCanvas.MouseLeftButtonDown += OutsidePopup_MouseLeftButtonDown; + + PopupChildCanvas.Children.Add(OutsidePopupCanvas); + PopupChildCanvas.Children.Add(PopupChild); +#endif + + PopupChild.GotFocus += PopupChild_GotFocus; + PopupChild.LostFocus += PopupChild_LostFocus; + PopupChild.MouseEnter += PopupChild_MouseEnter; + PopupChild.MouseLeave += PopupChild_MouseLeave; + PopupChild.SizeChanged += PopupChild_SizeChanged; + } + } + } + + /// + /// The size of the popup child has changed. + /// + /// The source object. + /// The event data. + private void PopupChild_SizeChanged(object sender, SizeChangedEventArgs e) + { + Arrange(null); + } + + /// + /// The mouse has clicked outside of the popup. + /// + /// The source object. + /// The event data. + private void OutsidePopup_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + if (Popup != null) + { + Popup.IsOpen = false; + } + } + + /// + /// Connected to the Popup Closed event and fires the Closed event. + /// + /// The source object. + /// The event data. + private void Popup_Closed(object sender, EventArgs e) + { + OnClosed(EventArgs.Empty); + } + + /// + /// Connected to several events that indicate that the FocusChanged + /// event should bubble up to the parent control. + /// + /// The event data. + private void OnFocusChanged(EventArgs e) + { + var handler = FocusChanged; + if (handler != null) + { + handler(this, e); + } + } + + /// + /// Fires the UpdateVisualStates event. + /// + /// The event data. + private void OnUpdateVisualStates(EventArgs e) + { + var handler = UpdateVisualStates; + if (handler != null) + { + handler(this, e); + } + } + + /// + /// The popup child has received focus. + /// + /// The source object. + /// The event data. + private void PopupChild_GotFocus(object sender, RoutedEventArgs e) + { + OnFocusChanged(EventArgs.Empty); + } + + /// + /// The popup child has lost focus. + /// + /// The source object. + /// The event data. + private void PopupChild_LostFocus(object sender, RoutedEventArgs e) + { + OnFocusChanged(EventArgs.Empty); + } + + /// + /// The popup child has had the mouse enter its bounds. + /// + /// The source object. + /// The event data. + private void PopupChild_MouseEnter(object sender, MouseEventArgs e) + { + OnUpdateVisualStates(EventArgs.Empty); + } + + /// + /// The mouse has left the popup child's bounds. + /// + /// The source object. + /// The event data. + private void PopupChild_MouseLeave(object sender, MouseEventArgs e) + { + OnUpdateVisualStates(EventArgs.Empty); + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/SelectorSelectionAdapter.cs b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/SelectorSelectionAdapter.cs new file mode 100644 index 0000000..fd85a14 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/AutoCompleteBox/SelectorSelectionAdapter.cs @@ -0,0 +1,379 @@ +// (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.Linq; +using System.Windows; +using System.Windows.Automation.Peers; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// Represents the selection adapter contained in the drop-down portion of + /// an control. + /// + /// Stable + public partial class SelectorSelectionAdapter : ISelectionAdapter + { + /// + /// The Selector instance. + /// + private Selector _selector; + + /// + /// Gets or sets a value indicating whether the selection change event + /// should not be fired. + /// + private bool IgnoringSelectionChanged { get; set; } + + /// + /// Gets or sets the underlying + /// + /// control. + /// + /// The underlying + /// + /// control. + public Selector SelectorControl + { + get { return _selector; } + + set + { + if (_selector != null) + { + _selector.SelectionChanged -= OnSelectionChanged; +#if !WINDOWS_PHONE + _selector.MouseLeftButtonUp -= OnSelectorMouseLeftButtonUp; +#endif + } + + _selector = value; + + if (_selector != null) + { + _selector.SelectionChanged += OnSelectionChanged; +#if !WINDOWS_PHONE + _selector.MouseLeftButtonUp += OnSelectorMouseLeftButtonUp; +#endif + } + } + } + + /// + /// Occurs when the + /// + /// property value changes. + /// + public event SelectionChangedEventHandler SelectionChanged; + + /// + /// Occurs when an item is selected and is committed to the underlying + /// + /// control. + /// + public event RoutedEventHandler Commit; + + /// + /// Occurs when a selection is canceled before it is committed. + /// + public event RoutedEventHandler Cancel; + + /// + /// Initializes a new instance of the + /// + /// class. + /// + public SelectorSelectionAdapter() + { + } + + /// + /// Initializes a new instance of the + /// + /// class with the specified + /// + /// control. + /// + /// The + /// control + /// to wrap as a + /// . + public SelectorSelectionAdapter(Selector selector) + { + SelectorControl = selector; + } + + /// + /// Gets or sets the selected item of the selection adapter. + /// + /// The selected item of the underlying selection adapter. + public object SelectedItem + { + get + { + return SelectorControl == null ? null : SelectorControl.SelectedItem; + } + + set + { + IgnoringSelectionChanged = true; + if (SelectorControl != null) + { + SelectorControl.SelectedItem = value; + } + + // Attempt to reset the scroll viewer's position + if (value == null) + { + ResetScrollViewer(); + } + + IgnoringSelectionChanged = false; + } + } + + /// + /// Gets or sets a collection that is used to generate the content of + /// the selection adapter. + /// + /// The collection used to generate content for the selection + /// adapter. + public IEnumerable ItemsSource + { + get + { + return SelectorControl == null ? null : SelectorControl.ItemsSource; + } + set + { + if (SelectorControl != null) + { + SelectorControl.ItemsSource = value; + } + } + } + + /// + /// If the control contains a ScrollViewer, this will reset the viewer + /// to be scrolled to the top. + /// + private void ResetScrollViewer() + { + if (SelectorControl != null) + { + ScrollViewer sv = SelectorControl.GetLogicalChildrenBreadthFirst().OfType().FirstOrDefault(); + if (sv != null) + { + sv.ScrollToVerticalOffset(0); + } + } + } + +#if !WINDOWS_PHONE + /// + /// Handles the mouse left button up event on the selector control. + /// + /// The source object. + /// The event data. + private void OnSelectorMouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + OnCommit(); + } +#endif + + /// + /// Handles the SelectionChanged event on the Selector control. + /// + /// The source object. + /// The selection changed event data. + private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (IgnoringSelectionChanged) + { + return; + } + + var handler = SelectionChanged; + if (handler != null) + { + handler(sender, e); + } + +#if WINDOWS_PHONE + OnCommit(); +#endif + } + + /// + /// Increments the + /// + /// property of the underlying + /// + /// control. + /// + protected void SelectedIndexIncrement() + { + if (SelectorControl != null) + { + SelectorControl.SelectedIndex = SelectorControl.SelectedIndex + 1 >= SelectorControl.Items.Count ? -1 : SelectorControl.SelectedIndex + 1; + } + } + + /// + /// Decrements the + /// + /// property of the underlying + /// + /// control. + /// + protected void SelectedIndexDecrement() + { + if (SelectorControl != null) + { + int index = SelectorControl.SelectedIndex; + if (index >= 0) + { + SelectorControl.SelectedIndex--; + } + else if (index == -1) + { + SelectorControl.SelectedIndex = SelectorControl.Items.Count - 1; + } + } + } + + /// + /// Provides handling for the + /// event that occurs + /// when a key is pressed while the drop-down portion of the + /// has focus. + /// + /// A + /// that contains data about the + /// event. + public void HandleKeyDown(KeyEventArgs e) + { + if (e == null) + { + throw new ArgumentNullException("e"); + } + switch (e.Key) + { + case Key.Enter: + OnCommit(); + e.Handled = true; + break; + + case Key.Up: + SelectedIndexDecrement(); + e.Handled = true; + break; + + case Key.Down: + if ((ModifierKeys.Alt & Keyboard.Modifiers) == ModifierKeys.None) + { + SelectedIndexIncrement(); + e.Handled = true; + } + break; + + case Key.Escape: + OnCancel(); + e.Handled = true; + break; + + default: + break; + } + } + + /// + /// Raises the + /// + /// event. + /// + protected virtual void OnCommit() + { + OnCommit(this, new RoutedEventArgs()); + } + + /// + /// Fires the Commit event. + /// + /// The source object. + /// The event data. + private void OnCommit(object sender, RoutedEventArgs e) + { + var handler = Commit; + if (handler != null) + { + handler(sender, e); + } + + AfterAdapterAction(); + } + + /// + /// Raises the + /// + /// event. + /// + protected virtual void OnCancel() + { + OnCancel(this, new RoutedEventArgs()); + } + + /// + /// Fires the Cancel event. + /// + /// The source object. + /// The event data. + private void OnCancel(object sender, RoutedEventArgs e) + { + var handler = Cancel; + if (handler != null) + { + handler(sender, e); + } + + AfterAdapterAction(); + } + + /// + /// Change the selection after the actions are complete. + /// + private void AfterAdapterAction() + { + IgnoringSelectionChanged = true; + if (SelectorControl != null) + { + SelectorControl.SelectedItem = null; + SelectorControl.SelectedIndex = -1; + } + IgnoringSelectionChanged = false; + } + + /// + /// Returns an automation peer for the underlying + /// + /// control, for use by the Silverlight automation infrastructure. + /// + /// An automation peer for use by the Silverlight automation + /// infrastructure. + public AutomationPeer CreateAutomationPeer() + { + return _selector != null ? FrameworkElementAutomationPeer.CreatePeerForElement(_selector) : null; + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Common/BindingEvaluator.cs b/Microsoft.Phone.Controls.Toolkit/Common/BindingEvaluator.cs new file mode 100644 index 0000000..fa311ed --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/BindingEvaluator.cs @@ -0,0 +1,121 @@ +// (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.Windows; +using System.Windows.Data; + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// A framework element that permits a binding to be evaluated in a new data + /// context leaf node. + /// + /// The type of dynamic binding to return. + internal partial class BindingEvaluator : FrameworkElement + { + /// + /// Gets or sets the string value binding used by the control. + /// + private Binding _binding; + + #region public T Value + /// + /// Gets or sets the data item string value. + /// + public T Value + { + get { return (T)GetValue(ValueProperty); } + set { SetValue(ValueProperty, value); } + } + + /// + /// Identifies the Value dependency property. + /// + public static readonly DependencyProperty ValueProperty = + DependencyProperty.Register( + "Value", + typeof(T), + typeof(BindingEvaluator), + new PropertyMetadata(default(T))); + + #endregion public string Value + + /// + /// Gets or sets the value binding. + /// + public Binding ValueBinding + { + get { return _binding; } + set + { + _binding = value; + SetBinding(ValueProperty, _binding); + } + } + + /// + /// Initializes a new instance of the BindingEvaluator class. + /// + public BindingEvaluator() + { + } + + /// + /// Initializes a new instance of the BindingEvaluator class, + /// setting the initial binding to the provided parameter. + /// + /// The initial string value binding. + public BindingEvaluator(Binding binding) + { + SetBinding(ValueProperty, binding); + } + + /// + /// Clears the data context so that the control does not keep a + /// reference to the last-looked up item. + /// + public void ClearDataContext() + { + DataContext = null; + } + + /// + /// Updates the data context of the framework element and returns the + /// updated binding value. + /// + /// The object to use as the data context. + /// If set to true, this parameter will + /// clear the data context immediately after retrieving the value. + /// Returns the evaluated T value of the bound dependency + /// property. + public T GetDynamicValue(object o, bool clearDataContext) + { + DataContext = o; + T value = Value; + if (clearDataContext) + { + DataContext = null; + } + return value; + } + + /// + /// Updates the data context of the framework element and returns the + /// updated binding value. + /// + /// The object to use as the data context. + /// Returns the evaluated T value of the bound dependency + /// property. + public T GetDynamicValue(object o) + { + DataContext = o; + return Value; + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Common/CultureInfoExtensions.cs b/Microsoft.Phone.Controls.Toolkit/Common/CultureInfoExtensions.cs new file mode 100644 index 0000000..e16bb85 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/CultureInfoExtensions.cs @@ -0,0 +1,90 @@ +// (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.Generic; +using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Linq; + +namespace Microsoft.Phone.Controls +{ + /// + /// Extends the CultureInfo class to add Weekdays and Weekends methods. + /// + public static class CultureInfoExtensions + { + private static string[] CulturesWithTFWeekends = { "ar-SA" }; + private static string[] CulturesWithFSWeekends = { "he-IL", "ar-EG" }; + + /// + /// Returns a list of days that are weekdays in the given culture. + /// + /// The culture to lookup. + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Extension method.")] + public static ReadOnlyCollection Weekdays(this CultureInfo culture) + { + DayOfWeek[] daysOfWeek; + + if (CulturesWithTFWeekends.Contains(culture.Name)) + { + daysOfWeek = new DayOfWeek[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Saturday, DayOfWeek.Sunday }; + } + else if (CulturesWithFSWeekends.Contains(culture.Name)) + { + daysOfWeek = new DayOfWeek[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Sunday }; + } + else + { + daysOfWeek = new DayOfWeek[] { DayOfWeek.Monday, DayOfWeek.Tuesday, DayOfWeek.Wednesday, DayOfWeek.Thursday, DayOfWeek.Friday }; + } + + List weekdays = new List(); + + foreach (DayOfWeek day in daysOfWeek) + { + weekdays.Add(culture.DateTimeFormat.GetDayName(day)); + } + + return new ReadOnlyCollection(weekdays); + } + + /// + /// Returns a list of days that are weekends in the given culture. + /// + /// The culture to lookup. + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Extension method.")] + public static ReadOnlyCollection Weekends(this CultureInfo culture) + { + DayOfWeek[] daysOfWeek; + + if (CulturesWithTFWeekends.Contains(culture.Name)) + { + DayOfWeek[] d = { DayOfWeek.Thursday, DayOfWeek.Friday }; + daysOfWeek = d; + } + else if (CulturesWithFSWeekends.Contains(culture.Name)) + { + DayOfWeek[] d = { DayOfWeek.Friday, DayOfWeek.Saturday }; + daysOfWeek = d; + } + else + { + DayOfWeek[] d = { DayOfWeek.Saturday, DayOfWeek.Sunday }; + daysOfWeek = d; + } + + List weekends = new List(); + + foreach (DayOfWeek day in daysOfWeek) + { + weekends.Add(culture.DateTimeFormat.GetDayName(day)); + } + + return new ReadOnlyCollection(weekends); + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/Common/Extensions.cs b/Microsoft.Phone.Controls.Toolkit/Common/Extensions.cs new file mode 100644 index 0000000..ae0a2e1 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/Extensions.cs @@ -0,0 +1,113 @@ +// (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.Windows; +using System.Windows.Data; +using System.Windows.Media; + +namespace Microsoft.Phone.Controls +{ + /// + /// This set of internal extension methods provide general solutions and + /// utilities in a small enough number to not warrant a dedicated extension + /// methods class. + /// + internal static partial class Extensions + { + private const string ExternalAddress = "app://external/"; + + /// + /// Inverts a Matrix. The Invert functionality on the Matrix type is + /// internal to the framework only. Since Matrix is a struct, an out + /// parameter must be presented. + /// + /// The Matrix object. + /// The matrix to return by an output + /// parameter. + /// Returns a value indicating whether the type was + /// successfully inverted. If the determinant is 0.0, then it cannot + /// be inverted and the original instance will remain untouched. + public static bool Invert(this Matrix m, out Matrix outputMatrix) + { + double determinant = m.M11 * m.M22 - m.M12 * m.M21; + if (determinant == 0.0) + { + outputMatrix = m; + return false; + } + + Matrix matCopy = m; + m.M11 = matCopy.M22 / determinant; + m.M12 = -1 * matCopy.M12 / determinant; + m.M21 = -1 * matCopy.M21 / determinant; + m.M22 = matCopy.M11 / determinant; + m.OffsetX = (matCopy.OffsetY * matCopy.M21 - matCopy.OffsetX * matCopy.M22) / determinant; + m.OffsetY = (matCopy.OffsetX * matCopy.M12 - matCopy.OffsetY * matCopy.M11) / determinant; + + outputMatrix = m; + return true; + } + + /// + /// An implementation of the Contains member of string that takes in a + /// string comparison. The traditional .NET string Contains member uses + /// StringComparison.Ordinal. + /// + /// The string. + /// The string value to search for. + /// The string comparison type. + /// Returns true when the substring is found. + public static bool Contains(this string s, string value, StringComparison comparison) + { + return s.IndexOf(value, comparison) >= 0; + } + + /// + /// Returns whether the page orientation is in portrait. + /// + /// Page orientation + /// If the orientation is portrait + public static bool IsPortrait(this PageOrientation orientation) + { + return (PageOrientation.Portrait == (PageOrientation.Portrait & orientation)); + } + + /// + /// Returns whether the dark visual theme is currently active. + /// + /// Resource Dictionary + public static bool IsDarkThemeActive(this ResourceDictionary resources) + { + return ((Visibility)resources["PhoneDarkThemeVisibility"] == Visibility.Visible); + } + + /// + /// Returns whether the uri is from an external source. + /// + /// The uri + public static bool IsExternalNavigation(this Uri uri) + { + return (ExternalAddress == uri.ToString()); + } + + /// + /// Registers a property changed callback for a given property. + /// + /// The element registering the notification + /// Property name to register + /// Callback function + /// This allows a child to be notified of when a property declared in its parent is changed. + public static void RegisterNotification(this FrameworkElement element, string propertyName, PropertyChangedCallback callback) + { + DependencyProperty prop = DependencyProperty.RegisterAttached("Notification" + propertyName, + typeof(object), + typeof(FrameworkElement), + new PropertyMetadata(callback)); + + element.SetBinding(prop, new Binding(propertyName) { Source = element }); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Common/IUpdateVisualState.cs b/Microsoft.Phone.Controls.Toolkit/Common/IUpdateVisualState.cs new file mode 100644 index 0000000..75c9920 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/IUpdateVisualState.cs @@ -0,0 +1,30 @@ +// (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.Diagnostics.CodeAnalysis; + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// The IUpdateVisualState interface is used to provide the + /// InteractionHelper with access to the type's UpdateVisualState method. + /// + [SuppressMessage("Microsoft.Design", "CA1064:ExceptionsShouldBePublic", Justification = "This is not an exception class.")] + internal interface IUpdateVisualState + { + /// + /// Update the visual state of the control. + /// + /// + /// A value indicating whether to automatically generate transitions to + /// the new state, or instantly transition to the new state. + /// + void UpdateVisualState(bool useTransitions); + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Common/InteractionHelper.cs b/Microsoft.Phone.Controls.Toolkit/Common/InteractionHelper.cs new file mode 100644 index 0000000..951aed6 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/InteractionHelper.cs @@ -0,0 +1,528 @@ +// (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.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Windows.Controls; + + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// The InteractionHelper provides controls with support for all of the + /// common interactions like mouse movement, mouse clicks, key presses, + /// etc., and also incorporates proper event semantics when the control is + /// disabled. + /// + internal sealed partial class InteractionHelper + { + /// + /// The threshold used to determine whether two clicks are temporally + /// local and considered a double click (or triple, quadruple, etc.). + /// 500 milliseconds is the default double click value on Windows. + /// This value would ideally be pulled form the system settings. + /// + private const double SequentialClickThresholdInMilliseconds = 500.0; + + /// + /// The threshold used to determine whether two clicks are spatially + /// local and considered a double click (or triple, quadruple, etc.) + /// in pixels squared. We use pixels squared so that we can compare to + /// the distance delta without taking a square root. + /// + private const double SequentialClickThresholdInPixelsSquared = 3.0 * 3.0; + + /// + /// Gets the control the InteractionHelper is targeting. + /// + public Control Control { get; private set; } + + /// + /// Gets a value indicating whether the control has focus. + /// + public bool IsFocused { get; private set; } + + /// + /// Gets a value indicating whether the mouse is over the control. + /// + public bool IsMouseOver { get; private set; } + + /// + /// Gets a value indicating whether the read-only property is set. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Linked file.")] + public bool IsReadOnly { get; private set; } + + /// + /// Gets a value indicating whether the mouse button is pressed down + /// over the control. + /// + public bool IsPressed { get; private set; } + +#if !WINDOWS_PHONE + /// + /// Gets or sets the last time the control was clicked. + /// + /// + /// The value is stored as Utc time because it is slightly more + /// performant than converting to local time. + /// + private DateTime LastClickTime { get; set; } + + /// + /// Gets or sets the mouse position of the last click. + /// + /// The value is relative to the control. + private Point LastClickPosition { get; set; } + + /// + /// Gets the number of times the control was clicked. + /// + public int ClickCount { get; private set; } +#endif + + /// + /// Reference used to call UpdateVisualState on the base class. + /// + private IUpdateVisualState _updateVisualState; + + /// + /// Initializes a new instance of the InteractionHelper class. + /// + /// Control receiving interaction. + public InteractionHelper(Control control) + { + Debug.Assert(control != null, "control should not be null!"); + Control = control; + _updateVisualState = control as IUpdateVisualState; + + // Wire up the event handlers for events without a virtual override + control.Loaded += OnLoaded; + control.IsEnabledChanged += OnIsEnabledChanged; + } + + #region UpdateVisualState + /// + /// Update the visual state of the control. + /// + /// + /// A value indicating whether to automatically generate transitions to + /// the new state, or instantly transition to the new state. + /// + /// + /// UpdateVisualState works differently than the rest of the injected + /// functionality. Most of the other events are overridden by the + /// calling class which calls Allow, does what it wants, and then calls + /// Base. UpdateVisualState is the opposite because a number of the + /// methods in InteractionHelper need to trigger it in the calling + /// class. We do this using the IUpdateVisualState internal interface. + /// + private void UpdateVisualState(bool useTransitions) + { + if (_updateVisualState != null) + { + _updateVisualState.UpdateVisualState(useTransitions); + } + } + + /// + /// Update the visual state of the control. + /// + /// + /// A value indicating whether to automatically generate transitions to + /// the new state, or instantly transition to the new state. + /// + public void UpdateVisualStateBase(bool useTransitions) + { + // Handle the Common states + if (!Control.IsEnabled) + { + VisualStates.GoToState(Control, useTransitions, VisualStates.StateDisabled, VisualStates.StateNormal); + } + else if (IsReadOnly) + { + VisualStates.GoToState(Control, useTransitions, VisualStates.StateReadOnly, VisualStates.StateNormal); + } + else if (IsPressed) + { + VisualStates.GoToState(Control, useTransitions, VisualStates.StatePressed, VisualStates.StateMouseOver, VisualStates.StateNormal); + } + else if (IsMouseOver) + { + VisualStates.GoToState(Control, useTransitions, VisualStates.StateMouseOver, VisualStates.StateNormal); + } + else + { + VisualStates.GoToState(Control, useTransitions, VisualStates.StateNormal); + } + + // Handle the Focused states + if (IsFocused) + { + VisualStates.GoToState(Control, useTransitions, VisualStates.StateFocused, VisualStates.StateUnfocused); + } + else + { + VisualStates.GoToState(Control, useTransitions, VisualStates.StateUnfocused); + } + } + #endregion UpdateVisualState + + /// + /// Handle the control's Loaded event. + /// + /// The control. + /// Event arguments. + private void OnLoaded(object sender, RoutedEventArgs e) + { + UpdateVisualState(false); + } + + /// + /// Handle changes to the control's IsEnabled property. + /// + /// The control. + /// Event arguments. + private void OnIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) + { + bool enabled = (bool)e.NewValue; + if (!enabled) + { + IsPressed = false; + IsMouseOver = false; + IsFocused = false; + } + + UpdateVisualState(true); + } + + /// + /// Handles changes to the control's IsReadOnly property. + /// + /// The value of the property. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Linked file.")] + public void OnIsReadOnlyChanged(bool value) + { + IsReadOnly = value; + if (!value) + { + IsPressed = false; + IsMouseOver = false; + IsFocused = false; + } + + UpdateVisualState(true); + } + + /// + /// Update the visual state of the control when its template is changed. + /// + public void OnApplyTemplateBase() + { + UpdateVisualState(false); + } + +#if !WINDOWS_PHONE + #region GotFocus + /// + /// Check if the control's GotFocus event should be handled. + /// + /// Event arguments. + /// + /// A value indicating whether the event should be handled. + /// + public bool AllowGotFocus(RoutedEventArgs e) + { + if (e == null) + { + throw new ArgumentNullException("e"); + } + + bool enabled = Control.IsEnabled; + if (enabled) + { + IsFocused = true; + } + return enabled; + } + + /// + /// Base implementation of the virtual GotFocus event handler. + /// + public void OnGotFocusBase() + { + UpdateVisualState(true); + } + #endregion GotFocus + + #region LostFocus + /// + /// Check if the control's LostFocus event should be handled. + /// + /// Event arguments. + /// + /// A value indicating whether the event should be handled. + /// + public bool AllowLostFocus(RoutedEventArgs e) + { + if (e == null) + { + throw new ArgumentNullException("e"); + } + + bool enabled = Control.IsEnabled; + if (enabled) + { + IsFocused = false; + } + return enabled; + } + + /// + /// Base implementation of the virtual LostFocus event handler. + /// + public void OnLostFocusBase() + { + IsPressed = false; + UpdateVisualState(true); + } + #endregion LostFocus + + #region MouseEnter + /// + /// Check if the control's MouseEnter event should be handled. + /// + /// Event arguments. + /// + /// A value indicating whether the event should be handled. + /// + public bool AllowMouseEnter(MouseEventArgs e) + { + if (e == null) + { + throw new ArgumentNullException("e"); + } + + bool enabled = Control.IsEnabled; + if (enabled) + { + IsMouseOver = true; + } + return enabled; + } + + /// + /// Base implementation of the virtual MouseEnter event handler. + /// + public void OnMouseEnterBase() + { + UpdateVisualState(true); + } + #endregion MouseEnter + + #region MouseLeave + /// + /// Check if the control's MouseLeave event should be handled. + /// + /// Event arguments. + /// + /// A value indicating whether the event should be handled. + /// + public bool AllowMouseLeave(MouseEventArgs e) + { + if (e == null) + { + throw new ArgumentNullException("e"); + } + + bool enabled = Control.IsEnabled; + if (enabled) + { + IsMouseOver = false; + } + return enabled; + } + + /// + /// Base implementation of the virtual MouseLeave event handler. + /// + public void OnMouseLeaveBase() + { + UpdateVisualState(true); + } + #endregion MouseLeave + + #region MouseLeftButtonDown + /// + /// Check if the control's MouseLeftButtonDown event should be handled. + /// + /// Event arguments. + /// + /// A value indicating whether the event should be handled. + /// + public bool AllowMouseLeftButtonDown(MouseButtonEventArgs e) + { + if (e == null) + { + throw new ArgumentNullException("e"); + } + + bool enabled = Control.IsEnabled; + if (enabled) + { + // Get the current position and time + DateTime now = DateTime.UtcNow; + Point position = e.GetPosition(Control); + + // Compute the deltas from the last click + double timeDelta = (now - LastClickTime).TotalMilliseconds; + Point lastPosition = LastClickPosition; + double dx = position.X - lastPosition.X; + double dy = position.Y - lastPosition.Y; + double distance = dx * dx + dy * dy; + + // Check if the values fall under the sequential click temporal + // and spatial thresholds + if (timeDelta < SequentialClickThresholdInMilliseconds && + distance < SequentialClickThresholdInPixelsSquared) + { + ClickCount++; + } + else + { + ClickCount = 1; + } + + // Set the new position and time + LastClickTime = now; + LastClickPosition = position; + + // Raise the event + IsPressed = true; + } + else + { + ClickCount = 1; + } + + return enabled; + } + + /// + /// Base implementation of the virtual MouseLeftButtonDown event + /// handler. + /// + public void OnMouseLeftButtonDownBase() + { + UpdateVisualState(true); + } + #endregion MouseLeftButtonDown + + #region MouseLeftButtonUp + /// + /// Check if the control's MouseLeftButtonUp event should be handled. + /// + /// Event arguments. + /// + /// A value indicating whether the event should be handled. + /// + public bool AllowMouseLeftButtonUp(MouseButtonEventArgs e) + { + if (e == null) + { + throw new ArgumentNullException("e"); + } + + bool enabled = Control.IsEnabled; + if (enabled) + { + IsPressed = false; + } + return enabled; + } + + /// + /// Base implementation of the virtual MouseLeftButtonUp event handler. + /// + public void OnMouseLeftButtonUpBase() + { + UpdateVisualState(true); + } + #endregion MouseLeftButtonUp + + #region KeyDown + /// + /// Check if the control's KeyDown event should be handled. + /// + /// Event arguments. + /// + /// A value indicating whether the event should be handled. + /// + public bool AllowKeyDown(KeyEventArgs e) + { + if (e == null) + { + throw new ArgumentNullException("e"); + } + + return Control.IsEnabled; + } + #endregion KeyDown + + #region KeyUp + /// + /// Check if the control's KeyUp event should be handled. + /// + /// Event arguments. + /// + /// A value indicating whether the event should be handled. + /// + public bool AllowKeyUp(KeyEventArgs e) + { + if (e == null) + { + throw new ArgumentNullException("e"); + } + + return Control.IsEnabled; + } + #endregion KeyUp + + #region RightToLeft key translation + /// + /// Translates keys for proper RightToLeft mode support. + /// + /// Control's flow direction mode. + /// Original key. + /// + /// A translated key code, indicating how the original key should be interpreted. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Linked file.")] + public static Key GetLogicalKey(FlowDirection flowDirection, Key originalKey) + { + Key result = originalKey; + if (flowDirection == FlowDirection.RightToLeft) + { + switch (originalKey) + { + case Key.Left: + result = Key.Right; + break; + case Key.Right: + result = Key.Left; + break; + } + } + return result; + } + #endregion +#endif + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Common/ItemsControlExtensions.cs b/Microsoft.Phone.Controls.Toolkit/Common/ItemsControlExtensions.cs new file mode 100644 index 0000000..85b231b --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/ItemsControlExtensions.cs @@ -0,0 +1,120 @@ +// (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.Generic; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace Microsoft.Phone.Controls +{ + /// + /// Provides helper methods to work with ItemsControl. + /// + internal static class ItemsControlExtensions + { + /// + /// Gets the parent ItemsControl. + /// + /// The type of ItemsControl. + /// The dependency object + /// + /// The parent ItemsControl or null if there is not. + /// + public static T GetParentItemsControl(DependencyObject element) + where T : ItemsControl + { + var parent = VisualTreeHelper.GetParent(element); + + while (!(parent is T) && (parent != null)) + { + parent = VisualTreeHelper.GetParent(parent as DependencyObject); + } + + return (T)parent; + } + + /// + /// Gets the items that are currently in the view port + /// of an ItemsControl with a ScrollViewer. + /// + /// The ItemsControl to search on. + /// + /// A collection of weak references to the items in the view port. + /// + public static IList GetItemsInViewPort(ItemsControl list) + { + int index; + FrameworkElement container; + GeneralTransform itemTransform; + Rect boundingBox; + IList viewPortItems = new List(); + ScrollViewer scrollHost = VisualTreeHelper.GetChild(list, 0) as ScrollViewer; + + list.UpdateLayout(); + + if (scrollHost == null) + { + return viewPortItems; + } + + for (index = 0; index < list.Items.Count; index++) + { + container = (FrameworkElement)list.ItemContainerGenerator.ContainerFromIndex(index); + if (container != null) + { + itemTransform = null; + try + { + itemTransform = container.TransformToVisual(scrollHost); + } + catch (ArgumentException) + { + // Ignore failures when not in the visual tree + return viewPortItems; + } + + boundingBox = new Rect(itemTransform.Transform(new Point()), itemTransform.Transform(new Point(container.ActualWidth, container.ActualHeight))); + + if (boundingBox.Bottom > 0) + { + viewPortItems.Add(new WeakReference(container)); + index++; + break; + } + } + } + + for (; index < list.Items.Count; index++) + { + container = (FrameworkElement)list.ItemContainerGenerator.ContainerFromIndex(index); + itemTransform = null; + try + { + itemTransform = container.TransformToVisual(scrollHost); + } + catch (ArgumentException) + { + // Ignore failures when not in the visual tree + return viewPortItems; + } + + boundingBox = new Rect(itemTransform.Transform(new Point()), itemTransform.Transform(new Point(container.ActualWidth, container.ActualHeight))); + + if (boundingBox.Top < scrollHost.ActualHeight) + { + viewPortItems.Add(new WeakReference(container)); + } + else + { + break; + } + } + + return viewPortItems; + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/Common/ItemsControlHelper.cs b/Microsoft.Phone.Controls.Toolkit/Common/ItemsControlHelper.cs new file mode 100644 index 0000000..dd39432 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/ItemsControlHelper.cs @@ -0,0 +1,235 @@ +// (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.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Windows.Media; + +namespace System.Windows.Controls +{ + /// + /// The ItemContainerGenerator provides useful utilities for ItemsControls. + /// + /// Preview + internal sealed partial class ItemsControlHelper + { + /// + /// Gets or sets the ItemsControl being tracked by the + /// ItemContainerGenerator. + /// + private ItemsControl ItemsControl { get; set; } + + /// + /// A Panel that is used as the ItemsHost of the ItemsControl. This + /// property will only be valid when the ItemsControl is live in the + /// tree and has generated containers for some of its items. + /// + private Panel _itemsHost; + + /// + /// Gets a Panel that is used as the ItemsHost of the ItemsControl. + /// This property will only be valid when the ItemsControl is live in + /// the tree and has generated containers for some of its items. + /// + internal Panel ItemsHost + { + get + { + // Lookup the ItemsHost if we haven't already cached it. + if (_itemsHost == null && ItemsControl != null && ItemsControl.ItemContainerGenerator != null) + { + // Get any live container + DependencyObject container = ItemsControl.ItemContainerGenerator.ContainerFromIndex(0); + if (container != null) + { + // Get the parent of the container + _itemsHost = VisualTreeHelper.GetParent(container) as Panel; + } + } + + return _itemsHost; + } + } + + /// + /// A ScrollViewer that is used to scroll the items in the ItemsHost. + /// + private ScrollViewer _scrollHost; + + /// + /// Gets a ScrollViewer that is used to scroll the items in the + /// ItemsHost. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is linked into multiple projects.")] + internal ScrollViewer ScrollHost + { + get + { + if (_scrollHost == null) + { + Panel itemsHost = ItemsHost; + if (itemsHost != null) + { + for (DependencyObject obj = itemsHost; obj != ItemsControl && obj != null; obj = VisualTreeHelper.GetParent(obj)) + { + ScrollViewer viewer = obj as ScrollViewer; + if (viewer != null) + { + _scrollHost = viewer; + break; + } + } + } + } + return _scrollHost; + } + } + + /// + /// Initializes a new instance of the ItemContainerGenerator. + /// + /// + /// The ItemsControl being tracked by the ItemContainerGenerator. + /// + internal ItemsControlHelper(ItemsControl control) + { + Debug.Assert(control != null, "control cannot be null!"); + ItemsControl = control; + } + + /// + /// Apply a control template to the ItemsControl. + /// + internal void OnApplyTemplate() + { + // Clear the cached ItemsHost, ScrollHost + _itemsHost = null; + _scrollHost = null; + } + + /// + /// Prepares the specified container to display the specified item. + /// + /// + /// Container element used to display the specified item. + /// + /// + /// The ItemContainerStyle for the parent ItemsControl. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Code is linked into multiple projects.")] + internal static void PrepareContainerForItemOverride(DependencyObject element, Style parentItemContainerStyle) + { + // Apply the ItemContainerStyle to the item + Control control = element as Control; + if (parentItemContainerStyle != null && control != null && control.Style == null) + { + control.SetValue(Control.StyleProperty, parentItemContainerStyle); + } + + // Note: WPF also does preparation for ContentPresenter, + // ContentControl, HeaderedContentControl, and ItemsControl. Since + // we don't have any other ItemsControls using this + // ItemContainerGenerator, we've removed that code for now. It + // should be added back later when necessary. + } + + /// + /// Update the style of any generated items when the ItemContainerStyle + /// has been changed. + /// + /// The ItemContainerStyle. + /// + /// Silverlight does not support setting a Style multiple times, so we + /// only attempt to set styles on elements whose style hasn't already + /// been set. + /// + internal void UpdateItemContainerStyle(Style itemContainerStyle) + { + if (itemContainerStyle == null) + { + return; + } + + Panel itemsHost = ItemsHost; + if (itemsHost == null || itemsHost.Children == null) + { + return; + } + + foreach (UIElement element in itemsHost.Children) + { + FrameworkElement obj = element as FrameworkElement; + if (obj.Style == null) + { + obj.Style = itemContainerStyle; + } + } + } + + /// + /// Scroll the desired element into the ScrollHost's viewport. + /// + /// Element to scroll into view. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "File is linked across multiple projects and this method is used in some but not others.")] + internal void ScrollIntoView(FrameworkElement element) + { + // Get the ScrollHost + ScrollViewer scrollHost = ScrollHost; + if (scrollHost == null) + { + return; + } + + // Get the position of the element relative to the ScrollHost + GeneralTransform transform = null; + try + { + transform = element.TransformToVisual(scrollHost); + } + catch (ArgumentException) + { + // Ignore failures when not in the visual tree + return; + } + Rect itemRect = new Rect( + transform.Transform(new Point()), + transform.Transform(new Point(element.ActualWidth, element.ActualHeight))); + + // Scroll vertically + double verticalOffset = scrollHost.VerticalOffset; + double verticalDelta = 0; + double hostBottom = scrollHost.ViewportHeight; + double itemBottom = itemRect.Bottom; + if (hostBottom < itemBottom) + { + verticalDelta = itemBottom - hostBottom; + verticalOffset += verticalDelta; + } + double itemTop = itemRect.Top; + if (itemTop - verticalDelta < 0) + { + verticalOffset -= verticalDelta - itemTop; + } + scrollHost.ScrollToVerticalOffset(verticalOffset); + + // Scroll horizontally + double horizontalOffset = scrollHost.HorizontalOffset; + double horizontalDelta = 0; + double hostRight = scrollHost.ViewportWidth; + double itemRight = itemRect.Right; + if (hostRight < itemRight) + { + horizontalDelta = itemRight - hostRight; + horizontalOffset += horizontalDelta; + } + double itemLeft = itemRect.Left; + if (itemLeft - horizontalDelta < 0) + { + horizontalOffset -= horizontalDelta - itemLeft; + } + scrollHost.ScrollToHorizontalOffset(horizontalOffset); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Common/LengthConverter.cs b/Microsoft.Phone.Controls.Toolkit/Common/LengthConverter.cs new file mode 100644 index 0000000..842eeb7 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/LengthConverter.cs @@ -0,0 +1,220 @@ +// (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.Generic; +using System.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Windows.Controls; + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// Converts instances of other types to and from instances of a double that + /// represent an object measurement such as a height or width. + /// + /// Stable + public partial class LengthConverter : TypeConverter + { + /// + /// Conversions from units to pixels. + /// + private static Dictionary UnitToPixelConversions = new Dictionary + { + { "px", 1.0 }, + { "in", 96.0 }, + { "cm", 37.795275590551178 }, + { "pt", 1.3333333333333333 } + }; + + /// + /// Initializes a new instance of the + /// class. + /// + public LengthConverter() + { + } + + /// + /// Determines whether conversion is possible from a specified type to a + /// that represents an object + /// measurement. + /// + /// + /// An + /// that provides a format context. + /// + /// + /// A that represents the type you want to + /// convert from. + /// + /// + /// True if this converter can perform the conversion; otherwise, false. + /// + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")] + public override bool CanConvertFrom(ITypeDescriptorContext typeDescriptorContext, Type sourceType) + { + // Convert numeric types and strings + switch (Type.GetTypeCode(sourceType)) + { + case TypeCode.Int16: + case TypeCode.UInt16: + case TypeCode.Int32: + case TypeCode.UInt32: + case TypeCode.Int64: + case TypeCode.UInt64: + case TypeCode.Single: + case TypeCode.Double: + case TypeCode.Decimal: + case TypeCode.String: + return true; + default: + return false; + } + } + + /// + /// Converts from the specified value to values of the + /// type. + /// + /// + /// An + /// that provides a format context. + /// + /// + /// The to use as the + /// current culture. + /// + /// The value to convert. + /// The converted value. + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")] + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "1#", Justification = "Compat with WPF.")] + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "2#", Justification = "Compat with WPF.")] + public override object ConvertFrom(ITypeDescriptorContext typeDescriptorContext, CultureInfo cultureInfo, object source) + { + if (source == null) + { + string message = string.Format( + CultureInfo.CurrentCulture, +#if WINDOWS_PHONE + Microsoft.Phone.Controls.Properties. +#else + Controls.Properties. +#endif + Resources.TypeConverters_ConvertFrom_CannotConvertFromType, + GetType().Name, + "null"); + throw new NotSupportedException(message); + } + + string text = source as string; + if (text != null) + { + // Convert Auto to NaN + if (string.Compare(text, "Auto", StringComparison.OrdinalIgnoreCase) == 0) + { + return double.NaN; + } + + // Get the unit conversion factor + string number = text; + double conversionFactor = 1.0; + foreach (KeyValuePair conversion in UnitToPixelConversions) + { + if (number.EndsWith(conversion.Key, StringComparison.Ordinal)) + { + conversionFactor = conversion.Value; + number = text.Substring(0, number.Length - conversion.Key.Length); + break; + } + } + + // Convert the value + try + { + return conversionFactor * Convert.ToDouble(number, cultureInfo); + } + catch (FormatException) + { + string message = string.Format( + CultureInfo.CurrentCulture, +#if WINDOWS_PHONE + Microsoft.Phone.Controls.Properties. +#else + Controls.Properties. +#endif + Resources.TypeConverters_Convert_CannotConvert, + GetType().Name, + text, + typeof(double).Name); + throw new FormatException(message); + } + } + + return Convert.ToDouble(source, cultureInfo); + } + + /// + /// Returns whether the type converter can convert a measurement to the + /// specified type. + /// + /// + /// An + /// that provides a format context. + /// + /// + /// A that represents the type you want to + /// convert to. + /// + /// + /// True if this converter can perform the conversion; otherwise, false. + /// + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")] + public override bool CanConvertTo(ITypeDescriptorContext typeDescriptorContext, Type destinationType) + { + return TypeConverters.CanConvertTo(destinationType); + } + + /// + /// Converts the specified measurement to the specified type. + /// + /// + /// An object that provides a format context. + /// + /// + /// The to use as the + /// current culture. + /// + /// The value to convert. + /// + /// A that represents the type you want to + /// convert to. + /// + /// The converted value. + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")] + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "1#", Justification = "Compat with WPF.")] + public override object ConvertTo(ITypeDescriptorContext typeDescriptorContext, CultureInfo cultureInfo, object value, Type destinationType) + { + // Convert the length to a String + if (value is double) + { + double length = (double) value; + if (destinationType == typeof(string)) + { + return length.IsNaN() ? + "Auto" : + Convert.ToString(length, cultureInfo); + } + } + + return TypeConverters.ConvertTo(this, value, destinationType); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Common/MathHelpers.cs b/Microsoft.Phone.Controls.Toolkit/Common/MathHelpers.cs new file mode 100644 index 0000000..e34cfb4 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/MathHelpers.cs @@ -0,0 +1,55 @@ +// (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.Windows; + +namespace Microsoft.Phone.Controls +{ + internal static class MathHelpers + { + /// + /// Return the angle of the hypotenuse of a triangle with + /// sides defined by deltaX and deltaY. + /// + /// Change in X. + /// Change in Y. + /// The angle (in degrees). + public static double GetAngle(double deltaX, double deltaY) + { + double angle = Math.Atan2(deltaY, deltaX); + if (angle < 0) + { + angle = 2 * Math.PI + angle; + } + + return (angle * 360) / (2 * Math.PI); + } + + /// + /// Return the distance between two points + /// + /// The first point. + /// The second point. + /// The distance between the two points. + public static double GetDistance(Point p0, Point p1) + { + double dx = p0.X - p1.X; + double dy = p0.Y - p1.Y; + + return Math.Sqrt(dx * dx + dy * dy); + } + + /// + /// Helper extension method for turning XNA's Vector2 type into a Point + /// + /// The Vector2. + /// The point. + public static Point ToPoint(this Microsoft.Xna.Framework.Vector2 v) + { + return new Point(v.X, v.Y); + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/Common/NumericExtensions.cs b/Microsoft.Phone.Controls.Toolkit/Common/NumericExtensions.cs new file mode 100644 index 0000000..f3e9344 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/NumericExtensions.cs @@ -0,0 +1,127 @@ +// (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.Diagnostics.CodeAnalysis; +using System.Runtime.InteropServices; + +namespace System.Windows.Controls +{ + /// + /// Numeric utility methods used by controls. These methods are similar in + /// scope to the WPF DoubleUtil class. + /// + internal static class NumericExtensions + { + /// + /// NanUnion is a C++ style type union used for efficiently converting + /// a double into an unsigned long, whose bits can be easily + /// manipulated. + /// + [StructLayout(LayoutKind.Explicit)] + private struct NanUnion + { + /// + /// Floating point representation of the union. + /// + [SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Justification = "It is accessed through the other member of the union")] + [FieldOffset(0)] + internal double FloatingValue; + + /// + /// Integer representation of the union. + /// + [FieldOffset(0)] + internal ulong IntegerValue; + } + +#if !WINDOWS_PHONE + /// + /// Check if a number is zero. + /// + /// The number to check. + /// True if the number is zero, false otherwise. + public static bool IsZero(this double value) + { + // We actually consider anything within an order of magnitude of + // epsilon to be zero + return Math.Abs(value) < 2.2204460492503131E-15; + } +#endif + + /// + /// Check if a number isn't really a number. + /// + /// The number to check. + /// + /// True if the number is not a number, false if it is a number. + /// + public static bool IsNaN(this double value) + { + // Get the double as an unsigned long + NanUnion union = new NanUnion { FloatingValue = value }; + + // An IEEE 754 double precision floating point number is NaN if its + // exponent equals 2047 and it has a non-zero mantissa. + ulong exponent = union.IntegerValue & 0xfff0000000000000L; + if ((exponent != 0x7ff0000000000000L) && (exponent != 0xfff0000000000000L)) + { + return false; + } + ulong mantissa = union.IntegerValue & 0x000fffffffffffffL; + return mantissa != 0L; + } + + /// + /// Determine if one number is greater than another. + /// + /// First number. + /// Second number. + /// + /// True if the first number is greater than the second, false + /// otherwise. + /// + public static bool IsGreaterThan(double left, double right) + { + return (left > right) && !AreClose(left, right); + } + + /// + /// Determine if two numbers are close in value. + /// + /// First number. + /// Second number. + /// + /// True if the first number is close in value to the second, false + /// otherwise. + /// + public static bool AreClose(double left, double right) + { + if (left == right) + { + return true; + } + + double a = (Math.Abs(left) + Math.Abs(right) + 10.0) * 2.2204460492503131E-16; + double b = left - right; + return (-a < b) && (a > b); + } + +#if !WINDOWS_PHONE + /// + /// Determine if one number is less than or close to another. + /// + /// First number. + /// Second number. + /// + /// True if the first number is less than or close to the second, false + /// otherwise. + /// + public static bool IsLessThanOrClose(double left, double right) + { + return (left < right) || AreClose(left, right); + } +#endif + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Common/OpacityAnimator.cs b/Microsoft.Phone.Controls.Toolkit/Common/OpacityAnimator.cs new file mode 100644 index 0000000..f768c3d --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/OpacityAnimator.cs @@ -0,0 +1,137 @@ +// (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.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Windows.Media.Animation; + +namespace Microsoft.Phone.Controls +{ + /// + /// A utility for animating a horizontal translation value. + /// + internal sealed class OpacityAnimator + { + /// + /// Single static instance of a PropertyPath with string path "X". + /// + private static readonly PropertyPath OpacityPropertyPath = new PropertyPath("Opacity"); + + /// + /// The Storyboard instance for the animation. + /// + private readonly Storyboard _sbRunning = new Storyboard(); + + /// + /// The DoubleAnimation instance for a running animation. + /// + private readonly DoubleAnimation _daRunning = new DoubleAnimation(); + + /// + /// Flag to suppress the Completed event notification from happening. + /// + private bool _suppressChangeNotification; + + /// + /// A one-time action for the current GoTo statement only. Cleared if + /// GoTo is called before the action runs. + /// + private Action _oneTimeAction; + + /// + /// Initializes a new instance of the TransformAnimator class. + /// + /// Target element. + public OpacityAnimator(UIElement target) + { + Debug.Assert(target != null); + + _sbRunning.Completed += OnCompleted; + _sbRunning.Children.Add(_daRunning); + Storyboard.SetTarget(_daRunning, target); + Storyboard.SetTargetProperty(_daRunning, OpacityPropertyPath); + } + + /// + /// Targets a new opacity value over a specified duration. + /// + /// The target opacity value. + /// The duration for the animation. + public void GoTo(double targetOpacity, Duration duration) + { + GoTo(targetOpacity, duration, null, null); + } + + /// + /// Targets a new opacity value over a specified duration. + /// + /// The target opacity value. + /// The duration for the animation. + /// A completion Action. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Keeping existing implementation.")] + public void GoTo(double targetOpacity, Duration duration, Action completionAction) + { + GoTo(targetOpacity, duration, null, completionAction); + } + + /// + /// Targets a new opacity value over a specified duration. + /// + /// The target opacity value. + /// The duration for the animation. + /// An easing function value. + /// A completion Action. + public void GoTo(double targetOpacity, Duration duration, IEasingFunction easingFunction, Action completionAction) + { + _daRunning.To = targetOpacity; + _daRunning.Duration = duration; + _daRunning.EasingFunction = easingFunction; + _sbRunning.Begin(); + _suppressChangeNotification = true; + _sbRunning.SeekAlignedToLastTick(TimeSpan.Zero); + _oneTimeAction = completionAction; + } + + /// + /// Handles and passes on the Completed event. + /// + /// Event source. + /// Event arguments. + private void OnCompleted(object sender, EventArgs e) + { + Action action = _oneTimeAction; + if (action != null) + { + _oneTimeAction = null; + action(); + } + if (!_suppressChangeNotification) + { + _suppressChangeNotification = false; + } + } + + /// + /// Ensures and creates if needed the animator for an element. Will also + /// verify that a translate transform is present. + /// + /// The target element. + /// The animator reference. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Keeping existing implementation.")] + public static void EnsureAnimator(UIElement targetElement, ref OpacityAnimator animator) + { + if (animator == null) + { + animator = new OpacityAnimator(targetElement); + } + if (animator == null) + { + throw new InvalidOperationException("The animation system could not be prepared for the target element."); + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Common/PhoneHelper.cs b/Microsoft.Phone.Controls.Toolkit/Common/PhoneHelper.cs new file mode 100644 index 0000000..d7b72d3 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/PhoneHelper.cs @@ -0,0 +1,166 @@ +// (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.Collections; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; + +namespace Microsoft.Phone.Controls +{ + /// + /// Helper for the phone. + /// + /// + /// All orientations are condensed into portrait and landscape, where landscape includes . + /// + internal static class PhoneHelper + { + /// + /// The height of the SIP in landscape orientation. + /// + public const double SipLandscapeHeight = 259; + + /// + /// The height of the SIP in portrait orientation. + /// + public const double SipPortraitHeight = 339; + + /// + /// The height of the SIP text completion in either orientation. + /// + public const double SipTextCompletionHeight = 62; + + /// + /// Gets the current . + /// + /// The current . + /// true if the current was found; false otherwise. + public static bool TryGetPhoneApplicationFrame(out PhoneApplicationFrame phoneApplicationFrame) + { + phoneApplicationFrame = Application.Current.RootVisual as PhoneApplicationFrame; + return phoneApplicationFrame != null; + } + + /// + /// Determines whether a is oriented as portrait. + /// + /// The . + /// true if the is oriented as portrait; false otherwise. + public static bool IsPortrait(this PhoneApplicationFrame phoneApplicationFrame) + { + PageOrientation portrait = PageOrientation.Portrait | PageOrientation.PortraitDown | PageOrientation.PortraitUp; + return (portrait & phoneApplicationFrame.Orientation) == phoneApplicationFrame.Orientation; + } + + /// + /// Gets the correct width of a in either orientation. + /// + /// The . + /// The width. + public static double GetUsefulWidth(this PhoneApplicationFrame phoneApplicationFrame) + { + return phoneApplicationFrame.IsPortrait() ? phoneApplicationFrame.ActualWidth : phoneApplicationFrame.ActualHeight; + } + + /// + /// Gets the correct height of a in either orientation. + /// + /// The . + /// The height. + public static double GetUsefulHeight(this PhoneApplicationFrame phoneApplicationFrame) + { + return IsPortrait(phoneApplicationFrame) ? phoneApplicationFrame.ActualHeight : phoneApplicationFrame.ActualWidth; + } + + /// + /// Gets the correct of a . + /// + /// The . + /// The . + public static Size GetUsefulSize(this PhoneApplicationFrame phoneApplicationFrame) + { + return new Size(phoneApplicationFrame.GetUsefulWidth(), phoneApplicationFrame.GetUsefulHeight()); + } + + /// + /// Gets the focused , if there is one. + /// + /// The . + /// true if there is a focused ; false otherwise. + private static bool TryGetFocusedTextBox(out TextBox textBox) + { + textBox = FocusManager.GetFocusedElement() as TextBox; + return textBox != null; + } + + /// + /// Determines whether the SIP is shown. + /// + /// true if the SIP is shown; false otherwise. + public static bool IsSipShown() + { + TextBox textBox; + return TryGetFocusedTextBox(out textBox); + } + + /// + /// Determines whether the would show the SIP text completion. + /// + /// The . + /// true if the woudl show the SIP text completion; false otherwise. + public static bool IsSipTextCompletionShown(this TextBox textBox) + { + if (textBox.InputScope == null) + { + return false; + } + IList inputScopeNames = textBox.InputScope.Names; + foreach (InputScopeName inputScopeName in inputScopeNames) + { + switch (inputScopeName.NameValue) + { + case InputScopeNameValue.Text: + case InputScopeNameValue.Chat: + return true; + } + } + return false; + } + + /// + /// Gets the covered by the SIP when it is shown. + /// + /// The . + /// The . + public static Size GetSipCoveredSize(this PhoneApplicationFrame phoneApplicationFrame) + { + if (!IsSipShown()) + { + return new Size(0, 0); + } + double width = phoneApplicationFrame.GetUsefulWidth(); + double height = phoneApplicationFrame.IsPortrait() ? SipPortraitHeight : SipLandscapeHeight; + TextBox textBox; + if (TryGetFocusedTextBox(out textBox) && textBox.IsSipTextCompletionShown()) + { + height += SipTextCompletionHeight; + } + return new Size(width, height); + } + + /// + /// Gets the uncovered by the SIP when it is shown. + /// + /// The . + /// The . + public static Size GetSipUncoveredSize(this PhoneApplicationFrame phoneApplicationFrame) + { + double width = phoneApplicationFrame.GetUsefulWidth(); + double height = phoneApplicationFrame.GetUsefulHeight() - phoneApplicationFrame.GetSipCoveredSize().Height; + return new Size(width, height); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Common/PhysicsConstants.cs b/Microsoft.Phone.Controls.Toolkit/Common/PhysicsConstants.cs new file mode 100644 index 0000000..19d6bc2 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/PhysicsConstants.cs @@ -0,0 +1,158 @@ +// (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.Windows; +using System.Windows.Media.Animation; + +namespace Microsoft.Phone.Controls +{ + internal static class MotionParameters + { + public static double MaximumSpeed { get { return 4000.0; } } + public static double ParkingSpeed { get { return 80.0; } } + public static double Friction { get { return 0.2; } } + } + + internal static class PhysicsConstants + { + internal static double GetStopTime(Point initialVelocity) + { + // We need to cap the velocity's magnitude at the maximum speed in order not to have an unbounded scrolling velocity. + double initialVelocityMagnitude = + Math.Min( + Math.Sqrt(initialVelocity.X * initialVelocity.X + initialVelocity.Y * initialVelocity.Y), + MotionParameters.MaximumSpeed); + + // The formula is + // + // t_max = ln(gamma / |v_0|) / ln mu + // + // where t_max is the stop time, gamma is the parking speed + // (that is to say, the speed at which we stop the animation), + // v_0 is the initial velocity, and mu is the friction coefficient. + // + // This is derived by solving the below equation for velocity + // + // v = v_0 mu^t + // + // for t when |v| = gamma. + // + // This parking speed is necessary because the equation for velocity + // will only asymptotically trend towards 0; it will never reach it. + // + // If the parking speed is greater than the initial velocity, we just stop immediately. + // + if (MotionParameters.ParkingSpeed >= initialVelocityMagnitude) + { + return 0; + } + else + { + return Math.Log(MotionParameters.ParkingSpeed / initialVelocityMagnitude) / Math.Log(MotionParameters.Friction); + } + } + + internal static Point GetStopPoint(Point initialVelocity) + { + // We need to cap the velocity's magnitude at the maximum speed in order not to have an unbounded scrolling velocity. + double initialVelocityMagnitude = Math.Sqrt(initialVelocity.X * initialVelocity.X + initialVelocity.Y * initialVelocity.Y); + Point cappedInitialVelocity = initialVelocity; + + if (initialVelocityMagnitude > MotionParameters.MaximumSpeed && initialVelocityMagnitude > 0) + { + // We want to cap the magnitude, so multiplying each directional value by + // the ratio between the maximum speed and the current magnitude accomplishes this. + cappedInitialVelocity.X *= MotionParameters.MaximumSpeed / initialVelocityMagnitude; + cappedInitialVelocity.Y *= MotionParameters.MaximumSpeed / initialVelocityMagnitude; + } + + // The formula is + // + // v = v_0 mu^t + // + // where v is the velocity at time t, v_0 is the initial velocity, + // and mu is the friction coefficient. + // + // To find the distance reached after a certain amount of time, + // we integrate + // + // v dt between 0 and t (t being the current time) + // + // to get + // + // r = v_0 (mu^t - 1) / ln mu + // + // (mu^t - 1) / ln mu is a scalar value, so we only need + // to calculate it once. + // + double initialVelocityCoefficient = (Math.Pow(MotionParameters.Friction, GetStopTime(cappedInitialVelocity)) - 1) / Math.Log(MotionParameters.Friction); + + return new Point( + cappedInitialVelocity.X * initialVelocityCoefficient, + cappedInitialVelocity.Y * initialVelocityCoefficient); + } + + internal static IEasingFunction GetEasingFunction(double stopTime) + { + // From above, we have the equation of position + // + // r = v_0 (mu^t - 1) / ln mu + // + // IEasingFunction.Ease() is a method that accepts + // a normalized time as a parameter + // (that is, a number between 0.0 and 1.0 such that + // 0.0 represents the beginning of the animation duration and + // 1.0 represents the end of the animation duration), + // and which returns a normalized progress + // (that is, a number between 0.0 and 1.0 such that + // 0.0 represents no progress along the animation and + // 1.0 represents full progress along the animation). + // + // In order to get the above equation of position + // to work as an easing function, we need two things: + // to normalize the LHS such that it varies between 0.0 and 1.0 + // (corresponding to its initial position and its final position, respectively), + // and to have it accept as a parameter on the RHS a normalized time, + // rather than an actual time. + // + // First, to get a normalized LHS, we divide |r| by |r_max| (the stop distance as above) + // to get the normalized position r_n: + // + // |r| / |r_max| = r_n = (mu^t - 1) / (mu^t_max- 1) + // + // Now we note that + // + // t = t_n t_max + // + // where t_max is the stop time as above and where t_n is the normalized time + // to get + // + // r_n = (mu^(t_n t_max) - 1) / (mu^t_max- 1) + // + // Finally, we can take advantage of the fact that + // + // x^y = e^(y ln x) + // + // where e is Euler's number to put this in the form of + // + // r_n = (e^(t_n t_max ln mu) - 1) / (e^(t_max ln mu) - 1) + // + // and if we define a = t_max ln mu, then we have + // + // r_n = (e^(a t_n) - 1) / (e^a - 1) + // + // which is precisely the form of the exponential easing function. + // So, we can use an exponential easing function here with its + // Exponent property set to t_max ln mu, and it will get us what we want. + // + ExponentialEase ee = new ExponentialEase(); + ee.Exponent = stopTime * Math.Log(MotionParameters.Friction); + ee.EasingMode = EasingMode.EaseIn; + return ee; + } + } +} + diff --git a/Microsoft.Phone.Controls.Toolkit/Common/RoutedPropertyChangingEventArgs.cs b/Microsoft.Phone.Controls.Toolkit/Common/RoutedPropertyChangingEventArgs.cs new file mode 100644 index 0000000..0e22d5b --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/RoutedPropertyChangingEventArgs.cs @@ -0,0 +1,146 @@ +// (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.Windows; + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// Provides event data for various routed events that track property values + /// changing. Typically the events denote a cancellable action. + /// + /// + /// The type of the value for the dependency property that is changing. + /// + /// Preview + public class RoutedPropertyChangingEventArgs : RoutedEventArgs + { + /// + /// Gets the + /// identifier for the property that is changing. + /// + /// + /// The identifier + /// for the property that is changing. + /// + public DependencyProperty Property { get; private set; } + + /// + /// Gets a value that reports the previous value of the changing + /// property. + /// + /// + /// The previous value of the changing property. + /// + public T OldValue { get; private set; } + + /// + /// Gets or sets a value that reports the new value of the changing + /// property, assuming that the property change is not cancelled. + /// + /// + /// The new value of the changing property. + /// + public T NewValue { get; set; } + + /// + /// Gets a value indicating whether the property change that originated + /// the RoutedPropertyChanging event is cancellable. + /// + /// + /// True if the property change is cancellable. false if the property + /// change is not cancellable. + /// + public bool IsCancelable { get; private set; } + + /// + /// Gets or sets a value indicating whether the property change that + /// originated the RoutedPropertyChanging event should be cancelled. + /// + /// + /// True to cancel the property change; this resets the property to + /// . + /// false to not cancel the property change; the value changes to + /// . + /// + /// + /// Attempted to cancel in an instance where + /// + /// is false. + /// + public bool Cancel + { + get { return _cancel; } + set + { + if (IsCancelable) + { + _cancel = value; + } + else if (value) + { + throw new InvalidOperationException(Properties.Resources.RoutedPropertyChangingEventArgs_CancelSet_InvalidOperation); + } + } + } + + /// + /// Private member variable for Cancel property. + /// + private bool _cancel; + + /// + /// Gets or sets a value indicating whether internal value coercion is + /// acting on the property change that originated the + /// RoutedPropertyChanging event. + /// + /// + /// True if coercion is active. false if coercion is not active. + /// + /// + /// This is a total hack to work around the class hierarchy for Value + /// coercion in NumericUpDown. + /// + public bool InCoercion { get; set; } + + /// + /// Initializes a new instance of the + /// + /// class. + /// + /// + /// The identifier + /// for the property that is changing. + /// + /// The previous value of the property. + /// + /// The new value of the property, assuming that the property change is + /// not cancelled. + /// + /// + /// True if the property change is cancellable by setting + /// + /// to true in event handling. false if the property change is not + /// cancellable. + /// + public RoutedPropertyChangingEventArgs( + DependencyProperty property, + T oldValue, + T newValue, + bool isCancelable) + { + Property = property; + OldValue = oldValue; + NewValue = newValue; + IsCancelable = isCancelable; + Cancel = false; + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Common/RoutedPropertyChangingEventHandler.cs b/Microsoft.Phone.Controls.Toolkit/Common/RoutedPropertyChangingEventHandler.cs new file mode 100644 index 0000000..e1de1bb --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/RoutedPropertyChangingEventHandler.cs @@ -0,0 +1,26 @@ +// (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. + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// Represents methods that handle various routed events that track property + /// values changing. Typically the events denote a cancellable action. + /// + /// + /// The type of the value for the dependency property that is changing. + /// + /// + /// The object where the initiating property is changing. + /// + /// Event data for the event. + /// Preview + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Design", "CA1003:UseGenericEventHandlerInstances", Justification = "To match pattern of RoutedPropertyChangedEventHandler")] + public delegate void RoutedPropertyChangingEventHandler(object sender, RoutedPropertyChangingEventArgs e); +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Common/SafeRaise.cs b/Microsoft.Phone.Controls.Toolkit/Common/SafeRaise.cs new file mode 100644 index 0000000..4f2a84b --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/SafeRaise.cs @@ -0,0 +1,93 @@ +// (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.Diagnostics.CodeAnalysis; + +namespace Microsoft.Phone.Controls +{ + /// + /// A helper class for raising events safely. + /// + internal static class SafeRaise + { + /// + /// Raises an event in a thread-safe manner, also does the null check. + /// + /// The event to raise. + /// The event sender. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Keeping existing implementation.")] + public static void Raise(EventHandler eventToRaise, object sender) + { + if (eventToRaise != null) + { + eventToRaise(sender, EventArgs.Empty); + } + } + + /// + /// Raises an event in a thread-safe manner, also does the null check. + /// + /// The event to raise. + /// The event sender. + public static void Raise(EventHandler eventToRaise, object sender) + { + Raise(eventToRaise, sender, EventArgs.Empty); + } + + /// + /// Raises an event in a thread-safe manner, also does the null check. + /// + /// The event args type. + /// The event to raise. + /// The event sender. + /// The event args. + public static void Raise(EventHandler eventToRaise, object sender, T args) where T : EventArgs + { + if (eventToRaise != null) + { + eventToRaise(sender, args); + } + } + + // Lazy event args creation example: + // + // public class MyEventArgs : EventArgs + // { + // public MyEventArgs(int x) { X = x; } + // public int X { get; set; } + // } + // + // event EventHandler Foo; + // + // public void Bar() + // { + // int y = 2; + // Raise(Foo, null, () => { return new MyEventArgs(y); }); + // } + + /// + /// This is a method that returns event args, used for lazy creation. + /// + /// The event type. + /// + public delegate T GetEventArgs() where T : EventArgs; + + /// + /// Raise an event in a thread-safe manner, with the required null check. Lazily creates event args. + /// + /// The event args type. + /// The event to raise. + /// The event sender. + /// The delegate to return the event args if needed. + public static void Raise(EventHandler eventToRaise, object sender, GetEventArgs getEventArgs) where T : EventArgs + { + if (eventToRaise != null) + { + eventToRaise(sender, getEventArgs()); + } + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/Common/TemplatedVisualTreeExtensions.cs b/Microsoft.Phone.Controls.Toolkit/Common/TemplatedVisualTreeExtensions.cs new file mode 100644 index 0000000..9d1e691 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/TemplatedVisualTreeExtensions.cs @@ -0,0 +1,107 @@ +// (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.Collections.Generic; +using System.Diagnostics; +using System.Linq; + +namespace System.Windows.Controls +{ + /// + /// A static class providing methods for working with the visual tree using generics. + /// + public static class TemplatedVisualTreeExtensions + { + + #region GetFirstLogicalChildByType(...) + /// + /// Retrieves the first logical child of a specified type using a + /// breadth-first search. A visual element is assumed to be a logical + /// child of another visual element if they are in the same namescope. + /// For performance reasons this method manually manages the queue + /// instead of using recursion. + /// + /// The parent framework element. + /// Specifies whether to apply templates on the traversed framework elements + /// The first logical child of the framework element of the specified type. + internal static T GetFirstLogicalChildByType(this FrameworkElement parent, bool applyTemplates) + where T : FrameworkElement + { + Debug.Assert(parent != null, "The parent cannot be null."); + + Queue queue = new Queue(); + queue.Enqueue(parent); + + while (queue.Count > 0) + { + FrameworkElement element = queue.Dequeue(); + var elementAsControl = element as Control; + if (applyTemplates && elementAsControl != null) + { + elementAsControl.ApplyTemplate(); + } + + if (element is T && element != parent) + { + return (T)element; + } + + foreach (FrameworkElement visualChild in element.GetVisualChildren().OfType()) + { + queue.Enqueue(visualChild); + } + } + + return null; + } + #endregion + + #region GetLogicalChildrenByType(...) + /// + /// Retrieves all the logical children of a specified type using a + /// breadth-first search. A visual element is assumed to be a logical + /// child of another visual element if they are in the same namescope. + /// For performance reasons this method manually manages the queue + /// instead of using recursion. + /// + /// The parent framework element. + /// Specifies whether to apply templates on the traversed framework elements + /// The logical children of the framework element of the specified type. + internal static IEnumerable GetLogicalChildrenByType(this FrameworkElement parent, bool applyTemplates) + where T : FrameworkElement + { + Debug.Assert(parent != null, "The parent cannot be null."); + + if (applyTemplates && parent is Control) + { + ((Control)parent).ApplyTemplate(); + } + + Queue queue = + new Queue(parent.GetVisualChildren().OfType()); + + while (queue.Count > 0) + { + FrameworkElement element = queue.Dequeue(); + if (applyTemplates && element is Control) + { + ((Control)element).ApplyTemplate(); + } + + if (element is T) + { + yield return (T)element; + } + + foreach (FrameworkElement visualChild in element.GetVisualChildren().OfType()) + { + queue.Enqueue(visualChild); + } + } + } + #endregion + + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/Common/TimeTypeConverter.cs b/Microsoft.Phone.Controls.Toolkit/Common/TimeTypeConverter.cs new file mode 100644 index 0000000..e65ad0b --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/TimeTypeConverter.cs @@ -0,0 +1,192 @@ +// (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.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; + +namespace System.Windows.Controls +{ + /// + /// Allows time to be set from xaml. + /// + /// Preview + /// This converter is used by xaml and thus uses the + /// English formats. + public class TimeTypeConverter : TypeConverter + { + /// + /// BackingField for the TimeFormats being used. + /// + private static readonly string[] _timeFormats = new[] + { + "h:mm tt", + "h:mm:ss tt", + "HH:mm", + "HH:mm:ss", + "H:mm", + "H:mm:ss", + }; + + /// + /// BackingField for the DateFormats being used. + /// + private static readonly string[] _dateFormats = new[] + { + "M/d/yyyy", + }; + + /// + /// Determines whether this instance can convert from + /// the specified type descriptor context. + /// + /// The type descriptor context. + /// Type of the source. + /// + /// True if this instance can convert from the specified type + /// descriptor context; otherwise, false. + /// + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")] + public override bool CanConvertFrom(ITypeDescriptorContext typeDescriptorContext, Type sourceType) + { + return Type.GetTypeCode(sourceType) == TypeCode.String; + } + + /// + /// Determines whether this instance can convert to the specified + /// type descriptor context. + /// + /// The type descriptor context. + /// Type of the destination. + /// + /// True if this instance can convert to the specified type + /// descriptor context; otherwise, false. + /// + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")] + public override bool CanConvertTo(ITypeDescriptorContext typeDescriptorContext, Type destinationType) + { + return Type.GetTypeCode(destinationType) == TypeCode.String ? true : TypeConverters.CanConvertTo(destinationType); + } + + /// + /// Converts instances of other data types into instances of DateTime that + /// represent a time. + /// + /// + /// The type descriptor context. + /// + /// The culture used to convert. This culture + /// is not used during conversion, but a specific set of formats is used. + /// + /// The string being converted to the DateTime. + /// + /// + /// A DateTime that is the value of the conversion. + /// + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")] + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "1#", Justification = "Compat with WPF.")] + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "2#", Justification = "Compat with WPF.")] + public override object ConvertFrom(ITypeDescriptorContext typeDescriptorContext, CultureInfo cultureInfo, object source) + { + if (source == null) + { + return null; + } + + string text = source as string; + + if (text == null) + { + string invalidCastMessage = string.Format( + CultureInfo.CurrentCulture, + Microsoft.Phone.Controls.Properties.Resources.TypeConverters_Convert_CannotConvert, + GetType().Name, + source, + typeof(DateTime).Name); + throw new InvalidCastException(invalidCastMessage); + } + + if (string.IsNullOrEmpty(text)) + { + return null; + } + + DateTime result; + // test using times + foreach (string format in _timeFormats) + { + if (DateTime.TryParseExact(text, format, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)) + { + return DateTime.Now.Date.Add(result.TimeOfDay); + } + } + + // test using combinations of date and time + foreach (string dateFormat in _dateFormats) + { + foreach (string timeFormat in _timeFormats) + { + if (DateTime.TryParseExact(text, String.Format(CultureInfo.InvariantCulture, "{0} {1}", dateFormat, timeFormat), CultureInfo.InvariantCulture, DateTimeStyles.None, out result)) + { + return result; + } + } + } + + // test using date only + foreach (string dateFormat in _dateFormats) + { + if (DateTime.TryParseExact(text, dateFormat, CultureInfo.InvariantCulture, DateTimeStyles.NoCurrentDateDefault, out result)) + { + return result; + } + } + + string invalidFormatMessage = string.Format( + CultureInfo.CurrentCulture, + Microsoft.Phone.Controls.Properties.Resources.TypeConverters_Convert_CannotConvert, + GetType().Name, + text, + typeof(DateTime).Name); + throw new FormatException(invalidFormatMessage); + } + + /// + /// Converts a DateTime into a string. + /// + /// + /// The type descriptor context. + /// + /// The culture used to convert. + /// + /// The value that is being converted to a specified type. + /// + /// + /// The type to convert the value to. + /// + /// + /// The value of the conversion to the specified type. + /// + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")] + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "1#", Justification = "Compat with WPF.")] + public override object ConvertTo(ITypeDescriptorContext typeDescriptorContext, CultureInfo cultureInfo, object value, Type destinationType) + { + if (destinationType == typeof(string)) + { + if (value == null) + { + return String.Empty; + } + else if (value is DateTime) + { + DateTime time = (DateTime)value; + return time.ToString("HH:mm:ss", new CultureInfo("en-US")); + } + } + + return TypeConverters.ConvertTo(this, value, destinationType); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Common/TransformAnimator.cs b/Microsoft.Phone.Controls.Toolkit/Common/TransformAnimator.cs new file mode 100644 index 0000000..0ca94df --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/TransformAnimator.cs @@ -0,0 +1,217 @@ +// (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.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows; +using System.Windows.Media; +using System.Windows.Media.Animation; + +namespace Microsoft.Phone.Controls +{ + /// + /// A utility for animating a horizontal translation value. + /// + internal sealed class TransformAnimator + { + /// + /// Single static instance of a PropertyPath with string path "X". + /// + private static readonly PropertyPath TranslateXPropertyPath = new PropertyPath("X"); + + /// + /// The Storyboard instance for the animation. + /// + private readonly Storyboard _sbRunning = new Storyboard(); + + /// + /// The DoubleAnimation instance for a running animation. + /// + private readonly DoubleAnimation _daRunning = new DoubleAnimation(); + + /// + /// The target translate transform instance. + /// + private TranslateTransform _transform; + + /// + /// A one-time action for the current GoTo statement only. Cleared if + /// GoTo is called before the action runs. + /// + private Action _oneTimeAction; + + /// + /// Initializes a new instance of the TransformAnimator class. + /// + /// TranslateTransform instance. + public TransformAnimator(TranslateTransform translateTransform) + { + Debug.Assert(translateTransform != null); + _transform = translateTransform; + + _sbRunning.Completed += OnCompleted; + _sbRunning.Children.Add(_daRunning); + Storyboard.SetTarget(_daRunning, _transform); + Storyboard.SetTargetProperty(_daRunning, TranslateXPropertyPath); + } + + /// + /// Gets the current offset value from the translate transform object. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Keeping the public API available.")] + public double CurrentOffset + { + get { return _transform.X; } + } + + /// + /// Targets a new horizontal offset over a specified duration. + /// + /// The target offset value. + /// The duration for the animation. + public void GoTo(double targetOffset, Duration duration) + { + GoTo(targetOffset, duration, null, null); + } + + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Keeping the public API available.")] + public void GoTo(double targetOffset, Duration duration, Action completionAction) + { + GoTo(targetOffset, duration, null, completionAction); + } + + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Keeping the public API available.")] + public void GoTo(double targetOffset, Duration duration, IEasingFunction easingFunction) + { + GoTo(targetOffset, duration, easingFunction, null); + } + + public void GoTo(double targetOffset, Duration duration, IEasingFunction easingFunction, Action completionAction) + { + _daRunning.To = targetOffset; + _daRunning.Duration = duration; + _daRunning.EasingFunction = easingFunction; + _sbRunning.Begin(); + _sbRunning.SeekAlignedToLastTick(TimeSpan.Zero); + _oneTimeAction = completionAction; + } + + /// + /// Updates the easing function of the double animation. + /// + /// The easing funciton, if any, to use. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Keeping existing implementation.")] + public void UpdateEasingFunction(IEasingFunction ease) + { + if (_daRunning != null && _daRunning.EasingFunction != ease) + { + _daRunning.EasingFunction = ease; + } + } + + /// + /// Immediately updates the duration of the running double animation. + /// + /// The new duration value to use. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Keeping the public API available.")] + public void UpdateDuration(Duration duration) + { + if (_daRunning != null) + { + _daRunning.Duration = duration; + } + } + + /// + /// Handles animation completed + /// + /// Event source. + /// Event arguments. + private void OnCompleted(object sender, EventArgs e) + { + // Make sure the action is called when the animation is Stopped + // The complete event is triggered when it changes to Filling + // This is also called before the state has been changed so the state + // is still Active when switching to Filling + Action action = _oneTimeAction; + if (action != null && _sbRunning.GetCurrentState() != ClockState.Active) + { + _oneTimeAction = null; + action(); + } + } + + /// + /// Ensures and creates if needed the animator for an element. Will also + /// verify that a translate transform is present. + /// + /// The target element. + /// The animator reference. + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Keeping the public API available.")] + public static void EnsureAnimator(FrameworkElement targetElement, ref TransformAnimator animator) + { + if (animator == null) + { + TranslateTransform transform = TransformAnimator.GetTranslateTransform(targetElement); + if (transform != null) + { + animator = new TransformAnimator(transform); + } + } + if (animator == null) + { + throw new InvalidOperationException("The animation system could not be prepared for the target element."); + } + } + + /// + /// Find a translate transform for the container or create one. + /// + /// The container. + /// Returns the TranslateTransform reference. + public static TranslateTransform GetTranslateTransform(UIElement container) + { + if (container == null) + { + throw new ArgumentNullException("container"); + } + + TranslateTransform transform = container.RenderTransform as TranslateTransform; + if (transform == null) + { + if (container.RenderTransform == null) + { + transform = new TranslateTransform(); + container.RenderTransform = transform; + } + else if (container.RenderTransform is TransformGroup) + { + TransformGroup g = container.RenderTransform as TransformGroup; + transform = (from t in g.Children + where t is TranslateTransform + select (TranslateTransform)t).FirstOrDefault(); + if (transform == null) + { + transform = new TranslateTransform(); + g.Children.Add(transform); + } + } + else + { + TransformGroup tg = new TransformGroup(); + var existing = container.RenderTransform; + container.RenderTransform = null; + tg.Children.Add(existing); + transform = new TranslateTransform(); + tg.Children.Add(transform); + container.RenderTransform = tg; + } + } + return transform; + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Common/Tuple.cs b/Microsoft.Phone.Controls.Toolkit/Common/Tuple.cs new file mode 100644 index 0000000..5ffac5d --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/Tuple.cs @@ -0,0 +1,36 @@ +// (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. + +namespace System +{ + /// + /// Represents a 2-tuple, or pair. + /// + /// The type of the tuple's first component. + /// The type of the tuple's second component. + internal class Tuple + { + /// + /// Gets the value of the current Tuple(T1, T2) object's first component. + /// + public T1 Item1 { get; private set; } + + /// + /// Gets the value of the current Tuple(T1, T2) object's second component. + /// + public T2 Item2 { get; private set; } + + /// + /// Initializes a new instance of the Tuple(T1, T2) class. + /// + /// The value of the tuple's first component. + /// The value of the tuple's second component. + public Tuple(T1 item1, T2 item2) + { + Item1 = item1; + Item2 = item2; + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Common/TypeConverters.cs b/Microsoft.Phone.Controls.Toolkit/Common/TypeConverters.cs new file mode 100644 index 0000000..5808593 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/TypeConverters.cs @@ -0,0 +1,142 @@ +// (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.ComponentModel; +using System.Diagnostics; +using System.Globalization; + +namespace System.Windows.Controls +{ + /// + /// Common TypeConverter functionality. + /// + internal static partial class TypeConverters + { +#if !WINDOWS_PHONE + /// + /// Returns a value indicating whether this converter can convert an + /// object of the given type to an instance of the expected type. + /// + /// Expected type of the converter. + /// + /// The type of the source that is being evaluated for conversion. + /// + /// + /// A value indicating whether the converter can convert the provided + /// type. + /// + internal static bool CanConvertFrom(Type sourceType) + { + if (sourceType == null) + { + throw new ArgumentNullException("sourceType"); + } + return (sourceType == typeof(string)) || + typeof(T).IsAssignableFrom(sourceType); + } + + /// + /// Attempts to convert a specified object to an instance of the + /// expected type. + /// + /// Expected type of the converter. + /// TypeConverter instance. + /// The object being converted. + /// + /// The instance of the expected type created from the converted object. + /// + internal static object ConvertFrom(TypeConverter converter, object value) + { + Debug.Assert(converter != null, "converter should not be null!"); + + if (value is T) + { + // There's nothing to convert if it's already the correct type + return value; + } + else + { + // Otherwise throw an error + throw new NotSupportedException(string.Format( + CultureInfo.CurrentCulture, +#if WINDOWS_PHONE + Microsoft.Phone.Controls.Toolkit.Properties. +#else + Controls.Properties. +#endif + Resources.TypeConverters_ConvertFrom_CannotConvertFromType, + converter.GetType().Name, + value != null ? value.GetType().FullName : "(null)")); + } + } +#endif + + /// + /// Determines whether conversion is possible to a specified type. + /// + /// Expected type of the converter. + /// + /// Identifies the data type to evaluate for conversion. + /// + /// + /// A value indicating whether conversion is possible. + /// + internal static bool CanConvertTo(Type destinationType) + { + if (destinationType == null) + { + throw new ArgumentNullException("destinationType"); + } + return (destinationType == typeof(string)) || + destinationType.IsAssignableFrom(typeof(T)); + } + + /// + /// Attempts to convert a specified object to an instance of the + /// desired type. + /// + /// TypeConverter instance. + /// The object being converted. + /// + /// The type to convert the value to. + /// + /// + /// The value of the conversion to the specified type. + /// + internal static object ConvertTo(TypeConverter converter, object value, Type destinationType) + { + Debug.Assert(converter != null, "converter should not be null!"); + + if (destinationType == null) + { + throw new ArgumentNullException("destinationType"); + } + + // Just return the value if it is already an instance of the + // destination type + if (value == null && !destinationType.IsValueType) + { + return null; + } + else if (value != null && destinationType.IsAssignableFrom(value.GetType())) + { + return value; + } + + // Otherwise throw an error + throw new NotSupportedException(string.Format( + CultureInfo.CurrentCulture, +#if WINDOWS_PHONE + Microsoft.Phone.Controls.Properties. +#else + Properties. +#endif + Resources.TypeConverters_Convert_CannotConvert, + converter.GetType().Name, + value != null ? value.GetType().FullName : "(null)", + destinationType.GetType().Name)); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Common/VisualStates.cs b/Microsoft.Phone.Controls.Toolkit/Common/VisualStates.cs new file mode 100644 index 0000000..d813666 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/VisualStates.cs @@ -0,0 +1,414 @@ +// (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.Diagnostics; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// Names and helpers for visual states in the controls. + /// + internal static class VisualStates + { + #region GroupCommon + /// + /// Common state group. + /// + public const string GroupCommon = "CommonStates"; + + /// + /// Normal state of the Common state group. + /// + public const string StateNormal = "Normal"; + + /// + /// Normal state of the Common state group. + /// + public const string StateReadOnly = "ReadOnly"; + + /// + /// MouseOver state of the Common state group. + /// + public const string StateMouseOver = "MouseOver"; + + /// + /// Pressed state of the Common state group. + /// + public const string StatePressed = "Pressed"; + + /// + /// Disabled state of the Common state group. + /// + public const string StateDisabled = "Disabled"; + #endregion GroupCommon + + #region GroupFocus + /// + /// Focus state group. + /// + public const string GroupFocus = "FocusStates"; + + /// + /// Unfocused state of the Focus state group. + /// + public const string StateUnfocused = "Unfocused"; + + /// + /// Focused state of the Focus state group. + /// + public const string StateFocused = "Focused"; + #endregion GroupFocus + + #region GroupSelection + /// + /// Selection state group. + /// + public const string GroupSelection = "SelectionStates"; + + /// + /// Selected state of the Selection state group. + /// + public const string StateSelected = "Selected"; + + /// + /// Unselected state of the Selection state group. + /// + public const string StateUnselected = "Unselected"; + + /// + /// Selected inactive state of the Selection state group. + /// + public const string StateSelectedInactive = "SelectedInactive"; + #endregion GroupSelection + + #region GroupExpansion + /// + /// Expansion state group. + /// + public const string GroupExpansion = "ExpansionStates"; + + /// + /// Expanded state of the Expansion state group. + /// + public const string StateExpanded = "Expanded"; + + /// + /// Collapsed state of the Expansion state group. + /// + public const string StateCollapsed = "Collapsed"; + #endregion GroupExpansion + + #region GroupPopup + /// + /// Popup state group. + /// + public const string GroupPopup = "PopupStates"; + + /// + /// Opened state of the Popup state group. + /// + public const string StatePopupOpened = "PopupOpened"; + + /// + /// Closed state of the Popup state group. + /// + public const string StatePopupClosed = "PopupClosed"; + #endregion + + #region GroupValidation + /// + /// ValidationStates state group. + /// + public const string GroupValidation = "ValidationStates"; + + /// + /// The valid state for the ValidationStates group. + /// + public const string StateValid = "Valid"; + + /// + /// Invalid, focused state for the ValidationStates group. + /// + public const string StateInvalidFocused = "InvalidFocused"; + + /// + /// Invalid, unfocused state for the ValidationStates group. + /// + public const string StateInvalidUnfocused = "InvalidUnfocused"; + #endregion + + #region GroupExpandDirection + /// + /// ExpandDirection state group. + /// + public const string GroupExpandDirection = "ExpandDirectionStates"; + + /// + /// Down expand direction state of ExpandDirection state group. + /// + public const string StateExpandDown = "ExpandDown"; + + /// + /// Up expand direction state of ExpandDirection state group. + /// + public const string StateExpandUp = "ExpandUp"; + + /// + /// Left expand direction state of ExpandDirection state group. + /// + public const string StateExpandLeft = "ExpandLeft"; + + /// + /// Right expand direction state of ExpandDirection state group. + /// + public const string StateExpandRight = "ExpandRight"; + #endregion + + #region GroupHasItems + /// + /// HasItems state group. + /// + public const string GroupHasItems = "HasItemsStates"; + + /// + /// HasItems state of the HasItems state group. + /// + public const string StateHasItems = "HasItems"; + + /// + /// NoItems state of the HasItems state group. + /// + public const string StateNoItems = "NoItems"; + #endregion GroupHasItems + + #region GroupIncrease + /// + /// Increment state group. + /// + public const string GroupIncrease = "IncreaseStates"; + + /// + /// State enabled for increment group. + /// + public const string StateIncreaseEnabled = "IncreaseEnabled"; + + /// + /// State disabled for increment group. + /// + public const string StateIncreaseDisabled = "IncreaseDisabled"; + #endregion GroupIncrease + + #region GroupDecrease + /// + /// Decrement state group. + /// + public const string GroupDecrease = "DecreaseStates"; + + /// + /// State enabled for decrement group. + /// + public const string StateDecreaseEnabled = "DecreaseEnabled"; + + /// + /// State disabled for decrement group. + /// + public const string StateDecreaseDisabled = "DecreaseDisabled"; + #endregion GroupDecrease + + #region GroupIteractionMode + /// + /// InteractionMode state group. + /// + public const string GroupInteractionMode = "InteractionModeStates"; + + /// + /// Edit of the DisplayMode state group. + /// + public const string StateEdit = "Edit"; + + /// + /// Display of the DisplayMode state group. + /// + public const string StateDisplay = "Display"; + #endregion GroupIteractionMode + + #region GroupLocked + /// + /// DisplayMode state group. + /// + public const string GroupLocked = "LockedStates"; + + /// + /// Edit of the DisplayMode state group. + /// + public const string StateLocked = "Locked"; + + /// + /// Display of the DisplayMode state group. + /// + public const string StateUnlocked = "Unlocked"; + #endregion GroupLocked + + #region GroupActive + /// + /// Active state. + /// + public const string StateActive = "Active"; + + /// + /// Inactive state. + /// + public const string StateInactive = "Inactive"; + + /// + /// Active state group. + /// + public const string GroupActive = "ActiveStates"; + #endregion GroupActive + + #region GroupWatermark + /// + /// Non-watermarked state. + /// + public const string StateUnwatermarked = "Unwatermarked"; + + /// + /// Watermarked state. + /// + public const string StateWatermarked = "Watermarked"; + + /// + /// Watermark state group. + /// + public const string GroupWatermark = "WatermarkStates"; + #endregion GroupWatermark + + #region GroupCalendarButtonFocus + /// + /// Unfocused state for Calendar Buttons. + /// + public const string StateCalendarButtonUnfocused = "CalendarButtonUnfocused"; + + /// + /// Focused state for Calendar Buttons. + /// + public const string StateCalendarButtonFocused = "CalendarButtonFocused"; + + /// + /// CalendarButtons Focus state group. + /// + public const string GroupCalendarButtonFocus = "CalendarButtonFocusStates"; + #endregion GroupCalendarButtonFocus + + #region GroupBusyStatus + /// + /// Busy state for BusyIndicator. + /// + public const string StateBusy = "Busy"; + + /// + /// Idle state for BusyIndicator. + /// + public const string StateIdle = "Idle"; + + /// + /// Busyness group name. + /// + public const string GroupBusyStatus = "BusyStatusStates"; + #endregion + + #region GroupVisibility + /// + /// Visible state name for BusyIndicator. + /// + public const string StateVisible = "Visible"; + + /// + /// Hidden state name for BusyIndicator. + /// + public const string StateHidden = "Hidden"; + + /// + /// BusyDisplay group. + /// + public const string GroupVisibility = "VisibilityStates"; + #endregion + + /// + /// Use VisualStateManager to change the visual state of the control. + /// + /// + /// Control whose visual state is being changed. + /// + /// + /// A value indicating whether to use transitions when updating the + /// visual state, or to snap directly to the new visual state. + /// + /// + /// Ordered list of state names and fallback states to transition into. + /// Only the first state to be found will be used. + /// + public static void GoToState(Control control, bool useTransitions, params string[] stateNames) + { + Debug.Assert(control != null, "control should not be null!"); + Debug.Assert(stateNames != null, "stateNames should not be null!"); + Debug.Assert(stateNames.Length > 0, "stateNames should not be empty!"); + + foreach (string name in stateNames) + { + if (VisualStateManager.GoToState(control, name, useTransitions)) + { + break; + } + } + } + + /// + /// Gets the implementation root of the Control. + /// + /// The DependencyObject. + /// + /// Implements Silverlight's corresponding internal property on Control. + /// + /// Returns the implementation root or null. + public static FrameworkElement GetImplementationRoot(DependencyObject dependencyObject) + { + Debug.Assert(dependencyObject != null, "DependencyObject should not be null."); + return (1 == VisualTreeHelper.GetChildrenCount(dependencyObject)) ? + VisualTreeHelper.GetChild(dependencyObject, 0) as FrameworkElement : + null; + } + + /// + /// This method tries to get the named VisualStateGroup for the + /// dependency object. The provided object's ImplementationRoot will be + /// looked up in this call. + /// + /// The dependency object. + /// The visual state group's name. + /// Returns null or the VisualStateGroup object. + public static VisualStateGroup TryGetVisualStateGroup(DependencyObject dependencyObject, string groupName) + { + FrameworkElement root = GetImplementationRoot(dependencyObject); + if (root == null) + { + return null; + } + + return VisualStateManager.GetVisualStateGroups(root) + .OfType() + .Where(group => string.CompareOrdinal(groupName, group.Name) == 0) + .FirstOrDefault(); + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/Common/VisualTreeExtensions.cs b/Microsoft.Phone.Controls.Toolkit/Common/VisualTreeExtensions.cs new file mode 100644 index 0000000..1d77a19 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/VisualTreeExtensions.cs @@ -0,0 +1,67 @@ +// (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.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Windows.Media; + +namespace System.Windows.Controls +{ + /// + /// A static class providing methods for working with the visual tree. + /// + internal static class VisualTreeExtensions + { + #region GetVisualChildren(...) + /// + /// Retrieves all the visual children of a framework element. + /// + /// The parent framework element. + /// The visual children of the framework element. + internal static IEnumerable GetVisualChildren(this DependencyObject parent) + { + Debug.Assert(parent != null, "The parent cannot be null."); + + int childCount = VisualTreeHelper.GetChildrenCount(parent); + for (int counter = 0; counter < childCount; counter++) + { + yield return VisualTreeHelper.GetChild(parent, counter); + } + } + #endregion + + #region GetLogicalChildrenBreadthFirst(...) + /// + /// Retrieves all the logical children of a framework element using a + /// breadth-first search. A visual element is assumed to be a logical + /// child of another visual element if they are in the same namescope. + /// For performance reasons this method manually manages the queue + /// instead of using recursion. + /// + /// The parent framework element. + /// The logical children of the framework element. + internal static IEnumerable GetLogicalChildrenBreadthFirst(this FrameworkElement parent) + { + Debug.Assert(parent != null, "The parent cannot be null."); + + Queue queue = + new Queue(parent.GetVisualChildren().OfType()); + + while (queue.Count > 0) + { + FrameworkElement element = queue.Dequeue(); + yield return element; + + foreach (FrameworkElement visualChild in element.GetVisualChildren().OfType()) + { + queue.Enqueue(visualChild); + } + } + } + #endregion + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Common/WeakEventListener.cs b/Microsoft.Phone.Controls.Toolkit/Common/WeakEventListener.cs new file mode 100644 index 0000000..570688c --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Common/WeakEventListener.cs @@ -0,0 +1,88 @@ +// (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.Diagnostics.CodeAnalysis; + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// Implements a weak event listener that allows the owner to be garbage + /// collected if its only remaining link is an event handler. + /// + /// Type of instance listening for the event. + /// Type of source for the event. + /// Type of event arguments for the event. + [SuppressMessage("Microsoft.Performance", "CA1812:AvoidUninstantiatedInternalClasses", Justification = "Used as link target in several projects.")] + internal class WeakEventListener where TInstance : class + { + /// + /// WeakReference to the instance listening for the event. + /// + private WeakReference _weakInstance; + + /// + /// Gets or sets the method to call when the event fires. + /// + public Action OnEventAction { get; set; } + + /// + /// Gets or sets the method to call when detaching from the event. + /// + public Action> OnDetachAction { get; set; } + + /// + /// Initializes a new instances of the WeakEventListener class. + /// + /// Instance subscribing to the event. + public WeakEventListener(TInstance instance) + { + if (null == instance) + { + throw new ArgumentNullException("instance"); + } + _weakInstance = new WeakReference(instance); + } + + /// + /// Handler for the subscribed event calls OnEventAction to handle it. + /// + /// Event source. + /// Event arguments. + public void OnEvent(TSource source, TEventArgs eventArgs) + { + TInstance target = (TInstance)_weakInstance.Target; + if (null != target) + { + // Call registered action + if (null != OnEventAction) + { + OnEventAction(target, source, eventArgs); + } + } + else + { + // Detach from event + Detach(); + } + } + + /// + /// Detaches from the subscribed event. + /// + public void Detach() + { + if (null != OnDetachAction) + { + OnDetachAction(this); + OnDetachAction = null; + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/ContextMenu/ContextMenu.cs b/Microsoft.Phone.Controls.Toolkit/ContextMenu/ContextMenu.cs new file mode 100644 index 0000000..dcbbda6 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/ContextMenu/ContextMenu.cs @@ -0,0 +1,1352 @@ +// (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.Generic; +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 System.Windows.Media.Imaging; +using System.Windows.Shapes; +using Microsoft.Phone.Controls.Primitives; +using Microsoft.Phone.Shell; + +namespace Microsoft.Phone.Controls +{ + /// + /// Represents a pop-up menu that enables a control to expose functionality that is specific to the context of the control. + /// + /// Preview + [TemplateVisualState(GroupName = VisibilityGroupName, Name = OpenVisibilityStateName)] + [TemplateVisualState(GroupName = VisibilityGroupName, Name = OpenReversedVisibilityStateName)] + [TemplateVisualState(GroupName = VisibilityGroupName, Name = ClosedVisibilityStateName)] + [TemplateVisualState(GroupName = VisibilityGroupName, Name = OpenLandscapeVisibilityStateName)] + [TemplateVisualState(GroupName = VisibilityGroupName, Name = OpenLandscapeReversedVisibilityStateName)] + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Code flow is reasonably clear.")] + public class ContextMenu : MenuBase + { + /// + /// Width of the Menu in Landscape + /// + private const double LandscapeWidth = 480; + + /// + /// Width of the system tray in Landscape Mode + /// + private const double SystemTrayLandscapeWidth = 72; + + /// + /// Width of the application bar in Landscape mode + /// + private const double ApplicationBarLandscapeWidth = 72; + + /// + /// Width of the borders around the menu + /// + private const double TotalBorderWidth = 8; + + /// + /// Visibility state group. + /// + private const string VisibilityGroupName = "VisibilityStates"; + + /// + /// Open visibility state. + /// + private const string OpenVisibilityStateName = "Open"; + + /// + /// Open state when the context menu grows upwards. + /// + private const string OpenReversedVisibilityStateName = "OpenReversed"; + + /// + /// Closed visibility state. + /// + private const string ClosedVisibilityStateName = "Closed"; + + /// + /// Open landscape visibility state. + /// + private const string OpenLandscapeVisibilityStateName = "OpenLandscape"; + + /// + /// Open landscape state when the context menu grows leftwards. + /// + private const string OpenLandscapeReversedVisibilityStateName = "OpenLandscapeReversed"; + + /// + /// The panel that holds all the content + /// + private StackPanel _outerPanel; + + /// + /// The grid that contains the item presenter + /// + private Grid _innerGrid; + + /// + /// Stores a reference to the PhoneApplicationPage that contains the owning object. + /// + private PhoneApplicationPage _page; + + /// + /// Stores a reference to a list of ApplicationBarIconButtons for which the Click event is being handled. + /// + private readonly List _applicationBarIconButtons = new List(); + + /// + /// Stores a reference to the Storyboard used to animate the background resize. + /// + private Storyboard _backgroundResizeStoryboard; + + /// + /// Stores a reference to the Storyboard used to animate the ContextMenu open. + /// + private List _openingStoryboard; + + /// + /// Tracks whether the Storyboard used to animate the ContextMenu open is active. + /// + private bool _openingStoryboardPlaying; + + /// + /// Tracks the threshold for releasing contact during the ContextMenu open animation. + /// + private DateTime _openingStoryboardReleaseThreshold; + + /// + /// Stores a reference to the current root visual. + /// + private PhoneApplicationFrame _rootVisual; + + /// + /// Stores the last known mouse position (via MouseMove). + /// + private Point _mousePosition; + + /// + /// Stores a reference to the object that owns the ContextMenu. + /// + private DependencyObject _owner; + + /// + /// Stores a reference to the current Popup. + /// + private Popup _popup; + + /// + /// Stores a reference to the current overlay. + /// + private Panel _overlay; + + /// + /// Stores a reference to the current Popup alignment point. + /// + private Point _popupAlignmentPoint; + + /// + /// Stores a value indicating whether the IsOpen property is being updated by ContextMenu. + /// + private bool _settingIsOpen; + + /// + /// Whether the opening animation is reversed (bottom to top or right to left). + /// + private bool _reversed; + + /// + /// Gets or sets the owning object for the ContextMenu. + /// + public DependencyObject Owner + { + get { return _owner; } + internal set + { + if (null != _owner) + { + FrameworkElement ownerFrameworkElement = _owner as FrameworkElement; + if (null != ownerFrameworkElement) + { + ownerFrameworkElement.Hold -= OnOwnerHold; + ownerFrameworkElement.Loaded -= OnOwnerLoaded; + ownerFrameworkElement.Unloaded -= OnOwnerUnloaded; + + OnOwnerUnloaded(null, null); + } + } + _owner = value; + if (null != _owner) + { + FrameworkElement ownerFrameworkElement = _owner as FrameworkElement; + if (null != ownerFrameworkElement) + { + ownerFrameworkElement.Hold += OnOwnerHold; + ownerFrameworkElement.Loaded += OnOwnerLoaded; + ownerFrameworkElement.Unloaded += OnOwnerUnloaded; + + // Owner *may* already be live and have fired its Loaded event - hook up manually if necessary + DependencyObject parent = ownerFrameworkElement; + while (parent != null) + { + parent = VisualTreeHelper.GetParent(parent); + if ((null != parent) && (parent == _rootVisual)) + { + OnOwnerLoaded(null, null); + break; + } + } + } + } + } + } + + /// + /// Gets or sets a value indicating whether the background will zoom out when the ContextMenu is open. + /// + public bool IsZoomEnabled + { + get { return (bool)GetValue(IsZoomEnabledProperty); } + set { SetValue(IsZoomEnabledProperty, value); } + } + + /// + /// Identifies the IsZoomEnabled dependency property. + /// + public static readonly DependencyProperty IsZoomEnabledProperty = DependencyProperty.Register( + "IsZoomEnabled", + typeof(bool), + typeof(ContextMenu), + new PropertyMetadata(true)); + + /// + /// Gets or sets a value indicating whether the background will fade when the ContextMenu is open. + /// IsZoomEnabled must be true for this value to take effect. + /// + public bool IsFadeEnabled + { + get { return (bool)GetValue(IsFadeEnabledProperty); } + set { SetValue(IsFadeEnabledProperty, value); } + } + + /// + /// Identifies the IsFadeEnabled dependency property. + /// + public static readonly DependencyProperty IsFadeEnabledProperty = DependencyProperty.Register( + "IsFadeEnabled", + typeof(bool), + typeof(ContextMenu), + new PropertyMetadata(true)); + + /// + /// Gets or sets the vertical distance between the target origin and the popup alignment point. + /// + [TypeConverterAttribute(typeof(LengthConverter))] + public double VerticalOffset + { + get { return (double)GetValue(VerticalOffsetProperty); } + set { SetValue(VerticalOffsetProperty, value); } + } + + /// + /// Identifies the VerticalOffset dependency property. + /// + public static readonly DependencyProperty VerticalOffsetProperty = DependencyProperty.Register( + "VerticalOffset", + typeof(double), + typeof(ContextMenu), + new PropertyMetadata(0.0, OnVerticalOffsetChanged)); + + /// + /// Handles changes to the VerticalOffset DependencyProperty. + /// + /// DependencyObject that changed. + /// Event data for the DependencyPropertyChangedEvent. + private static void OnVerticalOffsetChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((ContextMenu)o).UpdateContextMenuPlacement(); + } + + /// + /// Gets or sets a value indicating whether the ContextMenu is visible. + /// + public bool IsOpen + { + get { return (bool)GetValue(IsOpenProperty); } + set { SetValue(IsOpenProperty, value); } + } + + /// + /// Identifies the IsOpen dependency property. + /// + public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register( + "IsOpen", + typeof(bool), + typeof(ContextMenu), + new PropertyMetadata(false, OnIsOpenChanged)); + + /// + /// Handles changes to the IsOpen DependencyProperty. + /// + /// DependencyObject that changed. + /// Event data for the DependencyPropertyChangedEvent. + private static void OnIsOpenChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((ContextMenu)o).OnIsOpenChanged(/*(bool)e.OldValue,*/ (bool)e.NewValue); + } + + /// + /// Handles changes to the IsOpen property. + /// + /// New value. + private void OnIsOpenChanged(/*bool oldValue,*/ bool newValue) + { + if (!_settingIsOpen) + { + if (newValue) + { + OpenPopup(_mousePosition); + } + else + { + ClosePopup(); + } + } + } + + /// + /// Gets or sets the region of interest expressed in the coordinate system of the root visual. + /// A context menu will try to position itself outside the region of interest. + /// If null, the owner's bounding box is considered the region of interest. + /// + public Rect? RegionOfInterest + { + get { return (Rect?)GetValue(RegionOfInterestProperty); } + set { SetValue(RegionOfInterestProperty, value); } + } + + /// + /// Identifies the RegionOfInterest dependency property. + /// + public static readonly DependencyProperty RegionOfInterestProperty = + DependencyProperty.Register("RegionOfInterest", typeof(Rect?), typeof(ContextMenu), null); + + /// + /// Occurs when a particular instance of a ContextMenu opens. + /// + public event RoutedEventHandler Opened; + + /// + /// Called when the Opened event occurs. + /// + /// Event arguments. + protected virtual void OnOpened(RoutedEventArgs e) + { + UpdateContextMenuPlacement(); + + // Handles initial open (where OnOpened is called before OnApplyTemplate) + SetRenderTransform(); + UpdateVisualStates(true); + + var handler = Opened; + if (null != handler) + { + handler(this, e); + } + } + + private void SetRenderTransform() + { + if (DesignerProperties.IsInDesignTool || _rootVisual.Orientation.IsPortrait()) + { + double x = 0.5; + if (null != _popupAlignmentPoint) + { + x = _popupAlignmentPoint.X / Width; + } + + if (_outerPanel != null) + { + _outerPanel.RenderTransformOrigin = new Point(x, 0); + } + if (_innerGrid != null) + { + double pointY = _reversed ? 1 : 0; + _innerGrid.RenderTransformOrigin = new Point(0, pointY); + } + } + else + { + if (_outerPanel != null) + { + _outerPanel.RenderTransformOrigin = new Point(0, 0.5); + } + if (_innerGrid != null) + { + double pointX = _reversed ? 1 : 0; + _innerGrid.RenderTransformOrigin = new Point(pointX, 0); + } + } + } + + /// + /// Occurs when a particular instance of a ContextMenu closes. + /// + public event RoutedEventHandler Closed; + + /// + /// Called when the Closed event occurs. + /// + /// Event arguments. + protected virtual void OnClosed(RoutedEventArgs e) + { + UpdateVisualStates(true); + + var handler = Closed; + if (null != handler) + { + handler(this, e); + } + } + + /// + /// Initializes a new instance of the ContextMenu class. + /// + public ContextMenu() + { + DefaultStyleKey = typeof(ContextMenu); + + _openingStoryboard = new List(); + + // Temporarily hook LayoutUpdated to find out when Application.Current.RootVisual gets set. + LayoutUpdated += OnLayoutUpdated; + } + + /// + /// Called when a new Template is applied. + /// + public override void OnApplyTemplate() + { + // Unhook from old Template + if (null != _openingStoryboard) + { + foreach (Storyboard sb in _openingStoryboard) + { + sb.Completed -= OnStoryboardCompleted; + } + _openingStoryboard.Clear(); + } + _openingStoryboardPlaying = false; + + // Apply new template + base.OnApplyTemplate(); + + SetDefaultStyle(); + + // Hook up to new template + FrameworkElement templateRoot = VisualTreeHelper.GetChild(this, 0) as FrameworkElement; + if (null != templateRoot) + { + foreach (VisualStateGroup group in VisualStateManager.GetVisualStateGroups(templateRoot)) + { + if (VisibilityGroupName == group.Name) + { + foreach (VisualState state in group.States) + { + if ((OpenVisibilityStateName == state.Name || OpenLandscapeVisibilityStateName == state.Name || OpenReversedVisibilityStateName == state.Name || OpenLandscapeReversedVisibilityStateName == state.Name) && (null != state.Storyboard)) + { + _openingStoryboard.Add(state.Storyboard); + state.Storyboard.Completed += OnStoryboardCompleted; + } + } + } + } + } + + _outerPanel = GetTemplateChild("OuterPanel") as StackPanel; + _innerGrid = GetTemplateChild("InnerGrid") as Grid; + + // Go to correct visual state(s) + bool portrait = DesignerProperties.IsInDesignTool || _rootVisual.Orientation.IsPortrait(); + + SetRenderTransform(); + + if (IsOpen) + { + // Handles initial open (where OnOpened is called before OnApplyTemplate) + if (null != _innerGrid) + { + // if landscape to the full height. NOTE: device is rotated so use the ActualWidth + _innerGrid.MinHeight = portrait ? 0 : _rootVisual.ActualWidth; + } + + UpdateVisualStates(true); + } + } + + /// + /// Set up the background and border default styles + /// + private void SetDefaultStyle() + { + // These styles are not defined in the XAML because according to spec, + // the background color should be white (opaque) in Dark Theme and black (opaque) in Light Theme. + // There are no StaticResource brushes that have this property (the black is transparent). + // We define these in code, because we need to check the current theme to define the colors. + + SolidColorBrush backgroundBrush; + SolidColorBrush borderBrush; + if (DesignerProperties.IsInDesignTool || Resources.IsDarkThemeActive()) + { + backgroundBrush = new SolidColorBrush(Colors.White); + borderBrush = new SolidColorBrush(Colors.Black); + } + else + { + backgroundBrush = new SolidColorBrush(Colors.Black); + borderBrush = new SolidColorBrush(Colors.White); + } + + Style newStyle = new Style(typeof(ContextMenu)); + + Setter setterBackground = new Setter(ContextMenu.BackgroundProperty, backgroundBrush); + Setter settterBorderBrush = new Setter(ContextMenu.BorderBrushProperty, borderBrush); + + if (null == Style) + { + newStyle.Setters.Add(setterBackground); + newStyle.Setters.Add(settterBorderBrush); + } + else + { + // Merge the currently existing style with the new styles we want + bool foundBackground = false; + bool foundBorderBrush = false; + + foreach (Setter s in Style.Setters) + { + if (s.Property == ContextMenu.BackgroundProperty) + { + foundBackground = true; + } + else if (s.Property == ContextMenu.BorderBrushProperty) + { + foundBorderBrush = true; + } + + newStyle.Setters.Add(new Setter(s.Property, s.Value)); + } + + if (!foundBackground) + { + newStyle.Setters.Add(setterBackground); + } + if (!foundBorderBrush) + { + newStyle.Setters.Add(settterBorderBrush); + } + } + + Style = newStyle; + } + + /// + /// Handles the Completed event of the opening Storyboard. + /// + /// Source of the event. + /// Event arguments. + private void OnStoryboardCompleted(object sender, EventArgs e) + { + _openingStoryboardPlaying = false; + } + + /// + /// Uses VisualStateManager to go to the appropriate visual state. + /// + /// true to use a System.Windows.VisualTransition to + /// transition between states; otherwise, false. + private void UpdateVisualStates(bool useTransitions) + { + string stateName; + + if (IsOpen) + { + if (null != _openingStoryboard) + { + _openingStoryboardPlaying = true; + _openingStoryboardReleaseThreshold = DateTime.UtcNow.AddSeconds(0.3); + } + + if (_rootVisual != null && _rootVisual.Orientation.IsPortrait()) + { + if (_outerPanel != null) + { + _outerPanel.Orientation = Orientation.Vertical; + } + + stateName = _reversed ? OpenReversedVisibilityStateName : OpenVisibilityStateName; + } + else + { + if (_outerPanel != null) + { + _outerPanel.Orientation = Orientation.Horizontal; + } + + stateName = _reversed ? OpenLandscapeReversedVisibilityStateName : OpenLandscapeVisibilityStateName; + } + + if (null != _backgroundResizeStoryboard) + { + _backgroundResizeStoryboard.Begin(); + } + } + else + { + stateName = ClosedVisibilityStateName; + } + + VisualStateManager.GoToState(this, stateName, useTransitions); + } + + /// + /// Whether the position is on the right half of the screen. + /// Only supports landscape mode. + /// This is used to determine which side of the screen the context menu will display on. + /// + /// Position to check for + private bool PositionIsOnScreenRight(double position) + { + return (PageOrientation.LandscapeLeft == _rootVisual.Orientation ? + (position > _rootVisual.ActualHeight / 2) : + (position < _rootVisual.ActualHeight / 2)); + } + + /// + /// Called when the left mouse button is pressed. + /// + /// The event data for the MouseLeftButtonDown event. + protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e) + { + if (e == null) + { + throw new ArgumentNullException("e"); + } + + e.Handled = true; + base.OnMouseLeftButtonDown(e); + } + + /// + /// Responds to the KeyDown event. + /// + /// The event data for the KeyDown event. + protected override void OnKeyDown(KeyEventArgs e) + { + if (e == null) + { + throw new ArgumentNullException("e"); + } + + switch (e.Key) + { + case Key.Up: + FocusNextItem(false); + e.Handled = true; + break; + case Key.Down: + FocusNextItem(true); + e.Handled = true; + break; + case Key.Escape: + ClosePopup(); + e.Handled = true; + break; + // case Key.Apps: // Key.Apps not defined by Silverlight 4 + } + base.OnKeyDown(e); + } + + /// + /// Handles the LayoutUpdated event to capture Application.Current.RootVisual. + /// + /// Source of the event. + /// Event arguments. + private void OnLayoutUpdated(object sender, EventArgs e) + { + if (null != Application.Current.RootVisual) + { + // Application.Current.RootVisual is valid now + InitializeRootVisual(); + // Unhook event + LayoutUpdated -= OnLayoutUpdated; + } + } + + /// + /// Handles the RootVisual's MouseMove event to track the last mouse position. + /// + /// Source of the event. + /// Event arguments. + private void OnRootVisualMouseMove(object sender, MouseEventArgs e) + { + _mousePosition = e.GetPosition(null); + } + + /// + /// Handles the ManipulationCompleted event for the RootVisual. + /// + /// Source of the event. + /// Event arguments. + private void OnRootVisualManipulationCompleted(object sender, ManipulationCompletedEventArgs e) + { + // Breaking contact during the ContextMenu show animation should cancel the ContextMenu + if (_openingStoryboardPlaying && (DateTime.UtcNow <= _openingStoryboardReleaseThreshold)) + { + IsOpen = false; + } + } + + /// + /// Handles the Hold event for the owning element. + /// + /// Source of the event. + /// Event arguments. + private void OnOwnerHold(object sender, System.Windows.Input.GestureEventArgs e) + { + if (!IsOpen) + { + OpenPopup(e.GetPosition(null)); + e.Handled = true; + } + } + + /// + /// Identifies the ApplicationBarMirror dependency property. + /// + private static readonly DependencyProperty ApplicationBarMirrorProperty = DependencyProperty.Register( + "ApplicationBarMirror", + typeof(IApplicationBar), + typeof(ContextMenu), + new PropertyMetadata(OnApplicationBarMirrorChanged)); + + /// + /// Handles changes to the ApplicationBarMirror DependencyProperty. + /// + /// DependencyObject that changed. + /// Event data for the DependencyPropertyChangedEvent. + private static void OnApplicationBarMirrorChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((ContextMenu)o).OnApplicationBarMirrorChanged((IApplicationBar)e.OldValue, (IApplicationBar)e.NewValue); + } + + /// + /// Handles changes to the ApplicationBarMirror property. + /// + /// Old value. + /// New value. + private void OnApplicationBarMirrorChanged(IApplicationBar oldValue, IApplicationBar newValue) + { + if (null != oldValue) + { + oldValue.StateChanged -= OnEventThatClosesContextMenu; + } + if (null != newValue) + { + newValue.StateChanged += OnEventThatClosesContextMenu; + } + } + + /// + /// Handles an event which should close an open ContextMenu. + /// + /// Source of the event. + /// Event arguments. + private void OnEventThatClosesContextMenu(object sender, EventArgs e) + { + // Close the ContextMenu because the elements and/or layout is likely to have changed significantly + IsOpen = false; + } + + /// + /// Handles the Loaded event of the Owner. + /// + /// Source of the event. + /// Event arguments. + private void OnOwnerLoaded(object sender, RoutedEventArgs e) + { + if (null == _page) // Don't want to attach to BackKeyPress twice + { + InitializeRootVisual(); + if (null != _rootVisual) + { + _page = _rootVisual.Content as PhoneApplicationPage; + if (_page != null) + { + _page.BackKeyPress += OnPageBackKeyPress; + SetBinding(ApplicationBarMirrorProperty, new Binding { Source = _page, Path = new PropertyPath("ApplicationBar") }); + } + } + } + } + + /// + /// Handles the Unloaded event of the Owner. + /// + /// Source of the event. + /// Event arguments. + private void OnOwnerUnloaded(object sender, RoutedEventArgs e) + { + if (null != _rootVisual) + { + _rootVisual.MouseMove -= OnRootVisualMouseMove; + _rootVisual.ManipulationCompleted -= OnRootVisualManipulationCompleted; + _rootVisual.OrientationChanged -= OnEventThatClosesContextMenu; + } + if (_page != null) + { + _page.BackKeyPress -= OnPageBackKeyPress; + ClearValue(ApplicationBarMirrorProperty); + _page = null; + } + } + + /// + /// Handles the BackKeyPress of the containing PhoneApplicationPage. + /// + /// Source of the event. + /// Event arguments. + private void OnPageBackKeyPress(object sender, CancelEventArgs e) + { + if (IsOpen) + { + IsOpen = false; + e.Cancel = true; + } + } + + /// + /// Calls TransformToVisual on the specified element for the specified visual, suppressing the ArgumentException that can occur in some cases. + /// + /// Element on which to call TransformToVisual. + /// Visual to pass to the call to TransformToVisual. + /// Resulting GeneralTransform object. + private static GeneralTransform SafeTransformToVisual(UIElement element, UIElement visual) + { + GeneralTransform result; + try + { + result = element.TransformToVisual(visual); + } + catch (ArgumentException) + { + // Not perfect, but better than throwing an exception + result = new TranslateTransform(); + } + return result; + } + + /// + /// Initialize the _rootVisual property (if possible and not already done). + /// + private void InitializeRootVisual() + { + if (null == _rootVisual) + { + // Try to capture the Application's RootVisual + _rootVisual = Application.Current.RootVisual as + PhoneApplicationFrame; + if (null != _rootVisual) + { + _rootVisual.MouseMove -= OnRootVisualMouseMove; + _rootVisual.MouseMove += OnRootVisualMouseMove; + + _rootVisual.ManipulationCompleted -= OnRootVisualManipulationCompleted; + _rootVisual.ManipulationCompleted += OnRootVisualManipulationCompleted; + + _rootVisual.OrientationChanged -= OnEventThatClosesContextMenu; + _rootVisual.OrientationChanged += OnEventThatClosesContextMenu; + } + } + } + + /// + /// Sets focus to the next item in the ContextMenu. + /// + /// True to move the focus down; false to move it up. + private void FocusNextItem(bool down) + { + int count = Items.Count; + int startingIndex = down ? -1 : count; + MenuItem focusedMenuItem = FocusManager.GetFocusedElement() as MenuItem; + if (null != focusedMenuItem && (this == focusedMenuItem.ParentMenuBase)) + { + startingIndex = ItemContainerGenerator.IndexFromContainer(focusedMenuItem); + } + int index = startingIndex; + do + { + index = (index + count + (down ? 1 : -1)) % count; + MenuItem container = ItemContainerGenerator.ContainerFromIndex(index) as MenuItem; + if (null != container) + { + if (container.IsEnabled && container.Focus()) + { + break; + } + } + } + while (index != startingIndex); + } + + /// + /// Called when a child MenuItem is clicked. + /// + internal void ChildMenuItemClicked() + { + ClosePopup(); + } + + /// + /// Handles the SizeChanged event for the ContextMenu or RootVisual. + /// + /// Source of the event. + /// Event arguments. + private void OnContextMenuOrRootVisualSizeChanged(object sender, SizeChangedEventArgs e) + { + UpdateContextMenuPlacement(); + } + + /// + /// Handles the MouseButtonUp events for the overlay. + /// + /// Source of the event. + /// Event arguments. + private void OnOverlayMouseButtonUp(object sender, MouseButtonEventArgs e) + { + // If they clicked in the context menu, then don't close + List list = VisualTreeHelper.FindElementsInHostCoordinates(e.GetPosition(null), _rootVisual) as List; + if (!list.Contains(this)) + { + ClosePopup(); + } + + e.Handled = true; + } + + /// + /// Updates the location and size of the Popup and overlay. + /// + private void UpdateContextMenuPlacement() + { + if ((null != _rootVisual) && (null != _overlay)) + { + Point p = new Point(_popupAlignmentPoint.X, _popupAlignmentPoint.Y); + + // Determine frame/page bounds + bool portrait = _rootVisual.Orientation.IsPortrait(); + + double effectiveWidth = portrait ? _rootVisual.ActualWidth : _rootVisual.ActualHeight; + double effectiveHeight = portrait ? _rootVisual.ActualHeight : _rootVisual.ActualWidth; + Rect bounds = new Rect(0, 0, effectiveWidth, effectiveHeight); + if (_page != null) + { + bounds = SafeTransformToVisual(_page, _rootVisual).TransformBounds(new Rect(0, 0, _page.ActualWidth, _page.ActualHeight)); + } + + if (portrait && null != _rootVisual && null != bounds) + { + double roiY; + double roiHeight; + + if (RegionOfInterest.HasValue) + { + roiY = RegionOfInterest.Value.Y; + roiHeight = RegionOfInterest.Value.Height; + } + else if (Owner is FrameworkElement) + { + FrameworkElement el = (FrameworkElement)Owner; + GeneralTransform t = el.TransformToVisual(_rootVisual); + + roiY = t.Transform(new Point(0, 0)).Y; + roiHeight = el.ActualHeight; + } + else + { + roiY = _popupAlignmentPoint.Y; + roiHeight = 0; + } + + // Try placing context menu below ROI + p.Y = roiY + roiHeight; + _reversed = false; + + if (p.Y > (bounds.Bottom - ActualHeight)) + { + // Try placing context menu above ROI + p.Y = roiY - ActualHeight; + _reversed = true; + + if (p.Y < bounds.Top) + { + // Ignore ROI, place Context Menu at touch position and try downwards + p = _popupAlignmentPoint; + _reversed = false; + + if (p.Y > (bounds.Bottom - ActualHeight)) + { + // Expand upwards at touch position + _reversed = true; + + if (p.Y < bounds.Top) + { + p.Y = bounds.Bottom - ActualHeight; + _reversed = true; + } + } + } + } + } + + // Start with the current Popup alignment point + double x = p.X; + double y = p.Y; + + // Adjust for offset + y += VerticalOffset; + + if (portrait) + { + // Left align with full width + x = bounds.Left; + Width = bounds.Width; + if (null != _innerGrid) + { + _innerGrid.Width = Width; + } + } + else + { + if (PositionIsOnScreenRight(y)) + { + Width = (SystemTray.IsVisible) ? LandscapeWidth - SystemTrayLandscapeWidth : LandscapeWidth; + x = (SystemTray.IsVisible) ? SystemTrayLandscapeWidth : 0; + _reversed = true; + } + else + { + Width = (null != _page.ApplicationBar && _page.ApplicationBar.IsVisible) ? LandscapeWidth - ApplicationBarLandscapeWidth : LandscapeWidth; + x = bounds.Width - Width + ((SystemTray.IsVisible) ? SystemTrayLandscapeWidth : 0); + _reversed = false; + } + + if (null != _innerGrid) + { + _innerGrid.Width = Width - TotalBorderWidth; + } + + y = 0; + } + + // Do not let it stick out too far to the left/top + x = Math.Max(x, 0); + + // Set the new location + Canvas.SetLeft(this, x); + Canvas.SetTop(this, y); + + // Size the overlay to match the new container + _overlay.Width = effectiveWidth; + _overlay.Height = effectiveHeight; + } + } + + /// + /// Opens the Popup. + /// + /// Position to place the Popup. + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Code flow is reasonably clear.")] + private void OpenPopup(Point position) + { + _popupAlignmentPoint = position; + + InitializeRootVisual(); + + bool portrait = _rootVisual.Orientation.IsPortrait(); + + if (portrait) + { + if (_innerGrid != null) + { + _innerGrid.MinHeight = 0; + } + } + else + { + if (_innerGrid != null) + { + // if landscape to the full height. NOTE: device is rotated so use the ActualWidth + _innerGrid.MinHeight = _rootVisual.ActualWidth; + } + } + + _overlay = new Canvas { Background = new SolidColorBrush(Colors.Transparent) }; + _overlay.MouseLeftButtonUp += OnOverlayMouseButtonUp; + + if (IsZoomEnabled && (null != _rootVisual)) + { + // Capture effective width/height + double width = portrait ? _rootVisual.ActualWidth : _rootVisual.ActualHeight; + double height = portrait ? _rootVisual.ActualHeight : _rootVisual.ActualWidth; + + // Create a layer for the background brush + UIElement backgroundLayer = new Rectangle + { + Width = width, + Height = height, + Fill = (Brush)Application.Current.Resources["PhoneBackgroundBrush"], + CacheMode = new BitmapCache(), + }; + _overlay.Children.Insert(0, backgroundLayer); + + // Hide the owner for the snapshot we will take + FrameworkElement ownerElement = _owner as FrameworkElement; + if (null != ownerElement) + { + ownerElement.Opacity = 0; + } + + // Create a layer for the page content + WriteableBitmap writeableBitmap = new WriteableBitmap((int)width, (int)height); + writeableBitmap.Render(_rootVisual, null); + writeableBitmap.Invalidate(); + Transform scaleTransform = new ScaleTransform + { + CenterX = width / 2, + CenterY = height / 2, + }; + UIElement contentLayer = new Image + { + Source = writeableBitmap, + RenderTransform = scaleTransform, + CacheMode = new BitmapCache(), + }; + _overlay.Children.Insert(1, contentLayer); + + // Create a layer for the background brush + UIElement backgroundFadeLayer = new Rectangle + { + Width = width, + Height = height, + Fill = (Brush)Application.Current.Resources["PhoneBackgroundBrush"], + Opacity = 0, + CacheMode = new BitmapCache(), + }; + _overlay.Children.Insert(2, backgroundFadeLayer); + + + // Create a layer for the owner element and its background + + if (null != ownerElement) + { + ((FrameworkElement)Owner).Opacity = 1; + + Point point = SafeTransformToVisual(ownerElement, _rootVisual).Transform(new Point()); + + // Create a layer for the element's background + UIElement elementBackground = new Rectangle + { + Width = ownerElement.ActualWidth, + Height = ownerElement.ActualHeight, + Fill = new SolidColorBrush(Colors.Transparent), + CacheMode = new BitmapCache(), + }; + Canvas.SetLeft(elementBackground, point.X); + Canvas.SetTop(elementBackground, point.Y); + _overlay.Children.Insert(3, elementBackground); + + // Create a layer for the element + UIElement element = new Image { Source = new WriteableBitmap(ownerElement, null) }; + Canvas.SetLeft(element, point.X); + Canvas.SetTop(element, point.Y); + _overlay.Children.Insert(4, element); + } + + // Prepare for scale animation + double from = 1; + double to = 0.94; + TimeSpan timespan = TimeSpan.FromSeconds(0.42); + IEasingFunction easingFunction = new ExponentialEase { EasingMode = EasingMode.EaseInOut }; + _backgroundResizeStoryboard = new Storyboard(); + + // Create an animation for the X scale + DoubleAnimation animationX = new DoubleAnimation { From = from, To = to, Duration = timespan, EasingFunction = easingFunction }; + Storyboard.SetTarget(animationX, scaleTransform); + Storyboard.SetTargetProperty(animationX, new PropertyPath(ScaleTransform.ScaleXProperty)); + _backgroundResizeStoryboard.Children.Add(animationX); + + // Create an animation for the Y scale + DoubleAnimation animationY = new DoubleAnimation { From = from, To = to, Duration = timespan, EasingFunction = easingFunction }; + Storyboard.SetTarget(animationY, scaleTransform); + Storyboard.SetTargetProperty(animationY, new PropertyPath(ScaleTransform.ScaleYProperty)); + _backgroundResizeStoryboard.Children.Add(animationY); + + if (IsFadeEnabled) + { + DoubleAnimation animationFade = new DoubleAnimation { From = 0, To = .3, Duration = timespan, EasingFunction = easingFunction }; + Storyboard.SetTarget(animationFade, backgroundFadeLayer); + Storyboard.SetTargetProperty(animationFade, new PropertyPath(Rectangle.OpacityProperty)); + _backgroundResizeStoryboard.Children.Add(animationFade); + } + } + + // Create transforms for handling rotation + TransformGroup transforms = new TransformGroup(); + if (null != _rootVisual) + { + switch (_rootVisual.Orientation) + { + case PageOrientation.LandscapeLeft: + transforms.Children.Add(new RotateTransform { Angle = 90 }); + transforms.Children.Add(new TranslateTransform { X = _rootVisual.ActualWidth }); + break; + case PageOrientation.LandscapeRight: + transforms.Children.Add(new RotateTransform { Angle = -90 }); + transforms.Children.Add(new TranslateTransform { Y = _rootVisual.ActualHeight }); + break; + } + } + _overlay.RenderTransform = transforms; + + // Add Click handler for ApplicationBar Buttons + if ((null != _page) && (null != _page.ApplicationBar) && (null != _page.ApplicationBar.Buttons)) + { + foreach (object obj in _page.ApplicationBar.Buttons) + { + ApplicationBarIconButton button = obj as ApplicationBarIconButton; + if (null != button) + { + button.Click += OnEventThatClosesContextMenu; + _applicationBarIconButtons.Add(button); + } + } + } + + _overlay.Children.Add(this); + + _popup = new Popup { Child = _overlay }; + + _popup.Opened += (s, e) => + { + // When the popup is actually opened, call our OnOpened method + OnOpened(new RoutedEventArgs()); + }; + + SizeChanged += OnContextMenuOrRootVisualSizeChanged; + if (null != _rootVisual) + { + _rootVisual.SizeChanged += OnContextMenuOrRootVisualSizeChanged; + } + + UpdateContextMenuPlacement(); + + if (ReadLocalValue(DataContextProperty) == DependencyProperty.UnsetValue) + { + DependencyObject dataContextSource = Owner ?? _rootVisual; + SetBinding(DataContextProperty, new Binding("DataContext") { Source = dataContextSource }); + } + + _popup.IsOpen = true; + Focus(); + + // Update IsOpen + _settingIsOpen = true; + IsOpen = true; + _settingIsOpen = false; + } + + /// + /// Closes the Popup. + /// + private void ClosePopup() + { + if (null != _backgroundResizeStoryboard) + { + // Swap all the From/To values to reverse the animation + foreach (DoubleAnimation animation in _backgroundResizeStoryboard.Children) + { + double temp = animation.From.Value; + animation.From = animation.To; + animation.To = temp; + } + + // Capture member variables for delegate closure + Popup popup = _popup; + Panel overlay = _overlay; + _backgroundResizeStoryboard.Completed += delegate + { + // Clear/close popup and overlay + if (null != popup) + { + popup.IsOpen = false; + popup.Child = null; + } + if (null != overlay) + { + overlay.Children.Clear(); + } + }; + + // Begin the reverse animation + _backgroundResizeStoryboard.Begin(); + + // Reset member variables + _backgroundResizeStoryboard = null; + _popup = null; + _overlay = null; + } + else + { + if (null != _popup) + { + _popup.IsOpen = false; + _popup.Child = null; + _popup = null; + } + if (null != _overlay) + { + _overlay.Children.Clear(); + _overlay = null; + } + } + SizeChanged -= OnContextMenuOrRootVisualSizeChanged; + if (null != _rootVisual) + { + _rootVisual.SizeChanged -= OnContextMenuOrRootVisualSizeChanged; + } + + // Remove Click handler for ApplicationBar Buttons + foreach (ApplicationBarIconButton button in _applicationBarIconButtons) + { + button.Click -= OnEventThatClosesContextMenu; + } + _applicationBarIconButtons.Clear(); + + // Update IsOpen + _settingIsOpen = true; + IsOpen = false; + _settingIsOpen = false; + + OnClosed(new RoutedEventArgs()); + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/ContextMenu/ContextMenuService.cs b/Microsoft.Phone.Controls.Toolkit/ContextMenu/ContextMenuService.cs new file mode 100644 index 0000000..c3d7f4c --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/ContextMenu/ContextMenuService.cs @@ -0,0 +1,83 @@ +// (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.Windows; + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// Provides the system implementation for displaying a ContextMenu. + /// + /// Preview + public static class ContextMenuService + { + /// + /// Gets the value of the ContextMenu property of the specified object. + /// + /// Object to query concerning the ContextMenu property. + /// Value of the ContextMenu property. + public static ContextMenu GetContextMenu(DependencyObject element) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + return (ContextMenu)element.GetValue(ContextMenuProperty); + } + + /// + /// Sets the value of the ContextMenu property of the specified object. + /// + /// Object to set the property on. + /// Value to set. + public static void SetContextMenu(DependencyObject element, ContextMenu value) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + + element.SetValue(ContextMenuProperty, value); + } + + /// + /// Identifies the ContextMenu attached property. + /// + public static readonly DependencyProperty ContextMenuProperty = DependencyProperty.RegisterAttached( + "ContextMenu", + typeof(ContextMenu), + typeof(ContextMenuService), + new PropertyMetadata(null, OnContextMenuChanged)); + + /// + /// Handles changes to the ContextMenu DependencyProperty. + /// + /// DependencyObject that changed. + /// Event data for the DependencyPropertyChangedEvent. + private static void OnContextMenuChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + FrameworkElement element = o as FrameworkElement; + if (null != element) + { + ContextMenu oldContextMenu = e.OldValue as ContextMenu; + if (null != oldContextMenu) + { + oldContextMenu.Owner = null; + } + ContextMenu newContextMenu = e.NewValue as ContextMenu; + if (null != newContextMenu) + { + newContextMenu.Owner = element; + } + } + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/ContextMenu/MenuBase.cs b/Microsoft.Phone.Controls.Toolkit/ContextMenu/MenuBase.cs new file mode 100644 index 0000000..bde65b6 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/ContextMenu/MenuBase.cs @@ -0,0 +1,120 @@ +// (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.Windows; +using System.Windows.Controls; + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls.Primitives +#else +namespace System.Windows.Controls.Primitives +#endif +{ + /// + /// Represents a control that defines choices for users to select. + /// + /// Preview + [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(MenuItem))] + public abstract class MenuBase : ItemsControl + { + /// + /// Gets or sets the Style that is applied to the container element generated for each item. + /// + public Style ItemContainerStyle + { + get { return (Style)GetValue(ItemContainerStyleProperty); } + set { SetValue(ItemContainerStyleProperty, value); } + } + + /// + /// Identifies the ItemContainerStyle dependency property. + /// + public static readonly DependencyProperty ItemContainerStyleProperty = DependencyProperty.Register( + "ItemContainerStyle", + typeof(Style), + typeof(MenuBase), + null); + + /// + /// Initializes a new instance of the MenuBase class. + /// + protected MenuBase() + { + } + + /// + /// Determines whether the specified item is, or is eligible to be, its own item container. + /// + /// The item to check whether it is an item container. + /// True if the item is a MenuItem or a Separator; otherwise, false. + protected override bool IsItemItsOwnContainerOverride(object item) + { + return ((item is MenuItem) || (item is Separator)); + } + + /// + /// Creates or identifies the element used to display the specified item. + /// + /// A MenuItem. + protected override DependencyObject GetContainerForItemOverride() + { + return new MenuItem(); + } + + /// + /// Prepares the specified element to display the specified item. + /// + /// Element used to display the specified item. + /// Specified item. + protected override void PrepareContainerForItemOverride(DependencyObject element, object item) + { + base.PrepareContainerForItemOverride(element, item); + MenuItem menuItem = element as MenuItem; + if (null != menuItem) + { + menuItem.ParentMenuBase = this; + if (menuItem != item) + { + // Copy the ItemsControl properties from parent to child + DataTemplate itemTemplate = ItemTemplate; + Style itemContainerStyle = ItemContainerStyle; + if (itemTemplate != null) + { + menuItem.SetValue(HeaderedItemsControl.ItemTemplateProperty, itemTemplate); + } + if (itemContainerStyle != null && HasDefaultValue(menuItem, HeaderedItemsControl.ItemContainerStyleProperty)) + { + menuItem.SetValue(HeaderedItemsControl.ItemContainerStyleProperty, itemContainerStyle); + } + + // Copy the Header properties from parent to child + if (HasDefaultValue(menuItem, HeaderedItemsControl.HeaderProperty)) + { + menuItem.Header = item; + } + if (itemTemplate != null) + { + menuItem.SetValue(HeaderedItemsControl.HeaderTemplateProperty, itemTemplate); + } + if (itemContainerStyle != null) + { + menuItem.SetValue(HeaderedItemsControl.StyleProperty, itemContainerStyle); + } + } + } + } + + /// + /// Checks whether a control has the default value for a property. + /// + /// The control to check. + /// The property to check. + /// True if the property has the default value; false otherwise. + private static bool HasDefaultValue(Control control, DependencyProperty property) + { + return control.ReadLocalValue(property) == DependencyProperty.UnsetValue; + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/ContextMenu/MenuItem.cs b/Microsoft.Phone.Controls.Toolkit/ContextMenu/MenuItem.cs new file mode 100644 index 0000000..d3de8c6 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/ContextMenu/MenuItem.cs @@ -0,0 +1,337 @@ +// (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.Specialized; +using System.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using Microsoft.Phone.Controls.Primitives; + +namespace Microsoft.Phone.Controls +{ + /// + /// Represents a selectable item inside a Menu or ContextMenu. + /// + /// Preview + [TemplateVisualState(Name = "Normal", GroupName = "CommonStates")] + [TemplateVisualState(Name = "Disabled", GroupName = "CommonStates")] + [TemplateVisualState(Name = "Unfocused", GroupName = "FocusStates")] + [TemplateVisualState(Name = "Focused", GroupName = "FocusStates")] + [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(MenuItem))] + public class MenuItem : HeaderedItemsControl // , ICommandSource // ICommandSource not defined by Silverlight 4 + { + /// + /// Occurs when a MenuItem is clicked. + /// + public event RoutedEventHandler Click; + + /// + /// Stores a value indicating whether this element has logical focus. + /// + private bool _isFocused; + + /// + /// Gets or sets a reference to the MenuBase parent. + /// + internal MenuBase ParentMenuBase { get; set; } + + /// + /// Gets or sets the command associated with the menu item. + /// + public ICommand Command + { + get { return (ICommand)GetValue(CommandProperty); } + set { SetValue(CommandProperty, value); } + } + + /// + /// Identifies the Command dependency property. + /// + public static readonly DependencyProperty CommandProperty = DependencyProperty.Register( + "Command", + typeof(ICommand), + typeof(MenuItem), + new PropertyMetadata(null, OnCommandChanged)); + + /// + /// Handles changes to the Command DependencyProperty. + /// + /// DependencyObject that changed. + /// Event data for the DependencyPropertyChangedEvent. + private static void OnCommandChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((MenuItem)o).OnCommandChanged((ICommand)e.OldValue, (ICommand)e.NewValue); + } + + /// + /// Handles changes to the Command property. + /// + /// Old value. + /// New value. + private void OnCommandChanged(ICommand oldValue, ICommand newValue) + { + if (null != oldValue) + { + oldValue.CanExecuteChanged -= OnCanExecuteChanged; + } + if (null != newValue) + { + newValue.CanExecuteChanged += OnCanExecuteChanged; + } + UpdateIsEnabled(true); + } + + /// + /// Gets or sets the parameter to pass to the Command property of a MenuItem. + /// + public object CommandParameter + { + get { return (object)GetValue(CommandParameterProperty); } + set { SetValue(CommandParameterProperty, value); } + } + + /// + /// Identifies the CommandParameter dependency property. + /// + public static readonly DependencyProperty CommandParameterProperty = DependencyProperty.Register( + "CommandParameter", + typeof(object), + typeof(MenuItem), + new PropertyMetadata(null, OnCommandParameterChanged)); + + /// + /// Handles changes to the CommandParameter DependencyProperty. + /// + /// DependencyObject that changed. + /// Event data for the DependencyPropertyChangedEvent. + private static void OnCommandParameterChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((MenuItem)o).OnCommandParameterChanged(/*e.OldValue, e.NewValue*/); + } + + /// + /// Handles changes to the CommandParameter property. + /// + private void OnCommandParameterChanged(/*object oldValue, object newValue*/) + { + UpdateIsEnabled(true); + } + + /// + /// Initializes a new instance of the MenuItem class. + /// + [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "False parameter to UpdateIsEnabled ensures ChangeVisualState won't be called.")] + public MenuItem() + { + DefaultStyleKey = typeof(MenuItem); + IsEnabledChanged += OnIsEnabledChanged; + SetValue(TiltEffect.IsTiltEnabledProperty, true); + Loaded += OnLoaded; + UpdateIsEnabled(false); + } + + /// + /// Called when the template's tree is generated. + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + ChangeVisualState(false); + } + + /// + /// Invoked whenever an unhandled GotFocus event reaches this element in its route. + /// + /// A RoutedEventArgs that contains event data. + protected override void OnGotFocus(RoutedEventArgs e) + { + base.OnGotFocus(e); + _isFocused = true; + ChangeVisualState(true); + } + + /// + /// Raises the LostFocus routed event by using the event data that is provided. + /// + /// A RoutedEventArgs that contains event data. + protected override void OnLostFocus(RoutedEventArgs e) + { + base.OnLostFocus(e); + _isFocused = false; + ChangeVisualState(true); + } + + /// + /// Called whenever the mouse enters a MenuItem. + /// + /// The event data for the MouseEnter event. + protected override void OnMouseEnter(MouseEventArgs e) + { + base.OnMouseEnter(e); + Focus(); + ChangeVisualState(true); + } + + /// + /// Called whenever the mouse leaves a MenuItem. + /// + /// The event data for the MouseLeave event. + protected override void OnMouseLeave(MouseEventArgs e) + { + base.OnMouseLeave(e); + if (null != ParentMenuBase) + { + ParentMenuBase.Focus(); + } + ChangeVisualState(true); + } + + /// + /// Called when the left mouse button is released. + /// + /// The event data for the MouseLeftButtonUp event. + protected override void OnMouseLeftButtonUp(MouseButtonEventArgs e) + { + if (e == null) + { + throw new ArgumentNullException("e"); + } + + if (!e.Handled) + { + OnClick(); + e.Handled = true; + } + + base.OnMouseLeftButtonUp(e); + } + + /// + /// Responds to the KeyDown event. + /// + /// The event data for the KeyDown event. + protected override void OnKeyDown(KeyEventArgs e) + { + if (e == null) + { + throw new ArgumentNullException("e"); + } + + if (!e.Handled && (Key.Enter == e.Key)) + { + OnClick(); + e.Handled = true; + } + base.OnKeyDown(e); + } + + /// + /// Called when the Items property changes. + /// + /// The event data for the ItemsChanged event. + protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) + { + throw new NotImplementedException(); + } + + /// + /// Called when a MenuItem is clicked and raises a Click event. + /// + protected virtual void OnClick() + { + ContextMenu contextMenu = ParentMenuBase as ContextMenu; + if (null != contextMenu) + { + contextMenu.ChildMenuItemClicked(); + } + // Wrapping the remaining code in a call to Dispatcher.BeginInvoke provides + // WPF-compatibility by allowing the ContextMenu to close before the command + // executes. However, it breaks the Clipboard.SetText scenario because the + // call to SetText is no longer in direct response to user input. + var handler = Click; + if (null != handler) + { + handler(this, new RoutedEventArgs()); + } + if ((null != Command) && Command.CanExecute(CommandParameter)) + { + Command.Execute(CommandParameter); + } + } + + /// + /// Handles the CanExecuteChanged event of the Command property. + /// + /// Source of the event. + /// Event arguments. + private void OnCanExecuteChanged(object sender, EventArgs e) + { + UpdateIsEnabled(true); + } + + /// + /// Updates the IsEnabled property. + /// + /// + /// WPF overrides the local value of IsEnabled according to ICommand, so Silverlight does, too. + /// + /// True if ChangeVisualState should be called. + private void UpdateIsEnabled(bool changeVisualState) + { + IsEnabled = (null == Command) || Command.CanExecute(CommandParameter); + if (changeVisualState) + { + ChangeVisualState(true); + } + } + + /// + /// Called when the IsEnabled property changes. + /// + /// Source of the event. + /// Event arguments. + private void OnIsEnabledChanged(object sender, DependencyPropertyChangedEventArgs e) + { + ChangeVisualState(true); + } + + /// + /// Called when the Loaded event is raised. + /// + /// Source of the event. + /// Event arguments. + private void OnLoaded(object sender, RoutedEventArgs e) + { + ChangeVisualState(false); + } + + /// + /// Changes to the correct visual state(s) for the control. + /// + /// True to use transitions; otherwise false. + protected virtual void ChangeVisualState(bool useTransitions) + { + if (!IsEnabled) + { + VisualStateManager.GoToState(this, "Disabled", useTransitions); + } + else + { + VisualStateManager.GoToState(this, "Normal", useTransitions); + } + + if (_isFocused && IsEnabled) + { + VisualStateManager.GoToState(this, "Focused", useTransitions); + } + else + { + VisualStateManager.GoToState(this, "Unfocused", useTransitions); + } + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/ContextMenu/Separator.cs b/Microsoft.Phone.Controls.Toolkit/ContextMenu/Separator.cs new file mode 100644 index 0000000..2f250df --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/ContextMenu/Separator.cs @@ -0,0 +1,28 @@ +// (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.Windows.Controls; + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// Control that is used to separate items in items controls. + /// + /// Preview + public class Separator : Control + { + /// + /// Initializes a new instance of the Separator class. + /// + public Separator() + { + DefaultStyleKey = typeof(Separator); + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/Data/HierarchicalDataTemplate.cs b/Microsoft.Phone.Controls.Toolkit/Data/HierarchicalDataTemplate.cs new file mode 100644 index 0000000..3aa1a69 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Data/HierarchicalDataTemplate.cs @@ -0,0 +1,117 @@ +// (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.Diagnostics.CodeAnalysis; +using System.Windows.Data; + +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "HierarchicalDataTemplate.get_ItemsSource(), System.Windows.Data.Binding (Silverlight) vs System.Windows.Data.BindingBase (wpf)", Justification = "BindingBase isn't sufficiently rich enough for this purpose. We use Binding instead, just like FrameworkElement.SetBinding.")] + +namespace System.Windows +{ + /// + /// Represents a that supports + /// objects, + /// such as . + /// + /// Stable + public partial class HierarchicalDataTemplate : DataTemplate + { + /// + /// Gets or sets the collection that is used to generate content for the + /// next sublevel in the data hierarchy. + /// + /// + /// The collection that is used to generate content for the next + /// sublevel in the data hierarchy. The default value is null. + /// + public Binding ItemsSource { get; set; } + + #region public DataTemplate ItemTemplate + /// + /// The DataTemplate to apply to the ItemTemplate property on a + /// generated HeaderedItemsControl (such as a MenuItem or a + /// TreeViewItem), to indicate how to display items from the next level + /// in the data hierarchy. + /// + private DataTemplate _itemTemplate; + + /// + /// Gets a value indicating whether the ItemTemplate property was set on + /// the template. + /// + internal bool IsItemTemplateSet { get; private set; } + + /// + /// Gets or sets the to + /// apply to the + /// + /// property on a generated + /// , such + /// as a , to + /// indicate how to display items from the next sublevel in the data + /// hierarchy. + /// + /// + /// The to apply to the + /// + /// property on a generated + /// , such + /// as a , to + /// indicate how to display items from the next sublevel in the data + /// hierarchy. + /// + public DataTemplate ItemTemplate + { + get { return _itemTemplate; } + set + { + IsItemTemplateSet = true; + _itemTemplate = value; + } + } + #endregion public DataTemplate ItemTemplate + + #region public Style ItemContainerStyle + /// + /// The Style to apply to the ItemContainerStyle property on a generated + /// HeaderedItemsControl (such as a MenuItem or a TreeViewItem), to + /// indicate how to style items from the next level in the data + /// hierarchy. + /// + private Style _itemContainerStyle; + + /// + /// Gets a value indicating whether the ItemContainerStyle property was + /// set on the template. + /// + internal bool IsItemContainerStyleSet { get; private set; } + + /// + /// Gets or sets the that is + /// applied to the item container for each child item. + /// + /// + /// The style that is applied to the item container for each child item. + /// + public Style ItemContainerStyle + { + get { return _itemContainerStyle; } + set + { + IsItemContainerStyleSet = true; + _itemContainerStyle = value; + } + } + #endregion public Style ItemContainerStyle + + /// + /// Initializes a new instance of the + /// class. + /// + public HierarchicalDataTemplate() + { + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/DailyDateTimeConverter.cs b/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/DailyDateTimeConverter.cs new file mode 100644 index 0000000..3c7ea08 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/DailyDateTimeConverter.cs @@ -0,0 +1,80 @@ +// (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.Globalization; +using System.Windows.Data; +using System.Text; + +namespace Microsoft.Phone.Controls +{ + /// + /// Date and time converter for daily feeds. + /// + /// Preview + public class DailyDateTimeConverter : IValueConverter + { + /// + /// Converts a + /// + /// object into a string appropriately formatted for daily feeds. + /// This format can be found in the call history. + /// + /// The given date and time. + /// + /// The type corresponding to the binding property, which must be of + /// . + /// + /// (Not used). + /// (Not used). + /// The given date and time as a string. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + // Target value must be a System.DateTime object. + if (!(value is DateTime)) + { + throw new ArgumentException(Properties.Resources.InvalidDateTimeArgument); + } + + StringBuilder result = new StringBuilder(string.Empty); + + DateTime given = (DateTime)value; + + DateTime current = DateTime.Now; + + if (DateTimeFormatHelper.IsFutureDateTime(current, given)) + { + // Future dates and times are not supported. + throw new NotSupportedException(Properties.Resources.NonSupportedDateTime); + } + + if (DateTimeFormatHelper.IsAtLeastOneWeekOld(current, given)) + { + result.Append(DateTimeFormatHelper.GetShortDate(given)); + } + else + { + result.AppendFormat(CultureInfo.CurrentCulture, "{0} {1}", + DateTimeFormatHelper.GetAbbreviatedDay(given), + DateTimeFormatHelper.GetSuperShortTime(given)); + } + + return result.ToString(); + } + + /// + /// Not implemented. + /// + /// (Not used). + /// (Not used). + /// (Not used). + /// (Not used). + /// null + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/DateTimeFormatHelper.cs b/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/DateTimeFormatHelper.cs new file mode 100644 index 0000000..d5d67ea --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/DateTimeFormatHelper.cs @@ -0,0 +1,415 @@ +// (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.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; + +namespace Microsoft.Phone.Controls +{ + /// + /// Helper methods and constants for the date time converters. + /// + internal static class DateTimeFormatHelper + { + /// + /// An hour defined in minutes. + /// + private const double Hour = 60.0; + + /// + /// A day defined in minutes. + /// + private const double Day = 24 * Hour; + + /// + /// Format pattern for single-character meridiem designators. + /// e.g. 4:30p + /// + private const string SingleMeridiemDesignator = "t"; + + /// + /// Format pattern for double-character meridiem designators. + /// e.g. 4:30 p.m. + /// + private const string DoubleMeridiemDesignator = "tt"; + + /// + /// Date and time information used when getting the super short time + /// pattern. Syncs with the current culture. + /// + private static DateTimeFormatInfo formatInfo_GetSuperShortTime = null; + + /// + /// Date and time information used when getting the month and day + /// pattern. Syncs with the current culture. + /// + private static DateTimeFormatInfo formatInfo_GetMonthAndDay = null; + + /// + /// Date and time information used when getting the short time + /// pattern. Syncs with the current culture. + /// + private static DateTimeFormatInfo formatInfo_GetShortTime = null; + + /// + /// Lock object used to delimit a critical region when getting + /// the super short time pattern. + /// + private static Object lock_GetSuperShortTime = new Object(); + + /// + /// Lock object used to delimit a critical region when getting + /// the month and day pattern. + /// + private static Object lock_GetMonthAndDay = new Object(); + + /// + /// Lock object used to delimit a critical region when getting + /// the short time pattern. + /// + private static Object lock_GetShortTime = new Object(); + + /// + /// Regular expression used to get the month and day pattern. + /// + private static readonly Regex rxMonthAndDay = new Regex("(d{1,2}[^A-Za-z]M{1,3})|(M{1,3}[^A-Za-z]d{1,2})"); + + /// + /// Regular expression used to get the seconds pattern with separator. + /// + private static readonly Regex rxSeconds = new Regex("([^A-Za-z]s{1,2})"); + + /// + /// Gets the number representing the day of the week from a given + /// + /// object, relative to the first day of the week + /// according to the current culture. + /// + /// Date information. + /// + /// A number representing the day of the week. + /// e.g. if Monday is the first day of the week according to the + /// relative culture, Monday returns 0, Tuesday returns 1, etc. + /// + public static int GetRelativeDayOfWeek(DateTime dt) + { + return ((int)dt.DayOfWeek - (int)CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek + 7) % 7; + } + + #region DateTime comparison methods + + /// + /// Indicates if a given + /// + /// object represents a future instance when compared to another + /// + /// object. + /// + /// Relative date and time. + /// Given date and time. + /// + /// True if given date and time represents a future instance. + /// + public static bool IsFutureDateTime(DateTime relative, DateTime given) + { + return relative < given; + } + + /// + /// Indicates if a given + /// + /// object represents a past year when compared to another + /// + /// object. + /// + /// Relative date and time. + /// Given date and time. + /// + /// True if given date and time is set to a past year. + /// + public static bool IsAnOlderYear(DateTime relative, DateTime given) + { + return relative.Year > given.Year; + } + + /// + /// Indicates if a given + /// + /// object represents a past week when compared to another + /// + /// object. + /// + /// Relative date and time. + /// Given date and time. + /// + /// True if given date and time is set to a past week. + /// + public static bool IsAnOlderWeek(DateTime relative, DateTime given) + { + if (IsAtLeastOneWeekOld(relative, given)) + { + return true; + } + else + { + return GetRelativeDayOfWeek(given) > GetRelativeDayOfWeek(relative); + } + } + + /// + /// Indicates if a given + /// + /// object is at least one week behind from another + /// + /// object. + /// + /// Relative date and time. + /// Given date and time. + /// + /// True if given date and time is at least one week behind. + /// + public static bool IsAtLeastOneWeekOld(DateTime relative, DateTime given) + { + return ((int)(relative - given).TotalMinutes >= 7 * Day); + } + + /// + /// Indicates if a given + /// + /// object corresponds to a past day in the same week as another + /// + /// object. + /// + /// Relative date and time. + /// Given date and time. + /// + /// True if given date and time is a past day in the relative week. + /// + public static bool IsPastDayOfWeek(DateTime relative, DateTime given) + { + return GetRelativeDayOfWeek(relative) > GetRelativeDayOfWeek(given); + } + + /// + /// Indicates if a given + /// + /// object corresponds to a past day in the same week as another + /// + /// object and at least three hours behind it. + /// + /// Relative date and time. + /// Given date and time. + /// + /// True if given date and time is a past day in the relative week + /// and at least three hours behind the relative time. + /// + public static bool IsPastDayOfWeekWithWindow(DateTime relative, DateTime given) + { + return IsPastDayOfWeek(relative, given) && ((int)(relative - given).TotalMinutes > 3 * Hour); + } + + #endregion + + #region Culture awareness methods + + /// + /// Identifies if the current culture is set to Japanese. + /// + /// + /// True if current culture is set to Japanese. + /// + public static bool IsCurrentCultureJapanese() + { + return (CultureInfo.CurrentCulture.Name.StartsWith("ja", StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Identifies if the current culture is set to Korean. + /// + /// + /// True if current culture is set to Korean. + /// + public static bool IsCurrentCultureKorean() + { + return (CultureInfo.CurrentCulture.Name.StartsWith("ko", StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Identifies if the current culture is set to Turkish. + /// + /// + /// True if current culture is set to Turkish. + /// + public static bool IsCurrentCultureTurkish() + { + return (CultureInfo.CurrentCulture.Name.StartsWith("tr", StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Identifies if the current culture is set to Hungarian. + /// + /// + /// True if current culture is set to Hungarian. + /// + public static bool IsCurrentCultureHungarian() + { + return (CultureInfo.CurrentCulture.Name.StartsWith("hu", StringComparison.OrdinalIgnoreCase)); + } + + /// + /// Identifies if the current UI culture is set to French. + /// + /// + /// True if current UI culture is set to French. + /// + public static bool IsCurrentUICultureFrench() + { + return (CultureInfo.CurrentUICulture.Name.Equals("fr-FR", StringComparison.Ordinal)); + } + + #endregion + + #region String generating methods + + /// + /// Gets the abbreviated day from a + /// + /// object. + /// + /// Date information. + /// e.g. "Mon" for Monday when en-US. + public static string GetAbbreviatedDay(DateTime dt) + { + if (DateTimeFormatHelper.IsCurrentCultureJapanese() || DateTimeFormatHelper.IsCurrentCultureKorean()) + { + return "(" + dt.ToString("ddd", CultureInfo.CurrentCulture) + ")"; + } + else + { + return dt.ToString("ddd", CultureInfo.CurrentCulture); + } + } + + /// + /// Gets the time from a + /// + /// object in short Metro format. + /// + /// + /// Metro design guidelines normalize strings to lowercase. + /// + /// Time information. + /// "10:20p" for 10:20 p.m. when en-US. + [SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Metro design guidelines normalize strings to lowercase.")] + public static string GetSuperShortTime(DateTime dt) + { + if (formatInfo_GetSuperShortTime == null) + { + lock (lock_GetSuperShortTime) + { + StringBuilder result = new StringBuilder(string.Empty); + string seconds; + + formatInfo_GetSuperShortTime = (DateTimeFormatInfo)CultureInfo.CurrentCulture.DateTimeFormat.Clone(); + + result.Append(formatInfo_GetSuperShortTime.LongTimePattern); + seconds = rxSeconds.Match(result.ToString()).Value; + result.Replace(" ", string.Empty); + result.Replace(seconds, string.Empty); + if (!(DateTimeFormatHelper.IsCurrentCultureJapanese() + || DateTimeFormatHelper.IsCurrentCultureKorean() + || DateTimeFormatHelper.IsCurrentCultureHungarian())) + { + result.Replace(DoubleMeridiemDesignator, SingleMeridiemDesignator); + } + + formatInfo_GetSuperShortTime.ShortTimePattern = result.ToString(); + } + } + + return dt.ToString("t", formatInfo_GetSuperShortTime).ToLowerInvariant(); + } + + /// + /// Gets the month and day from a + /// + /// object. + /// + /// Date information. + /// "05/24" for May 24th when en-US. + public static string GetMonthAndDay(DateTime dt) + { + if (formatInfo_GetMonthAndDay == null) + { + lock (lock_GetMonthAndDay) + { + StringBuilder result = new StringBuilder(string.Empty); + + formatInfo_GetMonthAndDay = (DateTimeFormatInfo)CultureInfo.CurrentCulture.DateTimeFormat.Clone(); + + result.Append(rxMonthAndDay.Match(formatInfo_GetMonthAndDay.ShortDatePattern).Value); + if (result.ToString().Contains(".")) + { + result.Append("."); + } + + formatInfo_GetMonthAndDay.ShortDatePattern = result.ToString(); + } + } + + return dt.ToString("d", formatInfo_GetMonthAndDay); + } + + /// + /// Gets the date in short pattern from a + /// + /// object. + /// + /// Date information. + /// + /// Date in short pattern as defined by the system locale. + /// + public static string GetShortDate(DateTime dt) + { + return dt.ToString("d", CultureInfo.CurrentCulture); + } + + /// + /// Gets the time in short pattern from a + /// + /// object. + /// + /// Date information. + /// + /// Time in short pattern as defined by the system locale. + /// + public static string GetShortTime(DateTime dt) + { + if (formatInfo_GetShortTime == null) + { + lock (lock_GetShortTime) + { + StringBuilder result = new StringBuilder(string.Empty); + string seconds; + + formatInfo_GetShortTime = (DateTimeFormatInfo)CultureInfo.CurrentCulture.DateTimeFormat.Clone(); + + result.Append(formatInfo_GetSuperShortTime.LongTimePattern); + seconds = rxSeconds.Match(result.ToString()).Value; + result.Replace(seconds, string.Empty); + + formatInfo_GetShortTime.ShortTimePattern = result.ToString(); + } + } + + return dt.ToString("t", formatInfo_GetShortTime); + } + + #endregion + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/FullViewDateTimeConverter.cs b/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/FullViewDateTimeConverter.cs new file mode 100644 index 0000000..429870b --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/FullViewDateTimeConverter.cs @@ -0,0 +1,92 @@ +// (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.Globalization; +using System.Windows.Data; +using System.Text; + +namespace Microsoft.Phone.Controls +{ + /// + /// Date and time converter to display information in full format. + /// + /// Preview + public class FullViewDateTimeConverter : IValueConverter + { + /// + /// Converts a + /// + /// object into a string appropriately formatted to + /// display full date and time information. + /// This format can be found in email. + /// + /// + /// This format never displays the year. + /// + /// The given date and time. + /// + /// The type corresponding to the binding property, which must be of + /// . + /// + /// (Not used). + /// (Not used). + /// The given date and time as a string. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + // Target value must be a System.DateTime object. + if (!(value is DateTime)) + { + throw new ArgumentException(Properties.Resources.InvalidDateTimeArgument); + } + + StringBuilder result = new StringBuilder(string.Empty); + + DateTime given = (DateTime)value; + + if (DateTimeFormatHelper.IsCurrentCultureJapanese() || DateTimeFormatHelper.IsCurrentCultureKorean()) + { + result.AppendFormat(CultureInfo.CurrentCulture, "{0} {1} {2}", + DateTimeFormatHelper.GetMonthAndDay(given), + DateTimeFormatHelper.GetAbbreviatedDay(given), + DateTimeFormatHelper.GetShortTime(given)); + } + else if (DateTimeFormatHelper.IsCurrentCultureTurkish()) + { + result.AppendFormat(CultureInfo.CurrentCulture, "{0}, {1} {2}", + DateTimeFormatHelper.GetMonthAndDay(given), + DateTimeFormatHelper.GetAbbreviatedDay(given), + DateTimeFormatHelper.GetShortTime(given)); + } + else + { + result.AppendFormat(CultureInfo.CurrentCulture, "{0} {1}, {2}", + DateTimeFormatHelper.GetAbbreviatedDay(given), + DateTimeFormatHelper.GetMonthAndDay(given), + DateTimeFormatHelper.GetShortTime(given)); + } + + if (DateTimeFormatHelper.IsCurrentUICultureFrench()) + { + result.Replace(",", string.Empty); + } + + return result.ToString(); + } + + /// + /// Not implemented. + /// + /// (Not used). + /// (Not used). + /// (Not used). + /// (Not used). + /// null + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/HourlyDateTimeConverter.cs b/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/HourlyDateTimeConverter.cs new file mode 100644 index 0000000..4fb65f3 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/HourlyDateTimeConverter.cs @@ -0,0 +1,98 @@ +// (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.Globalization; +using System.Windows.Data; +using System.Text; + +namespace Microsoft.Phone.Controls +{ + /// + /// Date and time converter for hourly feeds. + /// + /// Preview + public class HourlyDateTimeConverter : IValueConverter + { + /// + /// Converts a + /// + /// object into a string appropriately formatted for hourly feeds. + /// This format can be found in messaging. + /// + /// The given date and time. + /// + /// The type corresponding to the binding property, which must be of + /// . + /// + /// (Not used). + /// (Not used). + /// The given date and time as a string. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + // Target value must be a System.DateTime object. + if (!(value is DateTime)) + { + throw new ArgumentException(Properties.Resources.InvalidDateTimeArgument); + } + + StringBuilder result = new StringBuilder(string.Empty); + + DateTime given = (DateTime)value; + + DateTime current = DateTime.Now; + + if (DateTimeFormatHelper.IsFutureDateTime(current, given)) + { + // Future dates and times are not supported. + throw new NotSupportedException(Properties.Resources.NonSupportedDateTime); + } + + if (DateTimeFormatHelper.IsAnOlderYear(current, given)) + { + result.AppendFormat(CultureInfo.CurrentCulture, "{0}, {1}", + DateTimeFormatHelper.GetShortDate(given), + DateTimeFormatHelper.GetSuperShortTime(given)); + } + else if (DateTimeFormatHelper.IsAnOlderWeek(current, given)) + { + result.AppendFormat(CultureInfo.CurrentCulture, "{0}, {1}", + DateTimeFormatHelper.GetMonthAndDay(given), + DateTimeFormatHelper.GetSuperShortTime(given)); + } + else if (DateTimeFormatHelper.IsPastDayOfWeekWithWindow(current, given)) + { + result.AppendFormat(CultureInfo.CurrentCulture, "{0}, {1}", + DateTimeFormatHelper.GetAbbreviatedDay(given), + DateTimeFormatHelper.GetSuperShortTime(given)); + } + else + { + // Given day time is today. + result.Append(DateTimeFormatHelper.GetSuperShortTime(given)); + } + + if (DateTimeFormatHelper.IsCurrentUICultureFrench()) + { + result.Replace(",", string.Empty); + } + + return result.ToString(); + } + + /// + /// Not implemented. + /// + /// (Not used). + /// (Not used). + /// (Not used). + /// (Not used). + /// null + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/ListViewDateTimeConverter.cs b/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/ListViewDateTimeConverter.cs new file mode 100644 index 0000000..593f5b8 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/ListViewDateTimeConverter.cs @@ -0,0 +1,85 @@ +// (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.Globalization; +using System.Windows.Data; + +namespace Microsoft.Phone.Controls +{ + /// + /// Date and time converter to be used in list-views. + /// + /// Preview + public class ListViewDateTimeConverter : IValueConverter + { + /// + /// Converts a + /// + /// object into a string appropriately formatted for list-views. + /// This format can be found in email. + /// + /// + /// This format never displays the year. + /// + /// The given date and time. + /// + /// The type corresponding to the binding property, which must be of + /// . + /// + /// (Not used). + /// (Not used). + /// The given date and time as a string. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + // Target value must be a System.DateTime object. + if (!(value is DateTime)) + { + throw new ArgumentException(Properties.Resources.InvalidDateTimeArgument); + } + + string result; + + DateTime given = (DateTime)value; + + DateTime current = DateTime.Now; + + if (DateTimeFormatHelper.IsFutureDateTime(current, given)) + { + // Future dates and times are not supported. + throw new NotSupportedException(Properties.Resources.NonSupportedDateTime); + } + + if (DateTimeFormatHelper.IsAnOlderWeek(current, given)) + { + result = DateTimeFormatHelper.GetMonthAndDay(given); + } + else if (DateTimeFormatHelper.IsPastDayOfWeek(current, given)) + { + result = DateTimeFormatHelper.GetAbbreviatedDay(given); + } + else + { + // Given day time is today. + result = DateTimeFormatHelper.GetSuperShortTime(given); + } + + return result; + } + + /// + /// Not implemented. + /// + /// (Not used). + /// (Not used). + /// (Not used). + /// (Not used). + /// null + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/RelativeTimeConverter.cs b/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/RelativeTimeConverter.cs new file mode 100644 index 0000000..fc6a5c1 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/RelativeTimeConverter.cs @@ -0,0 +1,350 @@ +// (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.Globalization; +using System.Windows.Data; +using Microsoft.Phone.Controls.LocalizedResources; + +namespace Microsoft.Phone.Controls +{ + /// + /// Time converter to display elapsed time relatively to the present. + /// + /// Preview + public class RelativeTimeConverter : IValueConverter + { + /// + /// A minute defined in seconds. + /// + private const double Minute = 60.0; + + /// + /// An hour defined in seconds. + /// + private const double Hour = 60.0 * Minute; + + /// + /// A day defined in seconds. + /// + private const double Day = 24 * Hour; + + /// + /// A week defined in seconds. + /// + private const double Week = 7 * Day; + + /// + /// A month defined in seconds. + /// + private const double Month = 30.5 * Day; + + /// + /// A year defined in seconds. + /// + private const double Year = 365 * Day; + + /// + /// Abbreviation for the default culture used by resources files. + /// + private const string DefaultCulture = "en-US"; + + /// + /// Four different strings to express hours in plural. + /// + private string[] PluralHourStrings; + + /// + /// Four different strings to express minutes in plural. + /// + private string[] PluralMinuteStrings; + + /// + /// Four different strings to express seconds in plural. + /// + private string[] PluralSecondStrings; + + /// + /// Resources use the culture in the system locale by default. + /// The converter must use the culture specified the ConverterCulture. + /// The ConverterCulture defaults to en-US when not specified. + /// Thus, change the resources culture only if ConverterCulture is set. + /// + /// The culture to use in the converter. + private void SetLocalizationCulture(CultureInfo culture) + { + if (!culture.Name.Equals(DefaultCulture, StringComparison.Ordinal)) + { + ControlResources.Culture = culture; + } + + PluralHourStrings = new string[4] { + ControlResources.XHoursAgo_2To4, + ControlResources.XHoursAgo_EndsIn1Not11, + ControlResources.XHoursAgo_EndsIn2To4Not12To14, + ControlResources.XHoursAgo_Other + }; + + PluralMinuteStrings = new string[4] { + ControlResources.XMinutesAgo_2To4, + ControlResources.XMinutesAgo_EndsIn1Not11, + ControlResources.XMinutesAgo_EndsIn2To4Not12To14, + ControlResources.XMinutesAgo_Other + }; + + PluralSecondStrings = new string[4] { + ControlResources.XSecondsAgo_2To4, + ControlResources.XSecondsAgo_EndsIn1Not11, + ControlResources.XSecondsAgo_EndsIn2To4Not12To14, + ControlResources.XSecondsAgo_Other + }; + } + + /// + /// Returns a localized text string to express months in plural. + /// + /// Number of months. + /// Localized text string. + private static string GetPluralMonth(int month) + { + if (month >= 2 && month <= 4) + { + return string.Format(CultureInfo.CurrentUICulture, ControlResources.XMonthsAgo_2To4, month.ToString(ControlResources.Culture)); + } + else if (month >= 5 && month <= 12) + { + return string.Format(CultureInfo.CurrentUICulture, ControlResources.XMonthsAgo_5To12, month.ToString(ControlResources.Culture)); + } + else + { + throw new ArgumentException(Properties.Resources.InvalidNumberOfMonths); + } + } + + /// + /// Returns a localized text string to express time units in plural. + /// + /// + /// Number of time units, e.g. 5 for five months. + /// + /// + /// Resources related to the specified time unit. + /// + /// Localized text string. + private static string GetPluralTimeUnits(int units, string[] resources) + { + int modTen = units % 10; + int modHundred = units % 100; + + if (units <= 1) + { + throw new ArgumentException(Properties.Resources.InvalidNumberOfTimeUnits); + } + else if (units >= 2 && units <= 4) + { + return string.Format(CultureInfo.CurrentUICulture, resources[0], units.ToString(ControlResources.Culture)); + } + else if (modTen == 1 && modHundred != 11) + { + return string.Format(CultureInfo.CurrentUICulture, resources[1], units.ToString(ControlResources.Culture)); + } + else if ((modTen >= 2 && modTen <= 4) && !(modHundred >= 12 && modHundred <= 14)) + { + return string.Format(CultureInfo.CurrentUICulture, resources[2], units.ToString(ControlResources.Culture)); + } + else + { + return string.Format(CultureInfo.CurrentUICulture, resources[3], units.ToString(ControlResources.Culture)); + } + } + + /// + /// Returns a localized text string for the day of week. + /// + /// Day of week. + /// Localized text string. + private static string GetDayOfWeek(DayOfWeek dow) + { + string result; + + switch (dow) + { + case DayOfWeek.Monday: + result = ControlResources.Monday; + break; + case DayOfWeek.Tuesday: + result = ControlResources.Tuesday; + break; + case DayOfWeek.Wednesday: + result = ControlResources.Wednesday; + break; + case DayOfWeek.Thursday: + result = ControlResources.Thursday; + break; + case DayOfWeek.Friday: + result = ControlResources.Friday; + break; + case DayOfWeek.Saturday: + result = ControlResources.Saturday; + break; + case DayOfWeek.Sunday: + result = ControlResources.Sunday; + break; + default: + result = ControlResources.Sunday; + break; + } + + return result; + } + + /// + /// Returns a localized text string to express "on {0}" + /// where {0} is a day of the week, e.g. Sunday. + /// + /// Day of week. + /// Localized text string. + private static string GetOnDayOfWeek(DayOfWeek dow) + { + if (dow == DayOfWeek.Tuesday) + { + return string.Format(CultureInfo.CurrentUICulture, ControlResources.OnDayOfWeek_Tuesday, GetDayOfWeek(dow)); + } + else + { + return string.Format(CultureInfo.CurrentUICulture, ControlResources.OnDayOfWeek_Other, GetDayOfWeek(dow)); + } + } + + /// + /// Converts a + /// + /// object into a string the represents the elapsed time + /// relatively to the present. + /// + /// The given date and time. + /// + /// The type corresponding to the binding property, which must be of + /// . + /// + /// (Not used). + /// + /// The culture to use in the converter. + /// When not specified, the converter uses the current culture + /// as specified by the system locale. + /// + /// The given date and time as a string. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + // Target value must be a System.DateTime object. + if (!(value is DateTime)) + { + throw new ArgumentException(Properties.Resources.InvalidDateTimeArgument); + } + + string result; + + DateTime given = ((DateTime)value).ToLocalTime(); + + DateTime current = DateTime.Now; + + TimeSpan difference = current - given; + + SetLocalizationCulture(culture); + + if (DateTimeFormatHelper.IsFutureDateTime(current, given)) + { + // Future dates and times are not supported, but to prevent crashing an app + // if the time they receive from a server is slightly ahead of the phone's clock + // we'll just default to the minimum, which is "2 seconds ago". + result = GetPluralTimeUnits(2, PluralSecondStrings); + } + + if (difference.TotalSeconds > Year) + { + // "over a year ago" + result = ControlResources.OverAYearAgo; + } + else if (difference.TotalSeconds > (1.5 * Month)) + { + // "x months ago" + int nMonths = (int)((difference.TotalSeconds + Month / 2) / Month); + result = GetPluralMonth(nMonths); + } + else if (difference.TotalSeconds >= (3.5 * Week)) + { + // "about a month ago" + result = ControlResources.AboutAMonthAgo; + } + else if (difference.TotalSeconds >= Week) + { + int nWeeks = (int)(difference.TotalSeconds / Week); + if (nWeeks > 1) + { + // "x weeks ago" + result = string.Format(CultureInfo.CurrentUICulture, ControlResources.XWeeksAgo_2To4, nWeeks.ToString(ControlResources.Culture)); + } + else + { + // "about a week ago" + result = ControlResources.AboutAWeekAgo; + } + } + else if (difference.TotalSeconds >= (5 * Day)) + { + // "last " + result = string.Format(CultureInfo.CurrentUICulture, ControlResources.LastDayOfWeek, GetDayOfWeek(given.DayOfWeek)); + } + else if (difference.TotalSeconds >= Day) + { + // "on " + result = GetOnDayOfWeek(given.DayOfWeek); + } + else if (difference.TotalSeconds >= (2 * Hour)) + { + // "x hours ago" + int nHours = (int)(difference.TotalSeconds / Hour); + result = GetPluralTimeUnits(nHours, PluralHourStrings); + } + else if (difference.TotalSeconds >= Hour) + { + // "about an hour ago" + result = ControlResources.AboutAnHourAgo; + } + else if (difference.TotalSeconds >= (2 * Minute)) + { + // "x minutes ago" + int nMinutes = (int)(difference.TotalSeconds / Minute); + result = GetPluralTimeUnits(nMinutes, PluralMinuteStrings); + } + else if (difference.TotalSeconds >= Minute) + { + // "about a minute ago" + result = ControlResources.AboutAMinuteAgo; + } + else + { + // "x seconds ago" or default to "2 seconds ago" if less than two seconds. + int nSeconds = ((int)difference.TotalSeconds > 1.0) ? (int)difference.TotalSeconds : 2; + result = GetPluralTimeUnits(nSeconds, PluralSecondStrings); + } + + return result; + } + + /// + /// Not implemented. + /// + /// (Not used). + /// (Not used). + /// (Not used). + /// (Not used). + /// null + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/ThreadDateTimeConverter.cs b/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/ThreadDateTimeConverter.cs new file mode 100644 index 0000000..87a550b --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimeConverters/ThreadDateTimeConverter.cs @@ -0,0 +1,85 @@ +// (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.Globalization; +using System.Windows.Data; + +namespace Microsoft.Phone.Controls +{ + /// + /// Date and time converter for threads. + /// + /// Preview + public class ThreadDateTimeConverter : IValueConverter + { + /// + /// Converts a + /// + /// object into a string appropriately formatted for threads. + /// This format can be found in messaging. + /// + /// + /// This format never displays the year. + /// + /// The given date and time. + /// + /// The type corresponding to the binding property, which must be of + /// . + /// + /// (Not used). + /// (Not used). + /// The given date and time as a string. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + // Target value must be a System.DateTime object. + if (!(value is DateTime)) + { + throw new ArgumentException(Properties.Resources.InvalidDateTimeArgument); + } + + string result; + + DateTime given = (DateTime)value; + + DateTime current = DateTime.Now; + + if (DateTimeFormatHelper.IsFutureDateTime(current, given)) + { + // Future dates and times are not supported. + throw new NotSupportedException(Properties.Resources.NonSupportedDateTime); + } + + if (DateTimeFormatHelper.IsAnOlderWeek(current, given)) + { + result = DateTimeFormatHelper.GetMonthAndDay(given); + } + else if (DateTimeFormatHelper.IsPastDayOfWeekWithWindow(current, given)) + { + result = DateTimeFormatHelper.GetAbbreviatedDay(given); + } + else + { + // Given day time is today. + result = DateTimeFormatHelper.GetSuperShortTime(given); + } + + return result; + } + + /// + /// Not implemented. + /// + /// (Not used). + /// (Not used). + /// (Not used). + /// (Not used). + /// null + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DataSource.cs b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DataSource.cs new file mode 100644 index 0000000..e785339 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DataSource.cs @@ -0,0 +1,134 @@ +// (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.Windows.Controls; +using Microsoft.Phone.Controls.Primitives; + +namespace Microsoft.Phone.Controls +{ + abstract class DataSource : ILoopingSelectorDataSource + { + private DateTimeWrapper _selectedItem; + + public object GetNext(object relativeTo) + { + DateTime? next = GetRelativeTo(((DateTimeWrapper)relativeTo).DateTime, 1); + return next.HasValue ? new DateTimeWrapper(next.Value) : null; + } + + public object GetPrevious(object relativeTo) + { + DateTime? next = GetRelativeTo(((DateTimeWrapper)relativeTo).DateTime, -1); + return next.HasValue ? new DateTimeWrapper(next.Value) : null; + } + + protected abstract DateTime? GetRelativeTo(DateTime relativeDate, int delta); + + public object SelectedItem + { + get { return _selectedItem; } + set + { + if (value != _selectedItem) + { + DateTimeWrapper valueWrapper = (DateTimeWrapper)value; + if ((null == valueWrapper) || (null == _selectedItem) || (valueWrapper.DateTime != _selectedItem.DateTime)) + { + object previousSelectedItem = _selectedItem; + _selectedItem = valueWrapper; + var handler = SelectionChanged; + if (null != handler) + { + handler(this, new SelectionChangedEventArgs(new object[] { previousSelectedItem }, new object[] { _selectedItem })); + } + } + } + } + } + + public event EventHandler SelectionChanged; + } + + class YearDataSource : DataSource + { + protected override DateTime? GetRelativeTo(DateTime relativeDate, int delta) + { + if ((1601 == relativeDate.Year) || (3000 == relativeDate.Year)) + { + return null; + } + int nextYear = relativeDate.Year + delta; + int nextDay = Math.Min(relativeDate.Day, DateTime.DaysInMonth(nextYear, relativeDate.Month)); + return new DateTime(nextYear, relativeDate.Month, nextDay, relativeDate.Hour, relativeDate.Minute, relativeDate.Second); + } + } + + class MonthDataSource : DataSource + { + protected override DateTime? GetRelativeTo(DateTime relativeDate, int delta) + { + int monthsInYear = 12; + int nextMonth = ((monthsInYear + relativeDate.Month - 1 + delta) % monthsInYear) + 1; + int nextDay = Math.Min(relativeDate.Day, DateTime.DaysInMonth(relativeDate.Year, nextMonth)); + return new DateTime(relativeDate.Year, nextMonth, nextDay, relativeDate.Hour, relativeDate.Minute, relativeDate.Second); + } + } + + class DayDataSource : DataSource + { + protected override DateTime? GetRelativeTo(DateTime relativeDate, int delta) + { + int daysInMonth = DateTime.DaysInMonth(relativeDate.Year, relativeDate.Month); + int nextDay = ((daysInMonth + relativeDate.Day - 1 + delta) % daysInMonth) + 1; + return new DateTime(relativeDate.Year, relativeDate.Month, nextDay, relativeDate.Hour, relativeDate.Minute, relativeDate.Second); + } + } + + class TwelveHourDataSource : DataSource + { + protected override DateTime? GetRelativeTo(DateTime relativeDate, int delta) + { + int hoursInHalfDay = 12; + int nextHour = (hoursInHalfDay + relativeDate.Hour + delta) % hoursInHalfDay; + nextHour += hoursInHalfDay <= relativeDate.Hour ? hoursInHalfDay : 0; + return new DateTime(relativeDate.Year, relativeDate.Month, relativeDate.Day, nextHour, relativeDate.Minute, 0); + } + } + + class MinuteDataSource : DataSource + { + protected override DateTime? GetRelativeTo(DateTime relativeDate, int delta) + { + int minutesInHour = 60; + int nextMinute = (minutesInHour + relativeDate.Minute + delta) % minutesInHour; + return new DateTime(relativeDate.Year, relativeDate.Month, relativeDate.Day, relativeDate.Hour, nextMinute, 0); + } + } + + class AmPmDataSource : DataSource + { + protected override DateTime? GetRelativeTo(DateTime relativeDate, int delta) + { + int hoursInDay = 24; + int nextHour = relativeDate.Hour + (delta * (hoursInDay / 2)); + if ((nextHour < 0) || (hoursInDay <= nextHour)) + { + return null; + } + return new DateTime(relativeDate.Year, relativeDate.Month, relativeDate.Day, nextHour, relativeDate.Minute, 0); + } + } + + class TwentyFourHourDataSource : DataSource + { + protected override DateTime? GetRelativeTo(DateTime relativeDate, int delta) + { + int hoursInDay = 24; + int nextHour = (hoursInDay + relativeDate.Hour + delta) % hoursInDay; + return new DateTime(relativeDate.Year, relativeDate.Month, relativeDate.Day, nextHour, relativeDate.Minute, 0); + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DatePicker.cs b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DatePicker.cs new file mode 100644 index 0000000..9db99da --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DatePicker.cs @@ -0,0 +1,25 @@ +// (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; + +namespace Microsoft.Phone.Controls +{ + /// + /// Represents a control that allows the user to choose a date (day/month/year). + /// + /// Preview + public class DatePicker : DateTimePickerBase + { + /// + /// Initializes a new instance of the DatePicker control. + /// + public DatePicker() + { + DefaultStyleKey = typeof(DatePicker); + Value = DateTime.Now.Date; + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DatePickerPage.xaml b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DatePickerPage.xaml new file mode 100644 index 0000000..2af25b0 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DatePickerPage.xaml @@ -0,0 +1,189 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DatePickerPage.xaml.cs b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DatePickerPage.xaml.cs new file mode 100644 index 0000000..de36f9e --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DatePickerPage.xaml.cs @@ -0,0 +1,63 @@ +// (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.Generic; +using System.Globalization; +using System.Windows; +using Microsoft.Phone.Controls.Primitives; + +namespace Microsoft.Phone.Controls +{ + /// + /// Represents a page used by the DatePicker control that allows the user to choose a date (day/month/year). + /// + public partial class DatePickerPage : DateTimePickerPageBase + { + /// + /// Initializes a new instance of the DatePickerPage control. + /// + public DatePickerPage() + { + InitializeComponent(); + + // Hook up the data sources + PrimarySelector.DataSource = new YearDataSource(); + SecondarySelector.DataSource = new MonthDataSource(); + TertiarySelector.DataSource = new DayDataSource(); + + InitializeDateTimePickerPage(PrimarySelector, SecondarySelector, TertiarySelector); + } + + /// + /// Gets a sequence of LoopingSelector parts ordered according to culture string for date/time formatting. + /// + /// LoopingSelectors ordered by culture-specific priority. + protected override IEnumerable GetSelectorsOrderedByCulturePattern() + { + return GetSelectorsOrderedByCulturePattern( + CultureInfo.CurrentCulture.DateTimeFormat.ShortDatePattern.ToUpperInvariant(), + new char[] { 'Y', 'M', 'D' }, + new LoopingSelector[] { PrimarySelector, SecondarySelector, TertiarySelector }); + } + + /// + /// Handles changes to the page's Orientation property. + /// + /// Event arguments. + protected override void OnOrientationChanged(OrientationChangedEventArgs e) + { + if (null == e) + { + throw new ArgumentNullException("e"); + } + + base.OnOrientationChanged(e); + SystemTrayPlaceholder.Visibility = (0 != (PageOrientation.Portrait & e.Orientation)) ? + Visibility.Visible : + Visibility.Collapsed; + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimePickerBase.cs b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimePickerBase.cs new file mode 100644 index 0000000..aad6563 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimePickerBase.cs @@ -0,0 +1,299 @@ +// (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.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Navigation; +using Microsoft.Phone.Controls.Primitives; + +namespace Microsoft.Phone.Controls +{ + /// + /// Represents a base class for controls that allow the user to choose a date/time. + /// + [TemplatePart(Name = ButtonPartName, Type = typeof(ButtonBase))] + public class DateTimePickerBase : Control + { + private const string ButtonPartName = "DateTimeButton"; + + private ButtonBase _dateButtonPart; + private PhoneApplicationFrame _frame; + private object _frameContentWhenOpened; + private NavigationInTransition _savedNavigationInTransition; + private NavigationOutTransition _savedNavigationOutTransition; + private IDateTimePickerPage _dateTimePickerPage; + + /// + /// Event that is invoked when the Value property changes. + /// + public event EventHandler ValueChanged; + + /// + /// Gets or sets the DateTime value. + /// + [TypeConverter(typeof(TimeTypeConverter))] + [SuppressMessage("Microsoft.Naming", "CA1721:PropertyNamesShouldNotMatchGetMethods", Justification = "Matching the use of Value as a Picker naming convention.")] + public DateTime? Value + { + get { return (DateTime?)GetValue(ValueProperty); } + set { SetValue(ValueProperty, value); } + } + + /// + /// Identifies the Value DependencyProperty. + /// + public static readonly DependencyProperty ValueProperty = DependencyProperty.Register( + "Value", typeof(DateTime?), typeof(DateTimePickerBase), new PropertyMetadata(null, OnValueChanged)); + + private static void OnValueChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((DateTimePickerBase)o).OnValueChanged((DateTime?)e.OldValue, (DateTime?)e.NewValue); + } + + private void OnValueChanged(DateTime? oldValue, DateTime? newValue) + { + UpdateValueString(); + OnValueChanged(new DateTimeValueChangedEventArgs(oldValue, newValue)); + } + + /// + /// Called when the value changes. + /// + /// The event data. + protected virtual void OnValueChanged(DateTimeValueChangedEventArgs e) + { + var handler = ValueChanged; + if (null != handler) + { + handler(this, e); + } + } + + /// + /// Gets the string representation of the selected value. + /// + public string ValueString + { + get { return (string)GetValue(ValueStringProperty); } + private set { SetValue(ValueStringProperty, value); } + } + + /// + /// Identifies the ValueString DependencyProperty. + /// + public static readonly DependencyProperty ValueStringProperty = DependencyProperty.Register( + "ValueString", typeof(string), typeof(DateTimePickerBase), null); + + /// + /// Gets or sets the format string to use when converting the Value property to a string. + /// + public string ValueStringFormat + { + get { return (string)GetValue(ValueStringFormatProperty); } + set { SetValue(ValueStringFormatProperty, value); } + } + + /// + /// Identifies the ValueStringFormat DependencyProperty. + /// + public static readonly DependencyProperty ValueStringFormatProperty = DependencyProperty.Register( + "ValueStringFormat", typeof(string), typeof(DateTimePickerBase), new PropertyMetadata(null, OnValueStringFormatChanged)); + + private static void OnValueStringFormatChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((DateTimePickerBase)o).OnValueStringFormatChanged(/*(string)e.OldValue, (string)e.NewValue*/); + } + + private void OnValueStringFormatChanged(/*string oldValue, string newValue*/) + { + UpdateValueString(); + } + + /// + /// 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(DateTimePickerBase), 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(DateTimePickerBase), null); + + /// + /// Gets or sets the Uri to use for loading the IDateTimePickerPage instance when the control is clicked. + /// + public Uri PickerPageUri + { + get { return (Uri)GetValue(PickerPageUriProperty); } + set { SetValue(PickerPageUriProperty, value); } + } + + /// + /// Identifies the PickerPageUri DependencyProperty. + /// + public static readonly DependencyProperty PickerPageUriProperty = DependencyProperty.Register( + "PickerPageUri", typeof(Uri), typeof(DateTimePickerBase), null); + + /// + /// Gets the fallback value for the ValueStringFormat property. + /// + protected virtual string ValueStringFormatFallback { get { return "{0}"; } } + + /// + /// Initializes a new instance of the DateTimePickerBase control. + /// + public DateTimePickerBase() + { + } + + /// + /// Called when the control's Template is expanded. + /// + public override void OnApplyTemplate() + { + // Unhook from old template + if (null != _dateButtonPart) + { + _dateButtonPart.Click -= OnDateButtonClick; + } + + base.OnApplyTemplate(); + + // Hook up to new template + _dateButtonPart = GetTemplateChild(ButtonPartName) as ButtonBase; + if (null != _dateButtonPart) + { + _dateButtonPart.Click += OnDateButtonClick; + } + } + + private void OnDateButtonClick(object sender, RoutedEventArgs e) + { + OpenPickerPage(); + } + + private void UpdateValueString() + { + ValueString = string.Format(CultureInfo.CurrentCulture, ValueStringFormat ?? ValueStringFormatFallback, Value); + } + + private void OpenPickerPage() + { + if (null == PickerPageUri) + { + throw new ArgumentException("PickerPageUri property must not be null."); + } + + if (null == _frame) + { + // Hook up to necessary events and navigate + _frame = Application.Current.RootVisual as PhoneApplicationFrame; + if (null != _frame) + { + _frameContentWhenOpened = _frame.Content; + + // Save and clear host page transitions for the upcoming "popup" navigation + UIElement frameContentWhenOpenedAsUIElement = _frameContentWhenOpened as UIElement; + if (null != frameContentWhenOpenedAsUIElement) + { + _savedNavigationInTransition = TransitionService.GetNavigationInTransition(frameContentWhenOpenedAsUIElement); + TransitionService.SetNavigationInTransition(frameContentWhenOpenedAsUIElement, null); + _savedNavigationOutTransition = TransitionService.GetNavigationOutTransition(frameContentWhenOpenedAsUIElement); + TransitionService.SetNavigationOutTransition(frameContentWhenOpenedAsUIElement, null); + } + + _frame.Navigated += OnFrameNavigated; + _frame.NavigationStopped += OnFrameNavigationStoppedOrFailed; + _frame.NavigationFailed += OnFrameNavigationStoppedOrFailed; + + _frame.Navigate(PickerPageUri); + } + } + + } + + private void ClosePickerPage() + { + // Unhook from events + if (null != _frame) + { + _frame.Navigated -= OnFrameNavigated; + _frame.NavigationStopped -= OnFrameNavigationStoppedOrFailed; + _frame.NavigationFailed -= OnFrameNavigationStoppedOrFailed; + + // Restore host page transitions for the completed "popup" navigation + UIElement frameContentWhenOpenedAsUIElement = _frameContentWhenOpened as UIElement; + if (null != frameContentWhenOpenedAsUIElement) + { + TransitionService.SetNavigationInTransition(frameContentWhenOpenedAsUIElement, _savedNavigationInTransition); + _savedNavigationInTransition = null; + TransitionService.SetNavigationOutTransition(frameContentWhenOpenedAsUIElement, _savedNavigationOutTransition); + _savedNavigationOutTransition = null; + } + + _frame = null; + _frameContentWhenOpened = null; + } + // Commit the value if available + if (null != _dateTimePickerPage) + { + if(_dateTimePickerPage.Value.HasValue) + { + Value = _dateTimePickerPage.Value.Value; + } + _dateTimePickerPage = null; + } + } + + private void OnFrameNavigated(object sender, NavigationEventArgs e) + { + if (e.Content == _frameContentWhenOpened) + { + // Navigation to original page; close the picker page + ClosePickerPage(); + } + else if (null == _dateTimePickerPage) + { + // Navigation to a new page; capture it and push the value in + _dateTimePickerPage = e.Content as IDateTimePickerPage; + if (null != _dateTimePickerPage) + { + _dateTimePickerPage.Value = Value.GetValueOrDefault(DateTime.Now); + } + } + } + + private void OnFrameNavigationStoppedOrFailed(object sender, EventArgs e) + { + // Abort + ClosePickerPage(); + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimePickerPageBase.cs b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimePickerPageBase.cs new file mode 100644 index 0000000..d300213 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimePickerPageBase.cs @@ -0,0 +1,304 @@ +// (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.Generic; +using System.ComponentModel; +using System.Diagnostics; +using System.Linq; +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.Primitives +{ + /// + /// Represents a base class for pages that work with DateTimePickerBase to allow users to choose a date/time. + /// + public abstract class DateTimePickerPageBase : PhoneApplicationPage, IDateTimePickerPage + { + private const string VisibilityGroupName = "VisibilityStates"; + private const string OpenVisibilityStateName = "Open"; + private const string ClosedVisibilityStateName = "Closed"; + private const string StateKey_Value = "DateTimePickerPageBase_State_Value"; + + private LoopingSelector _primarySelectorPart; + private LoopingSelector _secondarySelectorPart; + private LoopingSelector _tertiarySelectorPart; + private Storyboard _closedStoryboard; + + /// + /// Initializes the DateTimePickerPageBase class; must be called from the subclass's constructor. + /// + /// Primary selector. + /// Secondary selector. + /// Tertiary selector. + protected void InitializeDateTimePickerPage(LoopingSelector primarySelector, LoopingSelector secondarySelector, LoopingSelector tertiarySelector) + { + if (null == primarySelector) + { + throw new ArgumentNullException("primarySelector"); + } + if (null == secondarySelector) + { + throw new ArgumentNullException("secondarySelector"); + } + if (null == tertiarySelector) + { + throw new ArgumentNullException("tertiarySelector"); + } + + _primarySelectorPart = primarySelector; + _secondarySelectorPart = secondarySelector; + _tertiarySelectorPart = tertiarySelector; + + // Hook up to interesting events + _primarySelectorPart.DataSource.SelectionChanged += OnDataSourceSelectionChanged; + _secondarySelectorPart.DataSource.SelectionChanged += OnDataSourceSelectionChanged; + _tertiarySelectorPart.DataSource.SelectionChanged += OnDataSourceSelectionChanged; + _primarySelectorPart.IsExpandedChanged += OnSelectorIsExpandedChanged; + _secondarySelectorPart.IsExpandedChanged += OnSelectorIsExpandedChanged; + _tertiarySelectorPart.IsExpandedChanged += OnSelectorIsExpandedChanged; + + // Hide all selectors + _primarySelectorPart.Visibility = Visibility.Collapsed; + _secondarySelectorPart.Visibility = Visibility.Collapsed; + _tertiarySelectorPart.Visibility = Visibility.Collapsed; + + // Position and reveal the culture-relevant selectors + int column = 0; + foreach (LoopingSelector selector in GetSelectorsOrderedByCulturePattern()) + { + Grid.SetColumn(selector, column); + selector.Visibility = Visibility.Visible; + column++; + } + + // Hook up to storyboard(s) + FrameworkElement templateRoot = VisualTreeHelper.GetChild(this, 0) as FrameworkElement; + if (null != templateRoot) + { + foreach (VisualStateGroup group in VisualStateManager.GetVisualStateGroups(templateRoot)) + { + if (VisibilityGroupName == group.Name) + { + foreach (VisualState state in group.States) + { + if ((ClosedVisibilityStateName == state.Name) && (null != state.Storyboard)) + { + _closedStoryboard = state.Storyboard; + _closedStoryboard.Completed += OnClosedStoryboardCompleted; + } + } + } + } + } + + // 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; + } + } + } + } + + // Play the Open state + VisualStateManager.GoToState(this, OpenVisibilityStateName, true); + } + + private void OnDataSourceSelectionChanged(object sender, SelectionChangedEventArgs e) + { + // Push the selected item to all selectors + DataSource dataSource = (DataSource)sender; + _primarySelectorPart.DataSource.SelectedItem = dataSource.SelectedItem; + _secondarySelectorPart.DataSource.SelectedItem = dataSource.SelectedItem; + _tertiarySelectorPart.DataSource.SelectedItem = dataSource.SelectedItem; + } + + private void OnSelectorIsExpandedChanged(object sender, DependencyPropertyChangedEventArgs e) + { + if ((bool)e.NewValue) + { + // Ensure that only one selector is expanded at a time + _primarySelectorPart.IsExpanded = (sender == _primarySelectorPart); + _secondarySelectorPart.IsExpanded = (sender == _secondarySelectorPart); + _tertiarySelectorPart.IsExpanded = (sender == _tertiarySelectorPart); + } + } + + private void OnDoneButtonClick(object sender, EventArgs e) + { + // Commit the value and close + Debug.Assert((_primarySelectorPart.DataSource.SelectedItem == _secondarySelectorPart.DataSource.SelectedItem) && (_secondarySelectorPart.DataSource.SelectedItem == _tertiarySelectorPart.DataSource.SelectedItem)); + _value = ((DateTimeWrapper)_primarySelectorPart.DataSource.SelectedItem).DateTime; + ClosePickerPage(); + } + + private void OnCancelButtonClick(object sender, EventArgs e) + { + // Close without committing a value + _value = 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; + ClosePickerPage(); + } + + private void ClosePickerPage() + { + // Play the Close state (if available) + if (null != _closedStoryboard) + { + VisualStateManager.GoToState(this, ClosedVisibilityStateName, true); + } + else + { + OnClosedStoryboardCompleted(null, null); + } + } + + private void OnClosedStoryboardCompleted(object sender, EventArgs e) + { + // Close the picker page + NavigationService.GoBack(); + } + + /// + /// Gets a sequence of LoopingSelector parts ordered according to culture string for date/time formatting. + /// + /// LoopingSelectors ordered by culture-specific priority. + protected abstract IEnumerable GetSelectorsOrderedByCulturePattern(); + + /// + /// Gets a sequence of LoopingSelector parts ordered according to culture string for date/time formatting. + /// + /// Culture-specific date/time format string. + /// Date/time format string characters for the primary/secondary/tertiary LoopingSelectors. + /// Instances for the primary/secondary/tertiary LoopingSelectors. + /// LoopingSelectors ordered by culture-specific priority. + protected static IEnumerable GetSelectorsOrderedByCulturePattern(string pattern, char[] patternCharacters, LoopingSelector[] selectors) + { + if (null == pattern) + { + throw new ArgumentNullException("pattern"); + } + if (null == patternCharacters) + { + throw new ArgumentNullException("patternCharacters"); + } + if (null == selectors) + { + throw new ArgumentNullException("selectors"); + } + if (patternCharacters.Length != selectors.Length) + { + throw new ArgumentException("Arrays must contain the same number of elements."); + } + + // Create a list of index and selector pairs + List> pairs = new List>(patternCharacters.Length); + for (int i = 0; i < patternCharacters.Length; i++) + { + pairs.Add(new Tuple(pattern.IndexOf(patternCharacters[i]), selectors[i])); + } + + // Return the corresponding selectors in order + return pairs.Where(p => -1 != p.Item1).OrderBy(p => p.Item1).Select(p => p.Item2).Where(s => null != s); + } + + /// + /// Gets or sets the DateTime to show in the picker page and to set when the user makes a selection. + /// + public DateTime? Value + { + get { return _value; } + set + { + _value = value; + DateTimeWrapper wrapper = new DateTimeWrapper(_value.GetValueOrDefault(DateTime.Now)); + _primarySelectorPart.DataSource.SelectedItem = wrapper; + _secondarySelectorPart.DataSource.SelectedItem = wrapper; + _tertiarySelectorPart.DataSource.SelectedItem = wrapper; + } + } + private DateTime? _value; + + /// + /// 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 ("app://external/" == e.Uri.ToString()) + { + State[StateKey_Value] = Value; + } + } + + /// + /// 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)) + { + Value = State[StateKey_Value] as DateTime?; + + // Back out from picker page for consistency with behavior of core pickers in this scenario + if (NavigationService.CanGoBack) + { + NavigationService.GoBack(); + } + } + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimePickerResources.cs b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimePickerResources.cs new file mode 100644 index 0000000..dda3329 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimePickerResources.cs @@ -0,0 +1,26 @@ +// (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.Diagnostics.CodeAnalysis; + +namespace Microsoft.Phone.Controls +{ + /// + /// Provides access to the localized resources used by the DatePicker and TimePicker. + /// + [SuppressMessage("Microsoft.Design", "CA1053:StaticHolderTypesShouldNotHaveConstructors", Justification = "Making this class static breaks its use as a resource in generic.xaml.")] + public class DateTimePickerResources + { + /// + /// Gets the localized DatePicker title string. + /// + public static string DatePickerTitle { get { return LocalizedResources.ControlResources.DatePickerTitle; } } + + /// + /// Gets the localized TimePicker title string. + /// + public static string TimePickerTitle { get { return LocalizedResources.ControlResources.TimePickerTitle; } } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimeValueChangedEventArgs.cs b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimeValueChangedEventArgs.cs new file mode 100644 index 0000000..0d56849 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimeValueChangedEventArgs.cs @@ -0,0 +1,36 @@ +// (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; + +namespace Microsoft.Phone.Controls +{ + /// + /// Provides data for the DatePicker and TimePicker's ValueChanged event. + /// + public class DateTimeValueChangedEventArgs : EventArgs + { + /// + /// Initializes a new instance of the DateTimeValueChangedEventArgs class. + /// + /// Old DateTime value. + /// New DateTime value. + public DateTimeValueChangedEventArgs(DateTime? oldDateTime, DateTime? newDateTime) + { + OldDateTime = oldDateTime; + NewDateTime = newDateTime; + } + + /// + /// Gets or sets the old DateTime value. + /// + public DateTime? OldDateTime { get; private set; } + + /// + /// Gets or sets the new DateTime value. + /// + public DateTime? NewDateTime { get; private set; } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimeWrapper.cs b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimeWrapper.cs new file mode 100644 index 0000000..020626c --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/DateTimeWrapper.cs @@ -0,0 +1,81 @@ +// (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.Diagnostics.CodeAnalysis; +using System.Globalization; + +namespace Microsoft.Phone.Controls.Primitives +{ + /// + /// Implements a wrapper for DateTime that provides formatted strings for DatePicker. + /// + public class DateTimeWrapper + { + /// + /// Gets the DateTime being wrapped. + /// + public DateTime DateTime { get; private set; } + + /// + /// Gets the 4-digit year as a string. + /// + public string YearNumber { get { return DateTime.ToString("yyyy", CultureInfo.CurrentCulture); } } + + /// + /// Gets the 2-digit month as a string. + /// + public string MonthNumber { get { return DateTime.ToString("MM", CultureInfo.CurrentCulture); } } + + /// + /// Gets the month name as a string. + /// + public string MonthName { get { return DateTime.ToString("MMMM", CultureInfo.CurrentCulture); } } + + /// + /// Gets the 2-digit day as a string. + /// + public string DayNumber { get { return DateTime.ToString("dd", CultureInfo.CurrentCulture); } } + + /// + /// Gets the day name as a string. + /// + public string DayName { get { return DateTime.ToString("dddd", CultureInfo.CurrentCulture); } } + + /// + /// Gets the hour as a string. + /// + public string HourNumber { get { return DateTime.ToString(CurrentCultureUsesTwentyFourHourClock() ? "%H" : "%h", CultureInfo.CurrentCulture); } } + + /// + /// Gets the 2-digit minute as a string. + /// + public string MinuteNumber { get { return DateTime.ToString("mm", CultureInfo.CurrentCulture); } } + + /// + /// Gets the AM/PM designator as a string. + /// + [SuppressMessage("Microsoft.Naming", "CA1709:IdentifiersShouldBeCasedCorrectly", MessageId = "Pm", Justification = "Clearest way of expressing the concept.")] + public string AmPmString { get { return DateTime.ToString("tt", CultureInfo.CurrentCulture); } } + + /// + /// Initializes a new instance of the DateTimeWrapper class. + /// + /// DateTime to wrap. + public DateTimeWrapper(DateTime dateTime) + { + DateTime = dateTime; + } + + /// + /// Returns a value indicating whether the current culture uses a 24-hour clock. + /// + /// True if it uses a 24-hour clock; false otherwise. + public static bool CurrentCultureUsesTwentyFourHourClock() + { + return !CultureInfo.CurrentCulture.DateTimeFormat.LongTimePattern.Contains("t"); + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimePickers/IDateTimePickerPage.cs b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/IDateTimePickerPage.cs new file mode 100644 index 0000000..e0c0e3e --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/IDateTimePickerPage.cs @@ -0,0 +1,20 @@ +// (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; + +namespace Microsoft.Phone.Controls.Primitives +{ + /// + /// Represents an interface for DatePicker/TimePicker to use for communicating with a picker page. + /// + public interface IDateTimePickerPage + { + /// + /// Gets or sets the DateTime to show in the picker page and to set when the user makes a selection. + /// + DateTime? Value { get; set; } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimePickers/RecurringDaysPicker.cs b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/RecurringDaysPicker.cs new file mode 100644 index 0000000..8ad03df --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/RecurringDaysPicker.cs @@ -0,0 +1,180 @@ +// (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.Globalization; +using System.Linq; +using System.Text; +using System.Windows.Controls; +using Microsoft.Phone.Controls.LocalizedResources; + +namespace Microsoft.Phone.Controls +{ + /// + /// Represents a control that allows the user to choose days of the week. + /// + /// Experimental + public class RecurringDaysPicker : ListPicker + { + private const string CommaSpace = ", "; + + private const string EnglishLanguage = "en"; + + private string[] DayNames = CultureInfo.CurrentCulture.DateTimeFormat.DayNames; + private string[] ShortestDayNames = CultureInfo.CurrentCulture.DateTimeFormat.ShortestDayNames; + + /// + /// Initializes a new instance of the RecurringDaysPicker control. + /// + public RecurringDaysPicker() + { + if (CultureInfo.CurrentCulture.Name.StartsWith(EnglishLanguage, StringComparison.OrdinalIgnoreCase)) + { + // The shortestDayNames array shortens English weekdays to two letters. + // The native experience has 3 letters, so we initialize it correclty here. + ShortestDayNames = new string[] { "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat" }; + } + + DayOfWeek dow = CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek; + for (int i = 0; i < DayNames.Count(); i++) + { + Items.Add(DayNames[((int)dow + i) % DayNames.Count()]); + } + + SelectionMode = SelectionMode.Multiple; + + // Add a custom sumarizer, which will take a list of days and return a shortened string representation + SummaryForSelectedItemsDelegate = SummarizeDaysOfWeek; + } + + /// + /// Sumarizes a list of days into a shortened string representation. + /// If all days, all weekdays, or all weekends are in the list, then the string includes + /// the corresponding name rather than listing out all of those days separately. + /// If individual days are listed, they are abreviated. + /// If the list is null or empty, "only once" is returned. + /// + /// The list of days. Can be empty or null. + /// A string representation of the list of days. + protected string SummarizeDaysOfWeek(IList selection) + { + string str = ControlResources.RepeatsOnlyOnce; + + if (null != selection) + { + List contents = new List(); + foreach (object o in selection) + { + contents.Add((string)o); + } + str = DaysOfWeekToString(contents); + } + + return str; + } + + /// + /// Sumarizes a list of days into a shortened string representation. + /// If all days, all weekdays, or all weekends are in the list, then the string includes + /// the corresponding name rather than listing out all of those days separately. + /// If individual days are listed, they are abreviated. + /// If the list is empty, "only once" is returned. + /// + /// The list of days. Can be empty. + /// A string representation of the list of days. + private string DaysOfWeekToString(List daysList) + { + List days = new List(); + + foreach (string day in daysList) + { + // Only include unique days of the week. + // Though a list *should* never have duplicate days, protect against it anyways. + if (!days.Contains(day)) + { + days.Add(day); + } + } + + // No days chosen, return the 'only once' string + if (days.Count == 0) + { + return ControlResources.RepeatsOnlyOnce; + } + + StringBuilder builder = new StringBuilder(); + + IEnumerable unhandledDays; + + builder.Append(HandleGroups(days, out unhandledDays)); + + if (builder.Length > 0) + { + builder.Append(CommaSpace); + } + + DayOfWeek dow = CultureInfo.CurrentCulture.DateTimeFormat.FirstDayOfWeek; + for (int i = 0; i < DayNames.Count(); i++) + { + int index = ((int)dow + i) % DayNames.Count(); + string day = DayNames[index]; + + if (unhandledDays.Contains(day)) + { + builder.Append(ShortestDayNames[index]); + builder.Append(CommaSpace); + } + } + + // trim off the remaining ", " characters, as it was the last day + builder.Length -= CommaSpace.Length; + return builder.ToString(); + } + + /// + /// Finds a group (weekends, weekdays, every day) within a list of days and returns a string representing that group. + /// Days that are not in a group are set in the unhandledDays out parameter. + /// + /// List of days + /// Out parameter which will be written to with the list of days that were not in a group. + /// String of any group found. + private static string HandleGroups(List days, out IEnumerable unhandledDays) + { + // First do a check for all of the days of the week, and replace it with the 'every day' string + if (days.Count == 7) + { + unhandledDays = new List(); + return ControlResources.RepeatsEveryDay; + } + + var weekdays = CultureInfo.CurrentCulture.Weekdays(); + var weekends = CultureInfo.CurrentCulture.Weekends(); + + if (days.Intersect(weekdays).Count() == weekdays.Count) + { + unhandledDays = days.Where(day => !weekdays.Contains(day)); + return ControlResources.RepeatsOnWeekdays; + } + else if (days.Intersect(weekends).Count() == weekends.Count) + { + unhandledDays = days.Where(day => !weekends.Contains(day)); + return ControlResources.RepeatsOnWeekends; + } + else + { + unhandledDays = days; + return string.Empty; + } + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimePickers/TimePicker.cs b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/TimePicker.cs new file mode 100644 index 0000000..3864966 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/TimePicker.cs @@ -0,0 +1,47 @@ +// (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.Globalization; + +namespace Microsoft.Phone.Controls +{ + /// + /// Represents a control that allows the user to choose a time (hour/minute/am/pm). + /// + /// Preview + public class TimePicker : DateTimePickerBase + { + private string _fallbackValueStringFormat; + + /// + /// Initializes a new instance of the TimePicker control. + /// + public TimePicker() + { + DefaultStyleKey = typeof(TimePicker); + Value = DateTime.Now; + } + + /// + /// Gets the fallback value for the ValueStringFormat property. + /// + protected override string ValueStringFormatFallback + { + get + { + if (null == _fallbackValueStringFormat) + { + // Need to convert LongTimePattern into ShortTimePattern to work around a platform bug + // such that only LongTimePattern respects the "24-hour clock" override setting. + // This technique is not perfect, but works for all the initially-supported languages. + string pattern = CultureInfo.CurrentCulture.DateTimeFormat.LongTimePattern.Replace(":ss", ""); + _fallbackValueStringFormat = "{0:" + pattern + "}"; + } + return _fallbackValueStringFormat; + } + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimePickers/TimePickerPage.xaml b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/TimePickerPage.xaml new file mode 100644 index 0000000..1209f18 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/TimePickerPage.xaml @@ -0,0 +1,171 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Microsoft.Phone.Controls.Toolkit/DateTimePickers/TimePickerPage.xaml.cs b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/TimePickerPage.xaml.cs new file mode 100644 index 0000000..1c29126 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/DateTimePickers/TimePickerPage.xaml.cs @@ -0,0 +1,65 @@ +// (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.Generic; +using System.Globalization; +using System.Windows; +using Microsoft.Phone.Controls.Primitives; + +namespace Microsoft.Phone.Controls +{ + /// + /// Represents a page used by the TimePicker control that allows the user to choose a time (hour/minute/am/pm). + /// + public partial class TimePickerPage : DateTimePickerPageBase + { + /// + /// Initializes a new instance of the TimePickerPage control. + /// + public TimePickerPage() + { + InitializeComponent(); + + // Hook up the data sources + PrimarySelector.DataSource = DateTimeWrapper.CurrentCultureUsesTwentyFourHourClock() ? + (DataSource)(new TwentyFourHourDataSource()) : + (DataSource)(new TwelveHourDataSource()); + SecondarySelector.DataSource = new MinuteDataSource(); + TertiarySelector.DataSource = new AmPmDataSource(); + + InitializeDateTimePickerPage(PrimarySelector, SecondarySelector, TertiarySelector); + } + + /// + /// Gets a sequence of LoopingSelector parts ordered according to culture string for date/time formatting. + /// + /// LoopingSelectors ordered by culture-specific priority. + protected override IEnumerable GetSelectorsOrderedByCulturePattern() + { + return GetSelectorsOrderedByCulturePattern( + CultureInfo.CurrentCulture.DateTimeFormat.LongTimePattern.ToUpperInvariant(), + new char[] { 'H', 'M', 'T' }, + new LoopingSelector[] { PrimarySelector, SecondarySelector, TertiarySelector }); + } + + /// + /// Handles changes to the page's Orientation property. + /// + /// Event arguments. + protected override void OnOrientationChanged(OrientationChangedEventArgs e) + { + if (null == e) + { + throw new ArgumentNullException("e"); + } + + base.OnOrientationChanged(e); + SystemTrayPlaceholder.Visibility = (0 != (PageOrientation.Portrait & e.Orientation)) ? + Visibility.Visible : + Visibility.Collapsed; + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/ExpanderView/ExpanderView.cs b/Microsoft.Phone.Controls.Toolkit/ExpanderView/ExpanderView.cs new file mode 100644 index 0000000..31477b7 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/ExpanderView/ExpanderView.cs @@ -0,0 +1,737 @@ +// (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.Specialized; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Animation; + +namespace Microsoft.Phone.Controls +{ + /// + /// Represents a collection of items that can be expanded or collapsed. + /// + /// Experimental + [TemplateVisualState(Name = CollapsedState, GroupName = ExpansionStates)] + [TemplateVisualState(Name = ExpandedState, GroupName = ExpansionStates)] + [TemplateVisualState(Name = ExpandableState, GroupName = ExpandabilityStates)] + [TemplateVisualState(Name = NonExpandableState, GroupName = ExpandabilityStates)] + [TemplatePart(Name = Presenter, Type = typeof(ItemsPresenter))] + [TemplatePart(Name = ExpanderPanel, Type = typeof(Grid))] + [TemplatePart(Name = ExpandedStateAnimation, Type = typeof(DoubleAnimation))] + [TemplatePart(Name = CollapsedToExpandedKeyFrame, Type = typeof(EasingDoubleKeyFrame))] + [TemplatePart(Name = ExpandedToCollapsedKeyFrame, Type = typeof(EasingDoubleKeyFrame))] + public class ExpanderView : HeaderedItemsControl + { + /// + /// Expansion visual states. + /// + public const string ExpansionStates = "ExpansionStates"; + + /// + /// Expandability visual states. + /// + public const string ExpandabilityStates = "ExpandabilityStates"; + + /// + /// Collapsed visual state. + /// + public const string CollapsedState = "Collapsed"; + + /// + /// Expanded visual state. + /// + public const string ExpandedState = "Expanded"; + + /// + /// Expandable visual state. + /// + public const string ExpandableState = "Expandable"; + + /// + /// NonExpandable visual state. + /// + public const string NonExpandableState = "NonExpandable"; + + /// + /// Presenter template part name. + /// + private const string Presenter = "Presenter"; + + /// + /// Expander Panel template part name. + /// + private const string ExpanderPanel = "ExpanderPanel"; + + /// + /// Expanded State Animation template part name. + /// + private const string ExpandedStateAnimation = "ExpandedStateAnimation"; + + /// + /// Collapsed to Expanded Key Frame template part name. + /// + private const string CollapsedToExpandedKeyFrame = "CollapsedToExpandedKeyFrame"; + + /// + /// Expanded to Collapsed Key Frame template part name. + /// + private const string ExpandedToCollapsedKeyFrame = "ExpandedToCollapsedKeyFrame"; + + /// + /// Presenter template part. + /// + private ItemsPresenter _presenter; + + /// + /// Canvas template part + /// + private Canvas _itemsCanvas; + + /// + /// Expander Panel template part. + /// + private Grid _expanderPanel; + + /// + /// Expanded State Animation template part. + /// + private DoubleAnimation _expandedStateAnimation; + + /// + /// Collapsed to Expanded Key Frame template part. + /// + private EasingDoubleKeyFrame _collapsedToExpandedFrame; + + /// + /// Expanded to Collapsed Key Frame template part. + /// + private EasingDoubleKeyFrame _expandedToCollapsedFrame; + + /// + /// Step between the keytimes of drop-down animations + /// to create a feather effect. + /// + private const int KeyTimeStep = 35; + + /// + /// Initial key time for drop-down animations. + /// + private const int InitialKeyTime = 225; + + /// + /// Final key time for drop-down animations. + /// + private const int FinalKeyTime = 250; + + /// + /// Occurs when the Expander View opens to display its content. + /// + public event RoutedEventHandler Expanded; + + /// + /// Occurs when the Expander View closes to hide its content. + /// + public event RoutedEventHandler Collapsed; + + #region Expander DependencyProperty + + /// + /// Gets or sets the expander object. + /// + public object Expander + { + get { return (object)GetValue(ExpanderProperty); } + set { SetValue(ExpanderProperty, value); } + } + + /// + /// Identifies the Expander dependency property. + /// + public static readonly DependencyProperty ExpanderProperty = + DependencyProperty.Register("Expander", typeof(object), typeof(ExpanderView), new PropertyMetadata(null, OnExpanderPropertyChanged)); + + + /// + /// ExpanderProperty changed handler. + /// + /// The dependency object. + /// The event information. + private static void OnExpanderPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + ExpanderView source = (ExpanderView)obj; + source.OnExpanderChanged(e.OldValue, e.NewValue); + } + + #endregion + + #region ExpanderTemplate DependencyProperty + + /// + /// Gets or sets the data template that defines + /// the expander. + /// + public DataTemplate ExpanderTemplate + { + get { return (DataTemplate)GetValue(ExpanderTemplateProperty); } + set { SetValue(ExpanderTemplateProperty, value); } + } + + /// + /// Identifies the ExpanderTemplate dependency property. + /// + public static readonly DependencyProperty ExpanderTemplateProperty = + DependencyProperty.Register("ExpanderTemplate", typeof(DataTemplate), typeof(ExpanderView), new PropertyMetadata(null, OnExpanderTemplatePropertyChanged)); + + /// + /// ExpanderTemplateProperty changed handler. + /// + /// The dependency object. + /// The event information. + private static void OnExpanderTemplatePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + ExpanderView source = (ExpanderView)obj; + source.OnExpanderTemplateChanged((DataTemplate)e.OldValue, (DataTemplate)e.NewValue); + } + + #endregion + + #region NonExpandableHeader + + /// + /// Gets or sets the header object that is displayed + /// when the Expander View is non-expandable. + /// + public object NonExpandableHeader + { + get { return (object)GetValue(NonExpandableHeaderProperty); } + set { SetValue(NonExpandableHeaderProperty, value); } + } + + /// + /// Identifies the NonExpandableHeader dependency property. + /// + public static readonly DependencyProperty NonExpandableHeaderProperty = + DependencyProperty.Register("NonExpandableHeader", typeof(object), typeof(ExpanderView), new PropertyMetadata(null, OnNonExpandableHeaderPropertyChanged)); + + /// + /// NonExpandableHeaderProperty changed handler. + /// + /// The dependency object. + /// The event information. + private static void OnNonExpandableHeaderPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + ExpanderView source = (ExpanderView)obj; + source.OnNonExpandableHeaderChanged(e.OldValue, e.NewValue); + } + + #endregion + + #region NonExpandableHeadeTemplate DependencyProperty + + /// + /// Gets or sets the data template that defines + /// the non-expandable header. + /// + public DataTemplate NonExpandableHeaderTemplate + { + get { return (DataTemplate)GetValue(NonExpandableHeaderTemplateProperty); } + set { SetValue(NonExpandableHeaderTemplateProperty, value); } + } + + /// + /// Identifies the NonExpandableHeaderTemplate dependency property. + /// + public static readonly DependencyProperty NonExpandableHeaderTemplateProperty = + DependencyProperty.Register("NonExpandableHeaderTemplate", typeof(DataTemplate), typeof(ExpanderView), new PropertyMetadata(null, OnNonExpandableHeaderTemplatePropertyChanged)); + + /// + /// NonExpandableHeaderTemplate changed handler. + /// + /// The dependency object. + /// The event information. + private static void OnNonExpandableHeaderTemplatePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + ExpanderView source = (ExpanderView)obj; + source.OnNonExpandableHeaderTemplateChanged((DataTemplate)e.OldValue, (DataTemplate)e.NewValue); + } + + #endregion + + #region IsExpanded DependencyProperty + + /// + /// Gets or sets the flag that indicates whether the + /// Expander View is expanded. + /// + public bool IsExpanded + { + get { return (bool)GetValue(IsExpandedProperty); } + set + { + if (!IsNonExpandable) + { + SetValue(IsExpandedProperty, value); + } + else + { + throw new InvalidOperationException(Properties.Resources.InvalidExpanderViewOperation); + } + } + } + + /// + /// Identifies the IsExpanded dependency property. + /// + public static readonly DependencyProperty IsExpandedProperty = + DependencyProperty.Register("IsExpanded", typeof(bool), typeof(ExpanderView), new PropertyMetadata(false, OnIsExpandedPropertyChanged)); + + /// + /// IsExpandedProperty changed handler. + /// + /// The dependency object. + /// The event information. + private static void OnIsExpandedPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + ExpanderView source = (ExpanderView)obj; + + RoutedEventArgs args = new RoutedEventArgs(); + if ((bool)e.NewValue) + { + source.OnExpanded(args); + } + else + { + source.OnCollapsed(args); + } + + source.UpdateVisualState(true); + } + + #endregion + + #region HasItems DependencyProperty + + /// + /// Gets or sets the flag that indicates whether the + /// Expander View has items. + /// + public bool HasItems + { + get { return (bool)GetValue(HasItemsProperty); } + set { SetValue(HasItemsProperty, value); } + } + + /// + /// Identifies the HasItems dependency property. + /// + public static readonly DependencyProperty HasItemsProperty = + DependencyProperty.Register("HasItems", typeof(bool), typeof(ExpanderView), new PropertyMetadata(false, null)); + + #endregion + + #region IsNonExpandable + + /// + /// Gets or sets the flag that indicates whether the + /// Expander View is non-expandable. + /// + public bool IsNonExpandable + { + get { return (bool)GetValue(IsNonExpandableProperty); } + set { SetValue(IsNonExpandableProperty, value); } + } + + /// + /// Identifies the NonExpandable dependency property. + /// + public static readonly DependencyProperty IsNonExpandableProperty = + DependencyProperty.Register("IsNonExpandable", typeof(bool), typeof(ExpanderView), new PropertyMetadata(false, OnIsNonExpandablePropertyChanged)); + + /// + /// IsNonExpandableProperty changed handler. + /// + /// The dependency object. + /// The event information. + private static void OnIsNonExpandablePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + ExpanderView source = (ExpanderView)obj; + + if ((bool)e.NewValue) + { + if (source.IsExpanded) + { + source.IsExpanded = false; + } + } + + source.UpdateVisualState(true); + } + + #endregion + + /// + /// Gets the template parts and sets event handlers. + /// + public override void OnApplyTemplate() + { + if (_expanderPanel != null) + { + _expanderPanel.Tap -= OnExpanderPanelTap; + } + + base.OnApplyTemplate(); + + _expanderPanel = base.GetTemplateChild(ExpanderPanel) as Grid; + _expandedStateAnimation = (base.GetTemplateChild(ExpandedState) as VisualState).Storyboard.Children[0] as DoubleAnimation; + _expandedToCollapsedFrame = base.GetTemplateChild(ExpandedToCollapsedKeyFrame) as EasingDoubleKeyFrame; + _collapsedToExpandedFrame = base.GetTemplateChild(CollapsedToExpandedKeyFrame) as EasingDoubleKeyFrame; + _itemsCanvas = base.GetTemplateChild("ItemsCanvas") as Canvas; + _presenter = base.GetTemplateChild(Presenter) as ItemsPresenter; + _presenter.SizeChanged += OnPresenterSizeChanged; + + if (_expanderPanel != null) + { + _expanderPanel.Tap += OnExpanderPanelTap; + } + + UpdateVisualState(false); + } + + /// + /// Initializes a new instance of the ExpanderView class. + /// + public ExpanderView() + { + DefaultStyleKey = typeof(ExpanderView); + SizeChanged += OnSizeChanged; + } + + /// + /// Recalculates the size of the presenter to match its parent. + /// + /// Sender + /// Event args + private void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + if (_presenter != null) + { + UIElement parent = VisualTreeHelper.GetParent(_presenter) as UIElement; + while (!(parent is ExpanderView)) + { + parent = VisualTreeHelper.GetParent(parent) as UIElement; + } + GeneralTransform gt = parent.TransformToVisual(_presenter); + Point childToParentCoordinates = gt.Transform(new Point(0, 0)); + _presenter.Width = parent.RenderSize.Width + childToParentCoordinates.X; + } + } + + /// + /// Recalculates size of canvas based on the size change for the presenter. + /// + /// Sender + /// Event args + private void OnPresenterSizeChanged(object sender, SizeChangedEventArgs e) + { + if (null != _itemsCanvas && null != _presenter && IsExpanded) + { + // Already expanded, so we need to update the height of the canvas directly. + _itemsCanvas.Height = _presenter.DesiredSize.Height; + } + } + + /// + /// Updates the visual state. + /// + /// + /// Indicates whether visual transitions should be used. + /// + internal virtual void UpdateVisualState(bool useTransitions) + { + string isExpandedState, isNonExpandableState; + + if (_presenter != null) + { + if (_expandedStateAnimation != null) + { + _expandedStateAnimation.To = _presenter.DesiredSize.Height; + } + + if (_collapsedToExpandedFrame != null) + { + _collapsedToExpandedFrame.Value = _presenter.DesiredSize.Height; + } + + if (_expandedToCollapsedFrame != null) + { + _expandedToCollapsedFrame.Value = _presenter.DesiredSize.Height; + } + } + + // Handle the Expansion states + if (IsExpanded) + { + isExpandedState = ExpandedState; + if (useTransitions) + { + AnimateContainerDropDown(); + } + } + else + { + isExpandedState = CollapsedState; + } + + VisualStateManager.GoToState(this, isExpandedState, useTransitions); + + // Handle the Expandability states. + if (IsNonExpandable) + { + isNonExpandableState = NonExpandableState; + } + else + { + isNonExpandableState = ExpandableState; + } + + VisualStateManager.GoToState(this, isNonExpandableState, useTransitions); + } + + /// + /// Raises a routed event. + /// + /// Event handler. + /// Event arguments. + private void RaiseEvent(RoutedEventHandler handler, RoutedEventArgs args) + { + if (handler != null) + { + handler(this, args); + } + } + + /// + /// Provides the feathered animation for items + /// when the Expander View goes from collapsed to expanded. + /// + internal void AnimateContainerDropDown() + { + for (int i = 0; i < Items.Count; i++) + { + FrameworkElement container = ItemContainerGenerator.ContainerFromIndex(i) as FrameworkElement; + + if (container == null) + { + break; + } + + Storyboard itemDropDown = new Storyboard(); + IEasingFunction quadraticEase = new QuadraticEase { EasingMode = EasingMode.EaseOut }; + int initialKeyTime = InitialKeyTime + (KeyTimeStep * i); + int finalKeyTime = FinalKeyTime + (KeyTimeStep * i); + + TranslateTransform translation = new TranslateTransform(); + container.RenderTransform = translation; + + DoubleAnimationUsingKeyFrames transAnimation = new DoubleAnimationUsingKeyFrames(); + + EasingDoubleKeyFrame transKeyFrame_1 = new EasingDoubleKeyFrame(); + transKeyFrame_1.EasingFunction = quadraticEase; + transKeyFrame_1.KeyTime = TimeSpan.FromMilliseconds(0.0); + transKeyFrame_1.Value = -150.0; + + EasingDoubleKeyFrame transKeyFrame_2 = new EasingDoubleKeyFrame(); + transKeyFrame_2.EasingFunction = quadraticEase; + transKeyFrame_2.KeyTime = TimeSpan.FromMilliseconds(initialKeyTime); + transKeyFrame_2.Value = 0.0; + + EasingDoubleKeyFrame transKeyFrame_3 = new EasingDoubleKeyFrame(); + transKeyFrame_3.EasingFunction = quadraticEase; + transKeyFrame_3.KeyTime = TimeSpan.FromMilliseconds(finalKeyTime); + transKeyFrame_3.Value = 0.0; + + transAnimation.KeyFrames.Add(transKeyFrame_1); + transAnimation.KeyFrames.Add(transKeyFrame_2); + transAnimation.KeyFrames.Add(transKeyFrame_3); + + Storyboard.SetTarget(transAnimation, translation); + Storyboard.SetTargetProperty(transAnimation, new PropertyPath(TranslateTransform.YProperty)); + itemDropDown.Children.Add(transAnimation); + + DoubleAnimationUsingKeyFrames opacityAnimation = new DoubleAnimationUsingKeyFrames(); + + EasingDoubleKeyFrame opacityKeyFrame_1 = new EasingDoubleKeyFrame(); + opacityKeyFrame_1.EasingFunction = quadraticEase; + opacityKeyFrame_1.KeyTime = TimeSpan.FromMilliseconds(0.0); + opacityKeyFrame_1.Value = 0.0; + + EasingDoubleKeyFrame opacityKeyFrame_2 = new EasingDoubleKeyFrame(); + opacityKeyFrame_2.EasingFunction = quadraticEase; + opacityKeyFrame_2.KeyTime = TimeSpan.FromMilliseconds(initialKeyTime - 150); + opacityKeyFrame_2.Value = 0.0; + + EasingDoubleKeyFrame opacityKeyFrame_3 = new EasingDoubleKeyFrame(); + opacityKeyFrame_3.EasingFunction = quadraticEase; + opacityKeyFrame_3.KeyTime = TimeSpan.FromMilliseconds(finalKeyTime); + opacityKeyFrame_3.Value = 1.0; + + opacityAnimation.KeyFrames.Add(opacityKeyFrame_1); + opacityAnimation.KeyFrames.Add(opacityKeyFrame_2); + opacityAnimation.KeyFrames.Add(opacityKeyFrame_3); + + Storyboard.SetTarget(opacityAnimation, container); + Storyboard.SetTargetProperty(opacityAnimation, new PropertyPath(FrameworkElement.OpacityProperty)); + itemDropDown.Children.Add(opacityAnimation); + + itemDropDown.Begin(); + } + } + + #region ItemsControl overriden methods + + /// + /// Updates the HasItems property. + /// + /// The event information. + protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) + { + base.OnItemsChanged(e); + HasItems = Items.Count > 0; + } + + #endregion + + #region Input events + + /// + /// Toggles the IsExpanded property. + /// + /// The Expander Panel that triggers the event. + /// The event information. + private void OnExpanderPanelTap(object sender, System.Windows.Input.GestureEventArgs e) + { + if (!IsNonExpandable) + { + IsExpanded = !IsExpanded; + } + } + + #endregion + + #region Event overrides + + /// + /// Called when the value of th Expander property changes. + /// + /// + /// The old value of the Expander property. + /// + /// + /// The new value of the Expander property. + /// + protected virtual void OnExpanderChanged(object oldExpander, object newExpander) + { + } + + /// + /// Called when the value of the ExpanderTemplate property changes. + /// + /// + /// The old value of the ExpanderTemplate property. + /// + /// + /// The new value of the ExpanderTemplate property. + /// + protected virtual void OnExpanderTemplateChanged(DataTemplate oldTemplate, DataTemplate newTemplate) + { + } + + /// + /// Called when the value of the NonExpandableHeader property changes. + /// + /// + /// The old value of the NonExpandableHeader property. + /// + /// + /// The new value of the NonExpandableHeader property. + /// + protected virtual void OnNonExpandableHeaderChanged(object oldHeader, object newHeader) + { + } + + /// + /// Called when the value of the NonExpandableHeaderTemplate + /// property changes. + /// + /// + /// The old value of the NonExpandableHeaderTemplate property. + /// + /// + /// The new value of the NonExpandableHeaderTemplate property. + /// + protected virtual void OnNonExpandableHeaderTemplateChanged(DataTemplate oldTemplate, DataTemplate newTemplate) + { + } + + /// + /// Raises an Expanded event when the IsExpanded property + /// changes from false to true. + /// + /// The event information. + protected virtual void OnExpanded(RoutedEventArgs e) + { + RaiseEvent(Expanded, e); + } + + /// + /// Raises a Collapsed event when the IsExpanded property + /// changes from true to false. + /// + /// The event information. + protected virtual void OnCollapsed(RoutedEventArgs e) + { + RaiseEvent(Collapsed, e); + } + + #endregion + } + + /// + /// Represents the visual states of an Expander View + /// related to its current expansion. + /// + internal enum ExpansionStates + { + /// + /// Collapsed visual state value. + /// + Collapsed = 0, + + /// + /// Expanded visual state value. + /// + Expanded = 1, + } + + /// + /// Represents the visual states of an Expander View + /// related to its expandability. + /// + internal enum ExpandabilityStates + { + /// + /// Expandable visual state value. + /// + Expandable = 0, + + /// + /// NonExpandable visual state value. + /// + NonExpandable = 1, + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/GlobalSuppressions.cs b/Microsoft.Phone.Controls.Toolkit/GlobalSuppressions.cs new file mode 100644 index 0000000..ba1eb5e --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/GlobalSuppressions.cs @@ -0,0 +1,34 @@ +// (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.Diagnostics.CodeAnalysis; + +[assembly: SuppressMessage("Microsoft.Design", "CA2210:AssembliesShouldHaveValidStrongNames", Justification = "Satisfied for Release, not for Debug.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Windows", Justification = "Matching Silverlight namespaces for existing classes.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Windows.Controls", Justification = "Matching Silverlight namespaces for existing classes.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "System.Windows.Controls.Primitives", Justification = "Matching Silverlight namespaces for existing classes.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Phone.Controls", Justification = "Using corresponding Toolkit namespace for existing classes.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1020:AvoidNamespacesWithFewTypes", Scope = "namespace", Target = "Microsoft.Phone.Controls.Primitives", Justification = "Using corresponding Toolkit namespace for existing classes.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1703:ResourceStringsShouldBeSpelledCorrectly", MessageId = "png", Scope = "resource", Target = "Microsoft.Phone.Controls.Properties.Resources.resources", Justification = "Standard file extension for PNG images.")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Scope = "member", Target = "Microsoft.Phone.Controls.DatePickerPage.#VisibilityStates", Justification = "VSM group name.")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Scope = "member", Target = "Microsoft.Phone.Controls.DatePickerPage.#Open", Justification = "VSM state name.")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Scope = "member", Target = "Microsoft.Phone.Controls.DatePickerPage.#Closed", Justification = "VSM state name.")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Scope = "member", Target = "Microsoft.Phone.Controls.DatePickerPage.#PlaneProjection", Justification = "Referenced by a VSM state.")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Scope = "member", Target = "Microsoft.Phone.Controls.TimePickerPage.#VisibilityStates", Justification = "VSM group name.")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Scope = "member", Target = "Microsoft.Phone.Controls.TimePickerPage.#Open", Justification = "VSM state name.")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Scope = "member", Target = "Microsoft.Phone.Controls.TimePickerPage.#Closed", Justification = "VSM state name.")] +[assembly: SuppressMessage("Microsoft.Performance", "CA1823:AvoidUnusedPrivateFields", Scope = "member", Target = "Microsoft.Phone.Controls.TimePickerPage.#PlaneProjection", Justification = "Referenced by a VSM state.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.Phone.Controls.TransitionService.#GetNavigationInTransition(System.Windows.UIElement)", Justification = "Transitions can only be applied to UIElements.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.Phone.Controls.TransitionService.#SetNavigationInTransition(System.Windows.UIElement,Microsoft.Phone.Controls.NavigationInTransition)", Justification = "Transitions can only be applied to UIElements.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Scope = "member", Target = "Microsoft.Phone.Controls.ITransition.#GetCurrentState()", Justification = "Must match the Storyboard interface.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1024:UsePropertiesWhereAppropriate", Scope = "member", Target = "Microsoft.Phone.Controls.ITransition.#GetCurrentTime()", Justification = "Must match the Storyboard interface.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Resume", Scope = "member", Target = "Microsoft.Phone.Controls.ITransition.#Resume()", Justification = "Must match the Storyboard interface.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1716:IdentifiersShouldNotMatchKeywords", MessageId = "Stop", Scope = "member", Target = "Microsoft.Phone.Controls.ITransition.#Stop()", Justification = "Must match the Storyboard interface.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.Phone.Controls.TransitionService.#GetNavigationOutTransition(System.Windows.UIElement)", Justification = "Transitions can only be applied to UIElements.")] +[assembly: SuppressMessage("Microsoft.Design", "CA1011:ConsiderPassingBaseTypesAsParameters", Scope = "member", Target = "Microsoft.Phone.Controls.TransitionService.#SetNavigationOutTransition(System.Windows.UIElement,Microsoft.Phone.Controls.NavigationOutTransition)", Justification = "Transitions can only be applied to UIElements.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "FadeOut", Scope = "member", Target = "Microsoft.Phone.Controls.SlideTransitionMode.#SlideDownFadeOut", Justification = "Must match the metro motion names.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "FadeOut", Scope = "member", Target = "Microsoft.Phone.Controls.SlideTransitionMode.#SlideLeftFadeOut", Justification = "Must match the metro motion names.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "FadeOut", Scope = "member", Target = "Microsoft.Phone.Controls.SlideTransitionMode.#SlideRightFadeOut", Justification = "Must match the metro motion names.")] +[assembly: SuppressMessage("Microsoft.Naming", "CA1702:CompoundWordsShouldBeCasedCorrectly", MessageId = "FadeOut", Scope = "member", Target = "Microsoft.Phone.Controls.SlideTransitionMode.#SlideUpFadeOut", Justification = "Must match the FadeIn counterpart casing.")] diff --git a/Microsoft.Phone.Controls.Toolkit/HeaderedItemsControl/HeaderedItemsControl.cs b/Microsoft.Phone.Controls.Toolkit/HeaderedItemsControl/HeaderedItemsControl.cs new file mode 100644 index 0000000..ce7afb3 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/HeaderedItemsControl/HeaderedItemsControl.cs @@ -0,0 +1,371 @@ +// (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.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Windows.Data; + +namespace System.Windows.Controls +{ + /// + /// Represents a control that contains a collection of items and a header. + /// + /// Stable + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Headered", Justification = "Consistency with WPF")] + [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(ContentPresenter))] + public partial class HeaderedItemsControl : ItemsControl + { + /// + /// Gets or sets a value indicating whether the Header property has been + /// set to the item of an ItemsControl. + /// + internal bool HeaderIsItem { get; set; } + + #region public object Header + /// + /// Gets or sets the item that labels the control. + /// + /// + /// The item that labels the control. The default value is null. + /// + public object Header + { + get { return GetValue(HeaderProperty); } + set { SetValue(HeaderProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// + /// The identifier for the + /// + /// dependency property. + /// + /// + /// Note: WPF defines this property via a call to AddOwner of + /// HeaderedContentControl's HeaderProperty. + /// + public static readonly DependencyProperty HeaderProperty = + DependencyProperty.Register( + "Header", + typeof(object), + typeof(HeaderedItemsControl), + new PropertyMetadata(OnHeaderPropertyChanged)); + + /// + /// HeaderProperty property changed handler. + /// + /// + /// HeaderedItemsControl that changed its Header. + /// + /// Event arguments. + private static void OnHeaderPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + HeaderedItemsControl source = d as HeaderedItemsControl; + source.OnHeaderChanged(e.OldValue, e.NewValue); + } + #endregion public object Header + + #region public DataTemplate HeaderTemplate + /// + /// Gets or sets a data template that is used to display the contents of + /// the control's header. + /// + /// + /// Gets or sets a data template that is used to display the contents of + /// the control's header. The default is null. + /// + public DataTemplate HeaderTemplate + { + get { return GetValue(HeaderTemplateProperty) as DataTemplate; } + set { SetValue(HeaderTemplateProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// + /// The identifier for the + /// + /// dependency property. + /// + /// + /// Note: WPF defines this property via a call to AddOwner of + /// HeaderedContentControl's HeaderTemplateProperty. + /// + public static readonly DependencyProperty HeaderTemplateProperty = + DependencyProperty.Register( + "HeaderTemplate", + typeof(DataTemplate), + typeof(HeaderedItemsControl), + new PropertyMetadata(OnHeaderTemplatePropertyChanged)); + + /// + /// HeaderTemplateProperty property changed handler. + /// + /// + /// HeaderedItemsControl that changed its HeaderTemplate. + /// + /// Event arguments. + private static void OnHeaderTemplatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + HeaderedItemsControl source = d as HeaderedItemsControl; + DataTemplate oldHeaderTemplate = e.OldValue as DataTemplate; + DataTemplate newHeaderTemplate = e.NewValue as DataTemplate; + source.OnHeaderTemplateChanged(oldHeaderTemplate, newHeaderTemplate); + } + #endregion public DataTemplate HeaderTemplate + + #region public Style ItemContainerStyle + /// + /// Gets or sets the that is + /// applied to the container element generated for each item. + /// + /// + /// The that is applied to the + /// container element generated for each item. The default is null. + /// + public Style ItemContainerStyle + { + get { return GetValue(ItemContainerStyleProperty) as Style; } + set { SetValue(ItemContainerStyleProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// + /// The identifier for the + /// + /// dependency property. + /// + public static readonly DependencyProperty ItemContainerStyleProperty = + DependencyProperty.Register( + "ItemContainerStyle", + typeof(Style), + typeof(HeaderedItemsControl), + new PropertyMetadata(null, OnItemContainerStylePropertyChanged)); + + /// + /// ItemContainerStyleProperty property changed handler. + /// + /// + /// HeaderedItemsControl that changed its ItemContainerStyle. + /// + /// Event arguments. + private static void OnItemContainerStylePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + HeaderedItemsControl source = d as HeaderedItemsControl; + Style value = e.NewValue as Style; + source.ItemsControlHelper.UpdateItemContainerStyle(value); + } + #endregion public Style ItemContainerStyle + + /// + /// Gets the ItemsControlHelper that is associated with this control. + /// + internal ItemsControlHelper ItemsControlHelper { get; private set; } + + /// + /// Initializes a new instance of the + /// class. + /// + public HeaderedItemsControl() + { + DefaultStyleKey = typeof(HeaderedItemsControl); + ItemsControlHelper = new ItemsControlHelper(this); + } + + /// + /// Called when the value of the + /// + /// property changes. + /// + /// + /// The old value of the + /// + /// property. + /// + /// + /// The new value of the + /// + /// property. + /// + protected virtual void OnHeaderChanged(object oldHeader, object newHeader) + { + } + + /// + /// Called when the value of the + /// + /// property changes. + /// + /// + /// The old value of the + /// + /// property. + /// + /// + /// The new value of the + /// + /// property. + /// + protected virtual void OnHeaderTemplateChanged(DataTemplate oldHeaderTemplate, DataTemplate newHeaderTemplate) + { + } + + /// + /// Builds the visual tree for the + /// when a + /// new template is applied. + /// + public override void OnApplyTemplate() + { + ItemsControlHelper.OnApplyTemplate(); + base.OnApplyTemplate(); + } + + /// + /// Prepares the specified element to display the specified item. + /// + /// + /// The container element used to display the specified item. + /// + /// The content to display. + protected override void PrepareContainerForItemOverride(DependencyObject element, object item) + { + ItemsControlHelper.PrepareContainerForItemOverride(element, ItemContainerStyle); + PreparePrepareHeaderedItemsControlContainerForItemOverride(element, item, this, ItemContainerStyle); + + base.PrepareContainerForItemOverride(element, item); + } + + /// + /// Prepares the specified container to display the specified item. + /// + /// + /// Container element used to display the specified item. + /// + /// Specified item to display. + /// The parent ItemsControl. + /// + /// The ItemContainerStyle for the parent ItemsControl. + /// + internal static void PreparePrepareHeaderedItemsControlContainerForItemOverride(DependencyObject element, object item, ItemsControl parent, Style parentItemContainerStyle) + { + HeaderedItemsControl headeredItemsControl = element as HeaderedItemsControl; + if (headeredItemsControl != null) + { + PrepareHeaderedItemsControlContainer(headeredItemsControl, item, parent, parentItemContainerStyle); + } + } + + /// + /// Prepare a PrepareHeaderedItemsControlContainer container for an + /// item. + /// + /// Container to prepare. + /// Item to be placed in the container. + /// The parent ItemsControl. + /// + /// The ItemContainerStyle for the parent ItemsControl. + /// + private static void PrepareHeaderedItemsControlContainer(HeaderedItemsControl control, object item, ItemsControl parentItemsControl, Style parentItemContainerStyle) + { + if (control != item) + { + // Copy the ItemsControl properties from parent to child + DataTemplate parentItemTemplate = parentItemsControl.ItemTemplate; + if (parentItemTemplate != null) + { + control.SetValue(HeaderedItemsControl.ItemTemplateProperty, parentItemTemplate); + } + if (parentItemContainerStyle != null && HasDefaultValue(control, HeaderedItemsControl.ItemContainerStyleProperty)) + { + control.SetValue(HeaderedItemsControl.ItemContainerStyleProperty, parentItemContainerStyle); + } + + // Copy the Header properties from parent to child + if (control.HeaderIsItem || HasDefaultValue(control, HeaderedItemsControl.HeaderProperty)) + { + control.Header = item; + control.HeaderIsItem = true; + } + if (parentItemTemplate != null) + { + control.SetValue(HeaderedItemsControl.HeaderTemplateProperty, parentItemTemplate); + } + if (parentItemContainerStyle != null && control.Style == null) + { + control.SetValue(HeaderedItemsControl.StyleProperty, parentItemContainerStyle); + } + + // Note: this is where we would apply the HeaderTemplateSelector + // (if implemented) or attempt to lookup the implicit template + // for the type of the item if the headerTemplate were null. + + // Setup a hierarchical template + HierarchicalDataTemplate headerTemplate = parentItemTemplate as HierarchicalDataTemplate; + if (headerTemplate != null) + { + if (headerTemplate.ItemsSource != null && HasDefaultValue(control, HeaderedItemsControl.ItemsSourceProperty)) + { + control.SetBinding( + HeaderedItemsControl.ItemsSourceProperty, + new Binding + { + Converter = headerTemplate.ItemsSource.Converter, + ConverterCulture = headerTemplate.ItemsSource.ConverterCulture, + ConverterParameter = headerTemplate.ItemsSource.ConverterParameter, + Mode = headerTemplate.ItemsSource.Mode, + NotifyOnValidationError = headerTemplate.ItemsSource.NotifyOnValidationError, + Path = headerTemplate.ItemsSource.Path, + Source = control.Header, + ValidatesOnExceptions = headerTemplate.ItemsSource.ValidatesOnExceptions + }); + } + if (headerTemplate.IsItemTemplateSet && control.ItemTemplate == parentItemTemplate) + { + control.ClearValue(HeaderedItemsControl.ItemTemplateProperty); + if (headerTemplate.ItemTemplate != null) + { + control.ItemTemplate = headerTemplate.ItemTemplate; + } + } + if (headerTemplate.IsItemContainerStyleSet && control.ItemContainerStyle == parentItemContainerStyle) + { + control.ClearValue(HeaderedItemsControl.ItemContainerStyleProperty); + if (headerTemplate.ItemContainerStyle != null) + { + control.ItemContainerStyle = headerTemplate.ItemContainerStyle; + } + } + } + } + } + + /// + /// Check whether a control has the default value for a property. + /// + /// The control to check. + /// The property to check. + /// + /// True if the property has the default value; false otherwise. + /// + private static bool HasDefaultValue(Control control, DependencyProperty property) + { + Debug.Assert(control != null, "control should not be null!"); + Debug.Assert(property != null, "property should not be null!"); + return control.ReadLocalValue(property) == DependencyProperty.UnsetValue; + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/HubTile/HubTile.cs b/Microsoft.Phone.Controls.Toolkit/HubTile/HubTile.cs new file mode 100644 index 0000000..7856e05 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/HubTile/HubTile.cs @@ -0,0 +1,454 @@ +// (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.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Media; + +namespace Microsoft.Phone.Controls +{ + /// + /// Represents an animated tile that supports an image and a title. + /// Furthermore, it can also be associated with a message or a notification. + /// + /// Preview + [TemplateVisualState(Name = Expanded, GroupName = ImageStates)] + [TemplateVisualState(Name = Semiexpanded, GroupName = ImageStates)] + [TemplateVisualState(Name = Collapsed, GroupName = ImageStates)] + [TemplateVisualState(Name = Flipped, GroupName = ImageStates)] + [TemplatePart(Name = NotificationBlock, Type = typeof(TextBlock))] + [TemplatePart(Name = MessageBlock, Type = typeof(TextBlock))] + [TemplatePart(Name = BackTitleBlock, Type = typeof(TextBlock))] + public class HubTile : Control + { + /// + /// Common visual states. + /// + private const string ImageStates = "ImageState"; + + /// + /// Expanded visual state. + /// + private const string Expanded = "Expanded"; + + /// + /// Semiexpanded visual state. + /// + private const string Semiexpanded = "Semiexpanded"; + + /// + /// Collapsed visual state. + /// + private const string Collapsed = "Collapsed"; + + /// + /// Flipped visual state. + /// + private const string Flipped = "Flipped"; + + /// + /// Nofitication Block template part name. + /// + private const string NotificationBlock = "NotificationBlock"; + + /// + /// Message Block template part name. + /// + private const string MessageBlock = "MessageBlock"; + + /// + /// Back Title Block template part name. + /// + private const string BackTitleBlock = "BackTitleBlock"; + + /// + /// Notification Block template part. + /// + private TextBlock _notificationBlock; + + /// + /// Message Block template part. + /// + private TextBlock _messageBlock; + + /// + /// Back Title Block template part. + /// + private TextBlock _backTitleBlock; + + /// + /// Represents the number of steps inside the pipeline of stalled images + /// + internal int _stallingCounter; + + /// + /// Flag that determines if the hub tile has a primary text string associated to it. + /// If it does not, the hub tile will not drop. + /// + internal bool _canDrop; + + /// + /// Flag that determines if the hub tile has a secondary text string associated to it. + /// If it does not, the hub tile will not flip. + /// + internal bool _canFlip; + + #region Source DependencyProperty + + /// + /// Gets or sets the image source. + /// + public ImageSource Source + { + get { return (ImageSource)GetValue(SourceProperty); } + set { SetValue(SourceProperty, value); } + } + + /// + /// Identifies the Source dependency property. + /// + public static readonly DependencyProperty SourceProperty = + DependencyProperty.Register("Source", typeof(ImageSource), typeof(HubTile), new PropertyMetadata(null)); + + #endregion + + #region Title DependencyProperty + + /// + /// Gets or sets the title. + /// + public string Title + { + get { return (string)GetValue(TitleProperty); } + set { SetValue(TitleProperty, value); } + } + + /// + /// Identifies the Title dependency property. + /// + public static readonly DependencyProperty TitleProperty = + DependencyProperty.Register("Title", typeof(string), typeof(HubTile), new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnTitleChanged))); + + /// + /// Prevents the hub tile from transitioning into a Semiexpanded or Collapsed visual state if the title is not set. + /// + /// The dependency object. + /// The event information. + private static void OnTitleChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + HubTile tile = (HubTile)obj; + + if (string.IsNullOrEmpty((string)e.NewValue)) + { + tile._canDrop = false; + tile.State = ImageState.Expanded; + } + else + { + tile._canDrop = true; + } + } + + #endregion + + #region Notification DependencyProperty + + /// + /// Gets or sets the notification alert. + /// + public string Notification + { + get { return (string)GetValue(NotificationProperty); } + set { SetValue(NotificationProperty, value); } + } + + /// + /// Identifies the Notification dependency property. + /// + public static readonly DependencyProperty NotificationProperty = + DependencyProperty.Register("Notification", typeof(string), typeof(HubTile), new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnBackContentChanged))); + + /// + /// Prevents the hub tile from transitioning into a Flipped visual state if neither the notification alert nor the message are set. + /// + /// The dependency object. + /// The event information. + private static void OnBackContentChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + HubTile tile = (HubTile)obj; + + // If there is a new notification or a message, the hub tile can flip. + if ((!(string.IsNullOrEmpty(tile.Notification)) && tile.DisplayNotification) + || (!(string.IsNullOrEmpty(tile.Message)) && !tile.DisplayNotification)) + { + tile._canFlip = true; + } + else + { + tile._canFlip = false; + tile.State = ImageState.Expanded; + } + } + + #endregion + + #region Message DependencyProperty + + /// + /// Gets or sets the message. + /// + public string Message + { + get { return (string)GetValue(MessageProperty); } + set { SetValue(MessageProperty, value); } + } + + /// + /// Identifies the Message dependency property. + /// + public static readonly DependencyProperty MessageProperty = + DependencyProperty.Register("Message", typeof(string), typeof(HubTile), new PropertyMetadata(string.Empty, new PropertyChangedCallback(OnBackContentChanged))); + + #endregion + + #region DisplayNotification DependencyProperty + + /// + /// Gets or sets the flag for new notifications. + /// + public bool DisplayNotification + { + get { return (bool)GetValue(DisplayNotificationProperty); } + set { SetValue(DisplayNotificationProperty, value); } + } + + /// + /// Identifies the DisplayNotification dependency property. + /// + public static readonly DependencyProperty DisplayNotificationProperty = + DependencyProperty.Register("DisplayNotification", typeof(bool), typeof(HubTile), new PropertyMetadata(false, new PropertyChangedCallback(OnBackContentChanged))); + + #endregion + + #region IsFrozen DependencyProperty + + /// + /// Gets or sets the flag for images that do not animate. + /// + public bool IsFrozen + { + get { return (bool)GetValue(IsFrozenProperty); } + set { SetValue(IsFrozenProperty, value); } + } + + /// + /// Identifies the IsFrozen dependency property. + /// + public static readonly DependencyProperty IsFrozenProperty = + DependencyProperty.Register("IsFrozen", typeof(bool), typeof(HubTile), new PropertyMetadata(false, new PropertyChangedCallback(OnIsFrozenChanged))); + + /// + /// Removes the frozen image from the enabled image pool or the stalled image pipeline. + /// Adds the non-frozen image to the enabled image pool. + /// + /// The dependency object. + /// The event information. + private static void OnIsFrozenChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + HubTile tile = (HubTile)obj; + + if ((bool)e.NewValue) + { + HubTileService.FreezeHubTile(tile); + } + else + { + HubTileService.UnfreezeHubTile(tile); + } + } + + #endregion + + #region GroupTag DependencyProperty + + /// + /// Gets or sets the group tag. + /// + public string GroupTag + { + get { return (string)GetValue(GroupTagProperty); } + set { SetValue(GroupTagProperty, value); } + } + + /// + /// Identifies the GroupTag dependency property. + /// + public static readonly DependencyProperty GroupTagProperty = + DependencyProperty.Register("GroupTag", typeof(string), typeof(HubTile), new PropertyMetadata(string.Empty)); + + #endregion + + #region State DependencyProperty + + /// + /// Gets or sets the visual state. + /// + internal ImageState State + { + get { return (ImageState)GetValue(StateProperty); } + set { SetValue(StateProperty, value); } + } + + /// + /// Identifies the State dependency property. + /// + private static readonly DependencyProperty StateProperty = + DependencyProperty.Register("State", typeof(ImageState), typeof(HubTile), new PropertyMetadata(ImageState.Expanded, OnImageStateChanged)); + + /// + /// Triggers the transition between visual states. + /// + /// The dependency object. + /// The event information. + private static void OnImageStateChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + ((HubTile)obj).UpdateVisualState(); + } + + #endregion + + /// + /// Updates the visual state. + /// + private void UpdateVisualState() + { + string state; + + switch (State) + { + case ImageState.Expanded: + state = Expanded; + break; + case ImageState.Semiexpanded: + state = Semiexpanded; + break; + case ImageState.Collapsed: + state = Collapsed; + break; + case ImageState.Flipped: + state = Flipped; + break; + default: + state = Expanded; + break; + } + + VisualStateManager.GoToState(this, state, true); + } + + /// + /// Gets the template parts and sets binding. + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _notificationBlock = base.GetTemplateChild(NotificationBlock) as TextBlock; + _messageBlock = base.GetTemplateChild(MessageBlock) as TextBlock; + _backTitleBlock = base.GetTemplateChild(BackTitleBlock) as TextBlock; + + //Do binding in code to avoid exposing unnecessary value converters. + if (_notificationBlock != null) + { + Binding bindVisible = new Binding(); + bindVisible.Source = this; + bindVisible.Path = new PropertyPath("DisplayNotification"); + bindVisible.Converter = new VisibilityConverter(); + bindVisible.ConverterParameter = false; + _notificationBlock.SetBinding(TextBlock.VisibilityProperty, bindVisible); + } + + if(_messageBlock != null) + { + Binding bindCollapsed = new Binding(); + bindCollapsed.Source = this; + bindCollapsed.Path = new PropertyPath("DisplayNotification"); + bindCollapsed.Converter = new VisibilityConverter(); + bindCollapsed.ConverterParameter = true; + _messageBlock.SetBinding(TextBlock.VisibilityProperty, bindCollapsed); + } + + if(_backTitleBlock != null) + { + Binding bindTitle = new Binding(); + bindTitle.Source = this; + bindTitle.Path = new PropertyPath("Title"); + bindTitle.Converter = new MultipleToSingleLineStringConverter(); + _backTitleBlock.SetBinding(TextBlock.TextProperty, bindTitle); + } + + UpdateVisualState(); + } + + /// + /// Initializes a new instance of the HubTile class. + /// + public HubTile() + { + DefaultStyleKey = typeof(HubTile); + Loaded += HubTile_Loaded; + Unloaded += HubTile_Unloaded; + } + + /// + /// This event handler gets called as soon as a hub tile is added to the visual tree. + /// A reference of this hub tile is passed on to the service singleton. + /// + /// The hub tile. + /// The event information. + void HubTile_Loaded(object sender, RoutedEventArgs e) + { + HubTileService.InitializeReference(this); + } + + /// + /// This event handler gets called as soon as a hub tile is removed from the visual tree. + /// Any existing reference of this hub tile is eliminated from the service singleton. + /// + /// The hub tile. + /// The event information. + void HubTile_Unloaded(object sender, RoutedEventArgs e) + { + HubTileService.FinalizeReference(this); + } + } + + /// + /// Represents the visual states of a Hub tile. + /// + internal enum ImageState + { + /// + /// Expanded visual state value. + /// + Expanded = 0, + + /// + /// Semiexpanded visual state value. + /// + Semiexpanded = 1, + + /// + /// Collapsed visual state value. + /// + Collapsed = 2, + + /// + /// Flipped visual state value. + /// + Flipped = 3, + }; +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/HubTile/HubTileConverters.cs b/Microsoft.Phone.Controls.Toolkit/HubTile/HubTileConverters.cs new file mode 100644 index 0000000..8fb775e --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/HubTile/HubTileConverters.cs @@ -0,0 +1,54 @@ +// (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.Globalization; +using System.Windows; +using System.Windows.Data; + +namespace Microsoft.Phone.Controls +{ + /// + /// Converts a multi-line string into a single line string. + /// + internal class MultipleToSingleLineStringConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + return ((string)value).Replace(Environment.NewLine, " "); + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } + + /// + /// If there is a new notification (value) + /// Returns a Visible value for the notification block (parameter). + /// Or a Collapsed value for the message block (parameter). + /// Returns a opposite values otherwise. + /// + internal class VisibilityConverter : IValueConverter + { + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if ((bool)value ^ (bool)parameter) + { + return Visibility.Visible; + } + else + { + return Visibility.Collapsed; + } + } + + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotSupportedException(); + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/HubTile/HubTileService.cs b/Microsoft.Phone.Controls.Toolkit/HubTile/HubTileService.cs new file mode 100644 index 0000000..a579272 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/HubTile/HubTileService.cs @@ -0,0 +1,379 @@ +// (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.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Windows.Threading; + +namespace Microsoft.Phone.Controls +{ + /// + /// Provides organized animations for the hub tiles. + /// + /// Preview + public static class HubTileService + { + /// + /// Number of steps in the pipeline + /// + private const int WaitingPipelineSteps = 3; + + /// + /// Number of hub tile that can be animated at exactly the same time. + /// + private const int NumberOfSimultaneousAnimations = 1; + + /// + /// Track resurrection for weak references. + /// + private const bool TrackResurrection = false; + + /// + /// Timer to trigger animations in timely. + /// + private static DispatcherTimer Timer = new DispatcherTimer(); + + /// + /// Random number generator to take certain random decisions. + /// e.g. which hub tile is to be animated next. + /// + private static Random ProbabilisticBehaviorSelector = new Random(); + + /// + /// Pool that contains references to the hub tiles that are not frozen. + /// i.e. hub tiles that can be animated at the moment. + /// + private static List EnabledImagesPool = new List(); + + /// + /// Pool that contains references to the hub tiles which are frozen. + /// i.e. hub tiles that cannot be animated at the moment. + /// + private static List FrozenImagesPool = new List(); + + /// + /// Pipeline that contains references to the hub tiles that where animated previously. + /// These are stalled briefly before they can be animated again. + /// + private static List StalledImagesPipeline = new List(); + + /// + /// Static constructor to add the tick event handler. + /// + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Attaching event handlers cannot be done inline.")] + static HubTileService() + { + Timer.Tick += OnTimerTick; + } + + /// + /// Restart the timer to trigger animations. + /// + private static void RestartTimer() + { + if (!Timer.IsEnabled) + { + Timer.Interval = TimeSpan.FromMilliseconds(2500); + Timer.Start(); + } + } + + /// + /// Add a reference to a newly instantiated hub tile. + /// + /// The newly instantiated hub tile. + internal static void InitializeReference(HubTile tile) + { + WeakReference wref = new WeakReference(tile, TrackResurrection); + if (tile.IsFrozen) + { + AddReferenceToFrozenPool(wref); + } + else + { + AddReferenceToEnabledPool(wref); + } + + RestartTimer(); + } + + /// + /// Remove all references of a hub tile before finalizing it. + /// + /// The hub tile that is to be finalized. + internal static void FinalizeReference(HubTile tile) + { + WeakReference wref = new WeakReference(tile, TrackResurrection); + HubTileService.RemoveReferenceFromEnabledPool(wref); + HubTileService.RemoveReferenceFromFrozenPool(wref); + HubTileService.RemoveReferenceFromStalledPipeline(wref); + } + + /// + /// Add a reference of a hub tile to the enabled images pool. + /// + /// The hub tile to be added. + private static void AddReferenceToEnabledPool(WeakReference tile) + { + if (!ContainsTarget(EnabledImagesPool, tile.Target)) + { + EnabledImagesPool.Add(tile); + } + } + + /// + /// Add a reference of a hub tile to the frozen images pool. + /// + /// The hub tile to be added. + private static void AddReferenceToFrozenPool(WeakReference tile) + { + if (!ContainsTarget(FrozenImagesPool, tile.Target)) + { + FrozenImagesPool.Add(tile); + } + } + + /// + /// Add a reference of a hub tile to the stalled images pipeline. + /// + /// The hub tile to be added. + private static void AddReferenceToStalledPipeline(WeakReference tile) + { + if (!ContainsTarget(StalledImagesPipeline, tile.Target)) + { + StalledImagesPipeline.Add(tile); + } + } + + /// + /// Remove the reference of a hub tile from the enabled images pool. + /// + /// The hub tile to be removed. + private static void RemoveReferenceFromEnabledPool(WeakReference tile) + { + RemoveTarget(EnabledImagesPool, tile.Target); + } + + /// + /// Remove the reference of a hub tile from the frozen images pool. + /// + /// The hub tile to be removed. + private static void RemoveReferenceFromFrozenPool(WeakReference tile) + { + RemoveTarget(FrozenImagesPool, tile.Target); + } + + /// + /// Remove the reference of a hub tile from the stalled images pipeline. + /// + /// The hub tile to be removed. + private static void RemoveReferenceFromStalledPipeline(WeakReference tile) + { + RemoveTarget(StalledImagesPipeline, tile.Target); + } + + /// + /// Determine if there is a reference to a known target in a list. + /// + /// The list to be examined. + /// The known target. + /// True if a reference to the known target exists in the list. False otherwise. + private static bool ContainsTarget(List list, Object target) + { + for (int i = 0; i < list.Count; i++) + { + if (list[i].Target == target) + { + return true; + } + } + return false; + } + + /// + /// Remove a reference to a known target in a list. + /// + /// The list to be examined. + /// The known target. + private static void RemoveTarget(List list, Object target) + { + for (int i = 0; i < list.Count; i++) + { + if (list[i].Target == target) + { + list.RemoveAt(i); + return; + } + } + } + + /// + /// Executes the code to process a visual transition: + /// 1. Stop the timer. + /// 2. Advances the stalled tiles to the next step in the pipeline. + /// If there is at least one tile that can be currently animated ... + /// 3. Animate as many tiles as indicated. + /// 4. Select a tile andomly from the pool of enabled tiles. + /// 5. Based on this tile's current visual state, move it onto + /// the next one. + /// 6. Set the stalling counter for the recently animated image. + /// 7. Take it out from the pool and into the pipeline to prevent it + /// from being animated continuously. + /// 8. Restart the timer with a randomly generated time interval + /// between 100 and 3000 ms. + /// Notice that if there are no hub tiles that can be animated, + /// the timer is not restarted. + /// + /// The static timer. + /// The event information. + private static void OnTimerTick(object sender, EventArgs e) + { + Timer.Stop(); + + for (int i = 0; i < StalledImagesPipeline.Count; i++) + { + if ((StalledImagesPipeline[i].Target as HubTile)._stallingCounter-- == 0) + { + AddReferenceToEnabledPool(StalledImagesPipeline[i]); + RemoveReferenceFromStalledPipeline(StalledImagesPipeline[i]); + i--; + } + } + + if (EnabledImagesPool.Count > 0) + { + for (int j = 0; j < NumberOfSimultaneousAnimations; j++) + { + int index = ProbabilisticBehaviorSelector.Next(EnabledImagesPool.Count); + + switch ((EnabledImagesPool[index].Target as HubTile).State) + { + case ImageState.Expanded: + //If the tile can neither drop nor flip, do not change state. + if (!(EnabledImagesPool[index].Target as HubTile)._canDrop && !(EnabledImagesPool[index].Target as HubTile)._canFlip) + { + break; + } + + //If the tile can only flip, change to the Flipped state. + if (!(EnabledImagesPool[index].Target as HubTile)._canDrop && (EnabledImagesPool[index].Target as HubTile)._canFlip) + { + (EnabledImagesPool[index].Target as HubTile).State = ImageState.Flipped; + break; + } + + //If the tile can only drop, change to the Semidropped state. + if (!(EnabledImagesPool[index].Target as HubTile)._canFlip && (EnabledImagesPool[index].Target as HubTile)._canDrop) + { + (EnabledImagesPool[index].Target as HubTile).State = ImageState.Semiexpanded; + break; + } + + //If the tile can drop and flip, change randomly either to the Semidropped state or the Flipped state. + if (ProbabilisticBehaviorSelector.Next(2) == 0) + { + (EnabledImagesPool[index].Target as HubTile).State = ImageState.Semiexpanded; + } + else + { + (EnabledImagesPool[index].Target as HubTile).State = ImageState.Flipped; + } + break; + case ImageState.Semiexpanded: + (EnabledImagesPool[index].Target as HubTile).State = ImageState.Collapsed; + break; + case ImageState.Collapsed: + (EnabledImagesPool[index].Target as HubTile).State = ImageState.Expanded; + break; + case ImageState.Flipped: + (EnabledImagesPool[index].Target as HubTile).State = ImageState.Expanded; + break; + } + (EnabledImagesPool[index].Target as HubTile)._stallingCounter = WaitingPipelineSteps; + AddReferenceToStalledPipeline(EnabledImagesPool[index]); + RemoveReferenceFromEnabledPool(EnabledImagesPool[index]); + } + } + else if (StalledImagesPipeline.Count == 0) + { + return; + } + + Timer.Interval = TimeSpan.FromMilliseconds(ProbabilisticBehaviorSelector.Next(1, 31) * 100); + Timer.Start(); + } + + /// + /// Freeze a hub tile. + /// + /// The hub tile to be frozen. + public static void FreezeHubTile(HubTile tile) + { + WeakReference wref = new WeakReference(tile, TrackResurrection); + AddReferenceToFrozenPool(wref); + RemoveReferenceFromEnabledPool(wref); + RemoveReferenceFromStalledPipeline(wref); + } + + /// + /// Unfreezes a hub tile and restarts the timer if needed. + /// + /// The hub tile to be unfrozen. + public static void UnfreezeHubTile(HubTile tile) + { + WeakReference wref = new WeakReference(tile, TrackResurrection); + AddReferenceToEnabledPool(wref); + RemoveReferenceFromFrozenPool(wref); + RemoveReferenceFromStalledPipeline(wref); + + RestartTimer(); + } + + /// + /// Freezes all the hub tiles with the specified group tag that are not already frozen. + /// + /// The group tag representing the hub tiles that should be frozen. + public static void FreezeGroup(string group) + { + for (int i = 0; i < EnabledImagesPool.Count; i++) + { + if ((EnabledImagesPool[i].Target as HubTile).GroupTag == group) + { + (EnabledImagesPool[i].Target as HubTile).IsFrozen = true; + i--; + } + } + + for (int j = 0; j < StalledImagesPipeline.Count; j++) + { + if ((StalledImagesPipeline[j].Target as HubTile).GroupTag == group) + { + (StalledImagesPipeline[j].Target as HubTile).IsFrozen = true; + j--; + } + } + } + + /// + /// Unfreezes all the hub tiles with the specified group tag + /// that are currently frozen and restarts the timer if needed. + /// + /// The group tag representing the hub tiles that should be unfrozen. + public static void UnfreezeGroup(string group) + { + for (int i = 0; i < FrozenImagesPool.Count; i++) + { + if ((FrozenImagesPool[i].Target as HubTile).GroupTag == group) + { + (FrozenImagesPool[i].Target as HubTile).IsFrozen = false; + i--; + } + } + + RestartTimer(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Input/GestureHelperEventArgs.cs b/Microsoft.Phone.Controls.Toolkit/Input/GestureHelperEventArgs.cs new file mode 100644 index 0000000..924154d --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Input/GestureHelperEventArgs.cs @@ -0,0 +1,322 @@ +// (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.Windows; +using System.Windows.Controls; +using System.Windows.Media; + +namespace Microsoft.Phone.Controls +{ + /// + /// The base class for all gesture events. Also used by Tap, DoubleTap and Hold. + /// + public class GestureEventArgs : EventArgs + { + /// + /// The point, in unrotated screen coordinates, where the gesture occurred. + /// + protected Point GestureOrigin { get; private set; } + + /// + /// The point, in unrotated screen coordinates, where the first touchpoint is now. + /// + protected Point TouchPosition { get; private set; } + + internal GestureEventArgs(Point gestureOrigin, Point position) + { + GestureOrigin = gestureOrigin; + TouchPosition = position; + } + + /// + /// The first hit-testable item under the touch point. Determined by a combination of order in the tree and + /// Z-order. + /// + public object OriginalSource { get; internal set; } + + /// + /// If an event handler sets this to true, it stops event bubbling. + /// + public bool Handled { get; set; } + + /// + /// Returns the position of the gesture's starting point relative to a given UIElement. + /// + /// The return value will be relative to this element. + /// The gesture's starting point relative to the given UIElement. + public Point GetPosition(UIElement relativeTo) + { + return GetPosition(relativeTo, TouchPosition); + } + + /// + /// Returns the position of a given point relative to a given UIElement. + /// + /// The return value will be relative to this element. + /// The point to translate. + /// The given point relative to the given UIElement. + protected static Point GetPosition(UIElement relativeTo, Point point) + { + if (relativeTo == null) + { + // Transform relative to RootVisual + relativeTo = Application.Current.RootVisual; + } + if (relativeTo != null) + { + // Determine position + GeneralTransform transform = relativeTo.TransformToVisual(null).Inverse; + return transform.Transform(point); + } + else + { + // Unable to transform; return point as-is + return point; + } + } + } + + /// + /// The event args used in the DragStarted event. + /// + public class DragStartedGestureEventArgs : GestureEventArgs + { + internal DragStartedGestureEventArgs(Point gestureOrigin, Orientation direction) + : base(gestureOrigin, gestureOrigin) + { + Direction = direction; + } + + /// + /// The direction of the drag gesture, as determined by the initial drag change. + /// + public Orientation Direction { get; private set; } + } + + /// + /// The event args used by the DragDelta event. + /// + public class DragDeltaGestureEventArgs : GestureEventArgs + { + internal DragDeltaGestureEventArgs(Point gestureOrigin, Point currentPosition, Point change, Orientation direction) + : base(gestureOrigin, currentPosition) + { + HorizontalChange = change.X; + VerticalChange = change.Y; + Direction = direction; + } + + /// + /// The horizontal (X) change for this drag event. + /// + public double HorizontalChange { get; private set; } + + /// + /// The vertical (Y) change for this drag event. + /// + public double VerticalChange { get; private set; } + + /// + /// The direction of the drag gesture, as determined by the initial drag change. + /// + public Orientation Direction { get; private set; } + } + + /// + /// The event args used by the DragCompleted event. + /// + public class DragCompletedGestureEventArgs : GestureEventArgs + { + internal DragCompletedGestureEventArgs(Point gestureOrigin, Point currentPosition, Point change, Orientation direction, Point finalVelocity) + : base(gestureOrigin, currentPosition) + { + HorizontalChange = change.X; + VerticalChange = change.Y; + Direction = direction; + HorizontalVelocity = finalVelocity.X; + VerticalVelocity = finalVelocity.Y; + } + + /// + /// The total horizontal (X) change of the drag event. + /// + public double HorizontalChange { get; private set; } + + /// + /// The total vertical (Y) change of the drag event. + /// + public double VerticalChange { get; private set; } + + /// + /// The direction of the drag gesture, as determined by the initial drag change. + /// + public Orientation Direction { get; private set; } + + /// + /// The final horizontal (X) velocity of the drag, if the drag was inertial. + /// + public double HorizontalVelocity { get; private set; } + + /// + /// The final vertical (Y) velocity of the drag, if the drag was inertial. + /// + public double VerticalVelocity { get; private set; } + } + + /// + /// The event args used by the Flick event. + /// + public class FlickGestureEventArgs : GestureEventArgs + { + private Point _velocity; + + internal FlickGestureEventArgs(Point hostOrigin, Point velocity) + : base(hostOrigin, hostOrigin) + { + _velocity = velocity; + } + + /// + /// The horizontal (X) velocity of the flick. + /// + public double HorizontalVelocity { get { return _velocity.X; } } + + /// + /// The vertical (Y) velocity of the flick. + /// + public double VerticalVelocity { get { return _velocity.Y; } } + + /// + /// The angle of the flick. + /// + public double Angle + { + get { return MathHelpers.GetAngle(_velocity.X, _velocity.Y); } + } + + /// + /// The direction of the flick gesture, as determined by the flick velocities. + /// + public Orientation Direction + { + get { return Math.Abs(_velocity.X) >= Math.Abs(_velocity.Y) ? Orientation.Horizontal : Orientation.Vertical; } + } + } + + /// + /// The base class for multi-touch gesture event args. Currently used only for + /// two-finger (pinch) operations. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Multi")] + public class MultiTouchGestureEventArgs : GestureEventArgs + { + /// + /// The second touch point's initial position + /// + protected Point GestureOrigin2 { get; private set; } + + /// + /// The second touch point. The first is stored in GestureEventArgs. + /// + protected Point TouchPosition2 { get; private set; } + + internal MultiTouchGestureEventArgs(Point gestureOrigin, Point gestureOrigin2, Point position, Point position2) + : base(gestureOrigin, position) + { + GestureOrigin2 = gestureOrigin2; + TouchPosition2 = position2; + } + + /// + /// Returns the position of either of the two touch points (0 or 1) relative to + /// the UIElement provided. + /// + /// The return value will be relative to this element. + /// The touchpoint to use (0 or 1). + /// The gesture's starting point relative to the given UIElement. + public Point GetPosition(UIElement relativeTo, int index) + { + if (index == 0) + { + return GetPosition(relativeTo); + } + else if (index == 1) + { + return GetPosition(relativeTo, TouchPosition2); + } + else + throw new ArgumentOutOfRangeException("index"); + } + } + + /// + /// The event args used by the PinchStarted event. + /// + public class PinchStartedGestureEventArgs : MultiTouchGestureEventArgs + { + internal PinchStartedGestureEventArgs(Point gestureOrigin, Point gestureOrigin2, Point pinch, Point pinch2) + : base(gestureOrigin, gestureOrigin2, pinch, pinch2) + { + } + + /// + /// The distance between the two touch points. + /// + public double Distance + { + get { return MathHelpers.GetDistance(TouchPosition, TouchPosition2); } + } + + /// + /// The angle defined by the two touch points. + /// + public double Angle + { + get { return MathHelpers.GetAngle(TouchPosition2.X - TouchPosition.X, TouchPosition2.Y - TouchPosition.Y); } + } + } + + /// + /// The event args used by the PinchDelta and PinchCompleted events. + /// + public class PinchGestureEventArgs : MultiTouchGestureEventArgs + { + internal PinchGestureEventArgs(Point gestureOrigin, Point gestureOrigin2, Point position, Point position2) + : base(gestureOrigin, gestureOrigin2, position, position2) + { + } + + /// + /// Returns the ratio of the current distance between touchpoints / the original distance + /// between the touchpoints. + /// + public double DistanceRatio + { + get + { + double originalDistance = Math.Max(MathHelpers.GetDistance(GestureOrigin, GestureOrigin2), 1.0); + double newDistance = Math.Max(MathHelpers.GetDistance(TouchPosition, TouchPosition2), 1.0); + + return newDistance / originalDistance; + } + } + + /// + /// Returns the difference in angle between the current touch positions and the original + /// touch positions. + /// + public double TotalAngleDelta + { + get + { + double oldAngle = MathHelpers.GetAngle(GestureOrigin2.X - GestureOrigin.X, GestureOrigin2.Y - GestureOrigin.Y); + double newAngle = MathHelpers.GetAngle(TouchPosition2.X - TouchPosition.X, TouchPosition2.Y - TouchPosition.Y); + + return newAngle - oldAngle; + } + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/Input/GestureListener.cs b/Microsoft.Phone.Controls.Toolkit/Input/GestureListener.cs new file mode 100644 index 0000000..a1fa9cd --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Input/GestureListener.cs @@ -0,0 +1,88 @@ +// (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; + +namespace Microsoft.Phone.Controls +{ + //public enum GestureType + //{ + // None = 0, + // Tap = 1, + // DoubleTap = 2, + // Hold = 4, + // HorizontalDrag = 8, + // VerticalDrag = 16, + // FreeDrag = 32, + // PinchDelta = 64, + // Flick = 128, + // DragCompleted = 256, + // PinchCompleted = 512, + //} + + public partial class GestureListener + { + /// + /// The GestureBegin event. + /// + public event EventHandler GestureBegin; + + /// + /// The GestureCompleted event. + /// + public event EventHandler GestureCompleted; + + /// + /// The Tap event (touch, release, no movement). + /// + public event EventHandler Tap; + + /// + /// The DoubleTap event is raised instead of Tap if the time between two taps is short eonugh. + /// + public event EventHandler DoubleTap; + + /// + /// The Hold event (touch and hold for one second) + /// + public event EventHandler Hold; + + /// + /// The DragStarted event. + /// + public event EventHandler DragStarted; + + /// + /// The DragDelta event. + /// + public event EventHandler DragDelta; + + /// + /// The DragCompleted event. Will be raised on touch release after a drag, or + /// when a second touch point is added. + /// + public event EventHandler DragCompleted; + + /// + /// The Flick event. Raised when a drag that was fast enough ends with a release. + /// + public event EventHandler Flick; + + /// + /// The PinchStarted event. + /// + public event EventHandler PinchStarted; + + /// + /// Any two-touch point (two finger) operation. + /// + public event EventHandler PinchDelta; + + /// + /// The end of a pinch operation. + /// + public event EventHandler PinchCompleted; + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/Input/GestureListenerStatic.cs b/Microsoft.Phone.Controls.Toolkit/Input/GestureListenerStatic.cs new file mode 100644 index 0000000..ae21531 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Input/GestureListenerStatic.cs @@ -0,0 +1,372 @@ +// (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.Generic; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Threading; +using Microsoft.Xna.Framework.Input.Touch; + +namespace Microsoft.Phone.Controls +{ + /// + /// The GestureListener class raises events similar to those provided by the XNA TouchPanel, but it is designed for + /// Silverlight's event-driven model, rather than XNA's loop/polling model, and it also takes care of the hit testing + /// and event routing. + /// + public partial class GestureListener + { + private static DispatcherTimer _timer; + + private static bool _isInTouch; + + private static List _elements; + + private static Point _gestureOrigin; + private static bool _gestureOriginChanged; + private static Nullable _gestureOrientation; + + private static Point _cumulativeDelta; + private static Point _cumulativeDelta2; + + private static Point _finalVelocity; + + private static Point _pinchOrigin; + private static Point _pinchOrigin2; + + private static Point _lastSamplePosition; + private static Point _lastSamplePosition2; + + private static bool _isPinching; + private static bool _flicked; + private static bool _isDragging; + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification="Need static ctor for more than instantiation")] + static GestureListener() + { + Touch.FrameReported += OnTouchFrameReported; + + TouchPanel.EnabledGestures = + GestureType.Tap | + GestureType.DoubleTap | + GestureType.Hold | + GestureType.FreeDrag | + GestureType.DragComplete | + GestureType.Flick | + GestureType.Pinch | + GestureType.PinchComplete; + + _timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(100) }; + _timer.Tick += OnTimerTick; + } + + /// + /// Handle touch events. + /// + /// + /// + private static void OnTouchFrameReported(object sender, TouchFrameEventArgs e) + { + bool newIsInTouch = false; + Point gestureOrigin = new Point(0, 0); + + foreach (TouchPoint point in e.GetTouchPoints(null)) + { + if (point.Action != TouchAction.Up) + { + gestureOrigin = point.Position; + newIsInTouch = true; + break; + } + } + + if (!_isInTouch && newIsInTouch) + { + // The user was not in the middle of a gesture, but one has started. + _gestureOrigin = gestureOrigin; + TouchStart(); + } + else if (_isInTouch && !newIsInTouch) + { + // The user was in the middle of a gesture, but there are no active + // touch points anymore. + TouchComplete(); + } + else if (_isInTouch) + { + // The state has not changed, and the user was in the middle of a gesture. + TouchDelta(); + } + else + { + // Possible error condition? The user was not in the middle of a + // gesture, but a Touch.FrameReported event was received with no + // active touch points. We should poll the TouchPanel just to be + // safe, but do so in such a way that resets the state. + TouchStart(); + } + + _isInTouch = newIsInTouch; + } + + /// + /// A touch has started. + /// + private static void TouchStart() + { + _cumulativeDelta.X = _cumulativeDelta.Y = _cumulativeDelta2.X = _cumulativeDelta2.Y = 0; + _finalVelocity.X = _finalVelocity.Y = 0; + _isDragging = _flicked = false; + _elements = new List(VisualTreeHelper.FindElementsInHostCoordinates(_gestureOrigin, Application.Current.RootVisual)); + _gestureOriginChanged = false; + + RaiseGestureEvent((helper) => helper.GestureBegin, () => new GestureEventArgs(_gestureOrigin, _gestureOrigin), false); + + ProcessTouchPanelEvents(); + _timer.Start(); + } + + /// + /// A touch is continuing... + /// + private static void TouchDelta() + { + ProcessTouchPanelEvents(); + } + + /// + /// A touch has ended. + /// + private static void TouchComplete() + { + ProcessTouchPanelEvents(); + + RaiseGestureEvent((helper) => helper.GestureCompleted, () => new GestureEventArgs(_gestureOrigin, _lastSamplePosition), false); + + _elements = null; + _gestureOrientation = null; + _timer.Stop(); + } + + static void OnTimerTick(object sender, EventArgs e) + { + ProcessTouchPanelEvents(); + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1505:AvoidUnmaintainableCode"), System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Maintainability", "CA1502:AvoidExcessiveComplexity")] + private static void ProcessTouchPanelEvents() + { + Point delta = new Point(0, 0); + + GeneralTransform deltaTransform = null; + + while (TouchPanel.IsGestureAvailable) + { + GestureSample sample = TouchPanel.ReadGesture(); + + Point samplePosition = sample.Position.ToPoint(); + Point samplePosition2 = sample.Position2.ToPoint(); + + Point sampleDelta = sample.Delta.ToPoint(); + GetTranslatedDelta(ref deltaTransform, ref sampleDelta, ref _cumulativeDelta, sample.GestureType != GestureType.Flick); + Point sampleDelta2 = sample.Delta2.ToPoint(); + GetTranslatedDelta(ref deltaTransform, ref sampleDelta2, ref _cumulativeDelta2, sample.GestureType != GestureType.Flick); + + // Example: if a drag becomes a pinch, or vice-versa, we want to change the elements receiving the event + if (_elements == null || _gestureOriginChanged) + { + _gestureOrigin = samplePosition; + _elements = new List(VisualTreeHelper.FindElementsInHostCoordinates(_gestureOrigin, Application.Current.RootVisual)); + _gestureOriginChanged = false; + } + + if (!_gestureOrientation.HasValue && (sampleDelta.X != 0 || sampleDelta.Y != 0)) + { + _gestureOrientation = Math.Abs(sampleDelta.X) >= Math.Abs(sampleDelta.Y) ? Orientation.Horizontal : Orientation.Vertical; + } + + switch (sample.GestureType) + { + case GestureType.Tap: + RaiseGestureEvent((helper) => helper.Tap, () => new GestureEventArgs(_gestureOrigin, samplePosition), false); + break; + + case GestureType.DoubleTap: + RaiseGestureEvent((helper) => helper.DoubleTap, () => new GestureEventArgs(_gestureOrigin, samplePosition), false); + break; + + case GestureType.Hold: + RaiseGestureEvent((helper) => helper.Hold, () => new GestureEventArgs(_gestureOrigin, samplePosition), false); + break; + + case GestureType.FreeDrag: + if (sampleDelta.X != 0 || sampleDelta.Y != 0) + { + if (!_isDragging) + { + RaiseGestureEvent((helper) => helper.DragStarted, () => new DragStartedGestureEventArgs(_gestureOrigin, _gestureOrientation.Value), true); + _isDragging = true; + } + + delta.X += sampleDelta.X; + delta.Y += sampleDelta.Y; + _lastSamplePosition = samplePosition; + } + break; + + case GestureType.DragComplete: + if (!_flicked) + { + if (delta.X != 0 || delta.Y != 0) + { + // raise drag + RaiseGestureEvent((helper) => helper.DragDelta, () => new DragDeltaGestureEventArgs(_gestureOrigin, samplePosition, delta, _gestureOrientation.Value), false); + delta.X = delta.Y = 0; + } + } + + if (_isDragging) + { + RaiseGestureEvent((helper) => helper.DragCompleted, () => new DragCompletedGestureEventArgs(_gestureOrigin, _lastSamplePosition, _cumulativeDelta, _gestureOrientation.Value, _finalVelocity), false); + delta.X = delta.Y = 0; + } + + _cumulativeDelta.X = _cumulativeDelta.Y = 0; + _flicked = _isDragging = false; + _gestureOriginChanged = true; + break; + + case GestureType.Flick: + // Do not raise any additional drag events that may be queued. + _flicked = true; + _finalVelocity = sampleDelta; + RaiseGestureEvent((helper) => helper.Flick, () => new FlickGestureEventArgs(_gestureOrigin, sampleDelta), true); + break; + + case GestureType.Pinch: + { + if (!_isPinching) + { + _isPinching = true; + _pinchOrigin = samplePosition; + _pinchOrigin2 = samplePosition2; + RaiseGestureEvent((helper) => helper.PinchStarted, () => new PinchStartedGestureEventArgs(_pinchOrigin, _pinchOrigin2, _pinchOrigin, _pinchOrigin2), true); + } + + _lastSamplePosition = samplePosition; + _lastSamplePosition2 = samplePosition2; + RaiseGestureEvent((helper) => helper.PinchDelta, () => new PinchGestureEventArgs(_pinchOrigin, _pinchOrigin2, samplePosition, samplePosition2), false); + } + break; + + case GestureType.PinchComplete: + _isPinching = false; + RaiseGestureEvent((helper) => helper.PinchCompleted, () => new PinchGestureEventArgs(_pinchOrigin, _pinchOrigin2, _lastSamplePosition, _lastSamplePosition2), false); + _cumulativeDelta.X = _cumulativeDelta.Y = _cumulativeDelta2.X = _cumulativeDelta2.Y = 0; + _gestureOriginChanged = true; + break; + } + } + + if (!_flicked && (delta.X != 0 || delta.Y != 0)) + { + RaiseGestureEvent((helper) => helper.DragDelta, () => new DragDeltaGestureEventArgs(_gestureOrigin, _lastSamplePosition, delta, _gestureOrientation.Value), false); + } + } + + private static void GetTranslatedDelta( + ref GeneralTransform deltaTransform, + ref Point sampleDelta, + ref Point cumulativeDelta, + bool addToCumulative) + { + if (sampleDelta.X != 0 || sampleDelta.Y != 0) + { + if (deltaTransform == null && Application.Current.RootVisual != null) + { + deltaTransform = GetInverseRootTransformNoOffset(); + } + if (deltaTransform != null) + { + sampleDelta = deltaTransform.Transform(sampleDelta); + if (addToCumulative) + { + cumulativeDelta.X += sampleDelta.X; + cumulativeDelta.Y += sampleDelta.Y; + } + } + } + } + + private static GeneralTransform GetInverseRootTransformNoOffset() + { + GeneralTransform transform = Application.Current.RootVisual.TransformToVisual(null).Inverse; + + MatrixTransform matrixTransform = transform as MatrixTransform; + if (matrixTransform != null) + { + Matrix matrix = matrixTransform.Matrix; + matrix.OffsetX = matrix.OffsetY = 0; + matrixTransform.Matrix = matrix; + } + + return transform; + } + + /// + /// This method does all the necessary work to raise a gesture event. It sets the orginal source, does the routing, + /// handles Handled, and only creates the event args if they are needed. + /// + /// This is the type of event args that will be raised. + /// Gets the specific event to raise. + /// Lazy creator function for the event args. + /// Indicates whether the mouse capture should be released + private static void RaiseGestureEvent(Func> eventGetter, Func argsGetter, bool releaseMouseCapture) where T : GestureEventArgs + { + T args = null; + + FrameworkElement originalSource = null; + bool handled = false; + + foreach (FrameworkElement element in _elements) + { + if (releaseMouseCapture) + { + element.ReleaseMouseCapture(); + } + + if (!handled) + { + if (originalSource == null) + { + originalSource = element; + } + + GestureListener helper = GestureService.GetGestureListenerInternal(element, false); + if (helper != null) + { + SafeRaise.Raise(eventGetter(helper), element, () => + { + if (args == null) + { + args = argsGetter(); + args.OriginalSource = originalSource; + } + return args; + }); + } + + if (args != null && args.Handled == true) + { + handled = true; + } + } + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Input/GestureService.cs b/Microsoft.Phone.Controls.Toolkit/Input/GestureService.cs new file mode 100644 index 0000000..8f42a9c --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Input/GestureService.cs @@ -0,0 +1,85 @@ +// (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.Windows; + +namespace Microsoft.Phone.Controls +{ + /// + /// The GestureService class is the helper for getting and setting GestureListeners + /// on elements. + /// + public static class GestureService + { + /// + /// Gets a GestureListener for the new element. Will create a new one if necessary. + /// + /// The object to get the GestureListener from. + /// Either the previously existing GestureListener, or a new one. + public static GestureListener GetGestureListener(DependencyObject obj) + { + if (obj == null) + { + throw new ArgumentNullException("obj"); + } + + return GetGestureListenerInternal(obj, true); + } + + /// + /// Gets the GestureListener on an element. If one is not set, can create a new one + /// so that this will never return null, depending on the state of the createIfMissing + /// flag. + /// + /// The object to get the GestureListener from. + /// When this is true, if the attached property was not set on the element, it will create one and set it on the element. + /// + internal static GestureListener GetGestureListenerInternal(DependencyObject obj, bool createIfMissing) + { + GestureListener listener = (GestureListener)obj.GetValue(GestureListenerProperty); + if (listener == null && createIfMissing) + { + listener = new GestureListener(); + SetGestureListenerInternal(obj, listener); + } + return listener; + } + + /// + /// Sets the GestureListener on an element. Needed for XAML, but should not be used in code. Use + /// GetGestureListener instead, which will create a new instance if one is not already set, to + /// add your handlers to an element. + /// + /// The object to set the GestureListener on. + /// The GestureListener. + [Obsolete("Do not add handlers using this method. Instead, use GetGestureListener, which will create a new instance if one is not already set, to add your handlers to an element.", true)] + public static void SetGestureListener(DependencyObject obj, GestureListener value) + { + if (obj == null) + { + throw new ArgumentNullException("obj"); + } + + SetGestureListenerInternal(obj, value); + } + + /// + /// This is used to set the value of the attached DependencyProperty internally. + /// + /// The object to set the GestureListener on. + /// The GestureListener. + private static void SetGestureListenerInternal(DependencyObject obj, GestureListener value) + { + obj.SetValue(GestureListenerProperty, value); + } + + /// + /// The definition of the GestureListener attached DependencyProperty. + /// + public static readonly DependencyProperty GestureListenerProperty = + DependencyProperty.RegisterAttached("GestureListener", typeof(GestureListener), typeof(GestureService), new PropertyMetadata(null)); + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/ListPicker/ExpansionMode.cs b/Microsoft.Phone.Controls.Toolkit/ListPicker/ExpansionMode.cs new file mode 100644 index 0000000..e8e6de0 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/ListPicker/ExpansionMode.cs @@ -0,0 +1,23 @@ +// (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. + +namespace Microsoft.Phone.Controls +{ + /// + /// The expansion mode of a ListPicker. + /// + public enum ExpansionMode + { + /// + /// Allows all items to be visible on the page. + /// + ExpansionAllowed, + + /// + /// Requires all items to be displayed in a full screen popup. + /// + FullScreenOnly + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/ListPicker/ListPicker.cs b/Microsoft.Phone.Controls.Toolkit/ListPicker/ListPicker.cs new file mode 100644 index 0000000..1f4d378 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/ListPicker/ListPicker.cs @@ -0,0 +1,1340 @@ +// (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.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 System.Windows.Navigation; + +namespace Microsoft.Phone.Controls +{ + /// + /// Class that implements a flexible list-picking experience with a custom interface for few/many items. + /// + /// Preview + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "This is a complicated control.")] + [TemplatePart(Name = ItemsPresenterPartName, Type = typeof(ItemsPresenter))] + [TemplatePart(Name = ItemsPresenterTranslateTransformPartName, Type = typeof(TranslateTransform))] + [TemplatePart(Name = ItemsPresenterHostPartName, Type = typeof(Canvas))] + [TemplatePart(Name = MultipleSelectionModeSummaryPartName, Type = typeof(TextBlock))] + [TemplateVisualState(GroupName = PickerStatesGroupName, Name = PickerStatesNormalStateName)] + [TemplateVisualState(GroupName = PickerStatesGroupName, Name = PickerStatesHighlightedStateName)] + [TemplateVisualState(GroupName = PickerStatesGroupName, Name = PickerStatesDisabledStateName)] + public class ListPicker : ItemsControl + { + private const string ItemsPresenterPartName = "ItemsPresenter"; + private const string ItemsPresenterTranslateTransformPartName = "ItemsPresenterTranslateTransform"; + private const string ItemsPresenterHostPartName = "ItemsPresenterHost"; + private const string MultipleSelectionModeSummaryPartName = "MultipleSelectionModeSummary"; + private const string BorderPartName = "Border"; + + private const string PickerStatesGroupName = "PickerStates"; + private const string PickerStatesNormalStateName = "Normal"; + private const string PickerStatesHighlightedStateName = "Highlighted"; + private const string PickerStatesDisabledStateName = "Disabled"; + + /// + /// In Mango, the size of list pickers in expanded mode was given extra offset. + /// + private const double NormalModeOffset = 4; + + 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 TranslateTransform _itemsPresenterTranslateTransformPart; + private bool _updatingSelection; + private int _deferredSelectedIndex = -1; + private object _deferredSelectedItem = null; + + private object _frameContentWhenOpened; + private NavigationInTransition _savedNavigationInTransition; + private NavigationOutTransition _savedNavigationOutTransition; + private ListPickerPage _listPickerPage; + private TextBlock _multipleSelectionModeSummary; + private Border _border; + + /// + /// Whether this list picker has the picker page opened. + /// + private bool _hasPickerPageOpen; + + /// + /// Event that is raised when the selection changes. + /// + public event SelectionChangedEventHandler SelectionChanged; + + /// + /// Gets or sets the delegate, which is called to summarize a list of selections into a string. + /// If not implemented, the default summarizing behavior will be used. + /// If this delegate is implemented, default summarizing behavior can be achieved by returning + /// null instead of a string. + /// + public Func SummaryForSelectedItemsDelegate + { + get { return (Func)GetValue(SummaryForSelectedItemsDelegateProperty); } + set { SetValue(SummaryForSelectedItemsDelegateProperty, value); } + } + + /// + /// Identifies the SummaryForSelectedItemsDelegate DependencyProperty. + /// + public static readonly DependencyProperty SummaryForSelectedItemsDelegateProperty = + DependencyProperty.Register("SummaryForSelectedItemsDelegate", typeof(Func), typeof(ListPicker), null); + + /// + /// Gets or sets the ListPickerMode (ex: Normal/Expanded/Full). + /// + public ListPickerMode ListPickerMode + { + get { return (ListPickerMode)GetValue(ListPickerModeProperty); } + private 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) + { + if ((ListPickerMode.Expanded == oldValue)) + { + if (null != _page) + { + _page.BackKeyPress -= OnPageBackKeyPress; + _page = null; + } + + if (null != _frame) + { + _frame.ManipulationStarted -= OnFrameManipulationStarted; + _frame = null; + } + } + + if (ListPickerMode.Expanded == newValue) + { + // Hook up to frame if not already done + if (null == _frame) + { + _frame = Application.Current.RootVisual as PhoneApplicationFrame; + if (null != _frame) + { + _frame.AddHandler(ManipulationStartedEvent, new EventHandler(OnFrameManipulationStarted), true); + } + } + + if (null != _frame) + { + _page = _frame.Content as PhoneApplicationPage; + if (null != _page) + { + _page.BackKeyPress += OnPageBackKeyPress; + } + } + } + + if (ListPickerMode.Full == oldValue) + { + ClosePickerPage(); + } + if (ListPickerMode.Full == newValue) + { + OpenPickerPage(); + } + + SizeForAppropriateView(ListPickerMode.Full != oldValue); + IsHighlighted = (ListPickerMode.Expanded == newValue); + } + + + /// + /// Whether the list picker is highlighted. + /// This occurs when the user is manipulating the box or when in expanded mode. + /// + private bool IsHighlighted + { + get { return (bool)GetValue(IsHighlightedProperty); } + set { SetValue(IsHighlightedProperty, value); } + } + + private static readonly DependencyProperty IsHighlightedProperty = + DependencyProperty.Register("IsHighlighted", + typeof(bool), + typeof(ListPicker), + new PropertyMetadata(false, new PropertyChangedCallback(OnIsHighlightedChanged))); + + /// + /// Highlight property changed + /// + private static void OnIsHighlightedChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + (o as ListPicker).OnIsHighlightedChanged(); + } + + /// + /// Highlight property changed + /// + private void OnIsHighlightedChanged() + { + UpdateVisualStates(true); + } + + + /// + /// Enabled property changed + /// + private static void OnIsEnabledChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + (o as ListPicker).OnIsEnabledChanged(); + } + + /// + /// Enabled property changed + /// + private void OnIsEnabledChanged() + { + UpdateVisualStates(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) + { + if (null == Items || Items.Count == 0) + { + if (null == Template) + { + // Can't set the value now; remember it for later + _deferredSelectedItem = newValue; + return; + } + else + { + throw new InvalidOperationException(Properties.Resources.InvalidSelectedItem); + } + } + + // Validate new value + int 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 + var 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 the maximum number of items for which Expanded mode will be used, 5. + /// + public int ItemCountThreshold + { + get { return (int)GetValue(ItemCountThresholdProperty); } + private 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"); + } + } + + /// + /// Gets or sets the Uri to use for loading the ListPickerPage instance when the control is tapped. + /// + public Uri PickerPageUri + { + get { return (Uri)GetValue(PickerPageUriProperty); } + set { SetValue(PickerPageUriProperty, value); } + } + + /// + /// Identifies the PickerPageUri DependencyProperty. + /// + public static readonly DependencyProperty PickerPageUriProperty = DependencyProperty.Register( + "PickerPageUri", typeof(Uri), typeof(ListPicker), null); + + /// + /// Gets or sets how the list picker expands when tapped. + /// This property has an effect only when SelectionMode is Single. + /// When SelectionMode is Multiple, the ExpansionMode will be treated as FullScreenOnly. + /// ExpansionAllowed will only expand when the number of items is less than or equalt to ItemCountThreshold + /// Single by default. + /// + public ExpansionMode ExpansionMode + { + get { return (ExpansionMode)GetValue(ExpansionModeProperty); } + set { SetValue(ExpansionModeProperty, value); } + } + + /// + /// Identifies the ExpansionMode DependencyProperty. + /// + public static readonly DependencyProperty ExpansionModeProperty = DependencyProperty.Register( + "ExpansionMode", + typeof(ExpansionMode), + typeof(ListPicker), + new PropertyMetadata(ExpansionMode.ExpansionAllowed, null) + ); + + /// + /// Gets or sets the SelectionMode. Extended is treated as Multiple. + /// Single by default. + /// + public SelectionMode SelectionMode + { + get { return (SelectionMode)GetValue(SelectionModeProperty); } + set { SetValue(SelectionModeProperty, value); } + } + + /// + /// Identifies the SelectionMode DependencyProperty. + /// + public static readonly DependencyProperty SelectionModeProperty = DependencyProperty.Register( + "SelectionMode", + typeof(SelectionMode), + typeof(ListPicker), + new PropertyMetadata(SelectionMode.Single, OnSelectionModeChanged) + ); + + private static void OnSelectionModeChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((ListPicker)o).OnSelectionModeChanged((SelectionMode)e.NewValue); + } + + private void OnSelectionModeChanged(SelectionMode newValue) + { + // Show/Hide the multiple selection mode summary text block or the items presenter depending on which selection mode chosen + if (newValue == SelectionMode.Multiple || newValue == SelectionMode.Extended) + { + if (_multipleSelectionModeSummary != null && _itemsPresenterHostPart != null) + { + _multipleSelectionModeSummary.Visibility = Visibility.Visible; + _itemsPresenterHostPart.Visibility = Visibility.Collapsed; + } + + } + else + { + if (_multipleSelectionModeSummary != null && _itemsPresenterHostPart != null) + { + _multipleSelectionModeSummary.Visibility = Visibility.Collapsed; + _itemsPresenterHostPart.Visibility = Visibility.Visible; + } + } + } + + /// + /// Gets the selected items. + /// + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2227:CollectionPropertiesShouldBeReadOnly", Justification="Want to allow this to be bound to.")] + public IList SelectedItems + { + get { return (IList)GetValue(SelectedItemsProperty); } + set { SetValue(SelectedItemsProperty, value); } + } + + /// + /// Identifies the SelectedItems DependencyProperty. + /// + public static readonly DependencyProperty SelectedItemsProperty = DependencyProperty.Register( + "SelectedItems", + typeof(IList), + typeof(ListPicker), + new PropertyMetadata(OnSelectedItemsChanged) + ); + + private static void OnSelectedItemsChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + ((ListPicker)o).OnSelectedItemsChanged((IList)e.OldValue, (IList)e.NewValue); + } + + private void OnSelectedItemsChanged(IList oldValue, IList newValue) + { + UpdateSummary(newValue); + + // Fire SelectionChanged event + var handler = SelectionChanged; + if (null != handler) + { + IList removedItems = new List(); + if (null != oldValue) + { + foreach (object o in oldValue) + { + if (null == newValue || !newValue.Contains(o)) + { + removedItems.Add(o); + } + } + } + IList addedItems = new List(); + if (null != newValue) + { + foreach (object o in newValue) + { + if (null == oldValue || !oldValue.Contains(o)) + { + addedItems.Add(o); + } + } + } + + handler(this, new SelectionChangedEventArgs(removedItems, addedItems)); + } + } + + /// + /// 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; + + this.RegisterNotification("IsEnabled", OnIsEnabledChanged); + + Loaded += OnLoaded; + Unloaded += OnUnloaded; + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + UpdateVisualStates (true); + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + // Unhook any remaining event handlers + if (null != _frame) + { + _frame.ManipulationStarted -= OnFrameManipulationStarted; + _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 -= OnItemsPresenterHostParentSizeChanged; + } + _storyboard.Stop(); + + base.OnApplyTemplate(); + + // Hook up to new elements + _itemsPresenterPart = GetTemplateChild(ItemsPresenterPartName) as ItemsPresenter; + _itemsPresenterTranslateTransformPart = GetTemplateChild(ItemsPresenterTranslateTransformPartName) as TranslateTransform; + _itemsPresenterHostPart = GetTemplateChild(ItemsPresenterHostPartName) as Canvas; + _itemsPresenterHostParent = (null != _itemsPresenterHostPart) ? _itemsPresenterHostPart.Parent as FrameworkElement : null; + _multipleSelectionModeSummary = GetTemplateChild(MultipleSelectionModeSummaryPartName) as TextBlock; + _border = GetTemplateChild(BorderPartName) as Border; + + if (null != _itemsPresenterHostParent) + { + _itemsPresenterHostParent.SizeChanged += OnItemsPresenterHostParentSizeChanged; + } + 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); + } + } + + SetBinding(ShadowItemTemplateProperty, new Binding("ItemTemplate") { Source = this }); + + + // Commit deferred SelectedIndex (if any) + if (-1 != _deferredSelectedIndex) + { + SelectedIndex = _deferredSelectedIndex; + _deferredSelectedIndex = -1; + } + if (null != _deferredSelectedItem) + { + SelectedItem = _deferredSelectedItem; + _deferredSelectedItem = null; + } + + OnSelectionModeChanged(SelectionMode); + OnSelectedItemsChanged(SelectedItems, SelectedItems); + } + + /// + /// 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.Tap += OnContainerTap; + container.SizeChanged += OnListPickerItemSizeChanged; + + // 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.Tap -= OnContainerTap; + container.SizeChanged -= OnListPickerItemSizeChanged; + } + + /// + /// 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)); + } + + private bool IsValidManipulation(object OriginalSource, Point p) + { + DependencyObject element = OriginalSource as DependencyObject; + + while (null != element) + { + if (_itemsPresenterHostPart == element || _multipleSelectionModeSummary == element || _border == element) + { + double Padding = 11.0; + return (p.X > 0 && p.Y > 0 - Padding && p.X < _border.RenderSize.Width && p.Y < _border.RenderSize.Height + Padding); + } + + element = VisualTreeHelper.GetParent(element); + } + return false; + } + + /// + /// Handles the tap event. + /// + /// Event args + protected override void OnTap(System.Windows.Input.GestureEventArgs e) + { + if (null == e) + { + throw new ArgumentNullException("e"); + } + + if (ListPickerMode == ListPickerMode.Normal) + { + if (!IsEnabled) + { + e.Handled = true; + return; + } + + Point p = e.GetPosition((UIElement)e.OriginalSource); + if (IsValidManipulation(e.OriginalSource, p)) + { + if (Open()) + { + e.Handled = true; + } + } + } + } + /// + /// Called when the ManipulationStarted event occurs. + /// + /// Event data for the event. + protected override void OnManipulationStarted(ManipulationStartedEventArgs e) + { + if (null == e) + { + throw new ArgumentNullException("e"); + } + + base.OnManipulationStarted(e); + + if (ListPickerMode == ListPickerMode.Normal) + { + if (!IsEnabled) + { + e.Complete(); + return; + } + + Point p = e.ManipulationOrigin; + + if (e.OriginalSource != e.ManipulationContainer) + { + p = e.ManipulationContainer.TransformToVisual((UIElement)e.OriginalSource).Transform(p); + } + + if (IsValidManipulation(e.OriginalSource, p)) + { + IsHighlighted = true; + } + } + } + + /// + /// Called when the ManipulationDelta event occurs. + /// + /// Event data for the event. + protected override void OnManipulationDelta(ManipulationDeltaEventArgs e) + { + if (null == e) + { + throw new ArgumentNullException("e"); + } + + base.OnManipulationDelta(e); + + if (ListPickerMode == ListPickerMode.Normal) + { + if (!IsEnabled) + { + e.Complete(); + return; + } + + Point p = e.ManipulationOrigin; + + if (e.OriginalSource != e.ManipulationContainer) + { + p = e.ManipulationContainer.TransformToVisual((UIElement)e.OriginalSource).Transform(p); + } + + if (!IsValidManipulation(e.OriginalSource, p)) + { + IsHighlighted = false; + e.Complete(); + } + } + } + + /// + /// 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); + + if (!IsEnabled) + { + return; + } + + if (ListPickerMode == ListPickerMode.Normal) + { + // Style box to look unselected + IsHighlighted = false; + } + } + + /// + /// Opens the picker for selection either into Expanded or Full mode depending on the picker's state. + /// + /// Whether the picker was succesfully opened. + public bool Open() + { + if (SelectionMode == SelectionMode.Single) + { + // On interaction, switch to Expanded/Full mode + if ((ListPickerMode.Normal == ListPickerMode)) + { + if (ExpansionMode == ExpansionMode.ExpansionAllowed && Items.Count <= ItemCountThreshold) + { + ListPickerMode = ListPickerMode.Expanded; + } + else + { + ListPickerMode = ListPickerMode.Full; + } + return true; + } + } + else + { + ListPickerMode = ListPickerMode.Full; + return true; + } + + return false; + } + + private void OnItemsPresenterHostParentSizeChanged(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 OnListPickerItemSizeChanged(object sender, SizeChangedEventArgs e) + { + // Update size accordingly + ContentControl container = (ContentControl)sender; + if (object.Equals(ItemContainerGenerator.ItemFromContainer(container), SelectedItem)) + { + SizeForAppropriateView(false); + } + } + + private void OnPageBackKeyPress(object sender, CancelEventArgs e) + { + // Revert to Normal mode + ListPickerMode = ListPickerMode.Normal; + e.Cancel = true; + } + + 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 - (NormalModeOffset * 2)); + } + if (null != _itemsPresenterTranslateTransformPart) + { + if (!animate) + { + _itemsPresenterTranslateTransformPart.Y = -NormalModeOffset; + } + _translateAnimation.To = container.Margin.Top - LayoutInformation.GetLayoutSlot(container).Top - NormalModeOffset; + _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 OnFrameManipulationStarted(object sender, ManipulationStartedEventArgs 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 OnContainerTap(object sender, System.Windows.Input.GestureEventArgs 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 UpdateVisualStates(bool useTransitions) + { + if (!IsEnabled) + { + VisualStateManager.GoToState(this, PickerStatesDisabledStateName, useTransitions); + } + else if (IsHighlighted) + { + VisualStateManager.GoToState(this, PickerStatesHighlightedStateName, useTransitions); + } + else + { + VisualStateManager.GoToState(this, PickerStatesNormalStateName, useTransitions); + } + } + + /// + /// Updates the summary of the selected items to be displayed in the ListPicker. + /// + /// The list selected items + [SuppressMessage("Microsoft.Globalization", "CA1303:Do not pass literals as localized parameters", MessageId = "System.Windows.Controls.TextBlock.set_Text(System.String)", Justification = "By design.")] + private void UpdateSummary(IList newValue) + { + const string space = " "; + string summary = null; + + if (null != SummaryForSelectedItemsDelegate) + { + // Ask the delegate to sumarize the selected items. + summary = SummaryForSelectedItemsDelegate(newValue); + } + + if (summary == null) + { + // No summary was provided, so by default, show only the first item in the selection list. + if (null == newValue || newValue.Count == 0) + { + // In the case that there were no selected items, show the empty string. + summary = space; + } + else + { + summary = newValue[0].ToString(); + } + } + + // The display does not size correctly if the empty string is used. + if (String.IsNullOrEmpty(summary)) + { + summary = space; + } + + if (null != _multipleSelectionModeSummary) + { + _multipleSelectionModeSummary.Text = summary; + } + } + + private void OpenPickerPage() + { + if (null == PickerPageUri) + { + throw new ArgumentException("PickerPageUri"); + } + + if (null == _frame) + { + // Hook up to necessary events and navigate + _frame = Application.Current.RootVisual as PhoneApplicationFrame; + if (null != _frame) + { + _frameContentWhenOpened = _frame.Content; + + // Save and clear host page transitions for the upcoming "popup" navigation + UIElement frameContentWhenOpenedAsUIElement = _frameContentWhenOpened as UIElement; + + if (null != frameContentWhenOpenedAsUIElement) + { + _savedNavigationInTransition = TransitionService.GetNavigationInTransition(frameContentWhenOpenedAsUIElement); + TransitionService.SetNavigationInTransition(frameContentWhenOpenedAsUIElement, null); + _savedNavigationOutTransition = TransitionService.GetNavigationOutTransition(frameContentWhenOpenedAsUIElement); + TransitionService.SetNavigationOutTransition(frameContentWhenOpenedAsUIElement, null); + + } + + _frame.Navigated += OnFrameNavigated; + _frame.NavigationStopped += OnFrameNavigationStoppedOrFailed; + _frame.NavigationFailed += OnFrameNavigationStoppedOrFailed; + + _hasPickerPageOpen = true; + + _frame.Navigate(PickerPageUri); + } + } + } + + private void ClosePickerPage() + { + // Unhook from events + if (null != _frame) + { + _frame.Navigated -= OnFrameNavigated; + _frame.NavigationStopped -= OnFrameNavigationStoppedOrFailed; + _frame.NavigationFailed -= OnFrameNavigationStoppedOrFailed; + + // Restore host page transitions for the completed "popup" navigation + UIElement frameContentWhenOpenedAsUIElement = _frameContentWhenOpened as UIElement; + + if (null != frameContentWhenOpenedAsUIElement) + { + TransitionService.SetNavigationInTransition(frameContentWhenOpenedAsUIElement, _savedNavigationInTransition); + _savedNavigationInTransition = null; + TransitionService.SetNavigationOutTransition(frameContentWhenOpenedAsUIElement, _savedNavigationOutTransition); + _savedNavigationOutTransition = null; + } + + _frame = null; + _frameContentWhenOpened = null; + } + + // Commit the value if available + if (null != _listPickerPage) + { + if (SelectionMode == SelectionMode.Single && null != _listPickerPage.SelectedItem) + { + SelectedItem = _listPickerPage.SelectedItem; + } + else if ((SelectionMode == SelectionMode.Multiple || SelectionMode == SelectionMode.Extended) && null != _listPickerPage.SelectedItems) + { + SelectedItems = _listPickerPage.SelectedItems; + } + _listPickerPage = null; + } + } + + private void OnFrameNavigated(object sender, NavigationEventArgs e) + { + if (e.Content == _frameContentWhenOpened) + { + // Navigation to original page; close the picker page + ListPickerMode = ListPickerMode.Normal; + } + else if (null == _listPickerPage && _hasPickerPageOpen) + { + _hasPickerPageOpen = false; + _listPickerPage = e.Content as ListPickerPage; + if (null != _listPickerPage) + { + // Set up the list picker page with the necesarry fields. + if (null != FullModeHeader) + { + _listPickerPage.HeaderText = (string)FullModeHeader; + } + else + { + _listPickerPage.HeaderText = (string) Header; + } + + _listPickerPage.FullModeItemTemplate = FullModeItemTemplate; + + _listPickerPage.Items.Clear(); + if (null != Items) + { + foreach (var element in Items) + { + _listPickerPage.Items.Add(element); + } + } + + _listPickerPage.SelectionMode = SelectionMode; + + if (SelectionMode == SelectionMode.Single) + { + _listPickerPage.SelectedItem = SelectedItem; + } + else + { + _listPickerPage.SelectedItems.Clear(); + if (null != SelectedItems) + { + foreach (var element in SelectedItems) + { + _listPickerPage.SelectedItems.Add(element); + } + } + } + } + } + } + + private void OnFrameNavigationStoppedOrFailed(object sender, EventArgs e) + { + // Abort + ListPickerMode = ListPickerMode.Normal; + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/ListPicker/ListPickerItem.cs b/Microsoft.Phone.Controls.Toolkit/ListPicker/ListPickerItem.cs new file mode 100644 index 0000000..b75f2d1 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/ListPicker/ListPickerItem.cs @@ -0,0 +1,51 @@ +// (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.Windows; +using System.Windows.Controls; + +namespace Microsoft.Phone.Controls +{ + /// + /// Class that implements a container for the ListPicker control. + /// + /// Preview + [TemplateVisualState(GroupName = SelectionStatesGroupName, Name = SelectionStatesUnselectedStateName)] + [TemplateVisualState(GroupName = SelectionStatesGroupName, Name = SelectionStatesSelectedStateName)] + public class ListPickerItem : ContentControl + { + private const string SelectionStatesGroupName = "SelectionStates"; + private const string SelectionStatesUnselectedStateName = "Unselected"; + private const string SelectionStatesSelectedStateName = "Selected"; + + /// + /// Initializes a new instance of the ListPickerItem class. + /// + public ListPickerItem() + { + DefaultStyleKey = typeof(ListPickerItem); + } + + /// + /// Builds the visual tree for the control when a new template is applied. + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + VisualStateManager.GoToState(this, IsSelected ? SelectionStatesSelectedStateName : SelectionStatesUnselectedStateName, false); + } + + internal bool IsSelected + { + get { return _isSelected; } + set + { + _isSelected = value; + VisualStateManager.GoToState(this, _isSelected ? SelectionStatesSelectedStateName : SelectionStatesUnselectedStateName, true); + } + } + private bool _isSelected; + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/ListPicker/ListPickerMode.cs b/Microsoft.Phone.Controls.Toolkit/ListPicker/ListPickerMode.cs new file mode 100644 index 0000000..23b94af --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/ListPicker/ListPickerMode.cs @@ -0,0 +1,28 @@ +// (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. + +namespace Microsoft.Phone.Controls +{ + /// + /// An enumeration defining the supported ListPicker modes. + /// + public enum ListPickerMode + { + /// + /// Normal mode; only the selected item is visible on the original page. + /// + Normal, + + /// + /// Expanded mode; all items are visible on the original page. + /// + Expanded, + + /// + /// Full mode; all items are visible in a separate Popup. + /// + Full, + }; +} diff --git a/Microsoft.Phone.Controls.Toolkit/ListPicker/ListPickerPage.xaml b/Microsoft.Phone.Controls.Toolkit/ListPicker/ListPickerPage.xaml new file mode 100644 index 0000000..e1eee98 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/ListPicker/ListPickerPage.xaml @@ -0,0 +1,126 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Microsoft.Phone.Controls.Toolkit/ListPicker/ListPickerPage.xaml.cs b/Microsoft.Phone.Controls.Toolkit/ListPicker/ListPickerPage.xaml.cs new file mode 100644 index 0000000..6891469 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/ListPicker/ListPickerPage.xaml.cs @@ -0,0 +1,461 @@ +// (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(UpdateOutOfViewItems); + + 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 = 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(); + } + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.Designer.cs new file mode 100644 index 0000000..cfdc8a9 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.Designer.cs @@ -0,0 +1,414 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.235 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Phone.Controls.LocalizedResources { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + public class ControlResources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal ControlResources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Phone.Controls.LocalizedResources.ControlResources", typeof(ControlResources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + public static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to about a minute ago. + /// + public static string AboutAMinuteAgo { + get { + return ResourceManager.GetString("AboutAMinuteAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to about a month ago. + /// + public static string AboutAMonthAgo { + get { + return ResourceManager.GetString("AboutAMonthAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to about an hour ago. + /// + public static string AboutAnHourAgo { + get { + return ResourceManager.GetString("AboutAnHourAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to about a week ago. + /// + public static string AboutAWeekAgo { + get { + return ResourceManager.GetString("AboutAWeekAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to ALARM REPEATS ON. + /// + public static string AlarmRepeatsOn { + get { + return ResourceManager.GetString("AlarmRepeatsOn", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to CHOOSE DATE. + /// + public static string DatePickerTitle { + get { + return ResourceManager.GetString("DatePickerTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to cancel. + /// + public static string DateTimePickerCancelText { + get { + return ResourceManager.GetString("DateTimePickerCancelText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to done. + /// + public static string DateTimePickerDoneText { + get { + return ResourceManager.GetString("DateTimePickerDoneText", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Friday. + /// + public static string Friday { + get { + return ResourceManager.GetString("Friday", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to last {0}. + /// + public static string LastDayOfWeek { + get { + return ResourceManager.GetString("LastDayOfWeek", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Monday. + /// + public static string Monday { + get { + return ResourceManager.GetString("Monday", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to on {0}. + /// + public static string OnDayOfWeek_Other { + get { + return ResourceManager.GetString("OnDayOfWeek_Other", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to on {0}. + /// + public static string OnDayOfWeek_Tuesday { + get { + return ResourceManager.GetString("OnDayOfWeek_Tuesday", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to over a year ago. + /// + public static string OverAYearAgo { + get { + return ResourceManager.GetString("OverAYearAgo", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to every day. + /// + public static string RepeatsEveryDay { + get { + return ResourceManager.GetString("RepeatsEveryDay", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to only once. + /// + public static string RepeatsOnlyOnce { + get { + return ResourceManager.GetString("RepeatsOnlyOnce", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to weekdays. + /// + public static string RepeatsOnWeekdays { + get { + return ResourceManager.GetString("RepeatsOnWeekdays", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to weekends. + /// + public static string RepeatsOnWeekends { + get { + return ResourceManager.GetString("RepeatsOnWeekends", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Saturday. + /// + public static string Saturday { + get { + return ResourceManager.GetString("Saturday", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Sunday. + /// + public static string Sunday { + get { + return ResourceManager.GetString("Sunday", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Thursday. + /// + public static string Thursday { + get { + return ResourceManager.GetString("Thursday", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to CHOOSE TIME. + /// + public static string TimePickerTitle { + get { + return ResourceManager.GetString("TimePickerTitle", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Tuesday. + /// + public static string Tuesday { + get { + return ResourceManager.GetString("Tuesday", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Wednesday. + /// + public static string Wednesday { + get { + return ResourceManager.GetString("Wednesday", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} hours ago . + /// + public static string XHoursAgo_2To4 { + get { + return ResourceManager.GetString("XHoursAgo_2To4", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} hours ago . + /// + public static string XHoursAgo_EndsIn1Not11 { + get { + return ResourceManager.GetString("XHoursAgo_EndsIn1Not11", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} hours ago . + /// + public static string XHoursAgo_EndsIn2To4Not12To14 { + get { + return ResourceManager.GetString("XHoursAgo_EndsIn2To4Not12To14", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} hours ago . + /// + public static string XHoursAgo_Other { + get { + return ResourceManager.GetString("XHoursAgo_Other", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} minutes ago. + /// + public static string XMinutesAgo_2To4 { + get { + return ResourceManager.GetString("XMinutesAgo_2To4", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} minutes ago. + /// + public static string XMinutesAgo_EndsIn1Not11 { + get { + return ResourceManager.GetString("XMinutesAgo_EndsIn1Not11", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} minutes ago. + /// + public static string XMinutesAgo_EndsIn2To4Not12To14 { + get { + return ResourceManager.GetString("XMinutesAgo_EndsIn2To4Not12To14", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} minutes ago . + /// + public static string XMinutesAgo_Other { + get { + return ResourceManager.GetString("XMinutesAgo_Other", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} months ago. + /// + public static string XMonthsAgo_2To4 { + get { + return ResourceManager.GetString("XMonthsAgo_2To4", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} months ago . + /// + public static string XMonthsAgo_5To12 { + get { + return ResourceManager.GetString("XMonthsAgo_5To12", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} seconds ago. + /// + public static string XSecondsAgo_2To4 { + get { + return ResourceManager.GetString("XSecondsAgo_2To4", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} seconds ago. + /// + public static string XSecondsAgo_EndsIn1Not11 { + get { + return ResourceManager.GetString("XSecondsAgo_EndsIn1Not11", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} seconds ago. + /// + public static string XSecondsAgo_EndsIn2To4Not12To14 { + get { + return ResourceManager.GetString("XSecondsAgo_EndsIn2To4Not12To14", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} seconds ago. + /// + public static string XSecondsAgo_Other { + get { + return ResourceManager.GetString("XSecondsAgo_Other", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to {0} weeks ago. + /// + public static string XWeeksAgo_2To4 { + get { + return ResourceManager.GetString("XWeeksAgo_2To4", resourceCulture); + } + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.cs.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.cs.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.cs.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.cs.resx new file mode 100644 index 0000000..96f1bfd --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.cs.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + asi před minutou + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + asi před měsícem + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + asi před hodinou + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + asi před týdnem + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + OPAKOVÁNÍ BUDÍKU + Default title for recurring day ListPicker. + + + ZVOLTE DATUM + Default title for DatePicker. + + + storno + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + hotovo + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + pátek + Default text for "Friday" as used by the RelativeTimeConverter. + + + předchozí {0} + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + pondělí + Default text for "Monday" as used by the RelativeTimeConverter. + + + před více než rokem + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + každý den + Default text for the recurring day ListPicker when an alarm repeats daily. + + + pouze jednou + Default text for the recurring day ListPicker when an alarm is not recurring. + + + pracovní dny + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + víkendy + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + sobota + Default text for "Saturday" as used by the RelativeTimeConverter. + + + neděle + Default text for "Sunday" as used by the RelativeTimeConverter. + + + čtvrtek + Default text for "Thursday" as used by the RelativeTimeConverter. + + + ZVOLTE ČAS + Default title for TimePicker. + + + úterý + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + středa + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + před {0} hodinami + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + před {0} hodinami + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + před {0} hodinami + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + před {0} hodinami + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + před {0} minutami + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + před {0} minutami + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + před {0} minutami + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + před {0} minutami + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + před {0} měsíci + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + před {0} měsíci + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + před {0} sekundami + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + před {0} sekundami + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + před {0} sekundami + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + před {0} sekundami + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + před {0} týdny + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.da.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.da.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.da.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.da.resx new file mode 100644 index 0000000..14b8afb --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.da.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + for et minut siden + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + for ca. en måned siden + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + for ca. en time siden + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + for ca. en uge siden + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + ALARM GENTAGES + Default title for recurring day ListPicker. + + + VÆLG EN DATO + Default title for DatePicker. + + + annuller + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + udført + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + fredag + Default text for "Friday" as used by the RelativeTimeConverter. + + + sidste {0} + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + mandag + Default text for "Monday" as used by the RelativeTimeConverter. + + + for mere end et år siden + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + hver dag + Default text for the recurring day ListPicker when an alarm repeats daily. + + + kun én gang + Default text for the recurring day ListPicker when an alarm is not recurring. + + + hverdage + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + weekender + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + lørdag + Default text for "Saturday" as used by the RelativeTimeConverter. + + + søndag + Default text for "Sunday" as used by the RelativeTimeConverter. + + + torsdag + Default text for "Thursday" as used by the RelativeTimeConverter. + + + VÆLG ET KLOKKESLÆT + Default title for TimePicker. + + + tirsdag + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + onsdag + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + for {0} timer siden + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + for {0} timer siden + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + for {0} timer siden + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + for {0} timer siden + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + for {0} minutter siden + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + for {0} minutter siden + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + for {0} minutter siden + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + for {0} minutter siden + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + for {0} måneder siden + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + for {0} måneder siden + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + for {0} sekunder siden + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + for {0} sekunder siden + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + for {0} sekunder siden + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + for {0} sekunder siden + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + for {0} uger siden + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.de-DE.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.de-DE.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.de-DE.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.de-DE.resx new file mode 100644 index 0000000..b826228 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.de-DE.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + vor etwa einer Minute + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + vor etwa einem Monat + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + vor etwa einer Stunde + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + vor etwa einer Woche + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + WECKWIEDERHOLUNG + Default title for recurring day ListPicker. + + + Datum wählen + Default title for DatePicker. + + + Abbrechen + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + Fertig + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + Freitag + Default text for "Friday" as used by the RelativeTimeConverter. + + + letzten {0} + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + Montag + Default text for "Monday" as used by the RelativeTimeConverter. + + + vor über einem Jahr + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + Täglich + Default text for the recurring day ListPicker when an alarm repeats daily. + + + Nur einmal + Default text for the recurring day ListPicker when an alarm is not recurring. + + + Mo-Fr + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + An Wochenenden + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + Samstag + Default text for "Saturday" as used by the RelativeTimeConverter. + + + Sonntag + Default text for "Sunday" as used by the RelativeTimeConverter. + + + Donnerstag + Default text for "Thursday" as used by the RelativeTimeConverter. + + + Uhrzeit wählen + Default title for TimePicker. + + + Dienstag + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + Mittwoch + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + vor {0} Stunden + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + vor {0} Stunden + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + vor {0} Stunden + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + vor {0} Stunden + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + vor {0} Minuten + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + vor {0} Minuten + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + vor {0} Minuten + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + vor {0} Minuten + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + Vor {0} Monaten + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + Vor {0} Monaten + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + vor {0} Sekunden + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + vor {0} Sekunden + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + vor {0} Sekunden + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + vor {0} Sekunden + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + Vor {0} Wochen + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + am {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + am {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.el.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.el.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.el.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.el.resx new file mode 100644 index 0000000..44bd158 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.el.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + πριν από ένα λεπτό + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + πριν από ένα μήνα + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + πριν από μία ώρα + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + πριν από μία εβδομάδα + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + Επαναλήψεις αφύπνισης κάθε + Default title for recurring day ListPicker. + + + Επιλογή ημερομηνίας + Default title for DatePicker. + + + άκυρο + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + τέλος + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + Παρασκευή + Default text for "Friday" as used by the RelativeTimeConverter. + + + τελευταία {0} + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + Δευτέρα + Default text for "Monday" as used by the RelativeTimeConverter. + + + πριν από ένα έτος + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + καθημερινά + Default text for the recurring day ListPicker when an alarm repeats daily. + + + μόνο μία φορά + Default text for the recurring day ListPicker when an alarm is not recurring. + + + εργάσιμες ημέρες + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + Σαββατοκύριακα + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + Σάββατο + Default text for "Saturday" as used by the RelativeTimeConverter. + + + Κυριακή + Default text for "Sunday" as used by the RelativeTimeConverter. + + + Πέμπτη + Default text for "Thursday" as used by the RelativeTimeConverter. + + + Επιλογή ώρας + Default title for TimePicker. + + + Τρίτη + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + Τετάρτη + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + πριν από {0} ώρες + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + πριν από {0} ώρες + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + πριν από {0} ώρες + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + πριν από {0} ώρες + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + πριν από {0} λεπτά + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + πριν από {0} λεπτά + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + πριν από {0} λεπτά + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + πριν από {0} λεπτά + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} μήνες πριν + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} μήνες πριν + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + πριν από {0} δευτερόλεπτα + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + πριν από {0} δευτερόλεπτα + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + πριν από {0} δευτερόλεπτα + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + πριν από {0} δευτερόλεπτα + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} εβδομάδες πριν + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + - {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + - {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.en-GB.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.en-GB.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.en-GB.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.en-GB.resx new file mode 100644 index 0000000..2da76a9 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.en-GB.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + about a minute ago + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + about a month ago + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + about an hour ago + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + about a week ago + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + ALARM REPEATS ON + Default title for recurring day ListPicker. + + + CHOOSE DATE + Default title for DatePicker. + + + cancel + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + done + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + Friday + Default text for "Friday" as used by the RelativeTimeConverter. + + + last {0} + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + Monday + Default text for "Monday" as used by the RelativeTimeConverter. + + + over a year ago + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + every day + Default text for the recurring day ListPicker when an alarm repeats daily. + + + only once + Default text for the recurring day ListPicker when an alarm is not recurring. + + + weekdays + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + weekends + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + Saturday + Default text for "Saturday" as used by the RelativeTimeConverter. + + + Sunday + Default text for "Sunday" as used by the RelativeTimeConverter. + + + Thursday + Default text for "Thursday" as used by the RelativeTimeConverter. + + + CHOOSE TIME + Default title for TimePicker. + + + Tuesday + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + Wednesday + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + {0} hours ago + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} hours ago + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} hours ago + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} hours ago + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} minutes ago + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} minutes ago + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} minutes ago + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} minutes ago + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} months ago + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} months ago + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + {0} seconds ago + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} seconds ago + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} seconds ago + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} seconds ago + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} weeks ago + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + on {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + on {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.es-ES.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.es-ES.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.es-ES.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.es-ES.resx new file mode 100644 index 0000000..23330fa --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.es-ES.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + hace un minuto aproximadamente + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + hace un mes aproximadamente + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + hace una hora aproximadamente + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + hace una semana aproximadamente + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + REPETIR ALARMA CADA + Default title for recurring day ListPicker. + + + ELIGE UNA FECHA + Default title for DatePicker. + + + cancelar + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + listo + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + viernes + Default text for "Friday" as used by the RelativeTimeConverter. + + + el {0} pasado + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + lunes + Default text for "Monday" as used by the RelativeTimeConverter. + + + hace más de un año + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + todos los días + Default text for the recurring day ListPicker when an alarm repeats daily. + + + solo una vez + Default text for the recurring day ListPicker when an alarm is not recurring. + + + días laborables + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + fines de semana + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + sábado + Default text for "Saturday" as used by the RelativeTimeConverter. + + + domingo + Default text for "Sunday" as used by the RelativeTimeConverter. + + + jueves + Default text for "Thursday" as used by the RelativeTimeConverter. + + + ELIGE UNA HORA + Default title for TimePicker. + + + martes + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + miércoles + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + hace {0} horas + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + hace {0} horas + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + hace {0} horas + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + hace {0} horas + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + hace {0} minutos + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + hace {0} minutos + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + hace {0} minutos + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + hace {0} minutos + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + hace {0} meses + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + hace {0} meses + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + hace {0} segundos + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + hace {0} segundos + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + hace {0} segundos + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + hace {0} segundos + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + hace {0} semanas + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + el {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + el {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.fi.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.fi.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.fi.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.fi.resx new file mode 100644 index 0000000..8f5ec65 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.fi.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + noin minuutti sitten + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + noin kuukausi sitten + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + noin tunti sitten + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + noin viikko sitten + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + HÄLYTYKSEN TOISTO KÄYTÖSSÄ + Default title for recurring day ListPicker. + + + VALITSE PÄIVÄMÄÄRÄ + Default title for DatePicker. + + + peruuta + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + valmis + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + perjantai + Default text for "Friday" as used by the RelativeTimeConverter. + + + viime {0} + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + maanantai + Default text for "Monday" as used by the RelativeTimeConverter. + + + yli vuosi sitten + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + joka päivä + Default text for the recurring day ListPicker when an alarm repeats daily. + + + vain kerran + Default text for the recurring day ListPicker when an alarm is not recurring. + + + arkipäivisin + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + viikonloppuisin + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + lauantai + Default text for "Saturday" as used by the RelativeTimeConverter. + + + sunnuntai + Default text for "Sunday" as used by the RelativeTimeConverter. + + + torstai + Default text for "Thursday" as used by the RelativeTimeConverter. + + + VALITSE KELLONAIKA + Default title for TimePicker. + + + tiistai + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + keskiviikko + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + {0} tuntia sitten + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} tuntia sitten + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} tuntia sitten + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} tuntia sitten + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} minuuttia sitten + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} minuuttia sitten + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} minuuttia sitten + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} minuuttia sitten + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} kuukautta sitten + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} kuukautta sitten + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + {0} sekuntia sitten + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} sekuntia sitten + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} sekuntia sitten + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} sekuntia sitten + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} viikkoa sitten + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.fr-FR.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.fr-FR.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.fr-FR.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.fr-FR.resx new file mode 100644 index 0000000..f7debbb --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.fr-FR.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + il y a environ une minute + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + il y a environ un mois + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + il y a environ une heure + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + il y a environ une semaine + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + ALARME RÉPÉTÉE LE + Default title for recurring day ListPicker. + + + CHOISIR LA DATE + Default title for DatePicker. + + + annuler + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + terminé + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + vendredi + Default text for "Friday" as used by the RelativeTimeConverter. + + + {0} dernier + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + lundi + Default text for "Monday" as used by the RelativeTimeConverter. + + + il y a plus d'un an + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + tous les jours + Default text for the recurring day ListPicker when an alarm repeats daily. + + + une seule fois + Default text for the recurring day ListPicker when an alarm is not recurring. + + + jours ouvrables + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + week-ends + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + samedi + Default text for "Saturday" as used by the RelativeTimeConverter. + + + dimanche + Default text for "Sunday" as used by the RelativeTimeConverter. + + + jeudi + Default text for "Thursday" as used by the RelativeTimeConverter. + + + CHOISIR L'HEURE + Default title for TimePicker. + + + mardi + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + mercredi + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + il y a {0} heures + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + il y a {0} heures + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + il y a {0} heures + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + il y a {0} heures + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + il y a {0} minutes + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + il y a {0} minutes + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + il y a {0} minutes + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + il y a {0} minutes + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + il y a {0} mois + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + il y a {0} mois + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + il y a {0} secondes + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + il y a {0} secondes + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + il y a {0} secondes + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + il y a {0} secondes + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + il y a {0} semaines + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.hu.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.hu.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.hu.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.hu.resx new file mode 100644 index 0000000..529e979 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.hu.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + körülbelül egy perce + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + körülbelül egy hónapja + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + körülbelül egy órája + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + körülbelül egy hete + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + A RIASZTÁS ISMÉTLŐDIK: + Default title for recurring day ListPicker. + + + DÁTUM MEGADÁSA + Default title for DatePicker. + + + mégse + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + kész + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + péntek + Default text for "Friday" as used by the RelativeTimeConverter. + + + múlt {0} + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + hétfő + Default text for "Monday" as used by the RelativeTimeConverter. + + + egy évnél régebben + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + mindennap + Default text for the recurring day ListPicker when an alarm repeats daily. + + + csak egyszer + Default text for the recurring day ListPicker when an alarm is not recurring. + + + hétköznapokon + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + hétvégéken + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + szombat + Default text for "Saturday" as used by the RelativeTimeConverter. + + + vasárnap + Default text for "Sunday" as used by the RelativeTimeConverter. + + + csütörtök + Default text for "Thursday" as used by the RelativeTimeConverter. + + + IDŐ MEGADÁSA + Default title for TimePicker. + + + kedd + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + szerda + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + {0} órája + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} órája + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} órája + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} órája + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} perce + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} perce + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} perce + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} perce + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} hónapja + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} hónapja + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + {0} másodperce + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} másodperce + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} másodperce + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} másodperce + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} hete + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.it-IT.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.it-IT.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.it-IT.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.it-IT.resx new file mode 100644 index 0000000..1e7cacf --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.it-IT.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + circa un minuto fa + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + circa un mese fa + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + circa un'ora fa + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + circa una settimana fa + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + RIPETI LA SVEGLIA + Default title for recurring day ListPicker. + + + SCEGLI LA DATA + Default title for DatePicker. + + + annulla + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + fatto + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + venerdì + Default text for "Friday" as used by the RelativeTimeConverter. + + + {0} scorso + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + lunedì + Default text for "Monday" as used by the RelativeTimeConverter. + + + più di un anno fa + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + ogni giorno + Default text for the recurring day ListPicker when an alarm repeats daily. + + + una volta + Default text for the recurring day ListPicker when an alarm is not recurring. + + + giorni feriali + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + fine settimana + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + sabato + Default text for "Saturday" as used by the RelativeTimeConverter. + + + domenica + Default text for "Sunday" as used by the RelativeTimeConverter. + + + giovedì + Default text for "Thursday" as used by the RelativeTimeConverter. + + + SCEGLI L'ORA + Default title for TimePicker. + + + martedì + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + mercoledì + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + {0} ore fa + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} ore fa + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} ore fa + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} ore fa + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} minuti fa + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} minuti fa + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} minuti fa + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} minuti fa + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} mesi fa + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} mesi fa + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + {0} secondi fa + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} secondi fa + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} secondi fa + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} secondi fa + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} settimane fa + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ja.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ja.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ja.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ja.resx new file mode 100644 index 0000000..8e863a9 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ja.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 約1分前 + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + 約1か月前 + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + 約1時間前 + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + 約1週間前 + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + アラームの繰り返し + Default title for recurring day ListPicker. + + + 年月を選択 + Default title for DatePicker. + + + キャンセル + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + 完了 + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + 金曜日 + Default text for "Friday" as used by the RelativeTimeConverter. + + + 先週の{0} + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + 月曜日 + Default text for "Monday" as used by the RelativeTimeConverter. + + + 1年以上前 + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + 毎日 + Default text for the recurring day ListPicker when an alarm repeats daily. + + + なし + Default text for the recurring day ListPicker when an alarm is not recurring. + + + 平日 + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + 週末 + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + 土曜日 + Default text for "Saturday" as used by the RelativeTimeConverter. + + + 日曜日 + Default text for "Sunday" as used by the RelativeTimeConverter. + + + 木曜日 + Default text for "Thursday" as used by the RelativeTimeConverter. + + + 時刻を選択 + Default title for TimePicker. + + + 火曜日 + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + 水曜日 + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + {0}時間前 + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0}時間前 + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0}時間前 + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0}時間前 + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0}分前 + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0}分前 + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0}分前 + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0}分前 + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0}か月前 + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0}か月前 + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + {0}秒前 + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0}秒前 + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0}秒前 + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0}秒前 + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0}週間前 + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ko.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ko.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ko.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ko.resx new file mode 100644 index 0000000..e3fa98d --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ko.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 약 1분 전 + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + 약 한 달 전 + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + 약 1시간 전 + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + 약 1주 전 + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + 알람 반복 요일 + Default title for recurring day ListPicker. + + + 날짜 선택 + Default title for DatePicker. + + + 취소 + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + 완료 + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + 금요일 + Default text for "Friday" as used by the RelativeTimeConverter. + + + 지난 {0} + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + 월요일 + Default text for "Monday" as used by the RelativeTimeConverter. + + + 1년여 전 + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + 매일 + Default text for the recurring day ListPicker when an alarm repeats daily. + + + 한 번 + Default text for the recurring day ListPicker when an alarm is not recurring. + + + 평일 + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + 주말 + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + 토요일 + Default text for "Saturday" as used by the RelativeTimeConverter. + + + 일요일 + Default text for "Sunday" as used by the RelativeTimeConverter. + + + 목요일 + Default text for "Thursday" as used by the RelativeTimeConverter. + + + 시간 선택 + Default title for TimePicker. + + + 화요일 + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + 수요일 + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + {0}시간 전 + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0}시간 전 + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0}시간 전 + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0}시간 전 + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0}분 전 + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0}분 전 + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0}분 전 + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0}분 전 + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0}개월 전 + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0}개월 전 + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + {0}초 전 + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0}초 전 + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0}초 전 + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0}초 전 + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0}주 전 + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.nb-NO.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.nb-NO.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.nb-NO.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.nb-NO.resx new file mode 100644 index 0000000..8a909ae --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.nb-NO.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + for omtrent ett minutt siden + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + for en måned siden + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + for omtrent en time siden + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + for omtrent en uke siden + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + ALARM GJENTAS PÅ + Default title for recurring day ListPicker. + + + VELG DATO + Default title for DatePicker. + + + avbryt + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + fullført + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + fredag + Default text for "Friday" as used by the RelativeTimeConverter. + + + forrige {0} + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + mandag + Default text for "Monday" as used by the RelativeTimeConverter. + + + for over et år siden + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + hver dag + Default text for the recurring day ListPicker when an alarm repeats daily. + + + bare én gang + Default text for the recurring day ListPicker when an alarm is not recurring. + + + ukedager + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + helger + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + lørdag + Default text for "Saturday" as used by the RelativeTimeConverter. + + + søndag + Default text for "Sunday" as used by the RelativeTimeConverter. + + + torsdag + Default text for "Thursday" as used by the RelativeTimeConverter. + + + VELG KLOKKESLETT + Default title for TimePicker. + + + tirsdag + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + onsdag + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + for {0} timer siden + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + for {0} timer siden + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + for {0} timer siden + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + for {0} timer siden + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + for {0} minutter siden + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + for {0} minutter siden + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + for {0} minutter siden + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + for {0} minutter siden + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + for {0} måneder siden + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + for {0} måneder siden + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + for {0} sekunder siden + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + for {0} sekunder siden + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + for {0} sekunder siden + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + for {0} sekunder siden + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} uker siden + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + på {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + på {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.nl-NL.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.nl-NL.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.nl-NL.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.nl-NL.resx new file mode 100644 index 0000000..3ceea6f --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.nl-NL.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + ong. een minuut geleden + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + ong. een maand geleden + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + ong. een uur geleden + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + ong. een week geleden + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + ALARM HERHALEN OP + Default title for recurring day ListPicker. + + + DATUM KIEZEN + Default title for DatePicker. + + + annuleren + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + gereed + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + vrijdag + Default text for "Friday" as used by the RelativeTimeConverter. + + + afgelopen {0} + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + maandag + Default text for "Monday" as used by the RelativeTimeConverter. + + + meer dan een jaar geleden + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + elke dag + Default text for the recurring day ListPicker when an alarm repeats daily. + + + één keer + Default text for the recurring day ListPicker when an alarm is not recurring. + + + werkdagen + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + weekend + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + zaterdag + Default text for "Saturday" as used by the RelativeTimeConverter. + + + zondag + Default text for "Sunday" as used by the RelativeTimeConverter. + + + donderdag + Default text for "Thursday" as used by the RelativeTimeConverter. + + + TIJD KIEZEN + Default title for TimePicker. + + + dinsdag + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + woensdag + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + {0} uur geleden + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} uur geleden + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} uur geleden + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} uur geleden + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} minuten geleden + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} minuten geleden + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} minuten geleden + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} minuten geleden + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} maanden geleden + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} maanden geleden + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + {0} seconden geleden + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} seconden geleden + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} seconden geleden + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} seconden geleden + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} weken geleden + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + op {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + op {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pl.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pl.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pl.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pl.resx new file mode 100644 index 0000000..1057ad3 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pl.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + około minutę temu + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + około miesiąc temu + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + około godzinę temu + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + około tydzień temu + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + ALARM JEST POWTARZANY W + Default title for recurring day ListPicker. + + + WYBIERZ DATĘ + Default title for DatePicker. + + + anuluj + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + gotowe + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + piątek + Default text for "Friday" as used by the RelativeTimeConverter. + + + ostatni(a) {0} + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + poniedziałek + Default text for "Monday" as used by the RelativeTimeConverter. + + + ponad rok temu + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + codziennie + Default text for the recurring day ListPicker when an alarm repeats daily. + + + tylko raz + Default text for the recurring day ListPicker when an alarm is not recurring. + + + dni robocze + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + weekendy + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + sobota + Default text for "Saturday" as used by the RelativeTimeConverter. + + + niedziela + Default text for "Sunday" as used by the RelativeTimeConverter. + + + czwartek + Default text for "Thursday" as used by the RelativeTimeConverter. + + + WYBIERZ GODZINĘ + Default title for TimePicker. + + + wtorek + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + środa + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + {0} godziny temu + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} godzin temu + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} godziny temu + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} godzin temu + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} minuty temu + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} minut temu + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} minuty temu + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} minut temu + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} miesiące temu + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} miesięcy temu + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + {0} sekundy temu + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} sekund temu + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} sekundy temu + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} sekund temu + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} tygodnie temu + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + ({0}) + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + ({0}) + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pt-BR.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pt-BR.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pt-BR.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pt-BR.resx new file mode 100644 index 0000000..ef0d7a7 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pt-BR.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + há cerca de um minuto + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + há cerca de um mês + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + há cerca de uma hora + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + há cerca de uma semana + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + TOCAR ALARME + Default title for recurring day ListPicker. + + + ESCOLHER DATA + Default title for DatePicker. + + + cancelar + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + concluído + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + sexta-feira + Default text for "Friday" as used by the RelativeTimeConverter. + + + último(a) {0} + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + segunda-feira + Default text for "Monday" as used by the RelativeTimeConverter. + + + há mais de um ano + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + todos os dias + Default text for the recurring day ListPicker when an alarm repeats daily. + + + somente uma vez + Default text for the recurring day ListPicker when an alarm is not recurring. + + + dias úteis + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + finais de semana + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + sábado + Default text for "Saturday" as used by the RelativeTimeConverter. + + + domingo + Default text for "Sunday" as used by the RelativeTimeConverter. + + + quinta-feira + Default text for "Thursday" as used by the RelativeTimeConverter. + + + ESCOLHER HORA + Default title for TimePicker. + + + terça-feira + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + quarta-feira + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + há {0} horas + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + há {0} horas + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + há {0} horas + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + há {0} horas + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + há {0} minutos + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + há {0} minutos + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + há {0} minutos + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + há {0} minutos + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + há {0} meses + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + há {0} meses + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + há {0} segundos + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + há {0} segundos + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + há {0} segundos + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + há {0} segundos + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + há {0} semanas + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + no(a) {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + no(a) {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pt-PT.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pt-PT.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pt-PT.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pt-PT.resx new file mode 100644 index 0000000..a45bb39 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.pt-PT.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + há cerca de um minuto + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + há cerca de um mês + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + há cerca de uma hora + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + há cerca de uma semana + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + O ALARME REPETE À/AO + Default title for recurring day ListPicker. + + + ESCOLHER DATA + Default title for DatePicker. + + + cancelar + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + concluído + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + Sexta-feira + Default text for "Friday" as used by the RelativeTimeConverter. + + + última(o) {0} + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + Segunda-feira + Default text for "Monday" as used by the RelativeTimeConverter. + + + há mais de um ano + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + todos os dias + Default text for the recurring day ListPicker when an alarm repeats daily. + + + apenas uma vez + Default text for the recurring day ListPicker when an alarm is not recurring. + + + dias da semana + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + fins de semana + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + Sábado + Default text for "Saturday" as used by the RelativeTimeConverter. + + + Domingo + Default text for "Sunday" as used by the RelativeTimeConverter. + + + Quinta-feira + Default text for "Thursday" as used by the RelativeTimeConverter. + + + ESCOLHER HORA + Default title for TimePicker. + + + Terça-feira + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + Quarta-feira + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + há {0} horas + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + há {0} horas + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + há {0} horas + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + há {0} horas + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + há {0} minutos + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + há {0} minutos + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + há {0} minutos + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + há {0} minutos + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + há {0} meses + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + há {0} meses + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + há {0} segundos + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + há {0} segundos + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + há {0} segundos + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + há {0} segundos + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + há {0} semanas + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + no(a) {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + no(a) {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.resx new file mode 100644 index 0000000..31c3959 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + about a minute ago + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + about a month ago + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + about an hour ago + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + about a week ago + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + ALARM REPEATS ON + Default title for recurring day ListPicker. + + + CHOOSE DATE + Default title for DatePicker. + + + cancel + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + done + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + Friday + Default text for "Friday" as used by the RelativeTimeConverter. + + + last {0} + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + Monday + Default text for "Monday" as used by the RelativeTimeConverter. + + + over a year ago + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + every day + Default text for the recurring day ListPicker when an alarm repeats daily. + + + only once + Default text for the recurring day ListPicker when an alarm is not recurring. + + + weekdays + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + weekends + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + Saturday + Default text for "Saturday" as used by the RelativeTimeConverter. + + + Sunday + Default text for "Sunday" as used by the RelativeTimeConverter. + + + Thursday + Default text for "Thursday" as used by the RelativeTimeConverter. + + + CHOOSE TIME + Default title for TimePicker. + + + Tuesday + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + Wednesday + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + {0} hours ago + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} hours ago + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} hours ago + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} hours ago + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} minutes ago + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} minutes ago + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} minutes ago + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} minutes ago + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} months ago + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} months ago + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + {0} seconds ago + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} seconds ago + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} seconds ago + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} seconds ago + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} weeks ago + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + on {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + on {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ru.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ru.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ru.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ru.resx new file mode 100644 index 0000000..df269e0 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.ru.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + около минуты назад + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + около месяца назад + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + около часа назад + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + около недели назад + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + ДЕНЬ + Default title for recurring day ListPicker. + + + ВЫБЕРИТЕ ДАТУ + Default title for DatePicker. + + + Отмена + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + готово + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + пятницу + Default text for "Friday" as used by the RelativeTimeConverter. + + + в прошл. {0} + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + понедельник + Default text for "Monday" as used by the RelativeTimeConverter. + + + больше года назад + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + каждый день + Default text for the recurring day ListPicker when an alarm repeats daily. + + + только 1 раз + Default text for the recurring day ListPicker when an alarm is not recurring. + + + по будним дням + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + по выходным + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + субботу + Default text for "Saturday" as used by the RelativeTimeConverter. + + + воскресенье + Default text for "Sunday" as used by the RelativeTimeConverter. + + + четверг + Default text for "Thursday" as used by the RelativeTimeConverter. + + + ВЫБЕРИТЕ ВРЕМЯ + Default title for TimePicker. + + + вторник + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + среду + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + {0} часа назад + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} час назад + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} часа назад + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} часов назад + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} минуты назад + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} минуту назад + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} минуты назад + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} минут назад + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} месяца назад + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} месяцев назад + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + {0} секунды назад + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} секунду назад + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} секунды назад + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} секунд назад + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} недели назад + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + в {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + во {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.sv-SE.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.sv-SE.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.sv-SE.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.sv-SE.resx new file mode 100644 index 0000000..a509428 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.sv-SE.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + för cirka en minut sedan + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + för en månad sedan + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + för cirka en timme sedan + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + för en vecka sedan + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + ALARMET UPPREPAS PÅ + Default title for recurring day ListPicker. + + + VÄLJ DATUM + Default title for DatePicker. + + + avbryt + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + klart + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + fredag + Default text for "Friday" as used by the RelativeTimeConverter. + + + förra {0}en + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + måndag + Default text for "Monday" as used by the RelativeTimeConverter. + + + för över ett år sedan + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + varje dag + Default text for the recurring day ListPicker when an alarm repeats daily. + + + endast en gång + Default text for the recurring day ListPicker when an alarm is not recurring. + + + vardagar + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + helger + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + lördag + Default text for "Saturday" as used by the RelativeTimeConverter. + + + söndag + Default text for "Sunday" as used by the RelativeTimeConverter. + + + torsdag + Default text for "Thursday" as used by the RelativeTimeConverter. + + + VÄLJ TID + Default title for TimePicker. + + + tisdag + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + onsdag + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + för {0} timmar sedan + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + för {0} timmar sedan + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + för {0} timmar sedan + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + för {0} timmar sedan + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + för {0} minuter sedan + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + för {0} minuter sedan + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + för {0} minuter sedan + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + för {0} minuter sedan + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + för {0} månader sedan + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + för {0} månader sedan + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + för {0} sekunder sedan + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + för {0} sekunder sedan + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + för {0} sekunder sedan + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + för {0} sekunder sedan + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + för {0} veckor sedan + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + på {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + på {0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.zh-CN.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.zh-CN.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.zh-CN.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.zh-CN.resx new file mode 100644 index 0000000..d66361e --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.zh-CN.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 约 1 分钟前 + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + 约 1 个月前 + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + 约 1 小时前 + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + 约 1 周前 + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + 闹钟重复时间 + Default title for recurring day ListPicker. + + + 选择日期 + Default title for DatePicker. + + + 取消 + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + 完成 + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + 周五 + Default text for "Friday" as used by the RelativeTimeConverter. + + + 上{0} + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + 周一 + Default text for "Monday" as used by the RelativeTimeConverter. + + + 数年前 + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + 每天 + Default text for the recurring day ListPicker when an alarm repeats daily. + + + 仅一次 + Default text for the recurring day ListPicker when an alarm is not recurring. + + + 工作日 + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + 周末 + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + 周六 + Default text for "Saturday" as used by the RelativeTimeConverter. + + + 周日 + Default text for "Sunday" as used by the RelativeTimeConverter. + + + 周四 + Default text for "Thursday" as used by the RelativeTimeConverter. + + + 选择时间 + Default title for TimePicker. + + + 周二 + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + 周三 + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + {0} 小时前 + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} 小时前 + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} 小时前 + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} 小时前 + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} 分钟前 + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} 分钟前 + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} 分钟前 + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} 分钟前 + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} 个月前 + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} 个月前 + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + {0} 秒前 + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} 秒前 + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} 秒前 + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} 秒前 + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} 周前 + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + 在{0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + 在{0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.zh-TW.Designer.cs b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.zh-TW.Designer.cs new file mode 100644 index 0000000..e69de29 diff --git a/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.zh-TW.resx b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.zh-TW.resx new file mode 100644 index 0000000..432b50d --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LocalizedResources/ControlResources.zh-TW.resx @@ -0,0 +1,276 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 約 1 分鐘前 + Default text for "about a minute ago" as used by the RelativeTimeConverter. + + + 約 1 個月前 + Default text for "about a month ago" as used by the RelativeTimeConverter. + + + 約 1 小時前 + Default text for "about an hour ago" as used by the RelativeTimeConverter. + + + 約 1 週前 + Default text for "about a week ago" as used by the RelativeTimeConverter. + + + 鬧鐘重複日期 + Default title for recurring day ListPicker. + + + 選擇日期 + Default title for DatePicker. + + + 取消 + Default text for the "cancel" ApplicationBar button used by DatePicker/TimePicker. + + + 完成 + Default text for the "done" ApplicationBar button used by DatePicker/TimePicker. + + + 週五 + Default text for "Friday" as used by the RelativeTimeConverter. + + + 上{0} + Default text for "last {0}" as used by the RelativeTimeConverter, where {0} is day of the week, e.g. Sunday. + + + 週一 + Default text for "Monday" as used by the RelativeTimeConverter. + + + 1 年前 + Default text for "over a year ago" as used by the RelativeTimeConverter. + + + 每天 + Default text for the recurring day ListPicker when an alarm repeats daily. + + + 僅只一次 + Default text for the recurring day ListPicker when an alarm is not recurring. + + + 工作日 + Default text for the recurring day ListPicker when an alarm repeats on weekdays. + + + 週末 + Default text for the recurring day ListPicker when an alarm repeats on weekends. + + + 週六 + Default text for "Saturday" as used by the RelativeTimeConverter. + + + 週日 + Default text for "Sunday" as used by the RelativeTimeConverter. + + + 週四 + Default text for "Thursday" as used by the RelativeTimeConverter. + + + 選擇時間 + Default title for TimePicker. + + + 週二 + Default text for "Tuesday" as used by the RelativeTimeConverter. + + + 週三 + Default text for "Wednesday" as used by the RelativeTimeConverter. + + + {0} 小時前 + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} 小時前 + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} 小時前 + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} 小時前 + Default text for "{0} hours ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} 分鐘前 + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} 分鐘前 + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} 分鐘前 + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} 分鐘前 + Default text for "{0} minutes ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} 個月前 + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} 個月前 + Default text for "{0} months ago" as used by the RelativeTimeConverter, where {0} is a number from 5 to 12. + + + {0} 秒鐘前 + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + {0} 秒鐘前 + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 1 but doesn't end in 11. + + + {0} 秒鐘前 + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number that ends in 2, 3 or 4, but doesn't end in 12, 13 or 14. + + + {0} 秒鐘前 + Default text for "{0} seconds ago" as used by the RelativeTimeConverter, where {0} is a number not considered in any of the other exceptions, i.e. a number that ends in 0 or 5 to 9. + + + {0} 週前 + Default text for "{0} weeks ago" as used by the RelativeTimeConverter, where {0} is 2, 3 or 4. + + + 本{0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is day of the week not considered in any of the other exceptions, e.g. Sunday. + + + 本{0} + Default text for "on {0}" as used by the RelativeTimeConverter, where {0} is the localized string for Tuesday. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelector.cs b/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelector.cs new file mode 100644 index 0000000..acfd0fb --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelector.cs @@ -0,0 +1,1729 @@ +// (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.Collections.ObjectModel; +using System.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using Microsoft.Phone.Controls.Primitives; + +namespace Microsoft.Phone.Controls +{ + /// + /// Represents a virtualizing list designed for grouped lists. Can also be + /// used with flat lists. + /// + /// Preview + [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "LongListSelector is a complicated control.")] + [TemplatePart(Name = TemplatedListBoxName, Type = typeof(TemplatedListBox))] + public partial class LongListSelector : Control + { + #region Constants + /// + /// The templated list box name. + /// + private const string TemplatedListBoxName = "TemplatedListBox"; + + /// + /// This constant is not actively used in the new version, however, the + /// value is exposed through a deprecated property. For backward- + /// compatibility only. + /// + private const double BufferSizeDefault = 1.0; + + /// + /// The Scrolling state name. + /// + private const string ScrollingState = "Scrolling"; + + /// + /// The NotScrolling state name. + /// + private const string NotScrollingState = "NotScrolling"; + + /// + /// The vertical compression top state name. + /// + private const string CompressionTop = "CompressionTop"; + + /// + /// The vertical compression bottom state name. + /// + private const string CompressionBottom = "CompressionBottom"; + + /// + /// The absense of vertical compression state name. + /// + private const string NoVerticalCompression = "NoVerticalCompression"; + + /// + /// The vertical compression state name. + /// + private const string VerticalCompressionStateName = "VerticalCompression"; + + /// + /// The name of the scroll states visual state group. + /// + private const string ScrollStatesGroupName = "ScrollStates"; + #endregion + + /// + /// Reference to the ListBox hosted in this control. + /// + private TemplatedListBox _listBox; + + /// + /// Reference to the visual state group for scrolling. + /// + private VisualStateGroup _scrollGroup; + + /// + /// Reference to the visual state group for vertical compression. + /// + private VisualStateGroup _verticalCompressionGroup; + + /// + /// // Used to listen for changes in the ItemsSource + /// (_rootCollection = ItemsSource as INotifyCollectionChanged). + /// + private INotifyCollectionChanged _rootCollection; + + /// + /// Used to listen for changes in the groups within ItemsSource. + /// + private List _groupCollections = new List(); + + #region Properties + + /// + /// Gets or sets whether the list is flat instead of a group hierarchy. + /// + public bool IsFlatList { get; set; } + + /// + /// Gets or sets the selected item. + /// + public object SelectedItem + { + get + { + if (_listBox != null && _listBox.SelectedItem != null) + { + LongListSelectorItem tuple = (LongListSelectorItem)_listBox.SelectedItem; + if (tuple.ItemType == LongListSelectorItemType.Item) + return tuple.Item; + } + return null; + } + set + { + if (_listBox != null) + { + if (value == null) + { + _listBox.SelectedItem = null; + } + else + { + foreach (LongListSelectorItem tuple in _listBox.ItemsSource) + { + if (tuple.Item == value) + { + _listBox.SelectedItem = tuple; + break; + } + } + } + } + } + } + + /// + /// Gets or sets whether the list can be (temporarily) scrolled off of the ends. + /// + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "Backward compatible public setter.")] + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "value", Justification = "Backward compatible public setter.")] + [Obsolete("IsBouncy is always set to true.")] + public bool IsBouncy + { + get { return true; } + set { } + } + + /// + /// Gets whether a list header is shown. + /// + private bool HasListHeader { get { return ListHeaderTemplate != null || ListHeader is UIElement; } } + + /// + /// Gets whether a list footer is shown. + /// + private bool HasListFooter { get { return ListFooterTemplate != null || ListFooter is UIElement; } } + + /// + /// Gets whether or not the user is manipulating the list, or if an inertial animation is taking place. + /// + public bool IsScrolling + { + get; + private set; + } + + /// + /// Gets whether or not stretching is taking place. + /// + public bool IsStretching + { + get; + private set; + } + #endregion + + #region Dependency Properties + + #region ItemsSource DependencyProperty + + /// + /// The DataSource property. Where all of the items come from. + /// + public IEnumerable ItemsSource + { + get { return (IEnumerable)GetValue(ItemsSourceProperty); } + set { SetValue(ItemsSourceProperty, value); } + } + + /// + /// The DataSource DependencyProperty. + /// + public static readonly DependencyProperty ItemsSourceProperty = + DependencyProperty.Register("ItemsSource", typeof(IEnumerable), typeof(LongListSelector), new PropertyMetadata(null, OnItemsSourceChanged)); + + private static void OnItemsSourceChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + ((LongListSelector)obj).OnItemsSourceChanged(); + } + + + + #endregion + + #region ListHeader DependencyProperty + + /// + /// The ListHeader property. Will be used as the first scrollItem in the list. + /// + public object ListHeader + { + get { return (object)GetValue(ListHeaderProperty); } + set { SetValue(ListHeaderProperty, value); } + } + + /// + /// The ListHeader DependencyProperty. + /// + public static readonly DependencyProperty ListHeaderProperty = + DependencyProperty.Register("ListHeader", typeof(object), typeof(LongListSelector), new PropertyMetadata(null)); + + #endregion + + #region ListHeaderTemplate DependencyProperty + + /// + /// The ListHeaderTemplate provides the template for the ListHeader. + /// + public DataTemplate ListHeaderTemplate + { + get { return (DataTemplate)GetValue(ListHeaderTemplateProperty); } + set { SetValue(ListHeaderTemplateProperty, value); } + } + + /// + /// The ListHeaderTemplate DependencyProperty. + /// + public static readonly DependencyProperty ListHeaderTemplateProperty = + DependencyProperty.Register("ListHeaderTemplate", typeof(DataTemplate), typeof(LongListSelector), new PropertyMetadata(null, OnDataTemplateChanged)); + + #endregion + + #region ListFooter DependencyProperty + + /// + /// The ListFooter property. Will be used as the first scrollItem in the list. + /// + public object ListFooter + { + get { return (object)GetValue(ListFooterProperty); } + set { SetValue(ListFooterProperty, value); } + } + + /// + /// The ListFooter DependencyProperty. + /// + public static readonly DependencyProperty ListFooterProperty = + DependencyProperty.Register("ListFooter", typeof(object), typeof(LongListSelector), new PropertyMetadata(null)); + + #endregion + + #region ListFooterTemplate DependencyProperty + + /// + /// The ListFooterTemplate provides the template for the ListFooter. + /// + public DataTemplate ListFooterTemplate + { + get { return (DataTemplate)GetValue(ListFooterTemplateProperty); } + set { SetValue(ListFooterTemplateProperty, value); } + } + + /// + /// The ListFooterTemplate DependencyProperty. + /// + public static readonly DependencyProperty ListFooterTemplateProperty = + DependencyProperty.Register("ListFooterTemplate", typeof(DataTemplate), typeof(LongListSelector), new PropertyMetadata(null, OnDataTemplateChanged)); + + #endregion + + #region GroupHeaderTemplate DependencyProperty + + /// + /// The GroupHeaderTemplate provides the template for the groups in the items view. + /// + public DataTemplate GroupHeaderTemplate + { + get { return (DataTemplate)GetValue(GroupHeaderProperty); } + set { SetValue(GroupHeaderProperty, value); } + } + + /// + /// The GroupHeaderTemplate DependencyProperty. + /// + public static readonly DependencyProperty GroupHeaderProperty = + DependencyProperty.Register("GroupHeaderTemplate", typeof(DataTemplate), typeof(LongListSelector), new PropertyMetadata(null, OnDataTemplateChanged)); + #endregion + + #region GroupFooterTemplate DependencyProperty + + /// + /// The GroupFooterTemplate provides the template for the groups in the items view. + /// + public DataTemplate GroupFooterTemplate + { + get { return (DataTemplate)GetValue(GroupFooterProperty); } + set { SetValue(GroupFooterProperty, value); } + } + + /// + /// The GroupFooterTemplate DependencyProperty. + /// + public static readonly DependencyProperty GroupFooterProperty = + DependencyProperty.Register("GroupFooterTemplate", typeof(DataTemplate), typeof(LongListSelector), new PropertyMetadata(null, OnDataTemplateChanged)); + + #endregion + + #region ItemTemplate DependencyProperty + + /// + /// The ItemTemplate provides the template for the items in the items view. + /// + public DataTemplate ItemTemplate + { + get { return (DataTemplate)GetValue(ItemsTemplateProperty); } + set { SetValue(ItemsTemplateProperty, value); } + } + + /// + /// The ItemTemplate DependencyProperty. + /// + public static readonly DependencyProperty ItemsTemplateProperty = + DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(LongListSelector), new PropertyMetadata(null, OnDataTemplateChanged)); + + #endregion + + #region DisplayAllGroups DependencyProperty + + /// + /// Display all groups whether or not they have items. + /// + public bool DisplayAllGroups + { + get { return (bool)GetValue(DisplayAllGroupsProperty); } + set { SetValue(DisplayAllGroupsProperty, value); } + } + + /// + /// DisplayAllGroups DependencyProperty + /// + public static readonly DependencyProperty DisplayAllGroupsProperty = + DependencyProperty.Register("DisplayAllGroups", typeof(bool), typeof(LongListSelector), new PropertyMetadata(false, OnDisplayAllGroupsChanged)); + #endregion + + #region GroupItemTemplate DependencyProperty + + /// + /// The GroupItemTemplate specifies the template that will be used in group view mode. + /// + public DataTemplate GroupItemTemplate + { + get { return (DataTemplate)GetValue(GroupItemTemplateProperty); } + set { SetValue(GroupItemTemplateProperty, value); } + } + + /// + /// The GroupItemTemplate DependencyProperty. + /// + public static readonly DependencyProperty GroupItemTemplateProperty = + DependencyProperty.Register("GroupItemTemplate", typeof(DataTemplate), typeof(LongListSelector), new PropertyMetadata(null)); + + #endregion + + #region GroupItemsPanel DependencyProperty + + /// + /// The GroupItemsPanel specifies the panel that will be used in group view mode. + /// + public ItemsPanelTemplate GroupItemsPanel + { + get { return (ItemsPanelTemplate)GetValue(GroupItemsPanelProperty); } + set { SetValue(GroupItemsPanelProperty, value); } + } + + /// + /// The GroupItemsPanel DependencyProperty. + /// + public static readonly DependencyProperty GroupItemsPanelProperty = + DependencyProperty.Register("GroupItemsPanel", typeof(ItemsPanelTemplate), typeof(LongListSelector), new PropertyMetadata(null)); + + #endregion + + #region BufferSize DependencyProperty + /// + /// The number of "screens" (as defined by the ActualHeight of the LongListSelector) above and below the visible + /// items of the list that will be filled with items. + /// + [Obsolete("BufferSize no longer affect items virtualization")] + public double BufferSize + { + get { return (double)GetValue(BufferSizeProperty); } + set { SetValue(BufferSizeProperty, value); } + } + + /// + /// The BufferSize DependencyProperty + /// + [Obsolete("BufferSizeProperty no longer affect items virtualization")] + public static readonly DependencyProperty BufferSizeProperty = + DependencyProperty.Register("BufferSize", typeof(double), typeof(LongListSelector), new PropertyMetadata(BufferSizeDefault, OnBufferSizeChanged)); + + [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly")] + private static void OnBufferSizeChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + double newValue = (double)e.NewValue; + + if (newValue < 0) + { + throw new ArgumentOutOfRangeException("BufferSize"); + } + } + + #endregion + + #region MaximumFlickVelocity DependencyProperty + /// + /// The maximum velocity for flicks, in pixels per second. + /// + [Obsolete("MaximumFlickVelocity is not used anymore.")] + public double MaximumFlickVelocity + { + get { return (double)GetValue(MaximumFlickVelocityProperty); } + set { SetValue(MaximumFlickVelocityProperty, value); } + } + + /// + /// The MaximumFlickVelocity DependencyProperty. + /// + [Obsolete("MaximumFlickVelocityProperty is not used anymore.")] + public static readonly DependencyProperty MaximumFlickVelocityProperty = + DependencyProperty.Register("MaximumFlickVelocity", typeof(double), typeof(LongListSelector), new PropertyMetadata(MotionParameters.MaximumSpeed)); + + #endregion + + #region ShowListHeader DependencyProperty + + /// + /// Controls whether or not the ListHeader is shown. + /// + public bool ShowListHeader + { + get { return (bool)GetValue(ShowListHeaderProperty); } + set { SetValue(ShowListHeaderProperty, value); } + } + + /// + /// The ShowListHeader DependencyProperty. + /// + public static readonly DependencyProperty ShowListHeaderProperty = + DependencyProperty.Register("ShowListHeader", typeof(bool), typeof(LongListSelector), new PropertyMetadata(true, OnShowListHeaderChanged)); + + private static void OnShowListHeaderChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + LongListSelector control = (LongListSelector)obj; + + if (control._listBox != null) + { + Collection tuples = (Collection)control._listBox.ItemsSource; + if (control.ShowListHeader) + { + control.AddListHeader(tuples); + } + else + { + RemoveListHeader(tuples); + } + } + } + #endregion + + #region ShowListFooter DependencyProperty + + /// + /// Controls whether or not the ListFooter is shown. + /// + public bool ShowListFooter + { + get { return (bool)GetValue(ShowListFooterProperty); } + set { SetValue(ShowListFooterProperty, value); } + } + + /// + /// The ShowListFooter DependencyProperty. + /// + public static readonly DependencyProperty ShowListFooterProperty = + DependencyProperty.Register("ShowListFooter", typeof(bool), typeof(LongListSelector), new PropertyMetadata(true, OnShowListFooterChanged)); + + private static void OnShowListFooterChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + LongListSelector control = (LongListSelector)obj; + + if (control._listBox != null) + { + Collection tuples = (Collection)control._listBox.ItemsSource; + if (control.ShowListFooter) + { + control.AddListFooter(tuples); + } + else + { + RemoveListFooter(tuples); + } + } + + } + + #endregion + + #endregion + + #region Events + + /// + /// Occurs when the currently selected item changes. + /// + public event SelectionChangedEventHandler SelectionChanged; + + /// + /// Occurs when this control starts scrolling. + /// + public event EventHandler ScrollingStarted; + + /// + /// Occurs when this control stops scrolling. + /// + public event EventHandler ScrollingCompleted; + + /// + /// Occurs when the group Popup's IsOpen has been set to true. + /// + public event EventHandler GroupViewOpened; + + /// + /// Occurs when the group popup is about to close. + /// + public event EventHandler GroupViewClosing; + + /// + /// Occurs when an item is about to be "realized". + /// + public event EventHandler Link; + + /// + /// Occurs when an item is about to be "un-realized". + /// + public event EventHandler Unlink; + + /// + /// Occurs when the user has dragged the items up from the bottom as far as they can go. + /// + public event EventHandler StretchingBottom; + + /// + /// Occurs when the user is no longer stretching. + /// + public event EventHandler StretchingCompleted; + + /// + /// Occurs when the user has dragged the items down from the top as far as they can go. + /// + public event EventHandler StretchingTop; + + #endregion + + #region Constructor + /// + /// Initializes a new instance of . + /// + public LongListSelector() + { + DefaultStyleKey = typeof(LongListSelector); + } + #endregion + + // + // Public methods + // + + #region ScrollTo(...) + /// + /// Instantly jump to the specified item. + /// + /// Item to jump to. + public void ScrollTo(object item) + { + if (_listBox != null && item != null) + { + ObservableCollection tuples = (ObservableCollection)_listBox.ItemsSource; + LongListSelectorItem lastTuple = tuples[tuples.Count - 1]; + + _listBox.ScrollIntoView(lastTuple); + + UpdateLayout(); + + foreach (LongListSelectorItem tuple in _listBox.ItemsSource) + { + if (tuple.Item != null && tuple.Item.Equals(item)) + { + _listBox.ScrollIntoView(tuple); + break; + } + } + } + } + #endregion + + #region ScrollToGroup(...) + /// + /// Instantly jump to the specified group. + /// + /// Group to jump to. + public void ScrollToGroup(object group) + { + ScrollTo(group); + } + #endregion + + #region DisplayGroupView() + /// + /// Invokes the group view if a GroupItemTemplate has been defined. + /// + public void DisplayGroupView() + { + if (GroupItemTemplate != null && !IsFlatList) + { + OpenPopup(); + } + } + #endregion + + #region CloseGroupView() + /// + /// Closes the group view unconditionally. + /// + /// Does not raise the GroupViewClosingEventArgs event. + public void CloseGroupView() + { + ClosePopup(null, false); + } + #endregion + + // Obsolete: + + #region AnimateTo(...) + /// + /// Animate the scrolling of the list to the specified item. + /// + /// Item to scroll to. + [Obsolete("AnimateTo(...) call ScrollTo(...) to jump without animation to the given item.")] + public void AnimateTo(object item) + { + ScrollTo(item); + } + #endregion + + /// + /// Returns either containers or items for either all items with + /// containers or just the visible ones, as specified by the + /// parameters. + /// + /// When true, will return values for + /// only items that are in view. + /// When true, will return the containers + /// rather than the items. + /// Returns either containers or items for either all items + /// with containers or just the visible ones, as specified by the + /// parameters. + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "getContainers", Justification = "This is an obsolete old method that cannot change now.")] + [SuppressMessage("Microsoft.Usage", "CA1801:ReviewUnusedParameters", MessageId = "onlyItemsInView", Justification = "This is an obsolete old method that cannot change now.")] + [SuppressMessage("Microsoft.Performance", "CA1822:MarkMembersAsStatic", Justification = "This is an obsolete old method that cannot change now.")] + [Obsolete("GetItemsWithContainers(...) always returns an empty collection of items. Rely on Link/Unlink to know an item get realized or unrealized.")] + public ICollection GetItemsWithContainers(bool onlyItemsInView, bool getContainers) + { + return new Collection(); + } + + #region GetItemsInView() + /// + /// Returns all of the items that are currently in view. + /// This is not the same as the items that have associated visual elements: there are usually some visuals offscreen. + /// This might be an empty list if scrolling is happening too quickly. + /// + /// Returns all of the items that are currently in view. + [Obsolete("GetItemsInView() always returns an empty collection of items. Rely on Link/Unlink to know an item get realized or unrealized.")] + public ICollection GetItemsInView() + { + return GetItemsWithContainers(true, false); + } + #endregion + + #region OnItemsSourceChanged() + /// + /// Called when the ItemsSource dependency property changes. + /// + private void OnItemsSourceChanged() + { + // Reload the whole list. + LoadDataIntoListBox(); + } + #endregion + + #region OnApplyTemplate() + /// + /// Called whenever a template gets applied on this control. + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + // Unsubscribe from events we registered for in the past. + if (_listBox != null) + { + _listBox.SelectionChanged -= OnSelectionChanged; + _listBox.Link -= OnLink; + _listBox.Unlink -= OnUnlink; + } + + if (_scrollGroup != null) + { + _scrollGroup.CurrentStateChanging -= OnScrollStateChanging; + } + + // Locates and setup the TemplatedListBox in the template. If no TemplatedListBox is found, we + // initialize one. + _listBox = GetTemplateChild(TemplatedListBoxName) as TemplatedListBox ?? new TemplatedListBox(); + _listBox.ListHeaderTemplate = ListHeaderTemplate; + _listBox.ListFooterTemplate = ListFooterTemplate; + _listBox.GroupHeaderTemplate = GroupHeaderTemplate; + _listBox.GroupFooterTemplate = GroupFooterTemplate; + _listBox.ItemTemplate = ItemTemplate; + _listBox.SelectionChanged += OnSelectionChanged; + _listBox.Link += OnLink; + _listBox.Unlink += OnUnlink; + + // Retrieves the ScrollViewer of the list box. + ScrollViewer sv = _listBox.GetFirstLogicalChildByType(true); + + if (sv != null) + { + // Visual States are always on the first child of the control template + FrameworkElement element = VisualTreeHelper.GetChild(sv, 0) as FrameworkElement; + if (element != null) + { + _scrollGroup = VisualStates.TryGetVisualStateGroup(sv, ScrollStatesGroupName); + if (_scrollGroup != null) + { + _scrollGroup.CurrentStateChanging += OnScrollStateChanging; + } + + _verticalCompressionGroup = VisualStates.TryGetVisualStateGroup(sv, VerticalCompressionStateName); + if(_verticalCompressionGroup != null) + { + _verticalCompressionGroup.CurrentStateChanging += OnStretchStateChanging; + } + } + } + + LoadDataIntoListBox(); + } + #endregion + + #region LoadDataIntoListBox() + /// + /// Loads ItemsSource into the hosted list box. + /// + private void LoadDataIntoListBox() + { + if (_listBox != null) + { + ObservableCollection tuples = new ObservableCollection(); + + AddListHeader(tuples); + + UnsubscribeFromAllCollections(); + + // if it's a flat list, add the items without grouping. + if (IsFlatList) + { + if (ItemsSource != null) + { + foreach (object item in ItemsSource) + { + tuples.Add(new LongListSelectorItem() { Item = item, ItemType = LongListSelectorItemType.Item }); + } + } + } + // Otherwise, apply the grouping logic. + else + { + IEnumerable groups = ItemsSource; + if (groups != null) + { + foreach (object group in groups) + { + AddGroup(group, tuples); + } + } + } + + + AddListFooter(tuples); + + _rootCollection = ItemsSource as INotifyCollectionChanged; + if (_rootCollection != null) + { + _rootCollection.CollectionChanged += OnCollectionChanged; + } + + _listBox.ItemsSource = tuples; + } + } + #endregion + + #region List/Footer Headers + /// + /// Adds a list header to the given list. + /// + private void AddListHeader(IList tuples) + { + if (HasListHeader && ShowListHeader && // Adds the list header if it got a template or if it's a UI element itself. + (tuples.Count == 0 || tuples[0].ItemType != LongListSelectorItemType.ListHeader)) // Also, make sure its not already there + { + tuples.Insert(0, new LongListSelectorItem() { Item = ListHeader, ItemType = LongListSelectorItemType.ListHeader }); + } + } + /// + /// Adds a list header to the listbox. + /// + private void AddListHeader() + { + AddListHeader((ObservableCollection)_listBox.ItemsSource); + } + + /// + /// Removes the list header from the given list. + /// + private static void RemoveListHeader(IList tuples) + { + if (tuples.Count > 0 && tuples[0].ItemType == LongListSelectorItemType.ListHeader) + { + tuples.RemoveAt(0); + } + } + + /// + /// Removes the list header from the listbox. + /// + private void RemoveListHeader() + { + RemoveListHeader((ObservableCollection)_listBox.ItemsSource); + } + + /// + /// Adds a list footer to the given list. + /// + private void AddListFooter(IList tuples) + { + if (HasListFooter && ShowListFooter && // Adds the list footer if it got a template or if it's a UI element itself. + (tuples.Count == 0 || tuples[tuples.Count - 1].ItemType != LongListSelectorItemType.ListFooter)) // Also, make sure its not already there + { + tuples.Add(new LongListSelectorItem() { Item = ListFooter, ItemType = LongListSelectorItemType.ListFooter }); + } + } + + /// + /// Adds a list footer to the listbox. + /// + private void AddListFooter() + { + AddListFooter((ObservableCollection)_listBox.ItemsSource); + } + + /// + /// Removes the list footer from the given list. + /// + private static void RemoveListFooter(IList tuples) + { + LongListSelectorItem lastTuple = tuples[tuples.Count - 1]; + if (lastTuple != null && lastTuple.ItemType == LongListSelectorItemType.ListFooter) + { + tuples.RemoveAt(tuples.Count - 1); + } + } + + /// + /// Removes the list footer from the listbox. + /// + private void RemoveListFooter() + { + RemoveListFooter((ObservableCollection)_listBox.ItemsSource); + } + #endregion + + #region AddGroup(...) + /// + /// Adds a group to a list of tuples. + /// + /// Group to add. + /// List to which the method will add the group. + private void AddGroup(object groupToAdd, IList tuples) + { + IEnumerable group = groupToAdd as IEnumerable; + if (group != null) + { + bool groupHasItems = false; + + // Adds the group header + if (GroupHeaderTemplate != null) + { + tuples.Add(new LongListSelectorItem() { Item = group, ItemType = LongListSelectorItemType.GroupHeader }); + } + + // For each group header, add its items + foreach (object item in group) + { + tuples.Add(new LongListSelectorItem() { Item = item, ItemType = LongListSelectorItemType.Item, Group = group }); + groupHasItems = true; + } + + // Add the group footer if the group has items or if we must display all groups whether or not they have items. + if (groupHasItems || DisplayAllGroups) + { + if (GroupFooterTemplate != null) + { + tuples.Add(new LongListSelectorItem() { Item = group, ItemType = LongListSelectorItemType.GroupFooter }); + } + } + // Otherwise, remove the group header + else if (GroupHeaderTemplate != null) + { + tuples.RemoveAt(tuples.Count - 1); + } + + // Subscribe to collection change event + INotifyCollectionChanged groupCollection = group as INotifyCollectionChanged; + if (groupCollection != null && !_groupCollections.Contains(groupCollection)) + { + groupCollection.CollectionChanged += OnCollectionChanged; + _groupCollections.Add(groupCollection); + } + + } + } + #endregion + + #region AddGroupHeadersAndFooters(...) + /// + /// Adds group headers or footers after their template switch from being null + /// to an actual value. + /// + /// Specifies whether or not to add group headers. + /// Specifies whether or not to add group footers. + /// Used only when templates for group headers/footers switch from being null. + /// In this case, they must be added to the lisbox if a group is not empty or DisplayAllGroups is true. + /// For performance reasons, this method makes an assumption that headers/footers are not already present. + /// Which is the case when its called from OnDataTemplateChanged. + private void AddGroupHeadersAndFooters(bool addHeaders, bool addFooters) + { + int indexInListBox = 0; + + if (HasListHeader && ShowListHeader) + { + ++indexInListBox; + } + + IEnumerable groups = ItemsSource; + ObservableCollection tuples = (ObservableCollection)_listBox.ItemsSource; + + if (groups != null) + { + foreach (object group in groups) + { + var groupAsEnumerable = group as IEnumerable; + if (groupAsEnumerable != null) + { + int itemsCount = GetHeadersAndItemsCountFromGroup(groupAsEnumerable); + + // Adds the group header + if (addHeaders && GroupHeaderTemplate != null && itemsCount > 0) + { + tuples.Insert(indexInListBox, new LongListSelectorItem + { + Item = group, + ItemType = LongListSelectorItemType.GroupHeader + }); + } + + indexInListBox += itemsCount; + + // Adds the group footer + if (addFooters && GroupFooterTemplate != null && itemsCount > 0) + { + tuples.Insert(indexInListBox - 1, new LongListSelectorItem + { + Item = group, + ItemType = LongListSelectorItemType.GroupFooter + }); + } + } + } + } + } + + /// + /// Adds group headers after the GroupHeaderTeamplate switch from being null + /// to an actual value. + /// + private void AddGroupHeaders() + { + AddGroupHeadersAndFooters(true, false); + } + + /// + /// Adds group headers after the GroupFooterTeamplate switch from being null + /// to an actual value. + /// + private void AddGroupFooters() + { + AddGroupHeadersAndFooters(false, true); + } + #endregion + + #region RemoveAllGroupHeadersAndFooters(...) + /// + /// Removes group headers or footers after their template becomes null. + /// + /// Specifies whether or not to remove group headers. + /// Specifies whether or not to remove group footers. + private void RemoveAllGroupHeadersAndFooters(bool removeHeaders, bool removeFooters) + { + ObservableCollection tuples = (ObservableCollection)_listBox.ItemsSource; + for (int i = 0; i < tuples.Count; ++i) + { + if ((removeHeaders && tuples[i].ItemType == LongListSelectorItemType.GroupHeader) || + (removeFooters && tuples[i].ItemType == LongListSelectorItemType.GroupFooter)) + { + tuples.RemoveAt(i--); // the -- is there so we don't skip tuples + } + } + } + private void RemoveAllGroupHeaders() + { + RemoveAllGroupHeadersAndFooters(true, false); + } + private void RemoveAllGroupFooters() + { + RemoveAllGroupHeadersAndFooters(false, true); + } + #endregion + + #region UnsubscribeFromAllCollections() + /// + /// Unsubscrives from every collection in ItemsSource. + /// + private void UnsubscribeFromAllCollections() + { + if (_rootCollection != null) + { + _rootCollection.CollectionChanged -= OnCollectionChanged; + } + + foreach (INotifyCollectionChanged collection in _groupCollections) + { + collection.CollectionChanged -= OnCollectionChanged; + } + + _rootCollection = null; + _groupCollections.Clear(); + } + #endregion + + #region InsertNewGroups(...) + /// + /// Inserts new groups in the list box. + /// + /// List of the new groups to insert. + /// Insertion index relative to the collection. + private void InsertNewGroups(IList newGroups, int newGroupsIndex) + { + ObservableCollection tuples = (ObservableCollection)_listBox.ItemsSource; + + // 1 - Builds items tuples for the new groups + List newGroupsTuples = new List(); + + foreach (object group in newGroups) + { + AddGroup(group, newGroupsTuples); + } + + if (newGroupsTuples.Count > 0) + { + // 2 - Finds insertion index in the list box + int insertIndex = GetGroupIndexInListBox(newGroupsIndex); + + // 3 - Inserts the new items into the list box + foreach (LongListSelectorItem tuple in newGroupsTuples) + { + tuples.Insert(insertIndex++, tuple); + } + } + } + #endregion + + #region InsertNewItems(...) + /// + /// Inserts new items in the list box. + /// + /// List of new items to insert. + /// Insertion index relative to the collection + /// Group into which the items are inserted. Can be null if IsFlatList == true + private void InsertNewItems(IList newItems, int newItemsIndex, IEnumerable group) + { + ObservableCollection tuples = (ObservableCollection)_listBox.ItemsSource; + + // 1 - Builds items tuples for the new items + List newItemsTuples = new List(); + foreach (object item in newItems) + { + newItemsTuples.Add(new LongListSelectorItem + { + Group = group, + Item = item, + ItemType = LongListSelectorItemType.Item + }); + } + + // 2 - Finds the insertion index in the listbox + // Since a single group might be referenced by more than one, we might need to update more than one spot in the listbox + if (group != null) + { + int i = 0; + bool groupWasNotDisplayed = ((IList)group).Count == newItems.Count && !DisplayAllGroups; + + foreach (object g in ItemsSource) + { + if (g == group) + { + int insertIndex = GetGroupIndexInListBox(i); + + if (GroupHeaderTemplate != null) + { + if (groupWasNotDisplayed) + { + tuples.Insert(insertIndex, new LongListSelectorItem() { ItemType = LongListSelectorItemType.GroupHeader, Item = group }); + } + ++insertIndex; + } + + insertIndex += newItemsIndex; + + // 3 - Inserts the new items into the list box + foreach (LongListSelectorItem tuple in newItemsTuples) + { + tuples.Insert(insertIndex++, tuple); + } + + if (groupWasNotDisplayed && GroupFooterTemplate != null) + { + tuples.Insert(insertIndex++, new LongListSelectorItem() { ItemType = LongListSelectorItemType.GroupFooter, Item = group }); + } + } + ++i; + } + } + else + { + int insertIndex = newItemsIndex; + if (HasListHeader && ShowListHeader) + { + ++insertIndex; + } + + // 3 - Inserts the new items into the list box + foreach (LongListSelectorItem tuple in newItemsTuples) + { + tuples.Insert(insertIndex++, tuple); + } + } + } + #endregion + + #region RemoveOldGroups(...) + /// + /// Removes groups from the list box. + /// + /// List of groups to remove. + /// Start index relative to the root collection. + private void RemoveOldGroups(IList oldGroups, int oldGroupsIndex) + { + ObservableCollection tuples = (ObservableCollection)_listBox.ItemsSource; + + // 1 - Find the index at which we start removing groups + int removeStartIndex = 0; + if (oldGroupsIndex > 0) + { + removeStartIndex = GetGroupIndexInListBox(oldGroupsIndex - 1); + IEnumerable group = ((IList)ItemsSource)[oldGroupsIndex - 1] as IEnumerable; + if (group != null) + { + removeStartIndex += GetHeadersAndItemsCountFromGroup(group); + } + } + else + { + if (HasListHeader && ShowListHeader) + { + ++removeStartIndex; + } + } + + // 2 - Calculates how many items to delete from the ListBox + int itemsToRemoveCount = GetItemsCountFromGroups(oldGroups); + + // 3 - Removes the old items from the ListBox + for (int i = 0; i < itemsToRemoveCount; ++i) + { + tuples.RemoveAt(removeStartIndex); + } + + // 4 - Unsubscribe from the old groups + foreach (INotifyCollectionChanged collection in oldGroups) + { + collection.CollectionChanged -= OnCollectionChanged; + } + + } + #endregion + + #region RemoveOldItems(...) + /// + /// Removes old items from a group or from the root collection. + /// + /// List of items to remove. + /// Start index relative to the group or root collection. + /// Group from which items are removed. Can be null if IsFlatList == true. + private void RemoveOldItems(IList oldItems, int oldItemsIndex, IEnumerable group) + { + ObservableCollection tuples = (ObservableCollection)_listBox.ItemsSource; + + // 1 - Finds the remove index in the listbox + // Since a single group might be referenced by more than one, we might need to update more than one group + if (group != null) + { + int i = 0; + + foreach (object g in ItemsSource) + { + if (g == group) + { + int removeIndex = GetGroupIndexInListBox(i); + removeIndex += oldItemsIndex; + + if (GroupHeaderTemplate != null) + { + ++removeIndex; + } + + // 2 - Removes the old items + for (int j = 0; j < oldItems.Count; ++j) + { + tuples.RemoveAt(removeIndex); + } + + // 3 - Hides the group header and footer if it's empty and DisplayAllGroups is false + if (((IList)group).Count == 0 && !DisplayAllGroups) + { + if (GroupFooterTemplate != null) + { + tuples.RemoveAt(removeIndex); // Removes the group footer + } + if (GroupHeaderTemplate != null) + { + tuples.RemoveAt(removeIndex - 1); // Removes the group header + } + } + } + ++i; + } + } + else + { + int removeIndex = oldItemsIndex; + if (HasListHeader && ShowListHeader) + { + ++removeIndex; + } + for(int i = 0; i < oldItems.Count; ++i) + tuples.RemoveAt(removeIndex); + } + } + #endregion + + #region GetGroupIndexInListBox(...) + /// + /// Returns, for a group, an index relative to the templated list box from an index relative to the root collection. + /// + /// Index relative to the root collection. + /// Returns, for a group, an index relative to the templated list box from an index relative to the root collection. + private int GetGroupIndexInListBox(int indexInLLS) + { + int indexInListBox = 0, index = 0; + + if (HasListHeader && ShowListHeader) + { + ++indexInListBox; + } + + IEnumerable groups = ItemsSource; + + if (groups != null) + { + foreach (object group in groups) + { + if (indexInLLS == index) + { + break; + } + + ++index; + + var groupAsEnumerable = group as IEnumerable; + if (groupAsEnumerable != null) + { + indexInListBox += GetHeadersAndItemsCountFromGroup(groupAsEnumerable); + } + } + } + + return indexInListBox; + } + #endregion + + #region GetItemsCountFromGroups(...) + /// + /// Returns the number of items in a list of groups. + /// + /// List of groups from which to retrieve the items count. + /// Returns the number of items in a list of groups. + private int GetItemsCountFromGroups(IEnumerable groups) + { + int count = 0; + + foreach (object g in groups) + { + IEnumerable group = g as IEnumerable; + if (group != null) + { + count += GetHeadersAndItemsCountFromGroup(group); + } + } + + return count; + } + #endregion + + #region GetItemsCountFromGroup(...) + /// + /// Returns the number of items in a group including the group header. + /// + /// Group from which to retrieve the items count. + /// Returns the number of items in a group including the group header. + private int GetHeadersAndItemsCountFromGroup(IEnumerable group) + { + int count = 0; + + IList groupAsList = group as IList; + if (groupAsList != null) + { + count += groupAsList.Count; + } + else + { + count += group.Cast().Count(); + } + + bool groupHasItems = count > 0; + + if ((groupHasItems || DisplayAllGroups) && GroupHeaderTemplate != null) + { + ++count; + } + + if ((groupHasItems || DisplayAllGroups) && GroupFooterTemplate != null) + { + ++count; + } + + return count; + } + #endregion + + #region UpdateListBoxItemsTemplate(...) + /// + /// Updates the templates for a given item type in the list box. + /// + /// Item type for which to update the template. + /// New template that will replace the old one. + private void UpdateItemsTemplate(LongListSelectorItemType itemType, DataTemplate newTemplate) + { + if (_listBox != null) + { + // Update template for items that have been linked (realized) + IEnumerable items = _listBox.GetLogicalChildrenByType(false); + foreach (TemplatedListBoxItem item in items) + { + LongListSelectorItem tuple = (LongListSelectorItem)item.Tuple; + if (tuple.ItemType == itemType) + { + item.ContentTemplate = newTemplate; + } + } + + // Save the new template so they can be applied in the future when new items + // are linked (realized) + switch (itemType) + { + case LongListSelectorItemType.ListHeader: + _listBox.ListHeaderTemplate = newTemplate; + break; + case LongListSelectorItemType.ListFooter: + _listBox.ListFooterTemplate = newTemplate; + break; + case LongListSelectorItemType.GroupHeader: + _listBox.GroupHeaderTemplate = newTemplate; + break; + case LongListSelectorItemType.GroupFooter: + _listBox.GroupFooterTemplate = newTemplate; + break; + case LongListSelectorItemType.Item: + _listBox.ItemTemplate = newTemplate; + break; + } + } + } + #endregion + + #region OnDataTemplateChanged(...) + /// + /// Called when a data template has changed. + /// + private static void OnDataTemplateChanged(DependencyObject o, DependencyPropertyChangedEventArgs e) + { + LongListSelector lls = (LongListSelector)o; + if (lls._listBox == null) + return; + + DataTemplate newTemplate = (DataTemplate)e.NewValue; + + if (e.Property == ListHeaderTemplateProperty) + { + lls.UpdateItemsTemplate(LongListSelectorItemType.ListHeader, newTemplate); + + // If the old value was null, we might need to add the list header. + if (e.OldValue == null) + { + lls.AddListHeader(); // Will add a list header if it's not already there. + } + // If we don't have a list header anymore, then remove it from the listbox + else if (e.NewValue == null && !lls.HasListHeader) + { + lls.RemoveListHeader(); + } + + } + else if (e.Property == ListFooterTemplateProperty) + { + lls.UpdateItemsTemplate(LongListSelectorItemType.ListFooter, newTemplate); + + // If the old value was null, we might need to add the list footer. + if (e.OldValue == null) + { + lls.AddListFooter(); // Will add a list footer if it's not already there. + } + // If we don't have a list footer anymore, then remove it from the listbox + else if (e.NewValue == null && !lls.HasListHeader) + { + lls.RemoveListFooter(); + } + + } + else if (e.Property == GroupHeaderProperty) + { + lls.UpdateItemsTemplate(LongListSelectorItemType.GroupHeader, newTemplate); + + // If the old value was null, this means we might need to add group headers to the listbox + if (e.OldValue == null) + { + lls.AddGroupHeaders(); + } + // If the new value is null, this means we might need to remove group headers from the listbox + else if (e.NewValue == null) + { + lls.RemoveAllGroupHeaders(); + } + + } + else if(e.Property == GroupFooterProperty) + { + lls.UpdateItemsTemplate(LongListSelectorItemType.GroupFooter, newTemplate); + + // If the old value was null, this means we might need to add group footers to the listbox + if (e.OldValue == null) + { + lls.AddGroupFooters(); + } + // If the new value is null, this means we might need to remove group footers from the listbox + else if (e.NewValue == null) + { + lls.RemoveAllGroupFooters(); + } + + } + else if (e.Property == ItemsTemplateProperty) + { + lls.UpdateItemsTemplate(LongListSelectorItemType.Item, newTemplate); + } + } + #endregion + + #region OnDisplayAllGroupsChanged(...) + /// + /// Called when the DisplayAllGroups dependency property changes + /// + private static void OnDisplayAllGroupsChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + ((LongListSelector)obj).LoadDataIntoListBox(); + } + #endregion + + #region OnSelectionChanged(...) + /// + /// Called when there is a change in the selected item(s) in the listbox. + /// + private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + // Group navigation + //var group = (from t in (IEnumerable)e.AddedItems where ((ItemTuple)t).ItemType == ItemType.GroupHeader select (ItemTuple)t).FirstOrDefault(); + LongListSelectorItem group = null; + foreach (LongListSelectorItem tuple in e.AddedItems) + { + if (tuple.ItemType == LongListSelectorItemType.GroupHeader) + { + group = tuple; + break; + } + } + + if (group != null) + { + SelectedItem = null; + DisplayGroupView(); + } + else + { + var handler = SelectionChanged; + + if (handler != null) + { + List addedItems = new List(); + List removedItems = new List(); + + foreach (LongListSelectorItem tuple in e.AddedItems) + { + if (tuple.ItemType == LongListSelectorItemType.Item) + { + addedItems.Add(tuple); + } + } + + foreach (LongListSelectorItem tuple in e.RemovedItems) + { + if (tuple.ItemType == LongListSelectorItemType.Item) + { + removedItems.Add(tuple); + } + } + + handler(this, new SelectionChangedEventArgs(removedItems, addedItems)); + } + } + + } + #endregion + + #region OnCollectionChanged(...) + /// + /// Called when there is a change in the root or a group collection. + /// + private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e) + { + var senderAsEnumerable = sender as IEnumerable; + + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + if (sender == _rootCollection) + { + if (IsFlatList) + { + InsertNewItems(e.NewItems, e.NewStartingIndex, null); + } + else + { + InsertNewGroups(e.NewItems, e.NewStartingIndex); + } + } + else + { + InsertNewItems(e.NewItems, e.NewStartingIndex, senderAsEnumerable); + } + break; + + case NotifyCollectionChangedAction.Remove: + if (sender == _rootCollection) + { + if (IsFlatList) + { + RemoveOldItems(e.OldItems, e.OldStartingIndex, null); + } + else + { + RemoveOldGroups(e.OldItems, e.OldStartingIndex); + } + } + else + { + RemoveOldItems(e.OldItems, e.OldStartingIndex, senderAsEnumerable); + } + break; + + case NotifyCollectionChangedAction.Replace: + case NotifyCollectionChangedAction.Reset: + LoadDataIntoListBox(); + break; + } + } + #endregion + + #region OnScrollStateChanging(...) + /// + /// Called when the scrolling state of the list box changes. + /// + private void OnScrollStateChanging(object sender, VisualStateChangedEventArgs e) + { + IsScrolling = e.NewState.Name == ScrollingState; + + if (e.NewState.Name == ScrollingState && ScrollingStarted != null) + { + ScrollingStarted(this, null); + } + else if (e.NewState.Name == NotScrollingState && ScrollingCompleted != null) + { + ScrollingCompleted(this, null); + } + } + #endregion + + #region OnScrollStateChanging(...) + /// + /// Called when the scrolling state of the list box changes. + /// + private void OnStretchStateChanging(object sender, VisualStateChangedEventArgs e) + { + IsStretching = e.NewState.Name == CompressionBottom || e.NewState.Name == CompressionTop; + + if (e.NewState.Name == CompressionTop && StretchingTop != null) + { + StretchingTop(this, null); + } + else if (e.NewState.Name == CompressionBottom && StretchingBottom != null) + { + StretchingBottom(this, null); + } + else if (e.NewState.Name == NoVerticalCompression && StretchingCompleted != null) + { + StretchingCompleted(this, null); + } + } + #endregion + + #region OnLink(...) + /// + /// Called when an item gets realized. + /// + void OnLink(object sender, LinkUnlinkEventArgs e) + { + if (Link != null) + { + Link(this, e); + } + } + #endregion + + #region OnUnlink(...) + /// + /// Called when an item gets unrealized. + /// + void OnUnlink(object sender, LinkUnlinkEventArgs e) + { + if (Unlink != null) + { + Unlink(this, e); + } + } + #endregion + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorEventArgs.cs b/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorEventArgs.cs new file mode 100644 index 0000000..2827f4d --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorEventArgs.cs @@ -0,0 +1,68 @@ +using System; +using System.Windows.Controls; + +namespace Microsoft.Phone.Controls +{ + /// + /// The event args for the Link/Unlink events. + /// + public class LinkUnlinkEventArgs : EventArgs + { + /// + /// Create new LinkUnlinkEventArgs. + /// + /// The ContentPresenter. + public LinkUnlinkEventArgs(ContentPresenter cp) + { + ContentPresenter = cp; + } + + /// + /// The ContentPresenter which is displaying the item. + /// + public ContentPresenter ContentPresenter { get; private set; } + } + + /// + /// The GroupPopupOpened event args. + /// + public class GroupViewOpenedEventArgs : EventArgs + { + internal GroupViewOpenedEventArgs(ItemsControl itemsControl) + { + ItemsControl = itemsControl; + } + + /// + /// The ItemsControl containing the groups. + /// + public ItemsControl ItemsControl { get; private set; } + } + + /// + /// The GroupPopupClosing event args. + /// + public class GroupViewClosingEventArgs : EventArgs + { + internal GroupViewClosingEventArgs(ItemsControl itemsControl, object selectedGroup) + { + ItemsControl = itemsControl; + SelectedGroup = selectedGroup; + } + + /// + /// The ItemsControl containing the groups. + /// + public ItemsControl ItemsControl { get; private set; } + + /// + /// The selected group. Will be null if the back button was pressed. + /// + public object SelectedGroup { get; private set; } + + /// + /// Set this to true if the application will handle the popup closing and scrolling to the group. + /// + public bool Cancel { get; set; } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorGroup.cs b/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorGroup.cs new file mode 100644 index 0000000..7a9985b --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorGroup.cs @@ -0,0 +1,262 @@ +// (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.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Media; +using Microsoft.Phone.Shell; + +namespace Microsoft.Phone.Controls +{ + /// + /// Partial definition of LongListSelector. Includes group view code. + /// + public partial class LongListSelector : Control + { + private PhoneApplicationPage _page; + + private bool _systemTrayVisible; + private bool _applicationBarVisible; + private Border _border; + private LongListSelectorItemsControl _itemsControl; + private Popup _groupSelectorPopup; + + private static readonly double _screenWidth = Application.Current.Host.Content.ActualWidth; + private static readonly double _screenHeight = Application.Current.Host.Content.ActualHeight; + + private void OpenPopup() + { + SaveSystemState(false, false); + BuildPopup(); + AttachToPageEvents(); + _groupSelectorPopup.IsOpen = true; + + // This has to happen eventually anyway, and this forces the ItemsControl to + // expand it's template, populate it's items etc. + UpdateLayout(); + } + + private void popup_Opened(object sender, EventArgs e) + { + SafeRaise.Raise(GroupViewOpened, this, () => { return new GroupViewOpenedEventArgs(_itemsControl); }); + } + + /// + /// Closes the group popup. + /// + /// The selected group. + /// Should the GroupPopupClosing event be raised. + /// True if the event was not raised or if it was raised and e.Handled is false. + private bool ClosePopup(object selectedGroup, bool raiseEvent) + { + if (raiseEvent) + { + GroupViewClosingEventArgs args = null; + + SafeRaise.Raise(GroupViewClosing, this, () => { return args = new GroupViewClosingEventArgs(_itemsControl, selectedGroup); }); + + if (args != null && args.Cancel) + { + return false; + } + } + + if (_groupSelectorPopup != null) + { + RestoreSystemState(); + _groupSelectorPopup.IsOpen = false; + DetachFromPageEvents(); + _groupSelectorPopup.Child = null; + _border = null; + _itemsControl = null; + _groupSelectorPopup = null; + } + + return true; + } + + private void BuildPopup() + { + _groupSelectorPopup = new Popup(); + _groupSelectorPopup.Opened += popup_Opened; + + // Support the background color jumping through. Note that the + // alpha channel will be ignored, unless it is a purely transparent + // value (such as when a user uses Transparent as the background + // on the control). + SolidColorBrush background = Background as SolidColorBrush; + Color bg = (Color)Resources["PhoneBackgroundColor"]; + if (background != null + && background.Color != null + && background.Color.A > 0) + { + bg = background.Color; + } + _border = new Border + { + Background = new SolidColorBrush( + Color.FromArgb(0xa0, bg.R, bg.G, bg.B)) + }; + + // Prevents touch events from bubbling up for most handlers. + _border.ManipulationStarted += ((o, e) => e.Handled = true); + _border.ManipulationCompleted += ((o, e) => e.Handled = true); + _border.ManipulationDelta += ((o, e) => e.Handled = true); + + var gestureHandler = new EventHandler((o, e) => e.Handled = true); + _border.Tap += gestureHandler; + _border.DoubleTap += gestureHandler; + _border.Hold += gestureHandler; + + _itemsControl = new LongListSelectorItemsControl(); + _itemsControl.ItemTemplate = GroupItemTemplate; + _itemsControl.ItemsPanel = GroupItemsPanel; + _itemsControl.ItemsSource = ItemsSource; + + _itemsControl.GroupSelected += itemsControl_GroupSelected; + + _groupSelectorPopup.Child = _border; + ScrollViewer sv = new ScrollViewer() { HorizontalScrollBarVisibility = ScrollBarVisibility.Disabled }; + + _itemsControl.HorizontalAlignment = HorizontalAlignment.Center; + _itemsControl.Margin = new Thickness(0, 12, 0, 0); + _border.Child = sv; + sv.Content = _itemsControl; + + SetItemsControlSize(); + } + + private void SetItemsControlSize() + { + Rect client = GetTransformedRect(); + if (_border != null) + { + _border.RenderTransform = GetTransform(); + + _border.Width = client.Width; + _border.Height = client.Height; + } + } + + private void itemsControl_GroupSelected(object sender, LongListSelector.GroupSelectedEventArgs e) + { + if (ClosePopup(e.Group, true)) + { + ScrollToGroup(e.Group); + } + } + + private void AttachToPageEvents() + { + PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame; + if (frame != null) + { + _page = frame.Content as PhoneApplicationPage; + if (_page != null) + { + _page.BackKeyPress += page_BackKeyPress; + _page.OrientationChanged += page_OrientationChanged; + } + } + } + + private void DetachFromPageEvents() + { + if (_page != null) + { + _page.BackKeyPress -= page_BackKeyPress; + _page.OrientationChanged -= page_OrientationChanged; + _page = null; + } + } + + private void page_BackKeyPress(object sender, System.ComponentModel.CancelEventArgs e) + { + e.Cancel = true; + ClosePopup(null, true); + } + + void page_OrientationChanged(object sender, OrientationChangedEventArgs e) + { + SetItemsControlSize(); + } + + private static Rect GetTransformedRect() + { + bool isLandscape = IsLandscape(GetPageOrientation()); + + return new Rect(0, 0, + isLandscape ? _screenHeight : _screenWidth, + isLandscape ? _screenWidth : _screenHeight); + } + + private static Transform GetTransform() + { + PageOrientation orientation = GetPageOrientation(); + + switch (orientation) + { + case PageOrientation.LandscapeLeft: + case PageOrientation.Landscape: + return new CompositeTransform() { Rotation = 90, TranslateX = _screenWidth }; + case PageOrientation.LandscapeRight: + return new CompositeTransform() { Rotation = -90, TranslateY = _screenHeight }; + default: + return null; + } + } + + private static bool IsLandscape(PageOrientation orientation) + { + return orientation == PageOrientation.Landscape || orientation == PageOrientation.LandscapeLeft || orientation == PageOrientation.LandscapeRight; + } + + private static PageOrientation GetPageOrientation() + { + PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame; + if (frame != null) + { + PhoneApplicationPage page = frame.Content as PhoneApplicationPage; + return page.Orientation; + } + + return PageOrientation.None; + } + + private void SaveSystemState(bool newSystemTrayVisible, bool newApplicationBarVisible) + { + _systemTrayVisible = SystemTray.IsVisible; + SystemTray.IsVisible = newSystemTrayVisible; + + PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame; + if (frame != null) + { + PhoneApplicationPage page = frame.Content as PhoneApplicationPage; + if (page != null && page.ApplicationBar != null) + { + _applicationBarVisible = page.ApplicationBar.IsVisible; + page.ApplicationBar.IsVisible = newApplicationBarVisible; + } + } + } + + private void RestoreSystemState() + { + SystemTray.IsVisible = _systemTrayVisible; + + PhoneApplicationFrame frame = Application.Current.RootVisual as PhoneApplicationFrame; + if (frame != null) + { + PhoneApplicationPage page = frame.Content as PhoneApplicationPage; + if (page != null && page.ApplicationBar != null) + { + page.ApplicationBar.IsVisible = _applicationBarVisible; + } + } + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorItem.cs b/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorItem.cs new file mode 100644 index 0000000..6143f94 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorItem.cs @@ -0,0 +1,43 @@ +// (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.Diagnostics.CodeAnalysis; + +namespace Microsoft.Phone.Controls +{ + /// + /// Holds information about an item for use in the LongListSelector. + /// + public class LongListSelectorItem + { + /// + /// Gets or sets the item type. + /// + public LongListSelectorItemType ItemType + { + get; + set; + } + + /// + /// Gets or sets the associated group for the item. + /// + [SuppressMessage("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode", Justification = "Assists in debugging.")] + public object Group + { + get; + set; + } + + /// + /// Gets or sets the underlying item instance. + /// + public object Item + { + get; + set; + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorItemType.cs b/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorItemType.cs new file mode 100644 index 0000000..23f686f --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorItemType.cs @@ -0,0 +1,43 @@ +// (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. + +namespace Microsoft.Phone.Controls +{ + /// + /// Describes different items. + /// + public enum LongListSelectorItemType + { + /// + /// Indicates an unknown item type. + /// + Unknown, + + /// + /// Represents a standard list item. + /// + Item, + + /// + /// Represents a group header. + /// + GroupHeader, + + /// + /// Represents a group footer. + /// + GroupFooter, + + /// + /// Represents a list header. + /// + ListHeader, + + /// + /// Represents a list footer. + /// + ListFooter, + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorItemsControl.cs b/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorItemsControl.cs new file mode 100644 index 0000000..639a0d7 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LongListSelector/LongListSelectorItemsControl.cs @@ -0,0 +1,62 @@ +// (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.Windows; +using System.Windows.Controls; + +namespace Microsoft.Phone.Controls +{ + /// + /// Partial definition of LongListSelector. Includes ItemsControl subclass. + /// + public partial class LongListSelector : Control + { + private class GroupSelectedEventArgs : EventArgs + { + public GroupSelectedEventArgs(object group) + { + Group = group; + } + + public object Group { get; private set; } + } + + private delegate void GroupSelectedEventHandler(object sender, GroupSelectedEventArgs e); + + private class LongListSelectorItemsControl : ItemsControl + { + public event GroupSelectedEventHandler GroupSelected; + + protected override void PrepareContainerForItemOverride(DependencyObject element, object item) + { + base.PrepareContainerForItemOverride(element, item); + ((UIElement)element).Tap += LongListSelectorItemsControl_Tap; + } + + protected override void ClearContainerForItemOverride(DependencyObject element, object item) + { + base.ClearContainerForItemOverride(element, item); + + ((UIElement)element).Tap -= LongListSelectorItemsControl_Tap; + } + + private void LongListSelectorItemsControl_Tap(object sender, System.Windows.Input.GestureEventArgs e) + { + ContentPresenter cc = sender as ContentPresenter; + if (cc != null) + { + var handler = GroupSelected; + if (handler != null) + { + GroupSelectedEventArgs args = new GroupSelectedEventArgs(cc.Content); + handler(this, args); + } + + } + } + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/LongListSelector/TemplatedListBox.cs b/Microsoft.Phone.Controls.Toolkit/LongListSelector/TemplatedListBox.cs new file mode 100644 index 0000000..255e709 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LongListSelector/TemplatedListBox.cs @@ -0,0 +1,127 @@ +// (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.Windows; +using System.Windows.Controls; + +namespace Microsoft.Phone.Controls.Primitives +{ + /// + /// Represents a ListBox with item-specific templates. + /// + /// Preview + public class TemplatedListBox : ListBox + { + /// + /// Gets or sets the list header template. + /// + public DataTemplate ListHeaderTemplate { get; set; } + + /// + /// Gets or sets the list footer template. + /// + public DataTemplate ListFooterTemplate { get; set; } + + /// + /// Gets or sets the group header template. + /// + public DataTemplate GroupHeaderTemplate { get; set; } + + /// + /// Gets or sets the footer template. + /// + public DataTemplate GroupFooterTemplate { get; set; } + + /// + /// Occurs when an item is about to be "realized". + /// + public event EventHandler Link; + + /// + /// Occurs when an item is about to be "un-realized". + /// + public event EventHandler Unlink; + + /// + /// Creates or identifies the element used to display a specified item. + /// + /// Returns the new container. + protected override DependencyObject GetContainerForItemOverride() + { + return new TemplatedListBoxItem(); + } + + /// + /// Prepares the specified element to display the specified item. + /// + /// Element used to display the specified item. + /// Specified item. + protected override void PrepareContainerForItemOverride(DependencyObject element, object item) + { + base.PrepareContainerForItemOverride(element, item); + + DataTemplate template = null; + LongListSelectorItem itemTuple = item as LongListSelectorItem; + + if (itemTuple != null) + { + switch (itemTuple.ItemType) + { + case LongListSelectorItemType.ListHeader: + template = this.ListHeaderTemplate; + break; + case LongListSelectorItemType.ListFooter: + template = this.ListFooterTemplate; + break; + case LongListSelectorItemType.GroupHeader: + template = this.GroupHeaderTemplate; + break; + case LongListSelectorItemType.GroupFooter: + template = this.GroupFooterTemplate; + break; + case LongListSelectorItemType.Item: + template = this.ItemTemplate; + break; + } + + TemplatedListBoxItem listBoxItem = (TemplatedListBoxItem)element; + listBoxItem.Content = itemTuple.Item; + listBoxItem.Tuple = itemTuple; + listBoxItem.ContentTemplate = template; + + var result = listBoxItem.GetFirstLogicalChildByType(true); + var handler = Link; + if (result != null && handler != null) + { + handler(this, new LinkUnlinkEventArgs(result)); + } + } + } + + /// + /// When overridden in a derived class, undoes the effects of the + /// PrepareContainerForItemOverride method. + /// + /// The container element. + /// The item. + protected override void ClearContainerForItemOverride(DependencyObject element, object item) + { + LongListSelectorItem itemTuple = item as LongListSelectorItem; + + if (itemTuple != null) + { + var result = ((FrameworkElement)element).GetFirstLogicalChildByType(true); + var handler = Unlink; + if (result != null && handler != null) + { + handler(this, new LinkUnlinkEventArgs(result)); + } + } + + base.ClearContainerForItemOverride(element, item); + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/LongListSelector/TemplatedListBoxItem.cs b/Microsoft.Phone.Controls.Toolkit/LongListSelector/TemplatedListBoxItem.cs new file mode 100644 index 0000000..9dabc61 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/LongListSelector/TemplatedListBoxItem.cs @@ -0,0 +1,19 @@ +// (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.Windows.Controls; + +namespace Microsoft.Phone.Controls.Primitives +{ + /// + /// Represents an item within a + /// + /// Preview + internal class TemplatedListBoxItem : ListBoxItem + { + public LongListSelectorItem Tuple { get; set; } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/Microsoft.Phone.Controls.Toolkit.csproj b/Microsoft.Phone.Controls.Toolkit/Microsoft.Phone.Controls.Toolkit.csproj new file mode 100644 index 0000000..0991e24 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Microsoft.Phone.Controls.Toolkit.csproj @@ -0,0 +1,549 @@ + + + + Debug + AnyCPU + 10.0.20506 + 2.0 + {0754458A-7AFC-463A-B27D-2F6980522119} + {C089C8C0-30E0-4E22-80C0-CE093F111A43};{fae04ec0-301f-11d3-bf4b-00c04f79efbc} + Library + Properties + Microsoft.Phone.Controls + Microsoft.Phone.Controls.Toolkit + v4.0 + $(TargetFrameworkVersion) + WindowsPhone71 + Silverlight + false + true + true + True + ..\PhoneToolkit.snk + + + + + + + + + + + true + full + false + ..\Bin\Debug + DEBUG;TRACE;SILVERLIGHT;WINDOWS_PHONE + true + true + prompt + 4 + true + AllRules.ruleset + ..\Bin\Debug\Microsoft.Phone.Controls.Toolkit.XML + + + pdbonly + true + ..\Bin\Release + TRACE;SILVERLIGHT;WINDOWS_PHONE + true + true + prompt + 4 + ..\Bin\Release\Microsoft.Phone.Controls.Toolkit.XML + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + DatePickerPage.xaml + + + + + + + + + + + TimePickerPage.xaml + + + + + + + + + + + + + + + + + ListPickerPage.xaml + + + True + True + ControlResources.cs.resx + + + True + True + ControlResources.da.resx + + + True + True + ControlResources.de-DE.resx + + + True + True + ControlResources.resx + + + True + True + ControlResources.el.resx + + + True + True + ControlResources.en-GB.resx + + + True + True + ControlResources.es-ES.resx + + + True + True + ControlResources.fi.resx + + + True + True + ControlResources.fr-FR.resx + + + True + True + ControlResources.hu.resx + + + True + True + ControlResources.it-IT.resx + + + True + True + ControlResources.ja.resx + + + True + True + ControlResources.ko.resx + + + True + True + ControlResources.nb-NO.resx + + + True + True + ControlResources.nl-NL.resx + + + True + True + ControlResources.pl.resx + + + True + True + ControlResources.pt-BR.resx + + + True + True + ControlResources.pt-PT.resx + + + True + True + ControlResources.ru.resx + + + True + True + ControlResources.sv-SE.resx + + + True + True + ControlResources.zh-CN.resx + + + True + True + ControlResources.zh-TW.resx + + + + + + + + + + + + + + + + + + + + + + + + + + True + True + Resources.resx + + + + + + + + + + + + + + + + + + + + + + + + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + MSBuild:Compile + Designer + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + Designer + MSBuild:Compile + + + + + PublicResXFileCodeGenerator + ControlResources.cs.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.da.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.de-DE.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.el.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.en-GB.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.es-ES.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.fi.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.fr-FR.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.hu.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.it-IT.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.ja.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.ko.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.nb-NO.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.nl-NL.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.pl.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.pt-BR.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.pt-PT.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.ru.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.sv-SE.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.zh-CN.Designer.cs + + + PublicResXFileCodeGenerator + ControlResources.zh-TW.Designer.cs + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + + Designer + MSBuild:Compile + + + + + Designer + MSBuild:Compile + + + + + Designer + MSBuild:Compile + + + + + Designer + MSBuild:Compile + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/MultiselectList/MultiselectItem.cs b/Microsoft.Phone.Controls.Toolkit/MultiselectList/MultiselectItem.cs new file mode 100644 index 0000000..d1e8669 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/MultiselectList/MultiselectItem.cs @@ -0,0 +1,639 @@ +// (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.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Data; +using System.Windows.Input; +using System.Windows.Shapes; + +namespace Microsoft.Phone.Controls +{ + /// + /// An item container for a Multiselect List. + /// + /// Experimental + [TemplateVisualState(Name = Closed, GroupName = SelectionEnabledStates)] + [TemplateVisualState(Name = Exposed, GroupName = SelectionEnabledStates)] + [TemplateVisualState(Name = Opened, GroupName = SelectionEnabledStates)] + [TemplatePart(Name = OutterHintPanel, Type = typeof(Rectangle))] + [TemplatePart(Name = InnerHintPanel, Type = typeof(Rectangle))] + [TemplatePart(Name = OutterCover, Type = typeof(Grid))] + [TemplatePart(Name = InfoPresenter, Type = typeof(ContentControl))] + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + public class MultiselectItem : ContentControl + { + /// + /// Selection mode visual states. + /// + private const string SelectionEnabledStates = "SelectionEnabledStates"; + + /// + /// Closed visual state. + /// + private const string Closed = "Closed"; + + /// + /// Exposed visual state. + /// + private const string Exposed = "Exposed"; + + /// + /// Opened visual state. + /// + private const string Opened = "Opened"; + + /// + /// Select Box template part name. + /// + private const string SelectBox = "SelectBox"; + + /// + /// Outter Hint Panel template part name. + /// + private const string OutterHintPanel = "OutterHintPanel"; + + /// + /// Inner Hint Panel template part name. + /// + private const string InnerHintPanel = "InnerHintPanel"; + + /// + /// Outter Cover template part name. + /// + private const string OutterCover = "OutterCover"; + + /// + /// Item Info Presenter template part name. + /// + private const string InfoPresenter = "InfoPresenter"; + + /// + /// Limit for the manipulation delta in the X-axis. + /// + private const double _deltaLimitX = 0.0; + + /// + /// Limit for the manipulation delta in the Y-axis. + /// + private const double _deltaLimitY = 0.4; + + /// + /// Outter Hint Panel template part. + /// + private Rectangle _outterHintPanel; + + /// + /// Inner Hint Panel template part. + /// + private Rectangle _innerHintPanel; + + /// + /// Outter Cover template part. + /// + private Grid _outterCover; + + /// + /// Item Info Presenter template part. + /// + private ContentControl _infoPresenter; + + /// + /// Multiselect List that owns this Multiselect Item. + /// + private MultiselectList _parent; + + /// + /// Manipulation delta in the x-axis. + /// + private double _manipulationDeltaX; + + /// + /// Manipulation delta in the y-axis. + /// + private double _manipulationDeltaY; + + /// + /// Indicates that this Multiselect Item is a container + /// being reused for virtualization. + /// + internal bool _isBeingVirtualized; + + /// + /// Flag that is used to prevent multiple selection changed + /// events from being fired when all the items in the list are + /// unselected. Instead, a single event is fired. + /// + internal bool _canTriggerSelectionChanged = true; + + /// + /// Occurs when the multiselect item is selected. + /// + public event RoutedEventHandler Selected; + + /// + /// Occurs when the multiselect item is unselected. + /// + public event RoutedEventHandler Unselected; + + #region IsSelected DependencyProperty + + /// + /// Gets or sets the flag that indicates if the item + /// is currently selected. + /// + public bool IsSelected + { + get { return (bool)GetValue(IsSelectedProperty); } + set { SetValue(IsSelectedProperty, value); } + } + + /// + /// Identifies the IsSelected dependency property. + /// + public static readonly DependencyProperty IsSelectedProperty = + DependencyProperty.Register("IsSelected", typeof(bool), typeof(MultiselectItem), new PropertyMetadata(false, OnIsSelectedPropertyChanged)); + + /// + /// Adds or removes the item to the selected items collection + /// in the Multiselect List that owns it. + /// + /// The dependency object. + /// The event information. + private static void OnIsSelectedPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + MultiselectItem item = (MultiselectItem)obj; + RoutedEventArgs args = new RoutedEventArgs(); + bool isSelected = (bool)e.NewValue; + + if (isSelected) + { + item.OnSelected(args); + } + else + { + item.OnUnselected(args); + } + + //Ignore items being selected/unselected during parent virtualization. + if (item._parent != null && !item._isBeingVirtualized) + { + if (isSelected) + { + item._parent.SelectedItems.Add(item.Content); + + //Trigger a selection changed event for one added item. + if (item._canTriggerSelectionChanged) + { + item._parent.OnSelectionChanged(new object[0], new object[] { item.Content }); + } + } + else + { + item._parent.SelectedItems.Remove(item.Content); + + //Trigger a selection changed event for one removed item. + if (item._canTriggerSelectionChanged) + { + item._parent.OnSelectionChanged(new object[] { item.Content }, new object[0]); + } + } + } + } + + #endregion + + #region State DependencyProperty + + /// + /// Gets or sets the visual state. + /// + internal SelectionEnabledState State + { + get { return (SelectionEnabledState)GetValue(StateProperty); } + set { SetValue(StateProperty, value); } + } + + /// + /// Identifies the State dependency property. + /// + internal static readonly DependencyProperty StateProperty = + DependencyProperty.Register("State", typeof(SelectionEnabledState), typeof(MultiselectItem), new PropertyMetadata(SelectionEnabledState.Closed, null)); + + #endregion + + #region HintPanelHeight DependencyProperty + + /// + /// Gets or sets the height of the hint panel. + /// + public double HintPanelHeight + { + get { return (double)GetValue(HintPanelHeightProperty); } + set { SetValue(HintPanelHeightProperty, value); } + } + + /// + /// Identifies the HintPanelHeight dependency property. + /// + public static readonly DependencyProperty HintPanelHeightProperty = + DependencyProperty.Register("HintPanelHeight", typeof(double), typeof(MultiselectItem), new PropertyMetadata(Double.NaN, null)); + + /// + /// Sets the vertical alignment of the hint panels to stretch if the + /// height is not manually set. If it is, the alignment is set to top. + /// + /// The dependency object. + /// The event information. + private static void OnHintPanelHeightPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + MultiselectItem source = (MultiselectItem)obj; + + if (source._outterHintPanel != null) + { + if (double.IsNaN((double)e.NewValue)) + { + source._outterHintPanel.VerticalAlignment = VerticalAlignment.Stretch; + } + else + { + source._outterHintPanel.VerticalAlignment = VerticalAlignment.Top; + } + } + + if (source._innerHintPanel != null) + { + if (double.IsNaN(source.HintPanelHeight)) + { + source._innerHintPanel.VerticalAlignment = VerticalAlignment.Stretch; + } + else + { + source._innerHintPanel.VerticalAlignment = VerticalAlignment.Top; + } + } + } + + #endregion + + #region ContentInfo DependencyProperty + + /// + /// Gets or sets the content information. + /// + public object ContentInfo + { + get { return (object)GetValue(ContentInfoProperty); } + set { SetValue(ContentInfoProperty, value); } + } + + /// + /// Identifies the ContentInfo dependency property. + /// + public static readonly DependencyProperty ContentInfoProperty = + DependencyProperty.Register("ContentInfo", typeof(object), typeof(MultiselectItem), new PropertyMetadata(null, OnContentInfoPropertyChanged)); + + /// + /// ContentInfoProperty changed handler. + /// + /// The dependency object. + /// The event information. + private static void OnContentInfoPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + MultiselectItem source = (MultiselectItem)obj; + source.OnContentInfoChanged(e.OldValue, e.NewValue); + } + + #endregion + + #region ContentInfoTemplate DependencyProperty + + /// + /// Gets or sets the data template that defines + /// the content information. + /// + public DataTemplate ContentInfoTemplate + { + get { return (DataTemplate)GetValue(ContentInfoTemplateProperty); } + set { SetValue(ContentInfoTemplateProperty, value); } + } + + /// + /// Identifies the ContentInfoTemplate dependency property. + /// + public static readonly DependencyProperty ContentInfoTemplateProperty = + DependencyProperty.Register("ContentInfoTemplate", typeof(DataTemplate), typeof(MultiselectItem), new PropertyMetadata(null, OnContentInfoTemplatePropertyChanged)); + + /// + /// ContentInfoTemplate changed handler. + /// + /// The dependency object. + /// The event information. + private static void OnContentInfoTemplatePropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + MultiselectItem source = (MultiselectItem)obj; + DataTemplate oldTemplate = e.OldValue as DataTemplate; + DataTemplate newTemplate = e.NewValue as DataTemplate; + source.OnContentInfoTemplateChanged(oldTemplate, newTemplate); + } + + #endregion + + /// + /// Gets the template parts and sets event handlers. + /// + public override void OnApplyTemplate() + { + _parent = ItemsControlExtensions.GetParentItemsControl(this); + + if (_innerHintPanel != null) + { + _innerHintPanel.ManipulationStarted -= HintPanel_ManipulationStarted; + _innerHintPanel.ManipulationDelta -= HintPanel_ManipulationDelta; + _innerHintPanel.ManipulationCompleted -= HintPanel_ManipulationCompleted; + } + + if (_outterHintPanel != null) + { + _outterHintPanel.ManipulationStarted -= HintPanel_ManipulationStarted; + _outterHintPanel.ManipulationDelta -= HintPanel_ManipulationDelta; + _outterHintPanel.ManipulationCompleted -= HintPanel_ManipulationCompleted; + } + + if (_outterCover != null) + { + _outterCover.Tap -= Cover_Tap; + } + + _innerHintPanel = base.GetTemplateChild(InnerHintPanel) as Rectangle; + _outterHintPanel = base.GetTemplateChild(OutterHintPanel) as Rectangle; + _outterCover = base.GetTemplateChild(OutterCover) as Grid; + _infoPresenter = base.GetTemplateChild(InfoPresenter) as ContentControl; + + base.OnApplyTemplate(); + + if (_innerHintPanel != null) + { + _innerHintPanel.ManipulationStarted += HintPanel_ManipulationStarted; + _innerHintPanel.ManipulationDelta += HintPanel_ManipulationDelta; + _innerHintPanel.ManipulationCompleted += HintPanel_ManipulationCompleted; + } + + if (_outterHintPanel != null) + { + _outterHintPanel.ManipulationStarted += HintPanel_ManipulationStarted; + _outterHintPanel.ManipulationDelta += HintPanel_ManipulationDelta; + _outterHintPanel.ManipulationCompleted += HintPanel_ManipulationCompleted; + } + + + if (_outterCover != null) + { + _outterCover.Tap += Cover_Tap; + } + + if (ContentInfo == null && _parent != null) + { + if (_parent.ItemInfoTemplate != null) + { + _infoPresenter.ContentTemplate = _parent.ItemInfoTemplate; + Binding infoBinding = new Binding(); + this.SetBinding(ContentInfoProperty, infoBinding); + } + } + + if (_outterHintPanel != null) + { + if (double.IsNaN(HintPanelHeight)) + { + _outterHintPanel.VerticalAlignment = VerticalAlignment.Stretch; + } + else + { + _outterHintPanel.VerticalAlignment = VerticalAlignment.Top; + } + } + + if (_innerHintPanel != null) + { + if (double.IsNaN(HintPanelHeight)) + { + _innerHintPanel.VerticalAlignment = VerticalAlignment.Stretch; + } + else + { + _innerHintPanel.VerticalAlignment = VerticalAlignment.Top; + } + } + + UpdateVisualState(false); + } + + /// + /// Initializes a new instance of the MultiselectItem class. + /// + public MultiselectItem() + { + DefaultStyleKey = typeof(MultiselectItem); + } + + /// + /// Updates the visual state. + /// + /// + /// Indicates whether visual transitions should be used. + /// + internal void UpdateVisualState(bool useTransitions) + { + string state; + + switch (this.State) + { + case SelectionEnabledState.Closed: + state = Closed; + break; + case SelectionEnabledState.Exposed: + state = Exposed; + break; + case SelectionEnabledState.Opened: + state = Opened; + break; + default: + state = Closed; + break; + } + + VisualStateManager.GoToState(this, state, useTransitions); + } + + /// + /// Raises a routed event. + /// + /// Event handler. + /// Event arguments. + private void RaiseEvent(RoutedEventHandler handler, RoutedEventArgs args) + { + if (handler != null) + { + handler(this, args); + } + } + + #region Event overrides + + /// + /// Raises a Selected event when the IsSelected property + /// changes from false to true. + /// + /// The event information. + protected virtual void OnSelected(RoutedEventArgs e) + { + if (_parent == null) + { + State = SelectionEnabledState.Opened; + UpdateVisualState(true); + } + RaiseEvent(Selected, e); + } + + /// + /// Raises an Unselected event when the IsSelected property + /// changes from true to false. + /// + /// The event information. + protected virtual void OnUnselected(RoutedEventArgs e) + { + if (_parent == null) + { + State = SelectionEnabledState.Closed; + UpdateVisualState(true); + } + RaiseEvent(Unselected, e); + } + + /// + /// Called when the value of the ContentInfo property changes. + /// + /// + /// The old value of the ContentInfo property. + /// + /// + /// The new value of the ContentInfo property. + /// + protected virtual void OnContentInfoChanged(object oldContentInfo, object newContentInfo) + { + } + + /// + /// Called when the value of the ContentInfoTemplate property chages. + /// + /// + /// The old value of the ContentInfoTemplate property. + /// + /// + /// The new value of the ContentInfoTemplate property. + /// + protected virtual void OnContentInfoTemplateChanged(DataTemplate oldContentInfoTemplate, DataTemplate newContentInfoTemplate) + { + } + + #endregion + + #region Input events + + /// + /// Triggers a visual transition to the Exposed visual state. + /// + /// The Hint Panel that triggers the event. + /// The event information. + private void HintPanel_ManipulationStarted(object sender, ManipulationStartedEventArgs e) + { + this.State = SelectionEnabledState.Exposed; + UpdateVisualState(true); + } + + /// + /// Triggers a visual transition to the Closed visual state + /// if the manipulation delta goes out of bounds. + /// + /// The Hint Panel that triggers the event. + /// The event information. + private void HintPanel_ManipulationDelta(object sender, ManipulationDeltaEventArgs e) + { + _manipulationDeltaX = e.DeltaManipulation.Translation.X; + _manipulationDeltaY = e.DeltaManipulation.Translation.Y; + + if (_manipulationDeltaX < _deltaLimitX) + { + _manipulationDeltaX *= -1.0; + } + + if (_manipulationDeltaY < _deltaLimitX) + { + _manipulationDeltaY *= -1.0; + } + + if (_manipulationDeltaX > _deltaLimitX || _manipulationDeltaY >= _deltaLimitY) + { + this.State = SelectionEnabledState.Closed; + UpdateVisualState(true); + } + } + + /// + /// Selects this MultiselectItem if the manipulation delta + /// is within limits and fires an OnSelectionChanged event accordingly. + /// Resets the deltas for both axises. + /// + /// The Hint Panel that triggers the event. + /// The event information. + private void HintPanel_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e) + { + if (_manipulationDeltaX == _deltaLimitX && _manipulationDeltaY < _deltaLimitY) + { + this.IsSelected = true; + } + + _manipulationDeltaX = 0.0; + _manipulationDeltaY = 0.0; + } + + /// + /// Toogles the selection of a MultiselectItem + /// and fires an OnSelectionChanged event accordingly. + /// + /// The cover that triggers the event. + /// The event information. + private void Cover_Tap(object sender, System.Windows.Input.GestureEventArgs e) + { + this.IsSelected = !this.IsSelected; + } + + #endregion + } + + /// + /// Represents the visual states of a Multiselect item. + /// + internal enum SelectionEnabledState + { + /// + /// Closed visual state value. + /// + Closed = 0, + + /// + /// Exposed visual state value. + /// + Exposed = 1, + + /// + /// Opened visual state value. + /// + Opened = 2 + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/MultiselectList/MultiselectList.cs b/Microsoft.Phone.Controls.Toolkit/MultiselectList/MultiselectList.cs new file mode 100644 index 0000000..b911605 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/MultiselectList/MultiselectList.cs @@ -0,0 +1,330 @@ +// (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.Collections.Specialized; +using System.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Windows.Controls; + +namespace Microsoft.Phone.Controls +{ + /// + /// A collection of items that supports multiple selection. + /// + /// Experimental + [StyleTypedProperty(Property = "ItemContainerStyle", StyleTargetType = typeof(MultiselectList))] + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly")] + public class MultiselectList : ItemsControl + { + /// + /// Collection of items that are currently selected. + /// + public IList SelectedItems { get; private set; } + + /// + /// Occurs when there is a change to the SelectedItems collection. + /// + public event SelectionChangedEventHandler SelectionChanged; + + /// + /// Occurs when the selection mode is opened or closed. + /// + public event DependencyPropertyChangedEventHandler IsSelectionEnabledChanged; + + #region IsSelectionEnabled DependencyProperty + + /// + /// Gets or sets the flag that indicates if the list + /// is in selection mode or not. + /// + public bool IsSelectionEnabled + { + get { return (bool)GetValue(IsInSelectionModeProperty); } + set { SetValue(IsInSelectionModeProperty, value); } + } + + /// + /// Identifies the IsSelectionEnabled dependency property. + /// + public static readonly DependencyProperty IsInSelectionModeProperty = + DependencyProperty.Register("IsSelectionEnabled", typeof(bool), typeof(MultiselectList), new PropertyMetadata(false, OnIsSelectionEnabledPropertyChanged)); + + /// + /// Opens or closes the selection mode accordingly. + /// If closing, it unselects any selected item. + /// Finally, it fires up an IsSelectionEnabledChanged event. + /// + /// The dependency object. + /// The event information. + private static void OnIsSelectionEnabledPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + MultiselectList target = (MultiselectList)obj; + + if ((bool)e.NewValue) + { + target.OpenSelection(); + } + else + { + if (target.SelectedItems.Count > 0) + { + IList removedItems = new List(); + + //All the selected items will be unselected. + foreach (object item in target.SelectedItems) + { + removedItems.Add(item); + } + + //Unselect the containers that are not virtualized. + for (int k = 0; k < target.Items.Count && target.SelectedItems.Count > 0; k++) + { + MultiselectItem item = (MultiselectItem)target.ItemContainerGenerator.ContainerFromIndex(k); + if (item != null) + { + if (item.IsSelected == true) + { + item._canTriggerSelectionChanged = false; + item.IsSelected = false; + item._canTriggerSelectionChanged = true; + } + } + } + + //Clear the selected items and trigger event. + target.SelectedItems.Clear(); + target.OnSelectionChanged(removedItems, new object[0]); + } + + target.CloseSelection(); + } + + var handler = target.IsSelectionEnabledChanged; + if (handler != null) + { + handler(obj, e); + } + } + + #endregion + + #region ItemInfoTemplate DependencyProperty + + /// + /// Gets or sets the data template that is to be + /// used on the item information field of the MultiselectItems. + /// + public DataTemplate ItemInfoTemplate + { + get { return (DataTemplate)GetValue(ItemInfoTemplateProperty); } + set { SetValue(ItemInfoTemplateProperty, value); } + } + + /// + /// Identifies the ItemInfoTemplate dependency property. + /// + public static readonly DependencyProperty ItemInfoTemplateProperty = + DependencyProperty.Register("ItemInfoTemplate", typeof(DataTemplate), typeof(MultiselectList), new PropertyMetadata(null, null)); + + #endregion + + #region ItemContainerStyle DependencyProperty + + /// + /// Gets or sets the item container style. + /// + public Style ItemContainerStyle + { + get { return (Style)GetValue(ItemContainerStyleProperty); } + set { SetValue(ItemContainerStyleProperty, value); } + } + + /// + /// Identifies the ItemContainerStyle dependency property. + /// + public static readonly DependencyProperty ItemContainerStyleProperty = + DependencyProperty.Register("ItemContainerStyle", typeof(Style), typeof(MultiselectList), new PropertyMetadata(null, null)); + + #endregion + + /// + /// Initializes a new instance of the MultiselectList class. + /// + public MultiselectList() + { + DefaultStyleKey = typeof(MultiselectList); + SelectedItems = new List(); + } + + /// + /// Toogles the selection mode based on the count of selected items, + /// and fires a SelectionChanged event. + /// + /// + /// A collection containing the items that were unselected. + /// + /// + /// A collection containing the items that were selected. + /// + internal void OnSelectionChanged(IList removedItems, IList addedItems) + { + if (SelectedItems.Count <= 0) + { + IsSelectionEnabled = false; + } + else if (SelectedItems.Count == 1 && removedItems.Count == 0) + { + IsSelectionEnabled = true; + } + + var handler = SelectionChanged; + if (handler != null) + { + handler(this, new SelectionChangedEventArgs(removedItems, addedItems)); + } + } + + /// + /// Triggers the visual state changes and visual transitions + /// to open the list into selection mode. + /// + private void OpenSelection() + { + IList items = ItemsControlExtensions.GetItemsInViewPort(this); + + //Only animate the containers in the view port. + foreach (var i in items) + { + MultiselectItem item = (MultiselectItem)(((WeakReference)i).Target); + item.State = SelectionEnabledState.Opened; + item.UpdateVisualState(true); + } + + Dispatcher.BeginInvoke(() => + { + for (int j = 0; j < this.Items.Count; j++) + { + MultiselectItem item = (MultiselectItem)ItemContainerGenerator.ContainerFromIndex(j); + if (item != null) + { + item.State = SelectionEnabledState.Opened; + item.UpdateVisualState(false); + } + } + }); + } + + /// + /// Triggers the visual state changes and visual transitions + /// to close the list out of selection mode. + /// + private void CloseSelection() + { + IList items = ItemsControlExtensions.GetItemsInViewPort(this); + + //Only animate the containers in the view port. + foreach (var i in items) + { + MultiselectItem item = (MultiselectItem)(((WeakReference)i).Target); + item.State = SelectionEnabledState.Closed; + item.UpdateVisualState(true); + } + + Dispatcher.BeginInvoke(() => + { + for (int j = 0; j < this.Items.Count; j++) + { + MultiselectItem item = (MultiselectItem)ItemContainerGenerator.ContainerFromIndex(j); + if (item != null) + { + item.State = SelectionEnabledState.Closed; + item.UpdateVisualState(false); + } + } + }); + } + + #region ItemsControl overriden methods + + /// + /// Overrides the DependencyObject used by this ItemsControl + /// to be a MultiselectItem. + /// + /// + /// A new instance of a MultiselectItem. + /// + protected override DependencyObject GetContainerForItemOverride() + { + return new MultiselectItem(); + } + + /// + /// Acknowledges an item as being of the same type as its container + /// if it is a MultiselectItem. + /// + /// An item owned by the ItemsControl. + /// True if the item is a MultiselectItem. + protected override bool IsItemItsOwnContainerOverride(object item) + { + return (item is MultiselectItem); + } + + /// + /// Resets new or reused item containers appropiately. + /// + /// + /// + protected override void PrepareContainerForItemOverride(DependencyObject element, object item) + { + base.PrepareContainerForItemOverride(element, item); + + MultiselectItem container = (MultiselectItem)element; + + container.Style = this.ItemContainerStyle; + container._isBeingVirtualized = true; + + //Select or unselect item containers to put or clear checkmarks. + container.IsSelected = SelectedItems.Contains(item); + + //Open or close item containers depending on the selection mode. + container.State = IsSelectionEnabled ? SelectionEnabledState.Opened : SelectionEnabledState.Closed; + + container.UpdateVisualState(false); + container._isBeingVirtualized = false; + } + + /// + /// Unselects any selected item which was removed from the source. + /// + /// The event information. + protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) + { + base.OnItemsChanged(e); + + if (SelectedItems.Count > 0) + { + IList removedItems = new List(); + + for (int i = 0; i < SelectedItems.Count; i++) + { + var item = SelectedItems[i]; + if (!(Items.Contains(item))) + { + SelectedItems.Remove(item); + removedItems.Add(item); + i--; + } + } + + OnSelectionChanged(removedItems, new object[0]); + } + } + + #endregion + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/PhoneTextBox/PhoneTextBox.cs b/Microsoft.Phone.Controls.Toolkit/PhoneTextBox/PhoneTextBox.cs new file mode 100644 index 0000000..91b0c53 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/PhoneTextBox/PhoneTextBox.cs @@ -0,0 +1,582 @@ +// (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.Windows; +using Microsoft.Phone.Controls; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Shapes; +using System.Globalization; +using System.Diagnostics.CodeAnalysis; + +namespace Microsoft.Phone.Controls +{ + /// + /// An extended TextBox for Windows Phone which implements hint text, an action icon, and a + /// length indicator. + /// + /// Experimental + [TemplateVisualState(Name = LengthIndicatorVisibleState, GroupName = LengthIndicatorStates)] + [TemplateVisualState(Name = LengthIndicatorHiddenState, GroupName = LengthIndicatorStates)] + [TemplateVisualState(Name = DisabledState, GroupName = CommonStates)] + [TemplateVisualState(Name = ReadOnlyState, GroupName = CommonStates)] + [TemplateVisualState(Name = FocusedState, GroupName = FocusStates)] + [TemplateVisualState(Name = UnfocusedState, GroupName = FocusStates)] + [TemplatePart(Name = TextBoxName, Type = typeof(TextBox))] + [TemplatePart(Name = HintContentName, Type = typeof(ContentControl))] + [TemplatePart(Name = LengthIndicatorName, Type = typeof(TextBlock))] + public class PhoneTextBox : TextBox + { + + #region PhoneTextBox Properties & Variables + private Grid RootGrid; + private TextBox TextBox; + private TextBlock MeasurementTextBlock; // Used to measure the height of the TextBox to determine if the action icon is being overlapped. + + private Brush ForegroundBrushInactive = (Brush)Application.Current.Resources["PhoneTextBoxReadOnlyBrush"]; + + private Brush ForegroundBrushEdit; + + // Hint Private Variables. + private ContentControl HintContent; + private Border HintBorder; + + // Length Indicator Private Variables. + private TextBlock LengthIndicator; + + // Action Icon Private Variables. + private Border ActionIconBorder; + + // Ignore flags for the dependency properties. + private bool _ignorePropertyChange = false; + + //Temporarily ignore focus? + private bool _ignoreFocus = false; + + #endregion + + #region Constants + /// + /// Root grid. + /// + private const string RootGridName = "RootGrid"; + + /// + /// Main text box. + /// + private const string TextBoxName = "Text"; + + /// + /// Hint Content. + /// + private const string HintContentName = "HintContent"; + + /// + /// Hint border. + /// + private const string HintBorderName = "HintBorder"; + + /// + /// Length indicator name. + /// + private const string LengthIndicatorName = "LengthIndicator"; + + /// + /// Action icon canvas. + /// + private const string ActionIconCanvasName = "ActionIconCanvas"; + + /// + /// Measurement Text Block name. + /// + private const string MeasurementTextBlockName = "MeasurementTextBlock"; + + /// + /// Action icon image name. + /// + private const string ActionIconBorderName = "ActionIconBorder"; + #endregion + + #region Visual States + /// + /// Length indicator states. + /// + private const string LengthIndicatorStates = "LengthIndicatorStates"; + + /// + /// Length indicator visible visual state. + /// + private const string LengthIndicatorVisibleState = "LengthIndicatorVisible"; + + /// + /// Length indicator hidden visual state. + /// + private const string LengthIndicatorHiddenState = "LengthIndicatorHidden"; + + /// + /// Common States. + /// + private const string CommonStates = "CommonStates"; + + /// + /// Disabled state. + /// + private const string DisabledState = "Disabled"; + + /// + /// ReadOnly state. + /// + private const string ReadOnlyState = "ReadOnly"; + + /// + /// Focus states. + /// + private const string FocusStates = "FocusStates"; + + /// + /// Focused state. + /// + private const string FocusedState = "Focused"; + + /// + /// Unfocused state. + /// + private const string UnfocusedState = "Unfocused"; + #endregion + + #region Hint + /// + /// Identifies the Hint DependencyProperty. + /// + public static readonly DependencyProperty HintProperty = + DependencyProperty.Register("Hint", typeof(string), typeof(PhoneTextBox), new PropertyMetadata( + new PropertyChangedCallback(OnHintPropertyChanged) + ) + ); + + /// + /// Gets or sets the Hint + /// + public string Hint + { + get { return base.GetValue(HintProperty) as string; } + set { base.SetValue(HintProperty, value); } + } + + /// + /// Identifies the HintStyle DependencyProperty. + /// + public static readonly DependencyProperty HintStyleProperty = + DependencyProperty.Register("HintStyle", typeof(Style), typeof(PhoneTextBox), null); + + /// + /// Gets or sets the Hint style + /// + public Style HintStyle + { + get { return base.GetValue(HintStyleProperty) as Style; } + set { base.SetValue(HintStyleProperty, value); } + } + + /// + /// Identifies the HintVisibility DependencyProperty + /// + public static readonly DependencyProperty ActualHintVisibilityProperty = + DependencyProperty.Register("ActualHintVisibility", typeof(Visibility), typeof(PhoneTextBox), null); + + /// + /// Gets or sets whether the hint is actually visible. + /// + public Visibility ActualHintVisibility + { + get { return (Visibility)base.GetValue(ActualHintVisibilityProperty); } + set { base.SetValue(ActualHintVisibilityProperty, value); } + } + + + /// + /// When the Hint is changed, check if it needs to be hidden or shown. + /// + /// Sending PhoneTextBox. + /// DependencyPropertyChangedEvent Arguments. + private static void OnHintPropertyChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) + { + PhoneTextBox phoneTextBox = sender as PhoneTextBox; + + if (phoneTextBox != null && phoneTextBox.HintContent != null) + { + phoneTextBox.UpdateHintVisibility(); + } + } + + + /// + /// Determines if the Hint should be shown or not based on if there is content in the TextBox. + /// + private void UpdateHintVisibility() + { + if (HintContent != null) + { + if (string.IsNullOrEmpty(Text)) + { + ActualHintVisibility = Visibility.Visible; + Foreground = ForegroundBrushInactive; + } + else + { + ActualHintVisibility = Visibility.Collapsed; + } + } + } + + /// + /// Override the Blur/LostFocus event to show the Hint if needed. + /// + /// Event arguments. + protected override void OnLostFocus(RoutedEventArgs e) + { + UpdateHintVisibility(); + base.OnLostFocus(e); + } + + /// + /// Override the Focus event to hide the Hint. + /// + /// Event arguments. + protected override void OnGotFocus(RoutedEventArgs e) + { + if (_ignoreFocus) + { + _ignoreFocus = false; + + var rootFrame = Application.Current.RootVisual as Frame; + rootFrame.Focus(); + + return; + } + + Foreground = ForegroundBrushEdit; + + if (HintContent != null) + { + ActualHintVisibility = Visibility.Collapsed; + } + + base.OnGotFocus(e); + } + + #endregion + + #region Length Indicator + /// + /// Length Indicator Visibile Dependency Property. + /// + public static readonly DependencyProperty LengthIndicatorVisibleProperty = + DependencyProperty.Register("LengthIndicatorVisible", typeof(Boolean), typeof(PhoneTextBox), null); + + /// + /// Boolean that determines if the length indicator should be visible. + /// + public Boolean LengthIndicatorVisible + { + get { return (bool)base.GetValue(LengthIndicatorVisibleProperty); } + set { base.SetValue(LengthIndicatorVisibleProperty, value); } + } + + /// + /// Length Indicator Visibility Threshold Dependency Property. + /// + public static readonly DependencyProperty LengthIndicatorThresholdProperty = + DependencyProperty.Register("LengthIndicatorThreshold", typeof(int), typeof(PhoneTextBox), new PropertyMetadata( + new PropertyChangedCallback(OnLengthIndicatorThresholdChanged) + ) + ); + + /// + /// Threshold after which the length indicator will appear. + /// + public int LengthIndicatorThreshold + { + get { return (int)base.GetValue(LengthIndicatorThresholdProperty); } + set { base.SetValue(LengthIndicatorThresholdProperty, value); } + } + + [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "The property name is the appropriate value to throw.")] + private static void OnLengthIndicatorThresholdChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) + { + PhoneTextBox phoneTextBox = sender as PhoneTextBox; + + if (phoneTextBox._ignorePropertyChange) + { + phoneTextBox._ignorePropertyChange = false; + return; + } + + if (phoneTextBox.LengthIndicatorThreshold < 0) + { + phoneTextBox._ignorePropertyChange = true; + phoneTextBox.SetValue(LengthIndicatorThresholdProperty, args.OldValue); + + throw new ArgumentOutOfRangeException("LengthIndicatorThreshold", "The length indicator visibility threshold must be greater than zero."); + } + + } + + /// + /// The displayed maximum length of text that can be entered. This value takes + /// priority over the MaxLength property in the Length Indicator display. + /// + public static readonly DependencyProperty DisplayedMaxLengthProperty = + DependencyProperty.Register("DisplayedMaxLength", typeof(int), typeof(PhoneTextBox), new PropertyMetadata( + new PropertyChangedCallback(DisplayedMaxLengthChanged) + ) + ); + + + /// + /// The displayed value for the maximum length of the input. + /// + public int DisplayedMaxLength + { + get { return (int)base.GetValue(DisplayedMaxLengthProperty); } + set { base.SetValue(DisplayedMaxLengthProperty, value); } + } + + [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "The property name is the appropriate value to throw.")] + private static void DisplayedMaxLengthChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) + { + PhoneTextBox phoneTextBox = sender as PhoneTextBox; + + if (phoneTextBox.DisplayedMaxLength > phoneTextBox.MaxLength && phoneTextBox.MaxLength > 0) + { + throw new ArgumentOutOfRangeException("DisplayedMaxLength", "The displayed maximum length cannot be greater than the MaxLength."); + } + } + + [System.Diagnostics.CodeAnalysis.SuppressMessage( + "Microsoft.Globalization", + "CA1303:Do not pass literals as localized parameters", + MessageId = "System.Windows.Controls.TextBlock.set_Text(System.String)", + Justification = "At this time the length indicator is not culture-specific or retrieved from the resources.")] + private void UpdateLengthIndicatorVisibility() + { + if (RootGrid == null || LengthIndicator == null) + { + return; + } + + bool isHidden = true; + if (LengthIndicatorVisible) + { + // The current implementation is culture invariant. + LengthIndicator.Text = String.Format( + CultureInfo.InvariantCulture, + "{0}/{1}", Text.Length, + ((DisplayedMaxLength > 0) ? DisplayedMaxLength : MaxLength)); + + if (Text.Length >= LengthIndicatorThreshold) + { + isHidden = false; + } + } + + VisualStateManager.GoToState(this, isHidden ? LengthIndicatorHiddenState : LengthIndicatorVisibleState, false); + } + + #endregion + + #region Action Icon + /// + /// Identifies the ActionIcon DependencyProperty. + /// + public static readonly DependencyProperty ActionIconProperty = + DependencyProperty.Register("ActionIcon", typeof(ImageSource), typeof(PhoneTextBox), new PropertyMetadata( + new PropertyChangedCallback(OnActionIconChanged) + ) + ); + + /// + /// Gets or sets the ActionIcon. + /// + public ImageSource ActionIcon + { + get { return base.GetValue(ActionIconProperty) as ImageSource; } + set { base.SetValue(ActionIconProperty, value); } + } + + + /// + /// Gets or sets whether the ActionItem is hidden when there is not text entered in the PhoneTextBox. + /// + public bool HidesActionItemWhenEmpty + { + get { return (bool)GetValue(HidesActionItemWhenEmptyProperty); } + set { SetValue(HidesActionItemWhenEmptyProperty, value); } + } + + /// + /// Identifies the HidesActionItemWhenEmpty DependencyProperty. + /// + public static readonly DependencyProperty HidesActionItemWhenEmptyProperty = + DependencyProperty.Register("HidesActionItemWhenEmpty", typeof(bool), typeof(PhoneTextBox), new PropertyMetadata(false, OnActionIconChanged)); + + + + /// + /// Action Icon Tapped event. + /// + public event EventHandler ActionIconTapped; + + private static void OnActionIconChanged(DependencyObject sender, DependencyPropertyChangedEventArgs args) + { + PhoneTextBox phoneTextBox = sender as PhoneTextBox; + + if (phoneTextBox != null) + { + phoneTextBox.UpdateActionIconVisibility(); + } + } + + private void UpdateActionIconVisibility() + { + if (ActionIconBorder != null) + { + if (ActionIcon == null || (HidesActionItemWhenEmpty && string.IsNullOrEmpty(Text))) + { + ActionIconBorder.Visibility = Visibility.Collapsed; + HintBorder.Padding = new Thickness(0); + } + else + { + ActionIconBorder.Visibility = Visibility.Visible; + + if (TextWrapping != System.Windows.TextWrapping.Wrap) + { + HintBorder.Padding = new Thickness(0, 0, 48, 0); + } + } + } + } + + /// + /// Determines if the developer set an event for ActionIconTapped. + /// + /// The sender object + /// The RoutedEventArgs for the event + private void OnActionIconTapped(object sender, RoutedEventArgs e) + { + _ignoreFocus = true; + + var handler = ActionIconTapped; + + if (handler != null) + { + handler(sender, e); + } + } + + private void ResizeTextBox() + { + if (ActionIcon == null || TextWrapping != System.Windows.TextWrapping.Wrap) { return; } + + MeasurementTextBlock.Width = ActualWidth; + + if (MeasurementTextBlock.ActualHeight > ActualHeight - 72) + { + Height = ActualHeight + 72; + } + else if (ActualHeight > MeasurementTextBlock.ActualHeight + 144) + { + Height = ActualHeight - 72; + } + } + + #endregion + + /// + /// Initializes a new instance of the PhoneTextBox class. + /// + public PhoneTextBox() + { + DefaultStyleKey = typeof(PhoneTextBox); + } + + /// + /// Applies the template and checks to see if the Hint should be shown + /// when the page is first loaded. + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (TextBox != null) + { + TextBox.TextChanged -= OnTextChanged; + } + + if (ActionIconBorder != null) + { + ActionIconBorder.MouseLeftButtonDown -= OnActionIconTapped; + } + + RootGrid = GetTemplateChild(RootGridName) as Grid; + TextBox = GetTemplateChild(TextBoxName) as TextBox; + + // Getting the foreground color to save for later. + ForegroundBrushEdit = Foreground; + + // Getting template children for the hint text. + HintContent = GetTemplateChild(HintContentName) as ContentControl; + HintBorder = GetTemplateChild(HintBorderName) as Border; + + if (HintContent != null) + { + UpdateHintVisibility(); + } + + // Getting template children for the length indicator. + LengthIndicator = GetTemplateChild(LengthIndicatorName) as TextBlock; + + // Getting template child for the action icon + ActionIconBorder = GetTemplateChild(ActionIconBorderName) as Border; + + if (RootGrid != null && LengthIndicator != null) + { + UpdateLengthIndicatorVisibility(); + } + + if (TextBox != null) + { + TextBox.TextChanged += OnTextChanged; + } + + if (ActionIconBorder != null) + { + ActionIconBorder.MouseLeftButtonDown += OnActionIconTapped; + UpdateActionIconVisibility(); // Add back the padding if needed. + } + + + // Get template child for the action icon measurement text block. + MeasurementTextBlock = GetTemplateChild(MeasurementTextBlockName) as TextBlock; + } + + /// + /// Called when the selection changed event occurs. This determines whether the length indicator should be shown or + /// not and if the TextBox needs to grow. + /// + /// Sender TextBox + /// Event arguments + private void OnTextChanged(object sender, RoutedEventArgs e) + { + UpdateLengthIndicatorVisibility(); + UpdateActionIconVisibility(); + UpdateHintVisibility(); + ResizeTextBox(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Pivot/LockablePivot.cs b/Microsoft.Phone.Controls.Toolkit/Pivot/LockablePivot.cs new file mode 100644 index 0000000..641fdfb --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Pivot/LockablePivot.cs @@ -0,0 +1,287 @@ +// (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.Specialized; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using Microsoft.Phone.Controls.Primitives; + +namespace Microsoft.Phone.Controls +{ + /// + /// The lockable Pivot extends the base Pivot control with a property that + /// disables navigation between Pivot items. + /// + /// Experimental + [TemplatePart(Name = HeadersListElement, Type = typeof(PivotHeadersControl))] + [TemplatePart(Name = PivotItemPresenterElement, Type = typeof(ItemsPresenter))] + [StyleTypedProperty(Property = ItemContainerStyleName, StyleTargetType = typeof(PivotItem))] + public class LockablePivot : Pivot + { + /// + /// Headers list element template part. + /// + private const string HeadersListElement = "HeadersListElement"; + + /// + /// Content element template part. + /// + private const string PivotItemPresenterElement = "PivotItemPresenter"; + + /// + /// The item container style property name. + /// + internal const string ItemContainerStyleName = "ItemContainerStyle"; + + private bool _isLocked = false; + private bool _isUpdating = false; + + #region public bool IsLocked + /// + /// Gets or sets a value indicating whether the control is locked. + /// + public bool IsLocked + { + get { return (bool)GetValue(IsLockedProperty); } + set { SetValue(IsLockedProperty, value); } + } + + /// + /// Identifies the IsLocked dependency property. + /// + public static readonly DependencyProperty IsLockedProperty = + DependencyProperty.Register( + "IsLocked", + typeof(bool), + typeof(LockablePivot), + new PropertyMetadata(false, OnIsLockedPropertyChanged)); + + /// + /// IsLockedProperty property changed handler. + /// + /// LockablePivot that changed its IsLocked. + /// Event arguments. + private static void OnIsLockedPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + LockablePivot source = d as LockablePivot; + if (source != null) + { + source.OnIsLockedChanged((bool)e.NewValue); + } + } + #endregion public bool IsLocked + + private struct HeaderAnimationInfo + { + public double opacity; + public double originalX; + public OpacityAnimator opacityAnimator; + public TransformAnimator transformAnimator; + public TranslateTransform transform; + } + + private PivotItem[] _savedItems = null; + private HeaderAnimationInfo[] _animInfo = null; + private PivotHeadersControl _header = null; + private int _savedIndex; + + private static Duration _animTime = + new Duration(TimeSpan.FromMilliseconds(200)); + + private const double _animOffset = 20.0; + + /// + /// Initializes a new instance of the LockablePivot type. + /// + public LockablePivot() + { + SelectionChanged += OnSelectionChanged; + } + + /// + /// Invoked when the Items property changes. + /// + /// Information about the change. + protected override void OnItemsChanged(NotifyCollectionChangedEventArgs e) + { + if (!_isUpdating) + { + if (_isLocked) + { + throw new System.InvalidOperationException( + "Pivot Items cannot be modified when locked" + ); + } + + // Items have been changed, so animation information + // will need to be recreated. + _animInfo = null; + + base.OnItemsChanged(e); + } + } + + private PivotHeadersControl FindHeader(UIElement start) + { + UIElement target = null; + + int childCount = VisualTreeHelper.GetChildrenCount(start); + + for (int i = 0; i < childCount; i++) + { + UIElement e = VisualTreeHelper.GetChild(start, i) as UIElement; + + if (e is PivotHeadersControl) + { + target = e; + } + else + { + target = FindHeader(e); + } + + if (target != null) + { + break; + } + } + + return target as PivotHeadersControl; + } + + private void OnSelectionChanged(object sender, SelectionChangedEventArgs e) + { + // We've transitioned to another pivot item, so existing + // animation information is no longer valid. + _animInfo = null; + } + + private void OnIsLockedChanged(bool newValue) + { + _isLocked = newValue; + _isUpdating = true; + + if (_isLocked) + { + _savedIndex = SelectedIndex; + + FadeOutHeaders(); + SaveAndRemoveItems(); + } + else + { + RestoreItems(); + FadeInHeaders(); + } + + _isUpdating = false; + } + + // We create and store the animation information for the current + // state of the header the first time we need to perform an animation. + // It's stored so that an initial fade-out animation can be + // interrupted and resumed from its current state by the fade-in + // animation. + private void CreateAnimationInformation() + { + if (_animInfo == null) + { + int count = _header.Items.Count; + + // We won't animate the currently selected item, + // so ensure we don't create information for it. + _animInfo = new HeaderAnimationInfo[count - 1]; + + int i = 0; + + foreach (PivotHeaderItem headerItem in _header.Items) + { + if (!headerItem.IsSelected) + { + _animInfo[i].opacity = headerItem.Opacity; + _animInfo[i].opacityAnimator = new OpacityAnimator(headerItem); + _animInfo[i].transform = + TransformAnimator.GetTranslateTransform(headerItem); + _animInfo[i].transformAnimator = + new TransformAnimator(_animInfo[i].transform); + _animInfo[i].originalX = _animInfo[i].transform.X; + + i++; + } + } + } + } + + private void SaveAndRemoveItems() + { + _savedItems = new PivotItem[Items.Count]; + Items.CopyTo(_savedItems, 0); + + for (int i = Items.Count - 1; i > _savedIndex; i--) + { + Items.RemoveAt(i); + } + + for (int i = 0; i < _savedIndex; i++) + { + Items.RemoveAt(0); + } + } + + private void RestoreItems() + { + for (int i = 0; i < _savedIndex; i++) + { + Items.Insert(i, _savedItems[i]); + } + + for (int i = _savedIndex + 1; i < _savedItems.Length; i++) + { + Items.Add(_savedItems[i]); + } + + _savedItems = null; + } + + private void FadeOutHeaders() + { + if (_header == null) + { + _header = FindHeader(this); + } + + CreateAnimationInformation(); + + foreach (HeaderAnimationInfo anim in _animInfo) + { + anim.opacityAnimator.GoTo( + 0.0, + _animTime + ); + anim.transformAnimator.GoTo( + _animOffset + anim.originalX, + _animTime + ); + } + } + + private void FadeInHeaders() + { + foreach (HeaderAnimationInfo anim in _animInfo) + { + anim.opacityAnimator.GoTo( + anim.opacity, + _animTime + ); + anim.transformAnimator.GoTo( + anim.originalX, + _animTime + ); + } + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/Primitives/ClipToBounds.cs b/Microsoft.Phone.Controls.Toolkit/Primitives/ClipToBounds.cs new file mode 100644 index 0000000..42e9314 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Primitives/ClipToBounds.cs @@ -0,0 +1,88 @@ +// (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.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Windows.Media; + +namespace Microsoft.Phone.Controls.Primitives +{ + /// + /// Provides an attachable property that clips a FrameworkElement + /// to its bounds, e.g. clip the element when it is translated outside + /// of the panel it is placed in. + /// + /// Preview + public class ClipToBounds : DependencyObject + { + /// + /// Gets the IsEnabled dependency property from an object. + /// + /// The object to get the value from. + /// The property's value. + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")] + public static bool GetIsEnabled(DependencyObject obj) + { + return (bool)obj.GetValue(IsEnabledProperty); + } + + /// + /// Sets the IsEnabled dependency property on an object. + /// + /// The object to set the value on. + /// The value to set. + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")] + public static void SetIsEnabled(DependencyObject obj, bool value) + { + obj.SetValue(IsEnabledProperty, value); + } + + /// + /// Identifies the IsEnabled dependency property. + /// + public static readonly DependencyProperty IsEnabledProperty = + DependencyProperty.RegisterAttached("IsEnabled", typeof(bool), typeof(ClipToBounds), new PropertyMetadata(false, OnIsEnabledPropertyChanged)); + + /// + /// Attaches or detaches the SizeChanged event handler. + /// + /// The dependency object. + /// The event information. + private static void OnIsEnabledPropertyChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + FrameworkElement target = obj as FrameworkElement; + + if(target != null) + { + if ((bool)e.NewValue) + { + target.SizeChanged += OnSizeChanged; + } + else + { + target.SizeChanged -= OnSizeChanged; + } + } + } + + /// + /// Clips the associated object to a rectangular geometry determined + /// by its actual width and height. + /// + /// The event sender. + /// The event information. + private static void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + FrameworkElement source = sender as FrameworkElement; + + if (source != null) + { + RectangleGeometry geometry = new RectangleGeometry(); + geometry.Rect = new Rect(0, 0, source.ActualWidth, source.ActualHeight); + source.Clip = geometry; + } + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/Primitives/LoopingSelector.cs b/Microsoft.Phone.Controls.Toolkit/Primitives/LoopingSelector.cs new file mode 100644 index 0000000..3ff7361 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Primitives/LoopingSelector.cs @@ -0,0 +1,799 @@ +// (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.Generic; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; + +namespace Microsoft.Phone.Controls.Primitives +{ + /// + /// An infinitely scrolling, UI- and data-virtualizing selection control. + /// + /// Preview + [TemplatePart(Name = ItemsPanelName, Type = typeof(Panel))] + [TemplatePart(Name = CenteringTransformName, Type = typeof(TranslateTransform))] + [TemplatePart(Name = PanningTransformName, Type = typeof(TranslateTransform))] + public class LoopingSelector : Control + { + // The names of the template parts + private const string ItemsPanelName = "ItemsPanel"; + private const string CenteringTransformName = "CenteringTransform"; + private const string PanningTransformName = "PanningTransform"; + + // Amount of finger movement before the manipulation is considered a dragging manipulation. + private const double DragSensitivity = 12; + + private static readonly Duration _selectDuration = new Duration(TimeSpan.FromMilliseconds(500)); + private readonly IEasingFunction _selectEase = new ExponentialEase() { EasingMode = EasingMode.EaseInOut }; + + private static readonly Duration _panDuration = new Duration(TimeSpan.FromMilliseconds(100)); + private readonly IEasingFunction _panEase = new ExponentialEase(); + + private DoubleAnimation _panelAnimation; + private Storyboard _panelStoryboard; + + private Panel _itemsPanel; + private TranslateTransform _panningTransform; + private TranslateTransform _centeringTransform; + + private bool _isSelecting; + private LoopingSelectorItem _selectedItem; + + private Queue _temporaryItemsPool; + + private double _minimumPanelScroll = float.MinValue; + private double _maximumPanelScroll = float.MaxValue; + + private int _additionalItemsCount = 0; + + private bool _isAnimating; + + private double _dragTarget; + + // Once the user starts dragging horizontally, he is not allowed to drag vertically + // until he completes his touch gesture and starts again. + private bool _isAllowedToDragVertically = true; + + // Specify whether or not the user is dragging with his finger. + private bool _isDragging; + + private enum State + { + Normal, + Expanded, + Dragging, + Snapping, + Flicking + } + + private State _state; + + /// + /// The data source that the this control is the view for. + /// + public ILoopingSelectorDataSource DataSource + { + get { return (ILoopingSelectorDataSource)GetValue(DataSourceProperty); } + set + { + if (DataSource != null) + { + DataSource.SelectionChanged -= value_SelectionChanged; + } + + SetValue(DataSourceProperty, value); + + if (value != null) + { + value.SelectionChanged += value_SelectionChanged; + } + } + } + + void value_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (!IsReady) + { + return; + } + + if (!_isSelecting && e.AddedItems.Count == 1) + { + object selection = e.AddedItems[0]; + + foreach (LoopingSelectorItem child in _itemsPanel.Children) + { + if (child.DataContext == selection) + { + SelectAndSnapTo(child); + return; + } + } + UpdateData(); + } + } + + /// + /// The DataSource DependencyProperty + /// + public static readonly DependencyProperty DataSourceProperty = + DependencyProperty.Register("DataSource", typeof(ILoopingSelectorDataSource), typeof(LoopingSelector), new PropertyMetadata(null, OnDataModelChanged)); + + private static void OnDataModelChanged(DependencyObject obj, DependencyPropertyChangedEventArgs e) + { + LoopingSelector picker = (LoopingSelector)obj; + picker.UpdateData(); + } + + void DataModel_SelectionChanged(object sender, SelectionChangedEventArgs e) + { + if (!IsReady) + { + return; + } + + if (!_isSelecting && e.AddedItems.Count == 1) + { + object selection = e.AddedItems[0]; + + foreach (LoopingSelectorItem child in _itemsPanel.Children) + { + if (child.DataContext == selection) + { + SelectAndSnapTo(child); + break; + } + } + + UpdateData(); + } + } + + /// + /// The ItemTemplate property + /// + public DataTemplate ItemTemplate + { + get { return (DataTemplate)GetValue(ItemTemplateProperty); } + set { SetValue(ItemTemplateProperty, value); } + } + + /// + /// The ItemTemplate DependencyProperty + /// + public static readonly DependencyProperty ItemTemplateProperty = + DependencyProperty.Register("ItemTemplate", typeof(DataTemplate), typeof(LoopingSelector), new PropertyMetadata(null)); + + /// + /// The size of the items, excluding the ItemMargin. + /// + public Size ItemSize { get; set; } + + /// + /// The margin around the items, to be a part of the touchable area. + /// + public Thickness ItemMargin { get; set; } + + /// + /// Creates a new LoopingSelector. + /// + public LoopingSelector() + { + DefaultStyleKey = typeof(LoopingSelector); + CreateEventHandlers(); + } + + /// + /// The IsExpanded property controls the expanded state of this control. + /// + public bool IsExpanded + { + get { return (bool)GetValue(IsExpandedProperty); } + set { SetValue(IsExpandedProperty, value); } + } + + /// + /// The IsExpanded DependencyProperty. + /// + public static readonly DependencyProperty IsExpandedProperty = + DependencyProperty.Register("IsExpanded", typeof(bool), typeof(LoopingSelector), new PropertyMetadata(false, OnIsExpandedChanged)); + + /// + /// The IsExpandedChanged event will be raised whenever the IsExpanded state changes. + /// + public event DependencyPropertyChangedEventHandler IsExpandedChanged; + + private static void OnIsExpandedChanged(object sender, DependencyPropertyChangedEventArgs e) + { + LoopingSelector picker = (LoopingSelector)sender; + + picker.UpdateItemState(); + if (!picker.IsExpanded) + { + picker.SelectAndSnapToClosest(); + } + + if (picker._state == State.Normal || picker._state == State.Expanded) + { + picker._state = picker.IsExpanded ? State.Expanded : State.Normal; + } + + var listeners = picker.IsExpandedChanged; + if (listeners != null) + { + listeners(picker, e); + } + } + + /// + /// When overridden in a derived class, is invoked whenever application code or internal processes (such as a rebuilding layout pass) call . + /// In simplest terms, this means the method is called just before a UI element displays in an application. + /// For more information, see Remarks. + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + // Find the template parts. Create dummy objects if parts are missing to avoid + // null checks throughout the code (although we can't escape them completely.) + _itemsPanel = GetTemplateChild(ItemsPanelName) as Panel ?? new Canvas(); + _centeringTransform = GetTemplateChild(CenteringTransformName) as TranslateTransform ?? new TranslateTransform(); + _panningTransform = GetTemplateChild(PanningTransformName) as TranslateTransform ?? new TranslateTransform(); + + CreateVisuals(); + } + + void LoopingSelector_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + if (_isAnimating) + { + double y = _panningTransform.Y; + StopAnimation(); + _panningTransform.Y = y; + _isAnimating = false; + _state = State.Dragging; + } + } + + void LoopingSelector_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + if (_selectedItem != sender && _state == State.Dragging && !_isAnimating) + { + SelectAndSnapToClosest(); + _state = State.Expanded; + } + } + + #region Touch Events + private void OnTap(object sender, System.Windows.Input.GestureEventArgs e) + { + if (_panningTransform != null) + { + SelectAndSnapToClosest(); + e.Handled = true; + } + } + + private void OnManipulationStarted(object sender, ManipulationStartedEventArgs e) + { + _isAllowedToDragVertically = true; + _isDragging = false; + } + + private void OnManipulationDelta(object sender, ManipulationDeltaEventArgs e) + { + if (_isDragging) + { + AnimatePanel(_panDuration, _panEase, _dragTarget += e.DeltaManipulation.Translation.Y); + e.Handled = true; + } + else if (Math.Abs(e.CumulativeManipulation.Translation.X) > DragSensitivity) + { + _isAllowedToDragVertically = false; + } + else if (_isAllowedToDragVertically && Math.Abs(e.CumulativeManipulation.Translation.Y) > DragSensitivity) + { + _isDragging = true; + _state = State.Dragging; + e.Handled = true; + _selectedItem = null; + + if (!IsExpanded) + { + IsExpanded = true; + } + + _dragTarget = _panningTransform.Y; + UpdateItemState(); + } + } + + private void OnManipulationCompleted(object sender, ManipulationCompletedEventArgs e) + { + if (_isDragging) + { + // See if it was a flick + if (e.IsInertial) + { + _state = State.Flicking; + _selectedItem = null; + + if (!IsExpanded) + { + IsExpanded = true; + } + + Point velocity = new Point(0, e.FinalVelocities.LinearVelocity.Y); + double flickDuration = PhysicsConstants.GetStopTime(velocity); + Point flickEndPoint = PhysicsConstants.GetStopPoint(velocity); + IEasingFunction flickEase = PhysicsConstants.GetEasingFunction(flickDuration); + + AnimatePanel(new Duration(TimeSpan.FromSeconds(flickDuration)), flickEase, _panningTransform.Y + flickEndPoint.Y); + + e.Handled = true; + + _selectedItem = null; + UpdateItemState(); + } + + if (_state == State.Dragging) + { + SelectAndSnapToClosest(); + } + + _state = State.Expanded; + } + } + #endregion + + void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + _centeringTransform.Y = Math.Round(e.NewSize.Height / 2); + Clip = new RectangleGeometry() { Rect = new Rect(0, 0, e.NewSize.Width, e.NewSize.Height) }; + UpdateData(); + } + + void OnWrapperClick(object sender, EventArgs e) + { + if (_state == State.Normal) + { + _state = State.Expanded; + IsExpanded = true; + } + else if (_state == State.Expanded) + { + if (!_isAnimating && sender == _selectedItem) + { + _state = State.Normal; + IsExpanded = false; + } + else if (sender != _selectedItem && !_isAnimating) + { + SelectAndSnapTo((LoopingSelectorItem)sender); + } + } + } + + private void SelectAndSnapTo(LoopingSelectorItem item) + { + if (item == null) + { + return; + } + + if (_selectedItem != null) + { + _selectedItem.SetState(IsExpanded ? LoopingSelectorItem.State.Expanded : LoopingSelectorItem.State.Normal, true); + } + + if (_selectedItem != item) + { + _selectedItem = item; + // Update DataSource.SelectedItem aynchronously so that animations have a chance to start. + Dispatcher.BeginInvoke(() => + { + _isSelecting = true; + DataSource.SelectedItem = item.DataContext; + _isSelecting = false; + }); + } + + _selectedItem.SetState(LoopingSelectorItem.State.Selected, true); + + TranslateTransform transform = item.Transform; + if (transform != null) + { + double newPosition = -transform.Y - Math.Round(item.ActualHeight / 2); + if (_panningTransform.Y != newPosition) + { + AnimatePanel(_selectDuration, _selectEase, newPosition); + } + } + } + + private void UpdateData() + { + if (!IsReady) + { + return; + } + + // Save all items + _temporaryItemsPool = new Queue(_itemsPanel.Children.Count); + foreach (LoopingSelectorItem item in _itemsPanel.Children) + { + if (item.GetState() == LoopingSelectorItem.State.Selected) + { + item.SetState(LoopingSelectorItem.State.Normal, false); + } + _temporaryItemsPool.Enqueue(item); + item.Remove(); + } + + _itemsPanel.Children.Clear(); + StopAnimation(); + _panningTransform.Y = 0; + + // Reset the extents + _minimumPanelScroll = float.MinValue; + _maximumPanelScroll = float.MaxValue; + + Balance(); + } + + private void AnimatePanel(Duration duration, IEasingFunction ease, double to) + { + // Be sure not to run past the first or last items + double newTo = Math.Max(_minimumPanelScroll, Math.Min(_maximumPanelScroll, to)); + if (to != newTo) + { + // Adjust the duration + double originalDelta = Math.Abs(_panningTransform.Y - to); + double modifiedDelta = Math.Abs(_panningTransform.Y - newTo); + double factor = modifiedDelta / originalDelta; + + duration = new Duration(TimeSpan.FromMilliseconds(duration.TimeSpan.Milliseconds * factor)); + + to = newTo; + } + + double from = _panningTransform.Y; + StopAnimation(); + CompositionTarget.Rendering += AnimationPerFrameCallback; + + _panelAnimation.Duration = duration; + _panelAnimation.EasingFunction = ease; + _panelAnimation.From = from; + _panelAnimation.To = to; + _panelStoryboard.Begin(); + _panelStoryboard.SeekAlignedToLastTick(TimeSpan.Zero); + + _isAnimating = true; + } + + private void StopAnimation() + { + _panelStoryboard.Stop(); + CompositionTarget.Rendering -= AnimationPerFrameCallback; + } + + private void Brake(double newStoppingPoint) + { + double originalDelta = _panelAnimation.To.Value - _panelAnimation.From.Value; + double remainingDelta = newStoppingPoint - _panningTransform.Y; + double factor = remainingDelta / originalDelta; + + Duration duration = new Duration(TimeSpan.FromMilliseconds(_panelAnimation.Duration.TimeSpan.Milliseconds * factor)); + + AnimatePanel(duration, _panelAnimation.EasingFunction, newStoppingPoint); + } + + private bool IsReady + { + get { return ActualHeight > 0 && DataSource != null && _itemsPanel != null; } + } + + /// + /// Balances the items. + /// + private void Balance() + { + if (!IsReady) + { + return; + } + + double actualItemWidth = ActualItemWidth; + double actualItemHeight = ActualItemHeight; + + _additionalItemsCount = (int)Math.Round((ActualHeight * 1.5) / actualItemHeight); + + LoopingSelectorItem closestToMiddle = null; + int closestToMiddleIndex = -1; + + if (_itemsPanel.Children.Count == 0) + { + // We need to get the selection and start from there + closestToMiddleIndex = 0; + _selectedItem = closestToMiddle = CreateAndAddItem(_itemsPanel, DataSource.SelectedItem); + closestToMiddle.Transform.Y = -actualItemHeight / 2; + closestToMiddle.Transform.X = (ActualWidth - actualItemWidth) / 2; + closestToMiddle.SetState(LoopingSelectorItem.State.Selected, false); + } + else + { + closestToMiddleIndex = GetClosestItem(); + closestToMiddle = (LoopingSelectorItem)_itemsPanel.Children[closestToMiddleIndex]; + } + + int itemsBeforeCount; + LoopingSelectorItem firstItem = GetFirstItem(closestToMiddle, out itemsBeforeCount); + + int itemsAfterCount; + LoopingSelectorItem lastItem = GetLastItem(closestToMiddle, out itemsAfterCount); + + // Does the top need items? + if (itemsBeforeCount < itemsAfterCount || itemsBeforeCount < _additionalItemsCount) + { + while (itemsBeforeCount < _additionalItemsCount) + { + object newData = DataSource.GetPrevious(firstItem.DataContext); + if (newData == null) + { + // There may be room to display more items, but there is no more data. + _maximumPanelScroll = - firstItem.Transform.Y - actualItemHeight / 2; + if (_isAnimating && _panelAnimation.To.Value > _maximumPanelScroll) + { + Brake(_maximumPanelScroll); + } + break; + } + + LoopingSelectorItem newItem = null; + + // Can an item from the bottom be re-used? + if (itemsAfterCount > _additionalItemsCount) + { + newItem = lastItem; + lastItem = lastItem.Previous; + newItem.Remove(); + newItem.Content = newItem.DataContext = newData; + } + else + { + // Make a new item + newItem = CreateAndAddItem(_itemsPanel, newData); + newItem.Transform.X = (ActualWidth - actualItemWidth) / 2; + } + + // Put the new item on the top + newItem.Transform.Y = firstItem.Transform.Y - actualItemHeight; + newItem.InsertBefore(firstItem); + firstItem = newItem; + + ++itemsBeforeCount; + } + } + + // Does the bottom need items? + if (itemsAfterCount < itemsBeforeCount || itemsAfterCount < _additionalItemsCount) + { + while (itemsAfterCount < _additionalItemsCount) + { + object newData = DataSource.GetNext(lastItem.DataContext); + if (newData == null) + { + // There may be room to display more items, but there is no more data. + _minimumPanelScroll = - lastItem.Transform.Y - actualItemHeight / 2; + if (_isAnimating && _panelAnimation.To.Value < _minimumPanelScroll) + { + Brake(_minimumPanelScroll); + } + break; + } + + LoopingSelectorItem newItem = null; + + // Can an item from the top be re-used? + if (itemsBeforeCount > _additionalItemsCount) + { + newItem = firstItem; + firstItem = firstItem.Next; + newItem.Remove(); + newItem.Content = newItem.DataContext = newData; + } + else + { + // Make a new item + newItem = CreateAndAddItem(_itemsPanel, newData); + newItem.Transform.X = (ActualWidth - actualItemWidth) / 2; + } + + // Put the new item on the bottom + newItem.Transform.Y = lastItem.Transform.Y + actualItemHeight; + newItem.InsertAfter(lastItem); + lastItem = newItem; + + ++itemsAfterCount; + } + } + + _temporaryItemsPool = null; + } + + private static LoopingSelectorItem GetFirstItem(LoopingSelectorItem item, out int count) + { + count = 0; + while (item.Previous != null) + { + ++count; + item = item.Previous; + } + + return item; + } + + private static LoopingSelectorItem GetLastItem(LoopingSelectorItem item, out int count) + { + count = 0; + while (item.Next != null) + { + ++count; + item = item.Next; + } + + return item; + } + + void AnimationPerFrameCallback(object sender, EventArgs e) + { + Balance(); + } + + private int GetClosestItem() + { + if (!IsReady) + { + return -1; + } + + double actualItemHeight = ActualItemHeight; + + int count = _itemsPanel.Children.Count; + double panelY = _panningTransform.Y; + double halfHeight = actualItemHeight / 2; + int found = -1; + double closestDistance = double.MaxValue; + + for (int index = 0; index < count; ++index) + { + LoopingSelectorItem wrapper = (LoopingSelectorItem)_itemsPanel.Children[index]; + double distance = Math.Abs((wrapper.Transform.Y + halfHeight) + panelY); + if (distance <= halfHeight) + { + found = index; + break; + } + else if (closestDistance > distance) + { + closestDistance = distance; + found = index; + } + } + + return found; + } + + void PanelStoryboardCompleted(object sender, EventArgs e) + { + CompositionTarget.Rendering -= AnimationPerFrameCallback; + _isAnimating = false; + if (_state != State.Dragging) + { + SelectAndSnapToClosest(); + } + } + + private void SelectAndSnapToClosest() + { + if (!IsReady) + { + return; + } + + int index = GetClosestItem(); + if (index == -1) + { + return; + } + + LoopingSelectorItem item = (LoopingSelectorItem) _itemsPanel.Children[index]; + SelectAndSnapTo(item); + } + + private void UpdateItemState() + { + if (!IsReady) + { + return; + } + + bool isExpanded = IsExpanded; + + foreach (LoopingSelectorItem child in _itemsPanel.Children) + { + if (child == _selectedItem) + { + child.SetState(LoopingSelectorItem.State.Selected, true); + } + else + { + child.SetState(isExpanded ? LoopingSelectorItem.State.Expanded : LoopingSelectorItem.State.Normal, true); + } + } + } + + private double ActualItemWidth { get { return Padding.Left + Padding.Right + ItemSize.Width; } } + private double ActualItemHeight { get { return Padding.Top + Padding.Bottom + ItemSize.Height; } } + + private void CreateVisuals() + { + _panelAnimation = new DoubleAnimation(); + Storyboard.SetTarget(_panelAnimation, _panningTransform); + Storyboard.SetTargetProperty(_panelAnimation, new PropertyPath("Y")); + + _panelStoryboard = new Storyboard(); + _panelStoryboard.Children.Add(_panelAnimation); + _panelStoryboard.Completed += PanelStoryboardCompleted; + } + + private void CreateEventHandlers() + { + + SizeChanged += OnSizeChanged; + + this.ManipulationStarted += OnManipulationStarted; + this.ManipulationCompleted += OnManipulationCompleted; + this.ManipulationDelta += OnManipulationDelta; + + this.Tap += OnTap; + + AddHandler(MouseLeftButtonDownEvent, new MouseButtonEventHandler(LoopingSelector_MouseLeftButtonDown), true); + AddHandler(MouseLeftButtonUpEvent, new MouseButtonEventHandler(LoopingSelector_MouseLeftButtonUp), true); + } + + private LoopingSelectorItem CreateAndAddItem(Panel parent, object content) + { + bool reuse = _temporaryItemsPool != null && _temporaryItemsPool.Count > 0; + + LoopingSelectorItem wrapper = reuse ? _temporaryItemsPool.Dequeue() : new LoopingSelectorItem(); + + if (!reuse) + { + wrapper.ContentTemplate = this.ItemTemplate; + wrapper.Width = ItemSize.Width; + wrapper.Height = ItemSize.Height; + wrapper.Padding = ItemMargin; + + wrapper.Click += OnWrapperClick; + } + + wrapper.DataContext = wrapper.Content = content; + + parent.Children.Add(wrapper); // Need to do this before calling ApplyTemplate + if (!reuse) + { + wrapper.ApplyTemplate(); + } + + return wrapper; + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/Primitives/LoopingSelectorDataSource.cs b/Microsoft.Phone.Controls.Toolkit/Primitives/LoopingSelectorDataSource.cs new file mode 100644 index 0000000..493af84 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Primitives/LoopingSelectorDataSource.cs @@ -0,0 +1,41 @@ +// (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.Windows.Controls; + +namespace Microsoft.Phone.Controls.Primitives +{ + /// + /// Defines how the LoopingSelector communicates with data source. + /// + /// Preview + public interface ILoopingSelectorDataSource + { + /// + /// Get the next datum, relative to an existing datum. + /// + /// The datum the return value will be relative to. + /// The next datum. + object GetNext(object relativeTo); + + /// + /// Get the previous datum, relative to an existing datum. + /// + /// The datum the return value will be relative to. + /// The previous datum. + object GetPrevious(object relativeTo); + + /// + /// The selected item. Should never be null. + /// + object SelectedItem { get; set; } + + /// + /// Raised when the selection changes. + /// + event EventHandler SelectionChanged; + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/Primitives/LoopingSelectorItem.cs b/Microsoft.Phone.Controls.Toolkit/Primitives/LoopingSelectorItem.cs new file mode 100644 index 0000000..96fc22e --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Primitives/LoopingSelectorItem.cs @@ -0,0 +1,186 @@ +// (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.Windows; +using System.Windows.Controls; +using System.Windows.Input; +using System.Windows.Media; + +namespace Microsoft.Phone.Controls.Primitives +{ + /// + /// The items that will be contained in the LoopingSelector. + /// + /// Preview + [TemplatePart(Name = TransformPartName, Type = typeof(TranslateTransform))] + [TemplateVisualState(GroupName = CommonGroupName, Name = NormalStateName)] + [TemplateVisualState(GroupName = CommonGroupName, Name = ExpandedStateName)] + [TemplateVisualState(GroupName = CommonGroupName, Name = SelectedStateName)] + public class LoopingSelectorItem : ContentControl + { + private const string TransformPartName = "Transform"; + + private const string CommonGroupName = "Common"; + private const string NormalStateName = "Normal"; + private const string ExpandedStateName = "Expanded"; + private const string SelectedStateName = "Selected"; + + private bool _shouldClick; + + /// + /// The states that this can be in. + /// + internal enum State + { + /// + /// Not visible + /// + Normal, + /// + /// Visible + /// + Expanded, + /// + /// Selected + /// + Selected + }; + private State _state; + + /// + /// Create a new LoopingSelectorItem. + /// + public LoopingSelectorItem() + { + DefaultStyleKey = typeof(LoopingSelectorItem); + + MouseLeftButtonDown += LoopingSelectorItem_MouseLeftButtonDown; + MouseLeftButtonUp += LoopingSelectorItem_MouseLeftButtonUp; + LostMouseCapture += LoopingSelectorItem_LostMouseCapture; + Tap += LoopingSelectorItem_Tap; + } + + /// + /// Put this item into a new state. + /// + /// The new state. + /// Flag indicating that transitions should be used when going to the new state. + internal void SetState(State newState, bool useTransitions) + { + if (_state != newState) + { + _state = newState; + switch (_state) + { + case State.Normal: + VisualStateManager.GoToState(this, NormalStateName, useTransitions); + break; + case State.Expanded: + VisualStateManager.GoToState(this, ExpandedStateName, useTransitions); + break; + case State.Selected: + VisualStateManager.GoToState(this, SelectedStateName, useTransitions); + break; + } + } + } + + /// + /// Returns the current state. + /// + /// The current state. + internal State GetState() { return _state; } + + void LoopingSelectorItem_Tap(object sender, System.Windows.Input.GestureEventArgs e) + { + e.Handled = true; + } + + void LoopingSelectorItem_MouseLeftButtonDown(object sender, MouseButtonEventArgs e) + { + CaptureMouse(); + _shouldClick = true; + } + + void LoopingSelectorItem_MouseLeftButtonUp(object sender, MouseButtonEventArgs e) + { + ReleaseMouseCapture(); + + if (_shouldClick) + { + _shouldClick = false; + SafeRaise.Raise(Click, this); + } + } + + void LoopingSelectorItem_LostMouseCapture(object sender, MouseEventArgs e) + { + _shouldClick = false; + } + + /// + /// The Click event. This is needed because there is no gesture for touch-down, pause + /// longer than the Hold time, and touch-up. Tap will not be raise, and Hold is not + /// adequate. + /// + public event EventHandler Click; + + /// + /// Override of OnApplyTemplate + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + Transform = GetTemplateChild(TransformPartName) as TranslateTransform ?? new TranslateTransform(); + } + + internal LoopingSelectorItem Previous { get; private set; } + internal LoopingSelectorItem Next { get; private set; } + + internal void Remove() + { + if (Previous != null) + { + Previous.Next = Next; + } + if (Next != null) + { + Next.Previous = Previous; + } + Next = Previous = null; + } + + internal void InsertAfter(LoopingSelectorItem after) + { + Next = after.Next; + Previous = after; + + if (after.Next != null) + { + after.Next.Previous = this; + } + + after.Next = this; + } + + internal void InsertBefore(LoopingSelectorItem before) + { + Next = before; + Previous = before.Previous; + + if (before.Previous != null) + { + before.Previous.Next = this; + } + + before.Previous = this; + } + + internal TranslateTransform Transform { get; private set; } + } + +} diff --git a/Microsoft.Phone.Controls.Toolkit/Primitives/ToggleSwitchButton.cs b/Microsoft.Phone.Controls.Toolkit/Primitives/ToggleSwitchButton.cs new file mode 100644 index 0000000..14645d2 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Primitives/ToggleSwitchButton.cs @@ -0,0 +1,344 @@ +// (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.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Media; + +namespace Microsoft.Phone.Controls.Primitives +{ + /// + /// Represents a switch that can be toggled between two states. + /// + /// Preview + [TemplateVisualState(Name = NormalState, GroupName = CommonStates)] + [TemplateVisualState(Name = DisabledState, GroupName = CommonStates)] + [TemplateVisualState(Name = CheckedState, GroupName = CheckStates)] + [TemplateVisualState(Name = DraggingState, GroupName = CheckStates)] + [TemplateVisualState(Name = UncheckedState, GroupName = CheckStates)] + [TemplatePart(Name = SwitchRootPart, Type = typeof(Grid))] + [TemplatePart(Name = SwitchBackgroundPart, Type = typeof(UIElement))] + [TemplatePart(Name = SwitchTrackPart, Type = typeof(Grid))] + [TemplatePart(Name = SwitchThumbPart, Type = typeof(FrameworkElement))] + public class ToggleSwitchButton : ToggleButton + { + /// + /// Common visual states. + /// + private const string CommonStates = "CommonStates"; + + /// + /// Normal visual state. + /// + private const string NormalState = "Normal"; + + /// + /// Disabled visual state. + /// + private const string DisabledState = "Disabled"; + + /// + /// Check visual states. + /// + private const string CheckStates = "CheckStates"; + + /// + /// Checked visual state. + /// + private const string CheckedState = "Checked"; + + /// + /// Dragging visual state. + /// + private const string DraggingState = "Dragging"; + + /// + /// Unchecked visual state. + /// + private const string UncheckedState = "Unchecked"; + + /// + /// Switch root template part name. + /// + private const string SwitchRootPart = "SwitchRoot"; + + /// + /// Switch background template part name. + /// + private const string SwitchBackgroundPart = "SwitchBackground"; + + /// + /// Switch track template part name. + /// + private const string SwitchTrackPart = "SwitchTrack"; + + /// + /// Switch thumb template part name. + /// + private const string SwitchThumbPart = "SwitchThumb"; + + /// + /// Identifies the SwitchForeground dependency property. + /// + public static readonly DependencyProperty SwitchForegroundProperty = + DependencyProperty.Register("SwitchForeground", typeof(Brush), typeof(ToggleSwitchButton), new PropertyMetadata(null)); + + /// + /// Gets or sets the switch foreground. + /// + public Brush SwitchForeground + { + get + { + return (Brush)GetValue(SwitchForegroundProperty); + } + set + { + SetValue(SwitchForegroundProperty, value); + } + } + + /// + /// The background TranslateTransform. + /// + private TranslateTransform _backgroundTranslation; + + /// + /// The thumb TranslateTransform. + /// + private TranslateTransform _thumbTranslation; + + /// + /// The root template part. + /// + private Grid _root; + + /// + /// The track template part. + /// + private Grid _track; + + /// + /// The thumb template part. + /// + private FrameworkElement _thumb; + + /// + /// The minimum translation. + /// + private const double _uncheckedTranslation = 0; + + /// + /// The maximum translation. + /// + private double _checkedTranslation; + + /// + /// The drag translation. + /// + private double _dragTranslation; + + /// + /// Whether the translation ever changed during the drag. + /// + private bool _wasDragged; + + /// + /// Whether the dragging state is current. + /// + private bool _isDragging; + + /// + /// Initializes a new instance of the ToggleSwitch class. + /// + public ToggleSwitchButton() + { + DefaultStyleKey = typeof(ToggleSwitchButton); + } + + /// + /// Gets and sets the thumb and background translation. + /// + /// The translation. + private double Translation + { + get + { + return _backgroundTranslation == null ? _thumbTranslation.X : _backgroundTranslation.X; + } + set + { + if (_backgroundTranslation != null) + { + _backgroundTranslation.X = value; + } + + if (_thumbTranslation != null) + { + _thumbTranslation.X = value; + } + } + } + + /// + /// Change the visual state. + /// + /// Indicates whether to use animation transitions. + private void ChangeVisualState(bool useTransitions) + { + if (IsEnabled) + { + VisualStateManager.GoToState(this, NormalState, useTransitions); + } + else + { + VisualStateManager.GoToState(this, DisabledState, useTransitions); + } + + if (_isDragging) + { + VisualStateManager.GoToState(this, DraggingState, useTransitions); + } + else if (IsChecked ?? false) + { + VisualStateManager.GoToState(this, CheckedState, useTransitions); + } + else + { + VisualStateManager.GoToState(this, UncheckedState, useTransitions); + } + } + + /// + /// Called by the OnClick method to implement toggle behavior. + /// + protected override void OnToggle() + { + IsChecked = !(IsChecked ?? false); + ChangeVisualState(true); + } + + /// + /// Gets all the template parts and initializes the corresponding state. + /// + public override void OnApplyTemplate() + { + if (_track != null) + { + _track.SizeChanged -= OnSizeChanged; + } + if (_thumb != null) + { + _thumb.SizeChanged -= OnSizeChanged; + } + if (_root != null) + { + _root.ManipulationStarted -= OnManipulationStarted; + _root.ManipulationDelta -= OnManipulationDelta; + _root.ManipulationCompleted -= OnManipulationCompleted; + } + + base.OnApplyTemplate(); + + _root = GetTemplateChild(SwitchRootPart) as Grid; + UIElement background = GetTemplateChild(SwitchBackgroundPart) as UIElement; + _backgroundTranslation = background == null ? null : background.RenderTransform as TranslateTransform; + _track = GetTemplateChild(SwitchTrackPart) as Grid; + _thumb = GetTemplateChild(SwitchThumbPart) as Border; + _thumbTranslation = _thumb == null ? null : _thumb.RenderTransform as TranslateTransform; + + if (_root != null && _track != null && _thumb != null && (_backgroundTranslation != null || _thumbTranslation != null)) + { + _root.ManipulationStarted += OnManipulationStarted; + _root.ManipulationDelta += OnManipulationDelta; + _root.ManipulationCompleted += OnManipulationCompleted; + + _track.SizeChanged += OnSizeChanged; + _thumb.SizeChanged += OnSizeChanged; + } + ChangeVisualState(false); + } + + /// + /// Handles started drags on the root. + /// + /// The event sender. + /// The event information. + private void OnManipulationStarted(object sender, System.Windows.Input.ManipulationStartedEventArgs e) + { + e.Handled = true; + _isDragging = true; + _dragTranslation = Translation; + ChangeVisualState(true); + Translation = _dragTranslation; + } + + /// + /// Handles drags on the root. + /// + /// The event sender. + /// The event information. + private void OnManipulationDelta(object sender, System.Windows.Input.ManipulationDeltaEventArgs e) + { + e.Handled = true; + double horizontalChange = e.DeltaManipulation.Translation.X; + Orientation direction = Math.Abs(horizontalChange) >= Math.Abs(e.DeltaManipulation.Translation.Y) ? Orientation.Horizontal : Orientation.Vertical; + if (direction == Orientation.Horizontal && horizontalChange != 0) + { + _wasDragged = true; + _dragTranslation += horizontalChange; + Translation = Math.Max(_uncheckedTranslation, Math.Min(_checkedTranslation, _dragTranslation)); + } + } + + private void OnManipulationCompleted(object sender, System.Windows.Input.ManipulationCompletedEventArgs e) + { + e.Handled = true; + _isDragging = false; + bool click = false; + if (_wasDragged) + { + double edge = (IsChecked ?? false) ? _checkedTranslation : _uncheckedTranslation; + if (Translation != edge) + { + click = true; + } + } + else + { + click = true; + } + if (click) + { + OnClick(); + } + + _wasDragged = false; + } + + /// + /// Handles the mouse leave event. + /// + /// The event arguments. + protected override void OnMouseLeave(System.Windows.Input.MouseEventArgs e) + { + // Overrides parent method which causes events to fire multiple times. + } + + /// + /// Handles changed sizes for the track and the thumb. + /// Sets the clip of the track and computes the indeterminate and checked translations. + /// + /// The event sender. + /// The event information. + private void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + _track.Clip = new RectangleGeometry { Rect = new Rect(0, 0, _track.ActualWidth, _track.ActualHeight) }; + _checkedTranslation = _track.ActualWidth - _thumb.ActualWidth - _thumb.Margin.Left - _thumb.Margin.Right; + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/ProgressBar/PerformanceProgressBar.cs b/Microsoft.Phone.Controls.Toolkit/ProgressBar/PerformanceProgressBar.cs new file mode 100644 index 0000000..066d734 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/ProgressBar/PerformanceProgressBar.cs @@ -0,0 +1,243 @@ +// (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.Collections.Generic; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Data; + +namespace Microsoft.Phone.Controls +{ + /// + /// A progress bar implementation for a smoother appearance of the + /// indeterminate states, with the added behavior that after the behavior + /// is no longer needed, it smoothly fades out the dots for a less jarring + /// experience. No exposed Value property. + /// + /// + /// Important - this control is not intended for regular progress + /// bar use, but only indeterminate. As a result, only an IsIndeterminate + /// set of visual states are defined in the nested progress bar template. + /// Use the standard ProgressBar control in the platform for determinate + /// scenarios as there is no performance benefit in determinate mode. + /// + /// Preview + [TemplateVisualState(GroupName = VisualStateGroupName, Name = NormalState)] + [TemplateVisualState(GroupName = VisualStateGroupName, Name = HiddenState)] + [TemplatePart(Name = EmbeddedProgressBarName, Type = typeof(ProgressBar))] + public class PerformanceProgressBar : Control + { + // Name of embedded progress bar. + private const string EmbeddedProgressBarName = "EmbeddedProgressBar"; + + // Embedded progress bar. + private ProgressBar _progressBar; + + // Embedded progress bar binding - property path. + private static readonly PropertyPath ActualIsIndeterminatePath = new PropertyPath("ActualIsIndeterminate"); + + #region Visual States + private const string VisualStateGroupName = "VisibilityStates"; + private const string NormalState = "Normal"; + private const string HiddenState = "Hidden"; + #endregion + + /// + /// The visual state group reference used to wait until the hidden state + /// has fully transitioned to flip the underlying progress bar's + /// indeterminate value to False. + /// + private VisualStateGroup _visualStateGroup; + + /// + /// Gets or sets a value indicating the cached IsIndeterminate. + /// + private bool _cachedIsIndeterminate; + + /// + /// Gets or sets a value indicating the cached IsIndeterminate binding expression. + /// + private BindingExpression _cachedIsIndeterminateBindingExpression; + + #region public bool ActualIsIndeterminate + /// + /// Gets or sets the value indicating whether the actual indeterminate + /// property should be reflecting a particular value. + /// + public bool ActualIsIndeterminate + { + get { return (bool)GetValue(ActualIsIndeterminateProperty); } + set { SetValue(ActualIsIndeterminateProperty, value); } + } + + /// + /// Identifies the ActualIsIndeterminate dependency property. + /// + public static readonly DependencyProperty ActualIsIndeterminateProperty = + DependencyProperty.Register( + "ActualIsIndeterminate", + typeof(bool), + typeof(PerformanceProgressBar), + new PropertyMetadata(false)); + #endregion public bool ActualIsIndeterminate + + #region public bool IsIndeterminate + /// + /// Gets or sets a value indicating whether the control is in the + /// indeterminate state. + /// + public bool IsIndeterminate + { + get { return (bool)GetValue(IsIndeterminateProperty); } + set { SetValue(IsIndeterminateProperty, value); } + } + + /// + /// Identifies the IsIndeterminate dependency property. + /// + public static readonly DependencyProperty IsIndeterminateProperty = + DependencyProperty.Register( + "IsIndeterminate", + typeof(bool), + typeof(PerformanceProgressBar), + new PropertyMetadata(false, OnIsIndeterminatePropertyChanged)); + + /// + /// IsIndeterminateProperty property changed handler. + /// + /// PerformanceProgressBar that changed its IsIndeterminate. + /// Event arguments. + private static void OnIsIndeterminatePropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + PerformanceProgressBar source = d as PerformanceProgressBar; + if (source != null) + { + source.OnIsIndeterminateChanged((bool)e.NewValue); + } + } + #endregion public bool IsIndeterminate + + /// + /// Initializes a new instance of the PerformanceProgressBar type. + /// + public PerformanceProgressBar() + : base() + { + DefaultStyleKey = typeof(PerformanceProgressBar); + + Unloaded += OnUnloaded; + Loaded += OnLoaded; + } + + /// + /// Applies the template and extracts both a visual state and a template + /// part. + /// + public override void OnApplyTemplate() + { + if (_visualStateGroup != null) + { + _visualStateGroup.CurrentStateChanged -= OnVisualStateChanged; + } + + base.OnApplyTemplate(); + + _visualStateGroup = VisualStates.TryGetVisualStateGroup(this, VisualStateGroupName); + if (_visualStateGroup != null) + { + _visualStateGroup.CurrentStateChanged += OnVisualStateChanged; + } + + _progressBar = GetTemplateChild(EmbeddedProgressBarName) as ProgressBar; + _cachedIsIndeterminateBindingExpression = GetBindingExpression(PerformanceProgressBar.IsIndeterminateProperty); + + UpdateVisualStates(false); + } + + private void OnVisualStateChanged(object sender, VisualStateChangedEventArgs e) + { + // Turn off the visuals, the transition to hidden is complete. + if (ActualIsIndeterminate && e != null && e.NewState != null && e.NewState.Name == HiddenState && !IsIndeterminate) + { + ActualIsIndeterminate = false; + } + } + + private void OnIsIndeterminateChanged(bool newValue) + { + if (newValue) + { + ActualIsIndeterminate = true; + _cachedIsIndeterminate = true; + } + else if (ActualIsIndeterminate && _visualStateGroup == null) + { + ActualIsIndeterminate = false; + _cachedIsIndeterminate = false; + } + // else: visual state changed handler will take care of this. + + UpdateVisualStates(true); + } + + private void UpdateVisualStates(bool useTransitions) + { + VisualStateManager.GoToState( + this, + IsIndeterminate ? NormalState : HiddenState, + useTransitions); + } + + private void OnUnloaded(object sender, RoutedEventArgs ea) + { + if (_progressBar != null) + { + _cachedIsIndeterminate = IsIndeterminate; + _cachedIsIndeterminateBindingExpression = GetBindingExpression(PerformanceProgressBar.IsIndeterminateProperty); + _progressBar.IsIndeterminate = false; + } + } + + private void OnLoaded(object sender, RoutedEventArgs ea) + { + if (_progressBar != null) + { + if (_cachedIsIndeterminateBindingExpression != null) + { + SetBinding(PerformanceProgressBar.IsIndeterminateProperty, _cachedIsIndeterminateBindingExpression.ParentBinding); + } + else + { + IsIndeterminate = _cachedIsIndeterminate; + } + + _progressBar.SetBinding(ProgressBar.IsIndeterminateProperty, new Binding() { Source = this, Path = ActualIsIndeterminatePath }); + } + } + + private static T FindFirstChildOfType(DependencyObject root) where T : class + { + var queue = new Queue(); + queue.Enqueue(root); + while (0 < queue.Count) + { + var current = queue.Dequeue(); + for (var i = VisualTreeHelper.GetChildrenCount(current) - 1; 0 <= i; i--) + { + var child = VisualTreeHelper.GetChild(current, i); + var typedChild = child as T; + if (null != typedChild) + { + return typedChild; + } + queue.Enqueue(child); + } + } + + return null; + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/ProgressBar/RelativeAnimatingContentControl.cs b/Microsoft.Phone.Controls.Toolkit/ProgressBar/RelativeAnimatingContentControl.cs new file mode 100644 index 0000000..08a85a4 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/ProgressBar/RelativeAnimatingContentControl.cs @@ -0,0 +1,467 @@ +// (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.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Animation; + +// This is a very special primitive control that works around a limitation in +// the core animation subsystem of Silverlight: there is no way to declare in +// VSM states relative properties, such as animating from 0 to 33% the width of +// the control, using double animations for translation. +// +// It's a tough problem to solve property, but this primitive, unsupported +// control does offer a solution based on magic numbers that still allows a +// designer to make alterations to their animation values to present their +// vision for custom templates. +// +// This is instrumental in offering a Windows Phone ProgressBar implementation +// that uses the render thread instead of animating UI thread-only properties. +// +// This control is not supported other than that it is used by the performance +// progress bar control. It should not be used elsewhere. +// + +namespace Microsoft.Phone.Controls.Primitives +{ + /// + /// A very specialized primitive control that works around a specific visual + /// state manager issue. The platform does not support relative sized + /// translation values, and this special control walks through visual state + /// animation storyboards looking for magic numbers to use as percentages. + /// This control is not supported, unofficial, and is a hack in many ways. + /// It is used to enable a Windows Phone native platform-style progress bar + /// experience in indeterminate mode that remains performant. + /// + /// Preview + public class RelativeAnimatingContentControl : ContentControl + { + /// + /// A simple Epsilon-style value used for trying to determine the magic + /// state, if any, of a double. + /// + private const double SimpleDoubleComparisonEpsilon = 0.000009; + + /// + /// The last known width of the control. + /// + private double _knownWidth; + + /// + /// The last known height of the control. + /// + private double _knownHeight; + + /// + /// A set of custom animation adapters used to update the animation + /// storyboards when the size of the control changes. + /// + private List _specialAnimations; + + /// + /// Initializes a new instance of the RelativeAnimatingContentControl + /// type. + /// + public RelativeAnimatingContentControl() + { + SizeChanged += OnSizeChanged; + } + + /// + /// Handles the size changed event. + /// + /// The source object. + /// The event arguments. + private void OnSizeChanged(object sender, SizeChangedEventArgs e) + { + if (e != null && e.NewSize.Height > 0 && e.NewSize.Width > 0) + { + _knownWidth = e.NewSize.Width; + _knownHeight = e.NewSize.Height; + + Clip = new RectangleGeometry { Rect = new Rect(0, 0, _knownWidth, _knownHeight), }; + + UpdateAnyAnimationValues(); + } + } + + /// + /// Walks through the known storyboards in the control's template that + /// may contain magic double animation values, storing them for future + /// use and updates. + /// + private void UpdateAnyAnimationValues() + { + if (_knownHeight > 0 && _knownWidth > 0) + { + // Initially, before any special animations have been found, + // the visual state groups of the control must be explored. + // By definition they must be at the implementation root of the + // control, and this is designed to not walk into any other + // depth. + if (_specialAnimations == null) + { + _specialAnimations = new List(); + + foreach (VisualStateGroup group in VisualStateManager.GetVisualStateGroups(this)) + { + if (group == null) + { + continue; + } + foreach (VisualState state in group.States) + { + if (state != null) + { + Storyboard sb = state.Storyboard; + if (sb != null) + { + // Examine all children of the storyboards, + // looking for either type of double + // animation. + foreach (Timeline timeline in sb.Children) + { + DoubleAnimation da = timeline as DoubleAnimation; + DoubleAnimationUsingKeyFrames dakeys = timeline as DoubleAnimationUsingKeyFrames; + if (da != null) + { + ProcessDoubleAnimation(da); + } + else if (dakeys != null) + { + ProcessDoubleAnimationWithKeys(dakeys); + } + } + } + } + } + } + } + + // Update special animation values relative to the current size. + UpdateKnownAnimations(); + } + } + + /// + /// Walks through all special animations, updating based on the current + /// size of the control. + /// + private void UpdateKnownAnimations() + { + foreach (AnimationValueAdapter adapter in _specialAnimations) + { + adapter.UpdateWithNewDimension(_knownWidth, _knownHeight); + } + } + + /// + /// Processes a double animation with keyframes, looking for known + /// special values to store with an adapter. + /// + /// The double animation using key frames instance. + private void ProcessDoubleAnimationWithKeys(DoubleAnimationUsingKeyFrames da) + { + // Look through all keyframes in the instance. + foreach (DoubleKeyFrame frame in da.KeyFrames) + { + var d = DoubleAnimationFrameAdapter.GetDimensionFromMagicNumber(frame.Value); + if (d.HasValue) + { + _specialAnimations.Add(new DoubleAnimationFrameAdapter(d.Value, frame)); + } + } + } + + /// + /// Processes a double animation looking for special values. + /// + /// The double animation instance. + private void ProcessDoubleAnimation(DoubleAnimation da) + { + // Look for a special value in the To property. + if (da.To.HasValue) + { + var d = DoubleAnimationToAdapter.GetDimensionFromMagicNumber(da.To.Value); + if (d.HasValue) + { + _specialAnimations.Add(new DoubleAnimationToAdapter(d.Value, da)); + } + } + + // Look for a special value in the From property. + if (da.From.HasValue) + { + var d = DoubleAnimationFromAdapter.GetDimensionFromMagicNumber(da.To.Value); + if (d.HasValue) + { + _specialAnimations.Add(new DoubleAnimationFromAdapter(d.Value, da)); + } + } + } + + #region Private animation updating system + /// + /// A selection of dimensions of interest for updating an animation. + /// + private enum DoubleAnimationDimension + { + /// + /// The width (horizontal) dimension. + /// + Width, + + /// + /// The height (vertical) dimension. + /// + Height, + } + + /// + /// A simple class designed to store information about a specific + /// animation instance and its properties. Able to update the values at + /// runtime. + /// + private abstract class AnimationValueAdapter + { + /// + /// Initializes a new instance of the AnimationValueAdapter type. + /// + /// The dimension of interest for updates. + public AnimationValueAdapter(DoubleAnimationDimension dimension) + { + Dimension = dimension; + } + + /// + /// Gets the dimension of interest for the control. + /// + public DoubleAnimationDimension Dimension { get; private set; } + + /// + /// Updates the original instance based on new dimension information + /// from the control. Takes both and allows the subclass to make the + /// decision on which ratio, values, and dimension to use. + /// + /// The width of the control. + /// The height of the control. + public abstract void UpdateWithNewDimension(double width, double height); + } + + private abstract class GeneralAnimationValueAdapter : AnimationValueAdapter + { + /// + /// Stores the animation instance. + /// + protected T Instance { get; set; } + + /// + /// Gets the value of the underlying property of interest. + /// + /// Returns the value of the property. + protected abstract double GetValue(); + + /// + /// Sets the value for the underlying property of interest. + /// + /// The new value for the property. + protected abstract void SetValue(double newValue); + + /// + /// Gets the initial value (minus the magic number portion) that the + /// designer stored within the visual state animation property. + /// + protected double InitialValue { get; private set; } + + /// + /// The ratio based on the original magic value, used for computing + /// the updated animation property of interest when the size of the + /// control changes. + /// + private double _ratio; + + /// + /// Initializes a new instance of the GeneralAnimationValueAdapter + /// type. + /// + /// The dimension of interest. + /// The animation type instance. + [SuppressMessage("Microsoft.Usage", "CA2214:DoNotCallOverridableMethodsInConstructors", Justification = "Should not have any undesirable side-effects.")] + public GeneralAnimationValueAdapter(DoubleAnimationDimension d, T instance) + : base(d) + { + Instance = instance; + + InitialValue = StripMagicNumberOff(GetValue()); + _ratio = InitialValue / 100; + } + + /// + /// Approximately removes the magic number state from a value. + /// + /// The initial number. + /// Returns a double with an adjustment for the magic + /// portion of the number. + public double StripMagicNumberOff(double number) + { + return Dimension == DoubleAnimationDimension.Width ? number - .1 : number - .2; + } + + /// + /// Retrieves the dimension, if any, from the number. If the number + /// is not magic, null is returned instead. + /// + /// The double value. + /// Returs a double animation dimension, if the number was + /// partially magic; otherwise, returns null. + public static DoubleAnimationDimension? GetDimensionFromMagicNumber(double number) + { + double round = Math.Round(number); + double remainder = Math.Abs(number - round); + + if (remainder >= .1 - SimpleDoubleComparisonEpsilon && remainder <= .1 + SimpleDoubleComparisonEpsilon) + { + return DoubleAnimationDimension.Width; + } + if (remainder >= .2 - SimpleDoubleComparisonEpsilon && remainder <= .2 + SimpleDoubleComparisonEpsilon) + { + return DoubleAnimationDimension.Height; + } + return null; + } + + /// + /// Updates the animation instance based on the dimensions of the + /// control. + /// + /// The width of the control. + /// The height of the control. + public override void UpdateWithNewDimension(double width, double height) + { + double size = Dimension == DoubleAnimationDimension.Width ? width : height; + UpdateValue(size); + } + + /// + /// Updates the value of the property. + /// + /// The size of interest to use with a ratio + /// computation. + private void UpdateValue(double sizeToUse) + { + SetValue(sizeToUse * _ratio); + } + } + + /// + /// Adapter for DoubleAnimation's To property. + /// + private class DoubleAnimationToAdapter : GeneralAnimationValueAdapter + { + /// + /// Gets the value of the underlying property of interest. + /// + /// Returns the value of the property. + protected override double GetValue() + { + return (double)Instance.To; + } + + /// + /// Sets the value for the underlying property of interest. + /// + /// The new value for the property. + protected override void SetValue(double newValue) + { + Instance.To = newValue; + } + + /// + /// Initializes a new instance of the DoubleAnimationToAdapter type. + /// + /// The dimension of interest. + /// The instance of the animation type. + public DoubleAnimationToAdapter(DoubleAnimationDimension dimension, DoubleAnimation instance) + : base(dimension, instance) + { + } + } + + /// + /// Adapter for DoubleAnimation's From property. + /// + private class DoubleAnimationFromAdapter : GeneralAnimationValueAdapter + { + /// + /// Gets the value of the underlying property of interest. + /// + /// Returns the value of the property. + protected override double GetValue() + { + return (double)Instance.From; + } + + /// + /// Sets the value for the underlying property of interest. + /// + /// The new value for the property. + protected override void SetValue(double newValue) + { + Instance.From = newValue; + } + + /// + /// Initializes a new instance of the DoubleAnimationFromAdapter + /// type. + /// + /// The dimension of interest. + /// The instance of the animation type. + public DoubleAnimationFromAdapter(DoubleAnimationDimension dimension, DoubleAnimation instance) + : base(dimension, instance) + { + } + } + + /// + /// Adapter for double key frames. + /// + private class DoubleAnimationFrameAdapter : GeneralAnimationValueAdapter + { + /// + /// Gets the value of the underlying property of interest. + /// + /// Returns the value of the property. + protected override double GetValue() + { + return Instance.Value; + } + + /// + /// Sets the value for the underlying property of interest. + /// + /// The new value for the property. + protected override void SetValue(double newValue) + { + Instance.Value = newValue; + } + + /// + /// Initializes a new instance of the DoubleAnimationFrameAdapter + /// type. + /// + /// The dimension of interest. + /// The instance of the animation type. + public DoubleAnimationFrameAdapter(DoubleAnimationDimension dimension, DoubleKeyFrame frame) + : base(dimension, frame) + { + } + } + #endregion + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/Properties/AssemblyInfo.cs b/Microsoft.Phone.Controls.Toolkit/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..1cc1f6c --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Properties/AssemblyInfo.cs @@ -0,0 +1,34 @@ +// (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.Reflection; +using System.Resources; +using System.Runtime.InteropServices; +using System.Windows.Markup; + +[assembly: AssemblyTitle("Microsoft.Phone.Controls.Toolkit")] +[assembly: AssemblyDescription("Windows Phone Toolkit")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("Microsoft Corporation")] +[assembly: AssemblyProduct("Microsoft® Windows Phone")] +[assembly: AssemblyCopyright("© Microsoft Corporation. All rights reserved.")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +[assembly: ComVisible(false)] + +[assembly: Guid("c3074b8e-8b70-4c3a-8a8b-e0cc41a244ae")] + +[assembly: AssemblyVersion("7.0.0.0")] +[assembly: AssemblyFileVersion("4.0.0.0")] + +[assembly: CLSCompliant(false)] // IApplicationBar is not CLS-compliant, but its use matches the type of the platform's PhoneApplicationPage.ApplicationBar property +[assembly: NeutralResourcesLanguage("en-US")] + +[assembly: XmlnsPrefix("clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit", "toolkit")] +[assembly: XmlnsDefinition("clr-namespace:Microsoft.Phone.Controls;assembly=Microsoft.Phone.Controls.Toolkit", "Microsoft.Phone.Controls")] +[assembly: XmlnsPrefix("clr-namespace:Microsoft.Phone.Controls.Primitives;assembly=Microsoft.Phone.Controls.Toolkit", "toolkitPrimitives")] +[assembly: XmlnsDefinition("clr-namespace:Microsoft.Phone.Controls.Primitives;assembly=Microsoft.Phone.Controls.Toolkit", "Microsoft.Phone.Controls.Primitives")] diff --git a/Microsoft.Phone.Controls.Toolkit/Properties/Resources.Designer.cs b/Microsoft.Phone.Controls.Toolkit/Properties/Resources.Designer.cs new file mode 100644 index 0000000..84f587e --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Properties/Resources.Designer.cs @@ -0,0 +1,234 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.235 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Microsoft.Phone.Controls.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Microsoft.Phone.Controls.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Invalid FilterMode enumeration value. The value must be one of the defined AutoCompleteFilterMode values to be accepted.. + /// + internal static string AutoComplete_OnFilterModePropertyChanged_InvalidValue { + get { + return ResourceManager.GetString("AutoComplete_OnFilterModePropertyChanged_InvalidValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid maximum drop down height value '{0}'. The value must be greater than or equal to zero.. + /// + internal static string AutoComplete_OnMaxDropDownHeightPropertyChanged_InvalidValue { + get { + return ResourceManager.GetString("AutoComplete_OnMaxDropDownHeightPropertyChanged_InvalidValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid MinimumPopulateDelay value '{0}'. The value must be greater than or equal to zero.. + /// + internal static string AutoComplete_OnMinimumPopulateDelayPropertyChanged_InvalidValue { + get { + return ResourceManager.GetString("AutoComplete_OnMinimumPopulateDelayPropertyChanged_InvalidValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Cannot set read-only property SearchText.. + /// + internal static string AutoComplete_OnSearchTextPropertyChanged_InvalidWrite { + get { + return ResourceManager.GetString("AutoComplete_OnSearchTextPropertyChanged_InvalidWrite", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid type. Argument must be of the type System.DateTime.. + /// + internal static string InvalidDateTimeArgument { + get { + return ResourceManager.GetString("InvalidDateTimeArgument", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid operation. The IsExpanded property cannot be modified when IsNonExpandable is set to true.. + /// + internal static string InvalidExpanderViewOperation { + get { + return ResourceManager.GetString("InvalidExpanderViewOperation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid argument. The number of months should be greater than 1 and less than 12.. + /// + internal static string InvalidNumberOfMonths { + get { + return ResourceManager.GetString("InvalidNumberOfMonths", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid argument. The number of time units should be greater than 1.. + /// + internal static string InvalidNumberOfTimeUnits { + get { + return ResourceManager.GetString("InvalidNumberOfTimeUnits", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to SelectedIndex must always be set to a valid value.. + /// + internal static string InvalidSelectedIndex { + get { + return ResourceManager.GetString("InvalidSelectedIndex", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to SelectedItem must always be set to a valid value.. + /// + internal static string InvalidSelectedItem { + get { + return ResourceManager.GetString("InvalidSelectedItem", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid argument. Future dates and times are not supported.. + /// + internal static string NonSupportedDateTime { + get { + return ResourceManager.GetString("NonSupportedDateTime", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Off. + /// + internal static string Off { + get { + return ResourceManager.GetString("Off", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to On. + /// + internal static string On { + get { + return ResourceManager.GetString("On", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The RoutedPropertyChangingEvent cannot be canceled!. + /// + internal static string RoutedPropertyChangingEventArgs_CancelSet_InvalidOperation { + get { + return ResourceManager.GetString("RoutedPropertyChangingEventArgs_CancelSet_InvalidOperation", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' is unable to convert '{1}' to '{2}'.. + /// + internal static string TypeConverters_Convert_CannotConvert { + get { + return ResourceManager.GetString("TypeConverters_Convert_CannotConvert", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to '{0}' cannot convert from '{1}'.. + /// + internal static string TypeConverters_ConvertFrom_CannotConvertFromType { + get { + return ResourceManager.GetString("TypeConverters_ConvertFrom_CannotConvertFromType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to The type was unexpected.. + /// + internal static string UnexpectedType { + get { + return ResourceManager.GetString("UnexpectedType", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid length value '{0}'.. + /// + internal static string WrapPanel_OnItemHeightOrWidthPropertyChanged_InvalidValue { + get { + return ResourceManager.GetString("WrapPanel_OnItemHeightOrWidthPropertyChanged_InvalidValue", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid Orientation value '{0}'.. + /// + internal static string WrapPanel_OnOrientationPropertyChanged_InvalidValue { + get { + return ResourceManager.GetString("WrapPanel_OnOrientationPropertyChanged_InvalidValue", resourceCulture); + } + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/Properties/Resources.resx b/Microsoft.Phone.Controls.Toolkit/Properties/Resources.resx new file mode 100644 index 0000000..89f18c9 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Properties/Resources.resx @@ -0,0 +1,196 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Invalid FilterMode enumeration value. The value must be one of the defined AutoCompleteFilterMode values to be accepted. + Exception thrown when the FilterMode dependency property is set to a value that does not pass the enum validation step in the property changed handler. + + + Invalid maximum drop down height value '{0}'. The value must be greater than or equal to zero. + Exception thrown when the MaxDropDownHeight dependency property is seto to a value that is negative. + + + Invalid MinimumPopulateDelay value '{0}'. The value must be greater than or equal to zero. + Exception thrown when the MinimumPopulateDelay dependency property is changed to a negative value. + + + Cannot set read-only property SearchText. + Exception thrown when the SearchText property is improperly set. + + + Invalid type. Argument must be of the type System.DateTime. + Exception thrown when a DateTime converter receives a non-DateTime argument. + + + Invalid operation. The IsExpanded property cannot be modified when IsNonExpandable is set to true. + Exception thrown when the IsExpanded property of the ExpanderView is being modified programmatically when the IsNonExpandable property is set to true. + + + Invalid argument. The number of months should be greater than 1 and less than 12. + Exception thrown when trying to get a plural string for a non-plural number of months. + + + Invalid argument. The number of time units should be greater than 1. + Exception thrown when trying to get a plural string for a non-plural number of time units. + + + SelectedIndex must always be set to a valid value. + Exception thrown when the SelectedIndex property is provided an invalid value. + + + SelectedItem must always be set to a valid value. + Exception thrown when the SelectedItem property is provided an invalid value. + + + Invalid argument. Future dates and times are not supported. + Exception thrown when a DateTime converter receives a future DateTime as argument. + + + Off + ToggleSwitch string. + + + On + ToggleSwitch string. + + + The RoutedPropertyChangingEvent cannot be canceled! + Exception thrown when setting Cancel property while IsCancelable is false. + + + '{0}' cannot convert from '{1}'. + Exception thrown when a type converter is asked to convert something it cannot. + + + '{0}' is unable to convert '{1}' to '{2}'. + Exception thrown when a type converter fails to convert a value to another type. + + + The type was unexpected. + OffOnConverter exception. + + + Invalid length value '{0}'. + Exception thrown when the ItemWith or ItemHeight properties are provided an invalid value. + + + Invalid Orientation value '{0}'. + Exception thrown when the Orientation property is provided an invalid value. + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Themes/Generic.xaml b/Microsoft.Phone.Controls.Toolkit/Themes/Generic.xaml new file mode 100644 index 0000000..a2440b9 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Themes/Generic.xaml @@ -0,0 +1,1778 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Microsoft.Phone.Controls.Toolkit/Tilt/TiltEffect.cs b/Microsoft.Phone.Controls.Toolkit/Tilt/TiltEffect.cs new file mode 100644 index 0000000..69ca580 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Tilt/TiltEffect.cs @@ -0,0 +1,734 @@ +// (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.Generic; +using System.Diagnostics.CodeAnalysis; +using System.Linq; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Input; +using System.Windows.Media; +using System.Windows.Media.Animation; + +#if WINDOWS_PHONE + +#endif + +namespace Microsoft.Phone.Controls +{ + /// + /// This code provides attached properties for adding a 'tilt' effect to all + /// controls within a container. + /// + /// Preview + [SuppressMessage("Microsoft.Design", "CA1052:StaticHolderTypesShouldBeSealed", Justification = "Cannot be static and derive from DependencyObject.")] + public partial class TiltEffect : DependencyObject + { + /// + /// Cache of previous cache modes. Not using weak references for now. + /// + private static Dictionary _originalCacheMode = new Dictionary(); + + /// + /// Maximum amount of tilt, in radians. + /// + private const double MaxAngle = 0.3; + + /// + /// Maximum amount of depression, in pixels + /// + private const double MaxDepression = 25; + + /// + /// Delay between releasing an element and the tilt release animation + /// playing. + /// + private static readonly TimeSpan TiltReturnAnimationDelay = TimeSpan.FromMilliseconds(200); + + /// + /// Duration of tilt release animation. + /// + private static readonly TimeSpan TiltReturnAnimationDuration = TimeSpan.FromMilliseconds(100); + + /// + /// The control that is currently being tilted. + /// + private static FrameworkElement currentTiltElement; + + /// + /// The single instance of a storyboard used for all tilts. + /// + private static Storyboard tiltReturnStoryboard; + + /// + /// The single instance of an X rotation used for all tilts. + /// + private static DoubleAnimation tiltReturnXAnimation; + + /// + /// The single instance of a Y rotation used for all tilts. + /// + private static DoubleAnimation tiltReturnYAnimation; + + /// + /// The single instance of a Z depression used for all tilts. + /// + private static DoubleAnimation tiltReturnZAnimation; + + /// + /// The center of the tilt element. + /// + private static Point currentTiltElementCenter; + + /// + /// Whether the animation just completed was for a 'pause' or not. + /// + private static bool wasPauseAnimation = false; + + /// + /// Whether to use a slightly more accurate (but slightly slower) tilt + /// animation easing function. + /// + public static bool UseLogarithmicEase { get; set; } + + /// + /// Default list of items that are tiltable. + /// + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Tiltable", Justification = "By design.")] + [SuppressMessage("Microsoft.Design", "CA1002:DoNotExposeGenericLists", Justification = "Keeping it simple.")] + public static List TiltableItems { get; private set; } + + #region Constructor and Static Constructor + /// + /// This is not a constructable class, but it cannot be static because + /// it derives from DependencyObject. + /// + private TiltEffect() + { + } + + /// + /// Initialize the static properties + /// + [SuppressMessage("Microsoft.Performance", "CA1810:InitializeReferenceTypeStaticFieldsInline", Justification = "Need to initialize the tiltable items property.")] + static TiltEffect() + { + // The tiltable items list. + TiltableItems = new List() + { + typeof(ButtonBase), + typeof(ListBoxItem), + typeof(MenuItem), + }; + } + + #endregion + + #region Dependency properties + + /// + /// Whether the tilt effect is enabled on a container (and all its + /// children). + /// + public static readonly DependencyProperty IsTiltEnabledProperty = DependencyProperty.RegisterAttached( + "IsTiltEnabled", + typeof(bool), + typeof(TiltEffect), + new PropertyMetadata(OnIsTiltEnabledChanged) + ); + + /// + /// Gets the IsTiltEnabled dependency property from an object. + /// + /// The object to get the property from. + /// The property's value. + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")] + public static bool GetIsTiltEnabled(DependencyObject source) + { + return (bool)source.GetValue(IsTiltEnabledProperty); + } + + /// + /// Sets the IsTiltEnabled dependency property on an object. + /// + /// The object to set the property on. + /// The value to set. + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")] + public static void SetIsTiltEnabled(DependencyObject source, bool value) + { + source.SetValue(IsTiltEnabledProperty, value); + } + + /// + /// Suppresses the tilt effect on a single control that would otherwise + /// be tilted. + /// + public static readonly DependencyProperty SuppressTiltProperty = DependencyProperty.RegisterAttached( + "SuppressTilt", + typeof(bool), + typeof(TiltEffect), + null + ); + + /// + /// Gets the SuppressTilt dependency property from an object. + /// + /// The object to get the property from. + /// The property's value. + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")] + public static bool GetSuppressTilt(DependencyObject source) + { + return (bool)source.GetValue(SuppressTiltProperty); + } + + /// + /// Sets the SuppressTilt dependency property from an object. + /// + /// The object to get the property from. + /// The property's value. + [SuppressMessage("Microsoft.Design", "CA1062:Validate arguments of public methods", MessageId = "0", Justification = "Standard pattern.")] + public static void SetSuppressTilt(DependencyObject source, bool value) + { + source.SetValue(SuppressTiltProperty, value); + } + + /// + /// Property change handler for the IsTiltEnabled dependency property. + /// + /// The element that the property is atteched to. + /// Event arguments. + /// + /// Adds or removes event handlers from the element that has been + /// (un)registered for tilting. + /// + static void OnIsTiltEnabledChanged(DependencyObject target, DependencyPropertyChangedEventArgs args) + { + FrameworkElement fe = target as FrameworkElement; + if (fe != null) + { + // Add / remove the event handler if necessary + if ((bool)args.NewValue == true) + { + fe.ManipulationStarted += TiltEffect_ManipulationStarted; + } + else + { + fe.ManipulationStarted -= TiltEffect_ManipulationStarted; + } + } + } + + #endregion + + #region Top-level manipulation event handlers + + /// + /// Event handler for ManipulationStarted. + /// + /// sender of the event - this will be the tilt + /// container (eg, entire page). + /// Event arguments. + private static void TiltEffect_ManipulationStarted(object sender, ManipulationStartedEventArgs e) + { + TryStartTiltEffect(sender as FrameworkElement, e); + } + + /// + /// Event handler for ManipulationDelta + /// + /// sender of the event - this will be the tilting + /// object (eg a button). + /// Event arguments. + private static void TiltEffect_ManipulationDelta(object sender, ManipulationDeltaEventArgs e) + { + ContinueTiltEffect(sender as FrameworkElement, e); + } + + /// + /// Event handler for ManipulationCompleted. + /// + /// sender of the event - this will be the tilting + /// object (eg a button). + /// Event arguments. + static void TiltEffect_ManipulationCompleted(object sender, ManipulationCompletedEventArgs e) + { + EndTiltEffect(currentTiltElement); + } + + #endregion + + #region Core tilt logic + + /// + /// Checks if the manipulation should cause a tilt, and if so starts the + /// tilt effect. + /// + /// The source of the manipulation (the tilt + /// container, eg entire page). + /// The args from the ManipulationStarted event. + static void TryStartTiltEffect(FrameworkElement source, ManipulationStartedEventArgs e) + { + foreach (FrameworkElement ancestor in (e.OriginalSource as FrameworkElement).GetVisualAncestors()) + { + foreach (Type t in TiltableItems) + { + if (t.IsAssignableFrom(ancestor.GetType())) + { + FrameworkElement elementSuppressingTilt = null; + + // Look up the tree to find either an explicit DO or DO NOT suppress. + if (ancestor.ReadLocalValue(SuppressTiltProperty) is bool) + { + elementSuppressingTilt = ancestor; + } + else + { + elementSuppressingTilt = ancestor.GetVisualAncestors().FirstOrDefault(x => x.ReadLocalValue(SuppressTiltProperty) is bool); + } + + if (elementSuppressingTilt != null && (bool)elementSuppressingTilt.ReadLocalValue(SuppressTiltProperty) == true) + { + continue; + } + else + { + // Use first child of the control, so that we can add transforms and not + // impact any transforms on the control itself. + FrameworkElement element = VisualTreeHelper.GetChild(ancestor, 0) as FrameworkElement; + FrameworkElement container = e.ManipulationContainer as FrameworkElement; + + if (element == null || container == null) + return; + + // Touch point relative to the element being tilted. + Point tiltTouchPoint = container.TransformToVisual(element).Transform(e.ManipulationOrigin); + + // Center of the element being tilted. + Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2); + + // Camera adjustment. + Point centerToCenterDelta = GetCenterToCenterDelta(element, source); + + BeginTiltEffect(element, tiltTouchPoint, elementCenter, centerToCenterDelta); + return; + } + } + } + } + } + + /// + /// Computes the delta between the centre of an element and its + /// container. + /// + /// The element to compare. + /// The element to compare against. + /// A point that represents the delta between the two centers. + static Point GetCenterToCenterDelta(FrameworkElement element, FrameworkElement container) + { + Point elementCenter = new Point(element.ActualWidth / 2, element.ActualHeight / 2); + Point containerCenter; + +#if WINDOWS_PHONE + + // Need to special-case the frame to handle different orientations. + PhoneApplicationFrame frame = container as PhoneApplicationFrame; + if (frame != null) + { + // Switch width and height in landscape mode + if ((frame.Orientation & PageOrientation.Landscape) == PageOrientation.Landscape) + { + containerCenter = new Point(container.ActualHeight / 2, container.ActualWidth / 2); + } + else + { + containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2); + } + } + else + { + containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2); + } +#else + containerCenter = new Point(container.ActualWidth / 2, container.ActualHeight / 2); +#endif + + Point transformedElementCenter = element.TransformToVisual(container).Transform(elementCenter); + Point result = new Point(containerCenter.X - transformedElementCenter.X, containerCenter.Y - transformedElementCenter.Y); + + return result; + } + + /// + /// Begins the tilt effect by preparing the control and doing the + /// initial animation. + /// + /// The element to tilt. + /// The touch point, in element coordinates. + /// The center point of the element in element + /// coordinates. + /// The delta between the + /// 's center and the container's center. + static void BeginTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint, Point centerDelta) + { + if (tiltReturnStoryboard != null) + { + StopTiltReturnStoryboardAndCleanup(); + } + + if (PrepareControlForTilt(element, centerDelta) == false) + { + return; + } + + currentTiltElement = element; + currentTiltElementCenter = centerPoint; + PrepareTiltReturnStoryboard(element); + + ApplyTiltEffect(currentTiltElement, touchPoint, currentTiltElementCenter); + } + + /// + /// Prepares a control to be tilted by setting up a plane projection and + /// some event handlers. + /// + /// The control that is to be tilted. + /// Delta between the element's center and the + /// tilt container's. + /// true if successful; false otherwise. + /// + /// This method is conservative; it will fail any attempt to tilt a + /// control that already has a projection on it. + /// + static bool PrepareControlForTilt(FrameworkElement element, Point centerDelta) + { + // Prevents interference with any existing transforms + if (element.Projection != null || (element.RenderTransform != null && element.RenderTransform.GetType() != typeof(MatrixTransform))) + { + return false; + } + + _originalCacheMode[element] = element.CacheMode; + element.CacheMode = new BitmapCache(); + + TranslateTransform transform = new TranslateTransform(); + transform.X = centerDelta.X; + transform.Y = centerDelta.Y; + element.RenderTransform = transform; + + PlaneProjection projection = new PlaneProjection(); + projection.GlobalOffsetX = -1 * centerDelta.X; + projection.GlobalOffsetY = -1 * centerDelta.Y; + element.Projection = projection; + + element.ManipulationDelta += TiltEffect_ManipulationDelta; + element.ManipulationCompleted += TiltEffect_ManipulationCompleted; + + return true; + } + + /// + /// Removes modifications made by PrepareControlForTilt. + /// + /// THe control to be un-prepared. + /// + /// This method is basic; it does not do anything to detect if the + /// control being un-prepared was previously prepared. + /// + private static void RevertPrepareControlForTilt(FrameworkElement element) + { + element.ManipulationDelta -= TiltEffect_ManipulationDelta; + element.ManipulationCompleted -= TiltEffect_ManipulationCompleted; + element.Projection = null; + element.RenderTransform = null; + + CacheMode original; + if (_originalCacheMode.TryGetValue(element, out original)) + { + element.CacheMode = original; + _originalCacheMode.Remove(element); + } + else + { + element.CacheMode = null; + } + } + + /// + /// Creates the tilt return storyboard (if not already created) and + /// targets it to the projection. + /// + /// The framework element to prepare for + /// projection. + static void PrepareTiltReturnStoryboard(FrameworkElement element) + { + if (tiltReturnStoryboard == null) + { + tiltReturnStoryboard = new Storyboard(); + tiltReturnStoryboard.Completed += TiltReturnStoryboard_Completed; + + tiltReturnXAnimation = new DoubleAnimation(); + Storyboard.SetTargetProperty(tiltReturnXAnimation, new PropertyPath(PlaneProjection.RotationXProperty)); + tiltReturnXAnimation.BeginTime = TiltReturnAnimationDelay; + tiltReturnXAnimation.To = 0; + tiltReturnXAnimation.Duration = TiltReturnAnimationDuration; + + tiltReturnYAnimation = new DoubleAnimation(); + Storyboard.SetTargetProperty(tiltReturnYAnimation, new PropertyPath(PlaneProjection.RotationYProperty)); + tiltReturnYAnimation.BeginTime = TiltReturnAnimationDelay; + tiltReturnYAnimation.To = 0; + tiltReturnYAnimation.Duration = TiltReturnAnimationDuration; + + tiltReturnZAnimation = new DoubleAnimation(); + Storyboard.SetTargetProperty(tiltReturnZAnimation, new PropertyPath(PlaneProjection.GlobalOffsetZProperty)); + tiltReturnZAnimation.BeginTime = TiltReturnAnimationDelay; + tiltReturnZAnimation.To = 0; + tiltReturnZAnimation.Duration = TiltReturnAnimationDuration; + + if (UseLogarithmicEase) + { + tiltReturnXAnimation.EasingFunction = new LogarithmicEase(); + tiltReturnYAnimation.EasingFunction = new LogarithmicEase(); + tiltReturnZAnimation.EasingFunction = new LogarithmicEase(); + } + + tiltReturnStoryboard.Children.Add(tiltReturnXAnimation); + tiltReturnStoryboard.Children.Add(tiltReturnYAnimation); + tiltReturnStoryboard.Children.Add(tiltReturnZAnimation); + } + + Storyboard.SetTarget(tiltReturnXAnimation, element.Projection); + Storyboard.SetTarget(tiltReturnYAnimation, element.Projection); + Storyboard.SetTarget(tiltReturnZAnimation, element.Projection); + } + + /// + /// Continues a tilt effect that is currently applied to an element, + /// presumably because the user moved their finger. + /// + /// The element being tilted. + /// The manipulation event args. + static void ContinueTiltEffect(FrameworkElement element, ManipulationDeltaEventArgs e) + { + FrameworkElement container = e.ManipulationContainer as FrameworkElement; + if (container == null || element == null) + { + return; + } + + Point tiltTouchPoint = container.TransformToVisual(element).Transform(e.ManipulationOrigin); + + // If touch moved outside bounds of element, then pause the tilt + // (but don't cancel it) + if (new Rect(0, 0, currentTiltElement.ActualWidth, currentTiltElement.ActualHeight).Contains(tiltTouchPoint) != true) + { + PauseTiltEffect(); + } + else + { + // Apply the updated tilt effect + ApplyTiltEffect(currentTiltElement, e.ManipulationOrigin, currentTiltElementCenter); + } + } + + /// + /// Ends the tilt effect by playing the animation. + /// + /// The element being tilted. + private static void EndTiltEffect(FrameworkElement element) + { + if (element != null) + { + element.ManipulationCompleted -= TiltEffect_ManipulationCompleted; + element.ManipulationDelta -= TiltEffect_ManipulationDelta; + } + + if (tiltReturnStoryboard != null) + { + wasPauseAnimation = false; + if (tiltReturnStoryboard.GetCurrentState() != ClockState.Active) + { + tiltReturnStoryboard.Begin(); + } + } + else + { + StopTiltReturnStoryboardAndCleanup(); + } + } + + /// + /// Handler for the storyboard complete event. + /// + /// sender of the event. + /// event args. + private static void TiltReturnStoryboard_Completed(object sender, EventArgs e) + { + if (wasPauseAnimation) + { + ResetTiltEffect(currentTiltElement); + } + else + { + StopTiltReturnStoryboardAndCleanup(); + } + } + + /// + /// Resets the tilt effect on the control, making it appear 'normal' + /// again. + /// + /// The element to reset the tilt on. + /// + /// This method doesn't turn off the tilt effect or cancel any current + /// manipulation; it just temporarily cancels the effect. + /// + private static void ResetTiltEffect(FrameworkElement element) + { + PlaneProjection projection = element.Projection as PlaneProjection; + projection.RotationY = 0; + projection.RotationX = 0; + projection.GlobalOffsetZ = 0; + } + + /// + /// Stops the tilt effect and release resources applied to the currently + /// tilted control. + /// + private static void StopTiltReturnStoryboardAndCleanup() + { + if (tiltReturnStoryboard != null) + { + tiltReturnStoryboard.Stop(); + } + + RevertPrepareControlForTilt(currentTiltElement); + } + + /// + /// Pauses the tilt effect so that the control returns to the 'at rest' + /// position, but doesn't stop the tilt effect (handlers are still + /// attached). + /// + private static void PauseTiltEffect() + { + if ((tiltReturnStoryboard != null) && !wasPauseAnimation) + { + tiltReturnStoryboard.Stop(); + wasPauseAnimation = true; + tiltReturnStoryboard.Begin(); + } + } + + /// + /// Resets the storyboard to not running. + /// + private static void ResetTiltReturnStoryboard() + { + tiltReturnStoryboard.Stop(); + wasPauseAnimation = false; + } + + /// + /// Applies the tilt effect to the control. + /// + /// the control to tilt. + /// The touch point, in the container's + /// coordinates. + /// The center point of the container. + private static void ApplyTiltEffect(FrameworkElement element, Point touchPoint, Point centerPoint) + { + // Stop any active animation + ResetTiltReturnStoryboard(); + + // Get relative point of the touch in percentage of container size + Point normalizedPoint = new Point( + Math.Min(Math.Max(touchPoint.X / (centerPoint.X * 2), 0), 1), + Math.Min(Math.Max(touchPoint.Y / (centerPoint.Y * 2), 0), 1)); + + if (double.IsNaN(normalizedPoint.X) || double.IsNaN(normalizedPoint.Y)) + { + return; + } + + // Shell values + double xMagnitude = Math.Abs(normalizedPoint.X - 0.5); + double yMagnitude = Math.Abs(normalizedPoint.Y - 0.5); + double xDirection = -Math.Sign(normalizedPoint.X - 0.5); + double yDirection = Math.Sign(normalizedPoint.Y - 0.5); + double angleMagnitude = xMagnitude + yMagnitude; + double xAngleContribution = xMagnitude + yMagnitude > 0 ? xMagnitude / (xMagnitude + yMagnitude) : 0; + + double angle = angleMagnitude * MaxAngle * 180 / Math.PI; + double depression = (1 - angleMagnitude) * MaxDepression; + + // RotationX and RotationY are the angles of rotations about the x- + // or y-*axis*; to achieve a rotation in the x- or y-*direction*, we + // need to swap the two. That is, a rotation to the left about the + // y-axis is a rotation to the left in the x-direction, and a + // rotation up about the x-axis is a rotation up in the y-direction. + PlaneProjection projection = element.Projection as PlaneProjection; + projection.RotationY = angle * xAngleContribution * xDirection; + projection.RotationX = angle * (1 - xAngleContribution) * yDirection; + projection.GlobalOffsetZ = -depression; + } + + #endregion + + #region Custom easing function + + /// + /// Provides an easing function for the tilt return. + /// + private class LogarithmicEase : EasingFunctionBase + { + /// + /// Computes the easing function. + /// + /// The time. + /// The eased value. + protected override double EaseInCore(double normalizedTime) + { + // ln(t + 1) / ln(2) + return Math.Log(normalizedTime + 1) / 0.693147181; + } + } + + #endregion + } + + /// + /// Couple of simple helpers for walking the visual tree. + /// + static class TreeHelpers + { + /// + /// Gets the ancestors of the element, up to the root. + /// + /// The element to start from. + /// An enumerator of the ancestors. + public static IEnumerable GetVisualAncestors(this FrameworkElement node) + { + FrameworkElement parent = node.GetVisualParent(); + while (parent != null) + { + yield return parent; + parent = parent.GetVisualParent(); + } + } + + /// + /// Gets the visual parent of the element. + /// + /// The element to check. + /// The visual parent. + public static FrameworkElement GetVisualParent(this FrameworkElement node) + { + return VisualTreeHelper.GetParent(node) as FrameworkElement; + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/ToggleSwitch/OffOnConverter.cs b/Microsoft.Phone.Controls.Toolkit/ToggleSwitch/OffOnConverter.cs new file mode 100644 index 0000000..b4b6380 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/ToggleSwitch/OffOnConverter.cs @@ -0,0 +1,57 @@ +// (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.Globalization; +using System.Windows.Data; +using Microsoft.Phone.Controls.Properties; + +namespace Microsoft.Phone.Controls +{ + /// + /// Converts bool? values to "Off" and "On" strings. + /// + /// Preview + public class OffOnConverter : IValueConverter + { + /// + /// Converts a value. + /// + /// The value produced by the binding source. + /// The type of the binding target property. + /// The converter parameter to use. + /// The culture to use in the converter. + /// A converted value. If the method returns null, the valid null value is used. + public object Convert(object value, Type targetType, object parameter, CultureInfo culture) + { + if (targetType == null) + { + throw new ArgumentNullException("targetType"); + } + if (targetType != typeof(object)) + { + throw new ArgumentException(Resources.UnexpectedType, "targetType"); + } + if (value is bool? || value == null) + { + return (bool?)value == true ? Resources.On : Resources.Off; + } + throw new ArgumentException(Resources.UnexpectedType, "value"); + } + + /// + /// Converts a value. + /// + /// The value produced by the binding source. + /// The type of the binding target property. + /// The converter parameter to use. + /// The culture to use in the converter. + /// A converted value. If the method returns null, the valid null value is used. + public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/ToggleSwitch/ToggleSwitch.cs b/Microsoft.Phone.Controls.Toolkit/ToggleSwitch/ToggleSwitch.cs new file mode 100644 index 0000000..47e3daa --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/ToggleSwitch/ToggleSwitch.cs @@ -0,0 +1,314 @@ +// (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.ComponentModel; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using System.Windows.Data; +using System.Windows.Media; +using Microsoft.Phone.Controls.Primitives; + +namespace Microsoft.Phone.Controls +{ + /// + /// Represents a switch that can be toggled between two states. + /// + /// Preview + [TemplateVisualState(Name = NormalState, GroupName = CommonStates)] + [TemplateVisualState(Name = DisabledState, GroupName = CommonStates)] + [TemplatePart(Name = SwitchPart, Type = typeof(ToggleSwitchButton))] + public class ToggleSwitch : ContentControl + { + /// + /// Common visual states. + /// + private const string CommonStates = "CommonStates"; + + /// + /// Normal visual state. + /// + private const string NormalState = "Normal"; + + /// + /// Disabled visual state. + /// + private const string DisabledState = "Disabled"; + + /// + /// The ToggleButton that drives this. + /// + private const string SwitchPart = "Switch"; + + /// + /// Identifies the Header DependencyProperty. + /// + public static readonly DependencyProperty HeaderProperty = + DependencyProperty.Register("Header", typeof(object), typeof(ToggleSwitch), new PropertyMetadata(null)); + + /// + /// Gets or sets the header. + /// + public object Header + { + get { return GetValue(HeaderProperty); } + set { SetValue(HeaderProperty, value); } + } + + /// + /// Identifies the HeaderTemplate DependencyProperty. + /// + public static readonly DependencyProperty HeaderTemplateProperty = + DependencyProperty.Register("HeaderTemplate", typeof(DataTemplate), typeof(ToggleSwitch), new PropertyMetadata(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 SwitchForeground DependencyProperty. + /// + public static readonly DependencyProperty SwitchForegroundProperty = + DependencyProperty.Register("SwitchForeground", typeof(Brush), typeof(ToggleSwitch), null); + + /// + /// Gets or sets the switch foreground. + /// + public Brush SwitchForeground + { + get { return (Brush)GetValue(SwitchForegroundProperty); } + set { SetValue(SwitchForegroundProperty, value); } + } + + /// + /// Gets or sets whether the ToggleSwitch is checked. + /// + [TypeConverter(typeof(NullableBoolConverter))] + public bool? IsChecked + { + get { return (bool?)GetValue(IsCheckedProperty); } + set { SetValue(IsCheckedProperty, value); } + } + + /// + /// Identifies the IsChecked DependencyProperty. + /// + public static readonly DependencyProperty IsCheckedProperty = + DependencyProperty.Register("IsChecked", typeof(bool?), typeof(ToggleSwitch), new PropertyMetadata(false, OnIsCheckedChanged)); + + /// + /// Invoked when the IsChecked DependencyProperty is changed. + /// + /// The event sender. + /// The event information. + private static void OnIsCheckedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + ToggleSwitch toggleSwitch = (ToggleSwitch)d; + if (toggleSwitch._toggleButton != null) + { + toggleSwitch._toggleButton.IsChecked = (bool?)e.NewValue; + } + } + + /// + /// Occurs when the + /// + /// is checked. + /// + public event EventHandler Checked; + + /// + /// Occurs when the + /// + /// is unchecked. + /// + public event EventHandler Unchecked; + + /// + /// Occurs when the + /// + /// is indeterminate. + /// + public event EventHandler Indeterminate; + + /// + /// Occurs when the + /// + /// is clicked. + /// + public event EventHandler Click; + + /// + /// The + /// + /// template part. + /// + private ToggleSwitchButton _toggleButton; + + /// + /// Whether the content was set. + /// + private bool _wasContentSet; + + /// + /// Initializes a new instance of the ToggleSwitch class. + /// + public ToggleSwitch() + { + DefaultStyleKey = typeof(ToggleSwitch); + } + + /// + /// Makes the content an "Off" or "On" string to match the state. + /// + private void SetDefaultContent() + { + Binding binding = new Binding("IsChecked") { Source = this, Converter = new OffOnConverter() }; + SetBinding(ContentProperty, binding); + } + + /// + /// Change the visual state. + /// + /// Indicates whether to use animation transitions. + private void ChangeVisualState(bool useTransitions) + { + if (IsEnabled) + { + VisualStateManager.GoToState(this, NormalState, useTransitions); + } + else + { + VisualStateManager.GoToState(this, DisabledState, useTransitions); + } + } + + /// + /// Makes the content an "Off" or "On" string to match the state if the content is set to null in the design tool. + /// + /// The old content. + /// The new content. + protected override void OnContentChanged(object oldContent, object newContent) + { + base.OnContentChanged(oldContent, newContent); + _wasContentSet = true; + if (DesignerProperties.IsInDesignTool && newContent == null && GetBindingExpression(ContentProperty) == null) + { + SetDefaultContent(); + } + } + + /// + /// Gets all the template parts and initializes the corresponding state. + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + if (!_wasContentSet && GetBindingExpression(ContentProperty) == null) + { + SetDefaultContent(); + } + + if (_toggleButton != null) + { + _toggleButton.Checked -= OnChecked; + _toggleButton.Unchecked -= OnUnchecked; + _toggleButton.Indeterminate -= OnIndeterminate; + _toggleButton.Click -= OnClick; + } + _toggleButton = GetTemplateChild(SwitchPart) as ToggleSwitchButton; + if (_toggleButton != null) + { + _toggleButton.Checked += OnChecked; + _toggleButton.Unchecked += OnUnchecked; + _toggleButton.Indeterminate += OnIndeterminate; + _toggleButton.Click += OnClick; + _toggleButton.IsChecked = IsChecked; + } + IsEnabledChanged += delegate + { + ChangeVisualState(true); + }; + ChangeVisualState(false); + } + + /// + /// Mirrors the + /// + /// event. + /// + /// The event sender. + /// The event information. + private void OnChecked(object sender, RoutedEventArgs e) + { + IsChecked = true; + SafeRaise.Raise(Checked, this, e); + } + + /// + /// Mirrors the + /// + /// event. + /// + /// The event sender. + /// The event information. + private void OnUnchecked(object sender, RoutedEventArgs e) + { + IsChecked = false; + SafeRaise.Raise(Unchecked, this, e); + } + + /// + /// Mirrors the + /// + /// event. + /// + /// The event sender. + /// The event information. + private void OnIndeterminate(object sender, RoutedEventArgs e) + { + IsChecked = null; + SafeRaise.Raise(Indeterminate, this, e); + } + + /// + /// Mirrors the + /// + /// event. + /// + /// The event sender. + /// The event information. + private void OnClick(object sender, RoutedEventArgs e) + { + SafeRaise.Raise(Click, this, e); + } + + /// + /// Returns a + /// + /// that represents the current + /// + /// . + /// + /// + public override string ToString() + { + return string.Format( + CultureInfo.InvariantCulture, + "{{ToggleSwitch IsChecked={0}, Content={1}}}", + IsChecked, + Content + ); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/ITransition.cs b/Microsoft.Phone.Controls.Toolkit/Transitions/ITransition.cs new file mode 100644 index 0000000..4b69b61 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/ITransition.cs @@ -0,0 +1,87 @@ +// (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.Windows.Media.Animation; + +namespace Microsoft.Phone.Controls +{ + /// + /// Controls the behavior of transitions. + /// + public interface ITransition + { + /// + /// Occurs when the + /// + /// has completed playing. + /// + event EventHandler Completed; + + /// + /// Gets the + /// + /// of the + /// . + /// + /// + ClockState GetCurrentState(); + + /// + /// Gets the current time of the + /// . + /// + /// The current time. + TimeSpan GetCurrentTime(); + + /// + /// Pauses the animation clock associated with the + /// . + /// + void Pause(); + + /// + /// Resumes the animation clock, or run-time state, associated with the + /// . + /// + void Resume(); + + /// + /// Moves the + /// + /// to the specified animation position. The + /// + /// performs the requested seek when the next clock tick occurs. + /// + /// The specified animation position. + void Seek(TimeSpan offset); + + /// + /// Moves the + /// + /// to the specified animation position immediately (synchronously). + /// + /// The specified animation position + void SeekAlignedToLastTick(TimeSpan offset); + + /// + /// Advances the current time of the + /// 's + /// clock to the end of its active period. + /// + void SkipToFill(); + + /// + /// Initiates the set of animations associated with the + /// . + /// + void Begin(); + + /// + /// Stops the . + /// + void Stop(); + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/NavigationInTransition.cs b/Microsoft.Phone.Controls.Toolkit/Transitions/NavigationInTransition.cs new file mode 100644 index 0000000..4c79389 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/NavigationInTransition.cs @@ -0,0 +1,11 @@ +namespace Microsoft.Phone.Controls +{ + /// + /// Has navigation-in + /// s + /// for the designer experiences. + /// + public class NavigationInTransition : NavigationTransition + { + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/NavigationOutTransition.cs b/Microsoft.Phone.Controls.Toolkit/Transitions/NavigationOutTransition.cs new file mode 100644 index 0000000..4617514 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/NavigationOutTransition.cs @@ -0,0 +1,11 @@ +namespace Microsoft.Phone.Controls +{ + /// + /// Has navigation-out + /// s + /// for the designer experiences. + /// + public class NavigationOutTransition : NavigationTransition + { + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/NavigationTransition.cs b/Microsoft.Phone.Controls.Toolkit/Transitions/NavigationTransition.cs new file mode 100644 index 0000000..df18d4a --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/NavigationTransition.cs @@ -0,0 +1,99 @@ +// (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.Windows; + +namespace Microsoft.Phone.Controls +{ + /// + /// Has + /// s + /// for the designer experiences. + /// + public class NavigationTransition : DependencyObject + { + /// + /// The + /// + /// for the backward + /// . + /// + public static readonly DependencyProperty BackwardProperty = + DependencyProperty.Register("Backward", typeof(TransitionElement), typeof(NavigationTransition), null); + + /// + /// The + /// + /// for the forward + /// . + /// + public static readonly DependencyProperty ForwardProperty = + DependencyProperty.Register("Forward", typeof(TransitionElement), typeof(NavigationTransition), null); + + /// + /// The navigation transition will begin. + /// + public event RoutedEventHandler BeginTransition; + + /// + /// The navigation transition has ended. + /// + public event RoutedEventHandler EndTransition; + + /// + /// Gets or sets the backward + /// . + /// + public TransitionElement Backward + { + get + { + return (TransitionElement)GetValue(BackwardProperty); + } + set + { + SetValue(BackwardProperty, value); + } + } + + /// + /// Gets or sets the forward + /// . + /// + public TransitionElement Forward + { + get + { + return (TransitionElement)GetValue(ForwardProperty); + } + set + { + SetValue(ForwardProperty, value); + } + } + + /// + /// Triggers . + /// + internal void OnBeginTransition() + { + if (BeginTransition != null) + { + BeginTransition(this, new RoutedEventArgs()); + } + } + + /// + /// Triggers . + /// + internal void OnEndTransition() + { + if (EndTransition != null) + { + EndTransition(this, new RoutedEventArgs()); + } + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/Roll.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/Roll.xaml new file mode 100644 index 0000000..eb4f0fd --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/Roll.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateIn180Clockwise.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateIn180Clockwise.xaml new file mode 100644 index 0000000..a70e734 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateIn180Clockwise.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateIn180CounterClockwise.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateIn180CounterClockwise.xaml new file mode 100644 index 0000000..2051495 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateIn180CounterClockwise.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateIn90Clockwise.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateIn90Clockwise.xaml new file mode 100644 index 0000000..cbcef7d --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateIn90Clockwise.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateIn90CounterClockwise.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateIn90CounterClockwise.xaml new file mode 100644 index 0000000..a953df4 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateIn90CounterClockwise.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateOut180Clockwise.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateOut180Clockwise.xaml new file mode 100644 index 0000000..6312b89 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateOut180Clockwise.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateOut180CounterClockwise.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateOut180CounterClockwise.xaml new file mode 100644 index 0000000..873fd8b --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateOut180CounterClockwise.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateOut90Clockwise.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateOut90Clockwise.xaml new file mode 100644 index 0000000..b2aa54a --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateOut90Clockwise.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateOut90CounterClockwise.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateOut90CounterClockwise.xaml new file mode 100644 index 0000000..5f78ef7 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/RotateOut90CounterClockwise.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideDownFadeIn.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideDownFadeIn.xaml new file mode 100644 index 0000000..cea10fb --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideDownFadeIn.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideDownFadeOut.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideDownFadeOut.xaml new file mode 100644 index 0000000..08df15d --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideDownFadeOut.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideLeftFadeIn.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideLeftFadeIn.xaml new file mode 100644 index 0000000..29d01bd --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideLeftFadeIn.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideLeftFadeOut.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideLeftFadeOut.xaml new file mode 100644 index 0000000..cb82a3e --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideLeftFadeOut.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideRightFadeIn.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideRightFadeIn.xaml new file mode 100644 index 0000000..7c6e435 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideRightFadeIn.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideRightFadeOut.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideRightFadeOut.xaml new file mode 100644 index 0000000..1aba38a --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideRightFadeOut.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideUpFadeIn.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideUpFadeIn.xaml new file mode 100644 index 0000000..95be208 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideUpFadeIn.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideUpFadeOut.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideUpFadeOut.xaml new file mode 100644 index 0000000..261a558 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SlideUpFadeOut.xaml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelBackwardIn.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelBackwardIn.xaml new file mode 100644 index 0000000..8f590fc --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelBackwardIn.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelBackwardOut.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelBackwardOut.xaml new file mode 100644 index 0000000..60a7e1b --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelBackwardOut.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelForwardIn.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelForwardIn.xaml new file mode 100644 index 0000000..ccacb6a --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelForwardIn.xaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelForwardOut.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelForwardOut.xaml new file mode 100644 index 0000000..97651b3 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelForwardOut.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelFullScreenIn.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelFullScreenIn.xaml new file mode 100644 index 0000000..ca31795 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelFullScreenIn.xaml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelFullScreenOut.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelFullScreenOut.xaml new file mode 100644 index 0000000..3b997c5 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/SwivelFullScreenOut.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/TurnstileBackwardIn.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/TurnstileBackwardIn.xaml new file mode 100644 index 0000000..7e06c86 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/TurnstileBackwardIn.xaml @@ -0,0 +1,23 @@ + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/TurnstileBackwardOut.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/TurnstileBackwardOut.xaml new file mode 100644 index 0000000..3f7e253 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/TurnstileBackwardOut.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/TurnstileForwardIn.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/TurnstileForwardIn.xaml new file mode 100644 index 0000000..d227c4e --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/TurnstileForwardIn.xaml @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + + Visible + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/TurnstileForwardOut.xaml b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/TurnstileForwardOut.xaml new file mode 100644 index 0000000..5ebf94a --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Storyboards/TurnstileForwardOut.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/TransitionElement.cs b/Microsoft.Phone.Controls.Toolkit/Transitions/TransitionElement.cs new file mode 100644 index 0000000..57e215e --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/TransitionElement.cs @@ -0,0 +1,31 @@ +// (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.Windows; + +namespace Microsoft.Phone.Controls +{ + /// + /// Transition factory for a particular transition family. + /// + /// Preview + public abstract class TransitionElement : DependencyObject + { + /// + /// Creates a new + /// + /// for a + /// . + /// Existing + /// + /// or + /// + /// values may be saved and cleared before the start of the transition, then restored it after it is stopped or completed. + /// + /// The . + /// The . + public abstract ITransition GetTransition(UIElement element); + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/TransitionFrame.cs b/Microsoft.Phone.Controls.Toolkit/Transitions/TransitionFrame.cs new file mode 100644 index 0000000..88139a5 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/TransitionFrame.cs @@ -0,0 +1,479 @@ +// (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.Diagnostics; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Navigation; + +namespace Microsoft.Phone.Controls +{ + /// + /// Enables navigation transitions for + /// s. + /// + /// Preview + [TemplatePart(Name = FirstTemplatePartName, Type = typeof(ContentPresenter))] + [TemplatePart(Name = SecondTemplatePartName, Type = typeof(ContentPresenter))] + public class TransitionFrame : PhoneApplicationFrame + { + #region Constants and Statics + /// + /// The new + /// + /// template part name. + /// + private const string FirstTemplatePartName = "FirstContentPresenter"; + + /// + /// The old + /// + /// template part name. + /// + private const string SecondTemplatePartName = "SecondContentPresenter"; + + /// + /// A single shared instance for setting BitmapCache on a visual. + /// + internal static readonly CacheMode BitmapCacheMode = new BitmapCache(); + #endregion + + #region Template Parts + /// + /// The first . + /// + private ContentPresenter _firstContentPresenter; + + /// + /// The second . + /// + private ContentPresenter _secondContentPresenter; + + /// + /// The new . + /// + private ContentPresenter _newContentPresenter; + + /// + /// The old . + /// + private ContentPresenter _oldContentPresenter; + #endregion + + /// + /// Indicates whether a navigation is forward. + /// + private bool _isForwardNavigation; + + /// + /// Determines whether to set the new content to the first or second + /// . + /// + private bool _useFirstAsNew; + + /// + /// A value indicating whether the old transition has completed and the + /// new transition can begin. + /// + private bool _readyToTransitionToNewContent; + + /// + /// A value indicating whether the new content has been loaded and the + /// new transition can begin. + /// + private bool _contentReady; + + /// + /// A value indicating whether the exit transition is currently being performed. + /// + private bool _performingExitTransition; + + /// + /// The transition to use to move in new content once the old transition + /// is complete and ready for movement. + /// + private ITransition _storedNewTransition; + + /// + /// The stored NavigationIn transition instance to use once the old + /// transition is complete and ready for movement. + /// + private NavigationInTransition _storedNavigationInTransition; + + /// + /// The transition to use to complete the old transition. + /// + private ITransition _storedOldTransition; + + /// + /// The stored NavigationOut transition instance. + /// + private NavigationOutTransition _storedNavigationOutTransition; + + /// + /// Initialzies a new instance of the TransitionFrame class. + /// + public TransitionFrame() + : base() + { + DefaultStyleKey = typeof(TransitionFrame); + Navigating += OnNavigating; + BackKeyPress += OnBackKeyPress; + } + + /// + /// Flips the logical content presenters to prepare for the next visual + /// transition. + /// + private void FlipPresenters() + { + _newContentPresenter = _useFirstAsNew ? _firstContentPresenter : _secondContentPresenter; + _oldContentPresenter = _useFirstAsNew ? _secondContentPresenter : _firstContentPresenter; + _useFirstAsNew = !_useFirstAsNew; + } + + /// + /// Handles the Navigating event of the frame, the immediate way to + /// begin a transition out before the new page has loaded or had its + /// layout pass. + /// + /// The source object. + /// The event arguments. + private void OnNavigating(object sender, NavigatingCancelEventArgs e) + { + _isForwardNavigation = e.NavigationMode != NavigationMode.Back; + + var oldElement = Content as UIElement; + if (oldElement == null) + { + return; + } + + FlipPresenters(); + + TransitionElement oldTransitionElement = null; + NavigationOutTransition navigationOutTransition = null; + ITransition oldTransition = null; + + navigationOutTransition = TransitionService.GetNavigationOutTransition(oldElement); + + if (navigationOutTransition != null) + { + oldTransitionElement = _isForwardNavigation ? navigationOutTransition.Forward : navigationOutTransition.Backward; + } + if (oldTransitionElement != null) + { + oldTransition = oldTransitionElement.GetTransition(oldElement); + } + if (oldTransition != null) + { + EnsureStoppedTransition(oldTransition); + + _storedNavigationOutTransition = navigationOutTransition; + _storedOldTransition = oldTransition; + oldTransition.Completed += OnExitTransitionCompleted; + + _performingExitTransition = true; + + PerformTransition(navigationOutTransition, _oldContentPresenter, oldTransition); + + PrepareContentPresenterForCompositor(_oldContentPresenter); + } + else + { + _readyToTransitionToNewContent = true; + } + } + + /// + /// Handles the completion of the exit transition, automatically + /// continuing to bring in the new element's transition as well if it is + /// ready. + /// + /// The source object. + /// The event arguments. + private void OnExitTransitionCompleted(object sender, EventArgs e) + { + _readyToTransitionToNewContent = true; + _performingExitTransition = false; + + CompleteTransition(_storedNavigationOutTransition, /*_oldContentPresenter*/ null, _storedOldTransition); + _storedNavigationOutTransition = null; + _storedOldTransition = null; + + if (_contentReady) + { + ITransition newTransition = _storedNewTransition; + NavigationInTransition navigationInTransition = _storedNavigationInTransition; + + _storedNewTransition = null; + _storedNavigationInTransition = null; + + TransitionNewContent(newTransition, navigationInTransition); + } + } + + /// + /// When overridden in a derived class, is invoked whenever application + /// code or internal processes (such as a rebuilding layout pass) call + /// . + /// In simplest terms, this means the method is called just before a UI + /// element displays in an application. + /// + public override void OnApplyTemplate() + { + base.OnApplyTemplate(); + + _firstContentPresenter = GetTemplateChild(FirstTemplatePartName) as ContentPresenter; + _secondContentPresenter = GetTemplateChild(SecondTemplatePartName) as ContentPresenter; + _newContentPresenter = _secondContentPresenter; + _oldContentPresenter = _firstContentPresenter; + _useFirstAsNew = true; + + _readyToTransitionToNewContent = true; + + if (Content != null) + { + OnContentChanged(null, Content); + } + } + + /// + /// Called when the value of the + /// + /// property changes. + /// + /// The old . + /// The new . + protected override void OnContentChanged(object oldContent, object newContent) + { + base.OnContentChanged(oldContent, newContent); + + _contentReady = true; + + UIElement oldElement = oldContent as UIElement; + UIElement newElement = newContent as UIElement; + + // Require the appropriate template parts plus a new element to + // transition to. + if (_firstContentPresenter == null || _secondContentPresenter == null || newElement == null) + { + return; + } + + NavigationInTransition navigationInTransition = null; + ITransition newTransition = null; + + if (newElement != null) + { + navigationInTransition = TransitionService.GetNavigationInTransition(newElement); + TransitionElement newTransitionElement = null; + if (navigationInTransition != null) + { + newTransitionElement = _isForwardNavigation ? navigationInTransition.Forward : navigationInTransition.Backward; + } + if (newTransitionElement != null) + { + newElement.UpdateLayout(); + + newTransition = newTransitionElement.GetTransition(newElement); + PrepareContentPresenterForCompositor(_newContentPresenter); + } + } + + _newContentPresenter.Opacity = 0; + _newContentPresenter.Visibility = Visibility.Visible; + _newContentPresenter.Content = newElement; + + _oldContentPresenter.Opacity = 1; + _oldContentPresenter.Visibility = Visibility.Visible; + _oldContentPresenter.Content = oldElement; + + if (_readyToTransitionToNewContent) + { + TransitionNewContent(newTransition, navigationInTransition); + } + else + { + _storedNewTransition = newTransition; + _storedNavigationInTransition = navigationInTransition; + } + } + + /// + /// Handles the BackKeyPress to stop the animation and go back. + /// + /// The source object. + /// The event arguments. + private void OnBackKeyPress(object sender, System.ComponentModel.CancelEventArgs e) + { + // No need to handle backkeypress if exit transition is complete. + if (_performingExitTransition) + { + var oldElement = Content as UIElement; + if (oldElement == null) + { + return; + } + + TransitionElement oldTransitionElement = null; + NavigationOutTransition navigationOutTransition = null; + ITransition oldTransition = null; + + navigationOutTransition = TransitionService.GetNavigationOutTransition(oldElement); + + if (navigationOutTransition != null) + { + oldTransitionElement = _isForwardNavigation ? navigationOutTransition.Forward : navigationOutTransition.Backward; + } + if (oldTransitionElement != null) + { + oldTransition = oldTransitionElement.GetTransition(oldElement); + } + if (oldTransition != null) + { + CompleteTransition(_storedNavigationOutTransition, /*_oldContentPresenter*/ null, _storedOldTransition); + TransitionNewContent(oldTransition, null); + } + } + } + + /// + /// Transitions the new . + /// + /// The + /// for the new . + /// The + /// for the new . + private void TransitionNewContent(ITransition newTransition, NavigationInTransition navigationInTransition) + { + if (_oldContentPresenter != null) + { + _oldContentPresenter.Visibility = Visibility.Collapsed; + _oldContentPresenter.Content = null; + } + + if (null == newTransition) + { + RestoreContentPresenterInteractivity(_newContentPresenter); + return; + } + + EnsureStoppedTransition(newTransition); + newTransition.Completed += delegate + { + CompleteTransition(navigationInTransition, _newContentPresenter, newTransition); + }; + + _readyToTransitionToNewContent = false; + _storedNavigationInTransition = null; + _storedNewTransition = null; + + PerformTransition(navigationInTransition, _newContentPresenter, newTransition); + } + + /// + /// This checks to make sure that, if the transition not be in the clock + /// state of Stopped, that is will be stopped. + /// + /// The transition instance. + private static void EnsureStoppedTransition(ITransition transition) + { + if (transition != null && transition.GetCurrentState() != ClockState.Stopped) + { + transition.Stop(); + } + } + + /// + /// Performs a transition when given the appropriate components, + /// includes calling the appropriate start event and ensuring opacity + /// on the content presenter. + /// + /// The navigation transition. + /// The content presenter. + /// The transition instance. + private static void PerformTransition(NavigationTransition navigationTransition, ContentPresenter presenter, ITransition transition) + { + if (navigationTransition != null) + { + navigationTransition.OnBeginTransition(); + } + if (presenter != null && presenter.Opacity != 1) + { + presenter.Opacity = 1; + } + if (transition != null) + { + transition.Begin(); + } + } + + /// + /// Completes a transition operation by stopping it, restoring + /// interactivity, and then firing the OnEndTransition event. + /// + /// The navigation transition. + /// The content presenter. + /// The transition instance. + private static void CompleteTransition(NavigationTransition navigationTransition, ContentPresenter presenter, ITransition transition) + { + if (transition != null) + { + transition.Stop(); + } + + RestoreContentPresenterInteractivity(presenter); + + if (navigationTransition != null) + { + navigationTransition.OnEndTransition(); + } + } + + /// + /// Updates the content presenter for off-thread compositing for the + /// transition animation. Also disables interactivity on it to prevent + /// accidental touches. + /// + /// The content presenter instance. + /// A value indicating whether to apply + /// a bitmap cache. + private static void PrepareContentPresenterForCompositor(ContentPresenter presenter, bool applyBitmapCache = true) + { + if (presenter != null) + { + if (applyBitmapCache) + { + presenter.CacheMode = BitmapCacheMode; + } + presenter.IsHitTestVisible = false; + } + } + + /// + /// Restores the interactivity for the presenter post-animation, also + /// removes the BitmapCache value. + /// + /// The content presenter instance. + private static void RestoreContentPresenterInteractivity(ContentPresenter presenter) + { + if (presenter != null) + { + presenter.CacheMode = null; + + if (presenter.Opacity != 1) + { + presenter.Opacity = 1; + } + + presenter.IsHitTestVisible = true; + } + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/TransitionService.cs b/Microsoft.Phone.Controls.Toolkit/Transitions/TransitionService.cs new file mode 100644 index 0000000..4bf6de9 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/TransitionService.cs @@ -0,0 +1,112 @@ +// (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.Windows; + +namespace Microsoft.Phone.Controls +{ + /// + /// Provides attached properties for navigation + /// s. + /// + /// Preview + public static class TransitionService + { + /// + /// The + /// + /// for the in s. + /// + public static readonly DependencyProperty NavigationInTransitionProperty = + DependencyProperty.RegisterAttached("NavigationInTransition", typeof(NavigationInTransition), typeof(TransitionService), null); + + /// + /// The + /// + /// for the in s. + /// + public static readonly DependencyProperty NavigationOutTransitionProperty = + DependencyProperty.RegisterAttached("NavigationOutTransition", typeof(NavigationOutTransition), typeof(TransitionService), null); + + /// + /// Gets the + /// s + /// of + /// + /// for a + /// . + /// + /// The . + /// The + public static NavigationInTransition GetNavigationInTransition(UIElement element) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + return (NavigationInTransition)element.GetValue(NavigationInTransitionProperty); + } + + /// + /// Gets the + /// s + /// of + /// + /// for a + /// . + /// + /// The . + /// The + public static NavigationOutTransition GetNavigationOutTransition(UIElement element) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + return (NavigationOutTransition)element.GetValue(NavigationOutTransitionProperty); + } + + /// + /// Sets a + /// + /// to + /// + /// for a + /// . + /// + /// The . + /// The . + /// The + public static void SetNavigationInTransition(UIElement element, NavigationInTransition value) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + element.SetValue(NavigationInTransitionProperty, value); + } + + /// + /// Sets a + /// s + /// to + /// + /// for a + /// . + /// + /// The . + /// The . + /// The + public static void SetNavigationOutTransition(UIElement element, NavigationOutTransition value) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + element.SetValue(NavigationOutTransitionProperty, value); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions.cs b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions.cs new file mode 100644 index 0000000..6385b4e --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions.cs @@ -0,0 +1,202 @@ +// (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.Generic; +using System.IO; +using System.Windows; +using System.Windows.Markup; +using System.Windows.Media; +using System.Windows.Media.Animation; +using System.Windows.Resources; + +namespace Microsoft.Phone.Controls +{ + /// + /// Provides + /// s + /// for transition families and modes. + /// + /// Preview + internal static class Transitions + { + /// + /// The cached XAML read from the Storyboard resources. + /// + private static Dictionary _storyboardXamlCache; + + /// + /// Creates a + /// + /// for a transition family, transition mode, and + /// . + /// + /// The type of the transition mode. + /// The . + /// The transition family. + /// The transition mode. + /// The . + private static ITransition GetEnumStoryboard(UIElement element, string name, T mode) + { + string key = name + Enum.GetName(typeof(T), mode); + Storyboard storyboard = GetStoryboard(key); + if (storyboard == null) + { + return null; + } + Storyboard.SetTarget(storyboard, element); + return new Transition(element, storyboard); + } + + /// + /// Creates a + /// + /// for a particular transition family and transition mode. + /// + /// The transition family and transition mode. + /// The . + private static Storyboard GetStoryboard(string name) + { + if (_storyboardXamlCache == null) + { + _storyboardXamlCache = new Dictionary(); + } + string xaml = null; + if (_storyboardXamlCache.ContainsKey(name)) + { + xaml = _storyboardXamlCache[name]; + } + else + { + string path = "/Microsoft.Phone.Controls.Toolkit;component/Transitions/Storyboards/" + name + ".xaml"; + Uri uri = new Uri(path, UriKind.Relative); + StreamResourceInfo streamResourceInfo = Application.GetResourceStream(uri); + using (StreamReader streamReader = new StreamReader(streamResourceInfo.Stream)) + { + xaml = streamReader.ReadToEnd(); + _storyboardXamlCache[name] = xaml; + } + } + return XamlReader.Load(xaml) as Storyboard; + } + + /// + /// Creates an + /// + /// for a + /// + /// for the roll transition. + /// + /// The . + /// The . + public static ITransition Roll(UIElement element) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + Storyboard storyboard = GetStoryboard("Roll"); + Storyboard.SetTarget(storyboard, element); + element.Projection = new PlaneProjection { CenterOfRotationX = 0.5, CenterOfRotationY = 0.5 }; + return new Transition(element, storyboard); + } + + /// + /// Creates an + /// + /// for a + /// + /// for the rotate transition family. + /// + /// The . + /// The transition mode. + /// The . + public static ITransition Rotate(UIElement element, RotateTransitionMode rotateTransitionMode) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + if (!Enum.IsDefined(typeof(RotateTransitionMode), rotateTransitionMode)) + { + throw new ArgumentOutOfRangeException("rotateTransitionMode"); + } + element.Projection = new PlaneProjection { CenterOfRotationX = 0.5, CenterOfRotationY = 0.5 }; + return GetEnumStoryboard(element, "Rotate", rotateTransitionMode); + } + + /// + /// Creates an + /// + /// for a + /// + /// for the slide transition family. + /// + /// The . + /// The transition mode. + /// The . + public static ITransition Slide(UIElement element, SlideTransitionMode slideTransitionMode) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + if (!Enum.IsDefined(typeof(SlideTransitionMode), slideTransitionMode)) + { + throw new ArgumentOutOfRangeException("slideTransitionMode"); + } + element.RenderTransform = new TranslateTransform(); + return GetEnumStoryboard(element, string.Empty, slideTransitionMode); + } + + /// + /// Creates an + /// + /// for a + /// + /// for the swivel transition family. + /// + /// The . + /// The transition mode. + /// The . + public static ITransition Swivel(UIElement element, SwivelTransitionMode swivelTransitionMode) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + if (!Enum.IsDefined(typeof(SwivelTransitionMode), swivelTransitionMode)) + { + throw new ArgumentOutOfRangeException("swivelTransitionMode"); + } + element.Projection = new PlaneProjection(); + return GetEnumStoryboard(element, "Swivel", swivelTransitionMode); + } + + /// + /// Creates an + /// + /// for a + /// + /// for the turnstile transition family. + /// + /// The . + /// The transition mode. + /// The . + public static ITransition Turnstile(UIElement element, TurnstileTransitionMode turnstileTransitionMode) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + if (!Enum.IsDefined(typeof(TurnstileTransitionMode), turnstileTransitionMode)) + { + throw new ArgumentOutOfRangeException("turnstileTransitionMode"); + } + element.Projection = new PlaneProjection { CenterOfRotationX = 0 }; + return GetEnumStoryboard(element, "Turnstile", turnstileTransitionMode); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/RollTransition.cs b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/RollTransition.cs new file mode 100644 index 0000000..2a7df8b --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/RollTransition.cs @@ -0,0 +1,28 @@ +// (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.Windows; + +namespace Microsoft.Phone.Controls +{ + /// + /// Provides roll s. + /// + public class RollTransition : TransitionElement + { + /// + /// Creates a new + /// + /// for a + /// . + /// + /// The . + /// The . + public override ITransition GetTransition(UIElement element) + { + return Transitions.Roll(element); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/RotateTransition.cs b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/RotateTransition.cs new file mode 100644 index 0000000..c7325cd --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/RotateTransition.cs @@ -0,0 +1,55 @@ +// (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.Windows; + +namespace Microsoft.Phone.Controls +{ + /// + /// Provides rotate s. + /// + public class RotateTransition : TransitionElement + { + /// + /// The + /// + /// for the + /// . + /// + public static readonly DependencyProperty ModeProperty = + DependencyProperty.Register("Mode", typeof(RotateTransitionMode), typeof(RotateTransition), null); + + /// + /// The . + /// + public RotateTransitionMode Mode + { + get + { + return (RotateTransitionMode)GetValue(ModeProperty); + } + set + { + SetValue(ModeProperty, value); + } + } + + /// + /// Creates a new + /// + /// for a + /// . + /// Saves and clears the existing + /// + /// value before the start of the transition, then restores it after it is stopped or completed. + /// + /// The . + /// The . + public override ITransition GetTransition(UIElement element) + { + return Transitions.Rotate(element, Mode); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/RotateTransitionMode.cs b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/RotateTransitionMode.cs new file mode 100644 index 0000000..691ea44 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/RotateTransitionMode.cs @@ -0,0 +1,46 @@ +// (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. + +namespace Microsoft.Phone.Controls +{ + /// + /// The rotate transition modes. + /// + public enum RotateTransitionMode + { + /// + /// The rotate in 90 degrees clockwise transition mode. + /// + In90Clockwise, + /// + /// The rotate in 90 degrees counterclockwise transition mode. + /// + In90Counterclockwise, + /// + /// The rotate in 180 degrees clockwise transition mode. + /// + In180Clockwise, + /// + /// The rotate in 180 degrees counterclockwise transition mode. + /// + In180Counterclockwise, + /// + /// The rotate out 90 degrees clockwise transition mode. + /// + Out90Clockwise, + /// + /// The rotate out 90 degrees counterclockwise transition mode. + /// + Out90Counterclockwise, + /// + /// The rotate out 180 degrees clockwise transition mode. + /// + Out180Clockwise, + /// + /// The rotate out 180 degrees counterclockwise transition mode. + /// + Out180Counterclockwise + }; +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/SlideTransition.cs b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/SlideTransition.cs new file mode 100644 index 0000000..a85079d --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/SlideTransition.cs @@ -0,0 +1,55 @@ +// (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.Windows; + +namespace Microsoft.Phone.Controls +{ + /// + /// Provides slide s. + /// + public class SlideTransition : TransitionElement + { + /// + /// The + /// + /// for the + /// . + /// + public static readonly DependencyProperty ModeProperty = + DependencyProperty.Register("Mode", typeof(SlideTransitionMode), typeof(SlideTransition), null); + + /// + /// The . + /// + public SlideTransitionMode Mode + { + get + { + return (SlideTransitionMode)GetValue(ModeProperty); + } + set + { + SetValue(ModeProperty, value); + } + } + + /// + /// Creates a new + /// + /// for a + /// . + /// Saves and clears the existing + /// + /// value before the start of the transition, then restores it after it is stopped or completed. + /// + /// The . + /// The . + public override ITransition GetTransition(UIElement element) + { + return Transitions.Slide(element, Mode); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/SlideTransitionMode.cs b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/SlideTransitionMode.cs new file mode 100644 index 0000000..daa8ead --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/SlideTransitionMode.cs @@ -0,0 +1,46 @@ +// (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. + +namespace Microsoft.Phone.Controls +{ + /// + /// The slide transition modes. + /// + public enum SlideTransitionMode + { + /// + /// The slide up, fade in transition mode. + /// + SlideUpFadeIn, + /// + /// The slide up, fade out transition mode. + /// + SlideUpFadeOut, + /// + /// The slide down, fade in transition mode. + /// + SlideDownFadeIn, + /// + /// The slide down, fade out transition mode. + /// + SlideDownFadeOut, + /// + /// The slide left, fade in transition mode. + /// + SlideLeftFadeIn, + /// + /// The slide left, fade out transition mode. + /// + SlideLeftFadeOut, + /// + /// The slide right, fade in transition mode. + /// + SlideRightFadeIn, + /// + /// The slide right, fade out transition mode. + /// + SlideRightFadeOut + }; +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/SwivelTransition.cs b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/SwivelTransition.cs new file mode 100644 index 0000000..aecdaf0 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/SwivelTransition.cs @@ -0,0 +1,55 @@ +// (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.Windows; + +namespace Microsoft.Phone.Controls +{ + /// + /// Provides swivel s. + /// + public class SwivelTransition : TransitionElement + { + /// + /// The + /// + /// for the + /// . + /// + public static readonly DependencyProperty ModeProperty = + DependencyProperty.Register("Mode", typeof(SwivelTransitionMode), typeof(SwivelTransition), null); + + /// + /// The . + /// + public SwivelTransitionMode Mode + { + get + { + return (SwivelTransitionMode)GetValue(ModeProperty); + } + set + { + SetValue(ModeProperty, value); + } + } + + /// + /// Creates a new + /// + /// for a + /// . + /// Saves and clears the existing + /// + /// value before the start of the transition, then restores it after it is stopped or completed. + /// + /// The . + /// The . + public override ITransition GetTransition(UIElement element) + { + return Transitions.Swivel(element, Mode); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/SwivelTransitionMode.cs b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/SwivelTransitionMode.cs new file mode 100644 index 0000000..2348040 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/SwivelTransitionMode.cs @@ -0,0 +1,38 @@ +// (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. + +namespace Microsoft.Phone.Controls +{ + /// + /// The swivel transition modes. + /// + public enum SwivelTransitionMode + { + /// + /// The swivel full screen in transition mode. + /// + FullScreenIn, + /// + /// The swivel full screen out transition mode. + /// + FullScreenOut, + /// + /// The swivel forward in transition mode. + /// + ForwardIn, + /// + /// The swivel forward out transition mode. + /// + ForwardOut, + /// + /// The swivel backward in transition mode. + /// + BackwardIn, + /// + /// The swivel backward out transition mode. + /// + BackwardOut + }; +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/Transition.cs b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/Transition.cs new file mode 100644 index 0000000..67c2b99 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/Transition.cs @@ -0,0 +1,233 @@ +// (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.Windows; +using System.Windows.Media; +using System.Windows.Media.Animation; + +namespace Microsoft.Phone.Controls +{ + /// + /// Mirrors the + /// + /// interface to control an + /// + /// for a + /// . + /// Saves and restores the + /// + /// and + /// + /// values for the + /// . + /// + public class Transition : ITransition + { + /// + /// The original + /// + /// of the + /// . + /// + private CacheMode _cacheMode; + + /// + /// The . + /// + private UIElement _element; + + /// + /// The original + /// + /// of the + /// . + /// + private bool _isHitTestVisible; + + /// + /// The + /// + /// for the + /// . + /// + private Storyboard _storyboard; + + /// + /// Mirrors . + /// + public event EventHandler Completed + { + add + { + _storyboard.Completed += value; + } + remove + { + _storyboard.Completed -= value; + } + } + + /// + /// Constructs a + /// + /// for a + /// + /// and a + /// . + /// + /// The . + /// The . + public Transition(UIElement element, Storyboard storyboard) + { + if (element == null) + { + throw new ArgumentNullException("element"); + } + if (storyboard == null) + { + throw new ArgumentNullException("storyboard"); + } + _element = element; + _storyboard = storyboard; + } + + /// + /// Mirrors . + /// + public void Begin() + { + Save(); + _storyboard.Completed += OnCompletedRestore; + _storyboard.Begin(); + } + + /// + /// Restores the settings for the transition. + /// + /// + /// + private void OnCompletedRestore(object sender, EventArgs e) + { + Restore(); + } + + /// + /// Mirrors . + /// + public ClockState GetCurrentState() + { + return _storyboard.GetCurrentState(); + } + + /// + /// Mirrors . + /// + public TimeSpan GetCurrentTime() + { + return _storyboard.GetCurrentTime(); + } + + /// + /// Mirrors . + /// + public void Pause() + { + _storyboard.Pause(); + } + + /// + /// Restores the saved + /// + /// and + /// + /// values for the + /// . + /// + private void Restore() + { + if (!(_cacheMode is BitmapCache)) + { + _element.CacheMode = _cacheMode; + } + if (_isHitTestVisible) + { + _element.IsHitTestVisible = _isHitTestVisible; + } + else + { + // This is resolving a bug where the new page cannot be used. + // This may regress some scenarios for unsupported uses of the + // transitions. + _element.IsHitTestVisible = true; + } + } + + /// + /// Mirrors . + /// + public void Resume() + { + _storyboard.Resume(); + } + + /// + /// Saves the + /// + /// and + /// + /// values for the + /// . + /// + private void Save() + { + _cacheMode = _element.CacheMode; + if (!(_cacheMode is BitmapCache)) + { + _element.CacheMode = TransitionFrame.BitmapCacheMode; + } + _isHitTestVisible = _element.IsHitTestVisible; + if (_isHitTestVisible) + { + _element.IsHitTestVisible = false; + } + } + + /// + /// Mirrors . + /// + /// The time offset. + public void Seek(TimeSpan offset) + { + _storyboard.Seek(offset); + } + + /// + /// Mirrors . + /// + /// The time offset. + public void SeekAlignedToLastTick(TimeSpan offset) + { + _storyboard.SeekAlignedToLastTick(offset); + } + + /// + /// Mirrors . + /// + public void SkipToFill() + { + _storyboard.SkipToFill(); + } + + /// + /// Mirrors . + /// + public void Stop() + { + _storyboard.Stop(); + Restore(); + } + } +} diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/TurnstileTransition.cs b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/TurnstileTransition.cs new file mode 100644 index 0000000..dee04d3 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/TurnstileTransition.cs @@ -0,0 +1,55 @@ +// (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.Windows; + +namespace Microsoft.Phone.Controls +{ + /// + /// Provides turnstile s. + /// + public class TurnstileTransition : TransitionElement + { + /// + /// The + /// + /// for the + /// . + /// + public static readonly DependencyProperty ModeProperty = + DependencyProperty.Register("Mode", typeof(TurnstileTransitionMode), typeof(TurnstileTransition), null); + + /// + /// The . + /// + public TurnstileTransitionMode Mode + { + get + { + return (TurnstileTransitionMode)GetValue(ModeProperty); + } + set + { + SetValue(ModeProperty, value); + } + } + + /// + /// Creates a new + /// + /// for a + /// . + /// Saves and clears the existing + /// + /// value before the start of the transition, then restores it after it is stopped or completed. + /// + /// The . + /// The . + public override ITransition GetTransition(UIElement element) + { + return Transitions.Turnstile(element, Mode); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/TurnstileTransitionMode.cs b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/TurnstileTransitionMode.cs new file mode 100644 index 0000000..c23fe4d --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Transitions/Transitions/TurnstileTransitionMode.cs @@ -0,0 +1,30 @@ +// (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. + +namespace Microsoft.Phone.Controls +{ + /// + /// The turnstile transition modes. + /// + public enum TurnstileTransitionMode + { + /// + /// The turnstile forward in transition mode. + /// + ForwardIn, + /// + /// The turnstile forward out transition mode. + /// + ForwardOut, + /// + /// The turnstile backward in transition mode. + /// + BackwardIn, + /// + /// The turnstile backward out transition mode. + /// + BackwardOut + }; +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Viewbox/StretchDirection.cs b/Microsoft.Phone.Controls.Toolkit/Viewbox/StretchDirection.cs new file mode 100644 index 0000000..72d8b96 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Viewbox/StretchDirection.cs @@ -0,0 +1,34 @@ +// (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; + +namespace System.Windows.Controls +{ + /// + /// Describes the direction that content is scaled. + /// + /// Stable + public enum StretchDirection + { + /// + /// The content scales upward only when it is smaller than the parent. + /// If the content is larger, no scaling downward is performed. + /// + UpOnly = 0, + + /// + /// The content scales downward only when it is larger than the parent. + /// If the content is smaller, no scaling upward is performed. + /// + DownOnly = 1, + + /// + /// The content stretches to fit the parent according to the + /// property. + /// + Both = 2 + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/Viewbox/Viewbox.cs b/Microsoft.Phone.Controls.Toolkit/Viewbox/Viewbox.cs new file mode 100644 index 0000000..8a29da6 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/Viewbox/Viewbox.cs @@ -0,0 +1,406 @@ +// (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.Diagnostics; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; +using System.Windows.Markup; +using System.Windows.Media; + +[assembly: SuppressMessage("Compatibility", "SWC4000:GeneralWPFCompatibilityRule", MessageId = "Viewbox, base is System.Windows.Controls.ContentControl (Silverlight) vs System.Windows.Controls.Decorator (wpf)", Justification = "Silverlight for Windows Phone 7 does not support direct manipulation of the visual tree, so Viewbox has to inherit from a core control that can.")] + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// Defines a content decorator that can stretch and scale a single child to + /// fill the available space. + /// + /// + /// Viewbox should inherit from Decorator (which inherits from + /// FrameworkElement), but the closest working base in Silverlight is the + /// ContentControl class. This provides a number of extra APIs not present + /// in WPF that should be avoided (including the Template property which + /// should not be changed from its default value). Viewbox has been sealed + /// to prevent the creation of derived classes that depend on these features + /// not available on its WPF counterpart. + /// + /// Stable + [ContentProperty("Child")] + [SuppressMessage("Microsoft.Naming", "CA1704:IdentifiersShouldBeSpelledCorrectly", MessageId = "Viewbox", Justification = "Consistency with WPF")] + public sealed partial class Viewbox : ContentControl + { + /// + /// Name of child element in Viewbox's default template. + /// + private const string ChildElementName = "Child"; + + /// + /// XAML markup used to define the write-once Viewbox template. + /// + private const string DefaultTemplateMarkup = + "" + + "" + + ""; + + /// + /// Gets or sets the default ControlTemplate of the Viewbox. + /// + private ControlTemplate DefaultTemplate { get; set; } + + /// + /// Gets or sets the element of the Viewbox that will render the child. + /// + private ContentPresenter ChildElement { get; set; } + + /// + /// Gets or sets the transformation on the ChildElement used to scale the + /// Child content. + /// + private ScaleTransform Scale { get; set; } + + /// + /// Gets or sets the single child element of a + /// element. + /// + /// + /// The single child element of a + /// element. + /// + /// + /// Child must be an alias of ContentControl.Content property to ensure + /// continuous namescope, ie, named element within Viewbox can be found. + /// + public UIElement Child + { + get { return Content as UIElement; } + set { Content = value; } + } + + #region public Stretch Stretch + /// + /// Gets or sets the mode, + /// which determines how content fits into the available space. + /// + /// + /// A mode, which + /// determines how content fits in the available space. The default is + /// . + /// + public Stretch Stretch + { + get { return (Stretch)GetValue(StretchProperty); } + set { SetValue(StretchProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// + /// The identifier for the + /// dependency + /// property. + /// + public static readonly DependencyProperty StretchProperty = + DependencyProperty.Register( + "Stretch", + typeof(Stretch), + typeof(Viewbox), + new PropertyMetadata(Stretch.Uniform, OnStretchPropertyChanged)); + + /// + /// StretchProperty property changed handler. + /// + /// Viewbox that changed its Stretch. + /// Event arguments. + private static void OnStretchPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + Viewbox vb = (Viewbox)d; + if (!IsValidStretchValue(e.NewValue)) + { + // revert the change + vb.Stretch = (Stretch)e.OldValue; + + // throw exception + throw new ArgumentException("Stretch"); + } + + // The Stretch property affects measuring + vb.InvalidateMeasure(); + } + + /// + /// Check whether the passed in object value is a valid Stretch enum value. + /// + /// The object typed value to be checked. + /// True if o is a valid Stretch enum value, false o/w. + private static bool IsValidStretchValue(object o) + { + Stretch s = (Stretch)o; + return s == Stretch.None || s == Stretch.Uniform || s == Stretch.Fill || s == Stretch.UniformToFill; + } + #endregion public Stretch Stretch + + #region public StretchDirection StretchDirection + /// + /// Gets or sets the + /// , which + /// determines how scaling is applied to the contents of a + /// . + /// + /// + /// A , which + /// determines how scaling is applied to the contents of a + /// . The default is + /// . + /// + public StretchDirection StretchDirection + { + get { return (StretchDirection)GetValue(StretchDirectionProperty); } + set { SetValue(StretchDirectionProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// + /// The identifier for the + /// + /// dependency property. + /// + public static readonly DependencyProperty StretchDirectionProperty = + DependencyProperty.Register( + "StretchDirection", + typeof(StretchDirection), + typeof(Viewbox), + new PropertyMetadata(StretchDirection.Both, OnStretchDirectionPropertyChanged)); + + /// + /// StretchDirectionProperty property changed handler. + /// + /// Viewbox that changed its StretchDirection. + /// Event arguments. + private static void OnStretchDirectionPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + Viewbox vb = (Viewbox)d; + if (!IsValidStretchDirectionValue(e.NewValue)) + { + // revert the change + vb.StretchDirection = (StretchDirection)e.OldValue; + + throw new ArgumentException("StretchDirection"); + } + + // The StretchDirection property affects measuring + vb.InvalidateMeasure(); + } + + /// + /// Check whether the passed in object value is a valid StretchDirection enum value. + /// + /// The object typed value to be checked. + /// True if o is a valid StretchDirection enum value, false o/w. + private static bool IsValidStretchDirectionValue(object o) + { + StretchDirection sd = (StretchDirection)o; + return sd == StretchDirection.UpOnly || sd == StretchDirection.DownOnly || sd == StretchDirection.Both; + } + #endregion public StretchDirection StretchDirection + + /// + /// Initializes a new instance of the + /// class. + /// + public Viewbox() + { + // Load the default template + Template = DefaultTemplate = XamlReader.Load(DefaultTemplateMarkup) as ControlTemplate; + ApplyTemplate(); + IsTabStop = false; + } + + /// + /// Builds the visual tree for the + /// control when a new + /// template is applied. + /// + public override void OnApplyTemplate() + { + // Ensure the Template property never changes from the + // DefaultTemplate, and only apply it one time. + if (Template != DefaultTemplate) + { + throw new InvalidOperationException(); + } + + // Get the root visual of the template + ChildElement = GetTemplateChild(ChildElementName) as ContentPresenter; + Debug.Assert(ChildElement != null, "The required template part ChildElement was not found!"); + + // Create the transformation to scale the container + ChildElement.RenderTransform = Scale = new ScaleTransform(); + } + + /// + /// Measures the child element of a Viewbox to prepare for arranging + /// it during the ArrangeOverride pass. + /// + /// + /// Viewbox measures it's child at an infinite constraint; it allows the child to be however large it so desires. + /// The child's returned size will be used as it's natural size for scaling to Viewbox's size during Arrange. + /// + /// + /// An upper limit Size that should not be exceeded. + /// + /// The target Size of the element. + protected override Size MeasureOverride(Size availableSize) + { + Size size = new Size(); + if (Child != null) + { + Debug.Assert(ChildElement != null, "The required template part ChildElement was not found!"); + + // Get the child's desired size + ChildElement.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity)); + Size desiredSize = ChildElement.DesiredSize; + + // Determine how much we should scale the child + Size scale = ComputeScaleFactor(availableSize, desiredSize); + Debug.Assert(!double.IsPositiveInfinity(scale.Width), "The scale scaleX should not be infinite."); + Debug.Assert(!double.IsPositiveInfinity(scale.Height), "The scale scaleY should not be infinite."); + + // Determine the desired size of the Viewbox + size.Width = scale.Width * desiredSize.Width; + size.Height = scale.Height * desiredSize.Height; + } + return size; + } + + /// + /// Arranges the content of a Viewbox element. + /// Viewbox always sets the child to its desired size. It then computes and applies a transformation + /// from that size to the space available: Viewbox's own input size less child margin. + /// + /// + /// The Size this element uses to arrange its child content. + /// + /// + /// The Size that represents the arranged size of this Viewbox element + /// and its child. + /// + protected override Size ArrangeOverride(Size finalSize) + { + Debug.Assert(ChildElement != null, "The required template part ChildElement was not found!"); + if (Child != null) + { + // Determine the scale factor given the final size + Size desiredSize = ChildElement.DesiredSize; + Size scale = ComputeScaleFactor(finalSize, desiredSize); + + // Scale the ChildElement by the necessary factor + Debug.Assert(Scale != null, "Scale should not be null!"); + Scale.ScaleX = scale.Width; + Scale.ScaleY = scale.Height; + + // Position the ChildElement to fill the ChildElement + Rect originalPosition = new Rect(0, 0, desiredSize.Width, desiredSize.Height); + ChildElement.Arrange(originalPosition); + + // Determine the final size used by the Viewbox + finalSize.Width = scale.Width * desiredSize.Width; + finalSize.Height = scale.Height * desiredSize.Height; + } + return finalSize; + } + + /// + /// Compute the scale factor of the Child content. + /// + /// + /// Available size to fill with content. + /// + /// Desired size of the content. + /// Width and Height scale factors. + private Size ComputeScaleFactor(Size availableSize, Size contentSize) + { + double scaleX = 1.0; + double scaleY = 1.0; + + bool isConstrainedWidth = !double.IsPositiveInfinity(availableSize.Width); + bool isConstrainedHeight = !double.IsPositiveInfinity(availableSize.Height); + Stretch stretch = Stretch; + + // Don't scale if we shouldn't stretch or the scaleX and scaleY are both infinity. + if ((stretch != Stretch.None) && (isConstrainedWidth || isConstrainedHeight)) + { + // Compute the individual scaleX and scaleY scale factors + scaleX = contentSize.Width.IsZero() ? 0.0 : (availableSize.Width / contentSize.Width); + scaleY = contentSize.Height.IsZero() ? 0.0 : (availableSize.Height / contentSize.Height); + + // Make the scale factors uniform by setting them both equal to + // the larger or smaller (depending on infinite lengths and the + // Stretch value) + if (!isConstrainedWidth) + { + scaleX = scaleY; + } + else if (!isConstrainedHeight) + { + scaleY = scaleX; + } + else + { + // (isConstrainedWidth && isConstrainedHeight) + switch (stretch) + { + case Stretch.Uniform: + // Use the smaller factor for both + scaleX = scaleY = Math.Min(scaleX, scaleY); + break; + case Stretch.UniformToFill: + // Use the larger factor for both + scaleX = scaleY = Math.Max(scaleX, scaleY); + break; + case Stretch.Fill: + default: + break; + } + } + + // Prevent scaling in an undesired direction + switch (StretchDirection) + { + case StretchDirection.UpOnly: + scaleX = Math.Max(1.0, scaleX); + scaleY = Math.Max(1.0, scaleY); + break; + case StretchDirection.DownOnly: + scaleX = Math.Min(1.0, scaleX); + scaleY = Math.Min(1.0, scaleY); + break; + case StretchDirection.Both: + default: + break; + } + } + + return new Size(scaleX, scaleY); + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/WrapPanel/OrientedSize.cs b/Microsoft.Phone.Controls.Toolkit/WrapPanel/OrientedSize.cs new file mode 100644 index 0000000..27a787c --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/WrapPanel/OrientedSize.cs @@ -0,0 +1,142 @@ +// (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.Runtime.InteropServices; + +namespace System.Windows.Controls +{ + /// + /// The OrientedSize structure is used to abstract the growth direction from + /// the layout algorithms of WrapPanel. When the growth direction is + /// oriented horizontally (ex: the next element is arranged on the side of + /// the previous element), then the Width grows directly with the placement + /// of elements and Height grows indirectly with the size of the largest + /// element in the row. When the orientation is reversed, so is the + /// directional growth with respect to Width and Height. + /// + /// Mature + [StructLayout(LayoutKind.Sequential)] + internal struct OrientedSize + { + /// + /// The orientation of the structure. + /// + private Orientation _orientation; + + /// + /// Gets the orientation of the structure. + /// + public Orientation Orientation + { + get { return _orientation; } + } + + /// + /// The size dimension that grows directly with layout placement. + /// + private double _direct; + + /// + /// Gets or sets the size dimension that grows directly with layout + /// placement. + /// + public double Direct + { + get { return _direct; } + set { _direct = value; } + } + + /// + /// The size dimension that grows indirectly with the maximum value of + /// the layout row or column. + /// + private double _indirect; + + /// + /// Gets or sets the size dimension that grows indirectly with the + /// maximum value of the layout row or column. + /// + public double Indirect + { + get { return _indirect; } + set { _indirect = value; } + } + + /// + /// Gets or sets the width of the size. + /// + public double Width + { + get + { + return (Orientation == Orientation.Horizontal) ? + Direct : + Indirect; + } + set + { + if (Orientation == Orientation.Horizontal) + { + Direct = value; + } + else + { + Indirect = value; + } + } + } + + /// + /// Gets or sets the height of the size. + /// + public double Height + { + get + { + return (Orientation != Orientation.Horizontal) ? + Direct : + Indirect; + } + set + { + if (Orientation != Orientation.Horizontal) + { + Direct = value; + } + else + { + Indirect = value; + } + } + } + + /// + /// Initializes a new OrientedSize structure. + /// + /// Orientation of the structure. + public OrientedSize(Orientation orientation) : + this(orientation, 0.0, 0.0) + { + } + + /// + /// Initializes a new OrientedSize structure. + /// + /// Orientation of the structure. + /// Un-oriented width of the structure. + /// Un-oriented height of the structure. + public OrientedSize(Orientation orientation, double width, double height) + { + _orientation = orientation; + + // All fields must be initialized before we access the this pointer + _direct = 0.0; + _indirect = 0.0; + + Width = width; + Height = height; + } + } +} \ No newline at end of file diff --git a/Microsoft.Phone.Controls.Toolkit/WrapPanel/WrapPanel.cs b/Microsoft.Phone.Controls.Toolkit/WrapPanel/WrapPanel.cs new file mode 100644 index 0000000..00187a0 --- /dev/null +++ b/Microsoft.Phone.Controls.Toolkit/WrapPanel/WrapPanel.cs @@ -0,0 +1,453 @@ +// (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.ComponentModel; +using System.Diagnostics.CodeAnalysis; +using System.Globalization; +using System.Windows; +using System.Windows.Controls; + +#if WINDOWS_PHONE +namespace Microsoft.Phone.Controls +#else +namespace System.Windows.Controls +#endif +{ + /// + /// Positions child elements sequentially from left to right or top to + /// bottom. When elements extend beyond the panel edge, elements are + /// positioned in the next row or column. + /// + /// Mature + public partial class WrapPanel : Panel + { + /// + /// A value indicating whether a dependency property change handler + /// should ignore the next change notification. This is used to reset + /// the value of properties without performing any of the actions in + /// their change handlers. + /// + private bool _ignorePropertyChange; + + #region public double ItemHeight + /// + /// Gets or sets the height of the layout area for each item that is + /// contained in a . + /// + /// + /// The height applied to the layout area of each item that is contained + /// within a . The + /// default value is . + /// + [TypeConverter(typeof(LengthConverter))] + public double ItemHeight + { + get { return (double)GetValue(ItemHeightProperty); } + set { SetValue(ItemHeightProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// + /// The identifier for the + /// + /// dependency property + /// + public static readonly DependencyProperty ItemHeightProperty = + DependencyProperty.Register( + "ItemHeight", + typeof(double), + typeof(WrapPanel), + new PropertyMetadata(double.NaN, OnItemHeightOrWidthPropertyChanged)); + #endregion public double ItemHeight + + #region public double ItemWidth + /// + /// Gets or sets the width of the layout area for each item that is + /// contained in a . + /// + /// + /// The width that applies to the layout area of each item that is + /// contained in a . + /// The default value is . + /// + [TypeConverter(typeof(LengthConverter))] + public double ItemWidth + { + get { return (double)GetValue(ItemWidthProperty); } + set { SetValue(ItemWidthProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// + /// The identifier for the + /// + /// dependency property. + /// + public static readonly DependencyProperty ItemWidthProperty = + DependencyProperty.Register( + "ItemWidth", + typeof(double), + typeof(WrapPanel), + new PropertyMetadata(double.NaN, OnItemHeightOrWidthPropertyChanged)); + #endregion public double ItemWidth + + #region public Orientation Orientation + /// + /// Gets or sets the direction in which child elements are arranged. + /// + /// + /// One of the + /// values. The default is + /// . + /// + public Orientation Orientation + { + get { return (Orientation)GetValue(OrientationProperty); } + set { SetValue(OrientationProperty, value); } + } + + /// + /// Identifies the + /// + /// dependency property. + /// + /// + /// The identifier for the + /// + /// dependency property. + /// + // TODO: In WPF, WrapPanel uses AddOwner to register the Orientation + // property via StackPanel. It then gets the default value of + // StackPanel's Orientation property. It looks like this should be no + // different than using the same default value on a new Orientation + // property. + public static readonly DependencyProperty OrientationProperty = + DependencyProperty.Register( + "Orientation", + typeof(Orientation), + typeof(WrapPanel), + new PropertyMetadata(Orientation.Horizontal, OnOrientationPropertyChanged)); + + /// + /// OrientationProperty property changed handler. + /// + /// WrapPanel that changed its Orientation. + /// Event arguments. + [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "Almost always set from the CLR property.")] + private static void OnOrientationPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + WrapPanel source = (WrapPanel)d; + Orientation value = (Orientation)e.NewValue; + + // Ignore the change if requested + if (source._ignorePropertyChange) + { + source._ignorePropertyChange = false; + return; + } + + // Validate the Orientation + if ((value != Orientation.Horizontal) && + (value != Orientation.Vertical)) + { + // Reset the property to its original state before throwing + source._ignorePropertyChange = true; + source.SetValue(OrientationProperty, (Orientation)e.OldValue); + + string message = string.Format( + CultureInfo.InvariantCulture, + Properties.Resources.WrapPanel_OnOrientationPropertyChanged_InvalidValue, + value); + throw new ArgumentException(message, "value"); + } + + // Orientation affects measuring. + source.InvalidateMeasure(); + } + #endregion public Orientation Orientation + + /// + /// Initializes a new instance of the + /// class. + /// + public WrapPanel() + { + } + + /// + /// Property changed handler for ItemHeight and ItemWidth. + /// + /// + /// WrapPanel that changed its ItemHeight or ItemWidth. + /// + /// Event arguments. + [SuppressMessage("Microsoft.Usage", "CA2208:InstantiateArgumentExceptionsCorrectly", Justification = "Almost always set from the CLR property.")] + private static void OnItemHeightOrWidthPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) + { + WrapPanel source = (WrapPanel)d; + double value = (double)e.NewValue; + + // Ignore the change if requested + if (source._ignorePropertyChange) + { + source._ignorePropertyChange = false; + return; + } + + // Validate the length (which must either be NaN or a positive, + // finite number) + if (!value.IsNaN() && ((value <= 0.0) || double.IsPositiveInfinity(value))) + { + // Reset the property to its original state before throwing + source._ignorePropertyChange = true; + source.SetValue(e.Property, (double)e.OldValue); + + string message = string.Format( + CultureInfo.InvariantCulture, + Properties.Resources.WrapPanel_OnItemHeightOrWidthPropertyChanged_InvalidValue, + value); + throw new ArgumentException(message, "value"); + } + + // The length properties affect measuring. + source.InvalidateMeasure(); + } + + /// + /// Measures the child elements of a + /// in anticipation + /// of arranging them during the + /// + /// pass. + /// + /// + /// The size available to child elements of the wrap panel. + /// + /// + /// The size required by the + /// and its + /// elements. + /// + [SuppressMessage("Microsoft.Naming", "CA1725:ParameterNamesShouldMatchBaseDeclaration", MessageId = "0#", Justification = "Compat with WPF.")] + protected override Size MeasureOverride(Size constraint) + { + // Variables tracking the size of the current line, the total size + // measured so far, and the maximum size available to fill. Note + // that the line might represent a row or a column depending on the + // orientation. + Orientation o = Orientation; + OrientedSize lineSize = new OrientedSize(o); + OrientedSize totalSize = new OrientedSize(o); + OrientedSize maximumSize = new OrientedSize(o, constraint.Width, constraint.Height); + + // Determine the constraints for individual items + double itemWidth = ItemWidth; + double itemHeight = ItemHeight; + bool hasFixedWidth = !itemWidth.IsNaN(); + bool hasFixedHeight = !itemHeight.IsNaN(); + Size itemSize = new Size( + hasFixedWidth ? itemWidth : constraint.Width, + hasFixedHeight ? itemHeight : constraint.Height); + + // Measure each of the Children + foreach (UIElement element in Children) + { + // Determine the size of the element + element.Measure(itemSize); + OrientedSize elementSize = new OrientedSize( + o, + hasFixedWidth ? itemWidth : element.DesiredSize.Width, + hasFixedHeight ? itemHeight : element.DesiredSize.Height); + + // If this element falls of the edge of the line + if (NumericExtensions.IsGreaterThan(lineSize.Direct + elementSize.Direct, maximumSize.Direct)) + { + // Update the total size with the direct and indirect growth + // for the current line + totalSize.Direct = Math.Max(lineSize.Direct, totalSize.Direct); + totalSize.Indirect += lineSize.Indirect; + + // Move the element to a new line + lineSize = elementSize; + + // If the current element is larger than the maximum size, + // place it on a line by itself + if (NumericExtensions.IsGreaterThan(elementSize.Direct, maximumSize.Direct)) + { + // Update the total size for the line occupied by this + // single element + totalSize.Direct = Math.Max(elementSize.Direct, totalSize.Direct); + totalSize.Indirect += elementSize.Indirect; + + // Move to a new line + lineSize = new OrientedSize(o); + } + } + else + { + // Otherwise just add the element to the end of the line + lineSize.Direct += elementSize.Direct; + lineSize.Indirect = Math.Max(lineSize.Indirect, elementSize.Indirect); + } + } + + // Update the total size with the elements on the last line + totalSize.Direct = Math.Max(lineSize.Direct, totalSize.Direct); + totalSize.Indirect += lineSize.Indirect; + + // Return the total size required as an un-oriented quantity + return new Size(totalSize.Width, totalSize.Height); + } + + /// + /// Arranges and sizes the + /// control and its + /// child elements. + /// + /// + /// The area within the parent that the + /// should use + /// arrange itself and its children. + /// + /// + /// The actual size used by the + /// . + /// + protected override Size ArrangeOverride(Size finalSize) + { + // Variables tracking the size of the current line, and the maximum + // size available to fill. Note that the line might represent a row + // or a column depending on the orientation. + Orientation o = Orientation; + OrientedSize lineSize = new OrientedSize(o); + OrientedSize maximumSize = new OrientedSize(o, finalSize.Width, finalSize.Height); + + // Determine the constraints for individual items + double itemWidth = ItemWidth; + double itemHeight = ItemHeight; + bool hasFixedWidth = !itemWidth.IsNaN(); + bool hasFixedHeight = !itemHeight.IsNaN(); + double indirectOffset = 0; + double? directDelta = (o == Orientation.Horizontal) ? + (hasFixedWidth ? (double?)itemWidth : null) : + (hasFixedHeight ? (double?)itemHeight : null); + + // Measure each of the Children. We will process the elements one + // line at a time, just like during measure, but we will wait until + // we've completed an entire line of elements before arranging them. + // The lineStart and lineEnd variables track the size of the + // currently arranged line. + UIElementCollection children = Children; + int count = children.Count; + int lineStart = 0; + for (int lineEnd = 0; lineEnd < count; lineEnd++) + { + UIElement element = children[lineEnd]; + + // Get the size of the element + OrientedSize elementSize = new OrientedSize( + o, + hasFixedWidth ? itemWidth : element.DesiredSize.Width, + hasFixedHeight ? itemHeight : element.DesiredSize.Height); + + // If this element falls of the edge of the line + if (NumericExtensions.IsGreaterThan(lineSize.Direct + elementSize.Direct, maximumSize.Direct)) + { + // Then we just completed a line and we should arrange it + ArrangeLine(lineStart, lineEnd, directDelta, indirectOffset, lineSize.Indirect); + + // Move the current element to a new line + indirectOffset += lineSize.Indirect; + lineSize = elementSize; + + // If the current element is larger than the maximum size + if (NumericExtensions.IsGreaterThan(elementSize.Direct, maximumSize.Direct)) + { + // Arrange the element as a single line + ArrangeLine(lineEnd, ++lineEnd, directDelta, indirectOffset, elementSize.Indirect); + + // Move to a new line + indirectOffset += lineSize.Indirect; + lineSize = new OrientedSize(o); + } + + // Advance the start index to a new line after arranging + lineStart = lineEnd; + } + else + { + // Otherwise just add the element to the end of the line + lineSize.Direct += elementSize.Direct; + lineSize.Indirect = Math.Max(lineSize.Indirect, elementSize.Indirect); + } + } + + // Arrange any elements on the last line + if (lineStart < count) + { + ArrangeLine(lineStart, count, directDelta, indirectOffset, lineSize.Indirect); + } + + return finalSize; + } + + /// + /// Arrange a sequence of elements in a single line. + /// + /// + /// Index of the first element in the sequence to arrange. + /// + /// + /// Index of the last element in the sequence to arrange. + /// + /// + /// Optional fixed growth in the primary direction. + /// + /// + /// Offset of the line in the indirect direction. + /// + /// + /// Shared indirect growth of the elements on this line. + /// + private void ArrangeLine(int lineStart, int lineEnd, double? directDelta, double indirectOffset, double indirectGrowth) + { + double directOffset = 0.0; + + Orientation o = Orientation; + bool isHorizontal = o == Orientation.Horizontal; + + UIElementCollection children = Children; + for (int index = lineStart; index < lineEnd; index++) + { + // Get the size of the element + UIElement element = children[index]; + OrientedSize elementSize = new OrientedSize(o, element.DesiredSize.Width, element.DesiredSize.Height); + + // Determine if we should use the element's desired size or the + // fixed item width or height + double directGrowth = directDelta != null ? + directDelta.Value : + elementSize.Direct; + + // Arrange the element + Rect bounds = isHorizontal ? + new Rect(directOffset, indirectOffset, directGrowth, indirectGrowth) : + new Rect(indirectOffset, directOffset, indirectGrowth, directGrowth); + element.Arrange(bounds); + + directOffset += directGrowth; + } + } + } +} \ No newline at end of file diff --git a/PhoneToolkit.sln b/PhoneToolkit.sln new file mode 100644 index 0000000..6267fa1 --- /dev/null +++ b/PhoneToolkit.sln @@ -0,0 +1,50 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Phone.Controls.Toolkit", "Microsoft.Phone.Controls.Toolkit\Microsoft.Phone.Controls.Toolkit.csproj", "{0754458A-7AFC-463A-B27D-2F6980522119}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Phone.Controls.Toolkit.Design", "Microsoft.Phone.Controls.Toolkit.Design\Microsoft.Phone.Controls.Toolkit.Design.csproj", "{AF13D696-5FAC-4A05-BE77-B8A1C971C5E5}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Microsoft.Phone.Controls.Toolkit.VisualStudio.Design", "Microsoft.Phone.Controls.Toolkit.VisualStudio.Design\Microsoft.Phone.Controls.Toolkit.VisualStudio.Design.csproj", "{BF8E3967-F531-440F-9AD9-C8330E96577B}" +EndProject +Global + GlobalSection(TeamFoundationVersionControl) = preSolution + SccNumberOfProjects = 5 + SccEnterpriseProvider = {4CA58AB2-18FA-4F8D-95D4-32DDF27D184C} + SccTeamFoundationServer = http://vstfdevdiv:8080/devdiv + SccLocalPath0 = . + SccProjectUniqueName1 = PhoneToolkitSample\\PhoneToolkitSample.csproj + SccProjectName1 = PhoneToolkitSample + SccLocalPath1 = PhoneToolkitSample + SccProjectUniqueName2 = Microsoft.Phone.Controls.Toolkit\\Microsoft.Phone.Controls.Toolkit.csproj + SccProjectName2 = Microsoft.Phone.Controls.Toolkit + SccLocalPath2 = Microsoft.Phone.Controls.Toolkit + SccProjectUniqueName3 = Microsoft.Phone.Controls.Toolkit.Design\\Microsoft.Phone.Controls.Toolkit.Design.csproj + SccProjectName3 = Microsoft.Phone.Controls.Toolkit.Design + SccLocalPath3 = Microsoft.Phone.Controls.Toolkit.Design + SccProjectUniqueName4 = Microsoft.Phone.Controls.Toolkit.VisualStudio.Design\\Microsoft.Phone.Controls.Toolkit.VisualStudio.Design.csproj + SccProjectName4 = Microsoft.Phone.Controls.Toolkit.VisualStudio.Design + SccLocalPath4 = Microsoft.Phone.Controls.Toolkit.VisualStudio.Design + EndGlobalSection + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0754458A-7AFC-463A-B27D-2F6980522119}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0754458A-7AFC-463A-B27D-2F6980522119}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0754458A-7AFC-463A-B27D-2F6980522119}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0754458A-7AFC-463A-B27D-2F6980522119}.Release|Any CPU.Build.0 = Release|Any CPU + {AF13D696-5FAC-4A05-BE77-B8A1C971C5E5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AF13D696-5FAC-4A05-BE77-B8A1C971C5E5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AF13D696-5FAC-4A05-BE77-B8A1C971C5E5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AF13D696-5FAC-4A05-BE77-B8A1C971C5E5}.Release|Any CPU.Build.0 = Release|Any CPU + {BF8E3967-F531-440F-9AD9-C8330E96577B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BF8E3967-F531-440F-9AD9-C8330E96577B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BF8E3967-F531-440F-9AD9-C8330E96577B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BF8E3967-F531-440F-9AD9-C8330E96577B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/PhoneToolkit.snk b/PhoneToolkit.snk new file mode 100644 index 0000000000000000000000000000000000000000..be4aff83f7581586f175dda5de3a061992967046 GIT binary patch literal 596 zcmV-a0;~N80ssI2Bme+XQ$aES1ONa50098!j;!t#KUr5;;jBo=Rj$+AU^6tW>HB8E+0P9>;j3b3*KJch1Gj znZ5QD>{Di9vzwbvtgt5OIMM{I78{bPbKJ|W&(&CsdGzs0j=>5{(Vy~;@kS-_l(N!! zXkB@nS-K$GoVft7qcZ`Qp9Ksx-o=DHLZbCC9{CXnsf=zs_mJ|6GS>h23=h*<$y?g! zPKAm@niz^a6h-`Bn);s5hmX;MOErz#oGFz=`M4;31T1!z3M8y|bJd67)b<{GEvE7Y z{VW!FoQTUP%JJZ`BvN|?>M6W_Bhb~UOPQkP6J}3X?~U~z^!=9rU)oGeXyoCHJak-U znb~mT&Y3a@cKN8~J&*>>*S|IVy=q7}G9(n&bBR!h3!x~xWpt%ff+f7QB`}|lf&ypL z79OtCUg2KKE}Epa-f&E<2>iE$k{Eyg+}HGJg@6+&xxt>}5+x}(n~9?cYuPNveFQf* z(JgO^o4%b(Gi>Q7@Mn2jKXZ_jQ=vqXUw?0rL!2IV|1OU7KWcBp-?eno1o7YyCdy9c zm*i;ig&@#jp1M_B*HWBO4R0AyVfro|yAyY0kqizgEkvagNfEqK;xm!xHHbkM$Vcdl z?ybxRI3w=_*)d@HX>B+=45-sK?C<;ZqRuO=1fkaq(tO^w=?TnB>;?1`W0|3dc6c{C i&-g52d&8Vs;uX;bPJ*>~4Mv5h1>Tq5>>moupN-I6b|4=B literal 0 HcmV?d00001