1

I've managed to add a button to another application, in this case its notepad for testing purposes.

var w1 = NativeMethods.GetShellWindow();

        if (w1 != IntPtr.Zero)
        {
            var hWndNotepad = TopLevelWindowUtils.FindWindow(wh => wh.GetWindowText().Contains("Notepad"));


            IntPtr hWndEdit = NativeMethods.FindWindowEx(hWndNotepad.RawPtr, IntPtr.Zero, "Edit", null);


            var button = new Button { Text = "Click Me!", Left = 5, Top = 5, Width = 75, Height = 75 };
            button.Click += (o, args) => { MessageBox.Show("You've clicked me"); };

            NativeMethods.SetParent(button.Handle, hWndEdit);
            btHandle = button.Handle;
            btns.Add(button);

            Timer timer = new Timer();
            timer.Interval = (1 * 1000); // 10 secs
            timer.Tick += Timer_Tick;
            timer.Start();

        }

This fires fine and I see the button in notepad:

Button in notepad which is clicakble

However when I lose focus of the window, minimize etc, the button disappears. How do I keep it there?

I've tried to SendMessage on a timer to repaint, but it doesnt work:

private void Timer_Tick(object sender, EventArgs e)
    {
        var w1 = NativeMethods.GetShellWindow();

        if (w1 != IntPtr.Zero)
        {
            var hWndNotepad = TopLevelWindowUtils.FindWindow(wh => wh.GetWindowText().Contains("Notepad"));
            IntPtr hWndEdit = NativeMethods.FindWindowEx(hWndNotepad.RawPtr, IntPtr.Zero, "Edit", null);

            NativeMethods.SendMessage(btHandle, NativeMethods.WM_PAINT, IntPtr.Zero, IntPtr.Zero);

        }
    }

Can you help?

PS. im trying to run this as a windows service so that when the service is running, it always has the button in the other application - in this case notepad.

user1112324
  • 623
  • 1
  • 7
  • 23
  • 2
    We can help but it's not the kind of help your looking for: This cannot be done reliably. – IInspectable Dec 12 '19 at 08:07
  • Certainly got zero chance from a service process, running isolated away in session 0. But even a process on the user's desktop isn't going to be able to achieve this reliably. Whatever your problem is, this is not the solution. – David Heffernan Dec 12 '19 at 08:54
  • Button should not be created in an edit box, even if you own the process. The edit box wants to draw in that area. I don't know what is your goal, maybe you want to write your own simple Notepad program from scratch, with a toolbar button, there must be sample .net examples available. – Barmak Shemirani Dec 12 '19 at 16:19
  • @BarmakShemirani this was just testing the solution – user1112324 Dec 12 '19 at 21:52
  • You can have a look at this example https://stackoverflow.com/a/36455629/4603670 , where it adds a button (not a standard button class) to the title bar. Although personally I wouldn't try that code, it will have too many problems and it can cause other processes to crash. I would suggest finding a more standard solution, for example using a tray icon etc. – Barmak Shemirani Dec 13 '19 at 02:22

1 Answers1

-1

So I think I did it:

private void Timer_Tick(object sender, EventArgs e)
    {
        var w1 = NativeMethods.GetShellWindow();

        if (w1 != IntPtr.Zero)
        {
            var hWndNotepad = TopLevelWindowUtils.FindWindow(wh => wh.GetWindowText().Contains("Notepad"));
            IntPtr hWndEdit = NativeMethods.FindWindowEx(hWndNotepad.RawPtr, IntPtr.Zero, "Edit", null);

            NativeMethods.SendMessage(btHandle, NativeMethods.WM_SHOWWINDOW, IntPtr.Zero, IntPtr.Zero);
            NativeMethods.SendMessage(btHandle, NativeMethods.WM_SETFOCUS, IntPtr.Zero, IntPtr.Zero);

        }
    }

I experimented with different messages - I run this timer on a 100ms loop.

As Im not an expert, what are the downsides?

user1112324
  • 623
  • 1
  • 7
  • 23
  • 1
    https://devblogs.microsoft.com/oldnewthing/20130412-00/?p=4683 – David Heffernan Dec 12 '19 at 09:57
  • 2
    Too many downsides to list them. An *unsolvable* downside is: Your button is eventually going to have to be useful, by allowing the user to trigger an event in the application. That event is usually delivered in the shape of a `WM_COMMAND` message, using the control ID to identify the action. There is no way for you to find a *unique* control ID that will not clash with your target applications'. – IInspectable Dec 12 '19 at 10:52
  • 1
    Plus, of course, the totally obvious downside: Constantly setting the focus to a control on a 100ms timer completely bricks your keyboard interface, and most of the mouse interface. And that's ignoring, that you do not *ever* send a `WM_SETFOCUS` message; that message is sent by system *only*, as part of focus navigation. – IInspectable Dec 12 '19 at 11:03
  • @IInspectable - What happens if my button click simply opened an internet explorer browser with a particular url? – user1112324 Dec 12 '19 at 21:54
  • It makes no difference, *what* you are trying to make happen. What matters is, *how* you make it happen. And that problem has no reliable solution. – IInspectable Dec 12 '19 at 23:25