Información general sobre los complementos de WPF
.NET Framework incluye un modelo de complementos que los desarrolladores pueden usar para crear aplicaciones que admiten la extensibilidad de los complementos. Dicho modelo permite la creación de complementos que se integran con las aplicaciones y amplían su funcionalidad. En algunos escenarios, las aplicaciones también necesitan mostrar interfaces de usuario que proporcionan los complementos. En este tema se muestra la forma en que WPF aumenta el modelo del complemento de .NET Framework para habilitar estos escenarios, la arquitectura subyacente, sus ventajas y sus limitaciones.
Requisitos previos
Se requieren conocimientos del modelo del complemento de .NET Framework. Para más información, consulte Complementos y extensibilidad.
Información general sobre los complementos
Para evitar las complejidades de la recompilación y reimplementación de aplicaciones para incorporar la nueva funcionalidad, estas implementan mecanismos de extensibilidad que permiten a los desarrolladores (tanto propios como de terceros) crear otras aplicaciones que se integran con ellas. La manera más común de admitir este tipo de extensibilidad es mediante el uso de complementos (también conocido como "complementos" y "módulos"). Estos son algunos ejemplos de aplicaciones reales que exponen la extensibilidad con complementos:
Complementos de Internet Explorer.
Módulos del Reproductor de Windows Media.
Complementos de Visual Studio.
Por ejemplo, el modelo del complemento del Reproductor de Windows Media permite a los desarrolladores de otras empresas implementar "módulos" que extienden el Reproductor de Windows Media de varias formas, entre las que se incluye incluida la creación de descodificadores y codificadores para los formatos multimedia que el Reproductor de Windows Media no admite de forma nativa (por ejemplo, DVD o MP3), efectos de audio y máscaras. Cada modelo del complemento se crea para exponer la funcionalidad que es única para una aplicación, aunque hay varias entidades y comportamientos que son comunes a todos los modelos de complementos.
Las tres entidades principales de las soluciones de extensibilidad de complemento habituales son los contratos, los complementos y las aplicaciones host. Los contratos definen la forma en que los complementos se integran con las aplicaciones host de dos maneras:
Los complementos se integran con la funcionalidad que implementan las aplicaciones host.
Las aplicaciones host exponen la funcionalidad para que los complementos se integren con ella.
Para que se usen los complementos, es preciso que las aplicaciones host los encuentren y los carguen en tiempo de ejecución. En consecuencia, las aplicaciones que admiten complementos tienen las siguientes responsabilidades adicionales:
Detección: buscar complementos que se adhieran a los contratos que admiten las aplicaciones host.
Activación: cargar, ejecutar y establecer de comunicación con complementos.
Aislamiento: usar dominios de aplicación o procesos para establecer límites de aislamiento que protejan las aplicaciones frente a posibles problemas de seguridad y de ejecución con los complementos.
Comunicación: permitir a los complementos y aplicaciones host comunicarse entre sí en los límites de aislamiento llamando a métodos y pasando datos.
Administración de la duración: cargar y descargar dominios de aplicación y procesos de una manera limpia y predecible (consulte Dominios de aplicación).
Control de versiones: asegurarse de que las aplicaciones host y los complementos se pueden comunicar cuando se crean nuevas versiones de cualquiera de ellos.
Por último, el desarrollo de un sólido modelo de complemento es una empresa que no es trivial. Por este motivo, .NET Framework proporciona una infraestructura para compilar modelos de complementos.
Nota:
Para más información acerca de los complementos, consulte Complementos y extensibilidad.
Introducción al modelo de complemento de .NET Framework
El modelo de complemento de .NET Framework, que se encuentra en el espacio de nombres System.AddIn, contiene un conjunto de tipos diseñados para simplificar el desarrollo de la extensibilidad de los complementos. La unidad fundamental del modelo de complemento de .NET Framework es el contrato, que define cómo se comunican una aplicación host y un complemento entre sí. Un contrato se expone a una aplicación host mediante una vista específica de la aplicación host del contrato. Del mismo modo, se expone una vista específica del complemento al complemento. Se usa un adaptador para que una aplicación host y un complemento se comuniquen entre sus respectivas vistas del contrato. Los contratos, vistas y adaptadores se conocen como segmentos y un conjunto de segmentos relacionados constituye una canalización. Las canalizaciones son la base sobre la que el modelo de complemento de .NET Framework admite la detección, la activación, el aislamiento de el seguridad, aislamiento de ejecución (mediante procesos y dominios de aplicación), la comunicación, la administración de la duración y el control de versiones.
La suma de todo ello permite a los desarrolladores compilar complementos que se integran con la funcionalidad de una aplicación host. Sin embargo, algunos escenarios requieren que las aplicaciones host muestren las interfaces de usuario que proporcionan los complementos. Dado que cada una de las tecnologías de presentación de .NET Framework tiene su propio modelo de implementación de interfaces de usuario, el modelo de complemento de .NET Framework no admite ninguna tecnología de presentación concreta. En su lugar, WPF amplía el modelo de complemento de .NET Framework con la compatibilidad de los complementos con la interfaz de usuario.
Complementos WPF
WPF, junto con el modelo de complemento de .NET Framework, permite abordar una amplia variedad de escenarios que requieren que las aplicaciones host muestren las interfaces de usuario de los complementos. En concreto, WPF aborda estos escenarios con los dos modelos de programación siguientes:
El complemento devuelve una interfaz de usuario. Un complemento devuelve una interfaz de usuario a la aplicación host mediante la llamada a un método, como se define en el contrato. Este escenario se utiliza en los casos siguientes:
La apariencia de una interfaz de usuario que devuelve un complemento depende de datos o condiciones que solo existen en tiempo de ejecución, como los informes generados dinámicamente.
La interfaz de usuario de los servicios que proporciona un complemento no es la misma que la de las aplicaciones host que pueden usar el complemento.
El complemento realiza principalmente un servicio para la aplicación host y notifica el estado a esta con una interfaz de usuario.
El complemento es una interfaz de usuario. Un complemento es una interfaz de usuario, como define el contrato. Este escenario se utiliza en los casos siguientes:
Los complementos solo muestran los servicios que se muestran, como un anuncio.
La interfaz de usuario de los servicios que proporciona un complemento es común a todas las aplicaciones host que pueden utilizar dicho complemento, como una calculadora o un selector de colores.
Estos escenarios requieren que se puedan pasar objetos de la interfaz de usuario entre la aplicación host y los dominios de aplicaciones del complemento. Dado que el modelo de complemento de .NET Framework se basa en la comunicación remota entre dominios de aplicación, los objetos que se pasan entre ellos deben poder utilizarse de forma remota.
Un objeto que se puede usar de forma remota es una instancia de una clase que realiza una o varias de las siguientes acciones:
Se deriva de la clase MarshalByRefObject.
Implementa la interfaz ISerializable.
Tiene el atributo SerializableAttribute aplicado.
Nota:
Para más información acerca de la creación de objetos de .NET Framework remotos, consulte Hacer que los objetos sean remotos.
Los tipo de interfaz de usuario de WPF no se pueden usar de forma remota. Para solucionar el problema, WPF amplía el modelo de complemento de .NET Framework para habilitar la interfaz de usuario de WPF creada por los complementos que se muestran desde las aplicaciones host. WPF proporciona esta compatibilidad mediante dos tipos: la interfaz INativeHandleContract y dos métodos estáticos implementados por la clase FrameworkElementAdapters: ContractToViewAdapter y ViewToContractAdapter. A un nivel alto, estos tipos y métodos se utilizan como se indica a continuación:
WPF requiere que las interfaces de usuario proporcionadas por complementos sean clases que deriven directa o indirectamente de FrameworkElement, como formas, controles, controles de usuario, paneles de diseño y páginas.
Siempre que el contrato declare que se va a pasar una interfaz de usuario entre el complemento y la aplicación host, se debe declarar como INativeHandleContract (no FrameworkElement); INativeHandleContract es una representación que se puede usar de forma remota de la interfaz de usuario del complemento de interfaz de usuario que se puede pasar a través de los límites de aislamiento.
Antes de pasarlo desde el dominio de aplicación del complemento, FrameworkElement se empaqueta como INativeHandleContract, para lo que se llama a ViewToContractAdapter.
Después de que pasarlo al dominio de aplicación de la aplicación host, INativeHandleContract se deben volver a empaquetar como FrameworkElement, para lo que se llama a ContractToViewAdapter.
La forma en que se usen INativeHandleContract, ContractToViewAdapter y ViewToContractAdapter depende del escenario concreto. En las siguientes secciones se proporciona información acerca de cada modelo de programación.
El complemento devuelve una interfaz de usuario
Para que un complemento devuelva una interfaz de usuario a una aplicación host, se necesita lo siguiente:
La aplicación host, el complemento y la canalización deben crearse, como se describe en la documentación de .NET Framework Complementos y extensibilidad.
El contrato debe implementar IContract y, para devolver una interfaz de usuario, debe declarar un método con un valor devuelto del tipo INativeHandleContract.
La interfaz de usuario que se pasa entre el complemento y la aplicación host debe derivar, directa o indirectamente, de FrameworkElement.
La interfaz de usuario que devuelve el complemento se debe convertir de FrameworkElement a INativeHandleContract antes de cruzar el límite de aislamiento.
La interfaz de usuario que se devuelve se debe convertir de INativeHandleContract a FrameworkElement después de cruzar el límite de aislamiento.
La aplicación host muestra el FrameworkElement devuelto.
Para ver un ejemplo en el que se muestra cómo implementar un complemento que devuelve una interfaz de usuario, consulte Cómo: Crear un complemento que devuelva una interfaz de usuario.
El complemento es una interfaz de usuario
Cuando un complemento es una interfaz de usuario, se requiere lo siguiente:
La aplicación host, el complemento y la canalización deben crearse, como se describe en la documentación de .NET Framework Complementos y extensibilidad.
La interfaz del contrato para el complemento debe implementar INativeHandleContract.
El complemento que se pasa a la aplicación host debe derivar, directa o indirectamente, de FrameworkElement.
El complemento se debe convertir de FrameworkElement a INativeHandleContract antes de cruzar el límite de aislamiento.
El complemento se debe convertir de INativeHandleContract a FrameworkElement después de cruzar el límite de aislamiento.
La aplicación host muestra el FrameworkElement devuelto.
Para ver un ejemplo en el que se muestra cómo implementar un complemento que es una interfaz de usuario, consulte Cómo: Crear un complemento que sea una interfaz de usuario.
Devolución de varias interfaces de usuario desde un complemento
Los complementos a menudo proporcionan varias interfaces de usuario para que se muestren las aplicaciones host. Por ejemplo, considere un complemento que sea una interfaz de usuario y que también proporcione información de estado a la aplicación host, también como una interfaz de usuario. Un complemento como este se puede implementar mediante una combinación de técnicas de los modelos de El complemento devuelve una interfaz de usuario y El complemento es una interfaz de usuario.
Complementos y aplicaciones del explorador XAML
Hasta ahora, en los ejemplos, la aplicación host ha sido una aplicación independiente instalada. Pero las aplicaciones del explorador XAML (XBAP) también pueden hospedar complementos, aunque con los siguientes requisitos adicionales de compilación e implementación:
El manifiesto de aplicación de las aplicaciones XBAP se debe configurar especialmente para descargar la canalización (carpetas y ensamblados) y el ensamblado del complemento a la caché de la aplicación ClickOnce en el equipo cliente, en la misma carpeta que la aplicación XBAP.
Para detectar y cargar complementos, el código de la aplicación XBAP debe usar la caché de la aplicación ClickOnce para la aplicación XBAP como ubicación de la canalización y del complemento.
La aplicación XBAP debe cargar el complemento en un contexto de seguridad especial si el complemento hace referencia a archivos dinámicos que se encuentran en el sitio de origen; cuando los hospedan aplicaciones XBAP, los complementos solo pueden hacer referencia a los archivos dinámico que se encuentran en el sitio de origen de la aplicación host.
Estas tareas se describen con detalle en las subsecciones siguientes.
Configuración de la canalización y el complemento para la implementación de ClickOnce
Las aplicaciones XBAP se descargan en una carpeta segura de la caché de implementación de ClickOnce y se ejecutan desde ella. Para que una aplicación XBAP hospede un complemento, la canalización y el ensamblado del complemento también se debe descargar en la carpeta segura. Para lograrlo, es preciso que configure el manifiesto de aplicación para incluir tanto la canalización como el ensamblado del complemento en la descarga. Esto se hace más fácilmente en Visual Studio, aunque el ensamblado de la canalización y del ensamblado debe estar en la carpeta raíz del proyecto de aplicación XBAP del host para que Visual Studio detecte los ensamblados de canalización.
Por consiguiente, el primer paso es crear el ensamblado de la canalización y del complemento para la raíz del proyecto de aplicación XBAP estableciendo la salida de la compilación de cada proyecto de ensamblado de canalización y ensamblado de complemento. La siguiente tabla muestra las rutas de la salida de la compilación de los proyectos de ensamblado de canalización el proyecto de ensamblado de complemento que se encuentran en la misma carpeta de solución y raíz que el proyecto de aplicación XBAP del host.
Tabla 1: Crear rutas de la salida de la compilación para los ensamblados de canalización que hospeda una aplicación XBAP
Proyecto de ensamblado de canalización | Ruta de acceso de salida de la compilación |
---|---|
Contrato | ..\HostXBAP\Contracts\ |
Vista de complemento | ..\HostXBAP\AddInViews\ |
Adaptador del lado del complemento | ..\HostXBAP\AddInSideAdapters\ |
Adaptador del lado del host | ..\HostXBAP\HostSideAdapters\ |
Complemento | ..\HostXBAP\AddIns\WPFAddIn1 |
El siguiente paso es especificar los ensamblados de canalización y el ensamblado del complemento como archivos de contenido de las aplicaciones XBAP en Visual Studio haciendo lo siguiente:
Incluir Incluso el ensamblado de la canalización y del complemento en el proyecto haciendo clic con el botón derecho en cada carpeta de la canalización en el Explorador de soluciones y elegir Incluir en el proyecto.
Establecer el acción de compilación de cada ensamblado de canalización y ensamblado de complemento en Contenido desde la ventana Propiedades.
El paso final es configurar el manifiesto de aplicación para incluir los archivos del ensamblado de la canalización como el archivo del ensamblado del complemento en la descarga. Los archivos deben encontrarse en las carpetas de la raíz de la carpeta de la caché de ClickOnce que ocupa la aplicación XBAP. En Visual Studio, para lograr esta configuración es preciso realizar las siguientes acciones:
Haga clic con el botón derecho en el proyecto de aplicación XBAP, haga clic en Propiedades, Publicar y, después, haga clic en el botón Archivos de aplicación.
En el cuadro de diálogo Archivos de aplicación, establezca el estado de la publicación de la DLL de cada canalización y complemento en Incluir (automático) y establezca el grupo de descarga de la DLL de cada canalización y complemento en (Requerido).
Uso de la canalización y del complemento de la base de aplicación
Cuando la canalización y el complemento están configurados para la implementación de ClickOnce, se descargan en la misma carpeta de caché de ClickOnce que la aplicación XBAP. Para usar la canalización y el complemento de la aplicación XBAP, el código de la aplicación XBAP debe obtenerlos de la base de la aplicación. Los distintos tipos y miembros del modelo de complemento de .NET Framework para el uso de canalizaciones y complementos proporcionan una compatibilidad especial con este escenario. En primer lugar, la ruta de acceso se identifica mediante el valor de enumeración ApplicationBase. Este valor se utiliza en las sobrecargas de los miembros del complemento pertinentes para usar canalizaciones que incluyan los siguientes:
Acceso a sitio de origen del Host
Para asegurarse de que un complemento puede hacer referencia a los archivos del sitio de origen, debe cargarse con un aislamiento de seguridad equivalente a la aplicación host. Este nivel de seguridad se identifica mediante el valor de enumeración AddInSecurityLevel.Host y se pasa al método Activate cuando se activa un complemento.
Arquitectura de complemento WPF
En el nivel más alto, como hemos visto, WPF permite a los complementos de .NET Framework implementar interfaces de usuario (que se derivan, directa o indirectamente, de FrameworkElement) mediante INativeHandleContract, ViewToContractAdapter y ContractToViewAdapter. El resultado es que se devuelve a la aplicación host un FrameworkElement que se muestra desde la interfaz de usuario en la aplicación host.
En el caso de escenarios de complementos de interfaz de usuario simples, este es todo el detalle que necesita el desarrollador. En el caso de escenarios más complejos, especialmente los que intenten usar servicios de WPF adicionales , como el diseño, los recursos y enlace de datos, se requiere un conocimiento más detallado de la forma en que WPF amplía el modelo de complemento de .NET Framework con la compatibilidad con interfaz de usuario para conocer sus ventajas y limitaciones.
Fundamentalmente, WPF no pasa una interfaz de usuario desde un complemento a una aplicación host; en su lugar, WPF pasa el identificador de ventana de Win32 para la interfaz de usuario mediante la interoperabilidad de WPF. Por lo tanto, cuando una interfaz de usuario de un complemento se pasa a una aplicación host, ocurre lo siguiente:
En el lado del complemento, WPF adquiere un identificador de ventana para la interfaz de usuario que mostrará la aplicación host. El identificador de ventana está encapsulado mediante una clase de WPF interna que deriva de HwndSource e implementa INativeHandleContract. ViewToContractAdapter devuelve una instancia de esta clase y se calculan las referencias del dominio de aplicación del complemento al dominio de aplicación de la aplicación host.
En el lado de la aplicación host, WPF vuelve a empaquetar HwndSource como una clase de WPF interna que deriva de HwndHost y consume INativeHandleContract. ContractToViewAdapter devuelve una instancia de esta clase a la aplicación host.
HwndHost existe para mostrar interfaces de usuario, que se identifican mediante identificadores de ventana, desde las interfaces de usuario de WPF. Para más información, consulte Interoperabilidad de WPF y Win32.
En resumen, INativeHandleContract, ViewToContractAdapter yContractToViewAdapter existen para permitir que el identificador de ventana de una interfaz de usuario de WPF se pase de un complemento a una aplicación host, donde se encapsula mediante HwndHost y se muestra la interfaz de usuario de la aplicación host.
Nota:
Dado que la aplicación host obtiene HwndHost, no puede convertir el objeto que devuelve ContractToViewAdapter al tipo que implementa el complemento (por ejemplo, UserControl).
Por su naturaleza, HwndHost tiene ciertas limitaciones que afectan a la forma en que las aplicaciones host puede usarlo. Sin embargo, WPF amplía HwndHost con varias funcionalidades para escenarios de complemento. A continuación se describen estas ventajas y limitaciones.
Ventajas del complemento de WPF
Dado que las interfaces de usuario de los complementos de WPF se muestran desde las aplicaciones host mediante una clase interna que deriva de HwndHost, están restringidas por las funcionalidades de HwndHost con respecto a los servicios de la interfaz de usuario de WPF como el diseño, la representación, el enlace de datos, los estilos, las plantillas y los recursos. Sin embargo, WPF aumenta su subclase HwndHost interna con funcionalidades adicionales que incluyen:
Desplazamiento mediante tabulador entre la interfaz de usuario de una aplicación host y la de un complemento. Tenga en cuenta que el modelo de programación "el complemento es una interfaz de usuario" requiere que el adaptador del conversor invalide QueryContract para habilitar la tabulación, si el complemento es de plena confianza o de confianza parcial.
Cumplimiento de los requisitos de accesibilidad para las interfaces de usuario del complemento de que se muestran en las interfaces de usuario de la aplicación host.
Habilitación de aplicaciones WPF que se ejecutan de forma segura en varios escenarios de dominio de aplicación.
Se impide el acceso no válido a los identificadores de la ventana de la interfaz de usuario del complemento cuando los complementos se ejecutan con aislamiento de seguridad (es decir, un espacio aislado de seguridad de confianza parcial). Llamar a ViewToContractAdapter garantiza esta seguridad:
En el caso del modelo de programación de "el complemento devuelve una interfaz de usuario", la única manera de pasar el identificador de la ventana a una interfaz de usuario del complemento a través del límite de aislamiento es llamar a ViewToContractAdapter.
En el caso del modelo de programación de "el complemento es una interfaz de usuario", se requiere que se reemplace QueryContract en el adaptador de conversión y se llame a ViewToContractAdapter (como se muestra en los ejemplos anteriores), así como llamar a la implementación de
QueryContract
del adaptador de conversión desde el adaptador del host.
Se proporciona protección para la ejecución de varios dominios de aplicaciones. Dadas las limitaciones de los dominios de aplicación, las excepciones no controladas que se producen en los dominios de aplicación del complemento provocan que toda la aplicación se bloquee, aunque exista el límite de aislamiento. Sin embargo, WPF y el modelo del complemento de .NET Framework proporcionan una manera sencilla de evitar este problema y mejorar la estabilidad de la aplicación. Un complemento de WPF que muestra una interfaz de usuario crea Dispatcher para el subproceso en el que se ejecuta el dominio de aplicación, si la aplicación host es una aplicación de WPF. Para detectar todas las excepciones no controladas que se producen en el dominio de aplicación, controle el evento UnhandledException del Dispatcher del complemento de WPF. Dispatcher se puede obtener de la propiedad CurrentDispatcher.
Limitaciones del complemento de WPF
Más allá de las ventajas que WPF agrega a los comportamientos predeterminados que suministran HwndSource, HwndHost y los identificadores de ventana, también hay limitaciones para las interfaces de usuario del complemento que se muestran desde las aplicaciones host:
Las interfaces de usuario del complemento que se muestran desde una aplicación host no respetan el comportamiento de recorte de la aplicación host.
El concepto de espacio aéreo en los escenarios de interoperabilidad también se aplica a los complementos (consulte Información general sobre áreas de la tecnología).
Los servicios de la interfaz de usuario de una aplicación host, como la herencia de los recursos, el enlace de datos y los comandos, no están disponibles en las interfaces de usuario del complemento. Para proporcionar estos servicios al complemento, es preciso actualizar la canalización.
Una interfaz de usuario del complemento no se puede girar, escalar, sesgar ni verse afectada de ninguna otra forma por una transformación (consulte Información general sobre transformaciones).
El contenido de las interfaces de usuario de del complemento que se representa mediante el dibujo de las operaciones desde el espacio de nombres System.Drawing puede incluir la combinación alfa. Sin embargo, tanto la interfaz de usuario del complemento como la de aplicación host que la contiene deben ser 100 % opacas; es decir, la propiedad
Opacity
de ambas se debe establecer en 1.Si la propiedad AllowsTransparency de una ventana de la aplicación host que contiene una interfaz de usuario del complemento se establece en
true
, el complemento será invisible. Esto sucede aunque la interfaz de usuario del complemento es 100 % opaca (es decir, la propiedadOpacity
tiene un valor de 1).Una interfaz de usuario del complemento aparece encima de los restantes elementos de WPF en la misma ventana de nivel superior.
Ninguna de las partes de una interfaz de usuario del complemento se puede representar mediante VisualBrush. En su lugar, el complemento puede tomar una instantánea de la interfaz de usuario generada para crear un mapa de bits que se puede pasar a la aplicación host mediante los métodos que se definen en el contrato.
Los archivos multimedia no se pueden reproducir desde MediaElement en una interfaz de usuario del complemento.
Los eventos del mouse generados para la interfaz de usuario del complemento no los genera ni los recibe la aplicación host y la propiedad
IsMouseOver
de la interfaz de usuario de la aplicación host tiene un valor defalse
.Cuando el foco cambia entre los controles de una interfaz de usuario del complemento, la aplicación host no recibe ni genera los eventos
GotFocus
yLostFocus
.La parte de una aplicación host que contiene una interfaz de usuario del complemento aparece en blanco cuando se imprime.
Todos los distribuidores (consulte Dispatcher) que crea la interfaz de usuario del complemento se debe apagar manualmente antes de que el complemento propietario se descargue si continúa la ejecución de la aplicación host. El contrato puede implementar métodos que permiten a la aplicación host señalar el complemento antes de que se descargue, lo que permite a la interfaz de usuario del complemento apagar sus distribuidores.
Si una interfaz de usuario es una InkCanvas o contiene InkCanvas, no se puede descargar el complemento.
Optimización del rendimiento
De manera predeterminada, si se utilizan varios dominios de aplicación, los distintos ensamblados de .NET Framework que requiere cada aplicación se cargan en el dominio de la aplicación. Como consecuencia, el tiempo requerido para crear nuevos dominios de aplicación e iniciar aplicaciones en ellas puede afectar al rendimiento. Sin embargo, .NET Framework proporciona una manera de reducir los tiempos de inicio indicando a las aplicaciones que compartan ensamblados entre los dominios de aplicación si ya están cargados. Para ello, use el atributo LoaderOptimizationAttribute, que debe aplicarse al método de punto de entrada (Main
). En este caso, solo debe usar el código para implementar la definición de aplicación (consulte Información general sobre la administración de aplicaciones).
Vea también
.NET Desktop feedback