Administrar proyectos universales de Windows
Las aplicaciones universales de Windows son aplicaciones destinadas tanto a Windows 8.1 como a Windows Teléfono 8.1, lo que permite a los desarrolladores usar código y otros recursos en ambas plataformas. El código compartido y los recursos se mantienen en un proyecto compartido, mientras que el código y los recursos específicos de la plataforma se mantienen en proyectos independientes, uno para Windows y el otro para Windows Teléfono. Para obtener más información sobre las aplicaciones universales de Windows, consulta Aplicaciones universales de Windows. Las extensiones de Visual Studio que administran proyectos deben tener en cuenta que los proyectos de aplicaciones universales de Windows tienen una estructura que difiere de las aplicaciones de una sola plataforma. En este tutorial se muestra cómo navegar por el proyecto compartido y administrar los elementos compartidos.
Navegación por el proyecto compartido
Cree un proyecto VSIX de C# denominado TestUniversalProject. (Archivo>nuevo>proyecto y, a continuación, paquete de Visual Studio de extensibilidad>de C#).> Agregue una plantilla de elemento de proyecto Comando personalizado (en el Explorador de soluciones, haga clic con el botón derecho en el nodo del proyecto y seleccione Agregar>nuevo elemento y, a continuación, vaya a Extensibilidad). Asigne al archivo el nombre TestUniversalProject.
Agregue una referencia a Microsoft.VisualStudio.Shell.Interop.12.1.DesignTime.dll y Microsoft.VisualStudio.Shell.Interop.14.0.DesignTime.dll (en la sección Extensiones).
Abra TestUniversalProject.cs y agregue las siguientes
using
directivas: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;
En la
TestUniversalProject
clase, agregue un campo privado que apunte a la ventana Salida .public sealed class TestUniversalProject { IVsOutputWindowPane output; . . . }
Establezca la referencia al panel de salida dentro del constructor 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)); }
Quite el código existente del
ShowMessageBox
método :private void ShowMessageBox(object sender, EventArgs e) { }
Obtenga el objeto DTE, que usaremos para varios propósitos diferentes en este tutorial. Además, asegúrese de que se carga una solución cuando se haga clic en el botón de menú.
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; } }
Busque el proyecto compartido. El proyecto compartido es un contenedor puro; no compila ni genera salidas. El método siguiente busca el primer proyecto compartido de la solución buscando el IVsHierarchy objeto que tiene la funcionalidad del proyecto compartido.
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; }
En el
ShowMessageBox
método , muestre el subtítulo (el nombre del proyecto que aparece en el Explorador de soluciones) del proyecto compartido.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; } }
Obtenga el proyecto de plataforma activa. Los proyectos de plataforma son los proyectos que contienen código y recursos específicos de la plataforma. El método siguiente usa el nuevo campo VSHPROPID_SharedItemContextHierarchy para obtener el proyecto de plataforma activa.
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; } }
En el
ShowMessageBox
método , genera la subtítulo del proyecto de plataforma activa.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"); } }
Recorrer en iteración los proyectos de la plataforma. El método siguiente obtiene todos los proyectos de importación (plataforma) del proyecto compartido.
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; } } }
Importante
Si el usuario ha abierto un proyecto de aplicación universal de Windows de C++ en la instancia experimental, el código anterior produce una excepción. Este es un problema conocido. Para evitar la excepción, reemplace el
foreach
bloque anterior por lo siguiente:var importingProjects = sharedAssetsProject.EnumImportingProjects(); for (int i = 0; i < importingProjects.Count; ++i) { yield return importingProjects[i]; }
En el
ShowMessageBox
método , genera el subtítulo de cada proyecto de plataforma. Inserte el código siguiente después de la línea que genera la subtítulo del proyecto de plataforma activa. Solo los proyectos de plataforma que se cargan aparecen en esta lista.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)); }
Cambie el proyecto de plataforma activa. El método siguiente establece el proyecto activo mediante SetProperty.
private int SetActiveProjectContext(IVsHierarchy hierarchy, IVsHierarchy activeProjectContext) { return hierarchy.SetProperty((uint)VSConstants.VSITEMID.Root, (int)__VSHPROPID7.VSHPROPID_SharedItemContextHierarchy, activeProjectContext); }
En el
ShowMessageBox
método , cambie el proyecto de plataforma activa. Inserte este código dentro delforeach
bloque .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');
Ahora pruébelo. Presione F5 para iniciar la instancia experimental. Cree un proyecto de aplicación central universal de C# en la instancia experimental (en el cuadro de diálogo Nuevo proyecto, Visual C#>Windows Windows>8>Universal>Hub App). Una vez cargada la solución, vaya al menú Herramientas y haga clic en Invocar TestUniversalProject y, a continuación, compruebe el texto en el panel Salida. Debe ver algo parecido a lo siguiente:
Found shared project: HubApp.Shared The active platform project: HubApp.Windows Platform projects: * HubApp.Windows * HubApp.WindowsPhone set active project: HubApp.WindowsPhone
Administración de los elementos compartidos en el proyecto de plataforma
Busque los elementos compartidos en el proyecto de plataforma. Los elementos del proyecto compartido aparecen en el proyecto de plataforma como elementos compartidos. No puede verlos en el Explorador de soluciones, pero puede recorrer la jerarquía del proyecto para encontrarlos. El método siguiente recorre la jerarquía y recopila todos los elementos compartidos. Opcionalmente, genera la subtítulo de cada elemento. Los elementos compartidos se identifican mediante la nueva propiedad 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); } } }
En el
ShowMessageBox
método , agregue el código siguiente para guiar los elementos de jerarquía del proyecto de plataforma. Insértelo dentro delforeach
bloque.output.OutputStringThreadSafe("Walk the active platform project:\n"); var sharedItemIds = new List<uint>(); this.InspectHierarchyItems(activePlatformHier, (uint)VSConstants.VSITEMID.Root, 1, sharedItemIds, true, true);
Lee los elementos compartidos. Los elementos compartidos aparecen en el proyecto de plataforma como archivos vinculados ocultos y puede leer todas las propiedades como archivos vinculados normales. El código siguiente lee la ruta de acceso completa del primer elemento compartido.
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));
Ahora pruébelo. Presione F5 para iniciar la instancia experimental. Cree un proyecto de aplicación central universal de C# en la instancia experimental (en el cuadro de diálogo Nuevo proyecto, Visual C#>Windows Windows>8>>Universal Hub App) vaya al menú Herramientas y haga clic en Invocar TestUniversalProject y, a continuación, active el texto en el panel Salida. Debe ver algo parecido a lo siguiente:
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
Detección de cambios en proyectos de plataforma y proyectos compartidos
Puede usar eventos de jerarquía y proyecto para detectar cambios en proyectos compartidos, igual que para proyectos de plataforma. Sin embargo, los elementos de proyecto del proyecto compartido no están visibles, lo que significa que determinados eventos no se activan cuando se cambian los elementos de proyecto compartidos.
Tenga en cuenta la secuencia de eventos cuando se cambia el nombre de un archivo de un proyecto:
El nombre de archivo se cambia en el disco.
El archivo del proyecto se actualiza para incluir el nuevo nombre del archivo.
Los eventos de jerarquía (por ejemplo, IVsHierarchyEvents) suelen realizar un seguimiento de los cambios mostrados en la interfaz de usuario, como en el Explorador de soluciones. Los eventos de jerarquía consideran una operación de cambio de nombre de archivo para que conste de una eliminación de archivos y, a continuación, una adición de archivos. Sin embargo, cuando se cambian los elementos invisibles, el sistema de eventos de jerarquía desencadena un OnItemDeleted evento, pero no un OnItemAdded evento. Por lo tanto, si cambia el nombre de un archivo en un proyecto de plataforma, obtiene y OnItemDeleted OnItemAdded, pero si cambia el nombre de un archivo en un proyecto compartido, solo OnItemDeletedobtendrá .
Para realizar un seguimiento de los cambios en los elementos del proyecto, puede controlar eventos de elemento de proyecto DTE (los que se encuentran en ProjectItemsEventsClass). Sin embargo, si controla un gran número de eventos, puede obtener un mejor control del rendimiento de los eventos en IVsTrackProjectDocuments2. En este tutorial solo se muestran los eventos de jerarquía y los eventos DTE. En este procedimiento, agregará un agente de escucha de eventos a un proyecto compartido y un proyecto de plataforma. A continuación, al cambiar el nombre de un archivo en un proyecto compartido y otro archivo de un proyecto de plataforma, puede ver los eventos que se desencadenan para cada operación de cambio de nombre.
En este procedimiento, agregará un agente de escucha de eventos a un proyecto compartido y un proyecto de plataforma. A continuación, al cambiar el nombre de un archivo en un proyecto compartido y otro archivo de un proyecto de plataforma, puede ver los eventos que se desencadenan para cada operación de cambio de nombre.
Agregue un agente de escucha de eventos. Agregue un nuevo archivo de clase al proyecto y llámelo HierarchyEventListener.cs.
Abra el archivo HierarchyEventListener.cs y agregue las siguientes directivas using:
using Microsoft.VisualStudio.Shell.Interop; using Microsoft.VisualStudio; using System.IO;
Haga que la
HierarchyEventListener
clase implemente IVsHierarchyEvents:class HierarchyEventListener : IVsHierarchyEvents { }
Implemente los miembros de IVsHierarchyEvents, como en el código siguiente.
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; } }
En la misma clase, agregue otro controlador de eventos para el evento ItemRenamedDTE , que se produce cada vez que se cambia el nombre de un elemento de proyecto.
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)); }
Regístrese para los eventos de jerarquía. Debe registrarse por separado para cada proyecto que esté realizando el seguimiento. Agregue el código siguiente en
ShowMessageBox
, uno para el proyecto compartido y el otro para uno de los proyectos de la plataforma.// 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);
Regístrese para el evento ItemRenamedde elemento de proyecto DTE . Agregue el código siguiente después de enlazar el segundo agente de escucha.
// hook up DTE events for project items Events2 dteEvents = (Events2)dte.Events; dteEvents.ProjectItemsEvents.ItemRenamed += listener1.OnItemRenamed;
Modifique el elemento compartido. No se pueden modificar elementos compartidos en un proyecto de plataforma; en su lugar, debe modificarlos en el proyecto compartido que es el propietario real de estos elementos. Puede obtener el identificador de elemento correspondiente en el proyecto compartido con IsDocumentInProject, lo que le proporciona la ruta de acceso completa del elemento compartido. A continuación, puede modificar el elemento compartido. El cambio se propaga a los proyectos de la plataforma.
Importante
Debe averiguar si un elemento de proyecto es o no un elemento compartido antes de modificarlo.
El método siguiente modifica el nombre de un archivo de elemento de proyecto.
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)); } }
Llame a este método después del otro código de
ShowMessageBox
para modificar el nombre de archivo del elemento en el proyecto compartido. Inserte esto después del código que obtiene la ruta de acceso completa del elemento en el proyecto compartido.// 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);
Compile y ejecute el proyecto. Cree una aplicación central universal de C# en la instancia experimental, vaya al menú Herramientas y haga clic en Invocar TestUniversalProject y compruebe el texto en el panel de salida general. El nombre del primer elemento del proyecto compartido (esperamos que sea el archivo App.xaml ) debe cambiarse y debería ver que el ItemRenamed evento se ha desencadenado. En este caso, dado que cambiar el nombre de App.xaml hace que también se cambie el nombre de App.xaml.cs , debería ver cuatro eventos (dos para cada proyecto de plataforma). (Los eventos DTE no realizan un seguimiento de los elementos del proyecto compartido). Debería ver dos OnItemDeleted eventos (uno para cada uno de los proyectos de plataforma), pero no OnItemAdded hay ningún evento.
Ahora intente cambiar el nombre de un archivo en un proyecto de plataforma y puede ver la diferencia en los eventos que se desencadenan. Agregue el código siguiente en
ShowMessageBox
después de la llamada aModifyFileName
.// 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);
Compile y ejecute el proyecto. Cree un proyecto universal de C# en la instancia experimental, vaya al menú Herramientas y haga clic en Invocar TestUniversalProject y compruebe el texto en el panel de salida general. Después de cambiar el nombre del archivo en el proyecto de plataforma, debería ver tanto un OnItemAdded evento como un OnItemDeleted evento. Dado que cambiar el archivo no hizo que se cambiara ningún otro archivo y, dado que los cambios en los elementos de un proyecto de plataforma no se propagan en ningún lugar, solo hay uno de estos eventos.