Parte 1. Introducción a XAML
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:
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:
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:
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:
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:
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 propiedadVerticalOptions
FontSizeConverter
para la propiedadFontSize
ColorTypeConverter
para la propiedadTextColor
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.
Navegación de páginas
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:
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="“Hello, XAML!”" … />
Este es su aspecto:
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:
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:
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.