Advertisement
Guest User

Untitled

a guest
Aug 7th, 2011
272
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C# 40.54 KB | None | 0 0
  1. // (c) Copyright Microsoft Corporation.
  2. // This source is subject to the Microsoft Public License (Ms-PL).
  3. // Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
  4. // All other rights reserved.
  5.  
  6. using System;
  7. using System.Collections.Generic;
  8. using System.ComponentModel;
  9. using System.Diagnostics.CodeAnalysis;
  10. using System.Windows;
  11. using System.Windows.Controls;
  12. using System.Windows.Controls.Primitives;
  13. using System.Windows.Data;
  14. using System.Windows.Input;
  15. using System.Windows.Media;
  16. using System.Windows.Media.Animation;
  17. using System.Windows.Media.Imaging;
  18. using System.Windows.Shapes;
  19. #if WINDOWS_PHONE
  20. using Microsoft.Phone.Controls.Primitives;
  21. using Microsoft.Phone.Controls;
  22. using Microsoft.Phone.Shell;
  23. #endif
  24.  
  25. #if WINDOWS_PHONE
  26. namespace Microsoft.Phone.Controls
  27. #else
  28. namespace System.Windows.Controls
  29. #endif
  30. {
  31.     /// <summary>
  32.     /// Represents a pop-up menu that enables a control to expose functionality that is specific to the context of the control.
  33.     /// </summary>
  34.     /// <QualityBand>Preview</QualityBand>
  35. #if WINDOWS_PHONE
  36.     [TemplateVisualState(GroupName = VisibilityGroupName, Name = OpenVisibilityStateName)]
  37.     [TemplateVisualState(GroupName = VisibilityGroupName, Name = ClosedVisibilityStateName)]
  38.     [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Code flow is reasonably clear.")]
  39. #endif
  40.     public class ContextMenu : MenuBase
  41.     {
  42. #if WINDOWS_PHONE
  43.         /// <summary>
  44.         /// Visibility state group.
  45.         /// </summary>
  46.         private const string VisibilityGroupName = "VisibilityStates";
  47.  
  48.         /// <summary>
  49.         /// Open visibility state.
  50.         /// </summary>
  51.         private const string OpenVisibilityStateName = "Open";
  52.  
  53.         /// <summary>
  54.         /// Closed visibility state.
  55.         /// </summary>
  56.         private const string ClosedVisibilityStateName = "Closed";
  57.  
  58.         /// <summary>
  59.         /// Stores a reference to the PhoneApplicationPage that contains the owning object.
  60.         /// </summary>
  61.         private PhoneApplicationPage _page;
  62.  
  63.         /// <summary>
  64.         /// Stores a reference to a list of ApplicationBarIconButtons for which the Click event is being handled.
  65.         /// </summary>
  66.         private readonly List<ApplicationBarIconButton> _applicationBarIconButtons = new List<ApplicationBarIconButton>();
  67.  
  68.         /// <summary>
  69.         /// Stores a reference to the Storyboard used to animate the background resize.
  70.         /// </summary>
  71.         private Storyboard _backgroundResizeStoryboard;
  72.  
  73.         /// <summary>
  74.         /// Stores a reference to the Storyboard used to animate the ContextMenu open.
  75.         /// </summary>
  76.         private Storyboard _openingStoryboard;
  77.  
  78.         /// <summary>
  79.         /// Tracks whether the Storyboard used to animate the ContextMenu open is active.
  80.         /// </summary>
  81.         private bool _openingStoryboardPlaying;
  82.  
  83.         /// <summary>
  84.         /// Tracks the threshold for releasing contact during the ContextMenu open animation.
  85.         /// </summary>
  86.         private DateTime _openingStoryboardReleaseThreshold;
  87. #endif
  88.  
  89.         /// <summary>
  90.         /// Stores a reference to the current root visual.
  91.         /// </summary>
  92. #if WINDOWS_PHONE
  93.         private PhoneApplicationFrame _rootVisual;
  94. #else
  95.         private FrameworkElement _rootVisual;
  96. #endif
  97.  
  98.         /// <summary>
  99.         /// Stores the last known mouse position (via MouseMove).
  100.         /// </summary>
  101.         private Point _mousePosition;
  102.  
  103.         /// <summary>
  104.         /// Stores a reference to the object that owns the ContextMenu.
  105.         /// </summary>
  106.         private DependencyObject _owner;
  107.  
  108.         /// <summary>
  109.         /// Stores a reference to the current Popup.
  110.         /// </summary>
  111.         private Popup _popup;
  112.  
  113.         /// <summary>
  114.         /// Stores a reference to the current overlay.
  115.         /// </summary>
  116.         private Panel _overlay;
  117.  
  118.         /// <summary>
  119.         /// Stores a reference to the current Popup alignment point.
  120.         /// </summary>
  121.         private Point _popupAlignmentPoint;
  122.  
  123.         /// <summary>
  124.         /// Stores a value indicating whether the IsOpen property is being updated by ContextMenu.
  125.         /// </summary>
  126.         private bool _settingIsOpen;
  127.  
  128.         /// <summary>
  129.         /// Gets or sets the owning object for the ContextMenu.
  130.         /// </summary>
  131.         internal DependencyObject Owner
  132.         {
  133.             get { return _owner; }
  134.             set
  135.             {
  136.                 if (null != _owner)
  137.                 {
  138.                     FrameworkElement ownerFrameworkElement = _owner as FrameworkElement;
  139.                     if (null != ownerFrameworkElement)
  140.                     {
  141. #if WINDOWS_PHONE
  142.                         GestureListener listener = GestureService.GetGestureListener(ownerFrameworkElement);
  143.                         listener.Hold -= new EventHandler<GestureEventArgs>(HandleOwnerHold);
  144.                         ownerFrameworkElement.Loaded -= new RoutedEventHandler(HandleOwnerLoaded);
  145.                         ownerFrameworkElement.Unloaded -= new RoutedEventHandler(HandleOwnerUnloaded);
  146.                         HandleOwnerUnloaded(null, null);
  147. #else
  148.                         ownerFrameworkElement.MouseRightButtonDown -= new MouseButtonEventHandler(HandleOwnerMouseRightButtonDown);
  149. #endif
  150.                     }
  151.                 }
  152.                 _owner = value;
  153.                 if (null != _owner)
  154.                 {
  155.                     FrameworkElement ownerFrameworkElement = _owner as FrameworkElement;
  156.                     if (null != ownerFrameworkElement)
  157.                     {
  158. #if WINDOWS_PHONE
  159.                         GestureListener listener = GestureService.GetGestureListener(ownerFrameworkElement);
  160.                         listener.Hold += new EventHandler<GestureEventArgs>(HandleOwnerHold);
  161.                         ownerFrameworkElement.Loaded += new RoutedEventHandler(HandleOwnerLoaded);
  162.                         ownerFrameworkElement.Unloaded += new RoutedEventHandler(HandleOwnerUnloaded);
  163.                         // Owner *may* already be live and have fired its Loaded event - hook up manually if necessary
  164.                         DependencyObject parent = ownerFrameworkElement;
  165.                         while (parent != null)
  166.                         {
  167.                             parent = VisualTreeHelper.GetParent(parent);
  168.                             if ((null != parent) && (parent == _rootVisual))
  169.                             {
  170.                                 HandleOwnerLoaded(null, null);
  171.                                 break;
  172.                             }
  173.                         }
  174. #else
  175.                         ownerFrameworkElement.MouseRightButtonDown += new MouseButtonEventHandler(HandleOwnerMouseRightButtonDown);
  176. #endif
  177.                     }
  178.                 }
  179.             }
  180.         }
  181.  
  182. #if WINDOWS_PHONE
  183.         /// <summary>
  184.         /// Gets or sets a value indicating whether the background will zoom out when the ContextMenu is open.
  185.         /// </summary>
  186.         public bool IsZoomEnabled
  187.         {
  188.             get { return (bool)GetValue(IsZoomEnabledProperty); }
  189.             set { SetValue(IsZoomEnabledProperty, value); }
  190.         }
  191.  
  192.         /// <summary>
  193.         /// Identifies the IsZoomEnabled dependency property.
  194.         /// </summary>
  195.         public static readonly DependencyProperty IsZoomEnabledProperty = DependencyProperty.Register(
  196.             "IsZoomEnabled",
  197.             typeof(bool),
  198.             typeof(ContextMenu),
  199.             new PropertyMetadata(true));
  200. #else
  201.         /// <summary>
  202.         /// Gets or sets the horizontal distance between the target origin and the popup alignment point.
  203.         /// </summary>
  204.         [TypeConverterAttribute(typeof(LengthConverter))]
  205.         public double HorizontalOffset
  206.         {
  207.             get { return (double)GetValue(HorizontalOffsetProperty); }
  208.             set { SetValue(HorizontalOffsetProperty, value); }
  209.         }
  210.  
  211.         /// <summary>
  212.         /// Identifies the HorizontalOffset dependency property.
  213.         /// </summary>
  214.         public static readonly DependencyProperty HorizontalOffsetProperty = DependencyProperty.Register(
  215.             "HorizontalOffset",
  216.             typeof(double),
  217.             typeof(ContextMenu),
  218.             new PropertyMetadata(0.0, OnHorizontalVerticalOffsetChanged));
  219. #endif
  220.  
  221.         /// <summary>
  222.         /// Gets or sets the vertical distance between the target origin and the popup alignment point.
  223.         /// </summary>
  224.         [TypeConverterAttribute(typeof(LengthConverter))]
  225.         public double VerticalOffset
  226.         {
  227.             get { return (double)GetValue(VerticalOffsetProperty); }
  228.             set { SetValue(VerticalOffsetProperty, value); }
  229.         }
  230.  
  231.         /// <summary>
  232.         /// Identifies the VerticalOffset dependency property.
  233.         /// </summary>
  234.         public static readonly DependencyProperty VerticalOffsetProperty = DependencyProperty.Register(
  235.             "VerticalOffset",
  236.             typeof(double),
  237.             typeof(ContextMenu),
  238.             new PropertyMetadata(0.0, OnHorizontalVerticalOffsetChanged));
  239.  
  240.         /// <summary>
  241.         /// Handles changes to the HorizontalOffset or VerticalOffset DependencyProperty.
  242.         /// </summary>
  243.         /// <param name="o">DependencyObject that changed.</param>
  244.         /// <param name="e">Event data for the DependencyPropertyChangedEvent.</param>
  245.         private static void OnHorizontalVerticalOffsetChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  246.         {
  247.             ((ContextMenu)o).UpdateContextMenuPlacement();
  248.         }
  249.  
  250.         /// <summary>
  251.         /// Gets or sets a value indicating whether the ContextMenu is visible.
  252.         /// </summary>
  253.         public bool IsOpen
  254.         {
  255.             get { return (bool)GetValue(IsOpenProperty); }
  256.             set { SetValue(IsOpenProperty, value); }
  257.         }
  258.  
  259.         /// <summary>
  260.         /// Identifies the IsOpen dependency property.
  261.         /// </summary>
  262.         public static readonly DependencyProperty IsOpenProperty = DependencyProperty.Register(
  263.             "IsOpen",
  264.             typeof(bool),
  265.             typeof(ContextMenu),
  266.             new PropertyMetadata(false, OnIsOpenChanged));
  267.  
  268.         /// <summary>
  269.         /// Handles changes to the IsOpen DependencyProperty.
  270.         /// </summary>
  271.         /// <param name="o">DependencyObject that changed.</param>
  272.         /// <param name="e">Event data for the DependencyPropertyChangedEvent.</param>
  273.         private static void OnIsOpenChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  274.         {
  275.             ((ContextMenu)o).OnIsOpenChanged(/*(bool)e.OldValue,*/ (bool)e.NewValue);
  276.         }
  277.  
  278.         /// <summary>
  279.         /// Handles changes to the IsOpen property.
  280.         /// </summary>
  281.         /// <param name="newValue">New value.</param>
  282.         private void OnIsOpenChanged(/*bool oldValue,*/ bool newValue)
  283.         {
  284.             if (!_settingIsOpen)
  285.             {
  286.                 if (newValue)
  287.                 {
  288.                     OpenPopup(_mousePosition);
  289.                 }
  290.                 else
  291.                 {
  292.                     ClosePopup();
  293.                 }
  294.             }
  295.         }
  296.  
  297.         /// <summary>
  298.         /// Occurs when a particular instance of a ContextMenu opens.
  299.         /// </summary>
  300.         public event RoutedEventHandler Opened;
  301.  
  302.         /// <summary>
  303.         /// Called when the Opened event occurs.
  304.         /// </summary>
  305.         /// <param name="e">Event arguments.</param>
  306.         protected virtual void OnOpened(RoutedEventArgs e)
  307.         {
  308. #if WINDOWS_PHONE
  309.             GoToVisualState(OpenVisibilityStateName, true);
  310. #endif
  311.             RoutedEventHandler handler = Opened;
  312.             if (null != handler)
  313.             {
  314.                 handler.Invoke(this, e);
  315.             }
  316.         }
  317.  
  318.         /// <summary>
  319.         /// Occurs when a particular instance of a ContextMenu closes.
  320.         /// </summary>
  321.         public event RoutedEventHandler Closed;
  322.  
  323.         /// <summary>
  324.         /// Called when the Closed event occurs.
  325.         /// </summary>
  326.         /// <param name="e">Event arguments.</param>
  327.         protected virtual void OnClosed(RoutedEventArgs e)
  328.         {
  329. #if WINDOWS_PHONE
  330.             GoToVisualState(ClosedVisibilityStateName, true);
  331. #endif
  332.             RoutedEventHandler handler = Closed;
  333.             if (null != handler)
  334.             {
  335.                 handler.Invoke(this, e);
  336.             }
  337.         }
  338.  
  339.         /// <summary>
  340.         /// Initializes a new instance of the ContextMenu class.
  341.         /// </summary>
  342.         public ContextMenu()
  343.         {
  344.             DefaultStyleKey = typeof(ContextMenu);
  345.  
  346.             // Temporarily hook LayoutUpdated to find out when Application.Current.RootVisual gets set.
  347.             LayoutUpdated += new EventHandler(HandleLayoutUpdated);
  348.         }
  349.  
  350. #if WINDOWS_PHONE
  351.         /// <summary>
  352.         /// Called when a new Template is applied.
  353.         /// </summary>
  354.         public override void OnApplyTemplate()
  355.         {
  356.             // Unhook from old Template
  357.             if (null != _openingStoryboard)
  358.             {
  359.                 _openingStoryboard.Completed -= new EventHandler(HandleStoryboardCompleted);
  360.                 _openingStoryboard = null;
  361.             }
  362.             _openingStoryboardPlaying = false;
  363.  
  364.             // Apply new template
  365.             base.OnApplyTemplate();
  366.  
  367.             // Hook up to new template
  368.             FrameworkElement templateRoot = VisualTreeHelper.GetChild(this, 0) as FrameworkElement;
  369.             if (null != templateRoot)
  370.             {
  371.                 foreach (VisualStateGroup group in VisualStateManager.GetVisualStateGroups(templateRoot))
  372.                 {
  373.                     if (VisibilityGroupName == group.Name)
  374.                     {
  375.                         foreach (VisualState state in group.States)
  376.                         {
  377.                             if ((OpenVisibilityStateName == state.Name) && (null != state.Storyboard))
  378.                             {
  379.                                 _openingStoryboard = state.Storyboard;
  380.                                 _openingStoryboard.Completed += new EventHandler(HandleStoryboardCompleted);
  381.                             }
  382.                         }
  383.                     }
  384.                 }
  385.             }
  386.  
  387.             // Go to correct visual state(s)
  388.             GoToVisualState(ClosedVisibilityStateName, false);
  389.             if (IsOpen)
  390.             {
  391.                 // Handles initial open (where OnOpened is called before OnApplyTemplate)
  392.                 GoToVisualState(OpenVisibilityStateName, true);
  393.             }
  394.         }
  395.  
  396.         /// <summary>
  397.         /// Handles the Completed event of the opening Storyboard.
  398.         /// </summary>
  399.         /// <param name="sender">Source of the event.</param>
  400.         /// <param name="e">Event arguments.</param>
  401.         private void HandleStoryboardCompleted(object sender, EventArgs e)
  402.         {
  403.             _openingStoryboardPlaying = false;
  404.         }
  405.  
  406.         /// <summary>
  407.         /// Uses VisualStateManager to go to a new visual state.
  408.         /// </summary>
  409.         /// <param name="stateName">The state to transition to.</param>
  410.         /// <param name="useTransitions">true to use a System.Windows.VisualTransition to transition between states; otherwise, false.</param>
  411.         private void GoToVisualState(string stateName, bool useTransitions)
  412.         {
  413.             if ((OpenVisibilityStateName == stateName) && (null != _openingStoryboard))
  414.             {
  415.                 _openingStoryboardPlaying = true;
  416.                 _openingStoryboardReleaseThreshold = DateTime.UtcNow.AddSeconds(0.3);
  417.             }
  418.             VisualStateManager.GoToState(this, stateName, useTransitions);
  419.         }
  420. #endif
  421.  
  422.         /// <summary>
  423.         /// Called when the left mouse button is pressed.
  424.         /// </summary>
  425.         /// <param name="e">The event data for the MouseLeftButtonDown event.</param>
  426.         protected override void OnMouseLeftButtonDown(MouseButtonEventArgs e)
  427.         {
  428.             if (e == null)
  429.             {
  430.                 throw new ArgumentNullException("e");
  431.             }
  432.  
  433.             e.Handled = true;
  434.             base.OnMouseLeftButtonDown(e);
  435.         }
  436.  
  437. #if !WINDOWS_PHONE
  438.         /// <summary>
  439.         /// Called when the right mouse button is pressed.
  440.         /// </summary>
  441.         /// <param name="e">The event data for the MouseRightButtonDown event.</param>
  442.         protected override void OnMouseRightButtonDown(MouseButtonEventArgs e)
  443.         {
  444.             e.Handled = true;
  445.             base.OnMouseRightButtonDown(e);
  446.         }
  447. #endif
  448.  
  449.         /// <summary>
  450.         /// Responds to the KeyDown event.
  451.         /// </summary>
  452.         /// <param name="e">The event data for the KeyDown event.</param>
  453.         protected override void OnKeyDown(KeyEventArgs e)
  454.         {
  455.             if (e == null)
  456.             {
  457.                 throw new ArgumentNullException("e");
  458.             }
  459.  
  460.             switch (e.Key)
  461.             {
  462.                 case Key.Up:
  463.                     FocusNextItem(false);
  464.                     e.Handled = true;
  465.                     break;
  466.                 case Key.Down:
  467.                     FocusNextItem(true);
  468.                     e.Handled = true;
  469.                     break;
  470.                 case Key.Escape:
  471.                     ClosePopup();
  472.                     e.Handled = true;
  473.                     break;
  474.                 // case Key.Apps: // Key.Apps not defined by Silverlight 4
  475.             }
  476.             base.OnKeyDown(e);
  477.         }
  478.  
  479.         /// <summary>
  480.         /// Handles the LayoutUpdated event to capture Application.Current.RootVisual.
  481.         /// </summary>
  482.         /// <param name="sender">Source of the event.</param>
  483.         /// <param name="e">Event arguments.</param>
  484.         private void HandleLayoutUpdated(object sender, EventArgs e)
  485.         {
  486.             if (null != Application.Current.RootVisual)
  487.             {
  488.                 // Application.Current.RootVisual is valid now
  489.                 InitializeRootVisual();
  490.                 // Unhook event
  491.                 LayoutUpdated -= new EventHandler(HandleLayoutUpdated);
  492.             }
  493.         }
  494.  
  495.         /// <summary>
  496.         /// Handles the RootVisual's MouseMove event to track the last mouse position.
  497.         /// </summary>
  498.         /// <param name="sender">Source of the event.</param>
  499.         /// <param name="e">Event arguments.</param>
  500.         private void HandleRootVisualMouseMove(object sender, MouseEventArgs e)
  501.         {
  502.             _mousePosition = e.GetPosition(null);
  503.         }
  504.  
  505. #if WINDOWS_PHONE
  506.         /// <summary>
  507.         /// Handles the ManipulationCompleted event for the RootVisual.
  508.         /// </summary>
  509.         /// <param name="sender">Source of the event.</param>
  510.         /// <param name="e">Event arguments.</param>
  511.         private void HandleRootVisualManipulationCompleted(object sender, ManipulationCompletedEventArgs e)
  512.         {
  513.             // Breaking contact during the ContextMenu show animation should cancel the ContextMenu
  514.             if (_openingStoryboardPlaying && (DateTime.UtcNow <= _openingStoryboardReleaseThreshold))
  515.             {
  516.                 IsOpen = false;
  517.             }
  518.         }
  519.  
  520.         /// <summary>
  521.         /// Handles the Hold event for the owning element.
  522.         /// </summary>
  523.         /// <param name="sender">Source of the event.</param>
  524.         /// <param name="e">Event arguments.</param>
  525.         private void HandleOwnerHold(object sender, GestureEventArgs e)
  526. #else
  527.         /// <summary>
  528.         /// Handles the MouseRightButtonDown event for the owning element.
  529.         /// </summary>
  530.         /// <param name="sender">Source of the event.</param>
  531.         /// <param name="e">Event arguments.</param>
  532.         private void HandleOwnerMouseRightButtonDown(object sender, MouseButtonEventArgs e)
  533. #endif
  534.         {
  535.             if (!IsOpen)
  536.             {
  537.                 OpenPopup(e.GetPosition(null));
  538.                 e.Handled = true;
  539.             }
  540.         }
  541.  
  542. #if WINDOWS_PHONE
  543.         /// <summary>
  544.         /// Identifies the ApplicationBarMirror dependency property.
  545.         /// </summary>
  546.         private static readonly DependencyProperty ApplicationBarMirrorProperty = DependencyProperty.Register(
  547.             "ApplicationBarMirror",
  548.             typeof(IApplicationBar),
  549.             typeof(ContextMenu),
  550.             new PropertyMetadata(OnApplicationBarMirrorChanged));
  551.  
  552.         /// <summary>
  553.         /// Handles changes to the ApplicationBarMirror DependencyProperty.
  554.         /// </summary>
  555.         /// <param name="o">DependencyObject that changed.</param>
  556.         /// <param name="e">Event data for the DependencyPropertyChangedEvent.</param>
  557.         private static void OnApplicationBarMirrorChanged(DependencyObject o, DependencyPropertyChangedEventArgs e)
  558.         {
  559.             ((ContextMenu)o).OnApplicationBarMirrorChanged((IApplicationBar)e.OldValue, (IApplicationBar)e.NewValue);
  560.         }
  561.  
  562.         /// <summary>
  563.         /// Handles changes to the ApplicationBarMirror property.
  564.         /// </summary>
  565.         /// <param name="oldValue">Old value.</param>
  566.         /// <param name="newValue">New value.</param>
  567.         private void OnApplicationBarMirrorChanged(IApplicationBar oldValue, IApplicationBar newValue)
  568.         {
  569.             if (null != oldValue)
  570.             {
  571.                 oldValue.StateChanged -= new EventHandler<ApplicationBarStateChangedEventArgs>(HandleEventThatClosesContextMenu);
  572.             }
  573.             if (null != newValue)
  574.             {
  575.                 newValue.StateChanged += new EventHandler<ApplicationBarStateChangedEventArgs>(HandleEventThatClosesContextMenu);
  576.             }
  577.         }
  578.  
  579.         /// <summary>
  580.         /// Handles an event which should close an open ContextMenu.
  581.         /// </summary>
  582.         /// <param name="sender">Source of the event.</param>
  583.         /// <param name="e">Event arguments.</param>
  584.         private void HandleEventThatClosesContextMenu(object sender, EventArgs e)
  585.         {
  586.             // Close the ContextMenu because the elements and/or layout is likely to have changed significantly
  587.             IsOpen = false;
  588.         }
  589.  
  590.         /// <summary>
  591.         /// Handles the Loaded event of the Owner.
  592.         /// </summary>
  593.         /// <param name="sender">Source of the event.</param>
  594.         /// <param name="e">Event arguments.</param>
  595.         private void HandleOwnerLoaded(object sender, RoutedEventArgs e)
  596.         {
  597.             if (null == _page) // Don't want to attach to BackKeyPress twice
  598.             {
  599.                 InitializeRootVisual();
  600.                 if (null != _rootVisual)
  601.                 {
  602.                     _page = _rootVisual.Content as PhoneApplicationPage;
  603.                     if (_page != null)
  604.                     {
  605.                         _page.BackKeyPress += new EventHandler<CancelEventArgs>(HandlePageBackKeyPress);
  606.                         SetBinding(ApplicationBarMirrorProperty, new Binding { Source = _page, Path = new PropertyPath("ApplicationBar") });
  607.                     }
  608.                 }
  609.             }
  610.         }
  611.  
  612.         /// <summary>
  613.         /// Handles the Unloaded event of the Owner.
  614.         /// </summary>
  615.         /// <param name="sender">Source of the event.</param>
  616.         /// <param name="e">Event arguments.</param>
  617.         private void HandleOwnerUnloaded(object sender, RoutedEventArgs e)
  618.         {
  619.             if (_page != null)
  620.             {
  621.                 _page.BackKeyPress -= new EventHandler<CancelEventArgs>(HandlePageBackKeyPress);
  622.                 ClearValue(ApplicationBarMirrorProperty);
  623.                 _page = null;
  624.             }
  625.         }
  626.  
  627.         /// <summary>
  628.         /// Handles the BackKeyPress of the containing PhoneApplicationPage.
  629.         /// </summary>
  630.         /// <param name="sender">Source of the event.</param>
  631.         /// <param name="e">Event arguments.</param>
  632.         private void HandlePageBackKeyPress(object sender, CancelEventArgs e)
  633.         {
  634.             if (IsOpen)
  635.             {
  636.                 IsOpen = false;
  637.                 e.Cancel = true;
  638.             }
  639.         }
  640.  
  641.         /// <summary>
  642.         /// Calls TransformToVisual on the specified element for the specified visual, suppressing the ArgumentException that can occur in some cases.
  643.         /// </summary>
  644.         /// <param name="element">Element on which to call TransformToVisual.</param>
  645.         /// <param name="visual">Visual to pass to the call to TransformToVisual.</param>
  646.         /// <returns>Resulting GeneralTransform object.</returns>
  647.         private static GeneralTransform SafeTransformToVisual(UIElement element, UIElement visual)
  648.         {
  649.             GeneralTransform result;
  650.             try
  651.             {
  652.                 result = element.TransformToVisual(visual);
  653.             }
  654.             catch (ArgumentException)
  655.             {
  656.                 // Not perfect, but better than throwing an exception
  657.                 result = new TranslateTransform();
  658.             }
  659.             return result;
  660.         }
  661. #endif
  662.  
  663.         /// <summary>
  664.         /// Initialize the _rootVisual property (if possible and not already done).
  665.         /// </summary>
  666.         private void InitializeRootVisual()
  667.         {
  668.             if (null == _rootVisual)
  669.             {
  670.                 // Try to capture the Application's RootVisual
  671.                 _rootVisual = Application.Current.RootVisual as
  672. #if WINDOWS_PHONE
  673.                     PhoneApplicationFrame;
  674. #else
  675.                     FrameworkElement;
  676. #endif
  677.                 if (null != _rootVisual)
  678.                 {
  679.                     // Ideally, this would use AddHandler(MouseMoveEvent), but MouseMoveEvent doesn't exist
  680.                     _rootVisual.MouseMove += new MouseEventHandler(HandleRootVisualMouseMove);
  681.  
  682. #if WINDOWS_PHONE
  683.                     _rootVisual.ManipulationCompleted += new EventHandler<ManipulationCompletedEventArgs>(HandleRootVisualManipulationCompleted);
  684.                     _rootVisual.OrientationChanged += new EventHandler<OrientationChangedEventArgs>(HandleEventThatClosesContextMenu);
  685. #endif
  686.                 }
  687.             }
  688.         }
  689.  
  690.         /// <summary>
  691.         /// Sets focus to the next item in the ContextMenu.
  692.         /// </summary>
  693.         /// <param name="down">True to move the focus down; false to move it up.</param>
  694.         private void FocusNextItem(bool down)
  695.         {
  696.             int count = Items.Count;
  697.             int startingIndex = down ? -1 : count;
  698.             MenuItem focusedMenuItem = FocusManager.GetFocusedElement() as MenuItem;
  699.             if (null != focusedMenuItem && (this == focusedMenuItem.ParentMenuBase))
  700.             {
  701.                 startingIndex = ItemContainerGenerator.IndexFromContainer(focusedMenuItem);
  702.             }
  703.             int index = startingIndex;
  704.             do
  705.             {
  706.                 index = (index + count + (down ? 1 : -1)) % count;
  707.                 MenuItem container = ItemContainerGenerator.ContainerFromIndex(index) as MenuItem;
  708.                 if (null != container)
  709.                 {
  710.                     if (container.IsEnabled && container.Focus())
  711.                     {
  712.                         break;
  713.                     }
  714.                 }
  715.             }
  716.             while (index != startingIndex);
  717.         }
  718.  
  719.         /// <summary>
  720.         /// Called when a child MenuItem is clicked.
  721.         /// </summary>
  722.         internal void ChildMenuItemClicked()
  723.         {
  724.             ClosePopup();
  725.         }
  726.  
  727.         /// <summary>
  728.         /// Handles the SizeChanged event for the ContextMenu or RootVisual.
  729.         /// </summary>
  730.         /// <param name="sender">Source of the event.</param>
  731.         /// <param name="e">Event arguments.</param>
  732.         private void HandleContextMenuOrRootVisualSizeChanged(object sender, SizeChangedEventArgs e)
  733.         {
  734.             UpdateContextMenuPlacement();
  735.         }
  736.  
  737.         /// <summary>
  738.         /// Handles the MouseButtonDown events for the overlay.
  739.         /// </summary>
  740.         /// <param name="sender">Source of the event.</param>
  741.         /// <param name="e">Event arguments.</param>
  742.         private void HandleOverlayMouseButtonDown(object sender, MouseButtonEventArgs e)
  743.         {
  744.             ClosePopup();
  745.             e.Handled = true;
  746.         }
  747.  
  748.         /// <summary>
  749.         /// Updates the location and size of the Popup and overlay.
  750.         /// </summary>
  751.         private void UpdateContextMenuPlacement()
  752.         {
  753.             if ((null != _rootVisual) && (null != _overlay))
  754.             {
  755.                 // Start with the current Popup alignment point
  756.                 double x = _popupAlignmentPoint.X;
  757.                 double y = _popupAlignmentPoint.Y;
  758.                 // Adjust for offset
  759. #if !WINDOWS_PHONE
  760.                 x += HorizontalOffset;
  761. #endif
  762.                 y += VerticalOffset;
  763. #if WINDOWS_PHONE
  764.                 // Determine frame/page bounds
  765.                 bool portrait = (_rootVisual.Orientation & PageOrientation.Portrait) == PageOrientation.Portrait;
  766.                 double effectiveWidth = portrait ? _rootVisual.ActualWidth : _rootVisual.ActualHeight;
  767.                 double effectiveHeight = portrait ? _rootVisual.ActualHeight : _rootVisual.ActualWidth;
  768.                 Rect bounds = new Rect(0, 0, effectiveWidth, effectiveHeight);
  769.                 if (_page != null)
  770.                 {
  771.                     bounds = SafeTransformToVisual(_page, _rootVisual).TransformBounds(new Rect(0, 0, _page.ActualWidth, _page.ActualHeight));
  772.                 }
  773.                 // Left align with full width
  774.                 x = bounds.Left;
  775.                 Width = bounds.Width;
  776.                 // Ensure the bottom is visible / ensure the top is visible
  777.                 y = Math.Min(y, bounds.Bottom - ActualHeight);
  778.                 y = Math.Max(y, bounds.Top);
  779. #else
  780.                 // Try not to let it stick out too far to the right/bottom
  781.                 x = Math.Min(x, _rootVisual.ActualWidth - ActualWidth);
  782.                 y = Math.Min(y, _rootVisual.ActualHeight - ActualHeight);
  783. #endif
  784.                 // Do not let it stick out too far to the left/top
  785.                 x = Math.Max(x, 0);
  786.                 y = Math.Max(y, 0);
  787.                 // Set the new location
  788.                 Canvas.SetLeft(this, x);
  789.                 Canvas.SetTop(this, y);
  790.                 // Size the overlay to match the new container
  791. #if WINDOWS_PHONE
  792.                 _overlay.Width = effectiveWidth;
  793.                 _overlay.Height = effectiveHeight;
  794. #else
  795.                 _overlay.Width = _rootVisual.ActualWidth;
  796.                 _overlay.Height = _rootVisual.ActualHeight;
  797. #endif
  798.             }
  799.         }
  800.  
  801.         /// <summary>
  802.         /// Opens the Popup.
  803.         /// </summary>
  804.         /// <param name="position">Position to place the Popup.</param>
  805.         [SuppressMessage("Microsoft.Maintainability", "CA1506:AvoidExcessiveClassCoupling", Justification = "Code flow is reasonably clear.")]
  806.         private void OpenPopup(Point position)
  807.         {
  808.             _popupAlignmentPoint = position;
  809.  
  810.             InitializeRootVisual();
  811.  
  812.             _overlay = new Canvas { Background = new SolidColorBrush(Colors.Transparent) };
  813.             _overlay.MouseLeftButtonDown += new MouseButtonEventHandler(HandleOverlayMouseButtonDown);
  814. #if !WINDOWS_PHONE
  815.             _overlay.MouseRightButtonDown += new MouseButtonEventHandler(HandleOverlayMouseButtonDown);
  816. #endif
  817.             _overlay.Children.Add(this);
  818.  
  819. #if WINDOWS_PHONE
  820.             if (IsZoomEnabled && (null != _rootVisual))
  821.             {
  822.                 // Capture effective width/height
  823.                 bool portrait = PageOrientation.Portrait == (PageOrientation.Portrait & _rootVisual.Orientation);
  824.                 double width = portrait ? _rootVisual.ActualWidth : _rootVisual.ActualHeight;
  825.                 double height = portrait ? _rootVisual.ActualHeight : _rootVisual.ActualWidth;
  826.  
  827.                 // Create a layer for the background brush
  828.                 UIElement backgroundLayer = new Rectangle
  829.                 {
  830.                     Width = width,
  831.                     Height = height,
  832.                     Fill = new SolidColorBrush(Colors.White),
  833.                 };
  834.                 _overlay.Children.Insert(0, backgroundLayer);
  835.  
  836.                 // Create a layer for the page content
  837.                 WriteableBitmap writeableBitmap = new WriteableBitmap((int)width, (int)height);
  838.                 writeableBitmap.Render(_rootVisual, null);
  839.                 writeableBitmap.Invalidate();
  840.                 Transform scaleTransform = new ScaleTransform
  841.                 {
  842.                     CenterX = width / 2,
  843.                     CenterY = height / 2,
  844.                 };
  845.                 UIElement contentLayer = new Image
  846.                 {
  847.                     Source = writeableBitmap,
  848.                     RenderTransform = scaleTransform,
  849.                 };
  850.                 _overlay.Children.Insert(1, contentLayer);
  851.  
  852.                 // Create a layer for the owner element and its background
  853.                 FrameworkElement ownerElement = _owner as FrameworkElement;
  854.                 if (null != ownerElement)
  855.                 {
  856.                     Point point = SafeTransformToVisual(ownerElement, _rootVisual).Transform(new Point());
  857.  
  858.                     // Create a layer for the element's background
  859.                     UIElement elementBackground = new Rectangle
  860.                     {
  861.                         Width = ownerElement.ActualWidth,
  862.                         Height = ownerElement.ActualHeight,
  863.                         Fill = new SolidColorBrush(Colors.White),
  864.                     };
  865.                     Canvas.SetLeft(elementBackground, point.X);
  866.                     Canvas.SetTop(elementBackground, point.Y);
  867.                     _overlay.Children.Insert(2, elementBackground);
  868.  
  869.                     // Create a layer for the element
  870.                     UIElement element = new Image { Source = new WriteableBitmap(ownerElement, null) };
  871.                     Canvas.SetLeft(element, point.X);
  872.                     Canvas.SetTop(element, point.Y);
  873.                     _overlay.Children.Insert(3, element);
  874.                 }
  875.  
  876.                 // Prepare for scale animation
  877.                 double from = 1;
  878.                 double to = 0.94;
  879.                 TimeSpan timespan = TimeSpan.FromSeconds(0.40);
  880.                 IEasingFunction easingFunction = new ExponentialEase { EasingMode = EasingMode.EaseInOut };
  881.                 _backgroundResizeStoryboard = new Storyboard();
  882.  
  883.                 // Create an animation for the X scale
  884.                 DoubleAnimation animationX = new DoubleAnimation { From = from, To = to, Duration = timespan, EasingFunction = easingFunction };
  885.                 Storyboard.SetTarget(animationX, scaleTransform);
  886.                 Storyboard.SetTargetProperty(animationX, new PropertyPath(ScaleTransform.ScaleXProperty));
  887.                 _backgroundResizeStoryboard.Children.Add(animationX);
  888.  
  889.                 // Create an animation for the Y scale
  890.                 DoubleAnimation animationY = new DoubleAnimation { From = from, To = to, Duration = timespan, EasingFunction = easingFunction };
  891.                 Storyboard.SetTarget(animationY, scaleTransform);
  892.                 Storyboard.SetTargetProperty(animationY, new PropertyPath(ScaleTransform.ScaleYProperty));
  893.                 _backgroundResizeStoryboard.Children.Add(animationY);
  894.  
  895.                 // Play the animation
  896.                 _backgroundResizeStoryboard.Begin();
  897.             }
  898.  
  899.             // Create transforms for handling rotation
  900.             TransformGroup transforms = new TransformGroup();
  901.             if (null != _rootVisual)
  902.             {
  903.                 switch (_rootVisual.Orientation)
  904.                 {
  905.                     case PageOrientation.LandscapeLeft:
  906.                         transforms.Children.Add(new RotateTransform { Angle = 90 });
  907.                         transforms.Children.Add(new TranslateTransform { X = _rootVisual.ActualWidth });
  908.                         break;
  909.                     case PageOrientation.LandscapeRight:
  910.                         transforms.Children.Add(new RotateTransform { Angle = -90 });
  911.                         transforms.Children.Add(new TranslateTransform { Y = _rootVisual.ActualHeight });
  912.                         break;
  913.                 }
  914.             }
  915.             _overlay.RenderTransform = transforms;
  916.  
  917.             // Add Click handler for ApplicationBar Buttons
  918.             if ((null != _page) && (null != _page.ApplicationBar) && (null != _page.ApplicationBar.Buttons))
  919.             {
  920.                 foreach (object obj in _page.ApplicationBar.Buttons)
  921.                 {
  922.                     ApplicationBarIconButton button = obj as ApplicationBarIconButton;
  923.                     if (null != button)
  924.                     {
  925.                         button.Click += new EventHandler(HandleEventThatClosesContextMenu);
  926.                         _applicationBarIconButtons.Add(button);
  927.                     }
  928.                 }
  929.             }
  930. #endif
  931.  
  932.             _popup = new Popup { Child = _overlay };
  933.  
  934.             SizeChanged += new SizeChangedEventHandler(HandleContextMenuOrRootVisualSizeChanged);
  935.             if (null != _rootVisual)
  936.             {
  937.                 _rootVisual.SizeChanged += new SizeChangedEventHandler(HandleContextMenuOrRootVisualSizeChanged);
  938.             }
  939.             UpdateContextMenuPlacement();
  940.  
  941.             if (ReadLocalValue(DataContextProperty) == DependencyProperty.UnsetValue)
  942.             {
  943.                 DependencyObject dataContextSource = Owner ?? _rootVisual;
  944.                 SetBinding(DataContextProperty, new Binding("DataContext") { Source = dataContextSource });
  945.             }
  946.  
  947.             _popup.IsOpen = true;
  948.             Focus();
  949.  
  950.             // Update IsOpen
  951.             _settingIsOpen = true;
  952.             IsOpen = true;
  953.             _settingIsOpen = false;
  954.  
  955.             OnOpened(new RoutedEventArgs());
  956.         }
  957.  
  958.         /// <summary>
  959.         /// Closes the Popup.
  960.         /// </summary>
  961.         private void ClosePopup()
  962.         {
  963. #if WINDOWS_PHONE
  964.             if (null != _backgroundResizeStoryboard)
  965.             {
  966.                 // Swap all the From/To values to reverse the animation
  967.                 foreach (DoubleAnimation animation in _backgroundResizeStoryboard.Children)
  968.                 {
  969.                     double temp = animation.From.Value;
  970.                     animation.From = animation.To;
  971.                     animation.To = temp;
  972.                 }
  973.  
  974.                 // Capture member variables for delegate closure
  975.                 Popup popup = _popup;
  976.                 Panel overlay = _overlay;
  977.                 _backgroundResizeStoryboard.Completed += delegate
  978.                 {
  979.                     // Clear/close popup and overlay
  980.                     if (null != popup)
  981.                     {
  982.                         popup.IsOpen = false;
  983.                         popup.Child = null;
  984.                     }
  985.                     if (null != overlay)
  986.                     {
  987.                         overlay.Children.Clear();
  988.                     }
  989.                 };
  990.  
  991.                 // Begin the reverse animation
  992.                 _backgroundResizeStoryboard.Begin();
  993.  
  994.                 // Reset member variables
  995.                 _backgroundResizeStoryboard = null;
  996.                 _popup = null;
  997.                 _overlay = null;
  998.             }
  999.             else
  1000.             {
  1001. #endif
  1002.             if (null != _popup)
  1003.             {
  1004.                 _popup.IsOpen = false;
  1005.                 _popup.Child = null;
  1006.                 _popup = null;
  1007.             }
  1008.             if (null != _overlay)
  1009.             {
  1010.                 _overlay.Children.Clear();
  1011.                 _overlay = null;
  1012.             }
  1013. #if WINDOWS_PHONE
  1014.             }
  1015. #endif
  1016.             SizeChanged -= new SizeChangedEventHandler(HandleContextMenuOrRootVisualSizeChanged);
  1017.             if (null != _rootVisual)
  1018.             {
  1019.                 _rootVisual.SizeChanged -= new SizeChangedEventHandler(HandleContextMenuOrRootVisualSizeChanged);
  1020.             }
  1021.  
  1022. #if WINDOWS_PHONE
  1023.             // Remove Click handler for ApplicationBar Buttons
  1024.             foreach (ApplicationBarIconButton button in _applicationBarIconButtons)
  1025.             {
  1026.                 button.Click -= new EventHandler(HandleEventThatClosesContextMenu);
  1027.             }
  1028.             _applicationBarIconButtons.Clear();
  1029. #endif
  1030.  
  1031.             // Update IsOpen
  1032.             _settingIsOpen = true;
  1033.             IsOpen = false;
  1034.             _settingIsOpen = false;
  1035.  
  1036.             OnClosed(new RoutedEventArgs());
  1037.         }
  1038.     }
  1039. }
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement