Botón

El Button de .NET Multi-platform App UI (.NET MAUI) muestra texto y responde a una pulsación o un clic que dirige la aplicación para llevar a cabo una tarea. Un Button normalmente muestra una cadena de texto corta que indica un comando, pero también puede mostrar una imagen de mapa de bits o una combinación de texto y una imagen. Cuando se presiona Button con un dedo o se hace clic con un ratón, inicia ese comando.

Button define las siguientes propiedades:

  • BorderColor, del tipo Color, describe el color del borde del botón.
  • BorderWidth, del tipo double, define el ancho del borde del botón.
  • CharacterSpacing, del tipo double, define el espaciado entre los caracteres del texto del botón.
  • Command, del tipo ICommand, define el comando que se ejecuta cuando se pulsa el botón.
  • CommandParameter, del tipo object, es el parámetro que se pasa al objeto Command.
  • ContentLayout, del tipo ButtonContentLayout, define el objeto que controla la posición de la imagen del botón y el espaciado entre la imagen y el texto del botón.
  • CornerRadius, dle tipo int, describe el radio de esquina del borde del botón.
  • FontAttributes, del tipo FontAttributes, determina el estilo de texto.
  • FontAutoScalingEnabled, del tipo bool, define si el texto del botón reflejará las preferencias de escalado establecidas en el sistema operativo. El valor predeterminado de esta propiedad es true.
  • FontFamily, del tipo string, define la familia de fuentes.
  • FontSize, del tipo double, define el tamaño de fuente.
  • ImageSource, del tipo ImageSource, especifica una imagen de mapa de bits que se va a mostrar como el contenido del botón.
  • LineBreakMode, del tipo LineBreakMode, determina cómo se debe tratar el texto cuando no cabe en una línea.
  • Padding, del tipo Thickness, determina el relleno del botón.
  • Text, del tipo string, define el texto que se muestra como el contenido del botón.
  • TextColor, del tipo Color, describe el color del texto del botón.
  • TextTransform, del tipo TextTransform, define el uso de mayúsculas y minúsculas en el texto del botón.

Estas propiedades están respaldadas por objetos BindableProperty, lo que significa que pueden ser destinos de los enlaces de datos, y con estilo.

Nota:

Aunque Button define una propiedad ImageSource, que permite mostrar una imagen en el Button, esta propiedad está pensada para usarse al mostrar un icono pequeño junto al texto del Button.

Además, Button define los eventos Clicked, Pressed y Released. El evento Clicked se genera cuando se suelta una pulsación de Button con un dedo o puntero del ratón desde la superficie del botón. El evento Pressed se genera cuando se presiona un Button con un dedo o un botón del ratón con el puntero situado sobre el Button. El evento Released se genera cuando se suelta el botón del ratón. Por lo general, también se genera un evento Clicked al mismo tiempo que el evento Released, pero si el dedo o el puntero del ratón se aleja de la superficie del Button antes de soltarlo, es posible que el evento Clicked no se produzca.

Importante

Un Button debe tener su propiedad IsEnabled establecida en true para que responda a pulsaciones.

Creación de un botón

Para crear un botón, crea un objeto Button y controla su evento Clicked.

El siguiente ejemplo XAML muestra cómo crear un Button:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ButtonDemos.BasicButtonClickPage"
             Title="Basic Button Click">
    <StackLayout>
        <Button Text="Click to Rotate Text!"
                VerticalOptions="Center"
                HorizontalOptions="Center"
                Clicked="OnButtonClicked" />
        <Label x:Name="label"
               Text="Click the Button above"
               FontSize="18"
               VerticalOptions="Center"
               HorizontalOptions="Center" />
    </StackLayout>
</ContentPage>

La propiedad Text especifica el texto que aparece en el elemento Button. El evento Clicked se establece en un controlador de eventos denominado OnButtonClicked. Este controlador se encuentra en el archivo de código subyacente:

public partial class BasicButtonClickPage : ContentPage
{
    public BasicButtonClickPage ()
    {
        InitializeComponent ();
    }

    async void OnButtonClicked(object sender, EventArgs args)
    {
        await label.RelRotateTo(360, 1000);
    }
}

En este ejemplo, cuando se pulsa Button, se ejecuta el método OnButtonClicked. El argumento sender es el objeto Button responsable de este evento. Puedes usarlo para acceder al objeto Button o para distinguir entre varios objetos Button que comparten el mismo evento Clicked. El controlador Clicked llama a una función de animación que gira la Label 360 grados en 1000 milisegundos:

Screenshot of a Button.

El código de C# equivalente para crear un Button es:

Button button = new Button
{
    Text = "Click to Rotate Text!",
    VerticalOptions = LayoutOptions.Center,
    HorizontalOptions = LayoutOptions.Center
};
button.Clicked += async (sender, args) => await label.RelRotateTo(360, 1000);

Uso de la interfaz de comandos

Una aplicación puede responder a pulsaciones del Button sin controlar el evento Clicked. El Button implementa un mecanismo de notificación alternativo denominado interfaz comando o de comandos. Esta consta de dos propiedades:

