Reputation: 35
I am making a poll application in Xamarin Forms
and I want users to be able to vote just one time/day. On the UI page, I created an ImageButton and I want the user to be able to click it just one time/day. I tried using a Timer, and tried to test it for 5 seconds intervals. The Application disabled the button when I first clicked it but after 5 seconds I could click it all over again and it doesn't disable the button.
private void ImageButton_Clicked(object sender, EventArgs e)
{
this.IsEnabled = false;
Timer aTimer = new Timer();
aTimer.Interval = 5000; //ms
aTimer.Enabled = true;
aTimer.Elapsed += ATimer_Elapsed;
}
private void ATimer_Elapsed(object sender, ElapsedEventArgs e)
{
this.IsEnabled = true;
}
I saw that the application enters the ImageButton_Clicked
function all the time but it does not execute this.IsEnabled=false
;
Upvotes: 3
Views: 7695
Reputation: 9541
The answer for this question is a bit long, but simple.
Create a custom button component because XF doesn't disable button and change the color properly (at least for me):
<?xml version="1.0" encoding="utf-8"?>
<Button xmlns="http://xamarin.com/schemas/2014/forms"
xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
x:Class="LindeGctMobileApplication.Modules.CustomComponents.CustomButton.CustomButtonView"
FontAttributes="Bold"
TextColor="{StaticResource LightTextPrimary}"
FontSize="20"
TextTransform="Uppercase"
CornerRadius="20"
WidthRequest="200"
HorizontalOptions="Center" />
[XamlCompilation(XamlCompilationOptions.Compile)]
public partial class CustomButtonView
{
#region constructors
public CustomButtonView()
{
InitializeComponent();
BackgroundColor = Color.Blue;
Clicked += (_, args) =>
{
if (IsEnabled) Command?.Execute(args);
};
}
#endregion
#region Command
public new static readonly BindableProperty CommandProperty = BindableProperty.Create(
nameof(Command),
typeof(ICommand),
typeof(CustomButtonView));
public new ICommand? Command
{
get => (ICommand)GetValue(CommandProperty);
set => SetValue(CommandProperty, value);
}
#endregion
#region IsEnabled
public new static readonly BindableProperty IsEnabledProperty = BindableProperty.Create(
nameof(IsEnabled),
typeof(bool),
typeof(CustomButtonView),
true,
propertyChanged: (bindable, _, newValue) =>
{
var component = (CustomButtonView)bindable;
if ((bool)newValue)
{
component.BackgroundColor = Color.Blue;
}
else
{
component.BackgroundColor = Color.Gray;
}
});
public new bool IsEnabled
{
get => (bool)GetValue(IsEnabledProperty);
set => SetValue(IsEnabledProperty, value);
}
#endregion
}
<customButton:CustomButtonView Text="Your Custom Button"
Command="{Binding ClickCommand}"
IsEnabled="{Binding IsEnabled}"/>
public static class CustomTaskExtension
{
#region fields
private static int _last;
#endregion
public static void Debounce(CancellationTokenSource throttleCts, double debounceTimeMs, Action action)
{
var current = Interlocked.Increment(ref _last);
Task.Delay(TimeSpan.FromMilliseconds(debounceTimeMs), throttleCts.Token).ContinueWith(task =>
{
if (current == _last) action();
task.Dispose();
});
}
}
private bool _isEnabled;
public bool IsEnabled
{
get => _isEnabled;
set
{
OnPropertyChanged("IsEnabled");
_isEnabled = value;
}
}
public ICommand ClickCommand => new Command(() => Click());
private void Click()
{
// disable the button
IsEnabled = false;
// the debounce is used to enable the button after 5s of the last call of this method
CustomTaskExtension.Debounce(new CancellationTokenSource(), 5000, () =>
{
IsEnabled = true;
});
}
That's all Folks!
Upvotes: 0
Reputation: 4821
Actually, the application is doing what you have told it.
this.IsEnabled
is working, but this is referencing the page itself. If you want on click to disable the button that has fired event, then you need this:
private void ImageButton_Clicked(object sender, EventArgs e)
{
if (sender is Button button)
{
button.IsEnabled = false;
// the rest of the logic
}
}
Here, we are casting the sender
as a Xamarin.Forms.Button. If the cast is successful, meaning if the sender is our button, then we can disable it.
If you don't want to cast it or you want do use the button reference in another method (like you are enabling it again), the simply set the button with a Name
property in the xml like so:
<Button
x:Name="myButton"
Clicked="ImageButton_Clicked" />
Then, you can use it in the code-behind in the ATimer_Elapsed
method like this:
private void ATimer_Elapsed(object sender, ElapsedEventArgs e)
{
Device.BeginInvokeOnMainThread(() => { myButton.IsEnabled = true; });
}
Edit: It is very important to invoke the IsEnabled = true
here on the main/UI thread, since the timer logic is being done on a background thread.
Upvotes: 1
Reputation: 588
Try this code. I hope it is working.
private void ImageButton_Clicked(object sender, EventArgs e)
{
var btn = sender as ImageButton;
btn.IsEnabled = false;
Device.StartTimer(TimeSpan.FromSeconds(5), () =>
{
btn.IsEnabled = true;
return false;
});
}
Upvotes: 1