如何将任何应用程序中选定的文本获取到Windows表单应用程序中

13

这是我正在尝试做的事情,

当用户双击鼠标选择任何正在运行应用程序中的单词(文本)时,特定高亮显示的单词应插入到已经运行的Windows应用程序中。

到目前为止,我已经使用了全局按键实现了逻辑,其中用户必须触发CRT+C键盘键组合将所选单词复制到win表单应用程序中。

我想知道是否有任何方法可以在没有按下键盘上的任何按钮的情况下将选定文本获取到该应用程序中?


2
有一些程序可以记录鼠标移动,但这似乎与发现任何给定应用程序中被双击的底层控件相去甚远。例如,如果您双击窗口标签、桌面图标标签或自定义应用程序,那么怎么办?老实说,似乎不太可能制作出一个能够找到所有被双击的控件的应用程序。我可能完全错了,但我有一种感觉,认为这是不可能完成的。如果可以的话,我很想知道如何做到。 - Wim Ombelets
1
但是我发现一些应用程序已经实现了这个功能。当我们点击单词时,它会在他们的应用程序中显示该单词的含义。 - Thilina H
是的,但每个能够实现这一点的应用程序都有处理捕获和处理这些点击事件的代码,以获取你所说的这种“字典”行为。据我所知,没有操作系统范围内的方法来实现这一点,即使你可以让Windows以这种方式运行,每个应用程序也必须实现这一点。 - Wim Ombelets
如果您仍然有具有密钥组合的代码,能否将其发布?这正是我所需要的。 - Djeroen
1
@Djeroen:抱歉目前我没有此代码,但我会努力寻找并与您分享。 - Thilina H
@ThilinaH 很好!这是为未来的项目,所以不要着急。 - Djeroen
3个回答

7

阅读一些资料后,我找到了这个方法:

  1. Hook the double click event using something like globalmousekeyhook.codeplex.com
  2. (Optional) Save the current state of the clipboard
  3. Get The current mouse position with GetCursorPos from user32.dll
  4. Get windows based on cursor position with WindowFromPoint from user32.dll

    [DllImport("user32.dll")]
    public static extern IntPtr WindowFromPoint(Point lpPoint);
    
    [DllImport("user32.dll")]
    public static extern bool GetCursorPos(out Point lpPoint);
    
    public static IntPtr GetWindowUnderCursor()
    {
       Point ptCursor = new Point();
    
       if (!(PInvoke.GetCursorPos(out ptCursor)))
          return IntPtr.Zero;
    
       return WindowFromPoint(ptCursor);
    }
    
  5. Send copy command with SendMessage form user32.dll (see Using User32.dll SendMessage To Send Keys With ALT Modifier)

  6. Your Code
  7. (Optional) Restore the clipboard content saved in step 2

选项1不错。现在很有用。 - Oğuzhan Soykan
4
选项1是什么?这是一系列应该遵循的步骤:P。 - mcont

2
我实现了这个属于我的项目。好的,我该如何处理呢?让我来解释一下。
需要考虑两件重要的事情:
  • 如何获取任意窗口中的文本?
  • 应该将其存储在哪里?
因此,在上述方法的指导下,步骤如下:
  • 从Nuget添加globalmousekeyhook。
  • 通过Usr32.dll注册ClipboardContainsText事件。
  • 为鼠标注册正确的事件。
  • 并开始监听。

首先,创建一个包含剪贴板事件的Win32助手类。
/// <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);
}

其次,注册鼠标和剪贴板事件,
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;
}

鼠标事件;

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

最后一部分,当我们捕捉到问题时我们该怎么办,

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

这个技巧是向另一个窗口或操作系统发送复制命令,而不是使用Control+C命令。因此,可以使用SendKeys.SendWait("^c");来实现。


0

之前两个回答有些难以理解,而且不够详细。无论如何,非常感谢它们帮助我在尝试多次后实现了这个函数。

现在是2021年!让我们深入探讨我的详细、简单和最新的方法。

  1. 从Nuget安装MousekeyHook,它之前是globalmousekeyhook。

  2. 使用您的函数注册鼠标事件(MouseDoubleClick事件和MouseDragFinished事件,在这种情况下,您可能已选择了一些文本)。

  3. 保存剪贴板的当前内容以便稍后恢复。(可选)

  4. 清除剪贴板以确定稍后是否选择了一些文本。

  5. 通过System.Windows.Forms.SendKeys.SendWait("^c"); 简单地发送Ctrl+C命令。

  6. 获取剪贴板的内容。

    如果您选择了一些文本,则可以通过System.Windows.Clipboard.GetText() 简单地获取它并进行任何操作。

    如果没有选择,则恢复剪贴板。(可选)

这些是我的代码:

private IKeyboardMouseEvents globalMouseHook;

public MainWindow()
{
    // Note: for the application hook, use the Hook.AppEvents() instead.
    globalMouseHook = Hook.GlobalEvents();

    // Bind MouseDoubleClick event with a function named MouseDoubleClicked.
    globalMouseHook.MouseDoubleClick += MouseDoubleClicked;

    // Bind DragFinished event with a function.
    // Same as double click, so I didn't write here.
    globalMouseHook.MouseDragFinished += MouseDragFinished;
}

// I make the function async to avoid GUI lags.
private async void MouseDoubleClicked(object sender, System.Windows.Forms.MouseEventArgs e)
{
    // Save clipboard's current content to restore it later.
    IDataObject tmpClipboard = System.Windows.Clipboard.GetDataObject();

    System.Windows.Clipboard.Clear();

    // I think a small delay will be more safe.
    // You could remove it, but be careful.
    await Task.Delay(50);

    // Send Ctrl+C, which is "copy"
    System.Windows.Forms.SendKeys.SendWait("^c");

    // Same as above. But this is more important.
    // In some softwares like Word, the mouse double click will not select the word you clicked immediately.
    // If you remove it, you will not get the text you selected.
    await Task.Delay(50);

    if (System.Windows.Clipboard.ContainsText())
    {
        string text = System.Windows.Clipboard.GetText();
        
        // Your code

    }
    else
    {
        // Restore the Clipboard.
        System.Windows.Clipboard.SetDataObject(tmpClipboard);
    }
}

如果我使用键盘选择文本(例如Ctrl+A),它是否有效? - msi
@msi 嗯,只需将上面的函数(MouseDoubleClicked)绑定到您的键盘事件即可。 - Even Wonder

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接