管理通用 Windows 项目

通用 Windows 应用是面向 Windows 8.1 和 Windows 电话 8.1 的应用,允许开发人员在两个平台上使用代码和其他资产。 共享代码和资源保存在共享项目中,而特定于平台的代码和资源保存在单独的项目中,一个用于 Windows,另一个用于 Windows 电话。 有关通用 Windows 应用的详细信息,请参阅 通用 Windows 应用。 管理项目的 Visual Studio 扩展应注意,通用 Windows 应用项目的结构与单平台应用不同。 本演练演示如何导航共享项目和管理共享项。

  1. 创建名为 TestUniversalProject 的 C# VSIX 项目。 (文件>新建>项目,然后 C#>Extensibility>Visual Studio 包)。 添加自定义命令项目项模板(在解决方案资源管理器上,右键单击项目节点并选择“添加新>”,然后转到“扩展性”。 将文件 命名为 TestUniversalProject

  2. 添加对 Microsoft.VisualStudio.Shell.Interop.12.1.DesignTime.dll 和 Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll(扩展部分中)的引用。

  3. 打开 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;
    
  4. TestUniversalProject类中添加指向“输出”窗口的专用字段。

    public sealed class TestUniversalProject
    {
        IVsOutputWindowPane output;
    . . .
    }
    
  5. 在 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));
    }
    
  6. ShowMessageBox 方法中删除现有代码:

    private void ShowMessageBox(object sender, EventArgs e)
    {
    }
    
  7. 获取 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;
        }
    }
    
  8. 查找共享项目。 共享项目是一个纯容器;它不生成或生成输出。 以下方法通过查找 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;
    }
    
  9. 在方法中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;
        }
    }
    
  10. 获取活动平台项目。 平台项目是包含特定于平台的代码和资源的项目。 以下方法使用新字段 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;
        }
    }
    
  11. 在方法中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");
        }
    }
    
  12. 循环访问平台项目。 以下方法从共享项目获取所有导入(平台)项目。

    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];
    }
    
  13. 在方法中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));
    }
    
  14. 更改活动平台项目。 以下方法使用 SetProperty..

    private int SetActiveProjectContext(IVsHierarchy hierarchy, IVsHierarchy activeProjectContext)
    {
        return hierarchy.SetProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID7.VSHPROPID_SharedItemContextHierarchy, activeProjectContext);
    }
    
  15. 在方法中 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');
    
  16. 现在试一试。按 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
    

管理平台项目中的共享项

  1. 在平台项目中查找共享项。 共享项目中的项以共享项的形式显示在平台项目中。 在解决方案资源管理器看不到它们,但可以遍视项目层次结构来查找它们。 以下方法将演练层次结构并收集所有共享项。 它可以选择输出每个项的描述文字。 共享项由新属性 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);
            }
        }
    }
    
  2. 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);
    
  3. 读取共享项。 共享项以隐藏链接文件的形式显示在平台项目中,你可以将所有属性作为普通链接文件读取。 以下代码读取第一个共享项的完整路径。

    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));
    
  4. 现在试一试。按 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
    

检测平台项目和共享项目中的更改

  1. 可以使用层次结构和项目事件来检测共享项目中的更改,就像可以用于平台项目一样。 但是,共享项目中的项目项不可见,这意味着当共享项目项发生更改时,某些事件不会触发。

    重命名项目中的文件时,请考虑事件序列:

    1. 文件名在磁盘上更改。

    2. 项目文件更新为包含文件的新名称。

      层次结构事件(例如,IVsHierarchyEvents)通常跟踪 UI 中显示的更改,如解决方案资源管理器所示。 层次结构事件考虑文件重命名操作,以包含文件删除,然后添加文件。 但是,当不可见项发生更改时,层次结构事件系统会触发事件 OnItemDeleted ,但不会触发事件 OnItemAdded 。 因此,如果重命名平台项目中的文件,则同时获得 OnItemDeletedOnItemAdded,但如果重命名共享项目中的文件,则只会 OnItemDeleted获得。

      若要跟踪项目项的更改,可以处理 DTE 项目项事件(在中找到 ProjectItemsEventsClass的事件)。 但是,如果要处理大量事件,则可以获得更好的性能处理事件 IVsTrackProjectDocuments2。 在本演练中,我们仅显示层次结构事件和 DTE 事件。 在此过程中,将事件侦听器添加到共享项目和平台项目。 然后,在共享项目中重命名一个文件,并在平台项目中重命名另一个文件时,可以看到针对每个重命名操作触发的事件。

      在此过程中,将事件侦听器添加到共享项目和平台项目。 然后,在共享项目中重命名一个文件,并在平台项目中重命名另一个文件时,可以看到针对每个重命名操作触发的事件。

  2. 添加事件侦听器。 向项目添加新的类文件并调用 HierarchyEventListener.cs

  3. 打开 HierarchyEventListener.cs 文件并添加以下 using 指令:

    using Microsoft.VisualStudio.Shell.Interop;
    using Microsoft.VisualStudio;
    using System.IO;
    
  4. HierarchyEventListener 类实现 IVsHierarchyEvents

    class HierarchyEventListener : IVsHierarchyEvents
    { }
    
  5. 如下面的代码所示,实现其成员 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;
        }
    }
    
  6. 在同一类中,为 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));
    }
    
  7. 注册层次结构事件。 需要单独注册要跟踪的每个项目。 在以下代码中添加 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);
    
  8. 注册 DTE 项目项事件 ItemRenamed。 在连接第二个侦听器后添加以下代码。

    // hook up DTE events for project items
    Events2 dteEvents = (Events2)dte.Events;
    dteEvents.ProjectItemsEvents.ItemRenamed += listener1.OnItemRenamed;
    
  9. 修改共享项。 不能修改平台项目中的共享项;相反,必须在共享项目中修改这些项的实际所有者。 可以在共享项目中 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));
        }
    }
    
  10. 在所有其他代码 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);
    
  11. 生成并运行该项目。 在实验实例中创建 C# 通用中心应用,转到“工具”菜单,然后单击“调用 TestUniversalProject,并在常规输出窗格中检查文本。 共享项目中第一项的名称(我们期望它是 App.xaml 文件)应更改,应会看到 ItemRenamed 事件已触发。 在这种情况下,由于重命名 App.xaml 也会重命名 App.xaml.cs,因此应看到四个事件(每个平台项目有两个)。 (DTE 事件不跟踪共享项目中的项。你应该看到两 OnItemDeleted 个事件(每个平台项目都有一个),但没有 OnItemAdded 事件。

  12. 现在尝试重命名平台项目中的文件,可以看到触发的事件的差异。 在调用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);
    
  13. 生成并运行该项目。 在实验实例中创建 C# 通用项目,转到“工具”菜单,然后单击“调用 TestUniversalProject,并在常规输出窗格中检查文本。 重命名平台项目中的文件后,应同时看到事件 OnItemAddedOnItemDeleted 事件。 由于更改文件不会更改其他文件,并且由于平台项目中项的更改不会传播到任何位置,因此每个事件中只有一个。