扫一扫
分享文章到微信
扫一扫
关注官方公众号
至顶头条
作者:中国IT实验室 来源:中国IT实验室 2007年9月8日
关键字: 搜索 Windows VisualStuido
•创建 Visual Studio 加载项
•创建加载项工具窗口
•使用 Windows 桌面搜索进行开发
本文涉及以下技术:
Visual Studio 2005 和 Windows Desktop Search SDK
代码下载位置:
计算机硬盘容量越来越大,您保存的信息量也越来越多。您拥有成千上万的文件和电子邮件消息,因此很难准确查找所需的信息。令人欣慰的是,Windows® 桌面搜索可以帮助您进行查找。
Windows 桌面搜索非常简单。它可以通过可扩展性机制将您的所有文档、文件和电子邮件消息以及提供给它的任何其他数据编入索引。随着新项目的添加以及对现有项目的修改,它们会重新编入索引。您可以通过 UI 在编入索引的项目中搜索字符串和关键字,并使用相关联的应用程序打开任何找到的项目。
Windows 桌面搜索提供一个 SDK,它允许其他应用程序使用其索引和搜索功能。作为一名开发人员,我在 Microsoft® Visual Studio® 方面花费了很多时间,同时还在源代码中搜索与手头开发任务相关资料方面花费了大量时间。例如,我将搜索以前使用特殊类编写的其他代码段或与同事就某个集合的使用而进行的电子邮件会话。通过编写一个可以与 Windows 桌面搜索通信的 Visual Studio 加载项,就可以构建一个类似动态帮助的工具窗口,该窗口可使用桌面搜索来显示 Visual Studio 中当前活动文本的相关搜索结果。在本文中,我将向您介绍如何创建这种加载项。请下载此问题的代码以便于参考。
此项目展示了两项技术:使用可扩展性接口创建 Visual Studio 2005 加载项,以及将加载项集成到 Windows 桌面搜索中。
Visual Studio 加载项以非模式方式向用户提供数据,通常由一个工具窗口、至少一个菜单命令、一个提供配置选择的“选项”对话框和一个“关于”对话框组成。我希望加载项工具窗口包括 Windows 桌面搜索位置筛选器,例如“所有”、“文件”和“电子邮件”。我希望加载项可以根据 Visual Studio 编辑器中选择的文本进行自动查询(请参见图 1),还可以包含一个能关闭此自动刷新功能的选项。我希望列表视图中可以显示查询结果,并且当用户双击结果时可以用相关联的应用程序将其打开。最后,我希望可以通过“选项”对话框来提供配置功能。首先,我们来创建加载项,然后将其移到 Windows 桌面搜索集成中。
图 1 基于选择文本搜索
创建加载项
创建加载项是一个非常简单的过程。选择“文件”|“新建”|“项目”菜单项。在“新建项目”对话框中,选择“其他项目类型”,然后单击“扩展性”。在“模板”下,选择“Visual Studio 加载项”,为新加载项输入名称,然后单击“确定”。Visual Studio 加载项向导会指导您完成余下的操作。选择 C# 作为编程语言,Visual Studio 2005 作为编程工具,添加名称和说明,然后选中选项以创建“工具”菜单项并为自动创建的“关于”对话框添加信息。
单击“完成”按钮后,您将完成加载项的框架以备使用。注意,项目生成器会为您添加 IDTExtensibility2 和 IDTCommandTarget 接口实现,以及设计时环境 (DTE) 可扩展性应用程序对象引用。
大多数 Visual Studio 工具窗口相关项都显示在“视图”菜单下。要与之相匹配,可将加载项中的“工具”菜单引用更改为“视图”,将其文本更新到 Windows 桌面搜索,并将生成的菜单项和命令代码移到名为 CreateCommand 的方法中。
下一步是更改代码调用 AddNamedCommand2 命令的方式,该命令可创建“视图”项。Visual Studio 可为新菜单项提供默认的图像,但您可能更喜欢使用自己的图像。要实现此操作,图像必须位于附属程序集中。若使用以前版本的 Visual Studio,则您必须为附属程序集创建一个单独的项目。若使用 Visual Studio 2005,则需要通过将资源文件添加到项目中来实现。
将一个资源项添加到名为 WDSAddin.en 的加载项中,然后插入一个名为 1 的图标资源。编译带有新资源的加载项项目,将会在 en 文件夹下自动生成一个资源程序集。您可以将该资源 ID 1 与 AddNamedCommand2 调用一起使用来分配自己的菜单项图像。请不要忘记将 AddNamedCommand2 的 MSOButton 参数值更改为“false”,以表示您要使用自己的图像。
添加命令后,您会重新获得命令对象引用,并可为其分配一个快捷键。命令接口具有一个 Bindings 属性,专用于此目的。它可返回分配给所有作用域的快捷方式集合。作用域包括全局、文本编辑器、Windows 窗体设计器等。请注意,作用域名称已本地化。
BindCommandKey 方法负责分配快捷键。它包括一个用于设置快捷键的 try/catch 代码块。以这种方法进行设置可以防止在尝试更改只读键盘方案设置时引发异常。Ctrl+Alt+Shift+M 可成为工具窗口很好的快捷键,因为它模仿了 Windows 桌面搜索默认的 Ctrl+Alt+M 快捷键。
注意,生成的加载项类代码可实现 IDTCommandTarget 接口,它可以通知 Visual Studio 该命令是否可在给定的上下文中使用。此功能可通过 QueryStatus 方法完成。如果确定用户可使用此命令,那么在选择该命令后,Exec 方法将会得到调用。使用这两种方法时,可选中命令名称以确保此命令确实需要处理。您可通过调用私有的 Show-WDSToolWindow 方法,利用 Exec 方法来显示 Windows 桌面搜索工具窗口。此方法可检查是否设置了工具窗口引用。如果已设置,则此方法会通过将 Visible 属性设置为“true”来显示工具窗口。否则 ShowWDSToolWindow 方法将创建并显示一个新的工具窗口。
创建工具窗口
您以前必须依靠 C++ shim 控件来托管工具窗口中的托管用户控件。同时已知工具窗口状态持久性存在问题,例如保存工具窗口位置、大小和停靠状态。Microsoft 已收到许多有关此方面的问题和反馈,并听取了有关可扩展性团队的好的建议。Visual Studio 2005 推出一个新的带有 CreateToolWindow2 方法的 Window2 接口,它使您可以托管工具窗口中的托管用户控件。CreateToolWindow2 的最后一个参数可返回一个托管用户控件的引用。
要设置工具窗口,首先需要将一个名为 ToolWindowUserControl 的用户控件添加到您的项目中,然后插入顶部对齐的面板以作为工具栏。将一个客户区对齐的列表视图添加到标题面板下方用于搜索结果。图 2 显示创建工具窗口的代码。
注意,CreateToolWindow2 方法希望您提供用户控件程序集的位置和完整的类名称。它还需要一个 GUID 以将工具窗口与 Window 类引用相关联。此参数可以是任何 GUID 值,但是需要生成一次并在以后保持一致,以使工具窗口状态持久性正常运行。如果 CreateToolWindow2 调用成功,您会获得一个对象引用,该引用可被安全的转换回工具窗口用户控件类类型。
如果您具有工具窗口引用,就可以通过 SetTabPicture 调用来设置图像,并可通过将 Enabled 属性设置为“true”来显示工具窗口图像。Window.SetTabPicture 方法需要一个 GDI 位图,因此需要添加另一个名为 Resources 的资源文件,然后再将名为 WDSIcon 的图标资源添加到该文件中。由于 Visual Studio 可生成一个资源包装类,因此检索资源非常简单(必须在显示工具窗口前设置工具窗口图像):
以下是引用片段: _toolWindow.SetTabPicture(Resources.WDSIcon.ToBitmap().GetHbitmap()); _toolWindow.Visible = true; |
为什么不对工具窗口重用 View 命令图标?命令和工具窗口图标的透明度要求有所不同。Visual Studio 命令图标使用透明石灰绿色 (0,255,0 RGB),而工具窗口图标使用透明品红色 (255,0,255 RGB)。
常见的工具窗口创建方法是在加载项加载时或命令执行响应时创建工具窗口。之后,您应保留工具窗口引用直至加载项卸载为止,而不是创建新的工具窗口实例。您还应提供一个“视图”菜单项来显示工具窗口,以防用户将其关闭。如果将其关闭,工具窗口引用应继续为活动状态,并且仍可使工具窗口可见。
“选项”对话框页面
工具窗口创建完成后,您可以为加载项提供一些配置选项,如图 3 所示。首先创建一个名为 WDSToolsOptions 的新类库,然后向其中添加一个名为 OptionsPage 的新用户控件。
图 3 工具“选项”对话框页面
要添加“选项”对话框支持,您需要在新的用户控件上实现 IDTToolsOptionsPage 接口,其过程相当简单。此处只需要两个接口方法。OnAfterCreated 方法用于从注册表中读取以前保存的配置选项,并相应的更新 UI。OnOK 方法用于将 UI 选项保存到注册表。您可以通过一个静态的 Settings 类来保存所有配置选项,以避免频繁读取注册表。您可以在此文档的代码下载中查看这些实现。
最后一步是设置加载项,以使用新的选项页面程序集。.addin XML 配置文件是进行此步骤的最好选择,新的 Visual Studio 2005 加载项部署功能简化了加载项的安装并消除了基于注册表注册的需要。以下是使用加载项配置“选项”页面所需的全部代码:
以下是引用片段: <ToolsOptionsPage> <Category Name="Windows Desktop Search"> <SubCategory Name="General"> <Assembly>[Path-To-Addin]\WDSToolsOptions.dll</Assembly> <FullClassName>WDSToolsOptions.OptionsPage</FullClassName> </SubCategory> </Category> </ToolsOptionsPage> |
如您所见,您只需告诉 Visual Studio 加载哪些程序集以及该程序集中的哪些类名称用于“选项”页面即可。请注意,建议将第一个子页面命名为 General,以遵循标准的 Microsoft 页面命名约定。
.addin 文件中还包含加载项的所有“关于”对话框信息,包括图标。为加载项创建的默认图标是一个二进制编码字符串,这便很难进行修改,因此最好使用资源文件图标取而代之。
是否还记得您第一次创建加载项时添加的资源文件?对于“关于”对话框,您可以创建一个名为 AboutIcon 的新的 32×32 图标,并将其添加到 WDSAddin.en 资源文件中。通过 .addin 配置文件可以从附属程序集加载图标,并用其资源 ID 来代替二进制编码字符串:
以下是引用片段: <AboutIconData>@AboutIcon</AboutIconData> |
Windows Desktop Search SDK
通过简单的 COM API 可查询被 Windows 桌面搜索编入索引的信息。ISearchDesktop 接口上只有两个公共方法:ExecuteSQLQuery 和 ExecuteQuery。ExecuteSQLQuery 用于对底层索引存储执行符合语法的 SQL 查询。另一方面,ExecuteQuery 更加用户友好,并且其查询字符串在语法上与通过 Windows 桌面搜索查询输入框提供的字符串相匹配。以下代码显示如何定义 ExecuteQuery 方法:
以下是引用片段: HRESULT ExecuteQuery(LPCWSTR lpcwstrQuery, LPCWSTR lpcwstrColumn, LPCWSTR lpcwstrSort, LPCWSTR lpcwstrRestriction, Recordset **ppiRs); |
图 4 中解释了 ExecuteQuery 参数。如您所见,前四个参数是不同的字符串输入值,它们可控制查询和结果集,而最后一个参数可控制输出结果集。
幸运的是,您不必考虑将其转换为托管调用。Windows Desktop Search SDK 可提供下载,并包括两个托管程序集,其中一个位于 WDSQuery.dll。该 COM 互操作程序集可包装相关的 Windows Desktop Search COM 类和接口。最重要的是,它提供了一个名为 SearchDesktopClass 的类,该类可实现 ISearchDesktop 并用于执行 WDS 查询。对于 SearchDesktopClass 类,建议的使用模式是为每个查询创建一个新的实例,从而检索结果并允许垃圾收集器随后进行清理,而不是只创建该类的一个实例并将其保留用于所有搜索。以下代码显示如何在加载项中使用 SearchDesktopClass:
以下是引用片段: SearchDesktopClass wdsSearch = new SearchDesktopClass(); _Recordset result = wdsSearch.ExecuteQuery( "Secondary", "Rank, FileName, DisplayFolder, Url, PerceivedType", "Rank DESC, DocTitle", "Contains(PerceivedType,'document')"); |
该查询指定了 4 个元素。它针对字符串“Secondary”而执行。逗号分隔列以 Rank 开始,以 PerceivedType 结束。排序的列为 Rank 和 DocTitle。最后,将筛选器设置为检索“document”类型的所有项。该调用实际上转换为对所有其中含有单词“Secondary”的文档的一个请求。
Windows 桌面搜索支持许多内置的列,以进行查询和排序。图 5 中显示了您可以使用的一些常用列。注意,结果将在示例代码中有意按照 Rank 降序排序。这可确保最可能的结果位于结构集的顶部。
后台查询和 UI 更新
早期的设计目标之一是构建一个几乎不影响 Visual Studio 响应性的工具。Windows 桌面搜索运行索引查询时,查询的执行速度会极快。然而根据搜索内容,执行一次查询仍会花费几毫秒到一秒,或更长的时间。若查询是在 Visual Studio 主线程上执行的,那么运行查询时,UI 会出现暂时的锁定。明显的解决方案就是在次级线程上执行查询。
您可能会考虑使用新的 .NET Framework 2.0 BackgroundWorker 类。不幸的是,这行不通。因为该类与 SearchDesktopClass 及其 Windows Desktop Search COM 对象不兼容。尝试使用 BackgroundWorker 执行操作会产生 InvalidCastException 异常。要在次级线程上使用 SearchDesktopClass,您就必须使用单线程单元 (STA) 线程。BackgroundWorker 可以使用 .NET ThreadPool,默认情况下,其中会包含多线程单元 (MTA) 线程。
因此,添加一个名为 WDSQueryWorker 的新类到加载项项目,可以将 Run 方法用作线程启动委托参数来创建一个 STA 线程。Run 方法使用 WaitHandle 类等待单独的事件句柄,如下所示:
以下是引用片段: private void Run() { try { while (true) { WaitHandle.WaitAny(_runHandles); if (_stopped) break; ProcessEvent(); _runEvent.Reset(); } } finally { _stopped = true; } } |
WDSQueryWorker 类还提供一个名为 DoWork 的公共方法,用来设置 Run 方法的等待事件,随后还会在 ProcessEvent 方法中触发查询执行。要查询的字段应在调用 DoWork 之前设置。
WDSQueryWorker 类构造函数采用了三个参数:查询完成时调用的 UI 控件、查询完成委托和查询错误委托。两个委托都通过 Control.Invoke 调用。Invoke 方法可确保委托在控件的 UI 线程上执行。值得一提的是,传递到查询完成委托的参数都包含 _Resultset。另外,查询错误委托参数还包含 Exception 引用。
虽然将查询执行移至单独线程,可以解决某些与运行大型查询相关的 Visual Studio 响应性问题,但仍存在一个有待解决的问题。像提到的那样,控件的 Invoke 方法在控件的 UI 线程上执行委托,并且该线程恰巧是 Visual Studio UI 线程。如果 Windows 桌面搜索返回一个大型结果集,则仍需花费时间填充加载项的结果列表视图。明显的解决方案是限制显示结果。事实上,如果您已启用“根据输入进行搜索”选项,Windows 桌面搜索将只显示每个类别的前六个结果,同时会添加一个“more…”项以显示其余结果。使用“选项”对话框,您可以设置加载项来限制显示结果,比方说,默认每个类别显示 100 项。如果您想看到整个结果集,可将“显示首批结果”的值设置为“0”以取消限制。
您还希望确保当编辑器中存在用户类型代码时,即使具有受限的查询结果,也不会因查询执行而导致延迟。幸运的是,Visual Studio 可扩展性恰好为此提供了编辑器事件。可通过 Events 接口访问这些事件。图 6 显示了如何进行设置。
基本思路是,如果用户正在输入代码、滚动代码或从一个窗口移至另一窗口,则延迟查询。您可以添加自己的委托,并将所有这些事件的全部委托设置为调用私有 EditorUpdateInProgress 方法,以延迟查询执行。
将其放在一起
当我着手创建该加载项时,目标是当用户选择 Visual Studio 编辑器中的某文本时仍然可以执行查询,而且我希望最初能找到一些 Visual Studio 可扩展性文本选定事件。虽然 TextSelection 可扩展性接口可返回当前选定的文本,但可扩展性并不提供文本选定更改事件。这是一个令人遗憾的退步。唯一的解决方案是轮询,但会再度产生 Visual Studio 响应性问题。轮询本质上是一种代价高昂的操作,因为您会在不必要的时候结束轮询。因此需要建立仔细的安全措施,以防止过度轮询。
一种方法是为您的用户控件添加计时器,并设置为每两秒滴答一次。也可以为“选项”对话框页面添加几个较慢的计时器刷新设置。计时器的 Tick 事件处理程序仅调用私有 ExecuteQuery 方法,设置查询和初始化次级工作线程处理就在此进行,如图 7 所示。为强调该方法的目的,一个很妙的主意是,将其命名为与 Windows Desktop Search SDK 的方法名称 (SearchDesktopClass.ExecuteQuery) 相匹配的名称。
当 ExecuteQuery 运行时,首先进行的操作就是将私有处理字段设置为“true”,并禁用轮询计时器。然后检索 Visual Studio 活动文档,并检索其带有 Text 属性的 TextSelection(如果有)。该属性会返回编辑器中当前选定的文本。
将最后一次执行的选定保存在私有字段中。检索 TextSelection.Text 后,请检查当前选定文本是否与上次文本查询有所不同。如果相同,就不必重新执行,只需退出该方法即可。
您还应按照 选项对话框页面中的配置,检查选定文本是否满足最低选定文本要求。选定的文本至少应包含三个执行查询的字符,即针对用户选择的性能保障,例如,如果只输入一个字母 A,Windows 桌面搜索将返回数千个条目。
如果一切正常,将使用要搜索的文本设置查询工作线程(以前定义的 WDSQueryWorker 类),并且会调用它的 DoWork 方法。ExecuteQuery 方法结束时,计时器将重新启动(如果事先是运行的)并且处理字段将重新设置为“false”。结果列表视图将被清除,更新状态也将被更改,从而显示“Searching…”。
查询工作线程的 DoWork 方法将设置一个事件,用于执行一次查询。当然,所有这些工作都可以在次级线程上执行。当查询完成时,该方法将回调到主线程。回调委托恰好支持 ToolWindowUserControl。其中的 QueryCompleted 方法将只调用 DisplayResults 来更新结果列表视图。如果查询失败,它还会调用 ToolWindowUserControl 的 QueryError 方法。根据“选项”对话框页面中的配置,QueryError 可显示错误消息或无提示地忽略错误。
DisplayResults 方法将收到一个 _Recordset 参数。如果重新调用,Windows Desktop Search SDK ExecuteQuery 方法就会返回该参数。DisplayResults 将遍历结果,每次提取一行。它可以根据预定义的一组查询字段检索列值,并更新相应的列表视图列。它还可以根据 PerceivedType 列对项目进行分组,Windows 桌面搜索可依此识别项目类型。列表视图分组是 .NET Framework 2.0 的新功能,无法在 Windows XP 之前的系统上运行。
除针对最小查询字符串长度具有保障外,您还可以限制显示的结果数,例如让每个类别只显示前 100 项。您可以将该选项更改为 0 以查看整个结果集。通过列表视图结果的合理的端,UI 更新处理几乎可以瞬间发生,这极大改善了 Visual Studio 的响应性。研究一下结果选项,看看哪种最适合您。
存在潜在改进可能性的一个方面是,检查 Windows 桌面搜索是否支持在查询级别上限制结果集。即,如果将“显示首批结果”选项实现从调整显示结果转为调整实际查询范围,就可以加速该进程。
安装和部署
Visual Studio 2005 引入了一个新的基于 .addin 配置文件的免注册部署方案。这使加载项的安装就像将程序集复制到指定文件夹以及重启 Visual Studio 一样简单。以下是 Windows 桌面搜索加载项的手动安装步骤:
•将 WDSAddinBinaries 文件夹的内容复制到 \Program Files\WDSAddin 文件夹中。如果您要使用其他文件夹,则手动更新 WDSAddin.addin 中的文件路径引用。
•将 WDSAddin.addin 文件复制到 \My Documents\Visual Studio 2005\Addins 文件夹中。
•确保 Msvcp71.dll 的副本也位于 Windows 桌面搜索安装文件夹中,并位于 Windows 路径中的某个位置。否则只需将文件从 Windows 桌面搜索安装文件夹复制到 \Windows\System32 中即可。
如果您计划调试 Windows 桌面搜索加载项,请关闭 Visual Studio,并至少运行一次代码下载中包含的 ToolWindow.reg 文件。它包含一个针对 Visual Studio 2005 加载项工具窗口状态持久性问题的解决方案。否则只需跳过该步骤即可。
小结
Windows Desktop Search SDK 所做的工作及其应用相当简单,而且我所描述的加载项也不太复杂。而两者组合起来却非常有用。该加载项与 Visual Studio Dynamic Help 类似,但前者还具有扫描整个计算机的功能,而不仅仅是预定义的一组帮助文件。
Sergey Mishkovskiy 是 OpenSpan Inc. 的一名高级软件开发人员。闲暇之余,他会玩 Xbox、阅读,并且负责一个流行的免费 Visual Studio 加载项(名为 DPack)方面的工作。有关 DPack 的详细信息,请访问 www.usysware.com/dpack(英文)。您可以发送电子邮件到 sergey@usysware.com 与 Sergey 联系。
如果您非常迫切的想了解IT领域最新产品与技术信息,那么订阅至顶网技术邮件将是您的最佳途径之一。
现场直击|2021世界人工智能大会
直击5G创新地带,就在2021MWC上海
5G已至 转型当时——服务提供商如何把握转型的绝佳时机
寻找自己的Flag
华为开发者大会2020(Cloud)- 科技行者