PageRenderTime 54ms CodeModel.GetById 20ms RepoModel.GetById 0ms app.codeStats 0ms

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

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