我可以从Visual Studio“查找符号结果”窗口中复制多行吗?

时间:2009-07-23 18:59:30

标签: visual-studio

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

我无法相信我是第一个想要这样做的人,但我甚至找不到关于这个显然缺失的功能的讨论。

9 个答案:

答案 0 :(得分:18)

以下是一些使用.Net Automation库将所有文本复制到剪贴板的代码。

启动一个新的WinForms项目,然后添加以下引用:

  • WindowsBase
  • UIAutomationTypes
  • UIAutomationClient
  • System.Xaml
  • PresentationCore
  • PresentationFramework
  • System.Management

该代码还解释了如何在visual studio中设置菜单项以将内容复制到剪贴板。

编辑:UI自动化仅返回可见的树视图项。因此,要复制所有项目,将查找符号结果窗口设置为前景,然后发送{PGDN},并复制下一批项目。重复此过程,直到找不到新项目。最好使用ScrollPattern,但在尝试设置滚动时它会抛出Exception

编辑2:尝试通过在单独的线程上运行来提高AutomationElement FindAll的性能。在某些情况下似乎很慢。

编辑3:通过使TreeView窗口非常大来提高性能。可以在大约10秒钟内复制约400件物品。

编辑4:处理实施IDisposable的对象。更好的消息报告。更好地处理流程args。将窗口恢复到原始大小。

enter image description here

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: http://stackoverflow.com/questions/2531837/how-can-i-get-the-pid-of-the-parent-process-of-my-application
        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;
    }
}
}

答案 1 :(得分:2)

我通过使用Macro Express解决了这个问题。他们有30天的免费试用期,因为这对我来说是一次性的。我写了一个简单的宏来将所有的查找符号结果一次一行地复制到记事本文档中。

序列: *重复(x)次(无论你有多少符号结果) *激活“查找符号结果”窗口 *延迟.5秒 *模拟按键“向下箭头” *剪贴板复制 *激活记事本窗口 *延迟.5秒 *剪贴板粘贴 *模拟击键“ENTER” *结束重复

答案 2 :(得分:1)

有相同的要求并通过使用名为Hypersnap的截屏工具解决了这个问题,该工具也具有一些基本的OCR功能。

答案 3 :(得分:1)

如果您可以将符号编码为全局查找的表达式,则可以轻松地从“查找结果”窗口复制粘贴所有结果。

例如,查找属性'foo'的所有引用,您可以对'.foo'进行全局查找

答案 4 :(得分:0)

Visual Studio Code用作浏览器。 可以打开开发人员工具并搜索代码的那一部分

菜单:帮助&gt;切换开发人员工具

将以下说明写入开发人员工具控制台:

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

答案 5 :(得分:0)

我遇到了同样的问题。我必须列出某个方法的所有出现以及它的一些重载版本。

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

它还有一个非常好的列表文本导出功能。

答案 6 :(得分:0)

我正在使用Visual Studio 2017(版本15.6.4),它直接具有该功能。这是一个示例屏幕截图(我按Ctrl + A选择所有行): Find All References, copy button

该示例的文本输出为:

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

答案 7 :(得分:-1)

根据我之前的经验以及我刚刚做过的一些测试,没有内置功能可以做到这一点。

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

您可以随时扩展visual studio以添加此功能,请参阅 {* 3}}在egghead咖啡馆。

答案 8 :(得分:-1)

嘿,不知何故,你可以用另一种方式实现这一目标,

Just&#39; FindAll&#39;选定的文本,您将能够捕获所有行