SendKeys Ctrl + C to external applications (text into Clipboard)

六月ゝ 毕业季﹏ 提交于 2020-01-01 09:36:28

问题


I have an app that sits as a trayicon in the system tray. I have registered a Hotkey that when pressed will capture the current text selection in any application, even in Web Browsers.

My aproach is to send the key combination {Ctlr + C} to copy the text. Then access the Clipboard and use the text in my own application.

I am programming in VB.NET but any help in C# or even C++ with Win32_Api would be highly appreciated.

I use AutoHotkey and there, I have a script which access the Clipboard text and works fine.

Pause::
clipboard =  ; Start off empty to allow ClipWait to detect when the text has arrived.
Send ^c
ClipWait, 2  ; Wait for the clipboard to contain text.
if ErrorLevel
{
    ;Do nothing after 2 seconds timeout
    return
}
Run https://translate.google.com/#auto/es/%clipboard%
return

As AutoHotkey is open source, I downloaded the code and try to replicate the behaviour of ClipWait as much as I could.

My code works most of the time but sometimes there is an important delay. I cannot access the Clipboard and the win32 function IsClipboardFormatAvailable() keeps returning False for a While. This happens when I am trying to copy from Google Chrome specially in editable TextBoxes.

I tried a lot of different things including using the .Net Framework Clipboard Class. I read the problem could be that the thread that was running the commands was not set as STA, so I did it. In my desperation I also put a timer but nothing solves the problem completely.

I read as well the option of putting a hook to monitor the Clipboard, but I would like to avoid this unless it is the only way of doing it.

Here is my VB.NET code:

Imports System.Runtime.InteropServices
Imports System.Text
Imports System.Threading
Imports Hotkeys
Public Class Form1
    Public m_HotKey As Keys = Keys.F6

    Private Sub RegisterHotkeys()
        Try
            Dim alreaydRegistered As Boolean = False
            ' set the hotkey:
            ''---------------------------------------------------
            ' add an event handler for hot key pressed (or could just use Handles)
            AddHandler CRegisterHotKey.HotKeyPressed, AddressOf hotKey_Pressed
            Dim hkGetText As HotKey = New HotKey("hkGetText",
                            HotKey.GetKeySinModificadores(m_HotKey),
                            HotKey.FormatModificadores(m_HotKey.ToString),
                            "hkGetText")
            Try
                CRegisterHotKey.HotKeys.Add(hkGetText)
            Catch ex As HotKeyAddException
                alreaydRegistered = True
            End Try
        Catch ex As Exception
            CLogFile.addError(ex)
        End Try
    End Sub

    Private Sub hotKey_Pressed(sender As Object, e As HotKeyPressedEventArgs)
        Try
            Timer1.Start()
        Catch ex As Exception
            CLogFile.addError(ex)
        End Try
    End Sub

    Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
        RegisterHotkeys()
    End Sub

    Function copyText() As String
        Dim result As String = String.Empty
        Clipboard.Clear()
        Console.WriteLine("Control + C")
        SendKeys.SendWait("^c")
        Dim Attempts As Integer = 100
        Do While Attempts > 0
            Try
                result = GetText()
                If result = String.Empty Then
                    Attempts -= 1
                    'Console.WriteLine("Attempts {0}", Attempts)
                    Thread.Sleep(100)
                Else
                    Attempts = 0
                End If

            Catch ex As Exception
                Attempts -= 1
                Console.WriteLine("Attempts Exception {0}", Attempts)
                Console.WriteLine(ex.ToString)
                Threading.Thread.Sleep(100)
            End Try
        Loop
        Return result
    End Function

