如何真正避免在Xamarin.Forms中同时点击多个按钮?

Pat*_*ick 13 c# xamarin.forms

我在视图中使用多个按钮,每个按钮都会显示自己的弹出页面.同时单击多个按钮时,它一次转到不同的弹出页面.

我创建了一个包含3个按钮的示例内容页面(每个按钮都转到不同的弹出页面)来演示此问题:

截图

XAML页面:

<ContentPage.Content>
    <AbsoluteLayout>

        <!-- button 1 -->
        <Button x:Name="button1" Text="Button 1"
            BackgroundColor="White" Clicked="Button1Clicked"
            AbsoluteLayout.LayoutFlags="All"
            AbsoluteLayout.LayoutBounds="0.5, 0.3, 0.5, 0.1"/>

        <!-- button 2 -->
        <Button x:Name="button2" Text="Button 2"
            BackgroundColor="White" Clicked="Button2Clicked"
            AbsoluteLayout.LayoutFlags="All"
            AbsoluteLayout.LayoutBounds="0.5, 0.5, 0.5, 0.1"/>

        <!-- button 3 -->
        <Button x:Name="button3" Text="Button 3"
            BackgroundColor="White" Clicked="Button3Clicked"
            AbsoluteLayout.LayoutFlags="All"
            AbsoluteLayout.LayoutBounds="0.5, 0.7, 0.5, 0.1"/>

        <!-- popup page 1 -->
        <AbsoluteLayout x:Name="page1" BackgroundColor="#7f000000" IsVisible="false"
            AbsoluteLayout.LayoutFlags="All"
            AbsoluteLayout.LayoutBounds="0.5, 0.5, 1.0, 1.0">
            <BoxView Color="Red"
                AbsoluteLayout.LayoutFlags="All"
                AbsoluteLayout.LayoutBounds="0.5, 0.5, 0.75, 0.3"/>
            <Label Text="Button 1 clicked" TextColor="White"
                HorizontalTextAlignment="Center"
                AbsoluteLayout.LayoutFlags="All"
                AbsoluteLayout.LayoutBounds="0.5, 0.45, 0.75, 0.05"/>
            <Button Text="Back" BackgroundColor="White" Clicked="Back1Clicked"
                AbsoluteLayout.LayoutFlags="All"
                AbsoluteLayout.LayoutBounds="0.5, 0.6, 0.5, 0.1"/>
        </AbsoluteLayout>

        <!-- popup page 2 -->
        <AbsoluteLayout x:Name="page2" BackgroundColor="#7f000000" IsVisible="false"
            AbsoluteLayout.LayoutFlags="All"
            AbsoluteLayout.LayoutBounds="0.5, 0.5, 1.0, 1.0">
            <BoxView Color="Green"
                AbsoluteLayout.LayoutFlags="All"
                AbsoluteLayout.LayoutBounds="0.5, 0.5, 0.75, 0.3"/>
            <Label Text="Button 2 clicked" TextColor="White"
                HorizontalTextAlignment="Center"
                AbsoluteLayout.LayoutFlags="All"
                AbsoluteLayout.LayoutBounds="0.5, 0.45, 0.75, 0.05"/>
            <Button Text="Back" BackgroundColor="White" Clicked="Back2Clicked"
                AbsoluteLayout.LayoutFlags="All"
                AbsoluteLayout.LayoutBounds="0.5, 0.6, 0.5, 0.1"/>
        </AbsoluteLayout>

        <!-- popup page 3 -->
        <AbsoluteLayout x:Name="page3" BackgroundColor="#7f000000" IsVisible="false"
            AbsoluteLayout.LayoutFlags="All"
            AbsoluteLayout.LayoutBounds="0.5, 0.5, 1.0, 1.0">
            <BoxView Color="Blue"
                AbsoluteLayout.LayoutFlags="All"
                AbsoluteLayout.LayoutBounds="0.5, 0.5, 0.75, 0.3"/>
            <Label Text="Button 3 clicked" TextColor="White"
                HorizontalTextAlignment="Center"
                AbsoluteLayout.LayoutFlags="All"
                AbsoluteLayout.LayoutBounds="0.5, 0.45, 0.75, 0.05"/>
            <Button Text="Back" BackgroundColor="White" Clicked="Back3Clicked"
                AbsoluteLayout.LayoutFlags="All"
                AbsoluteLayout.LayoutBounds="0.5, 0.6, 0.5, 0.1"/>
        </AbsoluteLayout>

    </AbsoluteLayout>
</ContentPage.Content>
Run Code Online (Sandbox Code Playgroud)

C#事件处理程序:

void Button1Clicked(object sender, EventArgs e)
{
    // ... do something first ...
    page1.IsVisible = true;
    Console.WriteLine("Button 1 Clicked!");
}

void Button2Clicked(object sender, EventArgs e)
{
    // ... do something first ...
    page2.IsVisible = true;
    Console.WriteLine("Button 2 Clicked!");
}

void Button3Clicked(object sender, EventArgs e)
{
    // ... do something first ...
    page3.IsVisible = true;
    Console.WriteLine("Button 3 Clicked!");
}

