问题
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:
Monitor which was the last Active Window
(Optional) Save the current state of the clipboard (so you can restore it after)
Set the SetForegroundWindow() to the handle of the last Active Window
SendKeys.Send("^c");
(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:
- 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!!!
- 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