Interfaces gráficas multiplataforma en C# con .NET Core y Avalonia

Microsoft sorprendió a todos con la publicación de .NET Core el 27 de junio de 2016. Por primera vez, la plaatforma .NET se volvía multiplataforma, con soporte a macOS y GNU/Linux. Y además se volvía software libre, con licencia MIT y con un desarrollo transparente donde cualquiera puede proponer mejoras, subir parches, etc…

Esto posibilita tener soporte de primer nivel para uno de los lenguajes mejor diseñados actualmente, C#, que hasta entonces tenía un soporte de segunda en Linux, a través de Mono.

Inicialmente el soporte de Microsoft a .NET Core abarca: aplicaciones de consola, aplicaciones web ASP.NET y UWP. Mucha gente se desanimó por no tener WinForms o WPF en .NET Core. Sin embargo eso no quiera decir que no se puedan hacer interfaces gráficas en .NET Core. Aquí os presento la librería Avalonia, que aunque está en beta todavía, funciona sorprendentemente bien y en un futuro dará mucho de que hablar.

Avalonia funciona en Windows, macOS, GNU/Linux, Android e iOS. Usa XAML para las interfaces y su diseño se asemeja a WPF, aunque se han hecho cambios aprovechando características más potentes de C#. Avalonia no es WPF multiplataforma, es mejor.

Creando un proyecto

En primer lugar, tenemos que instalar .NET Core 2.0. Se descarga en la página oficial. A continuación comprobamos que se ha instalado correctamente con:

Creamos ahora un nuevo proyecto de tipo consola con new

Si todo va bien nos saldrá algo parecido a esto:

Se nos habrán generado una carpeta obj y dos archivos: Program.cs y GitHubRepos.csproj. El primero es el punto de entrada de nuestro programa, el otro es el fichero de proyecto de C#.

Vamos a probar que todo está en orden compilando el proyecto.

Añadiendo Avalonia

Vamos a añadir ahora Avalonia. Instalar dependencias antes en C# era algo complicado. Ahora es muy sencillo, gracias a la integración de dotnet con NuGet.

¡Ya no tenemos que hacer nada más! Nuestra aplicación será compatible ya con Windows y GNU/Linux. Para el resto de plataformas, hay más paquetes disponibles en NuGet, sin embargo desconozco su grado de funcionamiento. Solo he probado la librería en Windows 7, Windows 10, Ubuntu y Debian.

Program.cs

Program.cs define el punto de entrada a la aplicación. Aquí en una aplicación Avalonia podemos dejarlo tan simple como esto:

Básicamente viene a decir que arranque una aplicación Avalonia definida en la clase App con la ventana MainWindow. A continuación vamos a definir esas clases.

App.cs y App.xaml

En Avalonia tenemos que definir una clase que represente a la aplicación, normalmente la llamaremos App. Crea un fichero llamado App.cs en la misma carpeta de Program.cs, con un contenido tan simple como este:

Lo que hacemos aquí es pedir al intérprete de XAML que lea App.xaml simplemente y por lo demás, es una mera clase hija de Application.

El fichero App.xaml contiene definiciones XAML que se aplican a todo el programa, como el estilo visual:

MainWindow.cs y MainWindow.xaml

Ahora nos toca definir una clase para la ventana principal de la aplicación. El código para empezar es extremadamente simple también:

Aquí hacemos lo mismo que con App.cs, mandamos cargar el fichero XAML. Este fichero XAML contiene los widgets que va a llevar la ventana. Parecido a HTML, QML de Qt, Glade de GTK o FXML de Java o XUL de Mozilla.

GitHubRepos.csproj

Antes de poder compilar es necesario modificar el fichero de proyecto para incluir los ficheros XAML en el binario. Se trata de añadir un nuevo ItemGroup y dentro de él un EmbeddedResource.

Ahora ya podemos compilar con dotnet run.

Nos deberá salir algo como esto:

Añadiendo clicks

Vamos a darle vidilla a la aplicación añadiendo eventos. Para ello primero hay que darle un nombre al botón, un ID. Usamos Name en XAML. También usaremos un TextBlock para representar texto.

Ahora en MainWindow.cs, en el constructor, podemos obtener referencia a los objetos XAML con Find.

Y ya estaría. También comentar que la función LanzarDado puede ser async si nos conviene. A partir de ahora ya puedes sumergirte en el código de Avalonia (¡porque documentación todavía no hay!) y experimentar por tu cuenta.

Un ejemplo real, GitHubRepos

Ahora os voy a enseñar un ejemplo real de la comodidad que supone usar Avalononia con .NET Core. He aquí un pequeño programa que obtiene la lista de repositorios de un usuario y los muestra por pantalla.

Y este es su correspondiente XAML:

Adicionalmente, he usado una clase extra para serializar el JSON.

El resultado es bastante satisfactorio:

 

Crear ventanas y botones en Rust con GTK

