What is the best way to show a WPF window at the mouse location (to the top left of the mouse)?

14,017

Solution 1

In the end, this did the trick:

        protected override void OnContentRendered(EventArgs e)
        {
            base.OnContentRendered(e);
            MoveBottomRightEdgeOfWindowToMousePosition();
        }

        private void MoveBottomRightEdgeOfWindowToMousePosition()
        {
            var transform = PresentationSource.FromVisual(this).CompositionTarget.TransformFromDevice;
            var mouse = transform.Transform(GetMousePosition());
            Left = mouse.X - ActualWidth;
            Top = mouse.Y - ActualHeight;
        }

        public System.Windows.Point GetMousePosition()
        {
            System.Drawing.Point point = System.Windows.Forms.Control.MousePosition;
            return new System.Windows.Point(point.X, point.Y);
        }

Solution 2

Can you not use something like this?:

Point mousePositionInApp = Mouse.GetPosition(Application.Current.MainWindow);
Point mousePositionInScreenCoordinates = 
    Application.Current.MainWindow.PointToScreen(mousePositionInApp);

I haven't been able to test it, but I think it should work.


UPDATE >>>

You don't have to use the Application.Current.MainWindow as the parameter in these methods... it should still work if you have access to a Button or another UIElement in a handler:

Point mousePositionInApp = Mouse.GetPosition(openButton);
Point mousePositionInScreenCoordinates = openButton.PointToScreen(mousePositionInApp);

Again, I haven't been able to test this, but if that fails as well, then you can find one more method in the How do I get the current mouse screen coordinates in WPF? post.

Solution 3

You can also do this by slightly modifying your initial example and positioning the window before showing it.

MyWindowObjectThatInheritsWindow window = new MyWindowObjectThatInheritsWindow();

var helper = new WindowInteropHelper(window);
var hwndSource = HwndSource.FromHwnd(helper.EnsureHandle());
var transformFromDevice = hwndSource.CompositionTarget.TransformFromDevice;

System.Windows.Point wpfMouseLocation = transformFromDevice.Transform(GetMousePositionWindowsForms());
window.Left = wpfMouseLocation.X - 300;
window.Top = wpfMouseLocation.Y - 240;
window.Show();
Share:
14,017
Alexandru
Author by

Alexandru

"To avoid criticism, say nothing, do nothing, be nothing." - Aristotle "It is wise to direct your anger towards problems - not people; to focus your energies on answers - not excuses." - William Arthur Ward "Science does not know its debt to imagination." - Ralph Waldo Emerson "Money was never a big motivation for me, except as a way to keep score. The real excitement is playing the game." - Donald Trump "All our dreams can come true, if we have the courage to pursue them." - Walt Disney "Mitch flashes back to a basketball game held in the Brandeis University gymnasium in 1979. The team is doing well and chants, 'We're number one!' Morrie stands and shouts, 'What's wrong with being number two?' The students fall silent." - Tuesdays with Morrie

Updated on June 25, 2022

