我在视图中使用多个按钮,每个按钮都会显示自己的弹出页面。在同时单击多个按钮的同时,它会一次转到不同的弹出页面。
我创建了一个包含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>
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;
}
预期:
单击button1
打开page1
弹出页面,然后单击弹出窗口中的后退按钮会隐藏弹出页面。类似于button2
和button3
的行为。
实际
同时单击多个按钮(例如button1
和button2
)会打开两个弹出页面(page1
和page2
)。快速双击一个按钮也可以激发相同的按钮两次。
关于避免双击的一些研究
通过在stackoverflow中搜索类似的问题(例如this和this),我得出结论,你应该设置一个外部变量来控制事件是否被执行。这是我在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;
}
}
然后每个按钮事件处理程序都像这样修改(同样适用于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 * #####//
}
这通常很好用。双击同一个按钮可以快速触发按钮事件一次,同时单击多个按钮只打开1个弹出页面。
真正的问题
如上所述,在修改代码(在AllowTap
中添加共享状态变量struct S
)之后,仍然可以打开多个弹出页面。例如,如果用户使用2个手指按住button1
和button2
,请释放button1
,等待大约一秒钟,然后释放button2
,两个弹出页面{{1 }}和page1
将被打开。
尝试修复此问题失败
如果点击page2
,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;
}
和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();
}
和Back2Clicked()
):
Back3Clicked()
但是,同样的问题仍然存在(能够按住另一个按钮并稍后释放它们同时触发2个按钮)。
在我的应用中禁用多点触控不会是一个选项,因为我在我的应用中的其他页面中需要它。此外,弹出页面也可能包含多个按钮,这些按钮也可以指向其他页面,因此只需使用弹出页面中的后退按钮在void Back1Clicked(object sender, EventArgs e)
{
page1.IsVisible = false;
enableAllButtons(); //##### * NEW * #####//
}
中设置变量AllowTap
即可。选项也是。
任何帮助将不胜感激。感谢。
修改
&#34; 真正的问题&#34;影响Android和iOS。在Android上,当用户按住按钮时,一旦按钮被禁用,就无法激活按钮。这种保持禁用按钮问题不会影响iOS中的按钮。
答案 0 :(得分:5)
将您的停用呼叫(disableAllButtons()
)移至按钮的Pressed
事件。
一旦检测到第一次触摸,这将禁用其他按钮。
修改强>
要防止意外禁用所有内容,请创建自定义按钮渲染器,并挂钩到本机事件以取消禁用拖出:
iOS:UIControlEventTouchDragOutside
修改强>
Android在此方案中已经过测试,它与此问题中描述的问题相同。
答案 1 :(得分:4)
我认为以下代码适合您。
void Button1Clicked(object sender, EventArgs e)
{
disableAllButtons();
// ... do something first ...
page1.IsVisible = true;
Console.WriteLine("Button 1 Clicked!");
}
void Button2Clicked(object sender, EventArgs e)
{
disableAllButtons();
// ... do something first ...
page2.IsVisible = true;
Console.WriteLine("Button 2 Clicked!");
}
void Button3Clicked(object sender, EventArgs e)
{
disableAllButtons();
// ... do something first ...
page3.IsVisible = true;
Console.WriteLine("Button 3 Clicked!");
}
void Back1Clicked(object sender, EventArgs e)
{
enableAllButtons();
}
void Back2Clicked(object sender, EventArgs e)
{
enableAllButtons();
}
void Back3Clicked(object sender, EventArgs e)
{
enableAllButtons();
}
void disableAllButtons()
{
button1.IsEnabled = false;
button2.IsEnabled = false;
button3.IsEnabled = false;
disableAllPages();
}
void enableAllButtons()
{
button1.IsEnabled = true;
button2.IsEnabled = true;
button3.IsEnabled = true;
disableAllPages();
}
void disableAllPages()
{
page1.IsVisible = false;
page2.IsVisible = false;
page3.IsVisible = false;
}
答案 2 :(得分:4)
我建议使用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
}
}
}
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} />
...
然后,对于每个按钮的事件处理程序,您可以将AreButtonsEnabled
属性设置为false以禁用所有按钮。请注意,您应首先检查AreButtonsEnabled
的值是否为真,因为用户可能会在调用PropertyChanged
事件之前单击两次。但是,因为按钮的click事件处理程序在主线程上运行,所以AreButtonsEnabled
的值将在调用下一个HandleButtonXClicked
之前设置为false。换句话说,即使UI尚未更新,AreButtonsEnabled
的值也会更新。
<强> 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...
}
}
然后在您想要重新启用按钮时调用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
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} />
现在您无需在MyPage.xaml.cs
代码隐藏文件中添加任何代码。
答案 3 :(得分:4)
共享状态就是您所需要的。最少侵入性和最通用的方法就是这样 - 只需包装你的代码:
private bool isClicked;
private void AllowOne(Action a)
{
if (!isClicked)
{
try
{
isClicked = true;
a();
}
finally
{
isClicked = false;
}
}
}
void Button1Clicked(object sender, EventArgs e)
{
AllowOne(() =>
{
// ... do something first ...
page1.IsVisible = true;
Console.WriteLine("Button 1 Clicked!");
});
}
try..finally模式对于保证安全非常重要。如果a()抛出异常,则状态不会受到损害。
答案 4 :(得分:1)
我的基本视图模型中有这个
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;
}
}
然后,命令委托如下所示:
private async Task LoginAsync()
{
await RunIsBusyTaskAsync(Login);
}
...或者如果您有参数:
private async Task LoginAsync()
{
await RunIsBusyTaskAsync(async () => await LoginAsync(Username, Password));
}
登录方法将包含您的实际逻辑
private async Task Login()
{
var result = await _authenticationService.AuthenticateAsync(Username, Password);
...
}
另外,您可以使用内联委托:
private async Task LoginAsync()
{
await RunIsBusyTaskAsync(async () =>
{
// logic here
});
}
不需要IsEnabled设置。如果您要执行的实际逻辑不是异步的,则可以用Action替换Func。
您还可以将IsBusy属性绑定到诸如ActivityIndicator之类的东西
答案 5 :(得分:0)
您可以通过Command进行操作:
bool CanNavigate = true;
public ICommand OpenPageCommand
{
get
{
return new Command<View>(async (v) => await
GotoPage2ndPage(v), (v) => CanNavigate);
}
}
private async Task GotoPage2ndPage(View view)
{
CanNavigate = false;
// Logic for goto 2nd page
CanNavigate = true;
}
Note : Where v indicates CommandParameter.