Comportamientos

Browse sample.Examinar la muestra

Los comportamientos de .NET Multi-platform App UI (.NET MAUI) permiten agregar funciones a los controles de la interfaz de usuario sin tener que incluirlos en subclases. En su lugar, la función se implementa en una clase de comportamiento y se asocia al control como si fuera parte de este.

Los comportamientos permiten implementar código que normalmente tendría que escribirse como código subyacente, ya que interactúan directamente con la API del control, de manera que pueden asociarse al control de manera concisa y empaquetarse para reutilizarlos en más de una aplicación. Pueden usarse para proporcionar una amplia variedad de funciones para los controles, como:

  • Agregar un validador de correo electrónico a un elemento Entry.
  • Crear un control de calificación mediante un reconocedor de gestos de pulsar.
  • Controlar una animación.

.NET MAUI admite tres tipos diferentes de comportamientos:

Comportamientos asociados

Los comportamientos asociados son clases estáticas con una o varias propiedades asociadas. Una propiedad asociada es un tipo especial de propiedad enlazable. Se define en una clase, pero se asocia a otros objetos y puede reconocerse en XAML como un atributo que contiene una clase y un nombre de propiedad separado por un punto. Para obtener más información sobre las propiedades adjuntas, consulta Propiedades adjuntas.

Una propiedad asociada puede definir un delegado propertyChanged que se ejecutará cuando cambie el valor de la propiedad (por ejemplo, cuando la propiedad se establezca en un control). Cuando se ejecute el delegado propertyChanged, se pasará una referencia al control donde esté asociado, así como los parámetros que contienen los valores nuevos y anteriores de la propiedad. Este delegado puede usarse para agregar nuevas funciones al control al que se asocie la propiedad; para hacerlo, se manipula la referencia en la que se pasa, como se muestra a continuación:

  1. El delegado propertyChanged transmite la referencia del control, que se recibe como un elemento BindableObject, al tipo de control cuyo comportamiento se ha diseñado para mejorar.
  2. El delegado propertyChanged modifica las propiedades del control, llama a métodos del control o registra controladores de eventos expuestos por el control para implementar la función básica de comportamiento.

Advertencia

Los comportamientos asociados es que se definen en una clase static, con propiedades y métodos de static. Esto hace que sea difícil crear comportamientos asociados que tengan un estado.

Crear un comportamiento asociado

Un comportamiento asociado se puede implementar mediante la creación de una clase estática que contiene una propiedad adjunta que especifica un delegado propertyChanged.

En el ejemplo siguiente se muestra la clase AttachedNumericValidationBehavior, que resalta el valor especificado por el usuario en un control Entry en rojo si no es double:

public static class AttachedNumericValidationBehavior
{
    public static readonly BindableProperty AttachBehaviorProperty =
        BindableProperty.CreateAttached("AttachBehavior", typeof(bool), typeof(AttachedNumericValidationBehavior), false, propertyChanged: OnAttachBehaviorChanged);

    public static bool GetAttachBehavior(BindableObject view)
    {
        return (bool)view.GetValue(AttachBehaviorProperty);
    }

    public static void SetAttachBehavior(BindableObject view, bool value)
    {
        view.SetValue(AttachBehaviorProperty, value);
    }

    static void OnAttachBehaviorChanged(BindableObject view, object oldValue, object newValue)
    {
        Entry entry = view as Entry;
        if (entry == null)
        {
            return;
        }

        bool attachBehavior = (bool)newValue;
        if (attachBehavior)
        {
            entry.TextChanged += OnEntryTextChanged;
        }
        else
        {
            entry.TextChanged -= OnEntryTextChanged;
        }
    }

    static void OnEntryTextChanged(object sender, TextChangedEventArgs args)
    {
        double result;
        bool isValid = double.TryParse(args.NewTextValue, out result);
        ((Entry)sender).TextColor = isValid ? Colors.Black : Colors.Red;
    }
}

