Управление проектами универсальной версии Windows
Универсальные приложения Windows — это приложения, предназначенные для Windows 8.1 и Windows Телефон 8.1, что позволяет разработчикам использовать код и другие ресурсы на обеих платформах. Общий код и ресурсы хранятся в общем проекте, а код и ресурсы для конкретной платформы хранятся в отдельных проектах, один для Windows и другой для Windows Телефон. Дополнительные сведения о универсальных приложениях Windows см. в статье "Универсальные приложения Windows". Расширения Visual Studio, управляющие проектами, должны учитывать, что проекты универсальных приложений Windows имеют структуру, которая отличается от одноплатформенных приложений. В этом пошаговом руководстве показано, как перемещаться по общему проекту и управлять общими элементами.
Навигация по общему проекту
Создайте проект VSIX C# с именем TestUniversalProject. (Файл>нового>проекта, а затем пакет Visual Studio для C#>Extensibility).> Добавьте шаблон элемента пользовательского командного проекта (в Обозреватель решений щелкните правой кнопкой мыши узел проекта и выберите "Добавить>новый элемент", а затем перейдите к расширяемости). Назовите файл TestUniversalProject.
Добавьте ссылку на Microsoft.VisualStudio.Shell.Interop.12.1.DesignTime.dll и Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll (в разделе "Расширения").
Откройте TestUniversalProject.cs и добавьте следующие
using
директивы:using EnvDTE; using EnvDTE80; using Microsoft.VisualStudio; using Microsoft.VisualStudio.PlatformUI; using Microsoft.Internal.VisualStudio.PlatformUI; using System.Collections.Generic; using System.IO; using System.Windows.Forms;
В классе добавьте частное
TestUniversalProject
поле, указывающее на окно вывода .public sealed class TestUniversalProject { IVsOutputWindowPane output; . . . }
Задайте ссылку на область вывода в конструкторе TestUniversalProject:
private TestUniversalProject(Package package) { if (package == null) { throw new ArgumentNullException("package"); } this.package = package; OleMenuCommandService commandService = this.ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService; if (commandService != null) { CommandID menuCommandID = new CommandID(MenuGroup, CommandId); EventHandler eventHandler = this.ShowMessageBox; MenuCommand menuItem = new MenuCommand(eventHandler, menuCommandID); commandService.AddCommand(menuItem); } // get a reference to the Output window output = (IVsOutputWindowPane)ServiceProvider.GetService(typeof(SVsGeneralOutputWindowPane)); }
Удалите существующий
ShowMessageBox
код из метода:private void ShowMessageBox(object sender, EventArgs e) { }
Получите объект DTE, который мы будем использовать для нескольких различных целей в этом пошаговом руководстве. Кроме того, убедитесь, что решение загружается при нажатии кнопки меню.
private void ShowMessageBox(object sender, EventArgs e) { var dte = (EnvDTE.DTE)this.ServiceProvider.GetService(typeof(EnvDTE.DTE)); if (dte.Solution != null) { . . . } else { MessageBox.Show("No solution is open"); return; } }
Найдите общий проект. Общий проект — это чистый контейнер; Он не создает и не создает выходные данные. Следующий метод находит первый общий проект в решении, ищет IVsHierarchy объект, имеющий общую возможность проекта.
private IVsHierarchy FindSharedProject() { var sln = (IVsSolution)this.ServiceProvider.GetService(typeof(SVsSolution)); Guid empty = Guid.Empty; IEnumHierarchies enumHiers; //get all the projects in the solution ErrorHandler.ThrowOnFailure(sln.GetProjectEnum((uint)__VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION, ref empty, out enumHiers)); foreach (IVsHierarchy hier in ComUtilities.EnumerableFrom(enumHiers)) { if (PackageUtilities.IsCapabilityMatch(hier, "SharedAssetsProject")) { return hier; } } return null; }
В методе
ShowMessageBox
выводится подпись (имя проекта, которое отображается в Обозреватель решений) общего проекта.private void ShowMessageBox(object sender, EventArgs e) { var dte = (DTE)this.ServiceProvider.GetService(typeof(DTE)); if (dte.Solution != null) { var sharedHier = this.FindSharedProject(); if (sharedHier != null) { string sharedCaption = HierarchyUtilities.GetHierarchyProperty<string>(sharedHier, (uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_Caption); output.OutputStringThreadSafe(string.Format("Found shared project: {0}\n", sharedCaption)); } else { MessageBox.Show("Solution has no shared project"); return; } } else { MessageBox.Show("No solution is open"); return; } }
Получите активный проект платформы. Проекты платформы — это проекты, содержащие код и ресурсы для конкретной платформы. Следующий метод использует новое поле VSHPROPID_SharedItemContextHierarchy для получения активного проекта платформы.
private IVsHierarchy GetActiveProjectContext(IVsHierarchy hierarchy) { IVsHierarchy activeProjectContext; if (HierarchyUtilities.TryGetHierarchyProperty(hierarchy, (uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID7.VSHPROPID_SharedItemContextHierarchy, out activeProjectContext)) { return activeProjectContext; } else { return null; } }
В методе
ShowMessageBox
выводит подпись активного проекта платформы.private void ShowMessageBox(object sender, EventArgs e) { var dte = (DTE)this.ServiceProvider.GetService(typeof(DTE)); if (dte.Solution != null) { var sharedHier = this.FindSharedProject(); if (sharedHier != null) { string sharedCaption = HierarchyUtilities.GetHierarchyProperty<string>(sharedHier, (uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_Caption); output.OutputStringThreadSafe(string.Format("Shared project: {0}\n", sharedCaption)); var activePlatformHier = this.GetActiveProjectContext(sharedHier); if (activePlatformHier != null) { string activeCaption = HierarchyUtilities.GetHierarchyProperty<string>(activePlatformHier, (uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_Caption); output.OutputStringThreadSafe(string.Format("Active platform project: {0}\n", activeCaption)); } else { MessageBox.Show("Shared project has no active platform project"); } } else { MessageBox.Show("Solution has no shared project"); } } else { MessageBox.Show("No solution is open"); } }
Выполните итерацию по проектам платформы. Следующий метод получает все импортируемые (платформы) проекты из общего проекта.
private IEnumerable<IVsHierarchy> EnumImportingProjects(IVsHierarchy hierarchy) { IVsSharedAssetsProject sharedAssetsProject; if (HierarchyUtilities.TryGetHierarchyProperty(hierarchy, (uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID7.VSHPROPID_SharedAssetsProject, out sharedAssetsProject) && sharedAssetsProject != null) { foreach (IVsHierarchy importingProject in sharedAssetsProject.EnumImportingProjects()) { yield return importingProject; } } }
Важно!
Если пользователь открыл проект универсального приложения Windows C++ в экспериментальном экземпляре, приведенный выше код вызывает исключение. Это известная проблема. Чтобы избежать исключения, замените приведенный
foreach
выше блок следующим:var importingProjects = sharedAssetsProject.EnumImportingProjects(); for (int i = 0; i < importingProjects.Count; ++i) { yield return importingProjects[i]; }
В методе
ShowMessageBox
выводится подпись каждого проекта платформы. Вставьте следующий код после строки, которая выводит подпись активного проекта платформы. В этом списке отображаются только загруженные проекты платформы.output.OutputStringThreadSafe("Platform projects:\n"); IEnumerable<IVsHierarchy> projects = this.EnumImportingProjects(sharedHier); bool isActiveProjectSet = false; foreach (IVsHierarchy platformHier in projects) { string platformCaption = HierarchyUtilities.GetHierarchyProperty<string>(platformHier, (uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_Caption); output.OutputStringThreadSafe(string.Format(" * {0}\n", platformCaption)); }
Изменение активного проекта платформы. Следующий метод задает активный проект с помощью SetProperty.
private int SetActiveProjectContext(IVsHierarchy hierarchy, IVsHierarchy activeProjectContext) { return hierarchy.SetProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID7.VSHPROPID_SharedItemContextHierarchy, activeProjectContext); }
В методе измените
ShowMessageBox
активный проект платформы. Вставьте этот код вforeach
блок.bool isActiveProjectSet = false; string platformCaption = null; foreach (IVsHierarchy platformHier in projects) { platformCaption = HierarchyUtilities.GetHierarchyProperty<string>(platformHier, (uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_Caption); output.OutputStringThreadSafe(string.Format(" * {0}\n", platformCaption)); // if this project is neither the shared project nor the current active platform project, // set it to be the active project if (!isActiveProjectSet && platformHier != activePlatformHier) { this.SetActiveProjectContext(sharedHier, platformHier); activePlatformHier = platformHier; isActiveProjectSet = true; } } output.OutputStringThreadSafe("set active project: " + platformCaption +'\n');
Теперь попробуйте его. Нажмите клавишу F5, чтобы запустить экспериментальный экземпляр. Создайте проект универсального концентратора C# в экспериментальном экземпляре (в диалоговом окне "Новый проект", Visual C#>Windows>8>универсального концентратора>приложений). После загрузки решения перейдите в меню "Сервис" и нажмите кнопку "Вызвать TestUniversalProject", а затем проверка текст в области вывода. Вы должны увидеть нечто вроде этого:
Found shared project: HubApp.Shared The active platform project: HubApp.Windows Platform projects: * HubApp.Windows * HubApp.WindowsPhone set active project: HubApp.WindowsPhone
Управление общими элементами в проекте платформы
Найдите общие элементы в проекте платформы. Элементы в общем проекте отображаются в проекте платформы в качестве общих элементов. Их нельзя увидеть в Обозреватель решений, но их можно найти в иерархии проектов. Следующий метод проходит по иерархии и собирает все общие элементы. При необходимости он выводит подпись каждого элемента. Общие элементы определяются новым свойством VSHPROPID_IsSharedItem.
private void InspectHierarchyItems(IVsHierarchy hier, uint itemid, int level, List<uint> itemIds, bool getSharedItems, bool printItems) { string caption = HierarchyUtilities.GetHierarchyProperty<string>(hier, itemid, (int)__VSHPROPID.VSHPROPID_Caption); if (printItems) output.OutputStringThreadSafe(string.Format("{0}{1}\n", new string('\t', level), caption)); // if getSharedItems is true, inspect only shared items; if it's false, inspect only unshared items bool isSharedItem; if (HierarchyUtilities.TryGetHierarchyProperty(hier, itemid, (int)__VSHPROPID7.VSHPROPID_IsSharedItem, out isSharedItem) && (isSharedItem == getSharedItems)) { itemIds.Add(itemid); } uint child; if (HierarchyUtilities.TryGetHierarchyProperty(hier, itemid, (int)__VSHPROPID.VSHPROPID_FirstChild, Unbox.AsUInt32, out child) && child != (uint)VSConstants.VSITEMID.Nil) { this.InspectHierarchyItems(hier, child, level + 1, itemIds, isSharedItem, printItems); while (HierarchyUtilities.TryGetHierarchyProperty(hier, child, (int)__VSHPROPID.VSHPROPID_NextSibling, Unbox.AsUInt32, out child) && child != (uint)VSConstants.VSITEMID.Nil) { this.InspectHierarchyItems(hier, child, level + 1, itemIds, isSharedItem, printItems); } } }
В методе
ShowMessageBox
добавьте следующий код, чтобы пройти элементы иерархии проектов платформы. Вставьте его внутриforeach
блока.output.OutputStringThreadSafe("Walk the active platform project:\n"); var sharedItemIds = new List<uint>(); this.InspectHierarchyItems(activePlatformHier, (uint)VSConstants.VSITEMID.Root, 1, sharedItemIds, true, true);
Чтение общих элементов. Общие элементы отображаются в проекте платформы как скрытые связанные файлы, и вы можете читать все свойства как обычные связанные файлы. Следующий код считывает полный путь первого общего элемента.
var sharedItemId = sharedItemIds[0]; string fullPath; ErrorHandler.ThrowOnFailure(((IVsProject)activePlatformHier).GetMkDocument(sharedItemId, out fullPath)); output.OutputStringThreadSafe(string.Format("Shared item full path: {0}\n", fullPath));
Теперь попробуйте его. Нажмите клавишу F5 , чтобы запустить экспериментальный экземпляр. Создайте проект универсального концентратора C# в экспериментальном экземпляре (в диалоговом окне "Новый проект" Visual C#>Windows 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как в Обозреватель решений. События иерархии считают операцию переименования файла, состоящую из удаления файла, а затем добавления файла. Однако при изменении невидимых элементов система событий иерархии запускает OnItemDeleted событие, но не OnItemAdded событие. Таким образом, если вы переименовываете файл в проекте платформы, вы получите оба OnItemDeleted и OnItemAdded, но при переименовании файла в общем проекте вы получите только OnItemDeleted.
Чтобы отслеживать изменения в элементах проекта, можно обрабатывать события элементов проекта DTE (найденные в ProjectItemsEventsClass). Однако при обработке большого количества событий можно повысить производительность в IVsTrackProjectDocuments2событиях. В этом пошаговом руководстве показаны только события иерархии и события DTE. В этой процедуре вы добавите прослушиватель событий в общий проект и проект платформы. Затем при переименовании одного файла в общем проекте и другом файле в проекте платформы можно увидеть события, которые запускаются для каждой операции переименования.
В этой процедуре вы добавите прослушиватель событий в общий проект и проект платформы. Затем при переименовании одного файла в общем проекте и другом файле в проекте платформы можно увидеть события, которые запускаются для каждой операции переименования.
Добавьте прослушиватель событий. Добавьте новый файл класса в проект и вызовите его HierarchyEventListener.cs.
Откройте файл HierarchyEventListener.cs и добавьте следующие директивы using:
using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio; using System.IO;
Реализуйте
HierarchyEventListener
класс IVsHierarchyEvents:class HierarchyEventListener : IVsHierarchyEvents { }
Реализуйте элементы IVsHierarchyEvents, как в приведенном ниже коде.
class HierarchyEventListener : IVsHierarchyEvents { private IVsHierarchy hierarchy; IVsOutputWindowPane output; internal HierarchyEventListener(IVsHierarchy hierarchy, IVsOutputWindowPane outputWindow) { this.hierarchy = hierarchy; this.output = outputWindow; } int IVsHierarchyEvents.OnInvalidateIcon(IntPtr hIcon) { return VSConstants.S_OK; } int IVsHierarchyEvents.OnInvalidateItems(uint itemIDParent) { return VSConstants.S_OK; } int IVsHierarchyEvents.OnItemAdded(uint itemIDParent, uint itemIDSiblingPrev, uint itemIDAdded) { output.OutputStringThreadSafe("IVsHierarchyEvents.OnItemAdded: " + itemIDAdded + "\n"); return VSConstants.S_OK; } int IVsHierarchyEvents.OnItemDeleted(uint itemID) { output.OutputStringThreadSafe("IVsHierarchyEvents.OnItemDeleted: " + itemID + "\n"); return VSConstants.S_OK; } int IVsHierarchyEvents.OnItemsAppended(uint itemIDParent) { output.OutputStringThreadSafe("IVsHierarchyEvents.OnItemsAppended\n"); return VSConstants.S_OK; } int IVsHierarchyEvents.OnPropertyChanged(uint itemID, int propID, uint flags) { output.OutputStringThreadSafe("IVsHierarchyEvents.OnPropertyChanged: item ID " + itemID + "\n"); return VSConstants.S_OK; } }
В том же классе добавьте другой обработчик событий для события ItemRenamedDTE, который возникает при переименовании элемента проекта.
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);
Зарегистрируйтесь для события ItemRenamedэлемента проекта DTE. Добавьте следующий код после подключения второго прослушивателя.
// 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 событий.
Теперь попробуйте переименовать файл в проекте платформы, и вы можете увидеть разницу в событиях, которые будут запущены. Добавьте следующий код
ShowMessageBox
после вызова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);
Постройте и запустите проект. Создайте универсальный проект C# в экспериментальном экземпляре, перейдите в меню "Сервис" и щелкните "Вызвать TestUniversalProject" и проверка текст в общей области вывода. После переименования файла в проекте платформы вы увидите как событие, так OnItemAdded и OnItemDeleted событие. Так как изменение файла не привело к изменению других файлов, и поскольку изменения элементов в проекте платформы не распространяются нигде, существует только один из этих событий.