我能复制Visual Studio的“查找符号结果”窗口中的多个行吗?

37

有没有人知道如何将Visual Studio“查找符号结果”窗口中的所有行复制到剪贴板上?你可以复制单个行,但我想要复制它们全部。

我简直无法相信我是第一个想做这件事的人,但我甚至找不到讨论这个似乎缺失功能的讨论。

9个回答

18
以下是使用.NET自动化库将所有文本复制到剪贴板的代码。
开始一个新的WinForms项目,然后添加以下引用:
- WindowsBase - UIAutomationTypes - UIAutomationClient - System.Xaml - PresentationCore - PresentationFramework - System.Management
该代码还解释了如何在Visual Studio中设置菜单项以将内容复制到剪贴板。
编辑:UI自动化仅返回可见的树形视图项。因此,为了复制所有项目,将查找符号结果窗口设为前景,然后发送{PGDN},并复制下一批项目。重复此过程,直到没有新项目被发现。最好使用ScrollPattern,但是当尝试设置滚动时,它会抛出异常。
编辑2:尝试通过在单独的线程上运行AutomationElement FindAll来提高性能。在某些情况下似乎很慢。
编辑3:通过使TreeView窗口非常大来提高性能。大约可以在10秒钟内复制约400个项目。
编辑4:处理实现IDisposable的对象。更好的消息报告。更好地处理进程参数。将窗口恢复到其原始大小。
图片如下:https://istack.dev59.com/gOaei.webp
using System;
using System.Collections;
using System.Collections.Generic;
using System.Diagnostics;
using System.Management;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading;
using System.Windows.Automation;
using System.Windows.Forms;

namespace CopyFindSymbolResults {

// This program tries to find the 'Find Symbol Results' window in visual studio
// and copy all the text to the clipboard.
//
// The Find Symbol Results window uses a TreeView control that has the class name 'LiteTreeView32'
// In the future if this changes, then it's possible to pass in the class name as the first argument.
// Use TOOLS -> Spy++ to determine the class name.
//
// After compiling this code into an Exe, add a menu item (TOOLS -> Copy Find Symbol Results) in Visual Studio by:
// 1) TOOLS -> External Tools...
//      (Note: in the 'Menu contents:' list, count which item the new item is, starting at base-1).
//      Title: Copy Find Symbol Results
//      Command: C:\<Path>\CopyFindSymbolResults.exe             (e.g. C:\Windows\ is one option)
// 2) TOOLS -> Customize... -> Keyboard... (button)
//      Show Commands Containing: tools.externalcommand
//      Then select the n'th one, where n is the count from step 1).
//
static class Program {

    enum Tabify {
        No = 0,
        Yes = 1,
        Prompt = 2,
    }