Cuando salió el Macintosh allá por 1984, fue novedosa su interfaz gráfica. En efecto, fue la primera interfaz gráfica popular y ha servido de inspiración para muchos otros sistemas de ventanas.

Hoy vamos a introducir una manera de crear interfaces gráficas de usuario (GUI) con Rust. Para ello usaremos GTK. GTK funciona sobre GNU/Linux, macOS y Windows (aunque es un poco más complicado de lo que debiera).

Para ello usaremos la fantástica crate gtk del proyecto Gtk-rs.

Instalando gtk

Añade al fichero Cargo.toml las siguiente líneas:

Y ejecuta cargo build.

Creando una ventana

Lo primero que vamos a hacer es crear una ventana.

Importamos gtk e iniciamos GTK.

Ahora podemos crear una ventana. GtkWindow es un struct con métodos asociados, por lo que podemos ajustar ciertos parámetros. En este código parece que estamos usando orientación a objetos, pero recuerdo que Rust técnicamente no tiene clases ni herencia.

Ahora vamos a añadir un evento para que cuando se pulse la X en nuestra ventana, se cierre el programa. Para ello tenemos que entender como funciona GTK. Cuando programamos en GTK lo que hacemos es configurar la aplicación y posteriormente ceder la ejecución a una función gtk_main que procesa todos los eventos según haya sido configurado. Para configurar los eventos usaremos callbacks, que en Rust se pueden implementar con closures. Las funciones que en GTK nos permiten conectar eventos siempre tienen el prefijo connect.

Ahora vamos a mostrar la ventana. Por defecto en GTK todos los widgets (todo lo que se muestra en pantalla) está oculto. Para mostrar todos los widgets que se han añadido a la ventana se suele usar show_all, que va a haciendo show de forma recursiva. Por último, le damos el control de la aplicación a gtk::main.

Una vez hecho esto, si compilamos con cargo run ya deberíamos ver una preciosa ventana GTK.

Y por supuesto, si pulsamos la X, la aplicación se cierra.

Layouts y botones en GTK

Vamos ahora a añadir dos cosas: un botón y un label que nos de un número del dado. Para poner varios elementos en una aplicación GTK es recomendable usar un layout. Voy a usar el layout Box, que permite agrupar los widgets de forma vertical u horizontal. Para los números aleatorios voy a usar la crate rand. El código final sería así:

Aquí pasan varias cosas interesantes. La primera es que usamos add para ir añadiendo de forma jerárquica los widgets. Debajo de Window está Box y debajo de Box tenemos Button y Label.

Por otra parte, vemos que la función asociada al evento del click de los botones es connect_clicked. Bien, en este ejemplo he introducido algo importante. Estoy modificando un widget desde un evento relacionado a otro widget. ¿Esto como se lleva con las reglas de propiedad/ownership de Rust? Bastante mal. Rust no puede saber si cuando se ejecuta el evento tenemos acceso al widget en cuestión que vamos a modificar. Afortunadamente, la API de gtk-rs ha sido diseñada con esto en cuenta y simplemente podemos hacer una llamada a clone para obtener otra referencia al objeto, que podemos pasar al closure (con move). Este clonado no lo es tal, sino que hace uso de Rc. Simplemente se nos presenta de forma transparente.

El ejemplo final: dibujando con Cairo

En este último ejemplo voy a añadir un widget donde se podrá ver la cara del dado que ha salido. Para ello uso un GtkDrawingArea, que permite usar la API de Cairo.

Con esto tenemos lo básico para empezar a diseñar GUIs con GTK y Rust.

 

Anrokku, un videojuego tipo puzzle

Anrokku es un juego de puzles que he programado estas semanas. Las reglas son simples, somos una ambulancia y tenemos que salir del parking debido a una emergencia. Desafortunadamente el parking es un caos y los coches bloquean la salida. Tu labor es ir moviendo los coches para lograr que la ambulancia salga del parking. Y cuantos menos movimientos hagas mejor.

En el menú principal podremos seleccionar a cuál de los 20 niveles queremos jugar. No podremos jugar a todos a la vez, es necesario habernos pasado los niveles anteriores para desbloquear el siguiente.

Ya en el juego tenemos que ir arrastrando los coches con el ratón para que la ambulancia pueda irse por la derecha. El juego está programando en Python 2.7 usando GTK. El renderizado está hecho en un GtkDrawingArea donde he usado Cairo. ¿Quiéres jugar? El juego completo es gratuito y open source bajo la liencia MIT en GitHub. No obstante, puedes descargar el archivo ZIP con el juego desde este enlace.

Descargar Anrokku

Después de descargar y descomprimir el archivo. Ejecuta el fichero main.py haciendo doble click o desde la terminal:

python2 main.py

¡Quiero ver muchas pantallas como esta!

 El juego almacena los récords obtenidos en cada nivel. Puede ser interesante repetir los niveles para hacerlo en el menor número posible de movimientos. En el primer nivel he conseguido ganar en 32 movimientos, ¿alguien se atreve a superarme?