Este enfoque es especialmente adecuado en relación con el enlace de datos y, especialmente, al implementar el patrón Modelo-Vista-Modelo de vista (MVVM). En una aplicación MVVM, el modelo de vista define las propiedades de tipo ICommand que, después, se conectan a objetos Button con enlaces de datos. .NET MAUI también define clases Command y Command<T> que implementan la interfaz ICommand y ayudan al modelo de vista a definir propiedades de tipo ICommand. Para obtener más información sobre los comandos, consulta Comandos.

En el ejemplo siguiente se muestra una clase de modelo de vista muy simple que define una propiedad de tipo double denominada Number, y dos propiedades de tipo ICommand denominadas MultiplyBy2Command y DivideBy2Command:

public class CommandDemoViewModel : INotifyPropertyChanged
{
    double number = 1;

    public event PropertyChangedEventHandler PropertyChanged;

    public ICommand MultiplyBy2Command { get; private set; }
    public ICommand DivideBy2Command { get; private set; }

    public CommandDemoViewModel()
    {
        MultiplyBy2Command = new Command(() => Number *= 2);
        DivideBy2Command = new Command(() => Number /= 2);
    }

    public double Number
    {
        get
        {
            return number;
        }
        set
        {
            if (number != value)
            {
                number = value;
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs("Number"));
            }
        }
    }
}

En este ejemplo, las dos propiedades ICommand se inicializan en el constructor de la clase con dos objetos de tipo Command. Los constructores Command incluyen una función pequeña (denominada argumento constructor execute) que duplica o reduce a la mitad el valor de la propiedad Number.

En el ejemplo XAML siguiente se consume la clase CommandDemoViewModel:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             xmlns:local="clr-namespace:ButtonDemos"
             x:Class="ButtonDemos.BasicButtonCommandPage"
             Title="Basic Button Command">
    <ContentPage.BindingContext>
        <local:CommandDemoViewModel />
    </ContentPage.BindingContext>

    <StackLayout>
        <Label Text="{Binding Number, StringFormat='Value is now {0}'}"
               FontSize="18"
               VerticalOptions="Center"
               HorizontalOptions="Center" />
        <Button Text="Multiply by 2"
                VerticalOptions="Center"
                HorizontalOptions="Center"
                Command="{Binding MultiplyBy2Command}" />
        <Button Text="Divide by 2"
                VerticalOptions="Center"
                HorizontalOptions="Center"
                Command="{Binding DivideBy2Command}" />
    </StackLayout>
</ContentPage>

En este ejemplo, el elemento Label y dos objetos Button contienen enlaces a las tres propiedades de la clase CommandDemoViewModel. A medida que se pulsan los dos objetos Button, se ejecutan los comandos y el número cambia el valor. La ventaja de este enfoque sobre los controladores Clicked es que toda la lógica que implica la funcionalidad de esta página se encuentra en el modelo de vista en lugar del archivo de código subyacente, logrando una mejor separación de la interfaz de usuario de la lógica de negocios.

También es posible que los objetos Command controlen la habilitación y deshabilitación de los objetos Button. Por ejemplo, supongamos que deseas limitar el intervalo de valores numéricos entre 210 y 2-10. Puedes agregar otra función al constructor (denominado argumento canExecute) que devuelve true si se debe habilitar Button:

public class CommandDemoViewModel : INotifyPropertyChanged
{
    ···
    public CommandDemoViewModel()
    {
        MultiplyBy2Command = new Command(
            execute: () =>
            {
                Number *= 2;
                ((Command)MultiplyBy2Command).ChangeCanExecute();
                ((Command)DivideBy2Command).ChangeCanExecute();
            },
            canExecute: () => Number < Math.Pow(2, 10));

        DivideBy2Command = new Command(
            execute: () =>
            {
                Number /= 2;
                ((Command)MultiplyBy2Command).ChangeCanExecute();
                ((Command)DivideBy2Command).ChangeCanExecute();
            },
            canExecute: () => Number > Math.Pow(2, -10));
    }
    ···
}

En este ejemplo, las llamadas al método ChangeCanExecute de Command son necesarias para que el método Command pueda llamar al método canExecute y determinar si Button se debe deshabilitar o no. Con este cambio de código, a medida que el número alcanza el límite, Button se deshabilita.

También es posible que dos o más elementos Button estén enlazados a la misma propiedad ICommand. Los elementos Button se pueden distinguir mediante la propiedad CommandParameter de Button. En este caso, querrás usar la clase genérica Command<T>. El objeto CommandParameter se pasa como un argumento a los métodos execute y canExecute. Para obtener más información, consulta Comandos.

Presionar y soltar el botón

El evento Pressed se genera cuando se presiona un Button con un dedo o un botón del ratón con el puntero situado sobre el Button. El evento Released se genera cuando se suelta el botón del ratón. Por lo general, también se genera un evento Clicked al mismo tiempo que el evento Released, pero si el dedo o el puntero del ratón se aleja de la superficie del Button antes de soltarlo, es posible que el evento Clicked no se produzca.

