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

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

我不能相信我是第一个想要这样做的人,但我甚至找不到有关这个明显缺失的特征的讨论。

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

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

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

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

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

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

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

编辑4:处理实现IDisposable对象。 更好的消息报告。 更好地处理stream程参数。 把窗户放回原来的大小。

在这里输入图像说明

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

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

顺序:*重复(x)次(但有许多符号结果)*激活查找符号结果窗口*延迟0.5秒*模拟击键“箭头向下”*剪贴板复制*激活记事本窗口*延迟0.5秒*剪贴板粘贴*模拟击键“ENTER”*结束重复

有相同的要求,并通过使用一个名为Hypersnap的截图工具来解决这个问题,它也有一些基本的OCRfunction。

如果您可以将您的符号编码为全局查找的expression式,那么从查找结果窗口复制粘贴所有结果很容易。

例如find属性'foo'的所有引用,你可以为'.foo'

从我以前的经验和我刚刚做的一些testing来看,没有内置function可以做到这一点。

你为什么要这样做? 你为什么要将所有的引用复制到剪贴板? 据我所知,这些function的速度将使所有的引用静态副本将是相对无用的,如果你能快速生成一个dynamic和完整的副本。

你可以随时扩展视觉工作室来添加这个function,在egghead咖啡厅看到这个post 。

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

只需“查找所有”选定的文本,你将能够捕捉所有的行