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

Mr.*_*tty 36 visual-studio

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

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

Loa*_*ing 18

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

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

  • WindowsBase
  • UIAutomationTypes
  • UIAutomationClient
  • System.Xaml
  • PresentationCore
  • PresentationFramework
  • 系统管理

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

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

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

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

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

在此输入图像描述

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;
    }
}
}
Run Code Online (Sandbox Code Playgroud)

  • 这个响应是试金石,通过它可以比较Stack上的其他答案.好工作厌恶. (2认同)
  • 复制工作,但这似乎弄乱了结果窗口的宽度/渲染. (2认同)