管理通用 Windows 專案
通用 Windows 應用程式是以 Windows 8.1 和 Windows Phone 8.1 為目標的應用程式,可讓開發人員在兩個平台上使用程式碼和其他資產。 共用程式碼和資源會保留在共用專案中,而平台特定的程式碼和資源則保留在個別專案中,一個用於 Windows,另一個則用於 Windows Phone。 如需通用 Windows 應用程式的詳細資訊,請參閱 通用 Windows 應用程式。 管理專案的 Visual Studio 延伸模組應該注意通用 Windows 應用程式專案的結構與單一平台應用程式不同。 本逐步解說展示如何瀏覽共用專案及管理共用項目。
瀏覽共用專案
建立名為 TestUniversalProject 的 C# VSIX 專案。 (檔案>新建>專案 然後是 C#>擴充性>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"); } }
逐一查看平台專案。 下列方法會從共用專案取得所有匯入 (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]; }
在
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>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>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 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 取得共用專案中對應項目識別碼,並為其提供共用項目的完整路徑。 然後,您可以修改共用項目。 變更會傳播至平台專案。
重要
您應該先了解專案項目是否為共用項目,再加以修改。
下列方法會修改專案項目檔案的名稱。
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 事件。 因為變更某檔案不會變更其他任何檔案,而且因為平台專案中項目的變更不會傳播到任何地方,因此這些事件每個都只有一個。