Export (0) Print
Expand All

Walkthrough: Managing Universal Windows Projects

Visual Studio 2013

By creating universal Windows app solutions, you can use shared code and resources to build both Windows 8.1 apps and Windows Phone 8.1 apps. Shared code and resources are kept in one project in the solution, and platform-specific code and resources are kept in separate projects, one for Windows and one for Windows Phone. For more information about universal Windows apps, see Universal Windows Apps.

If you have Visual Studio extensions that manage projects, make sure they're aware that the structure of universal Windows app projects differs from single-platform app projects. This walkthrough shows how to navigate a shared project and manage the shared items.

Important note Important

Universal Windows apps were introduced in Visual Studio 2013 Update 2.

To complete this walkthrough, you must install the Visual Studio 2013 SDK.

To install Visual Studio 2013 Update 2, see Visual Studio 2013 Update 2. The Visual Studio SDK for Update 2 is installed along with Visual Studio.

To navigate the shared project

  1. Use the Visual Studio Package template to create a project named TestUniversalProject. (File, New, Project, and then in the New Project dialog box, C#, Extensibility, Visual Studio Package.)

  2. On the Select VSPackage Options page in the Visual Studio Package Wizard, select Menu Command.

  3. In the Extensions section, add a reference to Microsoft.VisualStudio.Shell.Interop.DesignTime.12.1.dll. This assembly is included in Visual Studio 2013 Update 2.

  4. Open TestUniversalProjectPackage.cs and add these using statements:

    using EnvDTE;
    using EnvDTE80;
    using Microsoft.VisualStudio.PlatformUI;
    using Microsoft.Internal.VisualStudio.PlatformUI; 
    using System.Collections.Generic; 
    using System.IO;
    using System.Windows.Forms;
    
  5. In the TestUniversalProjectPackage class, add a private field that points to the Output window:

    public sealed class TestUniversalProjectPackage : Package
    {
            IVsOutputWindowPane output;
    . . .
    }
    
  6. Set the reference to the Output pane in the Initialize method:

    protected override void Initialize()
    {
        Debug.WriteLine (string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
        base.Initialize();
    
        // Add command handlers for the menu. (Commands must exist in the 
        // .vsct file.)
        OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
                    if ( null != mcs )
        {
            // Create the command for the menu item.
            CommandID menuCommandID = new CommandID(GuidList.guidTestUniversalProjectCmdSet, (int)PkgCmdIDList.cmdidMyCommand);
            MenuCommand menuItem = new MenuCommand(MenuItemCallback, menuCommandID );
            mcs.AddCommand( menuItem );
        }
    
        // Get a reference to the Output window.
        output = (IVsOutputWindowPane)this.GetService(typeof(SVsGeneralOutputWindowPane));
    }
    
  7. Remove the existing code from the MenuItemCallback method:

    private void MenuItemCallback(object sender, EventArgs e) 
    {
    }
    
  8. Get the DTE object, which we'll use for several different purposes in this walkthrough. Also, make sure that a solution is loaded when the menu button is chosen:

    private void MenuItemCallback(object sender, EventArgs e)
    { 
        var dte = (EnvDTE.DTE)this.GetService(typeof(EnvDTE.DTE));
        if (dte.Solution != null) 
        {
            . . .
        }
        else 
        {
            MessageBox.Show("No solution is open");
            return; 
        }
    }
    
  9. Find the shared project. The shared project is just a container; it isn't built and it doesn't produce outputs. The following method finds the first shared project in the solution by looking for the IVsHierarchy object that has the shared project capability:

    private IVsHierarchy FindSharedProject()
    {
        var sln = (IVsSolution)this.GetService(typeof(SVsSolution));
        Guid empty = Guid.Empty;
        IEnumHierarchies enumHiers;
    
    //Get 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;
    }
    
  10. In the MenuItemCallback method, output the caption of the shared project—that is, the project name that appears in Solution Explorer.

    private void MenuItemCallback(object sender, EventArgs e)
    {
        var dte = (DTE)this.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));
            }
            else
            {
                MessageBox.Show("Solution does not have a shared project");
                return;
            }
        }
        else
        {
            MessageBox.Show("No solution is open");
            return;
        }
    }
    
  11. Get the platform project that's active. Platform projects contain platform-specific code and resources. The following method uses the new field VSHPROPID_SharedItemContextHierarchy to get the active platform project:

    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;
        }
    }
    
  12. In the MenuItemCallback method, output the caption of the active platform project:

    private void MenuItemCallback(object sender, EventArgs e)
    {
        var dte = (DTE)this.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 does not have an active platform project");
                }   
            }
             else
            {
                MessageBox.Show("Solution does not have a shared project");
                return;
            }
        }
        else
        {
            MessageBox.Show("No solution is open");
            return;
        }
    }
    
  13. Iterate through the platform projects. This method gets the importing (platform) projects from the shared project:

    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;
            }
        }
    }
    
  14. In the MenuItemCallback method, output the caption of each platform project. Insert the following code after the line that outputs the caption of the active platform project. Only the platform projects that are loaded appear in this list:

    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));
    }
    
    Important noteImportant

    If the user has opened a C++ universal Windows app project in the experimental instance, the previous code throws an exception. This is a known issue. To avoid the exception, replace the foreach block with this:

    var importingProjects = sharedAssetsProject.EnumImporingProjects();
    for (int i = 0; i < importingProjects.Count; ++i)
    {
        yield return importingProjects.Item(i);
    } 
    
  15. Change the active platform project. The following method sets the active project by using SetProperty:

    private int SetActiveProjectContext(IVsHierarchy hierarchy, IVsHierarchy activeProjectContext)
    {
        return hierarchy.SetProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID7.VSHPROPID_SharedItemContextHierarchy, activeProjectContext);
    }
    
  16. In the MenuItemCallback method, change the active platform project. Insert this code in the foreach block:

    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  
        // active platform project, make it the active project. 
    
        if (!isActiveProjectSet && platformHier != activePlatformHier)
        {
            this.SetActiveProjectContext(sharedHier, platformHier);
            activePlatformHier = platformHier;
            isActiveProjectSet = true;
        }
    }
    output.OutputStringThreadSafe("Set active platform project: " + platformCaption +'\n');
    
  17. Now try it out. Press F5 to start the experimental instance. In the experimental instance, create a C# universal Windows app hub project. (File, New, Project, and then in the New Project dialog box, Visual C#, Store Apps, Universal Apps, Hub App.) After the solution is loaded, on the menu bar, choose Tools, My Command name, and then examine the text in the Output window. It should look something like this:

    Shared project: HubApp.Shared
    
    Active platform project: HubApp.Windows
    
    Platform projects:
    
     * HubApp.Windows
    
     * HubApp.WindowsPhone
    Set active platform project:
    HubApp.WindowsPhone

