使用 MSN Desktop Search API 的 iTunes 风格音乐浏览器






4.30/5 (7投票s)
2005年7月6日
6分钟阅读

86479

1810
使用 MSN 桌面搜索查询 API 的音乐浏览器应用。
引言
从 Windows 2000 开始,微软就在 Windows 中加入了索引服务。作为索引服务的一部分,他们提供了 API,允许第三方通过实现 IFilter
组件和API 为其专有文件格式添加索引支持,并对索引执行查询。
MSN 桌面搜索应用程序是 Windows 自带的标准索引服务的进化版。它同样利用 IFilter
组件来允许第三方为其文件格式添加索引支持。然而,其初始版本并未提供用于执行查询的 API。最新发布的 2.05 版本现在则包含了查询 API。
为了测试这个查询 API,我决定实现一个音乐浏览器,让你能够浏览系统中所有已建立索引的音乐。我没有使用树形控件来浏览音乐收藏,而是决定使用三个列表,分别根据流派、艺术家和专辑进行筛选。这与苹果的 iTunes 的方式非常相似,或者按照 Contois Music Technology 的说法,是他们早在 90 年代初就发明的音乐浏览方法。
查询 API 详情
该查询 API 被标注为测试版(beta),相关文档非常少。实际上,目前所有可用的资料只有一个指定了 COM 接口 ISearchDesktop
的 IDL 文件和几条注释。
interface ISearchDesktop : IUnknown { // perform a search given the SQL HRESULT ExecuteSQLQuery([in] LPCWSTR lpcwstrSQL, [out, retval] _Recordset **ppiRs); // perform a search given the query string HRESULT ExecuteQuery([in] LPCWSTR lpcwstrQuery, [in] LPCWSTR lpcwstrColumn, [in] LPCWSTR lpcwstrSort, [in] LPCWSTR lpcwstrRestriction, [out, retval] _Recordset **ppiRs); } // lpcwstrQuery - The query (like you'd type in to the query box) // lpcwstrColumn - Columns to include (just comma-separated, // it'll be put right into the SQL) // lpcwstrRestriction - A "where" clause to be appended, // e.g., "Contains(foo,'bar')" // ppiRs - The resulting recordset
要了解可以在 MSN 桌面搜索查询窗口中输入的查询类型,可以查看帮助文档中关于高级查询语法的部分,该文档也发布在互联网上。
以下是一个可以执行的高级查询示例
kind:music genre:rock artists:("Cowboy Junkies" OR Coldplay)
MSN 桌面搜索的查询结果以 ADO recordset
的形式返回,其列由传入 ExecuteQuery()
方法的列名或传入 ExecuteSQLQuery()
方法的 SQL 语句定义。
为了使用 ISearchDesktop
接口,我首先对提供的 IDL 文件运行了 MIDL 编译器以生成 COM 类型库,然后使用 TlbImp 创建了一个 .NET 互操作程序集,以便从 .NET 应用程序中使用查询 API。
在我写完这篇初稿之后,微软发布了更新的 SDK,其中包含了预构建的互操作程序集,供 .NET 应用程序使用。请查看 MSN 桌面搜索团队发布的这篇博客文章了解更多详情。
以下代码片段展示了如何使用 .NET 互操作程序集执行查询
SearchDesktopClass msnDS = new SearchDesktopClass();
_Recordset rs = msnDS.ExecuteQuery("kind:music genre:rock",
"MusicArtist, MusicAlbum, DocTitle", null, null);
如果你在执行查询后检查 recordset
的属性,你会注意到 Source
属性中包含了等效的 SQL 查询语句。
SELECT MusicArtist, MusicAlbum, DocTitle FROM \"MyIndex\"..scope()
WHERE ((Contains(PerceivedType,'music') AND CONTAINS(MusicGenre,'\"Rock\"',1033))
为了填充我们计划用于筛选的列表(即流派、艺术家、专辑),我们理想中希望执行类似以下的查询
SELECT DISTINCT MusicGenre FROM MyIndex
不幸的是,MSN 桌面搜索查询引擎不支持 DISTINCT
这个 SQL 关键字。这意味着,为了获得我们音乐库中所有不同流派的列表,我们需要处理一个包含 MusicGenre
列的 resultset
,但这个结果集里每首音乐曲目都对应一行。所以,我们需要对结果进行后处理,以提取出不重复的流派集合。
为了将我们的结果数据绑定到支持数据的 WinForms 控件(如列表框、数据网格等),我们需要将 ADO recordset
转换为 .NET DataSet
。OleDbDataAdapter
类通过其 Fill()
方法提供了将 OLE DB recordset
转换为 .NET DataSet
所需的功能。
以下代码片段展示了如何将查询返回的 ADO recordset
转换为 .NET DataSet
,然后将其绑定到支持数据的控件上以显示结果。
OleDbDataAdapter dbAdapter = new OleDbDataAdapter();
DataSet ds = new DataSet();
dbAdapter.Fill(ds, rs, "MyIndex");
list.DataSource = ds.Tables[0];
事实证明,你可以在 DataSet
中的表上设置主键,然后让 OleDbDataAdapter
类在从 recordset
填充 DataTable
时,仅根据主键插入唯一的条目。
ds.Tables[0].PrimaryKey =
new DataColumn[] { ds.Tables[0].Columns[primaryKeyColumn] };
dbAdapter.FillLoadOption = LoadOption.Upsert;
有两个主要方法承担了大部分的实现工作。
private string BuildQuery(string[] categories, DataGridView[] listFilters)
private void LoadCategory(string categorySearch,
string[] columns, int primaryKeyColumn, DataGridView list)
BuildQuery()
方法接收一个类别字符串数组和一个 DataGridView
控件数组,这些控件用于显示要查询的类别,并包含用户在这些类别中的当前选择列表。
例如,要构建填充专辑列表所需的查询,可以看看 LoadAlbums()
方法的实现。
private void LoadAlbums()
{
string query = BuildQuery(new String[] { "genre", "artist" },
new DataGridView[] { listGenres, listArtists });
LoadCategory(query, new String[] { "MusicAlbum" }, 0, listAlbums);
}
在这种情况下,BuildQuery()
方法将返回一个类似于以下示例的查询字符串
kind:music genre:("rock") artists:("Cowboy Junkies" OR "Coldplay")
然后,LoadCategory()
方法执行从 BuildQuery()
返回的查询,使用提供的列名填充一个 DataSet
,并将其绑定到指定的 DataGridView
控件上。
private void LoadCategory(string categorySearch,
string[] columns, int primaryKeyColumn, DataGridView list)
{
StringBuilder columnList = new StringBuilder();
for (int iColumns = 0; iColumns < columns.Length; iColumns++)
{
if (iColumns > 0)
columnList.Append(",");
columnList.Append(columns[iColumns]);
}
_Recordset rs = msnDS.ExecuteQuery(categorySearch,
columnList.ToString(), null, null);
OleDbDataAdapter dbAdapter = new OleDbDataAdapter();
DataSet ds = new DataSet();
DataTable table = new DataTable("MyIndex");
foreach (string column in columns)
{
table.Columns.Add(column, typeof(string));
}
ds.Tables.Add(table);
if (primaryKeyColumn != -1)
{
ds.Tables[0].PrimaryKey =
new DataColumn[] { ds.Tables[0].Columns[primaryKeyColumn] };
dbAdapter.FillLoadOption = LoadOption.Upsert;
}
dbAdapter.Fill(ds, rs, "MyIndex");
list.DataSource = ds.Tables[0];
}
最后需要实现的代码是事件处理程序,用于处理列表触发的选择更改事件,这些事件需要刷新分类列表和音乐曲目列表。
private void listArtists_SelectionChanged(object sender, EventArgs e)
{
HandleSelectionChange((DataGridView)sender, delegate { LoadAlbums(); });
}
private void HandleSelectionChange(DataGridView list, ExecuteMe loader)
{
if (bLoaded && list.SelectedCells.Count > 0)
{
TimeMe(loader);
}
}
private void TimeMe(ExecuteMe exec)
{
Stopwatch sw = new Stopwatch();
sw.Start();
exec.Invoke();
sw.Stop();
DisplayQueryTime(sw.ElapsedMilliseconds);
}
包括空白行在内,总实现代码仅有 170 行。当前的实现是使用 Visual Studio 2005 Beta 2 构建的,因此需要 .NET 2.0 Beta 2 运行时。然而,它对 .NET 2.0 版本并没有特定的依赖,这个应用程序可以使用任何能够调用 COM 组件的语言和环境来实现。
性能
为了感受一下查询性能,我加入了代码来测量用户进行筛选选择时查询索引所需的时间。每次查询后,查询时间会显示在状态栏中。在我的系统上(一台 3GHz P4 电脑,音乐库包含 120 张专辑和 1700 首单曲),初始填充所有控件(包括所有音乐曲目的完整列表)大约需要一秒多一点的时间。
后续的选择操作,例如更改流派,耗时大约在 100-300 毫秒之间;而更改艺术家则耗时大约在 20-50 毫秒之间。
待办事项
目前这个应用程序只是一个用于测试 MSN 桌面搜索查询 API 的试验平台,并非一个功能齐全的音乐浏览器/播放器。如果你想把它变成一个音乐播放器,一种方法是托管 Windows Media Player 控件,并使用这个内嵌控件来播放所选的歌曲;或者你可以根据所选歌曲生成一个播放列表,并启动 Media Player 作为一个外部进程来播放这个列表。
此外,你可能还想美化用户界面,并在用于筛选的每个列表中加入一个“全部”选项。我正在考虑的另一个选择是,将此功能实现为一个带有 10 英尺用户界面(10 foot UI)的 Windows Media Center 插件,以提供一种在 Windows Media Center 中浏览和选择音乐的替代方式。
结论
随着现在加入了一个相当易于使用的查询 API,你可能会发现第三方应用程序会在其自己的用户界面中,基于 MSN 桌面搜索索引提供搜索功能。除了呈现一个模仿现有 MSN 界面的搜索和结果界面外,该查询 API 还允许你创建更适合特定应用领域的搜索和结果界面。