Button

.NET Multi-platform App UI (.NET MAUI) Button 显示文本,并响应指示应用执行任务的点击或单击。 Button 通常显示一个指示命令的短文本字符串,但它也可以显示位图图像或文本和图像的组合。 当用手指按下或用鼠标单击 Button 时,它会启动该命令。

Button 定义以下属性:

  • Color,类型的 BorderColor 描述按钮的边框颜色。
  • double,类型的 BorderWidth 定义按钮边框的宽度。
  • double,类型的 CharacterSpacing 定义按钮文本字符之间的间距。
  • ICommand,类型的 Command 定义在点击按钮时执行的命令。
  • object,类型的 CommandParameter 是传递给 Command 的参数。
  • ButtonContentLayout,类型的 ContentLayout 定义一个对象,该对象控制按钮图像的位置以及按钮图像与文本之间的间距。
  • int,类型的 CornerRadius 描述按钮边框的角半径。
  • FontAttributes,类型的 FontAttributes 确定文本样式。
  • bool,类型的 FontAutoScalingEnabled 定义按钮文本是否会反映操作系统中设置的缩放首选项。 此属性的默认值为 true
  • string,类型的 FontFamily 定义字体系列。
  • double,类型的 FontSize 定义字号。
  • ImageSource,类型的 ImageSource 指定要显示为按钮内容的位图图像。
  • LineBreakMode,类型的 LineBreakMode 确定文本在无法容纳一行时应如何处理文本。
  • Thickness,类型的 Padding 确定按钮的填充。
  • string,类型的 Text 定义显示为按钮内容的文本。
  • Color,类型的 TextColor 描述按钮文本的颜色。
  • TextTransform,类型的 TextTransform 定义按钮文本的大小写。

这些属性由 BindableProperty 对象提供支持,表示它们可以是数据绑定的目标,并可以设置样式。

注意

虽然 Button 定义了一个 ImageSource 属性,该属性允许你在 Button 上面显示图像,但该属性适合在当 Button 文本旁边显示小图标时使用。

此外,Button 还定义 ClickedPressedReleased 事件。 当用手指点击 Button 或者从按钮表面释放鼠标指针时,将引发 Clicked 事件。 当手指按下 Button 或者当指针位于 Button 上方的同时按下鼠标按钮时,将引发 Pressed 事件。 释放手指或鼠标按钮时,将引发 Released 事件。 通常,Clicked 事件还与 Released 事件同时引发,但如果手指或鼠标指针在从 Button 表面移开后释放,则可能不会发生 Clicked 事件。

重要说明

Button 必须将其 IsEnabled 属性设置为 true 后才能够对点击做出响应。

创建按钮

若要创建按钮,请创建 Button 对象并处理其 Clicked 事件。

以下 XAML 示例展示了如何创建 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>

Text 属性指定在 Button 中显示的文本。 Clicked 事件设置为名为 OnButtonClicked 的事件处理程序。 此处理程序位于代码隐藏文件中:

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

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

在此示例中,点击 Button 时,将执行 OnButtonClicked 方法。 sender 参数是负责此事件的 Button 对象。 可以使用此参数访问 Button 对象,或区分共享同一 Clicked 事件的多个 Button 对象。 Clicked 处理程序调用一个动画函数,该函数以 1000 毫秒为单位旋转 Label 360 度:

按钮的屏幕截图。

创建 Button 的等效 C# 代码为:

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);

使用命令接口

应用无需处理 Clicked 事件即可响应 Button 点击。 Button 执行称作命令发布命令接口的替代通知机制。 这包含两个属性:

此方法特别适用于数据绑定,尤其是在实现 Model-View-ViewModel (MVVM) 模式时。 在 MVVM 应用程序中,viewmodel 定义 ICommand 类型的属性,这些属性随后通过数据绑定连接到 Button 对象。 .NET MAUI 还定义了实现 ICommand 接口的 CommandCommand<T> 类,并帮助视图模型定义 ICommand 类型的属性。 有关每个命令的详细信息,请参阅命令

下面的示例展示了一个非常简单的视图模型类,该类定义了一个名为 Numberdouble 类型属性,以及名为 MultiplyBy2CommandDivideBy2Command 的两个 ICommand 类型属性:

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"));
            }
        }
    }
}

在本例中,两个 ICommand 属性是在类的构造函数中用两个 Command 类的对象初始化的。 Command 构造函数包含一个小函数(称为 execute 构造函数参数),可将 Number 属性的值加倍或减半。