To manage the shared items in a platform project

  1. Find the shared items in a platform project. The items in the shared project appear in the platform project as shared items. They don't appear in Solution Explorer, but you can walk the project hierarchy to find them. The following method walks the hierarchy and collects the shared items. As an option, it outputs the caption of each item. The shared items are identified by the new property 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, isSharedProject, 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, isSharedProject, printItems);
                    }
                }
            }
    
    private void InspectHierarchyItems(IVsHierarchy hier, uint itemid, int level, List<uint> sharedItemIds)
    {
        string caption = HierarchyUtilities.GetHierarchyProperty<string>(hier, itemid, (int)__VSHPROPID.VSHPROPID_Caption);
        output.OutputStringThreadSafe(string.Format("{0}{1}\n", new string('\t', level), caption));
    
        bool isSharedItem;
        if (HierarchyUtilities.TryGetHierarchyProperty(hier, itemid, (int)__VSHPROPID7.VSHPROPID_IsSharedItem, out isSharedItem)
            && isSharedItem)
        {
            sharedItemIds.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, sharedItemIds);
    
             while (HierarchyUtilities.TryGetHierarchyProperty(hier, child,
                 (int)__VSHPROPID.VSHPROPID_NextSibling, Unbox.AsUInt32, out child)
                && child != (uint)VSConstants.VSITEMID.Nil)
            {
                this.InspectHierarchyItems(hier, child, level + 1, sharedItemIds);
            }
        }
    }
    
  2. In the MenuItemCallback method, add the following code to walk the platform project hierarchy items. Insert it after the foreach block:

    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. Read the shared items. The shared items appear in the platform project as hidden linked files. You can read the properties as ordinary linked files. The following method reads the full path of the first shared item:

    var sharedItemId = sharedItemIds[0];
    string fullPath;
    ErrorHandler.ThrowOnFailure(((IVsProject)activePlatformHier).GetMkDocument(sharedItemId, out fullPath));
    output.OutputStringThreadSafe(string.Format("Full path of shared item: {0}\n", fullPath));
    
  4. Now try it out. Press F5 to start the experimental instance and there create a C# universal Windows app hub project. (File, New, Project, and then in the New Project dialog box, Visual C#, Store Apps, Universal Apps, Hub App.) On the menu bar, choose Tools, My Command name, and then examine the text in the Output pane. It should look like this:

    Shared project: HubApp.Shared
    Active platform project: HubApp.Windows
    Platform projects:
     * HubApp.Windows
     * HubApp.WindowsPhone
    Set active platform 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
    

