c # — SendKeys Ctrl + C для внешних приложений (текст в буфер обмена)

У меня есть приложение, которое находится в системном трее как 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?

7

Решение

В этом случае VB.Net фактически предоставляет метод для решения вашей проблемы. Это называется SendKeys.Send(<key>)Вы можете использовать это с аргументом SendKeys.Send("^(c)"), Это отправить Ctrl + C на компьютер, в соответствии с эта msdn-статья

1

Другие решения

Положите 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 # в этот ответ.

  1. Монитор, который был последним активным окном

  2. (Необязательно) Сохраните текущее состояние буфера обмена (чтобы вы могли восстановить его после)

  3. Установите SetForegroundWindow () для дескриптора последнего активного окна

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

  5. (Необязательно) Сброс значения буфера обмена, сохраненного в 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
}
  1. Если ваше приложение предназначено для международного использования с различными клавиатурами, использование SendKeys.Send может привести к непредсказуемым результатам, и его следует избегать. Ref: Имитация ввода с клавиатуры <- Метод НЕ РАБОТАЕТ !!!
  1. Будьте внимательны к изменениям операционной системы, как обсуждено здесь: https://superuser.com/questions/11308/how-can-i-determine-which-process-owns-a-hotkey-in-windows

Обнаружить изменение активного окна с помощью C # без опроса
Сбой симуляции CTRL + C с помощью Sendkeys
Можно ли отправить сообщение WM_COPY, которое копирует текст где-то, кроме буфера обмена?
Глобальный выпуск горячих клавиш (keyup)? (WIN32 API)
C # с помощью функции Sendkey для отправки ключа в другое приложение
Как выполнить событие .Onkey в надстройке Excel, созданной в Visual Studio 2010?
Как получить выделенный текст любого приложения в приложении Windows Form
Событие буфера обмена C #
Как я могу отслеживать изменения содержимого буфера обмена в C #?

введите описание изображения здесь

0

По вопросам рекламы [email protected]