2016-02-09 25 views
7

Ho un'app che si trova come vassoio nella barra delle applicazioni. Ho registrato un tasto di scelta rapida che quando premuto cattura la selezione del testo corrente in qualsiasi applicazione, anche nei browser Web.SendKeys Ctrl + C per applicazioni esterne (testo negli Appunti)

Il mio approccio consiste nell'inviare la combinazione di tasti {Ctlr + C} per copiare il testo. Quindi accedi agli Appunti e utilizza il testo nella mia applicazione.

Sto programmando in VB.NET ma qualsiasi aiuto in C# o anche C++ con Win32_Api sarebbe molto apprezzato.

Uso AutoHotkey e lì, ho uno script che accede al testo degli Appunti e funziona correttamente.

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 

Come AutoHotkey è open source, ho scaricato il codice e cercare di replicare il comportamento di ClipWait per quanto ho potuto.

Il mio codice funziona la maggior parte del tempo ma a volte c'è un ritardo importante. Non riesco ad accedere agli Appunti e alla funzione win32 IsClipboardFormatAvailable() continua a restituire False per un while. Questo succede quando sto provando a copiare da Google Chrome specialmente in TextBox modificabili.

Ho provato un sacco di cose diverse incluso l'uso della classe Clipboard .Net Framework. Ho letto il problema potrebbe essere che il thread che stava eseguendo i comandi non è stato impostato come STA, quindi l'ho fatto. Nella mia disperazione metto anche un timer, ma nulla risolve completamente il problema.

Ho letto anche l'opzione di mettere un gancio per monitorare gli Appunti, ma vorrei evitarlo a meno che non sia l'unico modo per farlo.

Ecco il mio codice 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 

Ho cercato anche in altre simili domande senza una soluzione definitiva al mio problema:

Send Ctrl+C to previous active window

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

+0

perché non utilizzare l'automazione? –

+0

Cosa intendi? Potresti essere un po 'più specifico per favore. –

+0

Intendo l'automazione dell'interfaccia utente. Il tuo approccio distrugge gli appunti. –

risposta

1

In questo caso, VB.Net fornisce in realtà un metodo per risolvere il tuo problema. Si chiama SendKeys.Send(<key>), puoi usarlo con l'argomento SendKeys.Send("^(c)"). Invia Ctrl + C al computer, in base a this msdn-article

0

Metti di nuovo AutoHotkey nell'armadio e abbandona il tuo fabbisogno di IsClipboardFormatAvailable.

Utilizzare un hook di tastiera globale fatto da Microsoft: RegisterHotKey function funziona davvero bene,
l'unica avvertenza per voi è che non funzionerà con F6 per sé il necessario Alt + Ctrl + Maiusc + o.

Scarica l'app WinForm campione e vedere di persona:

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

Se i collegamenti sopra riportati marcano, ho incluso il codice sorgente C# in this answer.

Strategia:

  1. Monitor che era l'ultima finestra attiva

  2. (opzionale) Salvare lo stato attuale degli appunti (in modo da poter ripristinare dopo)

  3. Impostare il SetForegroundWindow() all'handle dell'ultima finestra attiva

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

  5. (opzionale) reimpostare il valore appunti salvati in 2

Codice:

Ecco come ho modificato i progetti di esempio di Microsoft, sostituire il costruttore mainform.cs con questo codice:

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

Nel MainForm modificare l'evento HotKeyPressed a questa bontà:

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 
} 

Avvertenze:

  1. Se l'applicazione è destinata per l'uso internazionale con una varietà di tastiere, l'uso di SendKeys.Send potrebbe produrre risultati imprevedibili e dovrebbero essere evitati. Rif: Simulating Keyboard Input < - Il metodo NON FUNZIONA !!!
  1. Becareful intorno Sistema Operativo modifiche, come discusso qui: https://superuser.com/questions/11308/how-can-i-determine-which-process-owns-a-hotkey-in-windows

di ricerca:

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

Godetevi:

enter image description here