/// <summary> /// Displays the <see cref="ContextMenu"/> if it was set. /// </summary> private void ShowContextMenu(Point cursorPosition) { if (IsDisposed) { return; } // raise preview event no matter whether context menu is currently set // or not (enables client to set it on demand) var args = RaisePreviewTrayContextMenuOpenEvent(); if (args.Handled) { return; } if (ContextMenu == null) { return; } // use absolute positioning. We need to set the coordinates, or a delayed opening // (e.g. when left-clicked) opens the context menu at the wrong place if the mouse // is moved! ContextMenu.Placement = PlacementMode.AbsolutePoint; ContextMenu.HorizontalOffset = cursorPosition.X; ContextMenu.VerticalOffset = cursorPosition.Y; ContextMenu.IsOpen = true; IntPtr handle = IntPtr.Zero; // try to get a handle on the context itself HwndSource source = (HwndSource)PresentationSource.FromVisual(ContextMenu); if (source != null) { handle = source.Handle; } // if we don't have a handle for the popup, fall back to the message sink if (handle == IntPtr.Zero) { handle = messageSink.MessageWindowHandle; } // activate the context menu or the message window to track deactivation - otherwise, the context menu // does not close if the user clicks somewhere else. With the message window // fallback, the context menu can't receive keyboard events - should not happen though WinApi.SetForegroundWindow(handle); // bubble event RaiseTrayContextMenuOpenEvent(); }
/// <summary> /// Processes mouse events, which are bubbled /// through the class' routed events, trigger /// certain actions (e.g. show a popup), or /// both. /// </summary> /// <param name="me">Event flag.</param> private void OnMouseEvent(MouseEvent me) { if (IsDisposed) { return; } switch (me) { case MouseEvent.MouseMove: RaiseTrayMouseMoveEvent(); // immediately return - there's nothing left to evaluate return; case MouseEvent.IconRightMouseDown: RaiseTrayRightMouseDownEvent(); break; case MouseEvent.IconLeftMouseDown: RaiseTrayLeftMouseDownEvent(); break; case MouseEvent.IconRightMouseUp: RaiseTrayRightMouseUpEvent(); break; case MouseEvent.IconLeftMouseUp: RaiseTrayLeftMouseUpEvent(); break; case MouseEvent.IconMiddleMouseDown: RaiseTrayMiddleMouseDownEvent(); break; case MouseEvent.IconMiddleMouseUp: RaiseTrayMiddleMouseUpEvent(); break; case MouseEvent.IconDoubleClick: // cancel single click timer singleClickTimer.Change(Timeout.Infinite, Timeout.Infinite); // bubble event RaiseTrayMouseDoubleClickEvent(); break; case MouseEvent.BalloonToolTipClicked: RaiseTrayBalloonTipClickedEvent(); break; default: throw new ArgumentOutOfRangeException(nameof(me), "Missing handler for mouse event flag: " + me); } // get mouse coordinates Point cursorPosition = new Point(); if (messageSink.Version == NotifyIconVersion.Vista) { // physical cursor position is supported for Vista and above WinApi.GetPhysicalCursorPos(ref cursorPosition); } else { WinApi.GetCursorPos(ref cursorPosition); } cursorPosition = TrayInfo.GetDeviceCoordinates(cursorPosition); bool isLeftClickCommandInvoked = false; // show popup, if requested if (me.IsMatch(PopupActivation)) { if (me == MouseEvent.IconLeftMouseUp) { // show popup once we are sure it's not a double click singleClickTimerAction = () => { LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this); ShowTrayPopup(cursorPosition); }; singleClickTimer.Change(DoubleClickWaitTime, Timeout.Infinite); isLeftClickCommandInvoked = true; } else { // show popup immediately ShowTrayPopup(cursorPosition); } } // show context menu, if requested if (me.IsMatch(MenuActivation)) { if (me == MouseEvent.IconLeftMouseUp) { // show context menu once we are sure it's not a double click singleClickTimerAction = () => { LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this); ShowContextMenu(cursorPosition); }; singleClickTimer.Change(DoubleClickWaitTime, Timeout.Infinite); isLeftClickCommandInvoked = true; } else { // show context menu immediately ShowContextMenu(cursorPosition); } } // make sure the left click command is invoked on mouse clicks if (me == MouseEvent.IconLeftMouseUp && !isLeftClickCommandInvoked) { // show context menu once we are sure it's not a double click singleClickTimerAction = () => { LeftClickCommand.ExecuteIfEnabled(LeftClickCommandParameter, LeftClickCommandTarget ?? this); }; singleClickTimer.Change(DoubleClickWaitTime, Timeout.Infinite); } }
/// <summary> /// Displays the <see cref="TrayPopup"/> control if it was set. /// </summary> private void ShowTrayPopup(Point cursorPosition) { if (IsDisposed) { return; } // raise preview event no matter whether popup is currently set // or not (enables client to set it on demand) var args = RaisePreviewTrayPopupOpenEvent(); if (args.Handled) { return; } if (TrayPopup == null) { return; } // use absolute position, but place the popup centered above the icon TrayPopupResolved.Placement = PlacementMode.AbsolutePoint; TrayPopupResolved.HorizontalOffset = cursorPosition.X; TrayPopupResolved.VerticalOffset = cursorPosition.Y; // open popup TrayPopupResolved.IsOpen = true; IntPtr handle = IntPtr.Zero; if (TrayPopupResolved.Child != null) { // try to get a handle on the popup itself (via its child) HwndSource source = (HwndSource)PresentationSource.FromVisual(TrayPopupResolved.Child); if (source != null) { handle = source.Handle; } } // if we don't have a handle for the popup, fall back to the message sink if (handle == IntPtr.Zero) { handle = messageSink.MessageWindowHandle; } // activate either popup or message sink to track deactivation. // otherwise, the popup does not close if the user clicks somewhere else WinApi.SetForegroundWindow(handle); // raise attached event - item should never be null unless developers // changed the CustomPopup directly... if (TrayPopup != null) { RaisePopupOpenedEvent(TrayPopup); } // bubble routed event RaiseTrayPopupOpenEvent(); }
/// <summary> /// Shows a custom control as a tooltip in the tray location. /// </summary> /// <param name="balloon"></param> /// <param name="animation">An optional animation for the popup.</param> /// <param name="timeout">The time after which the popup is being closed. /// Submit null in order to keep the balloon open indefinitely /// </param> /// <exception cref="ArgumentNullException">If <paramref name="balloon"/> /// is a null reference.</exception> public void ShowCustomBalloon(UIElement balloon, PopupAnimation animation, int?timeout) { var dispatcher = this.GetDispatcher(); if (!dispatcher.CheckAccess()) { var action = new Action(() => ShowCustomBalloon(balloon, animation, timeout)); dispatcher.Invoke(DispatcherPriority.Normal, action); return; } if (balloon == null) { throw new ArgumentNullException(nameof(balloon)); } if (timeout.HasValue && timeout < 500) { string msg = "Invalid timeout of {0} milliseconds. Timeout must be at least 500 ms"; msg = string.Format(msg, timeout); throw new ArgumentOutOfRangeException(nameof(timeout), msg); } EnsureNotDisposed(); // make sure we don't have an open balloon lock (lockObject) { CloseBalloon(); } // create an invisible popup that hosts the UIElement Popup popup = new Popup { AllowsTransparency = true }; // provide the popup with the taskbar icon's data context UpdateDataContext(popup, null, DataContext); // don't animate by default - developers can use attached events or override popup.PopupAnimation = animation; // in case the balloon is cleaned up through routed events, the // control didn't remove the balloon from its parent popup when // if was closed the last time - just make sure it doesn't have // a parent that is a popup var parent = LogicalTreeHelper.GetParent(balloon) as Popup; if (parent != null) { parent.Child = null; } if (parent != null) { string msg = "Cannot display control [{0}] in a new balloon popup - that control already has a parent. You may consider creating new balloons every time you want to show one."; msg = string.Format(msg, balloon); throw new InvalidOperationException(msg); } popup.Child = balloon; //don't set the PlacementTarget as it causes the popup to become hidden if the //TaskbarIcon's parent is hidden, too... //popup.PlacementTarget = this; popup.Placement = PlacementMode.AbsolutePoint; popup.StaysOpen = true; Point position = CustomPopupPosition != null?CustomPopupPosition() : GetPopupTrayPosition(); popup.HorizontalOffset = position.X - 1; popup.VerticalOffset = position.Y - 1; //store reference lock (lockObject) { SetCustomBalloon(popup); } // assign this instance as an attached property SetParentTaskbarIcon(balloon, this); // fire attached event RaiseBalloonShowingEvent(balloon, this); // display item popup.IsOpen = true; if (timeout.HasValue) { // register timer to close the popup balloonCloseTimer.Change(timeout.Value, Timeout.Infinite); } }