To detect changes in platform projects and shared projects

  1. You can use hierarchy and project events to detect changes in platform projects and in shared projects. However, because project items in a shared project are not displayed, certain events do not fire when the items are changed.

    Consider the sequence of events when a file in a project is renamed:

    1. The file name is changed on disk.

    2. The project file is updated to include the new file name.

    Hierarchy events—for example, IVsHierarchyEvents—generally track the changes displayed in Solution Explorer and other UI. A file rename operation for a displayed item fires two hierarchy events—a OnItemDeleted file-deletion event and then a OnItemAdded file-addition event. However, when the name of an invisible item is changed, the hierarchy event system fires OnItemDeleted but not OnItemAdded. If you rename a file in a platform project, you get both events, but if you rename a file in a shared project, you get only the file-deletion event.

    To track changes in project items, in most cases you can handle the project-item events in ProjectItemsEventsClass. (If you're handling large numbers of events, you may get better performance by handling the events in IVsTrackProjectDocuments2.) The following steps show how to use the hierarchy events and the DTE events. You add event listeners to a shared project and a platform project. Then, when you rename a file in a shared project and another file in a platform project, you can observe the events that are fired.

  2. Add an event listener. Add a new class file to the project and name it HierarchyEventListener.cs.

  3. Open HierarchyEventListener.cs and add these using statements:

    using Microsoft.VisualStudio.Shell.Interop;
    using Microsoft.VisualStudio;
    using System.IO;
    
  4. Have the HierarchyEventListener class implement IVsHierarchyEvents:

    class HierarchyEventListener : IVsHierarchyEvents
    { }
    
  5. Implement the members of 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. In the same class, add another event handler for the DTE event ItemRenamed, which occurs whenever a project item is renamed.

    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. Sign up for the hierarchy events. You sign up separately for every project you are tracking. Add the following code in MenuItemCallback—the first part is for the shared project and the second part is for one of the platform projects:

    // 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 platform project.
    HierarchyEventListener listener2 = new HierarchyEventListener(activePlatformHier, output);
    uint cookie2;
    activePlatformHier.AdviseHierarchyEvents(listener2, out cookie2);
    
  8. Sign up for the DTE project-item event ItemRenamed. Add the following code after the code for the second listener:

    // Hook up DTE events for project items.
    Events2 dteEvents = (Events2)dte.Events;
    dteEvents.ProjectItemsEvents.ItemRenamed += listener1.OnItemRenamed;
    
  9. Modify the shared item. Shared items can't be modified in a platform project; instead, you modify them in the shared project because it actually owns them. By giving IsDocumentInProject the full path of a shared item, you can get its ID in the shared project, and then you can modify the item. The change is propagated to the platform projects.

    Important note Important

    Make sure a project item is a shared item before you modify it.

    This method modifies the name of a project-item file:

    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);
            ErrorHandler.ThrowOnFailure(project.SetProperty(projectItemID, (int)__VSHPROPID.VSHPROPID_EditLabel, name));
            output.OutputStringThreadSafe(string.Format("Renamed {0} to {1}\n", path,name));
        }
    }
    
  10. Now call this method to modify the file name the item in the shared project. In MenuItemCallback, after the code that gets the full path of the item in the shared project, insert this code:

    // Change the file name of an item in a shared project.
    var sharedItemIds = new List<uint>();
    this.InspectHierarchyItems(activePlatformHier, (uint)VSConstants.VSITEMID.Root, 1, sharedItemIds, true, true);
    
    uint sharedItemId = sharedItemIds[0];
    string fullPath;
     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. Build and run the project. In the experimental instance, create a C# universal Windows app hub project, choose Tools, My Command name, and examine the text in the Output window. The name of the first item in the shared project—typically the App.xaml file—should be changed, and the ItemRenamed event should have fired. In this case, because renaming App.xaml causes App.xaml.cs to be renamed as well, four events should appear—two for each platform project. (DTE events do not track the items in the shared project.) Two OnItemDeleted events should appear, one for each platform project, but no OnItemAdded events.

  12. Now examine the difference in the events that get fired when you rename a file in a platform project. Add the following code after the call to ModifyFileName:

    // 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. Build and run the project. In the experimental instance, create a C# universal Windows app hub project, choose Tools, My Command name, and examine the Output window. After the file in the platform project is renamed, only an OnItemAdded event and an OnItemDeleted event should appear because this change caused no other files to be changed and because changes to items in a platform project don't get propagated anywhere.

