Parte 1. Introducción a XAML

Download SampleDescargar el ejemplo

En una aplicación de Xamarin.Forms, XAML se usa principalmente para definir el contenido visual de una página y funciona junto con un archivo de código subyacente de C#.

El archivo de código subyacente proporciona compatibilidad de código para el marcado. Juntos, estos dos archivos contribuyen a una nueva definición de clase que incluye vistas secundarias e inicialización de propiedades. Dentro del archivo XAML, se hace referencia a clases y propiedades con elementos y atributos XML, y se establecen vínculos entre el marcado y el código.

Crear la solución

Para empezar a editar el primer archivo XAML, use Visual Studio o Visual Studio para Mac para crear una nueva solución Xamarin.Forms. (Seleccione la pestaña siguiente correspondiente a su entorno).

En Windows, inicie Visual Studio 2019 y, en la ventana de inicio, haga clic en Crear un proyecto nuevo para crear un proyecto:

New Solution Window

En la ventana Crear un proyecto nuevo, seleccione Móvil en la lista desplegable Tipo de proyecto, elija la plantilla Aplicación móvil (Xamarin.Forms) y haga clic en el botón Siguiente:

New Project Window

En la ventana Configurar el nuevo proyecto, establezca el Nombre del proyecto en XamlSamples (o lo que prefiera) y haga clic en el botón Crear.

En el cuadro de diálogo New Cross Platform App (Nueva aplicación multiplataforma), haga clic en En blanco y después en el botón Aceptar:

New App Dialog

Se crean cuatro proyectos en la solución: la biblioteca XamlSamples de .NET Standard, XamlSamples.Android, XamlSamples.iOS y XamlSamples.UWP de la solución de Plataforma universal de Windows.

Después de crear la solución XamlSamples, es posible que quiera probar el entorno de desarrollo seleccionando los distintos proyectos de plataforma como proyecto de inicio de la solución y compilando e implementando la aplicación sencilla creada por la plantilla de proyecto en emuladores de teléfono o dispositivos reales.

A menos que necesite escribir código específico de la plataforma, el proyecto compartido de la biblioteca XamlSamples de .NET Standard es donde pasará prácticamente todo el tiempo de programación. Estos artículos no se aventurarán fuera de ese proyecto.

Anatomía de un archivo XAML

Dentro de la biblioteca XamlSamples .NET Standard hay un par de archivos con los nombres siguientes:

  • App.xaml, el archivo XAML; y
  • App.xaml.cs, un archivo de código subyacente de C# asociado al archivo XAML.

Tendrá que hacer clic en la flecha situada junto a App.xaml para ver el archivo de código subyacente.

Tanto App.xaml como App.xaml.cs contribuyen a una clase denominada App que deriva de Application. La mayoría de las demás clases con archivos XAML contribuyen a una clase que deriva de ContentPage; esos archivos usan XAML para definir el contenido visual de una página completa. Esto también sucede en los otros dos archivos del proyecto XamlSamples:

  • MainPage.xaml, el archivo XAML; y
  • MainPage.xaml.cs, el archivo de código subyacente de C#.

El archivo MainPage.xaml tiene este aspecto (aunque el formato puede ser un poco diferente):

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:XamlSamples"
             x:Class="XamlSamples.MainPage">

    <StackLayout>
        <!-- Place new controls here -->
        <Label Text="Welcome to Xamarin Forms!"
               VerticalOptions="Center"
               HorizontalOptions="Center" />
    </StackLayout>

</ContentPage>

Las dos declaraciones de espacio de nombres XML (xmlns) hacen referencia a URI, la primera aparentemente en el sitio web de Xamarin y la segunda en Microsoft. No se moleste en comprobar a qué apuntan esos URI. No hay nada ahí. Son simplemente URI propiedad de Xamarin y Microsoft, y básicamente funcionan como identificadores de versión.

La primera declaración de espacio de nombres XML significa que las etiquetas definidas en el archivo XAML sin prefijo hacen referencia a clases de Xamarin.Forms, por ejemplo ContentPage. La segunda declaración de espacio de nombres define un prefijo de x. Se usa para varios elementos y atributos que son intrínsecos de XAML, y que son compatibles con otras implementaciones de XAML. No obstante, estos elementos y atributos son ligeramente diferentes en función del año incorporado en el URI. Xamarin.Forms admite la especificación XAML 2009, pero no toda.