Comments

  • Alexandru
    Alexandru almost 2 years

    I have found that this works PART of the time by inheriting the Windows Forms mouse point and subtracting out the height and width of my window to set the left and top (since my window's size is fixed):

    MyWindowObjectThatInheritsWindow window = new MyWindowObjectThatInheritsWindow();
    System.Windows.Point mouseLocation = GetMousePositionWindowsForms();
    window.Left = mouseLocation.X - 300;
    window.Top = mouseLocation.Y - 240;
    window.Show();
    

    Edit: Here is the code for getting the mouse position...

    public System.Windows.Point GetMousePositionWindowsForms()
    {
        System.Drawing.Point point = System.Windows.Forms.Control.MousePosition;
        return new System.Windows.Point(point.X, point.Y);
    }
    

    Note that this works by making the bottom right edge of the window touch the top left of your mouse cursor. But this breaks for different screen resolutions, or maybe multiple monitors with different resolutiosn? I haven't fully narrowed it down yet, but I just tried this same code on another PC, and it seems to spawn the window not to the top left of the mouse cursor, but to the bottom left of it, and a good distance past it...

    I should probably add that my window sizes to content, width and height, so I can't just use the ActualWidth and ActualHeight properties since they're not available. Perhaps the issue is in getting that sizing right? Is there any way to do that? I know for sure the 300 and 240 is correct according to my main PC with two monitors running 1920x1080 resolutions, as I have calculated the widths and heights of all the objects in my window which I have explicitly sized. Edit: Just tried explicitly setting the height and width to 240/300, to ensure that the window is no longer sized to content, and I still have this issue when subtracting out the actual height and width!

    Any ideas?

  • Alexandru
    Alexandru over 10 years
    I have tried this, but I get this error out of it: An unhandled exception of type 'System.InvalidOperationException' occurred in PresentationCore.dll Additional information: This Visual is not connected to a PresentationSource.
  • Sheridan
    Sheridan over 10 years
    Ahhh, sorry. In the constructor of MainWindow.xaml.cs, try adding this: Application.Current.MainWindow = this;. If that still doesn't work, try deferring the call to the above code... see this post for more information.
  • Alexandru
    Alexandru over 10 years
    Ah, actually that won't work because my MainWindow is hidden. I made a WPF application that creates a taskbar icon and creates a hidden window off the bat, so I still see this error. Actually, the code above is from MainWindow.cs so I just tried using the "this" object but, unfortunately no luck. Here is the MainWindow XAML: <Window x:Class="Executioner.MainWindow" xmlns="schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns:x="schemas.microsoft.com/winfx/2006/xaml" Visibility="Hidden"/>
  • Sheridan
    Sheridan over 10 years
    I'm not sure that I understand... what Window are you using for your application then?
  • Alexandru
    Alexandru over 10 years
    Good question. I don't use a window. I create a system tray icon in the system tray. When the user clicks it, an event handler fires and creates a window for the user interface of the application (which is actually not MainWindow).
  • Alexandru
    Alexandru over 10 years
    Here is the code I use to create a system tray icon on the MainWindow constructor (sorry, did I say taskbar before? I meant system tray): SystemTrayIcon = new System.Windows.Forms.NotifyIcon(); SystemTrayIcon.Icon = new System.Drawing.Icon(Assembly.GetExecutingAssembly().GetManif‌​estResourceStream("E‌​xecutioner.icon.ico"‌​)); SystemTrayIcon.Click += notifyIcon_Click; SystemTrayIcon.Visible = true;
  • Alexandru
    Alexandru over 10 years
    I guess this complicates things a lot...not sure why it doesn't work on my other machine with the same code though :(
  • Alexandru
    Alexandru over 10 years
    I tried to put your code edit into the constructor of the new window, but even when I try getting a UI element like a button or a textbox, I still get the same error as before about not being connected to a presentation source. Any ideas why that's happening? By the way, thanks for your help so far!
  • Sheridan
    Sheridan over 10 years
    I'm not really sure, but I think that it's because it hasn't been set up yet... like trying to access elements in a ControlTemplate before it's been applied. You might have to defer/delay running that code, eg. don't do it in the constructor. Can you do it upon item selection or Button.Click instead?
  • Alexandru
    Alexandru over 10 years
    Yes, this works. I created a thread that invokes the main dispatcher...however, still kind of puzzled because even this code doesn't exactly get the right point I want the window to be at. It aligns it to the top left of the screen. :(
  • Sheridan
    Sheridan over 10 years
    Were you using the MainWindow or another element as the parameter? The GetPosition method should get the position of the mouse relative to that specified element... if that element was not visible then I guess it might not work correctly.
  • Alexandru
    Alexandru over 10 years
    I'm trying to use it on the Loaded event of the new window that I pop open. Not MainWindow. However, mousePositionInScreenCoordinates seems to be (0, 0). You're saying this will change even later on? I can try to thread it out.
  • Alexandru
    Alexandru over 10 years
    Even if delayed the mousePositionInScreenCoordinates seems to always be (0, 0).
  • Sheridan
    Sheridan over 10 years
    No, using it in the Loaded event handler should be fine. Unfortunately, I can't really test it out, so I'm not sure what's going wrong.
  • Gordon Slysz
    Gordon Slysz almost 7 years
    Works well. I had better luck with hooking things up to the 'Loaded' event; I found there was a flashing effect when the dialog is moved from its initial rendered position, if I used the OnContentRendered