En este ejemplo, la clase AttachedNumericValidationBehavior contiene una propiedad adjunta denominada AttachBehavior con un captador y establecedor de static, que controla la adición o eliminación del comportamiento del control al que se va a asociar. Esta propiedad adjunta registra el método OnAttachBehaviorChanged que se ejecutará cuando cambie el valor de la propiedad. Este método registra o anula el registro de un controlador de eventos para el evento TextChanged basándose en el valor de la propiedad asociada AttachBehavior. El método OnEntryTextChanged proporciona la funcionalidad básica del comportamiento y analiza el valor especificado por el usuario en Entry y establece la propiedad TextColor en rojo si el valor no es double.

Consumo de una propiedad adjunta

Se puede consumir un comportamiento adjunto estableciendo su propiedad adjunta en el control de destino.

En el ejemplo siguiente se muestra cómo consumir la clase AttachedNumericValidationBehavior en Entry mediante la adición de la propiedad adjunta AttachBehavior a Entry:


<ContentPage ...
             xmlns:local="clr-namespace:BehaviorsDemos">
    <Entry Placeholder="Enter a System.Double" local:AttachedNumericValidationBehavior.AttachBehavior="true" />
</ContentPage>

El control Entry equivalente en C# se muestra en el ejemplo de código siguiente:

Entry entry = new Entry { Placeholder = "Enter a System.Double" };
AttachedNumericValidationBehavior.SetAttachBehavior(entry, true);

En las capturas de pantalla siguientes, se muestra la respuesta del comportamiento asociado en respuesta a entradas no válidas:

Screenshot of attached behavior responding to invalid input

Nota:

Los comportamientos asociados se escriben para un tipo de control específico (o una superclase que se puede aplicar a muchos controles) y solo se tienen que agregar a un control compatible.

Eliminación de un comportamiento adjunto

La clase AttachedNumericValidationBehavior se puede eliminar mediante el establecimiento de la propiedad adjunta AttachBehavior a false:

<Entry Placeholder="Enter a System.Double" local:AttachedNumericValidationBehavior.AttachBehavior="false" />

En tiempo de ejecución, el método OnAttachBehaviorChanged se ejecutará cuando el valor de la propiedad asociada AttachBehavior se establezca en false. Después, el método OnAttachBehaviorChanged anulará el registro del controlador de eventos del evento TextChanged con el fin de garantizar que el comportamiento no se ejecute cuando el usuario interactúe con el control.

Comportamientos de .NET MAUI

Los comportamientos de .NET MAUI se crean derivando de la clase Behavior o Behavior<T>.

El proceso de creación de un comportamiento de .NET MAUI es el siguiente:

  1. Cree una clase que herede de la clase Behavior o Behavior<T>, donde T sea el tipo del control al que se debe aplicar el comportamiento.
  2. Invalide el método OnAttachedTo para realizar cualquier configuración necesaria.
  3. Invalide el método OnDetachingFrom para realizar cualquier limpieza necesaria.
  4. Implemente la funcionalidad básica del comportamiento.

El resultado es la estructura que se muestra en el ejemplo de código siguiente:

public class MyBehavior : Behavior<View>
{
    protected override void OnAttachedTo(View bindable)
    {
        base.OnAttachedTo(bindable);
        // Perform setup
    }

    protected override void OnDetachingFrom(View bindable)
    {
        base.OnDetachingFrom(bindable);
        // Perform clean up
    }

    // Behavior implementation
}

El método OnAttachedTo se llama inmediatamente después de adjuntar el comportamiento a un control. Este método recibe una referencia al control al que se adjunta, y se puede usar para registrar controladores de eventos o realizar otra configuración necesaria para admitir la funcionalidad del comportamiento. Por ejemplo, se podría suscribir a un evento en un control. Después, se implementaría la funcionalidad del comportamiento en el controlador de eventos para el evento.

El método OnDetachingFrom se llama cuando se quita el comportamiento del control. Este método recibe una referencia al control al que se adjunta y se usa para realizar cualquier limpieza necesaria. Por ejemplo, podría cancelar la suscripción a un evento en un control para evitar fugas de memoria.

Después, el comportamiento se puede consumir adjuntándolo a la colección Behaviors del control.

