Does anyone know how to copy all the lines in the Visual Studio "Find Symbol Results" window onto the clipboard? You can copy a single line, but I want to copy them all.
I can't believe that I'm the first one to want to do this, but I can't even find a discussion about this apparently missing feature.
Here is some code that uses the .Net Automation library to copy all the text to the clipboard.
Start a new WinForms
project and then add the following references:
The code also explains how to setup a menu item in visual studio to copy the contents to the clipboard.
Edit: The UI Automation only returns visible tree view items. Thus, to copy all the items, the find symbol results window is set as foreground, and then a {PGDN}
is sent, and the next batch of items is copied. This process is repeated until no new items are found. It would have been preferable to use the ScrollPattern
, however it threw an Exception
when trying to set the scroll.
Edit 2: Tried to improve the performance of AutomationElement FindAll by running on a separate thread. Seems to be slow in some cases.
Edit 3: Improved performance by making the TreeView window very large. Can copy about 400 items in about 10 seconds.
Edit 4: Dispose objects implementing IDisposable
. Better message reporting. Better handling of process args. Put window back to its original size.
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; } } }
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With