Управление проектами универсальной версии Windows

Универсальные приложения Windows — это приложения, предназначенные для Windows 8.1 и Windows Phone 8.1, что позволяет разработчикам использовать код и другие ресурсы на обеих платформах. Общий код и ресурсы хранятся в общем проекте, а код и ресурсы для платформы хранятся в отдельных проектах, один для Windows и другой для Windows Phone. Дополнительные сведения о универсальных приложениях Windows см. в статье "Универсальные приложения Windows". Расширения Visual Studio, управляющие проектами, должны учитывать, что проекты универсальных приложений Windows имеют структуру, которая отличается от одноплатформенных приложений. В этом пошаговом руководстве показано, как перемещаться по общему проекту и управлять общими элементами.

  1. Создайте проект VSIX C# с именем TestUniversalProject. (Файл>Создать>Проект и затем C#>Расширяемость>Пакет Visual Studio). Добавьте шаблон элемента пользовательского командного проекта (в обозревателе решений щелкните правой кнопкой мыши узел проекта и выберите "Добавить>новый элемент", а затем перейдите к расширяемости). Назовите файл TestUniversalProject.

  2. Добавьте ссылку на Microsoft.VisualStudio.Shell.Interop.12.1.DesignTime.dll и Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll (в разделе "Расширения ").

  3. Откройте TestUniversalProject.cs и добавьте следующие using директивы:

    using EnvDTE;
    using EnvDTE80;
    using Microsoft.VisualStudio;
    using Microsoft.VisualStudio.PlatformUI;
    using Microsoft.Internal.VisualStudio.PlatformUI;
    using System.Collections.Generic;
    using System.IO;
    using System.Windows.Forms;
    
  4. В классе добавьте частное TestUniversalProject поле, указывающее на окно вывода .

    public sealed class TestUniversalProject
    {
        IVsOutputWindowPane output;
    . . .
    }
    
  5. Задайте ссылку на область вывода в конструкторе TestUniversalProject:

    private TestUniversalProject(Package package)
    {
        if (package == null)
        {
            throw new ArgumentNullException("package");
        }
    
        this.package = package;
    
        OleMenuCommandService commandService = this.ServiceProvider.GetService(typeof(IMenuCommandService)) as OleMenuCommandService;
        if (commandService != null)
        {
            CommandID menuCommandID = new CommandID(MenuGroup, CommandId);
            EventHandler eventHandler = this.ShowMessageBox;
            MenuCommand menuItem = new MenuCommand(eventHandler, menuCommandID);
            commandService.AddCommand(menuItem);
        }
    
        // get a reference to the Output window
        output = (IVsOutputWindowPane)ServiceProvider.GetService(typeof(SVsGeneralOutputWindowPane));
    }
    
  6. Удалите существующий ShowMessageBox код из метода:

    private void ShowMessageBox(object sender, EventArgs e)
    {
    }
    
  7. Получите объект DTE, который мы будем использовать для нескольких различных целей в этом пошаговом руководстве. Кроме того, убедитесь, что решение загружается при нажатии кнопки меню.

    private void ShowMessageBox(object sender, EventArgs e)
    {
        var dte = (EnvDTE.DTE)this.ServiceProvider.GetService(typeof(EnvDTE.DTE));
        if (dte.Solution != null)
        {
            . . .
        }
        else
        {
            MessageBox.Show("No solution is open");
            return;
        }
    }
    
  8. Найдите общий проект. Общий проект — это лишь контейнер; он не компилируется и не производит выходные данные. Следующий метод находит первый общий проект в решении, ищет IVsHierarchy объект, имеющий общую возможность проекта.

    private IVsHierarchy FindSharedProject()
    {
        var sln = (IVsSolution)this.ServiceProvider.GetService(typeof(SVsSolution));
        Guid empty = Guid.Empty;
        IEnumHierarchies enumHiers;
    
        //get all the projects in the solution
        ErrorHandler.ThrowOnFailure(sln.GetProjectEnum((uint)__VSENUMPROJFLAGS.EPF_LOADEDINSOLUTION, ref empty, out enumHiers));
        foreach (IVsHierarchy hier in ComUtilities.EnumerableFrom(enumHiers))
        {
            if (PackageUtilities.IsCapabilityMatch(hier, "SharedAssetsProject"))
            {
                return hier;
            }
        }
        return null;
    }
    
  9. В методе ShowMessageBox выводит заголовок (имя проекта, отображаемое в обозревателе решений) общего проекта.

    private void ShowMessageBox(object sender, EventArgs e)
    {
        var dte = (DTE)this.ServiceProvider.GetService(typeof(DTE));
    
        if (dte.Solution != null)
        {
            var sharedHier = this.FindSharedProject();
            if (sharedHier != null)
            {
                string sharedCaption = HierarchyUtilities.GetHierarchyProperty<string>(sharedHier, (uint)VSConstants.VSITEMID.Root,
                     (int)__VSHPROPID.VSHPROPID_Caption);
                output.OutputStringThreadSafe(string.Format("Found shared project: {0}\n", sharedCaption));
            }
            else
            {
                MessageBox.Show("Solution has no shared project");
                return;
            }
        }
        else
        {
            MessageBox.Show("No solution is open");
            return;
        }
    }
    
  10. Получите активный проект платформы. Проекты платформы — это проекты, содержащие код и ресурсы для конкретной платформы. Следующий метод использует новое поле VSHPROPID_SharedItemContextHierarchy для получения активного проекта платформы.

    private IVsHierarchy GetActiveProjectContext(IVsHierarchy hierarchy)
    {
        IVsHierarchy activeProjectContext;
        if (HierarchyUtilities.TryGetHierarchyProperty(hierarchy, (uint)VSConstants.VSITEMID.Root,
             (int)__VSHPROPID7.VSHPROPID_SharedItemContextHierarchy, out activeProjectContext))
        {
            return activeProjectContext;
        }
        else
        {
            return null;
        }
    }
    
  11. В методе ShowMessageBox выведите заголовок активного проекта платформы.

    private void ShowMessageBox(object sender, EventArgs e)
    {
        var dte = (DTE)this.ServiceProvider.GetService(typeof(DTE));
    
        if (dte.Solution != null)
        {
            var sharedHier = this.FindSharedProject();
            if (sharedHier != null)
            {
                string sharedCaption = HierarchyUtilities.GetHierarchyProperty<string>(sharedHier, (uint)VSConstants.VSITEMID.Root,
                     (int)__VSHPROPID.VSHPROPID_Caption);
                output.OutputStringThreadSafe(string.Format("Shared project: {0}\n", sharedCaption));
    
                var activePlatformHier = this.GetActiveProjectContext(sharedHier);
                if (activePlatformHier != null)
                {
                    string activeCaption = HierarchyUtilities.GetHierarchyProperty<string>(activePlatformHier,
                         (uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID.VSHPROPID_Caption);
                    output.OutputStringThreadSafe(string.Format("Active platform project: {0}\n", activeCaption));
                }
                else
                {
                    MessageBox.Show("Shared project has no active platform project");
                }
            }
            else
            {
                MessageBox.Show("Solution has no shared project");
            }
        }
        else
        {
            MessageBox.Show("No solution is open");
        }
    }
    
  12. Выполните итерацию по проектам платформы. Следующий метод получает все импортируемые платформенные проекты из совместного проекта.

    private IEnumerable<IVsHierarchy> EnumImportingProjects(IVsHierarchy hierarchy)
    {
        IVsSharedAssetsProject sharedAssetsProject;
        if (HierarchyUtilities.TryGetHierarchyProperty(hierarchy, (uint)VSConstants.VSITEMID.Root,
            (int)__VSHPROPID7.VSHPROPID_SharedAssetsProject, out sharedAssetsProject)
            && sharedAssetsProject != null)
        {
            foreach (IVsHierarchy importingProject in sharedAssetsProject.EnumImportingProjects())
            {
                yield return importingProject;
            }
        }
    }
    

    Это важно

    Если пользователь открыл проект универсального приложения Windows C++ в экспериментальном экземпляре, приведенный выше код вызывает исключение. Это известная проблема. Чтобы избежать исключения, замените приведенный 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>8>универсальное>приложение Hub), выберите в меню «Сервис» пункт «Вызвать TestUniversalProject», а затем проверьте текст на панели Output. Вы должны увидеть примерно следующее:

    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) обычно отслеживают изменения, отображаемые в пользовательском интерфейсе, как в обозревателе решений. В рамках событий иерархии операция переименования файла рассматривается как удаление файла с последующим добавлением нового файла. Однако при изменении невидимых элементов система событий иерархии запускает OnItemDeleted событие, но не OnItemAdded событие. Таким образом, если вы переименовываете файл в проекте платформы, вы получите оба OnItemDeleted и OnItemAdded, но при переименовании файла в общем проекте вы получите только OnItemDeleted.

      Чтобы отслеживать изменения в элементах проекта, можно обрабатывать события элементов проекта DTE (найденные в ProjectItemsEventsClass). Однако, если вы обрабатываете большое количество событий, можно повысить производительность, обрабатывая события в IVsTrackProjectDocuments2. В этом пошаговом руководстве показаны только события иерархии и события DTE. В этой процедуре вы добавите прослушиватель событий в общий проект и проект платформы. Затем при переименовании одного файла в общем проекте и другом файле в проекте платформы можно увидеть события, которые запускаются для каждой операции переименования.

      В этой процедуре вы добавите прослушиватель событий в общий проект и проект платформы. Затем при переименовании одного файла в общем проекте и другом файле в проекте платформы можно увидеть события, которые запускаются для каждой операции переименования.

  2. Добавьте прослушиватель событий. Добавьте новый файл класса в проект и вызовите его HierarchyEventListener.cs.

  3. Откройте файл HierarchyEventListener.cs и добавьте следующие директивы using:

    using Microsoft.VisualStudio.Shell.Interop;
    using Microsoft.VisualStudio;
    using System.IO;
    
  4. Сделайте так, чтобы класс HierarchyEventListener реализовал IVsHierarchyEvents:

    class HierarchyEventListener : IVsHierarchyEvents
    { }
    
  5. Реализуйте элементы IVsHierarchyEvents, как в приведенном ниже коде.

    class HierarchyEventListener : IVsHierarchyEvents
    {
        private IVsHierarchy hierarchy;
        IVsOutputWindowPane output;
    
        internal HierarchyEventListener(IVsHierarchy hierarchy, IVsOutputWindowPane outputWindow) {
             this.hierarchy = hierarchy;
             this.output = outputWindow;
        }
    
        int IVsHierarchyEvents.OnInvalidateIcon(IntPtr hIcon) {
            return VSConstants.S_OK;
        }
    
        int IVsHierarchyEvents.OnInvalidateItems(uint itemIDParent) {
            return VSConstants.S_OK;
        }
    
        int IVsHierarchyEvents.OnItemAdded(uint itemIDParent, uint itemIDSiblingPrev, uint itemIDAdded) {
            output.OutputStringThreadSafe("IVsHierarchyEvents.OnItemAdded: " + itemIDAdded + "\n");
            return VSConstants.S_OK;
        }
    
        int IVsHierarchyEvents.OnItemDeleted(uint itemID) {
            output.OutputStringThreadSafe("IVsHierarchyEvents.OnItemDeleted: " + itemID + "\n");
            return VSConstants.S_OK;
        }
    
        int IVsHierarchyEvents.OnItemsAppended(uint itemIDParent) {
            output.OutputStringThreadSafe("IVsHierarchyEvents.OnItemsAppended\n");
            return VSConstants.S_OK;
        }
    
        int IVsHierarchyEvents.OnPropertyChanged(uint itemID, int propID, uint flags) {
            output.OutputStringThreadSafe("IVsHierarchyEvents.OnPropertyChanged: item ID " + itemID + "\n");
            return VSConstants.S_OK;
        }
    }
    
  6. В том же классе добавьте другой обработчик событий для события 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));
    }
    
  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. Теперь попробуйте переименовать файл в проекте платформы, и вы можете увидеть разницу в событиях, которые будут запущены. Добавьте следующий код в 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);
    
  13. Соберите проект и запустите его. Создайте универсальный проект C# в экспериментальном экземпляре, перейдите в меню "Сервис " и нажмите кнопку "Вызвать TestUniversalProject" и проверьте текст в общей области вывода. После переименования файла в проекте платформы, вы увидите и событие OnItemAdded, и событие OnItemDeleted. Так как изменение файла не привело к изменению других файлов, и поскольку изменения элементов в проекте платформы не распространяются нигде, существует только один из этих событий.