    [STAThread]
    static void Main(String[] args) {

        String className = "LiteTreeView32";
        Tabify tabify = Tabify.Prompt;

        if (args.Length > 0) {
            String arg0 = args[0].Trim();
            if (arg0.Length > 0)
                className = arg0;

            if (args.Length > 1) {
                int x = 0;
                if (int.TryParse(args[1], out x))
                    tabify = (Tabify) x;
            }
        }

        DateTime startTime = DateTime.Now;
        Data data = new Data() { className = className };

        Thread t = new Thread((o) => {
            GetText((Data) o);
        });
        t.IsBackground = true;
        t.Start(data);

        lock(data) {
            Monitor.Wait(data);
        }

        if (data.p == null || data.p.MainWindowHandle == IntPtr.Zero) {
            System.Windows.Forms.MessageBox.Show("Cannot find Microsoft Visual Studio process.");
            return;
        }

        try {

        SimpleWindow owner = new SimpleWindow { Handle = data.MainWindowHandle };

        if (data.appRoot == null) {
            System.Windows.Forms.MessageBox.Show(owner, "Cannot find AutomationElement from process MainWindowHandle: " + data.MainWindowHandle);
            return;
        }

        if (data.treeViewNotFound) {
            System.Windows.Forms.MessageBox.Show(owner, "AutomationElement cannot find the tree view window with class name: " + data.className);
            return;
        }

        String text = data.text;
        if (text.Length == 0) { // otherwise Clipboard.SetText throws exception
            System.Windows.Forms.MessageBox.Show(owner, "No text was found: " + data.p.MainWindowTitle);
            return;
        }

        TimeSpan ts = DateTime.Now - startTime;

        if (tabify == Tabify.Prompt) {
            var dr = System.Windows.Forms.MessageBox.Show(owner, "Replace dashes and colons for easy pasting into Excel?", "Tabify", System.Windows.Forms.MessageBoxButtons.YesNo);
            if (dr == System.Windows.Forms.DialogResult.Yes)
                tabify = Tabify.Yes;

            ts = TimeSpan.Zero; // prevent second prompt
        }

        if (tabify == Tabify.Yes) {
            text = text.Replace(" - ", "\t");
            text = text.Replace(" : ", "\t");
        }

        System.Windows.Forms.Clipboard.SetText(text);

        String msg = "Data is ready on the clipboard.";
        var icon = System.Windows.Forms.MessageBoxIcon.None;

        if (data.lines != data.count) {
            msg = String.Format("Only {0} of {1} rows copied.", data.lines, data.count);
            icon = System.Windows.Forms.MessageBoxIcon.Error;
        }

        if (ts.TotalSeconds > 4 || data.lines != data.count)
            System.Windows.Forms.MessageBox.Show(owner, msg, "", System.Windows.Forms.MessageBoxButtons.OK, icon);

        } finally {
            data.p.Dispose();
        }
    }

    private class SimpleWindow : System.Windows.Forms.IWin32Window {
        public IntPtr Handle { get; set; }
    }

    private const int TVM_GETCOUNT = 0x1100 + 5;

    [DllImport("user32.dll")]
    static extern int SendMessage(IntPtr hWnd, int msg, int wparam, int lparam);

    [DllImport("user32.dll", SetLastError = true)]
    static extern bool MoveWindow(IntPtr hWnd, int X, int Y, int Width, int Height, bool Repaint);

    private class Data {
        public int lines = 0;
        public int count = 0;
        public IntPtr MainWindowHandle = IntPtr.Zero;
        public IntPtr TreeViewHandle = IntPtr.Zero;
        public Process p;
        public AutomationElement appRoot = null;
        public String text = null;
        public String className = null;
        public bool treeViewNotFound = false;
    }

    private static void GetText(Data data) {
        Process p = GetParentProcess();
        data.p = p;

        if (p == null || p.MainWindowHandle == IntPtr.Zero) {
            data.text = "";
            lock(data) { Monitor.Pulse(data); }
            return;
        }

        data.MainWindowHandle = p.MainWindowHandle;
        AutomationElement appRoot = AutomationElement.FromHandle(p.MainWindowHandle);
        data.appRoot = appRoot;

        if (appRoot == null) {
            data.text = "";
            lock(data) { Monitor.Pulse(data); }
            return;
        }

        AutomationElement treeView = appRoot.FindFirst(TreeScope.Subtree, new PropertyCondition(AutomationElement.ClassNameProperty, data.className));
        if (treeView == null) {
            data.text = "";
            data.treeViewNotFound = true;
            lock(data) { Monitor.Pulse(data); }
            return;
        }

        data.TreeViewHandle = new IntPtr(treeView.Current.NativeWindowHandle);
        data.count = SendMessage(data.TreeViewHandle, TVM_GETCOUNT, 0, 0);

        RECT rect = new RECT();
        GetWindowRect(data.TreeViewHandle, out rect);

        // making the window really large makes it so less calls to FindAll are required
        MoveWindow(data.TreeViewHandle, 0, 0, 800, 32767, false);
        int TV_FIRST = 0x1100;
        int TVM_SELECTITEM = (TV_FIRST + 11);
        int TVGN_CARET = TVGN_CARET = 0x9;

        // if a vertical scrollbar is detected, then scroll to the top sending a TVM_SELECTITEM command
        var vbar = treeView.FindFirst(TreeScope.Subtree, new PropertyCondition(AutomationElement.NameProperty, "Vertical Scroll Bar"));
        if (vbar != null) {
            SendMessage(data.TreeViewHandle, TVM_SELECTITEM, TVGN_CARET, 0); // select the first item
        }

        StringBuilder sb = new StringBuilder();
        Hashtable ht = new Hashtable();

        int chunk = 0;
        while (true) {
            bool foundNew = false;

            AutomationElementCollection treeViewItems = treeView.FindAll(TreeScope.Subtree, new PropertyCondition(AutomationElement.ControlTypeProperty, ControlType.TreeItem));
            if (treeViewItems.Count == 0)
                break;

            if (ht.Count == 0) {
                chunk = treeViewItems.Count - 1;
            }

            foreach (AutomationElement ele in treeViewItems) {
                try {
                    String n = ele.Current.Name;
                    if (!ht.ContainsKey(n)) {
                        ht[n] = n;
                        foundNew = true;
                        data.lines++;
                        sb.AppendLine(n);
                    }
                } catch {}
            }

            if (!foundNew || data.lines == data.count)
                break;

            int x = Math.Min(data.count-1, data.lines + chunk);
            SendMessage(data.TreeViewHandle, TVM_SELECTITEM, TVGN_CARET, x);
        }

        data.text = sb.ToString();
        MoveWindow(data.TreeViewHandle, rect.Left, rect.Top, rect.Right - rect.Left, rect.Bottom - rect.Top, false);
        lock(data) { Monitor.Pulse(data); }
    }

