У меня есть приложение, которое находится в системном трее как traicicon. Я зарегистрировал горячую клавишу, которая при нажатии фиксирует текущий выделенный текст в любом приложении, даже в веб-браузерах.
Мой подход — отправить комбинацию клавиш {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 () продолжает возвращать Ложь на некоторое время. Это происходит, когда я пытаюсь скопировать из Google Chrome специально в редактируемые текстовые поля.
Я пробовал много разных вещей, включая использование .Net Framework Clipboard Class. Я прочитал, что проблема может заключаться в том, что поток, который выполнял команды, не был установлен как 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
Я также искал другие похожие вопросы без окончательного решения моей проблемы:
Отправить Ctrl + C в предыдущее активное окно
Как получить выделенный текст из сфокусированного окна, используя собственный Win32 API?
В этом случае VB.Net фактически предоставляет метод для решения вашей проблемы. Это называется SendKeys.Send(<key>)
Вы можете использовать это с аргументом SendKeys.Send("^(c)")
, Это отправить Ctrl + C на компьютер, в соответствии с эта msdn-статья
Положите AutoHotkey обратно в шкаф и откажитесь от ваших потребностей IsClipboardFormatAvailable
,
Используйте глобальный хук клавиатуры, сделанный Microsoft: Функция RegisterHotKey работает очень хорошо,
единственное предостережение для вас — то, что он не будет работать с F6 сам по себе, вам нужны Alt +, Ctrl + или Shift +.
Загрузите пример приложения winform и убедитесь сами:
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
Если вышеперечисленные ссылки сгнили, я включил исходный код C # в этот ответ.
Монитор, который был последним активным окном
(Необязательно) Сохраните текущее состояние буфера обмена (чтобы вы могли восстановить его после)
Установите SetForegroundWindow () для дескриптора последнего активного окна
SendKeys.Send("^c");
(Необязательно) Сброс значения буфера обмена, сохраненного в 2
Вот как я изменил проекты Microsoft Sample, заменив конструктор 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";
}
В mainform измените событие 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
}
- Если ваше приложение предназначено для международного использования с различными клавиатурами, использование SendKeys.Send может привести к непредсказуемым результатам, и его следует избегать. Ref: Имитация ввода с клавиатуры <- Метод НЕ РАБОТАЕТ !!!
Обнаружить изменение активного окна с помощью C # без опроса
Сбой симуляции CTRL + C с помощью Sendkeys
Можно ли отправить сообщение WM_COPY, которое копирует текст где-то, кроме буфера обмена?
Глобальный выпуск горячих клавиш (keyup)? (WIN32 API)
C # с помощью функции Sendkey для отправки ключа в другое приложение
Как выполнить событие .Onkey в надстройке Excel, созданной в Visual Studio 2010?
Как получить выделенный текст любого приложения в приложении Windows Form
Событие буфера обмена C #
Как я могу отслеживать изменения содержимого буфера обмена в C #?