void Back1Clicked(object sender, EventArgs e)
{
    page1.IsVisible = false;
}

void Back2Clicked(object sender, EventArgs e)
{
    page2.IsVisible = false;
}

void Back3Clicked(object sender, EventArgs e)
{
    page3.IsVisible = false;
}
Run Code Online (Sandbox Code Playgroud)

预期:
单击button1打开page1弹出页面,然后单击弹出窗口中的后退按钮隐藏弹出页面.类似的行为button2button3.

实际:同时
单击多个按钮(例如.button1button2)将打开两个弹出页面(page1page2).快速双击一个按钮也可以两次触发相同的按钮.


关于避免双击的一些研究
通过在stackoverflow中搜索类似的问题(例如thisthis),我得出结论,你应该设置一个外部变量来控制事件是否被执行.这是我在Xamarin.forms中的实现:

C#struct作为外部变量,以便我可以在单独的类中访问此变量:

// struct to avoid multiple button click at the same time
public struct S
{
    // control whether the button events are executed
    public static bool AllowTap = true;

    // wait for 200ms after allowing another button event to be executed
    public static async void ResumeTap() {
        await Task.Delay(200);
        AllowTap = true;
    }
}
Run Code Online (Sandbox Code Playgroud)

然后像这样修改每个按钮事件处理程序(同样适用于Button2Clicked()Button3Clicked()):

void Button1Clicked(object sender, EventArgs e)
{
    // if some buttons are clicked recently, stop executing the method
    if (!S.AllowTap) return; S.AllowTap = false; //##### * NEW * #####//

    // ... do something first ...
    page1.IsVisible = true;
    Console.WriteLine("Button 1 Clicked!");

    // allow other button's event to be fired after the wait specified in struct S
    S.ResumeTap(); //##### * NEW * #####//
}
Run Code Online (Sandbox Code Playgroud)

这通常很好.双击同一个按钮只能快速触发按钮事件一次,同时单击多个按钮只打开1个弹出页面.


真正的问题
它仍然可能对矫正码(添加一个共享状态变量后打开超过1弹出页AllowTapstruct S),如上所述.例如,如果用户按住button1button2使用2个手指,释放button1,等待约一秒钟,然后松开button2,都弹出页面page1page2将被打开.


一个失败的尝试来解决这个问题,
我想如果任一禁用所有按钮button1,button2或者button3点击,并启用所有按钮如果单击后退按钮.

void disableAllButtons()
{
    button1.IsEnabled = false;
    button2.IsEnabled = false;
    button3.IsEnabled = false;
}

void enableAllButtons()
{
    button1.IsEnabled = true;
    button2.IsEnabled = true;
    button3.IsEnabled = true;
}
Run Code Online (Sandbox Code Playgroud)

然后像这样修改每个按钮事件处理程序(同样适用于Button2Clicked()Button3Clicked()):

void Button1Clicked(object sender, EventArgs e)
{
    if (!S.AllowTap) return; S.AllowTap = false;

    // ... do something first ...
    disableAllButtons(); //##### * NEW * #####//
    page1.IsVisible = true;
    Console.WriteLine("Button 1 Clicked!");

    S.ResumeTap();
}
Run Code Online (Sandbox Code Playgroud)

每个后退按钮事件处理程序都像这样修改(同样适用于Back2Clicked()Back3Clicked()):

void Back1Clicked(object sender, EventArgs e)
{
    page1.IsVisible = false;
    enableAllButtons(); //##### * NEW * #####//
}
Run Code Online (Sandbox Code Playgroud)

但是,同样的问题仍然存在(能够按住另一个按钮并稍后释放它们同时触发2个按钮).


在我的应用程序中禁用多点触控不是一个选项,因为我需要在我的应用程序的其他页面中.此外,在弹出的页面还可以包含多个按钮,这会导致其他网页一样,所以简单地使用,在弹出的页面后退按钮设置变量AllowTapstruct S将不会是一种选择为好.

任何帮助将不胜感激.谢谢.

编辑
" 真正的问题 "影响Android和iOS.在Android上,一旦按钮被禁用,当用户按住按钮时,无法激活按钮.这种按住 - 禁用 - 按钮问题不会影响iOS中的按钮.

Inq*_*Jax 7

我的基本视图模型中有这个:

public bool IsBusy { get; set; }

protected async Task RunIsBusyTaskAsync(Func<Task> awaitableTask)
{
    if (IsBusy)
    {
        // prevent accidental double-tap calls
        return;
    }
    IsBusy = true;
    try
    {
        await awaitableTask();
    }
    finally
    {
        IsBusy = false;
    }
}
Run Code Online (Sandbox Code Playgroud)

命令委托如下所示:

    private async Task LoginAsync()
    {
        await RunIsBusyTaskAsync(Login);
    }
Run Code Online (Sandbox Code Playgroud)

...或者如果你有参数:

    private async Task LoginAsync()
    {
        await RunIsBusyTaskAsync(async () => await LoginAsync(Username, Password));
    }
Run Code Online (Sandbox Code Playgroud)

登录方法将包含您的实际逻辑

    private async Task Login()
    {
        var result = await _authenticationService.AuthenticateAsync(Username, Password);

        ...
    }
