简体   繁体   中英

Send Ctrl+C to previous active window

I'm trying to write a small application that's sits in the system tray. I've registered a hotkey. When the hotkey is fired and the application is activated I want to send Ctrl+C to the last active window so I can get the highlighted text into the clipboard.

This is what I got so far:

    //http://stackoverflow.com/questions/9468476/switch-to-last-active-application-like-alt-tab

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool IsWindowVisible(IntPtr hWnd);

    [DllImport("user32.dll")]
    static extern IntPtr GetLastActivePopup(IntPtr hWnd);

    [DllImport("user32.dll", ExactSpelling = true)]
    static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool SetForegroundWindow(IntPtr hWnd);

    const uint GA_PARENT = 1;
    const uint GA_ROOT = 2;
    const uint GA_ROOTOWNER = 3;

    public static IntPtr GetPreviousWindow()
    {
        IntPtr activeAppWindow = GetForegroundWindow();
        if (activeAppWindow == IntPtr.Zero)
            return IntPtr.Zero;

        IntPtr prevAppWindow = GetLastActivePopup(activeAppWindow);
        return IsWindowVisible(prevAppWindow) ? prevAppWindow : IntPtr.Zero;
    }

    public static void FocusToPreviousWindow()
    {
        IntPtr prevWindow = GetPreviousWindow();
        if (prevWindow != IntPtr.Zero)
            SetForegroundWindow(prevWindow);
    }

    ...

    private static void OnHotKeyFired()
    {
        FocusToPreviousWindow();

        SendKeys.SendWait("^(c)");

        _viewModel.Input = Clipboard.GetText();

        new UIWindow(_viewModel).ShowDialog();
    }

But I can't get the SendKeys to work. In most apps nothing happpens, meaning ctrl-c is not fired. In Ms Word a copyright sign (c) is inserted in my document when SendWait is executed.

UPDATE:

I've tried with WM_COPY and SendMessage:

private static void OnHotKeyFired()
{
    IntPtr handle = GetPreviousWindow();
    SendMessage(handle, WM_COPY, IntPtr.Zero, IntPtr.Zero);
    _viewModel.Input = Clipboard.GetText();
    ...

And it works in Word but not in Excel, Notepad, Visual Studio

    SendKeys.SendWait("^(c)");

That does not send Ctrl+C, the parentheses are sent as well. You probably meant "^{c}" , curly braces instead of parentheses. Otherwise the reason that Word inserts the copyright symbol, it auto-corrects (c) to ©. Fix:

    SendKeys.SendWait("^c");

If you still have problems, you probably will, then read the MSDN article for SendKeys. It recommends using a .config file so it uses a different way to inject keystrokes. SendInput() works better than a journal hook on later Windows versions.

I think your issue is related to timing.

To set the windows focus and to do the actual copy to clipboard you need to wait for the window to get focus, and to wait for the clipboard to be updated.

There are a couple of ways to deal with these ugly win32 things.

For the clipboard content. I compare the original content to the current content. I set the original content to string.empty if its an image or some other none text data. I then await a function that checks the clipboard for changes.

For the SetForegroundWindow I currently add a delay in my async function. You could also probably find a win32 api call to wait for this window to properly be put in the foreground.

I do both of these in an async function and await it so as to no block.

SendKeys should work with SendWait("^c"). SendWait("^(c)") will not always work as noted in the other answers. However the ctrl+c copy to clipboard doesn't happen instantly.

Point p;
if (GetCursorPos(out p))
{
    IntPtr ptr = WindowFromPoint(p);
    if (ptr != IntPtr.Zero)
    {                    
        SetForegroundWindow(ptr);

        //wait for window to get focus quick and ugly
        // probably a cleaner way to wait for windows to send a message
        // that it has updated the foreground window
        await Task.Delay(300);

        //try to copy text in the current window
        SendKeys.Send("^c");

        await WaitForClipboardToUpdate(originalClipboardText);
   }
}

}

    private static async Task WaitForClipboardToUpdate(string originalClipboardText)
    {
        Stopwatch sw = Stopwatch.StartNew();

        while (true)
        {
            if (await DoClipboardCheck(originalClipboardText)) return;

            if (sw.ElapsedMilliseconds >= 1500) throw new Exception("TIMED OUT WAITING FOR CLIPBOARD TO UPDATE.");
        }
    }

    private static async Task<bool> DoClipboardCheck(string originalClipboardText)
    {
        await Task.Delay(10);

        if (!Clipboard.ContainsText()) return false;

        var currentText = Clipboard.GetText();

        Debug.WriteLine("current text: " + currentText + " original text: " + originalClipboardText);

        return currentText != originalClipboardText;
    }


   [DllImport("user32.dll")]
    private static extern bool GetCursorPos(out Point pt);

    [DllImport("user32.dll", EntryPoint = "WindowFromPoint", CharSet = CharSet.Auto, ExactSpelling = true)]
    private static extern IntPtr WindowFromPoint(Point pt);

    // Activate an application window.
    [DllImport("USER32.DLL")]
    public static extern bool SetForegroundWindow(IntPtr hWnd);

    [DllImportAttribute("user32.dll", EntryPoint = "GetForegroundWindow")]
    public static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll", SetLastError = true)]
    static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);

Also - A real quick and dirty way to quickly validate/check if timing is your issue is you could put a Thread.Sleep(1000); after your SetForegroundWindow, and SendKeys.SendWait calls.

I know you're programming something, but in case you do it because you didn't find an easier solution... You could try AutoHotKey , it's a small program that sits in your tray and do all kind of hotkeys/macros kind of stuff... You'll need to write a small script for your macro, something like that:

#c::Send !{ESC}, ^c

This simple script means:

  • #c:: (Define the hotkey Windows+c)
  • Send (Send some keystrokes)
  • !{ESC} (Alt+Esc which toggle to the last program)
  • ^c (Control+c)

There is all sort of fancy scripting that you can do with this software like launching programs, regexp, threading, mouse events and more...

I know it doesn't answer your question, but it's a simple alternative.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM