PageRenderTime 58ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 0ms

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

https://bitbucket.org/jeremejevs/milk-manager
C# | 1383 lines | 895 code | 161 blank | 327 comment | 194 complexity | 0da1ab0d5e2759a2abb4ee923de730f9 MD5 | raw file

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

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

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