Crear un comportamiento de .NET MAUI

Un comportamiento de .NET MAUI se puede implementar creando una clase que deriva de la clase Behavior o Behavior<T> y que reemplaza los OnAttachedTo métodos y OnDetachingFrom.

En el ejemplo siguiente se muestra la clase NumericValidationBehavior, que resalta el valor especificado por el usuario en un control Entry en rojo si no es double:

public class NumericValidationBehavior : Behavior<Entry>
{
    protected override void OnAttachedTo(Entry entry)
    {
        entry.TextChanged += OnEntryTextChanged;
        base.OnAttachedTo(entry);
    }

    protected override void OnDetachingFrom(Entry entry)
    {
        entry.TextChanged -= OnEntryTextChanged;
        base.OnDetachingFrom(entry);
    }

    void OnEntryTextChanged(object sender, TextChangedEventArgs args)
    {
        double result;
        bool isValid = double.TryParse(args.NewTextValue, out result);
        ((Entry)sender).TextColor = isValid ? Colors.Black : Colors.Red;
    }
}

En este ejemplo, la clase NumericValidationBehavior se deriva de la clase Behavior<T> donde T es un control Entry. El método OnAttachedTo registra un controlador de eventos para el evento TextChanged, y el método OnDetachingFrom anula el registro del evento TextChanged para evitar fugas de memoria. El método OnEntryTextChanged proporciona la funcionalidad básica del comportamiento y analiza el valor especificado por el usuario en Entry y establece la propiedad TextColor en rojo si el valor no es double.

Importante

.NET MAUI no establece el elemento BindingContext de un comportamiento, ya que los comportamientos se pueden compartir y aplicar a varios controles mediante estilos.

Consumir un comportamiento de .NET MAUI

Cada control de .NET MAUI tiene una colección Behaviors, a la que se pueden agregar uno o varios comportamientos:

<Entry Placeholder="Enter a System.Double">
    <Entry.Behaviors>
        <local:NumericValidationBehavior />
    </Entry.Behaviors>
</Entry>

El control Entry equivalente en C# se muestra en el ejemplo de código siguiente:

Entry entry = new Entry { Placeholder = "Enter a System.Double" };
entry.Behaviors.Add(new NumericValidationBehavior());

En las capturas de pantalla siguientes se muestra la respuesta del comportamiento de .NET MAUI a entrada no válida:

Screenshot of .NET MAUI behavior responding to invalid input

Advertencia

Los comportamientos de .NET MAUI se escriben para un tipo de control específico (o una superclase que se puede aplicar a muchos controles) y solo se deben agregar a un control compatible. El intento de adjuntar un comportamiento de .NET MAUI a un control incompatible iniciará una excepción.

Consumir un comportamiento de .NET MAUI con un estilo

Los comportamientos de .NET MAUI también se pueden consumir mediante un estilo explícito o implícito. Pero no se puede crear un estilo que establece la propiedad Behaviors de un control porque la propiedad es de solo lectura. La solución consiste en agregar una propiedad adjunta a la clase de comportamiento que controle la adición y eliminación del comportamiento. El proceso es el siguiente:

  1. Se agrega una propiedad adjunta a la clase de comportamiento que se usará para controlar la adición o eliminación del comportamiento del control al que se va a conectar el comportamiento. Se asegura que la propiedad adjunta registra un delegado propertyChanged que se ejecutará cuando cambie el valor de la propiedad.
  2. Se crea un captador y establecedor static para la propiedad adjunta.
  3. Se implementa la lógica en el delegado propertyChanged para agregar y quitar el comportamiento.

En el ejemplo siguiente se muestra la clase NumericValidationStyleBehavior, que tiene una propiedad adjunta que controla la adición y eliminación del comportamiento:

public class NumericValidationStyleBehavior : Behavior<Entry>
{
    public static readonly BindableProperty AttachBehaviorProperty =
        BindableProperty.CreateAttached("AttachBehavior", typeof(bool), typeof(NumericValidationStyleBehavior), false, propertyChanged: OnAttachBehaviorChanged);