La declaración de espacio de nombres local permite acceder a otras clases desde el proyecto de biblioteca de .NET Standard.

Al final de esa primera etiqueta, el prefijo x se usa para un atributo denominado Class. Dado que el uso de este prefijo x es prácticamente universal para el espacio de nombres XAML, los atributos XAML, como Class, casi siempre se denominan x:Class.

El atributo x:Class especifica un nombre de clase .NET completo: la clase MainPage del espacio de nombres XamlSamples. Esto significa que este archivo XAML define una nueva clase denominada MainPage en el espacio de nombres XamlSamples que deriva de ContentPage, la etiqueta en la que aparece el atributo x:Class.

El atributo x:Class solo puede aparecer en el elemento raíz de un archivo XAML para definir una clase derivada de C#. Es la única clase nueva definida en el archivo XAML. Para todo lo demás que aparece en el archivo XAML simplemente se crea una instancia de las clases existentes y se inicializa.

El archivo MainPage.xaml.cs tiene este aspecto (aparte de las directivas using no usadas):

using Xamarin.Forms;

namespace XamlSamples
{
    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
        }
    }
}

La clase MainPage deriva de ContentPage, pero observe la definición de clase partial. Esto sugiere que debe haber otra definición de clase parcial para MainPage, pero ¿dónde está? ¿Y qué es ese método InitializeComponent?

Cuando Visual Studio compila el proyecto, analiza el archivo XAML para generar un archivo de código de C#. Si busca en el directorio XamlSamples\XamlSamples\obj\Debug, encontrará un archivo denominado XamlSamples.MainPage.xaml.g.cs. 'G' significa generado. Esta es la otra definición de clase parcial de MainPage que contiene la definición del método InitializeComponent al que se llama desde el constructor MainPage. Estas dos definiciones de clase parciales MainPage se pueden compilar juntas. Dependiendo de si el XAML está compilado o no, el archivo XAML o una forma binaria del archivo XAML se incrusta en el archivo ejecutable.

En tiempo de ejecución, el código del proyecto de plataforma en particular llama a un método LoadApplication y lo pasa a una nueva instancia de la clase App en la biblioteca de .NET Standard. El constructor de clase App crea una instancia de MainPage. El constructor de esa clase llama a InitializeComponent, que a continuación llama al método LoadFromXaml que extrae el archivo XAML (o su binario compilado) de la biblioteca de .NET Standard. LoadFromXaml inicializa todos los objetos definidos en el archivo XAML, los conecta todos juntos en relaciones de elementos primarios y secundarios, adjunta controladores de eventos definidos en código a eventos establecidos en el archivo XAML y establece el árbol resultante de objetos como contenido de la página.

Aunque normalmente no es necesario dedicar mucho tiempo a los archivos de código generados, a veces se generan excepciones en tiempo de ejecución en el código de los archivos generados, por lo que debe estar familiarizado con ellos.

Al compilar y ejecutar este programa, el elemento Label aparece en el centro de la página, como sugiere XAML:

Default Xamarin.Forms display

Para objetos visuales más interesantes, todo lo que necesita es un XAML más interesante.

Adición de nuevas páginas XAML

Para agregar otras clases de ContentPage basadas en XAML al proyecto, seleccione el proyecto de biblioteca XamlSamples de .NET Standard, haga clic con el botón derecho y seleccione Agregar > Nuevo elemento.... En el cuadro de diálogo Agregar nuevo elemento, seleccione Elementos de Visual C# >Xamarin.Forms> Página de contenido (no Página de contenido (C#), que crea una página de solo código o Vista de contenido, que no es una página). Asigne un nombre a la página, por ejemplo, HelloXamlPage:

Add New Item Dialog

Se agregan dos archivos al proyecto, HelloXamlPage.xaml y el archivo de código subyacente HelloXamlPage.xaml.cs.

Establecer contenido de página

Edite el archivo HelloXamlPage.xaml para que las únicas etiquetas sean aquellas para ContentPage y ContentPage.Content:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.HelloXamlPage">
    <ContentPage.Content>

    </ContentPage.Content>
</ContentPage>

Las etiquetas ContentPage.Content forman parte de la sintaxis única de XAML. Al principio, pueden parecer que no son XML no válidos, pero son legales. El punto no es un carácter especial en XML.

