user13532840
user13532840

Reputation: 35

How to temporarily disable button after click in Xamarin Forms?

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

Answers (3)

Ângelo Polotto
Ângelo Polotto

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

    • XAML:
<?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" />
  • Code behind:
[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
}
  • Add the CustomButton inside your screen XAML:
<customButton:CustomButtonView Text="Your Custom Button"
                               Command="{Binding ClickCommand}"
                               IsEnabled="{Binding IsEnabled}"/>
  • Create the following static class to debounce your buttons clicks, the user can click multiple times and the method will compute the time after last click, it will be used later:
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();
         });
    }
}
  • Add the following property in your ViewModel:
private bool _isEnabled;

public bool IsEnabled
{
   get => _isEnabled;
   set
   {
      OnPropertyChanged("IsEnabled");
      _isEnabled = value;
   }
}
  • Add the following command to your button:
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

Mihail Duchev
Mihail Duchev

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

Chetan Rawat
Chetan Rawat

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

Related Questions