    public static bool GetAttachBehavior(BindableObject view)
    {
        return (bool)view.GetValue(AttachBehaviorProperty);
    }

    public static void SetAttachBehavior(BindableObject view, bool value)
    {
        view.SetValue(AttachBehaviorProperty, value);
    }

    static void OnAttachBehaviorChanged(BindableObject view, object oldValue, object newValue)
    {
        Entry entry = view as Entry;
        if (entry == null)
        {
            return;
        }

        bool attachBehavior = (bool)newValue;
        if (attachBehavior)
        {
            entry.Behaviors.Add(new NumericValidationStyleBehavior());
        }
        else
        {
            Behavior toRemove = entry.Behaviors.FirstOrDefault(b => b is NumericValidationStyleBehavior);
            if (toRemove != null)
            {
                entry.Behaviors.Remove(toRemove);
            }
        }
    }

    protected override void OnAttachedTo(Entry entry)
    {
        entry.TextChanged += OnEntryTextChanged;
        base.OnAttachedTo(entry);
    }

    protected override void OnDetachingFrom(Entry entry)
    {
        entry.TextChanged -= OnEntryTextChanged;
        base.OnDetachingFrom(entry);
    }

    void OnEntryTextChanged(object sender, TextChangedEventArgs args)
    {
        double result;
        bool isValid = double.TryParse(args.NewTextValue, out result);
        ((Entry)sender).TextColor = isValid ? Colors.Black : Colors.Red;
    }
}

En este ejemplo, la clase NumericValidationStyleBehavior contiene una propiedad adjunta denominada AttachBehavior con un captador y establecedor de static, que controla la adición o eliminación del comportamiento del control al que se va a asociar. Esta propiedad adjunta registra el método OnAttachBehaviorChanged que se ejecutará cuando cambie el valor de la propiedad. Este método agrega o quita el comportamiento del control, en función del valor de la propiedad adjunta AttachBehavior.

En el ejemplo de código siguiente se muestra un estilo explícito para el elemento NumericValidationStyleBehavior que usa la propiedad adjunta AttachBehavior y que se puede aplicar a controles Entry:

<Style x:Key="NumericValidationStyle" TargetType="Entry">
    <Style.Setters>
        <Setter Property="local:NumericValidationStyleBehavior.AttachBehavior" Value="true" />
    </Style.Setters>
</Style>

Style se puede aplicar a un Entry estableciendo su propiedad Style en el estilo mediante la extensión de marcado StaticResource:

<Entry Placeholder="Enter a System.Double" Style="{StaticResource NumericValidationStyle}">

Para obtener más información sobre los estilos, consulta Estilos.

Nota:

Aunque se pueden agregar propiedades enlazables a un comportamiento que se establece o se consulta en XAML, si se crean comportamientos que tienen estado, no se deberían compartir entre los controles en un elemento Style de un objeto ResourceDictionary.

Eliminación de un comportamiento de .NET MAUI

El método OnDetachingFrom se desencadena cuando se quita un comportamiento de un control, y se usa para realizar cualquier limpieza necesaria como cancelar la suscripción de un evento para evitar una pérdida de memoria. Pero los comportamientos no se quitan de forma implícita de los controles a menos que un método Remove o Clear modifique la colección Behaviors del control.

Behavior toRemove = entry.Behaviors.FirstOrDefault(b => b is NumericValidationStyleBehavior);
if (toRemove != null)
{
    entry.Behaviors.Remove(toRemove);
}

Como alternativa, la colección Behaviors del control se puede borrar:

entry.Behaviors.Clear();

Nota:

Los comportamientos de .NET MAUI no se quitan de forma implícita de los controles cuando se extraen páginas de la pila de navegación. En su lugar, se deben quitar explícitamente antes de que las páginas queden fuera del ámbito.

Comportamientos de la plataforma

Los comportamientos de la plataforma se crean mediante la derivación de la clase PlatformBehavior<TView> o PlatformBehavior<TView,TPlatformView>. Responden a condiciones arbitrarias y eventos en un control nativo.

