Creación de interfaces gráficas
Introducción
1. ¿Por qué una interfaz gráfica?
Cuando se aprende un lenguaje de scripting, casi siempre es para automatizar muchas tareas tediosas y repetitivas. Por lo general, estas tareas no aportan ningún valor añadido a nuestro trabajo ni a nuestras habilidades. ¿Qué sentido tiene entonces crear interfaces gráficas? Los scripts que creamos con el tiempo se convierten en herramientas. Las utilizamos a diario. Cuando otras personas ven el trabajo que hacemos con estas herramientas, les entran ganas de utilizarlas. Pero no todo el mundo está preparado para dar el paso y utilizar una línea de comandos podría confundirles. Así que creamos una especie de máscara atractiva que viste el script con casillas de verificación y botones fáciles de usar. Así, un usuario no técnico puede ejecutar el script con facilidad. Esto también reduce el número de errores de escritura que uelen derivar en una llamada al servicio de soporte.
Hay otra gran cosa acerca de las interfaces gráficas en PowerShell. Como los scripts no se compilan (a diferencia de C#), las interfaces pueden modificarse fácilmente. Así es posible personalizarlas libremente o añadir los elementos que falten.
2. ¿A dónde acudir?
Cuando se trata de diseñar interfaces gráficas en PowerShell, hay dos opciones: Windows Forms y Windows Presentation...
Windows Forms
1. Introducción a Windows Forms
Windows Forms (o WinForms) está disponibles desde la versión 2.0 del framework .NET. Se agrupan en un conjunto de clases en el ensamblado System.Windows.Forms. Este ensamblado no se carga por defecto en PowerShell. Por lo tanto, es necesario cargarlo manualmente antes de manipular WinForms. Para ello, utilice el comando Add-Type:
PS > Add-Type -AssemblyName System.Windows.Forms
Si los Windows Forms están contenidos en clases, esto significa que solo pueden utilizarse instanciando objetos a través de ellas (véase el capítulo Clases), que es lo que hacemos todo el tiempo en PowerShell. Por lo tanto, es posible revisar el contenido de cada objeto utilizando el comando Get-Member.
En una interfaz gráfica, cada elemento se denomina genéricamente control. Esto abarca desde un simple botón hasta el formulario completo de la ventana. Cada control está representado por una de las clases WinForms. La característica de los controles es que pueden injertarse unos sobre otros, por ejemplo, una casilla de verificación o un menú que se desea posicionar en una ventana (Form).
Una de las particularidades de los formularios es que tienen las mismas características gráficas que aquellos en los que se basa el sistema operativo. El ejemplo más evidente es la transparencia de las ventanas en Windows Vista y Windows 7.
Los controles tienen una amplia gama de eventos. Se trata de interacciones con las formas, como un clic del ratón o una pulsación del teclado. Y para cada uno de estos eventos, se pueden declarar acciones a realizar. Por ejemplo, se puede crear un nuevo Form para mostrar un mensaje. O ejecutar un proceso en segundo plano en varias máquinas remotas. Las posibilidades son enormes. Cualquier control puede tener uno o más eventos configurados, y cada evento puede tener a su vez una o más acciones configuradas.
Como las interfaces gráficas no son una prioridad en PowerShell, no existe ninguna herramienta nativa que facilite su diseño. La dificultad radica principalmente en el posicionamiento y dimensionamiento de los distintos controles. Por lo tanto, hay que armarse de paciencia y comprobar manualmente la representación gráfica a medida que se diseña, ejecutando el código repetidamente. Para gestionar el posicionamiento...
Windows Presentation Foundation
WPF representa el futuro del diseño gráfico. Microsoft desarrolló esta tecnología en 2006 y no ha dejado de añadir nuevas funciones. A diferencia de Windows Forms, que se ha quedado obsoleto. WPF se basa en un aspecto vectorial que se descorrelaciona de la resolución de pantalla. Además, aprovecha la aceleración por hardware de la GPU, liberando la carga de la CPU.
Se define utilizando un lenguaje en XAML, separando la definición de la interfaz gráfica del resto del procesamiento. El código escrito en XAML puede almacenarse de dos formas en PowerShell, bien en un archivo independiente con extensión XAML, o bien directamente dentro del script, contenido en una cadena Here-String. Se presentarán ambos casos. La ventaja de la cadena Here-String es que reduce la dependencia de archivos externos al script. La ventaja del archivo XAML separado es que la actualización de la interfaz gráfica de usuario no requiere ninguna modificación del archivo de script; solo se puede modificar el XAML.
Un concepto a tener en cuenta cuando se trabaja con WPF es el apartment. En términos sencillos, es la partición a la que se somete cada hilo. Existen dos modos. En el modo STA (Single-Threaded Apartment) de, cada hilo está completamente particionado. Los objetos creados en su interior solo pueden ser consultados por ese hilo. En el modo MTA (Multi-Threaded Apartment), varios hilos pueden compartir la misma partición, y por tanto los objetos que contienen.
Para la generación gráfica en WPF, debe utilizarse el modo STA. De hecho, desde la versión 3.0 de PowerShell, la consola se ejecuta en este modo por defecto. Y PowerShell ISE es una aplicación WPF que se ejecuta en STA.
Para Windows Forms, es necesario cargar un ensamblado que contenga las clases objeto. WPF no es una excepción a esta regla. Sin embargo, no hay uno, sino tres assamblies para cargar al inicio del script. Estos ensamblados son PresentationCore, PresentationFramework y WindowsBase. Para cargarlos, utilice el comando Add-Type:
PS > Add-Type -AssemblyName PresentationFramework,
PresentationCore...
Actualización de una GUI mediante runspaces
Cuando se utiliza WPF, la GUI no se actualiza hasta que las acciones asignadas a un evento de control se hayan completado. Esto hace que la GUI se congele. Vimos esto antes con la barra de progreso, que solo se llena cuando todos los pings se han completado.
Sin embargo, hay una forma de evitar este problema. Pero cuidado, los conceptos que siguen son difíciles.
El problema aquí es que un runspace en modo STA no puede ejecutar varios procesos simultáneamente. Este es un requisito previo cuando se utiliza WPF. Para resolver esta limitación, debemos crear varios runspaces. El primero contiene la definición de la interfaz gráfica. El o los siguientes se desglosan en función de los requisitos de la interfaz gráfica. Se recomienda crear uno para cada proceso relativamente largo. Por ejemplo, el proceso de la casilla de verificación añadido anteriormente es breve. No requiere un runspace. En cambio, el procesamiento implementado para el evento Click del botón es relativamente largo. Por lo tanto, se le debe asignar un runspace.
Para evitar sobrecargar los ejemplos de código, empezaremos con un ejemplo más sencillo.
Un runspace se crea utilizando la clase [runspacefactory] y su método estático CreateRunspace(). Esto crea un objeto de tipo [runspace].
Ejemplo
$newRunspace =[runspacefactory]::CreateRunspace()
$newRunspace.ApartmentState = "STA"
$newRunspace.ThreadOptions = "ReuseThread"
$newRunspace.Open()
En este caso, el runspace es de tipo STA. La primera instrucción crea un nuevo hilo que se reutilizará más adelante. A continuación, el runspace se abre mediante el método Open().
Los runspaces suelen estar aislados y, por tanto, no pueden comunicarse entre sí. Sin embargo, existe una forma de evitar este problema creando una hashtable que se sincronizará entre los dos runspaces.
Ejemplo de creación de una hashtable sincronizada
$syncHash = [hashtable]::Synchronized(@{})
A continuación, la variable $syncHash debe asignarse al runspace:
$newRunspace.SessionStateProxy.SetVariable("syncHash",$syncHash)
A continuación, creamos un proceso PowerShell al que asignamos el runspace creado anteriormente:
$psCmd = [PowerShell]::Create()
$psCmd.Runspace = $newRunspace...