Here's all of the code for TestUniversalProjectPackage.cs:

using System;
using System.Diagnostics;
using System.Globalization;
using System.Runtime.InteropServices;
using System.ComponentModel.Design;
using Microsoft.Win32;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.OLE.Interop;
using Microsoft.VisualStudio.Shell;

using Microsoft.VisualStudio.PlatformUI;
using Microsoft.Internal.VisualStudio.PlatformUI;
using System.Collections.Generic;
using System.IO;
using System.Windows.Forms;
using EnvDTE;
using EnvDTE80;

namespace Company.TestUniversalProject
    [InstalledProductRegistration("#110", "#112", "1.0", IconResourceID = 400)]
    [ProvideMenuResource("Menus.ctmenu", 1)]
    [Guid(GuidList.guidTestUniversalProjectPkgString)]
    public sealed class TestUniversalProjectPackage : Package
    {
        // A reference to the Output window, where the walkthrough reports
        // different actions.
        IVsOutputWindowPane output;
        public TestUniversalProjectPackage()
        {
            Debug.WriteLine(string.Format(CultureInfo.CurrentCulture, "Entering constructor for: {0}", this.ToString()));
        }
        protected override void Initialize()
        {
            Debug.WriteLine (string.Format(CultureInfo.CurrentCulture, "Entering Initialize() of: {0}", this.ToString()));
            base.Initialize();

            // Add command handlers for the menu. (Commands must exist in the 
            // .vsct file.)
            OleMenuCommandService mcs = GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
            if ( null != mcs )
            {
                // Create the command for the menu item.
                CommandID menuCommandID = new CommandID(GuidList.guidTestUniversalProjectCmdSet, (int)PkgCmdIDList.cmdidMyCommand);
                MenuCommand menuItem = new MenuCommand(MenuItemCallback, menuCommandID );
                mcs.AddCommand( menuItem );
            }

            // Get a reference to the Output window.
            output = (IVsOutputWindowPane)this.GetService(typeof(SVsGeneralOutputWindowPane));
        }
        #endregion

        /// <summary>
        /// This function is the callback that's used to execute a command 
        /// when a menu item is clicked.
        /// See the Initialize method to see how the menu item is associated 
        /// with this function by using
 the OleMenuCommandService service 
        /// and the MenuCommand class.
        /// </summary>
        private void MenuItemCallback(object sender, EventArgs e)
        {
            var dte = (EnvDTE.DTE)this.GetService(typeof(EnvDTE.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));

                    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));

                        output.OutputStringThreadSafe("Platform projects:\n");

                        IEnumerable<IVsHierarchy> projects = this.EnumImportingProjects(sharedHier);

                        bool foundInactiveProject = 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 not the current active 
                            // platform project, set it to be the 
                            // active project.
                            if (!foundInactiveProject && platformHier != activePlatformHier)
                            {
                                this.SetActiveProjectContext(sharedHier, platformHier);
                                activePlatformHier = platformHier;
                                foundInactiveProject = true;
                            }
                        }

                        output.OutputStringThreadSafe("Set active platform project: " + platformCaption +'\n');

                        // 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);

                        // Sign up for events on the active project.
                        HierarchyEventListener listener2 = new HierarchyEventListener(activePlatformHier, output);
                        uint cookie2;
                        activePlatformHier.AdviseHierarchyEvents(listener2, out cookie2);

                        // Hook up DTE events for project items.
                        Events2 dteEvents = (Events2)dte.Events;
                        dteEvents.ProjectItemsEvents.ItemRenamed += listener1.OnItemRenamed;

                        // Change the file name of an item in a shared 
                        // project.
                        var sharedItemIds = new List<uint>();
                        this.InspectHierarchyItems(activePlatformHier, (uint)VSConstants.VSITEMID.Root, 1, sharedItemIds, true, true);

                        uint sharedItemId = sharedItemIds[0];
                        string fullPath;
                        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);

                        // 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);
                    }
                    else
                    {
                        MessageBox.Show("Shared project does not have an active platform project.");
                    }   
                }
                else
                {
                    MessageBox.Show("Solution does not have a shared project.");
                    return;
                }
            }
            else                
            {
                MessageBox.Show("No solution is open.");
                return;
            }
        }

        private IVsHierarchy FindSharedProject()
        {
            var sln = (IVsSolution)this.GetService(typeof(SVsSolution));
            Guid empty = Guid.Empty;
            IEnumHierarchies enumHiers;
            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;
        }

        public 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;
            }
        }

        private int SetActiveProjectContext(IVsHierarchy hierarchy, IVsHierarchy activeProjectContext)
        {
            return hierarchy.SetProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID7.VSHPROPID_SharedItemContextHierarchy, activeProjectContext);
        }

        public 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;
                }
            }
        }

        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 == isSharedProject))
            {
                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, isSharedProject, 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, isSharedProject, printItems);
                }
            }
        }

        private void ModifyFileNameInProject(IVsHierarchy project, string path)
        {  
            uint itemId;
            int found;
            VSDOCUMENTPRIORITY[] priority = new VSDOCUMENTPRIORITY[1];
            if (ErrorHandler.Succeeded(((IVsProject)project).IsDocumentInProject(path, out found, priority, out itemId))
                && found != 0)
            {
                var newName = DateTime.Now.Ticks.ToString() + Path.GetExtension(path);
                ErrorHandler.ThrowOnFailure(project.SetProperty(itemId, (int)__VSHPROPID.VSHPROPID_EditLabel, newName));
                output.OutputStringThreadSafe(string.Format("Renamed {0} to {1}\n", path, newName));
            }
        }
    }
}
Show:
© 2014 Microsoft