#Region "Win32"

    <DllImport("User32.dll", SetLastError:=True)>
    Private Shared Function IsClipboardFormatAvailable(format As UInteger) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function

    <DllImport("User32.dll", SetLastError:=True)>
    Private Shared Function GetClipboardData(uFormat As UInteger) As IntPtr
    End Function

    <DllImport("User32.dll", SetLastError:=True)>
    Private Shared Function OpenClipboard(hWndNewOwner As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function

    <DllImport("User32.dll", SetLastError:=True)>
    Private Shared Function CloseClipboard() As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function

    <DllImport("Kernel32.dll", SetLastError:=True)>
    Private Shared Function GlobalLock(hMem As IntPtr) As IntPtr
    End Function

    <DllImport("Kernel32.dll", SetLastError:=True)>
    Private Shared Function GlobalUnlock(hMem As IntPtr) As <MarshalAs(UnmanagedType.Bool)> Boolean
    End Function

    <DllImport("Kernel32.dll", SetLastError:=True)>
    Private Shared Function GlobalSize(hMem As IntPtr) As Integer
    End Function

    Private Const CF_UNICODETEXT As UInteger = 13UI
    Private Const CF_TEXT As UInteger = 1UI

#End Region

    Public Shared Function GetText() As String
        If Not IsClipboardFormatAvailable(CF_UNICODETEXT) AndAlso Not IsClipboardFormatAvailable(CF_TEXT) Then
            Return Nothing
        End If

        Try
            If Not OpenClipboard(IntPtr.Zero) Then
                Return Nothing
            End If

            Dim handle As IntPtr = GetClipboardData(CF_UNICODETEXT)
            If handle = IntPtr.Zero Then
                Return Nothing
            End If

            Dim pointer As IntPtr = IntPtr.Zero

            Try
                pointer = GlobalLock(handle)
                If pointer = IntPtr.Zero Then
                    Return Nothing
                End If

                Dim size As Integer = GlobalSize(handle)
                Dim buff As Byte() = New Byte(size - 1) {}

                Marshal.Copy(pointer, buff, 0, size)

                Return Encoding.Unicode.GetString(buff).TrimEnd(ControlChars.NullChar)
            Finally
                If pointer <> IntPtr.Zero Then
                    GlobalUnlock(handle)
                End If
            End Try
        Finally
            CloseClipboard()
        End Try
    End Function

    Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick
        Try
            Timer1.Stop()
            Dim ThreadA As Thread
            ThreadA = New Thread(AddressOf Me.copyTextThread)
            ThreadA.SetApartmentState(ApartmentState.STA)
            ThreadA.Start()
        Catch ex As Exception
            CLogFile.addError(ex)
        End Try
    End Sub

    Sub copyTextThread()
        Dim result As String = copyText()
        If result <> String.Empty Then
            MsgBox(result)
        End If
    End Sub
End Class

I also searched in other similar questions without a final solution to my problem:

Send Ctrl+C to previous active window

How do I get the selected text from the focused window using native Win32 API?


回答1:


In this case, VB.Net actually provides a method to solve your problem. It's called SendKeys.Send(<key>), you can use it with argument SendKeys.Send("^(c)"). This send Ctrl+C to the computer, according to this msdn-article




回答2:


Put AutoHotkey back in the closet and ditch your need of IsClipboardFormatAvailable.

Use a Global Keyboard Hook done by Microsoft: RegisterHotKey function works really well,
the only caveat for you is that it wont work with F6 by itself you need Alt +, Ctrl + or Shift +.

Download the sample winform app and see for yourself:

https://code.msdn.microsoft.com/CppRegisterHotkey-7bd897a8 C++ https://code.msdn.microsoft.com/CSRegisterHotkey-e3f5061e C# https://code.msdn.microsoft.com/VBRegisterHotkey-50af3179 VB.Net

Should the above links rot I have included the C# source code in this answer.

Strategy:

  1. Monitor which was the last Active Window

  2. (Optional) Save the current state of the clipboard (so you can restore it after)

  3. Set the SetForegroundWindow() to the handle of the last Active Window

  4. SendKeys.Send("^c");

  5. (Optional) Reset the clipboard value saved in 2

Code:

Here is how I modified the Microsoft Sample projects, replace the mainform.cs constructor with this code:

namespace CSRegisterHotkey
{
public partial class MainForm : Form
{
    [DllImport("User32.dll")]
    static extern int SetForegroundWindow(IntPtr point);

    WinEventDelegate dele = null;
    delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);

    [DllImport("user32.dll")]
    static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);

    private const uint WINEVENT_OUTOFCONTEXT = 0;
    private const uint EVENT_SYSTEM_FOREGROUND = 3;

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

    [DllImport("user32.dll")]
    static extern int GetWindowText(IntPtr hWnd, StringBuilder text, int count);

    //Another way if SendKeys doesn't work (watch out for this with newer operating systems!)
    [DllImport("user32.dll")]
    static extern void keybd_event(byte bVk, byte bScan, uint dwFlags, uint dwExtraInfo);

    //----

    HotKeyRegister hotKeyToRegister = null;

    Keys registerKey = Keys.None;

    KeyModifiers registerModifiers = KeyModifiers.None;

    public MainForm()
    {
        InitializeComponent();

        dele = new WinEventDelegate(WinEventProc);
        IntPtr m_hhook = SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, dele, 0, 0, WINEVENT_OUTOFCONTEXT);
    }

    private string GetActiveWindowTitle()
    {
        const int nChars = 256;
        IntPtr handle = IntPtr.Zero;
        StringBuilder Buff = new StringBuilder(nChars);
        handle = GetForegroundWindow();

        if (GetWindowText(handle, Buff, nChars) > 0)
        {
            lastHandle = handle;
            return Buff.ToString();
        }
        return null;
    }

    public void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
    {
        txtLog.Text += GetActiveWindowTitle() + "\r\n";
    }