Un comportamiento de la plataforma se puede implementar mediante la compilación condicional o clases parciales. El enfoque adoptado aquí es usar clases parciales, donde un comportamiento de plataforma normalmente consta de una clase parcial multiplataforma que define la API de comportamientos y una clase parcial nativa que implementa el comportamiento en cada plataforma. En tiempo de compilación, la compatibilidad con múltiples versiones combina las clases parciales para compilar el comportamiento de plataforma en cada plataforma.

El proceso de creación de un comportamiento de plataforma es el siguiente:

  1. Crea una clase parcial multiplataforma que defina la API para el comportamiento de la plataforma.

  2. Crea una clase parcial nativa en cada plataforma para la que se compila la aplicación, que tiene el mismo nombre que la clase parcial multiplataforma. Esta clase parcial nativa debe heredar de la clase PlatformBehavior<TView> o PlatformBehavior<TView,TPlatformView> , donde TView es el control multiplataforma al que se debe aplicar el comportamiento y TPlatformView es la vista nativa que implementa el control multiplataforma en una plataforma determinada.

    Nota:

    Aunque es necesario crear una clase parcial nativa en cada plataforma para la que se compila la aplicación, no es necesario implementar la funcionalidad de comportamiento de la plataforma en cada plataforma. Por ejemplo, puedes crear un comportamiento de plataforma que modifique el grosor del borde de un control nativo en algunas plataformas, pero no todas.

  3. En cada clase parcial nativa que necesites para implementar el comportamiento de la plataforma, debes:

    1. Invalidar el método OnAttachedTo para realizar cualquier configuración.
    2. Invalidar el método OnDetachedFrom para realizar cualquier limpieza.
    3. Implementar la funcionalidad básica del comportamiento de plataforma.

Después, el comportamiento se puede consumir adjuntándolo a la colección Behaviors del control.

Creación de un comportamiento de plataforma

Para crear un comportamiento de plataforma, primero debes crear una clase parcial multiplataforma que defina la API para el comportamiento de la plataforma:

namespace BehaviorsDemos
{
    public partial class TintColorBehavior
    {
        public static readonly BindableProperty TintColorProperty =
            BindableProperty.Create(nameof(TintColor), typeof(Color), typeof(TintColorBehavior));

        public Color TintColor
        {
            get => (Color)GetValue(TintColorProperty);
            set => SetValue(TintColorProperty, value);
        }
    }
}

El comportamiento de plataforma es una clase parcial cuya implementación se completará en cada plataforma necesaria con una clase parcial adicional que use el mismo nombre. En este ejemplo, la clase TintColorBehavior define una sola propiedad enlazable, TintColor, que tintará una imagen con el color especificado.

Después de crear la clase parcial multiplataforma, debes crear una clase parcial nativa en cada plataforma para la que compiles la aplicación. Esto se puede lograr agregando clases parciales a las carpetas secundarias necesarias de la carpeta Plataforms:

Screenshot of the native partial classes for a platform behavior.

Como alternativa, puedes configurar el proyecto para admitir la compatibilidad con múltiples versiones basada en nombre de archivo, o la compatibilidad con múltiples versiones basada en carpetas, o ambas. Para obtener más información sobre la compatibilidad con múltiples versiones, consulta Configuración de compatibilidad con múltiples versiones.

Las clases parciales nativas deben heredar de la clase PlatformBehavior<TView> o de la clase PlatformBehavior<TView,TPlatformView>, donde TView es el control multiplataforma al que se debe aplicar el comportamiento y TPlatformView es la vista nativa que implementa el control multiplataforma en una plataforma determinada. En cada clase parcial nativa que necesites para implementar el comportamiento de plataforma, debes invalidar el método OnAttachedTo y el método OnDetachedFrom e implementar la funcionalidad principal del comportamiento de la plataforma.

