I implemented this project, which belongs to me. Well, how can I handle this, let me explain.
Two main things to consider.
- How can I get text inside any window?
- Where should I store it?
So, @jcrada's answer contains one good point, which is option 1.
The steps should be in light of the above approaches:
- Add globalmousekeyhook from Nuget.
- Register ClipboardContainsText event through Usr32.dll
- Register correct mouse events
- And start listening
First, create a Win32 helper class containing the clipboard event.
/// <summary> /// This static class holds the Win32 function declarations and constants needed by /// this sample application. /// </summary> internal static class Win32 { /// <summary> /// The WM_DRAWCLIPBOARD message notifies a clipboard viewer window that /// the content of the clipboard has changed. /// </summary> internal const int WmDrawclipboard = 0x0308; /// <summary> /// A clipboard viewer window receives the WM_CHANGECBCHAIN message when /// another window is removing itself from the clipboard viewer chain. /// </summary> internal const int WmChangecbchain = 0x030D; [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] internal static extern IntPtr SetClipboardViewer(IntPtr hWndNewViewer); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] internal static extern bool ChangeClipboardChain(IntPtr hWndRemove, IntPtr hWndNewNext); [DllImport("user32.dll", CharSet = CharSet.Auto, SetLastError = true)] internal static extern IntPtr SendMessage(IntPtr hWnd, int msg, IntPtr wParam, IntPtr lParam); }
Directly register mouse and clipboard events,
public void Initialize() { var wih = new WindowInteropHelper(this.mainWindow); this.hWndSource = HwndSource.FromHwnd(wih.Handle); this.globalMouseHook = Hook.GlobalEvents(); this.mainWindow.CancellationTokenSource = new CancellationTokenSource(); var source = this.hWndSource; if (source != null) { source.AddHook(this.WinProc); // start processing window messages this.hWndNextViewer = Win32.SetClipboardViewer(source.Handle); // set this window as a viewer } this.SubscribeLocalevents(); this.growlNotifications.Top = SystemParameters.WorkArea.Top + this.startupConfiguration.TopOffset; this.growlNotifications.Left = SystemParameters.WorkArea.Left + SystemParameters.WorkArea.Width - this.startupConfiguration.LeftOffset; this.IsInitialized = true; }
Mouse events;
private void SubscribeLocalevents() { this.globalMouseHook.MouseDoubleClick += async (o, args) => await this.MouseDoubleClicked(o, args); this.globalMouseHook.MouseDown += async (o, args) => await this.MouseDown(o, args); this.globalMouseHook.MouseUp += async (o, args) => await this.MouseUp(o, args); } private async Task MouseUp(object sender, MouseEventArgs e) { this.mouseSecondPoint = e.Location; if (this.isMouseDown && !this.mouseSecondPoint.Equals(this.mouseFirstPoint)) { await Task.Run(() => { if (this.mainWindow.CancellationTokenSource.Token.IsCancellationRequested) return; SendKeys.SendWait("^c"); }); this.isMouseDown = false; } this.isMouseDown = false; } private async Task MouseDown(object sender, MouseEventArgs e) { await Task.Run(() => { if (this.mainWindow.CancellationTokenSource.Token.IsCancellationRequested) return; this.mouseFirstPoint = e.Location; this.isMouseDown = true; }); } private async Task MouseDoubleClicked(object sender, MouseEventArgs e) { this.isMouseDown = false; await Task.Run(() => { if (this.mainWindow.CancellationTokenSource.Token.IsCancellationRequested) return; SendKeys.SendWait("^c"); }); }
And the last part: what will we do when we catch
private IntPtr WinProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled) { switch (msg) { case Win32.WmChangecbchain: if (wParam == this.hWndNextViewer) this.hWndNextViewer = lParam; //clipboard viewer chain changed, need to fix it. else if (this.hWndNextViewer != IntPtr.Zero) Win32.SendMessage(this.hWndNextViewer, msg, wParam, lParam); //pass the message to the next viewer. break; case Win32.WmDrawclipboard: Win32.SendMessage(this.hWndNextViewer, msg, wParam, lParam); //pass the message to the next viewer //clipboard content changed if (Clipboard.ContainsText() && !string.IsNullOrEmpty(Clipboard.GetText().Trim())) { Application.Current.Dispatcher.Invoke( DispatcherPriority.Background, (Action) delegate { var currentText = Clipboard.GetText().RemoveSpecialCharacters(); if (!string.IsNullOrEmpty(currentText)) { //In this section, we are doing something, because TEXT IS CAPTURED. Task.Run( async () => { if (this.mainWindow.CancellationTokenSource.Token.IsCancellationRequested) return; await this.WhenClipboardContainsTextEventHandler.InvokeSafelyAsync(this, new WhenClipboardContainsTextEventArgs { CurrentString = currentText }); }); } }); } break; } return IntPtr.Zero; }
The trick sends a copy command to a window or operating system, on the other hand, a Control + C command, so SendKeys.SendWait("^c"); doing this.