共用方式為


管理通用 Windows 專案

通用 Windows 應用程式是以 Windows 8.1 和 Windows Phone 8.1 為目標的應用程式,可讓開發人員在兩個平台上使用程式碼和其他資產。 共用程式碼和資源會保留在共用專案中,而平台特定的程式碼和資源則保留在個別專案中,一個用於 Windows,另一個則用於 Windows Phone。 如需通用 Windows 應用程式的詳細資訊,請參閱 通用 Windows 應用程式。 管理專案的 Visual Studio 延伸模組應該注意通用 Windows 應用程式專案的結構與單一平台應用程式不同。 本逐步解說展示如何瀏覽共用專案及管理共用項目。

  1. 建立名為 TestUniversalProject 的 C# VSIX 專案。 (檔案>新建>專案 然後是 C#>擴充性>Visual Studio 套件)。 新增 [自訂命令] 專案項目範本 (在 [方案總管] 上,以滑鼠右鍵按一下專案節點,然後選取 [新增新項目]>,然後移至 [擴充性])。 將檔案命名為 TestUniversalProject

  2. 新增 Microsoft.VisualStudio.Shell.Interop.12.1.DesignTime.dllMicrosoft.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. 逐一查看平台專案。 下列方法會從共用專案取得所有匯入 (platform) 專案。

    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>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>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 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 取得共用專案中對應項目識別碼,並為其提供共用項目的完整路徑。 然後,您可以修改共用項目。 變更會傳播至平台專案。

    重要

    您應該先了解專案項目是否為共用項目,再加以修改。

    下列方法會修改專案項目檔案的名稱。

    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],然後檢查一般輸出窗格中的文字。 在平台專案中的檔案重新命名之後,應該會看到 OnItemAdded 事件和 OnItemDeleted 事件。 因為變更某檔案不會變更其他任何檔案,而且因為平台專案中項目的變更不會傳播到任何地方,因此這些事件每個都只有一個。