El método OnAttachedTo se desencadena justo después de que se adjunte el comportamiento de plataforma a un control multiplataforma. El método recibe una referencia al control multiplataforma al que está asociado y, opcionalmente, una referencia al control nativo que implementa el control multiplataforma. Este método se puede usar para registrar controladores de eventos o realizar otra configuración necesaria para admitir la funcionalidad del comportamiento de plataforma. Por ejemplo, se podría suscribir a un evento en un control. Después, se implementaría la funcionalidad del comportamiento en el controlador de eventos para el evento.

El método OnDetachedFrom se desencadena cuando se quita el comportamiento del control multiplataforma. El método recibe una referencia al control al que está asociado y, opcionalmente, una referencia al control nativo que implementa el control multiplataforma. El método se debe usar para realizar cualquier limpieza necesaria. Por ejemplo, podría cancelar la suscripción a un evento en un control para evitar fugas de memoria.

Importante

Las clases parciales deben residir en el mismo espacio de nombres y usar nombres idénticos.

En el ejemplo siguiente se muestra la clase parcial TintColorBehavior para Android, que tinta una imagen con un color especificado:

using Android.Graphics;
using Android.Widget;
using Microsoft.Maui.Platform;
using Color = Microsoft.Maui.Graphics.Color;

namespace BehaviorsDemos
{
    public partial class TintColorBehavior : PlatformBehavior<Image, ImageView>
    {
        protected override void OnAttachedTo(Image bindable, ImageView platformView)
        {
            base.OnAttachedTo(bindable, platformView);

            if (bindable is null)
                return;
            if (TintColor is null)
                ClearColor(platformView);
            else
                ApplyColor(platformView, TintColor);
        }

        protected override void OnDetachedFrom(Image bindable, ImageView platformView)
        {
            base.OnDetachedFrom(bindable, platformView);

            if (bindable is null)
                return;
            ClearColor(platformView);
        }

        void ApplyColor(ImageView imageView, Color color)
        {
            imageView.SetColorFilter(new PorterDuffColorFilter(color.ToPlatform(), PorterDuff.Mode.SrcIn ?? throw new NullReferenceException()));
        }

        void ClearColor(ImageView imageView)
        {
            imageView.ClearColorFilter();
        }
    }
}

En este ejemplo, la clase TintColorBehavior deriva de la clase PlatformBehavior<TView,TPlatformView>, donde TView es Image y TPlatformView es ImageView. OnAttachedTo aplica el color de tono a la imagen, siempre que la propiedad TintColor tenga un valor. El método OnDetachedFrom quita el color de tono de la imagen.

Se debe agregar una clase parcial nativa en cada plataforma para la que compiles la aplicación. Sin embargo, puedes hacer que la clase parcial nativa NO-OP, si el comportamiento de plataforma no es necesario en una plataforma específica. Esto se puede lograr proporcionando una clase vacía:

using Microsoft.UI.Xaml;

namespace BehaviorsDemos
{
    public partial class TintColorBehavior : PlatformBehavior<Image, FrameworkElement>
    {
        // NO-OP on Windows
    }
}

Importante

.NET MAUI no establece BindingContext de un comportamiento de plataforma.

Consumo de un comportamiento de la plataforma

Cada control .NET MAUI tiene una colección Behaviors, a la que se pueden agregar uno o varios comportamientos de plataforma:

<Image Source="dotnet_bot.png"
       HeightRequest="200"
       HorizontalOptions="Center">
    <Image.Behaviors>
        <local:TintColorBehavior TintColor="Red" />
    </Image.Behaviors>
</Image>

El Image equivalente en C# se muestra en el ejemplo siguiente:

Image image = new Image { Source = "dotnet_bot.png", HeightRequest = 200, HorizontalOptions = LayoutOptions.Center };
image.Behaviors.Add(new TintColorBehavior());

En la captura de pantalla siguiente se muestra el comportamiento de plataforma que tinta una imagen:

Screenshot of .NET MAUI platform behavior tinting an image.

Advertencia

Los comportamientos de plataforma se escriben para un tipo de control específico (o una superclase que se puede aplicar a muchos controles), y solo se deben agregar a un control compatible. El intento de adjuntar un comportamiento de plataforma a un control incompatible iniciará una excepción.