0

I have a winform application (.NET C#). From this application I have started another process (notepad.exe) and docked it to my window - similar to how it was done here: Docking Window inside another Window.

Now my question is, how can I catch/handle mouse events made on this docked application? What I've tried:

  • creating a transparent panel over the docking panel. The issue arose when I couldn't click (or do anything else) "through" the invisible panel
  • global mouse hook. I didn't like this solution because I'm only interested in the mouse position within my form. Plus, I need the mouse position relative to the window.

For context, what I'm trying to achieve is to have a constant tooltip next to my mouse informing me of the mouse position relative to the panel. See the code bellow:

ToolTip trackTip;
public Form1
{
  trackTip = new ToolTip();
  transparentPanel1.MouseMove += new MouseEventHandler((object s, System.Windows.Forms.MouseEventArgs e) => trackTip.Hide(this));
  transparentPanel1.MouseLeave += new EventHandler(TransparentPanel1_MouseLeave);
}


void TransparentPanel1_MouseMove(object sender, System.Windows.Forms.MouseEventArgs e)
{
  String tipText = String.Format("({0}, {1})", e.X, e.Y);
  trackTip.Show(tipText, this, e.Location);
}

I've found a viable solution, however I would very much like to avoid injecting code into the process and I feel as though there must be a better solution to my specific problem.

I'd appreciate any pointers you could give me. I'm new to .NET programming.

Naltamer14
  • 187
  • 3
  • 10
  • Why are you doing this? seems like a lot of work to achieve nothing really substantial. Notepad is a fairly pedestrian app – TheGeneral Sep 04 '20 at 22:11
  • @MichaelRandall I'm using notepad simply to get up and running. It doesn't matter really what .exe is run at my stage of development. – Naltamer14 Sep 04 '20 at 22:46
  • I think you'll need to use global mouse hooks as you have already suggested. The reason I say this is because the windows APIs probably don't support the level of detail you are trying to get. e.g. advertising the mouse position relative to another window handle. You could combine the global mouse position with the window position of the external application and some basic math will give you a relative co-ordinate. Even if there is a windows API that supports it. you might find it only works when debugger is attached or when your application was the last active application. – Joe_DM Sep 05 '20 at 03:16

1 Answers1

1

For reference, I'm combining two answers together to one here:

Getting mouse position in c#

How to get window's position?

Using the global position of the mouse and the position of the window we can calculate the relative position of the mouse. Heres some sample code with really basic boundary handling and no real error handling for if the handle gets closed, etc. But should get you started.

First, basically a copy of the above answers bringing in windows APIs to get the window and mouse positions:

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

        public static implicit operator Point(POINT point)
        {
            return new Point(point.X, point.Y);
        }
    }

    [DllImport("user32.dll")]
    public static extern bool GetCursorPos(out POINT lpPoint);

    public static Point GetCursorPosition()
    {
        POINT lpPoint;
        GetCursorPos(out lpPoint);

        return lpPoint;
    }

    [DllImport("user32.dll", CharSet = CharSet.Auto)]
    public static extern IntPtr FindWindow(string strClassName, string strWindowName);

    [DllImport("user32.dll")]
    public static extern bool GetWindowRect(IntPtr hwnd, ref Rect rectangle);

    public struct Rect
    {
        public int Left { get; set; }
        public int Top { get; set; }
        public int Right { get; set; }
        public int Bottom { get; set; }
    }

Then a bit of math between the two:

    public Point GetRelativeMousePosition(IntPtr windowPtr)
    {
        Rect windowPostion = new Rect();
        GetWindowRect(windowPtr, ref windowPostion);
        var mousePosition = GetCursorPosition();

        var result = new Point();
        result.X = mousePosition.X - windowPostion.Left;
        result.Y = mousePosition.Y - windowPostion.Top;

        // set some boundaries so we can't go outside.
        if (result.X < 0)
            result.X = 0;

        var maxWidth = windowPostion.Right - windowPostion.Left;
        if (result.X > maxWidth)
            result.X = maxWidth;

        if (result.Y < 0)
            result.Y = 0;

        var maxHeight = windowPostion.Bottom - windowPostion.Top;
        if (result.Y > maxHeight)
            result.Y = maxHeight;

        return result;
    }

and then putting it all together:

    private void Sample()
    {
        Process[] processes = Process.GetProcessesByName("notepad");
        Process lol = processes[0];
        var ptr = lol.MainWindowHandle; // getting this reference is expensive. Better to store and reuse if possible.
        var relativePoint = GetRelativeMousePosition(ptr);
        Console.WriteLine($"relative mouse x:{relativePoint.X} y:{relativePoint.Y}");
    }
Joe_DM
  • 985
  • 1
  • 5
  • 12
  • I didn't want to over complicate it. But some things to consider is, what if notepad isnt open. What if the window is minimised. If X is outside of the boundary, should Y also show as out of boundary? And visa-versa for Y. Or should the boundaries default to -1 or something to indicate that the cursor is not inside of the window. This is all the kind of stuff that can be tweaked depending on how you need to use it. – Joe_DM Sep 05 '20 at 03:51
  • Thanks for the detailed feedback! Like I said, I don't prefer this solution; it may also get flagged by antiviruses, so in general I would like to stay away from global mouse hooks. I feel as though there has to be a way to do this. I think my idea of creating a transparent panel could work, I just haven't been able to get past the events issue. Anyhow, I might end up using your solution if I can't get it working. @Joe_DM – Naltamer14 Sep 05 '20 at 10:25
  • 1
    Overlaying other applications over the main application is likely going to have other issues. Such as the overlay taking the input which then needs to be replicated into the application underneath it. Windows APIs are notoriously difficult to do stuff like this as there is strict rules around what is and isn't allowed to send keys into another application. If you find a good answer then I'm always keen to see this kind of stuff in action. – Joe_DM Sep 05 '20 at 10:36
  • I've used your solution using a global mouse hook. Now I have just one more issue. Whenever I leave focus from my main window (I set focus to notepad for example), the tooltip disappears. I can still print the coordinates to the console (so the hook is working), however my goal is to have the mouse tooltip. Do you perhaps know how I can achieve this? I tried assigning tooltip.ShowAlways to true - did not help. Might I have to assign the window parameter in the Show() function to something other than my main window? @Joe_DM – Naltamer14 Sep 05 '20 at 22:17
  • 1
    @Naltamer14 This is going to a new question but going just from memory I think you might need to import some extra windows API DLLs to try to tell windows itself that it should always be on top. maybe try out `user32.dll` method `ShowWindow` with type 8 if `IsIconic` or `SetForegroundWindow`. All of these take in a `IntPtr` as the target. You may also play around with a hack to send a key command to the `IntPtr` such as jsut pressing ctrl, as to trick windows into letting you bring the focus forward. – Joe_DM Sep 06 '20 at 03:01
  • just adding to above, also on the user32.dll. `keybd_event` can be used to simulate a keypress. – Joe_DM Sep 06 '20 at 03:04