/// <summary> /// Recalculates OS coordinates in order to support WPFs coordinate /// system if OS scaling (DPIs) is not 100%. /// </summary> /// <param name="point"></param> /// <returns></returns> private Point GetDeviceCoordinates(Point point) { if (double.IsNaN(this._scalingFactor)) { //calculate scaling factor in order to support non-standard DPIs var presentationSource = PresentationSource.FromVisual(this); if (presentationSource == null) { this._scalingFactor = 1; } else { if (presentationSource.CompositionTarget != null) { var transform = presentationSource.CompositionTarget.TransformToDevice; this._scalingFactor = 1 / transform.M11; } } } //on standard DPI settings, just return the point return(Math.Abs(this._scalingFactor - 1.0) < double.Epsilon ? point : new Point { X = (int)(point.X * this._scalingFactor), Y = (int)(point.Y * this._scalingFactor) }); }
/// <summary> /// Recalculates OS coordinates in order to support WPFs coordinate /// system if OS scaling (DPIs) is not 100%. /// </summary> /// <param name="point"></param> /// <returns></returns> private Point GetDeviceCoordinates(Point point) { if (double.IsNaN(scalingFactor)) { //calculate scaling factor in order to support non-standard DPIs PresentationSource presentationSource = PresentationSource.FromVisual(this); if (presentationSource == null) { scalingFactor = 1; } else { Matrix transform = presentationSource.CompositionTarget.TransformToDevice; scalingFactor = 1 / transform.M11; } } //on standard DPI settings, just return the point if (scalingFactor == 1.0) { return(point); } return(new Point { X = (int)(point.X * scalingFactor), Y = (int)(point.Y * scalingFactor) }); }
/// <summary> /// Displays the <see cref="TrayPopup" /> control if /// it was set. /// </summary> private void ShowTrayPopup(Point cursorPosition) { if (this.IsDisposed) { return; } //raise preview event no matter whether popup is currently set //or not (enables client to set it on demand) var args = this.RaisePreviewTrayPopupOpenEvent(); if (args.Handled) { return; } if (this.TrayPopup != null) { //use absolute position, but place the popup centered above the icon this.TrayPopupResolved.Placement = PlacementMode.AbsolutePoint; this.TrayPopupResolved.HorizontalOffset = cursorPosition.X; this.TrayPopupResolved.VerticalOffset = cursorPosition.Y; //open popup this.TrayPopupResolved.IsOpen = true; IntPtr handle = IntPtr.Zero; if (this.TrayPopupResolved.Child != null) { //try to get a handle on the popup itself (via its child) var source = (HwndSource)PresentationSource.FromVisual(this.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 = this._messageSink.MessageWindowHandle; } //activate either popup or message sink to track deactivation. //otherwise, the popup does not close if the user clicks somewhere else NativeMethods.SetForegroundWindow(handle); //raise attached event - item should never be null unless developers //changed the CustomPopup directly... if (this.TrayPopup != null) { RaisePopupOpenedEvent(this.TrayPopup); } //bubble routed event this.RaiseTrayPopupOpenEvent(); } }
/// <summary> /// Displays the <see cref="ContextMenu" /> if /// it was set. /// </summary> private void ShowContextMenu(Point cursorPosition) { if (this.IsDisposed) { return; } //raise preview event no matter whether context menu is currently set //or not (enables client to set it on demand) var args = this.RaisePreviewTrayContextMenuOpenEvent(); if (args.Handled) { return; } if (this.ContextMenu != null) { //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! this.ContextMenu.Placement = PlacementMode.AbsolutePoint; this.ContextMenu.HorizontalOffset = cursorPosition.X; this.ContextMenu.VerticalOffset = cursorPosition.Y; this.ContextMenu.IsOpen = true; IntPtr handle = IntPtr.Zero; //try to get a handle on the context itself var source = (HwndSource)PresentationSource.FromVisual(this.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 = this._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 NativeMethods.SetForegroundWindow(handle); //bubble event this.RaiseTrayContextMenuOpenEvent(); } }
private void OnMouseEvent(MouseEvent me) { if (this.IsDisposed) { return; } switch (me) { case MouseEvent.MouseMove: this.RaiseTrayMouseMoveEvent(); //immediately return - there's nothing left to evaluate return; case MouseEvent.IconRightMouseDown: this.RaiseTrayRightMouseDownEvent(); break; case MouseEvent.IconLeftMouseDown: this.RaiseTrayLeftMouseDownEvent(); break; case MouseEvent.IconRightMouseUp: this.RaiseTrayRightMouseUpEvent(); break; case MouseEvent.IconLeftMouseUp: this.RaiseTrayLeftMouseUpEvent(); break; case MouseEvent.IconMiddleMouseDown: this.RaiseTrayMiddleMouseDownEvent(); break; case MouseEvent.IconMiddleMouseUp: this.RaiseTrayMiddleMouseUpEvent(); break; case MouseEvent.IconDoubleClick: //cancel single click timer this._singleClickTimer.Change(Timeout.Infinite, Timeout.Infinite); //bubble event this.RaiseTrayMouseDoubleClickEvent(); break; case MouseEvent.BalloonToolTipClicked: this.RaiseTrayBalloonTipClickedEvent(); break; default: throw new ArgumentOutOfRangeException("me", Paya.Automation.Editor.Resources.MissingHandlerForMouseEventFlag + me); } //get mouse coordinates var cursorPosition = new Point(); if (this._messageSink.Version == NotifyIconVersion.Vista) { //physical cursor position is supported for Vista and above NativeMethods.GetPhysicalCursorPos(ref cursorPosition); } else { NativeMethods.GetCursorPos(ref cursorPosition); } cursorPosition = this.GetDeviceCoordinates(cursorPosition); bool isLeftClickCommandInvoked = false; //show popup, if requested if (me.IsMatch(this.PopupActivation)) { if (me == MouseEvent.IconLeftMouseUp) { //show popup once we are sure it's not a double click this._singleClickTimerAction = () => { this.LeftClickCommand.ExecuteIfEnabled(this.LeftClickCommandParameter, this.LeftClickCommandTarget ?? this); this.ShowTrayPopup(cursorPosition); }; this._singleClickTimer.Change(NativeMethods.GetDoubleClickTime(), Timeout.Infinite); isLeftClickCommandInvoked = true; } else { //show popup immediately this.ShowTrayPopup(cursorPosition); } } //show context menu, if requested if (me.IsMatch(this.MenuActivation)) { if (me == MouseEvent.IconLeftMouseUp) { //show context menu once we are sure it's not a double click this._singleClickTimerAction = () => { this.LeftClickCommand.ExecuteIfEnabled(this.LeftClickCommandParameter, this.LeftClickCommandTarget ?? this); this.ShowContextMenu(cursorPosition); }; this._singleClickTimer.Change(NativeMethods.GetDoubleClickTime(), Timeout.Infinite); isLeftClickCommandInvoked = true; } else { //show context menu immediately this.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 this._singleClickTimerAction = () => this.LeftClickCommand.ExecuteIfEnabled(this.LeftClickCommandParameter, this.LeftClickCommandTarget ?? this); this._singleClickTimer.Change(NativeMethods.GetDoubleClickTime(), Timeout.Infinite); } }
/// <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 inde /// </param> /// <exception cref="ArgumentNullException"> /// If <paramref name="balloon" /> /// is a null reference. /// </exception> public void ShowCustomBalloon(UIElement balloon, PopupAnimation animation, int?timeout) { Dispatcher dispatcher = this.GetDispatcher(); if (!dispatcher.CheckAccess()) { var action = new Action(() => this.ShowCustomBalloon(balloon, animation, timeout)); dispatcher.Invoke(DispatcherPriority.Normal, action); return; } if (balloon == null) { throw new ArgumentNullException("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("timeout", msg); } this.EnsureNotDisposed(); //make sure we don't have an open balloon lock (this) { this.CloseBalloon(); } //create an invisible popup that hosts the UIElement var popup = new Popup { AllowsTransparency = true }; //provide the popup with the taskbar icon's data context this.UpdateDataContext(popup, null, this.DataContext); //don't animate by default - devs 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 = TrayInfo.GetTrayLocation(); position = this.GetDeviceCoordinates(position); popup.HorizontalOffset = position.X - 1; popup.VerticalOffset = position.Y - 1; //store reference lock (this) { this.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 this._balloonCloseTimer.Change(timeout.Value, Timeout.Infinite); } }