In the mainform change the HotKeyPressed event to this goodness:

void HotKeyPressed(object sender, EventArgs e)
{
    //if (this.WindowState == FormWindowState.Minimized)
    //{
    //    this.WindowState = FormWindowState.Normal;
    //}
    //this.Activate();

    //Here is the magic
    SendCtrlCKey(lastHandle);
}

private void SendCtrlCKey(IntPtr mainWindowHandle)
{
    SetForegroundWindow(mainWindowHandle);
    //IMPORTANT - Wait for the window to regain focus
    Thread.Sleep(300); 
    SendKeys.Send("^c");

    //Comment out the next 3 lines in Release
#if DEBUG 
    this.Activate();
    MessageBox.Show(Clipboard.GetData(DataFormats.Text).ToString());
    SetForegroundWindow(mainWindowHandle);
#endif
}

//Optional example of how to use the keybd_event encase with newer Operating System the SendKeys doesn't work
private void SendCtrlC(IntPtr hWnd)
{
    uint KEYEVENTF_KEYUP = 2;
    byte VK_CONTROL = 0x11;
    SetForegroundWindow(hWnd);
    keybd_event(VK_CONTROL, 0, 0, 0);
    keybd_event(0x43, 0, 0, 0); //Send the C key (43 is "C")
    keybd_event(0x43, 0, KEYEVENTF_KEYUP, 0);
    keybd_event(VK_CONTROL, 0, KEYEVENTF_KEYUP, 0);// 'Left Control Up
}

Warnings:

  1. If your application is intended for international use with a variety of keyboards, the use of SendKeys.Send could yield unpredictable results and should be avoided. Ref: Simulating Keyboard Input <- Method DOES NOT WORK!!!
  1. Becareful around Operating System Changes, as discussed here: https://superuser.com/questions/11308/how-can-i-determine-which-process-owns-a-hotkey-in-windows

Research:

Detect active window changed using C# without polling
Simulating CTRL+C with Sendkeys fails
Is it possible to send a WM_COPY message that copies text somewhere other than the Clipboard?
Global hotkey release (keyup)? (WIN32 API)
C# using Sendkey function to send a key to another application
How to perform .Onkey Event in an Excel Add-In created with Visual Studio 2010?
How to get selected text of any application into a windows form application
Clipboard event C#
How do I monitor clipboard content changes in C#?

Enjoy:



来源:https://stackoverflow.com/questions/35297710/sendkeys-ctrl-c-to-external-applications-text-into-clipboard

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!