【问题标题】:SendKeys Ctrl + C to external applications (text into Clipboard)SendKeys Ctrl + C 到外部应用程序(文本到剪贴板)
【发布时间】:2016-02-24 13:02:57
【问题描述】:

我有一个应用程序,它作为托盘图标位于系统托盘中。我已经注册了一个热键,当按下它时,它将捕获任何应用程序中的当前文本选择,即使在 Web 浏览器中也是如此。

我的方法是发送组合键 {Ctlr + C} 来复制文本。然后访问剪贴板并在我自己的应用程序中使用文本。

我正在使用 VB.NET 进行编程,但对于 C# 甚至 C++ 与 Win32_Api 的任何帮助将不胜感激。

我使用 AutoHotkey,我有一个脚本可以访问剪贴板文本并且工作正常。

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

由于 AutoHotkey 是开源的,我下载了代码并尝试尽可能多地复制 ClipWait 的行为。

我的代码大部分时间都能正常工作,但有时会出现严重的延迟。我无法访问剪贴板,win32 函数 IsClipboardFormatAvailable() 在一段时间内一直返回 False。当我试图在可编辑的文本框中从谷歌浏览器复制时,就会发生这种情况。

我尝试了很多不同的方法,包括使用 .Net Framework 剪贴板类。我读到的问题可能是运行命令的线程未设置为 STA,所以我这样做了。无奈之下,我也放了一个计时器,但没有什么能完全解决问题。

我也阅读了使用挂钩来监视剪贴板的选项,但我想避免这种情况,除非这是唯一的方法。

这是我的 VB.NET 代码:

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

我还搜索了其他类似的问题,但没有最终解决我的问题:

Send Ctrl+C to previous active window

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

【问题讨论】:

  • 为什么不使用自动化?
  • 什么意思?请你说得具体一点。
  • 我的意思是 UI 自动化。您的方法会破坏剪贴板。
  • 我知道我的方法目前会破坏剪贴板内容,但如果我成功了,我可以先保存剪贴板内容,然后在完成后加载旧内容。
  • 哪种语言?你忘记了 Java。

标签: c# c++ vb.net clipboard sendkeys


【解决方案1】:

在这种情况下,VB.Net 实际上提供了一种方法来解决您的问题。它被称为SendKeys.Send(&lt;key&gt;),您可以将它与参数SendKeys.Send("^(c)") 一起使用。这个发送Ctrl+C到电脑,根据this msdn-article

【讨论】:

    【解决方案2】:

    把 AutoHotkey 放回壁橱,放弃你对 IsClipboardFormatAvailable 的需要。

    使用 Microsoft 完成的全局键盘挂钩:RegisterHotKey function 效果非常好,
    唯一需要注意的是,它不能单独与 F6 一起使用,您需要 Alt + , Ctrl + 或 Shift +.

    下载示例 winform 应用程序并亲自查看:

    https://code.msdn.microsoft.com/CppRegisterHotkey-7bd897a8C++https://code.msdn.microsoft.com/CSRegisterHotkey-e3f5061eC#https://code.msdn.microsoft.com/VBRegisterHotkey-50af3179VB.Net

    如果上述链接失效,我已将 C# 源代码包含在 this answer 中。

    策略:

    1. 监视器是最后一个活动窗口

    2. (可选)保存剪贴板的当前状态(以便以后恢复)

    3. 将 SetForegroundWindow() 设置为最后一个活动窗口的句柄

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

    5. (可选)重置2中保存的剪贴板值

    代码:

    这是我修改 Microsoft 示例项目的方式,将 mainform.cs 构造函数替换为以下代码:

    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";
        }
    

    在主窗体中将 HotKeyPressed 事件更改为这样的好处:

    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
    }
    

    警告:

    1. 如果您的应用程序旨在通过各种键盘在国际上使用,则使用 SendKeys.Send 可能会产生不可预知的结果,应避免使用。参考:Simulating Keyboard Input
    1. 小心操作系统更改,如下所述:https://superuser.com/questions/11308/how-can-i-determine-which-process-owns-a-hotkey-in-windows

    研究:

    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#?

    享受:

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-12-02
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-04-17
      • 1970-01-01
      相关资源
      最近更新 更多