Las etiquetas ContentPage.Content se denominan etiquetas elemento de propiedad. Content es una propiedad de ContentPage y, por lo general, se establece en una sola vista o un diseño con vistas secundarias. Normalmente, las propiedades se convierten en atributos en XAML, pero sería difícil establecer un atributo Content en un objeto complejo. Por ese motivo, la propiedad se expresa como un elemento XML que consta del nombre de clase y el nombre de propiedad separados por un punto. Ahora la propiedad Content se puede establecer entre las etiquetas ContentPage.Content, de la siguiente manera:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.HelloXamlPage"
             Title="Hello XAML Page">
    <ContentPage.Content>

        <Label Text="Hello, XAML!"
               VerticalOptions="Center"
               HorizontalTextAlignment="Center"
               Rotation="-15"
               IsVisible="true"
               FontSize="Large"
               FontAttributes="Bold"
               TextColor="Blue" />

    </ContentPage.Content>
</ContentPage>

Observe también que se ha establecido un atributo Title en la etiqueta raíz.

En este momento, la relación entre clases, propiedades y XML debe ser evidente: una clase Xamarin.Forms (como ContentPage o Label) aparece en el archivo XAML como un elemento XML. Propiedades de esa clase, incluyendo Title en ContentPage y siete propiedades de Label, normalmente aparecen como atributos XML.

Existen muchos métodos abreviados para establecer los valores de estas propiedades. Algunas propiedades son tipos de datos básicos: por ejemplo, las propiedades Title y Text son de tipo String, Rotation es de tipo Double y IsVisible (que es true de forma predeterminada y se establece aquí solo como ejemplo) es de tipo Boolean.

La propiedad HorizontalTextAlignment es de tipo TextAlignment, que es una enumeración. Para una propiedad de cualquier tipo de enumeración, todo lo que debes suministrar es un nombre de miembro.

Sin embargo, para las propiedades de tipos más complejos, se usan convertidores para analizar el XAML. Estas son clases de Xamarin.Forms que derivan de TypeConverter. Muchas son clases públicas, pero algunas no. Para este archivo XAML concreto, varias de estas clases desempeñan un papel en segundo plano:

  • LayoutOptionsConverter para la propiedad VerticalOptions
  • FontSizeConverter para la propiedad FontSize
  • ColorTypeConverter para la propiedad TextColor

Estos convertidores rigen la sintaxis permitida de la configuración de la propiedad.

ThicknessTypeConverter puede controlar uno, dos o cuatro números separados por comas. Si se proporciona un número, se aplica a los cuatro lados. Con dos números, el primero es el relleno izquierdo y derecho, y el segundo es superior e inferior. Cuatro números están en el orden izquierdo, superior, derecho e inferior.

LayoutOptionsConverter puede convertir los nombres de los campos estáticos públicos de la estructura de LayoutOptions en valores de tipo LayoutOptions.

FontSizeConverter puede controlar un miembro de NamedSize o un tamaño numérico de fuente.