En el ejemplo XAML siguiente se muestra un Label y un Button con controladores adjuntos para los eventos Pressed y Released:

<ContentPage xmlns="http://schemas.microsoft.com/dotnet/2021/maui"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="ButtonDemos.PressAndReleaseButtonPage"
             Title="Press and Release Button">
    <StackLayout>
        <Button Text="Press to Rotate Text!"
                VerticalOptions="Center"
                HorizontalOptions="Center"
                Pressed="OnButtonPressed"
                Released="OnButtonReleased" />
        <Label x:Name="label"
               Text="Press and hold the Button above"
               FontSize="18"
               VerticalOptions="Center"
               HorizontalOptions="Center" />
    </StackLayout>
</ContentPage>

El archivo de código subyacente anima Label cuando se produce un evento Pressed, pero suspende la rotación cuando se produce un evento Released:

public partial class PressAndReleaseButtonPage : ContentPage
{
    IDispatcherTimer timer;
    Stopwatch stopwatch = new Stopwatch();

    public PressAndReleaseButtonPage()
    {
        InitializeComponent();

        timer = Dispatcher.CreateTimer();
        timer.Interval = TimeSpan.FromMilliseconds(16);
        timer.Tick += (s, e) =>
        {
            label.Rotation = 360 * (stopwatch.Elapsed.TotalSeconds % 1);
        };
    }

    void OnButtonPressed(object sender, EventArgs args)
    {
        stopwatch.Start();
        timer.Start();
    }

    void OnButtonReleased(object sender, EventArgs args)
    {
        stopwatch.Stop();
        timer.Stop();
    }
}

El resultado es que el Label solo gira mientras un dedo está en contacto con Button y se detiene cuando se libera el dedo.

Estados visuales del botón

Button tiene un PressedVisualState que se puede usar para iniciar un cambio visual en Button cuando se presiona, siempre que esté habilitado.

En el ejemplo XAML siguiente se muestra cómo definir un estado visual para el estado Pressed:

<Button Text="Click me!"
        ...>
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroup x:Name="CommonStates">
            <VisualState x:Name="Normal">
                <VisualState.Setters>
                    <Setter Property="Scale"
                            Value="1" />
                </VisualState.Setters>
            </VisualState>
            <VisualState x:Name="Pressed">
                <VisualState.Setters>
                    <Setter Property="Scale"
                            Value="0.8" />
                </VisualState.Setters>
            </VisualState>
        </VisualStateGroup>
    </VisualStateManager.VisualStateGroups>
</Button>

En este ejemplo, PressedVisualState especifica que cuando se presiona Button, su propiedad Scale se cambiará de su valor predeterminado de 1 a 0,8. NormalVisualState especifica que cuando Button está en un estado normal, su propiedad Scale se establecerá en 1. Por lo tanto, el efecto general es que, cuando se presiona Button, se vuelve a escalar para que sea ligeramente más pequeño y, cuando se libera Button, se vuelve a escalar a su tamaño predeterminado.

Para obtener más información sobre los estados visuales, consulta Estados visuales.

Uso de mapas de bits con botones

La clase Button define una propiedad ImageSource que te permite mostrar una imagen de mapa de bits pequeña en Button, sola o en combinación con texto. También puedes especificar cómo se organizan el texto y la imagen. La propiedad ImageSource es de tipo ImageSource, lo que significa que los mapas de bits se pueden cargar desde un archivo, un recurso incrustado, un URI o una secuencia.

Los mapas de bits no se escalan para ajustarse a Button. El mejor tamaño suele estar entre 32 y 64 unidades independientes del dispositivo, en función del tamaño que desees que sea el mapa de bits.

Puedes especificar cómo se organizan las propiedades Text y ImageSource en Button mediante la propiedad ContentLayout de Button. Esta propiedad es de tipo ButtonContentLayout y su constructor tiene dos argumentos:

  • Un miembro de la enumeración ImagePosition: Left, Top, Right o Bottom que indica cómo aparece el mapa de bits en relación con el texto.
  • Un valor double para el espaciado entre el mapa de bits y el texto.

En XAML, puedes crear Button y establecer la propiedad ContentLayout especificando solo el miembro de enumeración, o el espaciado, o ambos en cualquier orden separado por comas:

<Button Text="Button text"
        ImageSource="button.png"
        ContentLayout="Right, 20" />

El código de C# equivalente es el siguiente:

Button button = new Button
{
    Text = "Button text",
    ImageSource = new FileImageSource
    {
        File = "button.png"
    },
    ContentLayout = new Button.ButtonContentLayout(Button.ButtonContentLayout.ImagePosition.Right, 20)
};

Deshabilitación de un botón

A veces, la aplicación entra en un estado en el que un clic Button no es una operación válida. En tales casos, se puede deshabilitar el objeto Button estableciendo su propiedad IsEnabled en false.