管理通用 Windows 项目
通用 Windows 应用是面向 Windows 8.1 和 Windows 电话 8.1 的应用,允许开发人员在两个平台上使用代码和其他资产。 共享代码和资源保存在共享项目中,而特定于平台的代码和资源保存在单独的项目中,一个用于 Windows,另一个用于 Windows 电话。 有关通用 Windows 应用的详细信息,请参阅 通用 Windows 应用。 管理项目的 Visual Studio 扩展应注意,通用 Windows 应用项目的结构与单平台应用不同。 本演练演示如何导航共享项目和管理共享项。
导航共享项目
创建名为 TestUniversalProject 的 C# VSIX 项目。 (文件>新建>项目,然后 C#>Extensibility>Visual Studio 包)。 添加自定义命令项目项模板(在解决方案资源管理器上,右键单击项目节点并选择“添加新>项”,然后转到“扩展性”。 将文件 命名为 TestUniversalProject。
添加对 Microsoft.VisualStudio.Shell.Interop.12.1.DesignTime.dll 和 Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll(扩展部分中)的引用。
打开 TestUniversalProject.cs 并添加以下
using
指令:using EnvDTE; using EnvDTE80; using Microsoft.VisualStudio; using Microsoft.VisualStudio.PlatformUI; using Microsoft.Internal.VisualStudio.PlatformUI; using System.Collections.Generic; using System.IO; using System.Windows.Forms;
在
TestUniversalProject
类中添加指向“输出”窗口的专用字段。public sealed class TestUniversalProject { IVsOutputWindowPane output; . . . }
在 TestUniversalProject 构造函数中设置对输出窗格的引用:
private TestUniversalProject(Package package) { if (package == null) { throw new ArgumentNullException("package"); } this.package = package; OleMenuCommandService commandService = this.ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService; if (commandService != null) { CommandID menuCommandID = new CommandID(MenuGroup, CommandId); EventHandler eventHandler = this.ShowMessageBox; MenuCommand menuItem = new MenuCommand(eventHandler, menuCommandID); commandService.AddCommand(menuItem); } // get a reference to the Output window output = (IVsOutputWindowPane)ServiceProvider.GetService(typeof(SVsGeneralOutputWindowPane)); }
从
ShowMessageBox
方法中删除现有代码:private void ShowMessageBox(object sender, EventArgs e) { }
获取 DTE 对象,我们将在本演练中用于多个不同目的。 此外,请确保在单击菜单按钮时加载解决方案。
private void ShowMessageBox(object sender, EventArgs e) { var dte = (EnvDTE.DTE)this.ServiceProvider.GetService(typeof(EnvDTE.DTE)); if (dte.Solution != null) { . . . } else { MessageBox.Show("No solution is open"); return; } }
查找共享项目。 共享项目是一个纯容器;它不生成或生成输出。 以下方法通过查找 IVsHierarchy 具有共享项目功能的对象查找解决方案中的第一个共享项目。
private IVsHierarchy FindSharedProject() { var sln = (IVsSolution)this.ServiceProvider.GetService(typeof(SVsSolution)); Guid empty = Guid.Empty; IEnumHierarchies enumHiers; //get all the projects in the solution ErrorHandler.ThrowOnFailure(sln.GetProjectEnum((uint)__VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION, ref empty, out enumHiers)); foreach (IVsHierarchy hier in ComUtilities.EnumerableFrom(enumHiers)) { if (PackageUtilities.IsCapabilityMatch(hier, "SharedAssetsProject")) { return hier; } } return null; }
在方法中
ShowMessageBox
,输出共享项目的描述文字(显示在解决方案资源管理器中的项目名称)。private void ShowMessageBox(object sender, EventArgs e) { var dte = (DTE)this.ServiceProvider.GetService(typeof(DTE)); if (dte.Solution != null) { var sharedHier = this.FindSharedProject(); if (sharedHier != null) { string sharedCaption = HierarchyUtilities.GetHierarchyProperty<string>(sharedHier, (uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_Caption); output.OutputStringThreadSafe(string.Format("Found shared project: {0}\n", sharedCaption)); } else { MessageBox.Show("Solution has no shared project"); return; } } else { MessageBox.Show("No solution is open"); return; } }
获取活动平台项目。 平台项目是包含特定于平台的代码和资源的项目。 以下方法使用新字段 VSHPROPID_SharedItemContextHierarchy 获取活动平台项目。
private IVsHierarchy GetActiveProjectContext(IVsHierarchy hierarchy) { IVsHierarchy activeProjectContext; if (HierarchyUtilities.TryGetHierarchyProperty(hierarchy, (uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID7.VSHPROPID_SharedItemContextHierarchy, out activeProjectContext)) { return activeProjectContext; } else { return null; } }
在方法中
ShowMessageBox
,输出活动平台项目的描述文字。private void ShowMessageBox(object sender, EventArgs e) { var dte = (DTE)this.ServiceProvider.GetService(typeof(DTE)); if (dte.Solution != null) { var sharedHier = this.FindSharedProject(); if (sharedHier != null) { string sharedCaption = HierarchyUtilities.GetHierarchyProperty<string>(sharedHier, (uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_Caption); output.OutputStringThreadSafe(string.Format("Shared project: {0}\n", sharedCaption)); var activePlatformHier = this.GetActiveProjectContext(sharedHier); if (activePlatformHier != null) { string activeCaption = HierarchyUtilities.GetHierarchyProperty<string>(activePlatformHier, (uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_Caption); output.OutputStringThreadSafe(string.Format("Active platform project: {0}\n", activeCaption)); } else { MessageBox.Show("Shared project has no active platform project"); } } else { MessageBox.Show("Solution has no shared project"); } } else { MessageBox.Show("No solution is open"); } }
循环访问平台项目。 以下方法从共享项目获取所有导入(平台)项目。
private IEnumerable<IVsHierarchy> EnumImportingProjects(IVsHierarchy hierarchy) { IVsSharedAssetsProject sharedAssetsProject; if (HierarchyUtilities.TryGetHierarchyProperty(hierarchy, (uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID7.VSHPROPID_SharedAssetsProject, out sharedAssetsProject) && sharedAssetsProject != null) { foreach (IVsHierarchy importingProject in sharedAssetsProject.EnumImportingProjects()) { yield return importingProject; } } }
重要
如果用户在实验实例中打开了 C++ 通用 Windows 应用项目,上述代码将引发异常。 这是已知问题。 若要避免出现异常,请将上面的块替换为
foreach
以下内容:var importingProjects = sharedAssetsProject.EnumImportingProjects(); for (int i = 0; i < importingProjects.Count; ++i) { yield return importingProjects[i]; }
在方法中
ShowMessageBox
,输出每个平台项目的描述文字。 在输出活动平台项目的描述文字行后面插入以下代码。 仅加载的平台项目才会出现在此列表中。output.OutputStringThreadSafe("Platform projects:\n"); IEnumerable<IVsHierarchy> projects = this.EnumImportingProjects(sharedHier); bool isActiveProjectSet = false; foreach (IVsHierarchy platformHier in projects) { string platformCaption = HierarchyUtilities.GetHierarchyProperty<string>(platformHier, (uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_Caption); output.OutputStringThreadSafe(string.Format(" * {0}\n", platformCaption)); }
更改活动平台项目。 以下方法使用 SetProperty..
private int SetActiveProjectContext(IVsHierarchy hierarchy, IVsHierarchy activeProjectContext) { return hierarchy.SetProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID7.VSHPROPID_SharedItemContextHierarchy, activeProjectContext); }
在方法中
ShowMessageBox
,更改活动平台项目。 将此代码插入块中foreach
。bool isActiveProjectSet = false; string platformCaption = null; foreach (IVsHierarchy platformHier in projects) { platformCaption = HierarchyUtilities.GetHierarchyProperty<string>(platformHier, (uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_Caption); output.OutputStringThreadSafe(string.Format(" * {0}\n", platformCaption)); // if this project is neither the shared project nor the current active platform project, // set it to be the active project if (!isActiveProjectSet && platformHier != activePlatformHier) { this.SetActiveProjectContext(sharedHier, platformHier); activePlatformHier = platformHier; isActiveProjectSet = true; } } output.OutputStringThreadSafe("set active project: " + platformCaption +'\n');
现在试一试。按 F5 启动实验实例。 在实验实例中创建 C# 通用中心应用项目(在“新建项目”对话框中,Visual C#>Windows>8>通用>中心应用)。 加载解决方案后,转到“工具”菜单,然后单击“调用 TestUniversalProject”,然后在“输出”窗格中检查文本。 会看到下面这样的内容:
Found shared project: HubApp.Shared The active platform project: HubApp.Windows Platform projects: * HubApp.Windows * HubApp.WindowsPhone set active project: HubApp.WindowsPhone
管理平台项目中的共享项
在平台项目中查找共享项。 共享项目中的项以共享项的形式显示在平台项目中。 在解决方案资源管理器中看不到它们,但可以遍视项目层次结构来查找它们。 以下方法将演练层次结构并收集所有共享项。 它可以选择输出每个项的描述文字。 共享项由新属性 VSHPROPID_IsSharedItem标识。
private void InspectHierarchyItems(IVsHierarchy hier, uint itemid, int level, List<uint> itemIds, bool getSharedItems, bool printItems) { string caption = HierarchyUtilities.GetHierarchyProperty<string>(hier, itemid, (int)__VSHPROPID.VSHPROPID_Caption); if (printItems) output.OutputStringThreadSafe(string.Format("{0}{1}\n", new string('\t', level), caption)); // if getSharedItems is true, inspect only shared items; if it's false, inspect only unshared items bool isSharedItem; if (HierarchyUtilities.TryGetHierarchyProperty(hier, itemid, (int)__VSHPROPID7.VSHPROPID_IsSharedItem, out isSharedItem) && (isSharedItem == getSharedItems)) { itemIds.Add(itemid); } uint child; if (HierarchyUtilities.TryGetHierarchyProperty(hier, itemid, (int)__VSHPROPID.VSHPROPID_FirstChild, Unbox.AsUInt32, out child) && child != (uint)VSConstants.VSITEMID.Nil) { this.InspectHierarchyItems(hier, child, level + 1, itemIds, isSharedItem, printItems); while (HierarchyUtilities.TryGetHierarchyProperty(hier, child, (int)__VSHPROPID.VSHPROPID_NextSibling, Unbox.AsUInt32, out child) && child != (uint)VSConstants.VSITEMID.Nil) { this.InspectHierarchyItems(hier, child, level + 1, itemIds, isSharedItem, printItems); } } }
在
ShowMessageBox
方法中,添加以下代码以演练平台项目层次结构项。 将其插入块中foreach
。output.OutputStringThreadSafe("Walk the active platform project:\n"); var sharedItemIds = new List<uint>(); this.InspectHierarchyItems(activePlatformHier, (uint)VSConstants.VSITEMID.Root, 1, sharedItemIds, true, true);
读取共享项。 共享项以隐藏链接文件的形式显示在平台项目中,你可以将所有属性作为普通链接文件读取。 以下代码读取第一个共享项的完整路径。
var sharedItemId = sharedItemIds[0]; string fullPath; ErrorHandler.ThrowOnFailure(((IVsProject)activePlatformHier).GetMkDocument(sharedItemId, out fullPath)); output.OutputStringThreadSafe(string.Format("Shared item full path: {0}\n", fullPath));
现在试一试。按 F5 启动实验实例。 在实验实例中创建 C# 通用中心应用项目(在“新建项目”对话框中,Visual C#>Windows>8>通用>中心应用)转到“工具”菜单,然后单击“调用 TestUniversalProject”,然后在“输出”窗格中检查文本。 会看到下面这样的内容:
Found shared project: HubApp.Shared The active platform project: HubApp.Windows Platform projects: * HubApp.Windows * HubApp.WindowsPhone set active project: HubApp.WindowsPhone Walk the active platform project: HubApp.WindowsPhone <HubApp.Shared> App.xaml App.xaml.cs Assets DarkGray.png LightGray.png MediumGray.png Common NavigationHelper.cs ObservableDictionary.cs RelayCommand.cs SuspensionManager.cs DataModel SampleData.json SampleDataSource.cs HubApp.Shared.projitems Strings en-US Resources.resw Assets HubBackground.theme-dark.png HubBackground.theme-light.png Logo.scale-240.png SmallLogo.scale-240.png SplashScreen.scale-240.png Square71x71Logo.scale-240.png StoreLogo.scale-240.png WideLogo.scale-240.png HubPage.xaml HubPage.xaml.cs ItemPage.xaml ItemPage.xaml.cs Package.appxmanifest Properties AssemblyInfo.cs References .NET for Windows Store apps HubApp.Shared Windows Phone 8.1 SectionPage.xaml SectionPage.xaml.cs
检测平台项目和共享项目中的更改
可以使用层次结构和项目事件来检测共享项目中的更改,就像可以用于平台项目一样。 但是,共享项目中的项目项不可见,这意味着当共享项目项发生更改时,某些事件不会触发。
重命名项目中的文件时,请考虑事件序列:
文件名在磁盘上更改。
项目文件更新为包含文件的新名称。
层次结构事件(例如,IVsHierarchyEvents)通常跟踪 UI 中显示的更改,如解决方案资源管理器所示。 层次结构事件考虑文件重命名操作,以包含文件删除,然后添加文件。 但是,当不可见项发生更改时,层次结构事件系统会触发事件 OnItemDeleted ,但不会触发事件 OnItemAdded 。 因此,如果重命名平台项目中的文件,则同时获得 OnItemDeleted 和 OnItemAdded,但如果重命名共享项目中的文件,则只会 OnItemDeleted获得。
若要跟踪项目项的更改,可以处理 DTE 项目项事件(在中找到 ProjectItemsEventsClass的事件)。 但是,如果要处理大量事件,则可以获得更好的性能处理事件 IVsTrackProjectDocuments2。 在本演练中,我们仅显示层次结构事件和 DTE 事件。 在此过程中,将事件侦听器添加到共享项目和平台项目。 然后,在共享项目中重命名一个文件,并在平台项目中重命名另一个文件时,可以看到针对每个重命名操作触发的事件。
在此过程中,将事件侦听器添加到共享项目和平台项目。 然后,在共享项目中重命名一个文件,并在平台项目中重命名另一个文件时,可以看到针对每个重命名操作触发的事件。
添加事件侦听器。 向项目添加新的类文件并调用 HierarchyEventListener.cs。
打开 HierarchyEventListener.cs 文件并添加以下 using 指令:
using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio; using System.IO;
让
HierarchyEventListener
类实现 IVsHierarchyEvents:class HierarchyEventListener : IVsHierarchyEvents { }
如下面的代码所示,实现其成员 IVsHierarchyEvents。
class HierarchyEventListener : IVsHierarchyEvents { private IVsHierarchy hierarchy; IVsOutputWindowPane output; internal HierarchyEventListener(IVsHierarchy hierarchy, IVsOutputWindowPane outputWindow) { this.hierarchy = hierarchy; this.output = outputWindow; } int IVsHierarchyEvents.OnInvalidateIcon(IntPtr hIcon) { return VSConstants.S_OK; } int IVsHierarchyEvents.OnInvalidateItems(uint itemIDParent) { return VSConstants.S_OK; } int IVsHierarchyEvents.OnItemAdded(uint itemIDParent, uint itemIDSiblingPrev, uint itemIDAdded) { output.OutputStringThreadSafe("IVsHierarchyEvents.OnItemAdded: " + itemIDAdded + "\n"); return VSConstants.S_OK; } int IVsHierarchyEvents.OnItemDeleted(uint itemID) { output.OutputStringThreadSafe("IVsHierarchyEvents.OnItemDeleted: " + itemID + "\n"); return VSConstants.S_OK; } int IVsHierarchyEvents.OnItemsAppended(uint itemIDParent) { output.OutputStringThreadSafe("IVsHierarchyEvents.OnItemsAppended\n"); return VSConstants.S_OK; } int IVsHierarchyEvents.OnPropertyChanged(uint itemID, int propID, uint flags) { output.OutputStringThreadSafe("IVsHierarchyEvents.OnPropertyChanged: item ID " + itemID + "\n"); return VSConstants.S_OK; } }
在同一类中,为 DTE 事件 ItemRenamed添加另一个事件处理程序,每当重命名项目项时发生。
public void OnItemRenamed(EnvDTE.ProjectItem projItem, string oldName) { output.OutputStringThreadSafe(string.Format("[Event] Renamed {0} to {1} in project {2}\n", oldName, Path.GetFileName(projItem.get_FileNames(1)), projItem.ContainingProject.Name)); }
注册层次结构事件。 需要单独注册要跟踪的每个项目。 在以下代码中添加
ShowMessageBox
以下代码,一个用于共享项目,另一个用于平台项目。// hook up the event listener for hierarchy events on the shared project HierarchyEventListener listener1 = new HierarchyEventListener(sharedHier, output); uint cookie1; sharedHier.AdviseHierarchyEvents(listener1, out cookie1); // hook up the event listener for hierarchy events on the active project HierarchyEventListener listener2 = new HierarchyEventListener(activePlatformHier, output); uint cookie2; activePlatformHier.AdviseHierarchyEvents(listener2, out cookie2);
注册 DTE 项目项事件 ItemRenamed。 在连接第二个侦听器后添加以下代码。
// hook up DTE events for project items Events2 dteEvents = (Events2)dte.Events; dteEvents.ProjectItemsEvents.ItemRenamed += listener1.OnItemRenamed;
修改共享项。 不能修改平台项目中的共享项;相反,必须在共享项目中修改这些项的实际所有者。 可以在共享项目中 IsDocumentInProject获取相应的项 ID,并为其提供共享项的完整路径。 然后,可以修改共享项。 更改将传播到平台项目。
重要
在修改项目项之前,应确定项目项是否为共享项。
以下方法修改项目项文件的名称。
private void ModifyFileNameInProject(IVsHierarchy project, string path) { int found; uint projectItemID; VSDOCUMENTPRIORITY[] priority = new VSDOCUMENTPRIORITY[1]; if (ErrorHandler.Succeeded(((IVsProject)project).IsDocumentInProject(path, out found, priority, out projectItemID)) && found != 0) { var name = DateTime.Now.Ticks.ToString() + Path.GetExtension(path); project.SetProperty(projectItemID, (int)__VSHPROPID.VSHPROPID_EditLabel, name); output.OutputStringThreadSafe(string.Format("Renamed {0} to {1}\n", path,name)); } }
在所有其他代码
ShowMessageBox
之后调用此方法,以修改共享项目中的项的文件名。 在获取共享项目中项的完整路径的代码后面插入此代码。// change the file name of an item in a shared project this.InspectHierarchyItems(activePlatformHier, (uint)VSConstants.VSITEMID.Root, 1, sharedItemIds, true, true); ErrorHandler.ThrowOnFailure(((IVsProject)activePlatformHier).GetMkDocument(sharedItemId, out fullPath)); output.OutputStringThreadSafe(string.Format("Shared project item ID = {0}, full path = {1}\n", sharedItemId, fullPath)); this.ModifyFileNameInProject(sharedHier, fullPath);
生成并运行该项目。 在实验实例中创建 C# 通用中心应用,转到“工具”菜单,然后单击“调用 TestUniversalProject”,并在常规输出窗格中检查文本。 共享项目中第一项的名称(我们期望它是 App.xaml 文件)应更改,应会看到 ItemRenamed 事件已触发。 在这种情况下,由于重命名 App.xaml 也会重命名 App.xaml.cs,因此应看到四个事件(每个平台项目有两个)。 (DTE 事件不跟踪共享项目中的项。你应该看到两 OnItemDeleted 个事件(每个平台项目都有一个),但没有 OnItemAdded 事件。
现在尝试重命名平台项目中的文件,可以看到触发的事件的差异。 在调用
ModifyFileName
后添加以下代码ShowMessageBox
。// change the file name of an item in a platform project var unsharedItemIds = new List<uint>(); this.InspectHierarchyItems(activePlatformHier, (uint)VSConstants.VSITEMID.Root, 1, unsharedItemIds, false, false); var unsharedItemId = unsharedItemIds[0]; string unsharedPath; ErrorHandler.ThrowOnFailure(((IVsProject)activePlatformHier).GetMkDocument(unsharedItemId, out unsharedPath)); output.OutputStringThreadSafe(string.Format("Platform project item ID = {0}, full path = {1}\n", unsharedItemId, unsharedPath)); this.ModifyFileNameInProject(activePlatformHier, unsharedPath);
生成并运行该项目。 在实验实例中创建 C# 通用项目,转到“工具”菜单,然后单击“调用 TestUniversalProject”,并在常规输出窗格中检查文本。 重命名平台项目中的文件后,应同时看到事件 OnItemAdded 和 OnItemDeleted 事件。 由于更改文件不会更改其他文件,并且由于平台项目中项的更改不会传播到任何位置,因此每个事件中只有一个。