    // this program expects to be launched from Visual Studio
    // alternative approach is to look for "Microsoft Visual Studio" in main window title
    // but there could be multiple instances running.
    private static Process GetParentProcess() {
        // from thread: https://dev59.com/qnE85IYBdhLWcg3w8IbK
        int myId = 0;
        using (Process current = Process.GetCurrentProcess())
            myId = current.Id;
        String query = String.Format("SELECT ParentProcessId FROM Win32_Process WHERE ProcessId = {0}", myId);
        using (var search = new ManagementObjectSearcher("root\\CIMV2", query)) {
            using (ManagementObjectCollection list = search.Get()) {
                using (ManagementObjectCollection.ManagementObjectEnumerator results = list.GetEnumerator()) {
                    if (!results.MoveNext()) return null;
                    using (var queryObj = results.Current) {
                        uint parentId = (uint) queryObj["ParentProcessId"];
                        return Process.GetProcessById((int) parentId);
                    }
                }
            }
        }
    }

    [DllImport("user32.dll")]
    private static extern bool GetWindowRect(IntPtr hWnd, out RECT lpRect);

    [StructLayout(LayoutKind.Sequential)]
    private struct RECT {
        public int Left;
        public int Top;
        public int Right;
        public int Bottom;
    }
}
}

这对我非常有效。我不得不为VS 2013添加“System.Management”。 - mojo
哇!对于VS2013,我必须添加到System.ManagementSystem.Windows.FormsUIAutomationClientUIAutomationTypes的引用。 像魔术一样奏效了! - mdisibio
2
这个回答是Stack上其他答案应该比较的基准。干得好,Loathing。 - James Linden
2
复制是可行的,但似乎会破坏结果窗口的宽度/渲染。 - Cerbrus
感谢所有的积极评论!我进行了一些改进(请参见答案中的第四次编辑)。 - Loathing
显示剩余5条评论

2
我用Macro Express解决了这个问题。他们提供了一个免费的30天试用期,我使用了它,因为这对我来说只是一次性的。我编写了一个简单的宏,逐行复制所有查找符号结果到记事本文档中。
步骤: * 重复 (x) 次(你有多少个符号结果就重复多少次) * 激活查找符号结果窗口 * 延迟0.5秒 * 模拟按下“向下箭头”键 * 剪贴板复制 * 激活记事本窗口 * 延迟0.5秒 * 剪贴板粘贴 * 模拟按下“回车”键 * 结束循环