Run Code Online (Sandbox Code Playgroud)

此外,您可以使用内联委托:

    private async Task LoginAsync()
    {
        await RunIsBusyTaskAsync(async () =>
        {
            // logic here
        });
    }
Run Code Online (Sandbox Code Playgroud)

不需要 IsEnabled 设置。如果您正在执行的实际逻辑不是异步的,您可以用 Action 替换 Func。

您还可以绑定到 IsBusy 属性以获取诸如 ActivityIndi​​cator 之类的内容


sme*_*sme 6

我建议使用 MVVM 方法,并将IsEnabled每个按钮的属性绑定到视图模型中的相同属性,例如AreButtonsEnabled

MyViewModel.cs

private bool _areButtonsEnabled = true;

public bool AreButtonsEnabled
{
    get => _areButtonsEnabled;
    set
    {
        if (_areButtonsEnabled != value)
        {
            _areButtonsEnabled = value;
            OnPropertyChanged(nameof(AreButtonsEnabled)); // assuming your view model implements INotifyPropertyChanged
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

MyPage.xaml(仅显示一个按钮的代码):

...
<Button 
    Text="Back" 
    BackgroundColor="White" 
    Clicked="HandleButton1Clicked"
    AbsoluteLayout.LayoutFlags="All"
    AbsoluteLayout.LayoutBounds="0.5, 0.6, 0.5, 0.1"
    IsEnabled={Binding AreButtonsEnabled} />
...
Run Code Online (Sandbox Code Playgroud)

然后对于每个按钮的事件处理程序,您可以将AreButtonsEnabled属性设置为 false 以禁用所有按钮。请注意,您应该首先检查 的值是否AreButtonsEnabled为真,因为在PropertyChanged调用事件之前用户有可能单击两次。但是,由于按钮的单击事件处理程序在主线程上运行,因此AreButtonsEnabledHandleButtonXClicked调用next 之前,其值将设置为 false 。换句话说,AreButtonsEnabled即使 UI 尚未更新, 的值也会更新。

MyPage.xaml.cs

HandleButton1Clicked(object sender, EventArgs e)
{
    if (viewModel.AreButtonsEnabled)
    {
        viewModel.AreButtonsEnabled = false;
        // button 1 code...
    }
}

HandleButton2Clicked(object sender, EventArgs e)
{
    if (viewModel.AreButtonsEnabled)
    {
        viewModel.AreButtonsEnabled = false;
        // button 2 code...
    }
}

HandleButton3Clicked(object sender, EventArgs e)
{
    if (viewModel.AreButtonsEnabled)
    {
        viewModel.AreButtonsEnabled = false;
        // button 3 code...
    }
}
Run Code Online (Sandbox Code Playgroud)

然后viewModel.AreButtonsEnabled = true;在您想重新启用按钮时调用。


如果你想要一个“真正的”MVVM 模式,你可以将命令绑定到按钮而不是监听它们的Clicked事件。

MyViewModel.cs

private bool _areButtonsEnabled = true;

public bool AreButtonsEnabled
{
    get => _areButtonsEnabled;
    set
    {
        if (_areButtonsEnabled != value)
        {
            _areButtonsEnabled = value;
            OnPropertyChanged(nameof(AreButtonsEnabled)); // assuming your view model implements INotifyPropertyChanged
        }
    }
}

public ICommand Button1Command { get; protected set; }

public MyViewModel()
{
    Button1Command = new Command(HandleButton1Tapped);
}

private void HandleButton1Tapped()
{
    // Run on the main thread, to make sure that it is getting/setting the proper value for AreButtonsEnabled
    // And note that calls to Device.BeginInvokeOnMainThread are queued, therefore
    // you can be assured that AreButtonsEnabled will be set to false by one button's command
    // before the value of AreButtonsEnabled is checked by another button's command.
    // (Assuming you don't change the value of AreButtonsEnabled on another thread)
    Device.BeginInvokeOnMainThread(() => 
    {
        if (AreButtonsEnabled)
        {
            AreButtonsEnabled = false;
            // button 1 code...
        }
    });
}

// don't forget to add Commands for Button 2 and 3
Run Code Online (Sandbox Code Playgroud)

MyPage.xaml(仅显示一个按钮):

<Button 
    Text="Back" 
    BackgroundColor="White"
    AbsoluteLayout.LayoutFlags="All"
    AbsoluteLayout.LayoutBounds="0.5, 0.6, 0.5, 0.1"
    Command={Binding Button1Command}
    IsEnabled={Binding AreButtonsEnabled} />
Run Code Online (Sandbox Code Playgroud)

现在您无需在MyPage.xaml.cs代码隐藏文件中添加任何代码。


Tim*_*Tim 5

将禁用调用(disableAllButtons())移至Pressed按钮事件.

一旦检测到第一次触摸,这将禁用其他按钮.

编辑
为防止意外禁用所有内容,请创建自定义按钮渲染器,并挂钩到本机事件以取消禁用拖出:iOS:UIControlEventTouchDragOutside

编辑
Android已经在这种情况下进行了测试,它有与此问题中描述的相同的问题.