How to get mouse position related to desktop in WPF?
Asked Answered
J

3

8

Problem

When you search for such question using google you get a lot of hits but all solutions assume you have at least one window.

But my question is just like I phrased it -- not assumptions at all. I can have a window, but I could have zero windows (because I didn't even show one or I just closed the last one). So in short the solution cannot rely on any widget or window -- the only thing is known, is there is a desktop (and app running, but it does not have any windows).

So the question is -- how to get the mouse position?

Background

I would like to show windows centered to mouse position. There is no such mode in WPF (there are only center to owner, or center to screen) so I have to do it manually. The missing piece is mouse position.

Edits

Thank you all, so now I have the first part of the solution -- raw position. Now there is a problem how to convert the data for WPF. I found such topic: WPF Pixels to desktop pixels but again, it assumes having some window.

Then I googled more and I found solution: http://jerryclin.wordpress.com/2007/11/13/creating-non-rectangular-windows-with-interop/

the code includes class for scaling up/down coordinates relying only on info about desktop. So joining those two pieces, I finally get the solution :-). Thanks again.

Jacobina answered 20/10, 2010 at 19:21 Comment(0)
I
21

Getting the Screen Coordinates:

[DllImport("user32.dll")]
[return: MarshalAs(UnmanagedType.Bool)]
static extern bool GetCursorPos(out POINT lpPoint);

[StructLayout(LayoutKind.Sequential)]
public struct POINT
{
    public int X;
    public int Y;

    public POINT(int x, int y)
    {
        this.X = x;
        this.Y = y;
    }
}

private void WritePoint(object sender, RoutedEventArgs e)
{
    POINT p;
    if (GetCursorPos(out p))
    {
        System.Console.WriteLine(Convert.ToString(p.X) + ";" + Convert.ToString(p.Y));
    }
}

Converting Pixels to WPF Units:

[DllImport("User32.dll")]
static extern IntPtr GetDC(IntPtr hwnd);

[DllImport("gdi32.dll")]
static extern int GetDeviceCaps(IntPtr hdc, int nIndex);

[DllImport("user32.dll")]
static extern bool ReleaseDC(IntPtr hWnd, IntPtr hDC);

private Point ConvertPixelsToUnits(int x, int y)
{
    // get the system DPI
    IntPtr dDC = GetDC(IntPtr.Zero); // Get desktop DC
    int dpi = GetDeviceCaps(dDC, 88);
    bool rv = ReleaseDC(IntPtr.Zero, dDC);

    // WPF's physical unit size is calculated by taking the 
    // "Device-Independant Unit Size" (always 1/96)
    // and scaling it by the system DPI
    double physicalUnitSize = (1d / 96d) * (double)dpi;
    Point wpfUnits = new Point(physicalUnitSize * (double)x,
        physicalUnitSize * (double)y);

    return wpfUnits;          
}

Putting both together:

private void WriteMouseCoordinatesInWPFUnits()
{
    POINT p;
    if (GetCursorPos(out p))
    {
        Point wpfPoint = ConvertPixelsToUnits(p.X, p.Y);
        System.Console.WriteLine(Convert.ToString(wpfPoint.X) + ";" + Convert.ToString(wpfPoint.Y));
    }
}
Inelegancy answered 20/10, 2010 at 20:7 Comment(3)
Thank you, but this is half of the problem -- now I have screen coordinates in pixels, not in WPF units. I cannot use PointFromScreen because for that I need some widget. So how to convert pixels to units having only desktop? Is there any method for it?Jacobina
I added code to show how the pixels can be converted to WPF units, without the need to have a Window already shown. There is also a built in WPF method (on any WPF Visual) called PointFromScreen, however, you need to have an attached visual for that to work.Inelegancy
Point wpfUnits = new Point(physicalUnitSize * (double)x, physicalUnitSize * (double)y); should be replaced to Point wpfUnits = new Point((double)x / physicalUnitSize, (double)y / physicalUnitSize); to fix screen scalingMechanistic
A
4

Two options:

Use System.Windows.Forms.Control.MousePosition, or p/invoke

[DllImport("user32.dll", CharSet=CharSet.Auto, ExactSpelling=true)]
public static extern bool GetCursorPos([In, Out] NativeMethods.POINT pt);

The first option already does the p/invoke for you. I'm not entirely sure it requires you have some UI splashed up, but I don't think so. Yes, its winforms and not wpf, but it really doesn't have anything to do with where its located at.

If you want to skip any dependencies on system.windows.forms.dll then check out more information about the second on pinvoke.net.

Autocade answered 20/10, 2010 at 19:59 Comment(1)
Thank you, I use second route because it does not require adding new references, I used the code presented by mbursill.Jacobina
K
0

I stumbled over that thread while looking for a solution for the same problem. In the meantime, I found PointToScreen, which does not require any P/Invoke. The method is available on any Visual starting .NET 3.0 (and thus UIElement, Control, etc.) and an implementation would look like this:

protected void OnMouseLeave(object Sender, MouseEventArgs e) {
    var relativePosition = e.GetPosition(this);
    var screenPosition = this.PointToScreen(relativePosition);
}
Kassi answered 3/7, 2016 at 14:53 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.