1
开源替代品:AutoHotKeySikuli - Mike Lowery
@Sikuli - 我确实喜欢AutoHotKey!虽然我也讨厌它,但它确实解决了很多问题。我会把这个加入到我的长长的待办事项列表中,用AHK自动化的事情还有很多,但我认为微软在某些方面让我们失望了。 - Mr. Putty
虽然我来晚了,但是在使用VS2017时我遇到了同样的问题(无法升级更高版本)。我之前从未听说过AutoHotKeys :( 但最终还是成功让它工作了。 https://github.com/shashisadasivan/MyScripts/blob/master/Misc/Visual%20Studio/VSCopySymbolResult.ahk - Looneystar

1
我正在使用Visual Studio 2017(版本15.6.4),它具有直接功能。以下是一个示例截图(我按Ctrl + A选择了所有行): 查找所有引用,复制按钮 该示例的文本输出如下:
Status  Code    File    Line    Column  Project
    Console.WriteLine(xml); C:\Users\blah\Documents\ConsoleApp1\Program.cs  30  12  ConsoleApp1
    static void Console.WriteLine(object)               
    Console.WriteLine(xml); C:\Users\blah\Documents\ConsoleApp1\Program.cs  18  12  ConsoleApp1
    Console.WriteLine(xml); C:\Users\blah\Documents\ConsoleApp1\Program.cs  25  12  ConsoleApp1

1
如果您可以将符号编码为全局查找的表达式,那么从“查找结果”窗口复制所有结果就很容易了。
例如,要查找属性“foo”的所有引用,您可以进行全局查找“.foo”。

2
是的,这是真的,但查找所有引用似乎比我聪明一点,有时我真的想要它的力量。普通的“查找结果”窗口有复制命令,而“查找符号结果”窗口却没有,这让我感到难过。 - Mr. Putty
这并不是一个坏的解决方法,特别是如果你使用正则表达式(但如果你能将所有内容从查找符号窗口复制出来,那就更好了)。 - TooTone

1

我有相同的需求,并通过使用名为Hypersnap的截图工具来解决这个问题,该工具还具有一些基本的OCR功能。


0

我曾经有同样的问题。我不得不列出某个方法及其一些重载版本的所有出现情况。

为了解决我的问题,我使用了 ReSharper。(ReSharper->查找->高级用法查找)。

它还具有非常好的表格化文本导出功能。


-1

嘿,你可以用另一种方式实现这个功能,

只需“查找所有”选定的文本,就能捕获所有行。


-1

根据我的以往经验和最近进行的一些测试,没有内置功能可以做到这一点。

你为什么想要这样做?为什么要复制所有引用到剪贴板?据我所知,这些功能的速度使得如果您可以快速生成动态和完整的副本,则拥有所有引用的静态副本相对无用。

您始终可以扩展Visual Studio以添加此功能,请参见Egghead Cafe上的this post


3
对我来说,这些搜索无法迅速完成。我的解决方案相当大,包括多个相互依赖的项目,搜索符号需要花费数分钟的时间(我的电脑不算太慢)。此外,输出结果包含数百行,有点繁琐。不幸的是,这些搜索比我手动轻松完成的任何操作都要智能,我也想能够比较几次类似搜索的输出结果。也许我今天有点暴躁,但在我看来,这确实是一个缺失的功能。 - Mr. Putty
最近我一直在玩Visual Studio的可扩展性,尽管教程和文档质量一般,但你可以做很多酷炫的事情来扩展Visual Studio,而且启动一个插件只需要几个简单的步骤。我鼓励你也试试,因为这并不难。 - user142350

-2

Visual Studio Code可以作为浏览器使用。您可以打开开发者工具并搜索代码的某个部分。

菜单:帮助 > 切换开发者工具

在开发者工具控制台中输入以下指令:

var elementos = document.getElementsByClassName("plain match")
console.log(elementos.length) 
for(var i = 0; i<elementos.length;i++) console.log(elementos[i].title)

你可以看到比赛结果。

现在,如果你能复制这些结果

screen shot


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