Creación de una aplicación .NET MAUI
Esta serie de tutoriales está diseñada para demostrar cómo crear una aplicación de .NET Multi-platform App UI (.NET MAUI) que solo use código multiplataforma. Es decir, el código que escribas no será específico de Windows, Android, iOS ni macOS. La aplicación que vas a crear será una aplicación de toma de notas, donde el usuario puede crear, guardar y cargar varias notas.
En este tutorial, aprenderás a:
- Crear una aplicación Shell de .NET MAUI.
- Ejecutar la aplicación en la plataforma que elijas.
- Definir la interfaz de usuario con el lenguaje XAML e interactuar con elementos de XALM a través del código.
- Crear vistas y enlazarlas a datos.
- Usar la navegación para moverse hacia y desde páginas.
Usarás Visual Studio 2022 para crear una aplicación con la que puedes escribir una nota y guardarla en el almacenamiento del dispositivo. A continuación se muestra la aplicación final:
Creación de un proyecto
Para poder comenzar este tutorial, debes seguir el artículo Compilación de la primera aplicación. Usa la configuración siguiente al crear el proyecto:
Nombre del proyecto
Debe establecerse en
Notes
. Si das al proyecto un nombre diferente, el código que copias y pegas de este tutorial puede producir errores de compilación.Ponga la solución y el proyecto en el mismo directorio
Desactiva esta opción.
Elija la versión más reciente de .NET Framework al crear el proyecto.
Selección del dispositivo de destino
Las aplicaciones .NET MAUI están diseñadas para ejecutarse en varios sistemas operativos y dispositivos. Deberá seleccionar el destino con el que quiere probar y depurar la aplicación.
Establezca el Destino de depuración en la barra de herramientas de Visual Studio en el dispositivo con el que desea depurar y probar. En los siguientes pasos se muestra cómo establecer el Destino de depuración en Android:
- Selecciona el botón desplegable Destino de depuración.
- Selecciona el elemento Emuladores Android.
- Selecciona el dispositivo del emulador.
Personalización del shell de aplicaciones
Cuando Visual Studio crea un proyecto .NET MAUI, se generan cuatro archivos de código importantes. Estos se pueden ver en el panel Explorador de soluciones de Visual Studio:
Estos archivos ayudan a configurar y ejecutar la aplicación .NET MAUI. Cada archivo sirve para un propósito diferente, que se describe a continuación:
MauiProgram.cs
Se trata de un archivo de código que arranca la aplicación. El código de este archivo actúa como punto de entrada multiplataforma de la aplicación, que configura e inicia la aplicación. El código de inicio de la plantilla apunta a la clase
App
definida por el archivo App.xaml.App.xaml y App.xaml.cs
Solo para simplificar las cosas, ambos archivos se conocen como un único archivo. Por lo general, hay dos archivos con cualquier archivo XAML, el propio archivo .xaml y un archivo de código correspondiente que es un elemento secundario de él en el Explorador de soluciones. El archivo .xaml contiene marcado XAML y el archivo de código contiene código creado por el usuario para interactuar con el marcado XAML.
El archivo App.xaml contiene recursos XAML para toda la aplicación, como colores, estilos o plantillas. El archivo App.xaml.cs generalmente contiene código que crea instancias de la aplicación shell. En este proyecto, apunta a la clase
AppShell
.AppShell.xaml y AppShell.xaml.cs
Este archivo define la clase
AppShell
, que se usa para definir la jerarquía visual de la aplicación.MainPage.xaml y MainPage.xaml.cs
Esta es la página de inicio que muestra la aplicación. El archivo MainPage.xaml define la interfaz de usuario (IU) de la página. MainPage.xaml.cs contiene el código subyacente para el XAML, como el código para un evento de clic de botón.
Adición de una página “acerca de”
La primera personalización que vas a realizar es agregar otra página al proyecto. Esta página es una página "acerca de", que representa información sobre esta aplicación, como el autor, la versión y quizás un vínculo para obtener más información.
En el panel Explorador de soluciones de Visual Studio, haz clic con el botón derecho en el proyecto Notas>Agregar>Nuevo elemento....
En el cuadro de diálogo Agregar nuevo elemento, selecciona .NET MAUI en la lista de plantillas del lado izquierdo de la ventana. Después, selecciona la plantilla .NET MAUI ContentPage (XAML). Asigna al archivo el nombre AboutPage.xaml y, después, selecciona Agregar.
El archivo AboutPage.xaml se abrirá en una nueva pestaña de documento, mostrando todo el marcado XAML que representa la interfaz de usuario de la página. Reemplace el marcado XAML por el siguiente marcado:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Notes.AboutPage"> <VerticalStackLayout Spacing="10" Margin="10"> <HorizontalStackLayout Spacing="10"> <Image Source="dotnet_bot.png" SemanticProperties.Description="The dot net bot waving hello!" HeightRequest="64" /> <Label FontSize="22" FontAttributes="Bold" Text="Notes" VerticalOptions="End" /> <Label FontSize="22" Text="v1.0" VerticalOptions="End" /> </HorizontalStackLayout> <Label Text="This app is written in XAML and C# with .NET MAUI." /> <Button Text="Learn more..." Clicked="LearnMore_Clicked" /> </VerticalStackLayout> </ContentPage>
Para guardar el archivo, presiona CTRL+S o selecciona el menú Archivo>Guardar aboutPage.xaml.
Vamos a desglosar las partes clave de los controles XAML situados en la página:
<ContentPage>
es el objeto raíz de la claseAboutPage
.<VerticalStackLayout>
es el único elemento secundario de ContentPage. ContentPage solo puede tener un objeto secundario. El tipo VerticalStackLayout puede tener varios elementos secundarios. Este control de diseño organiza sus elementos secundarios verticalmente, uno después del otro.<HorizontalStackLayout>
funciona igual que un<VerticalStackLayout>
, excepto que sus elementos secundarios se organizan horizontalmente.<Image>
muestra una imagen, en este caso se usa la imagendotnet_bot.png
que viene con cada proyecto de .NET MAUI.Importante
El archivo agregado al proyecto es realmente
dotnet_bot.svg
. .NET MAUI convierte archivos de gráficos vectoriales escalables (SVG) en archivos portables de gráfico de red (PNG) basados en el dispositivo de destino. Por lo tanto, al agregar un archivo SVG al proyecto de aplicación de .NET MAUI, se debe hacer referencia desde XAML o C# con una extensión.png
. La única referencia al archivo SVG debe estar en el archivo del proyecto.<Label>
controla la visualización del texto.El usuario puede pulsar los controles
<Button>
, lo que genera el eventoClicked
. Puede ejecutar código en respuesta al eventoClicked
.Clicked="LearnMore_Clicked"
El evento
Clicked
del botón se asigna al controlador de eventosLearnMore_Clicked
, que se definirá en el archivo de código subyacente. La creará en el paso siguiente.
Control del evento Clicked
El siguiente paso consiste en agregar el código para el evento Clicked
del botón.
En el panel Explorador de soluciones de Visual Studio, expande el archivo AboutPage.xaml para mostrar su archivo de código subyacente AboutPage.xaml.cs. Después, haz doble clic en el archivo AboutPage.xaml.cs para abrirlo en el editor de código.
Agrega el siguiente código de controlador de eventos
LearnMore_Clicked
, que abre el explorador del sistema a una dirección URL específica:private async void LearnMore_Clicked(object sender, EventArgs e) { // Navigate to the specified URL in the system browser. await Launcher.Default.OpenAsync("https://aka.ms/maui"); }
Observa que la palabra clave
async
se ha agregado a la declaración de método, lo que permite el uso de la palabra claveawait
al abrir el explorador del sistema.Para guardar el archivo, presiona CTRL+S o selecciona el menú Archivo>Guardar AboutPage.xaml.cs.
Ahora que el XAML y el código subyacente de AboutPage
están completos, tendrás que mostrarlo en la aplicación.
Adición de recursos de imagen
Algunos controles pueden usar imágenes, lo que mejora cómo interactúan los usuarios con la aplicación. En esta sección descargarás dos imágenes que usarás en la aplicación, junto con dos imágenes alternativas para usarlas con iOS.
Descarga las imágenes siguientes:
Icono: Acerca de
Esta imagen se usa como icono para la página acerca de que creaste anteriormente.Icono: Notas
Esta imagen se usa como icono para la página de notas que crearás en la siguiente parte de este tutorial.
Después de descargar las imágenes, puedes moverlas con el Explorador de archivos a la carpeta Resources\Images del proyecto. Cualquier archivo de esta carpeta se incluye automáticamente en el proyecto como un recurso MauiImage. También puedes usar Visual Studio para agregar las imágenes al proyecto. Si mueves las imágenes manualmente, omite el procedimiento siguiente.
Importante
No omitas la descarga de las imágenes específicas de iOS, son necesarias para completar este tutorial.
Mover las imágenes con Visual Studio
En el panel de Visual Studio Explorador de soluciones, expanda la carpeta Recursos, que revela la carpeta Imágenes.
Sugerencia
Puedes usar el Explorador de archivos para arrastrar y soltar las imágenes directamente en el panel Explorador de soluciones, en la parte superior de la carpeta Imágenes. Esto mueve automáticamente los archivos a la carpeta y los incluye en el proyecto. Si decides arrastrar y soltar los archivos, omite el resto de este procedimiento.
Haga clic con el botón derecho en Imágenes y seleccione Agregar>Elemento existente....
Navegue a la carpeta que contiene las imágenes descargadas.
Cambie el filtro al filtro de tipo de archivo a Archivos de imagen.
Mantén presionada la tecla CTRL y haz clic en cada una de las imágenes que descargaste, y luego presiona Agregar.
Modificar el Shell de la aplicación
Tal y como mencionamos al inicio de este artículo, la clase AppShell
define la jerarquía visual de una aplicación, el marcado XAML que se usa para crear la interfaz de usuario de la aplicación. Actualiza el XAML para agregar un control TabBar:
Haz doble clic en el archivo AppShell.xaml del panel Explorador de soluciones para abrir el editor XAML. Reemplace el marcado XAML con el siguiente código:
<?xml version="1.0" encoding="UTF-8" ?> <Shell x:Class="Notes.AppShell" xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:local="clr-namespace:Notes" Shell.FlyoutBehavior="Disabled"> <TabBar> <ShellContent Title="Notes" ContentTemplate="{DataTemplate local:MainPage}" Icon="{OnPlatform 'icon_notes.png', iOS='icon_notes_ios.png', MacCatalyst='icon_notes_ios.png'}" /> <ShellContent Title="About" ContentTemplate="{DataTemplate local:AboutPage}" Icon="{OnPlatform 'icon_about.png', iOS='icon_about_ios.png', MacCatalyst='icon_about_ios.png'}" /> </TabBar> </Shell>
Para guardar el archivo, presiona CTRL+S o selecciona el menú Archivo>Guardar AppShell.xaml.
Desglosemos las partes clave del XAML:
<Shell>
es el objeto raíz del marcado XAML.<TabBar>
es el contenido de Shell.- Dos objetos
<ShellContent>
dentro de<TabBar>
. Antes de reemplazar el código de plantilla, había un único objeto<ShellContent>
que apuntaba a la páginaMainPage
.
El TabBar
y sus elementos secundarios no representan ningún elemento de interfaz de usuario, sino más bien la organización de la jerarquía visual de la aplicación. Shell toma estos objetos y genera la interfaz de usuario para el contenido, con una barra en la parte superior que representa cada página. La propiedad ShellContent.Icon
de cada página usa sintaxis especial: {OnPlatform ...}
. Esta sintaxis se procesa cuando se compilan las páginas XAML para cada plataforma y, con ella, puedes especificar un valor de propiedad para cada plataforma. En este caso, cada plataforma usa el icono icon_about.png
de forma predeterminada, pero iOS y MacCatalyst usarán icon_about_ios.png
.
Cada objeto <ShellContent>
apunta a una página que se va a mostrar. Esto se establece mediante la propiedad ContentTemplate
.
Ejecución de la aplicación
Ejecuta la aplicación presionando F5 o el botón Reproducir situado en la parte superior de Visual Studio:
Observarás que hay dos pestañas: Notas y Acerca de. Presiona la pestaña Acerca de y la aplicación navegará hasta el AboutPage
que creaste. Presiona el botón Más información... para abrir el explorador web.
Cierre la aplicación y vuelva a Visual Studio. Si usas el emulador de Android, finaliza la aplicación en el dispositivo virtual o presiona el botón Detener situado en la parte superior de Visual Studio:
Crear una página para una nota
Ahora que la aplicación contiene el MainPage
y el AboutPage
, puedes empezar a crear el resto de la aplicación. En primer lugar, crearás una página que permita a un usuario crear y mostrar una nota y, luego, escribirás el código para cargar y guardar la nota.
La página de notas mostrará la nota y te permitirá guardarla o eliminarla. En primer lugar, agrega la nueva página al proyecto:
En el panel Explorador de soluciones de Visual Studio, haz clic con el botón derecho en el proyecto Notas>Agregar>Nuevo elemento....
En el cuadro de diálogo Agregar nuevo elemento, selecciona .NET MAUI en la lista de plantillas del lado izquierdo de la ventana. Después, selecciona la plantilla .NET MAUI ContentPage (XAML). Asigna al archivo el nombre NotePage.xaml y luego selecciona Agregar.
El archivo NotePage.xaml se abrirá en una nueva pestaña, mostrando todo el marcado XAML que representa la interfaz de usuario de la página. Reemplaza el marcado de código XAML por el siguiente marcado:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Notes.NotePage" Title="Note"> <VerticalStackLayout Spacing="10" Margin="5"> <Editor x:Name="TextEditor" Placeholder="Enter your note" HeightRequest="100" /> <Grid ColumnDefinitions="*,*" ColumnSpacing="4"> <Button Text="Save" Clicked="SaveButton_Clicked" /> <Button Grid.Column="1" Text="Delete" Clicked="DeleteButton_Clicked" /> </Grid> </VerticalStackLayout> </ContentPage>
Para guardar el archivo, presiona CTRL + S o selecciona el menú Archivo>Guardar NotePage.xaml.
Vamos a desglosar las partes clave de los controles XAML situados en la página:
<VerticalStackLayout>
organiza sus elementos secundarios verticalmente, uno después del otro.<Editor>
es un control de editor de texto de varias líneas y es el primer control dentro de VerticalStackLayout.<Grid>
es un control de diseño y es el segundo control dentro de VerticalStackLayout.Este control define columnas y filas para crear celdas. Los controles secundarios se colocan dentro de esas celdas.
De forma predeterminada, el control Grid contiene una sola fila y columna, creando una sola celda. Las columnas se definen con un ancho y el valor
*
del ancho indica a la columna que rellene tanto espacio como sea posible. El fragmento de código anterior definía dos columnas, que usaban el mayor espacio posible, lo que distribuye uniformemente las columnas en el espacio asignado:ColumnDefinitions="*,*"
. Los nombres de columna se separan mediante el carácter,
.Las columnas y filas definidas mediante un elemento Grid indexan a partir de 0. Por lo tanto, la primera columna sería el índice 0, la segunda columna es el índice 1, etc.
Hay dos controles
<Button>
dentro de<Grid>
y se les asigna una columna. Si un control secundario no define una asignación de columna, se asigna automáticamente a la primera columna. En este marcado, el primer botón es el botón "Guardar" y se asigna automáticamente a la primera columna, la columna 0. El segundo botón es el botón "Eliminar" y se asigna a la segunda columna, la columna 1.Observa que los dos botones tienen el evento
Clicked
controlado. Agregarás el código para estos controladores en la sección siguiente.
Cargar y guardar una nota
Abre el archivo de código subyacente NotePage.xaml.cs. Puedes abrir el código subyacente para el archivo NotePage.xaml de tres maneras:
- Si NotePage.xaml está abierto y es el documento activo que se está editando, presiona F7.
- Si NotePage.xaml está abierto y es el documento activo que se está editando, haz clic con el botón derecho en el editor de texto y selecciona Ver código.
- Usa el Explorador de soluciones para expandir la entrada NotePage.xaml, revelando el archivo NotePage.xaml.cs. Haz doble clic en el archivo para abrirlo.
Al agregar un nuevo archivo XAML, el código subyacente contiene una sola línea en el constructor, una llamada al método InitializeComponent
:
namespace Notes;
public partial class NotePage : ContentPage
{
public NotePage()
{
InitializeComponent();
}
}
El método InitializeComponent
lee el marcado XAML e inicializa todos los objetos definidos por el marcado. Los objetos están conectados en sus relaciones de elementos primarios y secundarios, y los controladores de eventos definidos en el código se adjuntan a los eventos establecidos en el XAML.
Ahora que ya sabes un poco más sobre los archivos de código subyacente, vas a agregar código al archivo de código subyacente NotePage.xaml.cs para controlar la carga y el guardado de notas.
Cuando se crea una nota, se guarda en el dispositivo como un archivo de texto. El nombre del archivo se representa mediante la variable
_fileName
. Agrega la siguiente declaración de variablestring
a la claseNotePage
:public partial class NotePage : ContentPage { string _fileName = Path.Combine(FileSystem.AppDataDirectory, "notes.txt");
El código anterior construye una ruta de acceso al archivo y la almacena en el directorio de datos local de la aplicación. El nombre de archivo es notes.txt.
En el constructor de la clase , después de llamar al método
InitializeComponent
, lee el archivo del dispositivo y almacena su contenido en la propiedadText
del controlTextEditor
public NotePage() { InitializeComponent(); if (File.Exists(_fileName)) TextEditor.Text = File.ReadAllText(_fileName); }
Después, agrega el código para controlar los eventos
Clicked
definidos en el XAML:private void SaveButton_Clicked(object sender, EventArgs e) { // Save the file. File.WriteAllText(_fileName, TextEditor.Text); } private void DeleteButton_Clicked(object sender, EventArgs e) { // Delete the file. if (File.Exists(_fileName)) File.Delete(_fileName); TextEditor.Text = string.Empty; }
El método
SaveButton_Clicked
escribe el texto en el control Editor en el archivo representado por la variable_fileName
.El método
DeleteButton_Clicked
comprueba primero si existe el archivo representado por la variable_fileName
y, en tal caso, lo elimina. Después, se borra el texto del control Editor.Para guardar el archivo, presiona CTRL + S o selecciona el menúArchivo>Guardar NotePage.xaml.cs.
El código final del archivo de código subyacente debería ser similar a lo siguiente:
namespace Notes;
public partial class NotePage : ContentPage
{
string _fileName = Path.Combine(FileSystem.AppDataDirectory, "notes.txt");
public NotePage()
{
InitializeComponent();
if (File.Exists(_fileName))
TextEditor.Text = File.ReadAllText(_fileName);
}
private void SaveButton_Clicked(object sender, EventArgs e)
{
// Save the file.
File.WriteAllText(_fileName, TextEditor.Text);
}
private void DeleteButton_Clicked(object sender, EventArgs e)
{
// Delete the file.
if (File.Exists(_fileName))
File.Delete(_fileName);
TextEditor.Text = string.Empty;
}
}
Probar la nota
Ahora que la página de nota ha finalizado, necesitas una manera de presentarla al usuario. Abre el archivo AppShell.xaml y cambia la primera entrada ShellContent para que apunte a NotePage
en lugar de MainPage
:
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="Notes.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Notes"
Shell.FlyoutBehavior="Disabled">
<TabBar>
<ShellContent
Title="Notes"
ContentTemplate="{DataTemplate local:NotePage}"
Icon="{OnPlatform 'icon_notes.png', iOS='icon_notes_ios.png', MacCatalyst='icon_notes_ios.png'}" />
<ShellContent
Title="About"
ContentTemplate="{DataTemplate local:AboutPage}"
Icon="{OnPlatform 'icon_about.png', iOS='icon_about_ios.png', MacCatalyst='icon_about_ios.png'}" />
</TabBar>
</Shell>
Guarda el archivo y ejecuta la aplicación. Intenta escribir en el cuadro de entrada y presiona el botón Guardar. Cierra la aplicación y vuelve a ejecutarla. La nota especificada debe cargarse desde el almacenamiento del dispositivo.
Enlazar datos a la interfaz de usuario y navegar por páginas
En esta parte del tutorial se presentan los conceptos de vistas, modelos y navegación desde la aplicación.
En los pasos anteriores del tutorial, agregaste dos páginas al proyecto: NotePage
y AboutPage
. Las páginas representan una vista de datos. NotePage
es una "vista" que muestra "datos de notas" y AboutPage
es una "vista" que muestra "datos de información de la aplicación". Ambas vistas tienen un modelo de esos datos codificados o incrustados en ellas, y tendrás que separar el modelo de datos de la vista.
¿Cuál es la ventaja de separar el modelo de la vista? Permite diseñar la vista para representar e interactuar con cualquier parte del modelo sin preocuparte por el código real que implementa el modelo. Esto se logra mediante el enlace de datos, algo que se presentará más adelante en este tutorial. Por ahora, vamos a reestructurar el proyecto.
Separar la vista y el modelo
Refactoriza el código existente para separar el modelo de la vista. Los pasos siguientes organizarán el código para que las vistas y los modelos se definan por separado entre sí.
Elimine MainPage.xaml y MainPage.xaml.cs del proyecto, ya no son necesarios. En el panel Explorador de soluciones, busque la entrada de MainPage.xaml, haga clic con el botón derecho en ella y seleccione Eliminar.
Sugerencia
Al eliminar el elemento MainPage.xaml también debes eliminar el elemento MainPage.xaml.cs. Si MainPage.xaml.cs no se eliminó, haz clic con el botón derecho en él y selecciona Eliminar.
Haz clic con el botón derecho en el proyecto Notes y selecciona Agregar>Nueva carpeta. Asigne a la carpeta el nombre Models.
Haz clic con el botón derecho en el proyecto Notes y selecciona Agregar>Nueva carpeta. Asigne a la carpeta el nombre Views.
Busca el elemento NotePage.xaml y arrástralo a la carpeta Views. NotePage.xaml.cs debe moverse con él.
Importante
Al mover un archivo, Visual Studio normalmente te muestra una advertencia de que la operación de traslado puede tardar mucho tiempo. Esto no debería ser un problema aquí, presiona Aceptar si ves esta advertencia.
Visual Studio también puede preguntar si deseas ajustar el espacio de nombres del archivo trasladado. Selecciona No ya que los pasos siguientes cambiarán el espacio de nombres.
Busca el elemento AboutPage.xaml y arrástralo a la carpeta Views. AboutPage.xaml.cs debe moverse con él.
Actualización del espacio de nombres
Ahora que las vistas se han movido a la carpeta Views, deberá actualizar los espacios de nombres para que coincidan. El espacio de nombres para XAML y los archivos de código subyacente de las páginas se establece en Notes
. Debe actualizarse a Notes.Views
.
En el panel Explorador de soluciones, expande NotePage.xaml y AboutPage.xaml para mostrar los archivos de código subyacente:
Haz doble clic en el elemento NotePage.xaml.cs para abrir el editor de código. Cambia el espacio de nombres a
Notes.Views
.namespace Notes.Views;
Repita los pasos anteriores para el elemento AboutPage.xaml.cs.
Haz doble clic en el elemento NotePage.xaml para abrir el editor XAML. La referencia al espacio de nombres antiguo se hace a través del atributo
x:Class
, que define qué tipo de clase es el código subyacente para XAML. Esta entrada no es solo el espacio de nombres, sino el espacio de nombres con el tipo. Cambie el valor dex:Class
aNotes.Views.NotePage
:x:Class="Notes.Views.NotePage"
Repite el paso anterior para el elemento AboutPage.xaml, pero establece el valor
x:Class
enNotes.Views.AboutPage
.
Corrección de la referencia de espacio de nombres en Shell
AppShell.xaml define dos pestañas, una para NotesPage
y otra para AboutPage
. Ahora que esas dos páginas se movieron a un nuevo espacio de nombres, la asignación de tipos en XAML ahora no es válida. En el panel Explorador de soluciones, haz doble clic en la entrada AppShell.xaml para abrir el editor XAML. Debería tener el aspecto del siguiente fragmento:
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="Notes.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:local="clr-namespace:Notes"
Shell.FlyoutBehavior="Disabled">
<TabBar>
<ShellContent
Title="Notes"
ContentTemplate="{DataTemplate local:NotePage}"
Icon="{OnPlatform 'icon_notes.png', iOS='icon_notes_ios.png', MacCatalyst='icon_notes_ios.png'}" />
<ShellContent
Title="About"
ContentTemplate="{DataTemplate local:AboutPage}"
Icon="{OnPlatform 'icon_about.png', iOS='icon_about_ios.png', MacCatalyst='icon_about_ios.png'}" />
</TabBar>
</Shell>
Se importa un espacio de nombres .NET en el XAML a través de una declaración de espacio de nombres XML. En el marcado XAML anterior, es el atributo xmlns:local="clr-namespace:Notes"
del elemento raíz: <Shell>
. El formato de declarar un espacio de nombres XML para importar un espacio de nombres .NET en el mismo ensamblado es:
xmlns:{XML namespace name}="clr-namespace:{.NET namespace}"
Por lo tanto, la declaración anterior asigna el espacio de nombres XML de local
al espacio de nombres .NET de Notes
. Es habitual asignar el nombre local
al espacio de nombres raíz del proyecto.
Quita el espacio de nombres XML local
y agrega uno nuevo. Este nuevo espacio de nombres XML se asignará al espacio de nombres .NET de Notes.Views
, así que llámalo views
. La declaración debe tener un aspecto similar al siguiente atributo: xmlns:views="clr-namespace:Notes.Views"
.
Las propiedades ShellContent.ContentTemplate
usaron el espacio de nombres XML local
; cámbialas a views
. El XAML debe tener un aspecto parecido al siguiente fragmento:
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="Notes.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Notes.Views"
Shell.FlyoutBehavior="Disabled">
<TabBar>
<ShellContent
Title="Notes"
ContentTemplate="{DataTemplate views:NotePage}"
Icon="{OnPlatform 'icon_notes.png', iOS='icon_notes_ios.png', MacCatalyst='icon_notes_ios.png'}" />
<ShellContent
Title="About"
ContentTemplate="{DataTemplate views:AboutPage}"
Icon="{OnPlatform 'icon_about.png', iOS='icon_about_ios.png', MacCatalyst='icon_about_ios.png'}" />
</TabBar>
</Shell>
Ahora deberías poder ejecutar la aplicación sin errores del compilador y todo debería seguir funcionando como antes.
Definición del modelo
Actualmente, el modelo es los datos incrustados en la nota y sobre las vistas. Crearemos nuevas clases para representar esos datos. Primero, el modelo para representar los datos de una página de notas:
En el panel Explorador de soluciones, haz clic con el botón derecho en la carpeta Models y selecciona Agregar>Clase....
Nombra la clase Note.cs y presiona Agregar.
Abre Note.csy reemplaza el código por el siguiente fragmento:
namespace Notes.Models; internal class Note { public string Filename { get; set; } public string Text { get; set; } public DateTime Date { get; set; } }
Guarde el archivo.
Luego crea el modelo de acerca de la página:
En el panel Explorador de soluciones, haz clic con el botón derecho en la carpeta Models y selecciona Agregar>Clase....
Nombra la clase About.cs y presiona Agregar.
Abre About.csy reemplaza el código por el siguiente fragmento:
namespace Notes.Models; internal class About { public string Title => AppInfo.Name; public string Version => AppInfo.VersionString; public string MoreInfoUrl => "https://aka.ms/maui"; public string Message => "This app is written in XAML and C# with .NET MAUI."; }
Guarde el archivo.
Actualización de la página de acerca de
La página acerca de será la página más rápida de actualizar y podrás ejecutar la aplicación y ver cómo carga datos del modelo.
En el panel Explorador de soluciones, abre el archivo Views\AboutPage.xaml.
Reemplaza el contenido por el fragmento de código siguiente:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" xmlns:models="clr-namespace:Notes.Models" x:Class="Notes.Views.AboutPage"> <ContentPage.BindingContext> <models:About /> </ContentPage.BindingContext> <VerticalStackLayout Spacing="10" Margin="10"> <HorizontalStackLayout Spacing="10"> <Image Source="dotnet_bot.png" SemanticProperties.Description="The dot net bot waving hello!" HeightRequest="64" /> <Label FontSize="22" FontAttributes="Bold" Text="{Binding Title}" VerticalOptions="End" /> <Label FontSize="22" Text="{Binding Version}" VerticalOptions="End" /> </HorizontalStackLayout> <Label Text="{Binding Message}" /> <Button Text="Learn more..." Clicked="LearnMore_Clicked" /> </VerticalStackLayout> </ContentPage>
Echemos un vistazo a las líneas modificadas, resaltadas en el fragmento de código anterior:
xmlns:models="clr-namespace:Notes.Models"
Esta línea asigna el espacio de nombres .NET de
Notes.Models
al espacio de nombres XML demodels
.La propiedad
BindingContext
de ContentPage se establece en una instancia de la claseNote.Models.About
utilizando el espacio de nombres XML y el objeto demodels:About
. Esto se estableció mediante sintaxis de elementos de propiedad en lugar de un atributo XML.Importante
Hasta ahora, las propiedades se han establecido mediante un atributo XML. Esto funciona bien con valores simples, como una propiedad
Label.FontSize
. Pero si el valor de la propiedad es más complejo, debes usar sintaxis de elementos de propiedad para crear el objeto. Considera el ejemplo siguiente de creación de una etiqueta con su propiedadFontSize
configurada:<Label FontSize="22" />
La misma propiedad
FontSize
se puede establecer mediante sintaxis de elementos de propiedad:<Label> <Label.FontSize> 22 </Label.FontSize> </Label>
Tres controles
<Label>
tenían su valor de propiedadText
cambiado de una cadena codificada de forma rígida a sintaxis de enlace:{Binding PATH}
.La sintaxis
{Binding}
se procesa en tiempo de ejecución, lo que permite que el valor devuelto del enlace sea dinámico. La partePATH
de{Binding PATH}
es la ruta de acceso de la propiedad a la que se va a enlazar. La propiedad procede delBindingContext
del control actual. Con el control<Label>
, no se estableceBindingContext
. El contexto se hereda del elemento principal cuando no está configurado por el control. En este caso, el contexto de configuración del objeto principal es el objeto raíz: ContentPage.El objeto en
BindingContext
es una instancia del modeloAbout
. La ruta de acceso de enlace de una de las etiquetas enlaza la propiedadLabel.Text
a la propiedadAbout.Title
.
El cambio final a la página de acerca de es actualizar el clic del botón que abre una página web. La dirección URL se ha codificado de forma rígida en el código subyacente, pero la dirección URL debe provenir del modelo que se encuentra en la propiedad BindingContext
.
En el panel Explorador de soluciones, abre el archivo Views\AboutPage.xaml.cs.
Reemplace el método
LearnMore_Clicked
con el código siguiente:private async void LearnMore_Clicked(object sender, EventArgs e) { if (BindingContext is Models.About about) { // Navigate to the specified URL in the system browser. await Launcher.Default.OpenAsync(about.MoreInfoUrl); } }
Si observa la línea resaltada, el código comprueba si BindingContext
es un Models.About
tipo y, si es así, lo asigna a la about
variable . La siguiente línea dentro de la instrucción if
abre el explorador a la dirección URL proporcionada por la propiedad about.MoreInfoUrl
.
Ejecuta la aplicación y debería ver que se ejecuta exactamente igual que antes. Intenta cambiar los valores del modelo y ve cómo también cambia la interfaz de usuario y la dirección URL abiertas por el explorador.
Actualizar la página Note
La sección anterior enlazaba la vista de página about al modelo about y ahora hará lo mismo, enlazando la vista note al modelo note. Sin embargo, en este caso, el modelo no se creará en XAML, pero se proporcionará en el código subyacente en los pasos siguientes.
En el panel Explorador de soluciones, abre el archivo Views\NotePage.xaml.
Cambia el control
<Editor>
agregando la propiedadText
. Enlaza la propiedad a la propiedadText
:<Editor ... Text="{Binding Text}"
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Notes.Views.NotePage" Title="Note"> <VerticalStackLayout Spacing="10" Margin="5"> <Editor x:Name="TextEditor" Placeholder="Enter your note" Text="{Binding Text}" HeightRequest="100" /> <Grid ColumnDefinitions="*,*" ColumnSpacing="4"> <Button Text="Save" Clicked="SaveButton_Clicked" /> <Button Grid.Column="1" Text="Delete" Clicked="DeleteButton_Clicked" /> </Grid> </VerticalStackLayout> </ContentPage>
Las modificaciones del código subyacente son más complicadas que el XAML. El código actual carga el contenido del archivo en el constructor y después lo establece directamente en la propiedad TextEditor.Text
. Este es el aspecto del código actual:
public NotePage()
{
InitializeComponent();
if (File.Exists(_fileName))
TextEditor.Text = File.ReadAllText(_fileName);
}
En lugar de cargar la nota en el constructor, crea un nuevo método LoadNote
. Este método hará lo siguiente:
- Acepta un parámetro de nombre de archivo.
- Crea un nuevo modelo de nota y establece el nombre de archivo.
- Si el archivo existe, carga su contenido en el modelo.
- Si el archivo existe, actualiza el modelo con la fecha en que se creó el archivo.
- Establece la
BindingContext
de la página en el modelo.
En el panel Explorador de soluciones, abre el archivo Views\NotePage.xaml.cs.
Agregue el siguiente método a la clase :
private void LoadNote(string fileName) { Models.Note noteModel = new Models.Note(); noteModel.Filename = fileName; if (File.Exists(fileName)) { noteModel.Date = File.GetCreationTime(fileName); noteModel.Text = File.ReadAllText(fileName); } BindingContext = noteModel; }
Actualiza el constructor de clase para llamar a
LoadNote
. El nombre de archivo de la nota debe ser un nombre generado aleatoriamente que se creará en el directorio de datos local de la aplicación.public NotePage() { InitializeComponent(); string appDataPath = FileSystem.AppDataDirectory; string randomFileName = $"{Path.GetRandomFileName()}.notes.txt"; LoadNote(Path.Combine(appDataPath, randomFileName)); }
Agregar una vista y un modelo que muestre todas las notas
Esta parte del tutorial agrega la parte final de la aplicación, una vista que muestra todas las notas creadas anteriormente.
Varias notas y navegación
Actualmente, la vista de nota muestra una sola nota. Para mostrar varias notas, crea una nueva vista y un modelo: AllNotes.
- En el panel Explorador de soluciones, haz clic con el botón derecho en la carpeta Views y selecciona Agregar>Nuevo elemento...
- En el cuadro de diálogo Agregar nuevo elemento, selecciona .NET MAUI en la lista de plantillas del lado izquierdo de la ventana. Después, selecciona la plantilla .NET MAUI ContentPage (XAML). Asigna al archivo el nombre AllNotesPage.xaml y después selecciona Agregar.
- En el Explorador de soluciones, haz clic con el botón derecho en la carpeta Models y selecciona Agregar>Clase...
- Asigna un nombre a la clase AllNotes.cs y presiona Agregar.
Codificar el modelo de AllNotes
El nuevo modelo representará los datos necesarios para mostrar varias notas. Estos datos serán una propiedad que representa una colección de notas. La colección será una ObservableCollection
, que es una colección especializada. Cuando un control que enumera varios elementos, como un ListView, está enlazado a un ObservableCollection
, los dos funcionan juntos para mantener automáticamente la lista de elementos sincronizados con la colección. Si la lista agrega un elemento, la colección se actualiza. Si la colección agrega un elemento, el control se actualiza automáticamente con un nuevo elemento.
En el panel Explorador de soluciones, abre el archivo Models\AllNotes.cs.
Reemplaza todo el código por el fragmento siguiente:
using System.Collections.ObjectModel; namespace Notes.Models; internal class AllNotes { public ObservableCollection<Note> Notes { get; set; } = new ObservableCollection<Note>(); public AllNotes() => LoadNotes(); public void LoadNotes() { Notes.Clear(); // Get the folder where the notes are stored. string appDataPath = FileSystem.AppDataDirectory; // Use Linq extensions to load the *.notes.txt files. IEnumerable<Note> notes = Directory // Select the file names from the directory .EnumerateFiles(appDataPath, "*.notes.txt") // Each file name is used to create a new Note .Select(filename => new Note() { Filename = filename, Text = File.ReadAllText(filename), Date = File.GetLastWriteTime(filename) }) // With the final collection of notes, order them by date .OrderBy(note => note.Date); // Add each note into the ObservableCollection foreach (Note note in notes) Notes.Add(note); } }
El código anterior declara una colección, denominada Notes
, y usa el método LoadNotes
para cargar notas del dispositivo. Este método usa extensiones LINQ para cargar, transformar y ordenar los datos en la colección Notes
.
Diseñar la página AllNotes
A continuación, la vista debe diseñarse para admitir el modelo AllNotes.
En el panel Explorador de soluciones, abre el archivo Views\AllNotesPage.xaml.
Reemplaza el código con el marcado siguiente:
<?xml version="1.0" encoding="utf-8" ?> <ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui" xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml" x:Class="Notes.Views.AllNotesPage" Title="Your Notes"> <!-- Add an item to the toolbar --> <ContentPage.ToolbarItems> <ToolbarItem Text="Add" Clicked="Add_Clicked" IconImageSource="{FontImage Glyph='+', Color=Black, Size=22}" /> </ContentPage.ToolbarItems> <!-- Display notes in a list --> <CollectionView x:Name="notesCollection" ItemsSource="{Binding Notes}" Margin="20" SelectionMode="Single" SelectionChanged="notesCollection_SelectionChanged"> <!-- Designate how the collection of items are laid out --> <CollectionView.ItemsLayout> <LinearItemsLayout Orientation="Vertical" ItemSpacing="10" /> </CollectionView.ItemsLayout> <!-- Define the appearance of each item in the list --> <CollectionView.ItemTemplate> <DataTemplate> <StackLayout> <Label Text="{Binding Text}" FontSize="22"/> <Label Text="{Binding Date}" FontSize="14" TextColor="Silver"/> </StackLayout> </DataTemplate> </CollectionView.ItemTemplate> </CollectionView> </ContentPage>
El XAML anterior presenta algunos conceptos nuevos:
La propiedad
ContentPage.ToolbarItems
contiene unToolbarItem
. Los botones definidos aquí suelen mostrarse en la parte superior de la aplicación, a lo largo del título de la página. Pero dependiendo de la plataforma puede estar en una posición diferente. Cuando se presiona uno de estos botones, se genera el eventoClicked
, igual que un botón normal.La propiedad
ToolbarItem.IconImageSource
establece el icono que se va a mostrar en el botón. El icono puede ser cualquier recurso de imagen definido por el proyecto, pero en este ejemplo se usa unFontImage
. UnFontImage
puede usar un solo glifo de una fuente como una imagen.El control CollectionView muestra una colección de elementos y, en este caso, está enlazado a la propiedad
Notes
del modelo. La forma en que la vista de colección presenta cada elemento se establece a través de las propiedadesCollectionView.ItemsLayout
yCollectionView.ItemTemplate
.Para cada elemento de la colección,
CollectionView.ItemTemplate
genera el XAML declarado. ElBindingContext
ese XAML se convierte en el propio elemento de colección, en este caso, cada nota individual. La plantilla de la nota usa dos etiquetas, que están enlazadas a las propiedadesText
yDate
de la nota.CollectionView Controla el evento
SelectionChanged
, que se genera cuando se selecciona un elemento de la vista de colección.
El código subyacente de la vista debe escribirse para cargar las notas y controlar los eventos.
En el panel Explorador de soluciones, abre el archivo Views/AllNotesPage.xaml.cs.
Reemplaza todo el código por el fragmento siguiente:
namespace Notes.Views; public partial class AllNotesPage : ContentPage { public AllNotesPage() { InitializeComponent(); BindingContext = new Models.AllNotes(); } protected override void OnAppearing() { ((Models.AllNotes)BindingContext).LoadNotes(); } private async void Add_Clicked(object sender, EventArgs e) { await Shell.Current.GoToAsync(nameof(NotePage)); } private async void notesCollection_SelectionChanged(object sender, SelectionChangedEventArgs e) { if (e.CurrentSelection.Count != 0) { // Get the note model var note = (Models.Note)e.CurrentSelection[0]; // Should navigate to "NotePage?ItemId=path\on\device\XYZ.notes.txt" await Shell.Current.GoToAsync($"{nameof(NotePage)}?{nameof(NotePage.ItemId)}={note.Filename}"); // Unselect the UI notesCollection.SelectedItem = null; } } }
Este código usa el constructor para establecer el BindingContext
de la página en el modelo.
El método OnAppearing
se invalida en la clase base. Se llama automáticamente a este método cada vez que se muestra la página, por ejemplo cuando se navega a la página. El código aquí indica al modelo que cargue las notas. Como la CollectionView de la Vista AllNotes está vinculada a la propiedad Notes
del modelo de AllNotes, que es una ObservableCollection
, cada vez que se cargan las notas, la CollectionView se actualiza automáticamente.
El controlador Add_Clicked
presenta otro nuevo concepto: la navegación. Dado que la aplicación usa .NET MAUI Shell, puedes navegar a las páginas llamando al método Shell.Current.GoToAsync
. Observa que el controlador se declara con la palabra clave async
, que permite el uso de la palabra clave await
al navegar. Este controlador navega a NotePage
.
La última parte del código del fragmento de código anterior es el controlador notesCollection_SelectionChanged
. Este método toma el elemento seleccionado actualmente, un modelo Note y usa su información para navegar a NotePage
. GoToAsync usa una cadena de URI para la navegación. En este caso, se construye una cadena que usa un parámetro de cadena de consulta para establecer una propiedad en la página de destino. La cadena interpolada que representa el URI termina con un aspecto similar a la cadena siguiente:
NotePage?ItemId=path\on\device\XYZ.notes.txt
El parámetro ItemId=
se establece en el nombre de archivo en el dispositivo donde se almacena la nota.
Visual Studio puede indicar que la propiedad NotePage.ItemId
no existe, lo que es cierto. El siguiente paso consiste en modificar la vista Note para cargar el modelo en función del parámetro ItemId
que crearás.
Parámetros de cadena de consulta
La vista Note debe admitir el parámetro de cadena de consulta, ItemId
. Créalo ahora:
En el panel Explorador de soluciones, abre el archivo Views/NotePage.xaml.cs.
Agrega el atributo
QueryProperty
a la palabra claveclass
, proporcionando el nombre de la propiedad de cadena de consulta y la propiedad de clase a la que se asigna,ItemId
yItemId
respectivamente:[QueryProperty(nameof(ItemId), nameof(ItemId))] public partial class NotePage : ContentPage
Agrega una propiedad
string
llamadaItemId
. Esta propiedad llama al métodoLoadNote
, pasando el valor de la propiedad, que a su vez debe ser el nombre de archivo de la nota:public string ItemId { set { LoadNote(value); } }
Reemplaza los controladores
SaveButton_Clicked
yDeleteButton_Clicked
con el código siguiente:private async void SaveButton_Clicked(object sender, EventArgs e) { if (BindingContext is Models.Note note) File.WriteAllText(note.Filename, TextEditor.Text); await Shell.Current.GoToAsync(".."); } private async void DeleteButton_Clicked(object sender, EventArgs e) { if (BindingContext is Models.Note note) { // Delete the file. if (File.Exists(note.Filename)) File.Delete(note.Filename); } await Shell.Current.GoToAsync(".."); }
Los botones ahora son
async
. Una vez presionados, la página vuelve a la página anterior mediante un URI de..
.Elimina la variable
_fileName
de la parte superior del código, ya que ya no la usa la clase.
Modificación del árbol visual de la aplicación
AppShell
todavía está cargando la página de una sola nota, pero debe cargar la vista AllPages en su lugar. Abre el archivo AppShell.xaml y cambia la primera entrada ShellContent para que apunte a AllNotesPage
en lugar de NotePage
:
<?xml version="1.0" encoding="UTF-8" ?>
<Shell
x:Class="Notes.AppShell"
xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
xmlns:views="clr-namespace:Notes.Views"
Shell.FlyoutBehavior="Disabled">
<TabBar>
<ShellContent
Title="Notes"
ContentTemplate="{DataTemplate views:AllNotesPage}"
Icon="{OnPlatform 'icon_notes.png', iOS='icon_notes_ios.png', MacCatalyst='icon_notes_ios.png'}" />
<ShellContent
Title="About"
ContentTemplate="{DataTemplate views:AboutPage}"
Icon="{OnPlatform 'icon_about.png', iOS='icon_about_ios.png', MacCatalyst='icon_about_ios.png'}" />
</TabBar>
</Shell>
Si ejecutas la aplicación ahora, observarás que se bloquea si presionas el botón Agregar, indicando que no puede navegar a NotesPage
. Todas las páginas a las que se puede navegar desde otra página deben registrarse con el sistema de navegación. Las páginas AllNotesPage
y AboutPage
se registran automáticamente con el sistema de navegación mediante la declaración en TabBar.
Registra el NotesPage
con el sistema de navegación:
En el Explorador de soluciones, abre el archivo AppShell.xaml.cs.
Agrega una línea al constructor que registra la ruta de navegación:
namespace Notes; public partial class AppShell : Shell { public AppShell() { InitializeComponent(); Routing.RegisterRoute(nameof(Views.NotePage), typeof(Views.NotePage)); } }
El método Routing.RegisterRoute
toma dos parámetros:
- El primer parámetro es el nombre de cadena del URI que deseas registrar, en este caso el nombre resuelto es
"NotePage"
. - El segundo parámetro es el tipo de página que se va a cargar cuando se navega hasta
"NotePage"
.
Ahora, puedes ejecutar la aplicación. Intenta agregar nuevas notas, navegar hacia atrás y hacia delante entre notas, y eliminar notas.
Explorar el código de este tutorial.. Si desea descargar una copia del proyecto completado con el que comparar el código, descargue este proyecto.
¡Enhorabuena!
Has completado el tutorial de creación de una aplicación .NET MAUI.
Pasos siguientes
En la siguiente parte de la serie de tutoriales aprenderás a implementar patrones model-view-viewmodel (MVVM) en el proyecto.
Los vínculos siguientes proporcionan más información relacionada con algunos de los conceptos que has aprendido en este tutorial:
¿Tiene algún problema con esta sección? Si es así, envíenos sus comentarios para que podamos mejorarla.