ColorTypeConverter acepta los nombres de los campos estáticos públicos de la estructura Color o valores RGB hexadecimales, con o sin un canal alfa, precedido por un signo de número (#). Esta es la sintaxis sin un canal alfa:

TextColor="#rrggbb"

Cada una de las letras pequeñas es un dígito hexadecimal. Este es el modo en que se incluye un canal alfa:

TextColor="#aarrggbb">

Para el canal alfa, tenga en cuenta que FF es totalmente opaco y 00 es totalmente transparente.

Otros dos formatos permiten especificar solo un solo dígito hexadecimal para cada canal:

TextColor="#rgb" TextColor="#argb"

En estos casos, el dígito se repite para formar el valor. Por ejemplo, #CF3 es el color RGB CC-FF-33.

Al ejecutar el programa XamlSamples, se muestra MainPage. Para ver el nuevo HelloXamlPage, puede establecerlo como la nueva página de inicio en el archivo App.xaml.cs o navegar a la nueva página desde MainPage.

Para implementar la navegación, cambie primero el código en el constructor App.xaml.cs para que se cree un objeto NavigationPage:

public App()
{
    InitializeComponent();
    MainPage = new NavigationPage(new MainPage());
}

En el constructor MainPage.xaml.cs, puede crear un sencillo Button y usar el controlador de eventos para navegar a HelloXamlPage:

public MainPage()
{
    InitializeComponent();

    Button button = new Button
    {
        Text = "Navigate!",
        HorizontalOptions = LayoutOptions.Center,
        VerticalOptions = LayoutOptions.Center
    };

    button.Clicked += async (sender, args) =>
    {
        await Navigation.PushAsync(new HelloXamlPage());
    };

    Content = button;
}

Al establecer la propiedad Content de la página, se reemplaza el valor de la propiedad Content en el archivo XAML. Al compilar e implementar la nueva versión de este programa, aparece un botón en la pantalla. Al presionarlo, se desplaza a HelloXamlPage. Esta es la página resultante en iPhone, Android y UWP:

Rotated Label Text

Puede volver a MainPage con el botón < Atrás en iOS, usando la flecha izquierda situada en la parte superior de la página o en la parte inferior del teléfono en Android, o usando la flecha izquierda en la parte superior de la página de Windows 10.

No dude en experimentar con XAML para diferentes formas de representar el Label. Si necesita insertar caracteres Unicode en el texto, puede usar la sintaxis XML estándar. Por ejemplo, para colocar el saludo en comillas tipográficas, use:

<Label Text="&#x201C;Hello, XAML!&#x201D;" … />

Este es su aspecto:

Rotated Label Text with Unicode Characters

Interacciones de código y XAML

El ejemplo HelloXamlPage contiene solo un solo elemento Label en la página, pero esto es muy inusual. La mayoría de los derivados de ContentPage establecen la propiedad Content en un diseño de algún tipo, como un StackLayout. La propiedad Children del StackLayout se define como de tipo IList<View>, pero en realidad es un objeto de tipo ElementCollection<View> y esa colección se puede rellenar con varias vistas u otros diseños. En XAML, estas relaciones de elementos primarios y secundarios se establecen con la jerarquía XML normal. Este es un archivo XAML para una nueva página denominada XamlPlusCodePage:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.XamlPlusCodePage"
             Title="XAML + Code Page">
    <StackLayout>
        <Slider VerticalOptions="CenterAndExpand" />

        <Label Text="A simple Label"
               Font="Large"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <Button Text="Click Me!"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand" />
    </StackLayout>
</ContentPage>

Este archivo XAML está completo sintácticamente y tiene el aspecto siguiente:

Multiple Controls on a Page

Sin embargo, es probable que considere que este programa es funcionalmente deficiente. Quizás el Slider debe hacer que el Label muestre el valor actual y el Button probablemente esté pensado para hacer algo dentro del programa.

Como verá en la parte 4. Conceptos básicos de enlace de datos, el trabajo de mostrar un valor de Slider mediante un Label se puede controlar completamente en XAML con un enlace de datos. Pero es útil ver primero la solución de código. Aun así, controlar el clic Button requiere código. Esto significa que el archivo de código subyacente para XamlPlusCodePage debe contener controladores para el evento ValueChanged del Slider y el evento Clicked del Button. Vamos a agregarlos:

namespace XamlSamples
{
    public partial class XamlPlusCodePage
    {
        public XamlPlusCodePage()
        {
            InitializeComponent();
        }

        void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
        {

        }

        void OnButtonClicked(object sender, EventArgs args)
        {

        }
    }
}

Estos controladores de eventos no necesitan ser públicos.

De vuelta en el archivo XAML, las etiquetas Slider y Button deben incluir atributos para los eventos ValueChanged y Clicked que hacen referencia a estos controladores:

<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="XamlSamples.XamlPlusCodePage"
             Title="XAML + Code Page">
    <StackLayout>
        <Slider VerticalOptions="CenterAndExpand"
                ValueChanged="OnSliderValueChanged" />

        <Label Text="A simple Label"
               Font="Large"
               HorizontalOptions="Center"
               VerticalOptions="CenterAndExpand" />

        <Button Text="Click Me!"
                HorizontalOptions="Center"
                VerticalOptions="CenterAndExpand"
                Clicked="OnButtonClicked" />
    </StackLayout>
</ContentPage>

Observa que la asignación de un controlador a un evento tiene la misma sintaxis que asignar un valor a una propiedad.

Si el controlador del evento ValueChanged del Slider usará el Label para mostrar el valor actual, el controlador debe hacer referencia a ese objeto desde el código. Label necesita un nombre, que se especifica con el atributo x:Name.

<Label x:Name="valueLabel"
       Text="A simple Label"
       Font="Large"
       HorizontalOptions="Center"
       VerticalOptions="CenterAndExpand" />

El prefijo x del atributo x:Name indica que este atributo es intrínseco de XAML.

El nombre que asignes al atributo x:Name tiene las mismas reglas que los nombres de variables de C#. Por ejemplo, debe comenzar con una letra o un guion bajo y no contener espacios insertados.

Ahora, el controlador de eventos ValueChanged puede establecer el Label para mostrar el nuevo valor de Slider. El nuevo valor está disponible en los argumentos del evento:

void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
{
    valueLabel.Text = args.NewValue.ToString("F3");
}

O bien, el controlador podría obtener el objeto Slider que está generando este evento a partir del argumento sender y obtener la propiedad Value de ahí:

void OnSliderValueChanged(object sender, ValueChangedEventArgs args)
{
    valueLabel.Text = ((Slider)sender).Value.ToString("F3");
}

Cuando se ejecuta por primera vez el programa, el Label no muestra el valor de Slider porque el evento ValueChanged tiene aún no se ha desencadenado. Pero cualquier manipulación del Slider hace que se muestre el valor:

Slider Value Displayed

Ahora para el Button. Vamos a simular una respuesta a un evento de Clicked mostrando una alerta con el Text del botón. El controlador de eventos puede convertir de forma segura el argumento sender en un Button y, a continuación, acceder a sus propiedades:

async void OnButtonClicked(object sender, EventArgs args)
{
    Button button = (Button)sender;
    await DisplayAlert("Clicked!",
        "The button labeled '" + button.Text + "' has been clicked",
        "OK");
}

El método se define como async porque el método DisplayAlert es asincrónico y debe estar precedido por el operador await, que se devuelve cuando se completa el método. Debido a que este método obtiene el objeto Button que activa el evento a partir del argumento sender, se podría usar el mismo controlador para varios botones.

Ha visto que un objeto definido en XAML puede desencadenar un evento que se controla en el archivo de código subyacente y que el archivo de código subyacente puede tener acceso a un objeto definido en XAML mediante el nombre asignado a él con el atributo x:Name. Estas son las dos formas fundamentales de interactuar con el código y XAML.

Para obtener información adicional sobre cómo funciona XAML, examine el archivo XamlPlusCode.xaml.g.cs recién generado, que ahora incluye cualquier nombre asignado a cualquier atributo de x:Name como campo privado. Esta es una versión simplificada de ese archivo:

public partial class XamlPlusCodePage : ContentPage {

    private Label valueLabel;

    private void InitializeComponent() {
        this.LoadFromXaml(typeof(XamlPlusCodePage));
        valueLabel = this.FindByName<Label>("valueLabel");
    }
}

La declaración de este campo permite que la variable se use libremente en cualquier parte del archivo de clase parcial XamlPlusCodePage bajo su jurisdicción. En tiempo de ejecución, el campo se asigna después de analizar el XAML. Esto significa que el campo valueLabel es null cuando se inicia el constructor XamlPlusCodePage pero válido después de llamar a InitializeComponent.

Después de que InitializeComponent devuelve el control al constructor, los objetos visuales de la página se han construido igual que si se hubieran creado instancias e inicializados en el código. El archivo XAML ya no desempeña ningún rol en la clase. Puede manipular estos objetos en la página de cualquier manera que desee, por ejemplo, agregando vistas a StackLayout o estableciendo la propiedad Content de la página en otra cosa completamente. Puede “recorrer el árbol” examinando la propiedad Content de la página y los elementos de las colecciones de diseños Children. Puede establecer propiedades en las vistas a las que se accede de esta manera o asignar controladores de eventos a ellas dinámicamente.

Puede hacer lo que quiera. Es su página y XAML es solo una herramienta para compilar su contenido.

Resumen

Con esta introducción, ha visto cómo un archivo XAML y un archivo de código contribuyen a una definición de clase y cómo interactúan los archivos XAML y de código. Pero XAML también tiene sus propias características sintácticas únicas que permiten su uso de manera muy flexible. Puede empezar a explorarlos en la Parte 2. Sintaxis XAML esencial.