问题描述
有谁知道如何将 Visual Studio查找符号结果"窗口中的所有行复制到剪贴板上?你可以复制一行,但我想复制它们.
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.
推荐答案
以下是一些使用 .Net 自动化库将所有文本复制到剪贴板的代码.
Here is some code that uses the .Net Automation library to copy all the text to the clipboard.
启动一个新的 WinForms
项目,然后添加以下引用:
Start a new WinForms
project and then add the following references:
- WindowsBase
- UIAutomationTypes
- UIAutomationClient
- 系统.Xaml
- PresentationCore
- 演示框架
- 系统管理
该代码还解释了如何在 Visual Studio 中设置菜单项以将内容复制到剪贴板.
The code also explains how to setup a menu item in visual studio to copy the contents to the clipboard.
UI 自动化仅返回可见的树视图项.因此,要复制所有项目,将查找符号结果窗口设置为前台,然后发送{PGDN}
,然后复制下一批项目.重复此过程,直到找不到新项目.最好使用 ScrollPattern
,但是它在尝试设置滚动时抛出了 Exception
.
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.
编辑 2: 尝试通过在单独的线程上运行来提高 AutomationElement FindAll 的性能.在某些情况下似乎很慢.
Edit 2: Tried to improve the performance of AutomationElement FindAll by running on a separate thread. Seems to be slow in some cases.
编辑 3:通过使 TreeView 窗口非常大来提高性能.10秒左右可以复制400个左右.
Edit 3: Improved performance by making the TreeView window very large. Can copy about 400 items in about 10 seconds.
编辑 4:处置实现 IDisposable
的对象.更好的消息报告.更好地处理流程参数.将窗口恢复到原来的大小.
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;
}
}
}
这篇关于我可以从 Visual Studio 的“查找符号结果"中复制多行吗?窗户?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!