以下 XAML 示例使用了 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>

在本例中,Label 元素和两个 Button 对象包含与 CommandDemoViewModel 类中三个属性的绑定。 点击两个 Button 对象时,将执行命令,数字的值也随之改变。 与 Clicked 处理程序相比,这种方法的优势在于,涉及该页面功能的所有逻辑都位于视图模型中,而不是代码隐藏文件中,从而更好地将用户界面与业务逻辑分离。

Command 对象还可以控制 Button 对象的启用和禁用。 例如,假设你想将数值范围限制在 210 和 2-10 之间。 可以将另一个函数添加到构造函数(称为 canExecute 参数),如果应启用 Button 则返回 true

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));
    }
    ···
}

在本例中,需要调用 CommandChangeCanExecute 方法,以便 Command 方法能够调用 canExecute 方法,并确定 Button 是否应该禁用。 随着此代码更改,当数字达到限制时,Button 将被禁用。

也可以将两个或多个 Button 元素绑定到同一 ICommand 属性。 可以使用 ButtonCommandParameter 属性来区分 Button 元素。 在这种情况下,需要使用通用 Command<T> 类。 然后,CommandParameter 对象将作为参数传递给 executecanExecute 方法。 更多信息,请参阅 命令

按下并松开按钮

当手指按下 Button 或者当指针位于 Button 上方的同时按下鼠标按钮时,将引发 Pressed 事件。 释放手指或鼠标按钮时,将引发 Released 事件。 通常,Clicked 事件还与 Released 事件同时引发,但如果手指或鼠标指针在从 Button 表面移开后释放,则可能不会发生 Clicked 事件。

下面的 XAML 示例显示了为 PressedReleased 事件附加了处理程序的 LabelButton

<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>

Pressed 事件发生时,代码隐藏文件会对 Label 进行动画处理,但当 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();
    }
}

结果是,Label仅在手指与Button接触时旋转,手指松开时停止旋转。

按钮可视状态

Button 有一个 Pressed VisualState,可用于在按下时可启动 Button 的视觉变化(前提是已启用此功能)。

下面的 XAML 示例显示了如何为 Pressed 状态定义可视化状态:

<Button Text="Click me!"
        ...>
    <VisualStateManager.VisualStateGroups>
        <VisualStateGroupList>
            <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>
                <VisualState x:Name="PointerOver" />            
            </VisualStateGroup>
        </VisualStateGroupList>
    </VisualStateManager.VisualStateGroups>
</Button>

在此示例中,Pressed VisualState 指定在按下 Button 时,其 Scale 属性将从默认值 1 更改为 0.8。 Normal VisualState 指定在 Button 处于正常状态时,其 Scale 属性将设置为 1。 因此,总体效果是,按下 Button 时,它被重新缩放为稍小的尺寸,而松开 Button 时,它被重新缩放为默认尺寸。

重要

若要使 Button 返回其 Normal 状态,VisualStateGroup 必须也定义 PointerOver 状态。 如果使用 .NET MAUI 应用项目模板创建的样式 ResourceDictionary,则已经有一个定义 PointerOver 状态的隐式 Button 样式。

关于视觉状态的详细信息,请参阅视觉状态

使用带按钮的位图

Button 类定义了一个 ImageSource 属性,通过该属性,你可以在 Button 上单独或结合文本显示一个小的位图图像。 你还可以指定文本和图像的排列方式。 该 ImageSource 属性的类型为 ImageSource,这意味着可以从文件、嵌入的资源、URI 或流加载位图。

位图不会缩放以适应 Button。 最佳大小通常在 32 到 64 个独立于设备的单位之间,具体取决于你想要多大的位图。

你可以使用 ButtonContentLayout 属性指定 TextImageSource 属性在 Button 上的排列方式。 该属性的类型为 ButtonContentLayout,其构造函数有两个参数:

  • ImagePosition 枚举的成员:LeftTopRightBottom,表示位图相对于文本的显示方式。
  • 位图与文本之间的间距 double 值。

在 XAML 中,你可以创建 Button 并设置 ContentLayout 属性,方法是只指定枚举成员或间距,或以逗号分隔的任意顺序指定两者:

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

等效 C# 代码如下:

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

注意

Button如果包含文本和图像,则可能无法容纳按钮内的所有内容,因此应手动调整图像大小以实现所需的布局。

禁用按钮

有时,应用进入 Button 单击为无效操作的状态。 在这种情况下,可以通过将 ButtonIsEnabled 属性设置为 false 来对其进行禁用。