Disable and Enable Buttons: Observation Collection of Buttons placed in a Grid

Rakesh Jain 41 Reputation points
2021-03-25T07:09:49.177+00:00

I have a Grid of Six Rows and Five Columns. In each cell of the grid, I have placed a button using Observation Collection. I also have a ButtonCommand which implements ICommand interface.

Objective:

  • On Load: Enable only One Button: Left button in First Row
  • When user clicks on the enabled button:
    (1) Disable the Clicked button
    (2) Change Background Color of Clicked Button
    (3) Enable next Button

I am so far able to disable all buttons and keep the left button in the first row as intended. However, I am unable to figure out how to disable it when Command Executes and enable the next button.

All buttons in the grid share a single ButtonCommand PrayerCommand - Line 78 to 162 in View Model.

Code is posted below:

View

<?xml version="1.0" encoding="utf-8" ?>
<ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Navkar.Views.MatrixPage">
    <ContentPage.Padding>
        <OnPlatform x:TypeArguments="Thickness"
        iOS="1,20,1,1"
        Android="1"
        WinPhone="1"/>
    </ContentPage.Padding>
    <ContentPage.Resources>
        <Style TargetType="Label">
            <Setter Property="BackgroundColor" Value="LightBlue"/>
            <Setter Property="FontSize" Value="Medium"/>
            <Setter Property="FontAttributes" Value="Bold"/>
            <Setter Property="Padding" Value="8,0,8,0"/>
        </Style>
    </ContentPage.Resources>
    <StackLayout>
        <Frame BackgroundColor="#2196F3" Padding="1" CornerRadius="0">
            <StackLayout>
                <Image x:Name="matrixBanner" Source="apbanner" VerticalOptions="Center" HorizontalOptions="Center">
                    <Image.GestureRecognizers>
                        <TapGestureRecognizer Command="{Binding TapCommand}" NumberOfTapsRequired="2"/>
                    </Image.GestureRecognizers>
                </Image>
            </StackLayout>
        </Frame>
        <StackLayout x:Name="matrixLabels" Orientation="Horizontal">
            <Label Text="{Binding MatrixNumeral}" TextColor="Black" HorizontalTextAlignment="Start"/>
            <Label Text="{Binding NavkarPrayer}" TextColor="{Binding PrayerColor}" HorizontalOptions="FillAndExpand"/>
        </StackLayout>
        <ScrollView VerticalOptions="FillAndExpand">
            <StackLayout x:Name="matrixStack" HorizontalOptions="FillAndExpand">
                <StackLayout>
                    <Grid x:Name="matrixGrid"
                      BindableLayout.ItemsSource="{Binding Matrix}"
                      BindableLayout.ItemTemplate="{StaticResource ButtonTemplate}"
                      RowSpacing="1" ColumnSpacing="1" Padding="1" HorizontalOptions="CenterAndExpand">
                    <Grid.RowDefinitions>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="*"/>
                        <RowDefinition Height="*"/>
                    </Grid.RowDefinitions>
                    <Grid.ColumnDefinitions>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="*"/>
                        <ColumnDefinition Width="*"/>
                    </Grid.ColumnDefinitions>
                </Grid>
            </StackLayout>
            <StackLayout x:Name="matrixNav" Orientation="Horizontal" HorizontalOptions="CenterAndExpand">
                    <Button Text="{Binding StyleCommandText}" StyleId="0" CommandParameter="1" Command="{Binding StyleCommand}"/>
                    <Button Text="Prev" StyleId="0" CommandParameter="-1" Command="{Binding PrevCommand}"/>
                    <Button Text="Reset" StyleId="1" CommandParameter="0" Command="{Binding ResetCommand}"/>
                    <Button Text="Next" StyleId="2" CommandParameter="1" Command="{Binding NextCommand}"/>
                </StackLayout>
                <StackLayout x:Name="matrixFooter">
                    <Label BackgroundColor="White" TextColor="Black" FontAttributes="None" FontSize="16" Padding="8,10,8,0">
                        <Label.FormattedText>
                            <FormattedString>
                                <FormattedString.Spans>
                                    <Span Text="Starting in the first row from left to right press each numeral/symbol and recite corresponding Navkar Line displayed above matrix.&#10;"/>
                                    <Span Text="www.mynarada.ca" FontAttributes="Bold"/>
                                </FormattedString.Spans>
                            </FormattedString>
                        </Label.FormattedText>
                    </Label>
                </StackLayout>
            </StackLayout>
        </ScrollView>
    </StackLayout>
</ContentPage>

View Code Behind

using Navkar.Models;
using Navkar.ViewModels;
using Xamarin.Forms;
using Xamarin.Forms.Xaml;

namespace Navkar.Views
{
    [XamlCompilation(XamlCompilationOptions.Compile)]
    public partial class MatrixPage : ContentPage
    {
        private readonly MatrixModelObj _MatrixModelObj = new MatrixModelObj();
        private readonly TapViewModel _TapViewModel = new TapViewModel();
        public MatrixPage()
        {
            InitializeComponent();
            matrixBanner.BindingContext = _TapViewModel;
            MatrixViewModel _MatrixViewModel = new MatrixViewModel(_MatrixModelObj);
            BindingContext = _MatrixViewModel;
        }
    }
}

Bindable Layout - App.Xaml

<?xml version="1.0" encoding="utf-8" ?>
<Application xmlns="http://xamarin.com/schemas/2014/forms"
             xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
             x:Class="Navkar.App">
    <Application.Resources>
        <DataTemplate x:Key="ButtonTemplate">
            <Button
                Text="{Binding Text}" TextColor="{Binding TextColor}"
                FontSize="Title" FontAttributes="Bold"
                BackgroundColor="{Binding BackgroundColor}" CornerRadius="10"
                Grid.Row="{Binding GridRow}" Grid.Column="{Binding GridColumn}"
                CommandParameter="{Binding CommandParam}" Command="{Binding CommandAction}"/>
        </DataTemplate>
        <DataTemplate x:Key="ImageButtonTemplate">
            <ImageButton
                Source="{Binding StyleId, StringFormat='ap{0}'}"
                BackgroundColor="{Binding BackgroundColor}" CornerRadius="10"
                Grid.Row="{Binding GridRow}" Grid.Column="{Binding GridColumn}"
                CommandParameter="{Binding CommandParam}" Command="{Binding CommandAction}"/>
        </DataTemplate>
    </Application.Resources>
</Application>

Model

using Navkar.ViewModels.Commands;
using System.Collections.ObjectModel;
using Xamarin.Forms;

namespace Navkar.Models
{
    public class MatrixCell
    {
        public string Text { get; set; }
        public Color TextColor { get; set; }
        public Color BackgroundColor { get; set; }
        public int GridRow { get; set; }
        public int GridColumn { get; set; }
        public string StyleId { get; set; }
        public int TabIndex { get; set; }
        public string CommandParam { get; set; }
        public ButtonCommand CommandAction { get; set; }
    }
    public class Matrix : ObservableCollection<MatrixCell> { }
    public class MatrixModelObj
    {
        public Matrix MatrixCellItems { get; set; }
        public MatrixModelObj()
        {
            MatrixCellItems = new Matrix();
        }
    }
}

ViewModel

using Navkar.Models;
using Navkar.ViewModels.Commands;
using Navkar.ViewModels.Helpers;
using System;
using System.Collections.Specialized;
using System.ComponentModel;
using System.Diagnostics;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Xamarin.Forms;
using Xamarin.Forms.Internals;

namespace Navkar.ViewModels
{
    public class MatrixViewModel : INotifyPropertyChanged
    {
        private readonly App _App = Application.Current as App;
        private readonly MatrixModelObj MatrixData;
        private int _MatrixStyle;
        public MatrixViewModel(MatrixModelObj _MatrixModel)
        {
            SetPropertyValues();
            MatrixData = _MatrixModel;
            _MatrixCell = 0;
            GetMatrix();
            _TabIndex = MatrixData.MatrixCellItems[_MatrixCell].TabIndex;
            _ = GetNavkarPrayer("0");
        }
        private void SetPropertyValues()
        {
            _MatrixNumber = (_App.MatrixNumber < 1 || _App.MatrixNumber > 20) ? 1 : _App.MatrixNumber;
            _MatrixStyle = (_App.MatrixStyle < 0 || _App.MatrixStyle > 3) ? 0 : _App.MatrixStyle;
            _StyleCommandText = _MatrixStyle == 3 ? Helper.MatrixStyleText[0] :
                Helper.MatrixStyleText[_MatrixStyle + 1];
            MatrixNumeral = Helper.GetNumeral(_MatrixStyle, _MatrixNumber);
            _App.MatrixStyle = _MatrixStyle;
            _App.MatrixNumber = _MatrixNumber;
        }
        public Matrix Matrix
        {
            get => this.MatrixData.MatrixCellItems;
            private set
            {
                this.MatrixData.MatrixCellItems = value;
            }
        }
        #region Matrix
        private void GetMatrix()
        {
            int navkarLine;
            int matrixRow;
            int matrixCol;

            if (MatrixData.MatrixCellItems.Count > 0) { MatrixData.MatrixCellItems.Clear(); }
            for (matrixRow = 0; matrixRow < 6; matrixRow++)
            {
                for (matrixCol = 0; matrixCol < 5; matrixCol++)
                {
                    _TabIndex = (matrixRow * 5) + matrixCol + 1;
                    navkarLine = Helper.GetNavkarLine(_MatrixNumber, matrixRow, matrixCol);
                    MatrixData.MatrixCellItems.Add(new MatrixCell()
                    {   
                        Text = Helper.GetNumeral(_MatrixStyle, navkarLine),
                        TextColor = Helper.PrayerColor[navkarLine],
                        BackgroundColor = Helper.BackgroundColor[_MatrixStyle],
                        GridRow = matrixRow,
                        GridColumn = matrixCol,
                        StyleId = navkarLine.ToString(),
                        TabIndex = _TabIndex,
                        CommandParam = navkarLine.ToString(),
                        CommandAction = PrayerCommand
                    });
                }
            }
        }
        #endregion

        #region Matrix PrayerCommand
        int _MatrixCell;
        int _TabIndex;
        bool _boolBinding;
        public ButtonCommand PrayerCommand
        {
            get
            {
                _PrayerCommand = new ButtonCommand(
                    async(param) => await GetNavkarPrayer(param as string),
                    (param) => PrayerCommandCanExecute());
                return _PrayerCommand;
            }
            private set
            {
                if (_PrayerCommand == value) return; _PrayerCommand = value;
            }
        }
        private ButtonCommand _PrayerCommand;
        public string NavkarPrayer
        {
            get { return _NavkarPrayer; }
            private set
            {
                if (_NavkarPrayer == value) return; _NavkarPrayer = value;
            }
        }
        private string _NavkarPrayer;
        public Color PrayerColor
        {
            get { return _PrayerColor; }
            private set
            {
                if (_PrayerColor == value) return; _PrayerColor = value;
            }
        }
        private Color _PrayerColor;
        private async Task GetNavkarPrayer(string param)
        {
            int prayerLine;
            prayerLine = Convert.ToInt32(param);
            await Task.Run(() => DateTime.Now.AddSeconds(1.0));
            _NavkarPrayer = Helper.GetNavkarPrayer(_MatrixStyle, prayerLine);
            _PrayerColor = Helper.PrayerColor[prayerLine];
            RefreshProperties("Prayer");

            //***Change canExecute state of clicked button and next button***
            //***Change BackgroundColor of clicked button***
            if (prayerLine > 0) ChangeMatrixCellView();
        }

        private void ChangeMatrixCellView()
        {
            //_boolBinding = true;
            MatrixData.MatrixCellItems[_MatrixCell].BackgroundColor = Color.Accent;
            Matrix.CollectionChanged += OnCollectionChanged;
            _MatrixCell++;
            //How to do the following?
            //Disable clicked Button (Change canExecute=false)
            //Enable Next Button (Change canExecute=true)
            //Change background Color of the clicked Button
        }

        private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            throw new NotImplementedException();
        }

        private bool PrayerCommandCanExecute(object param=null)
        {
            //***code below sets _canExecute = true for all buttons***
            bool _canExecute;
            _canExecute = (param == null) || (bool)param;
            return _canExecute;

            //***Code below sets _canExecute = true for first button only***
            //bool _canExecute = false;
            //if (MatrixData.MatrixCellItems[_MatrixCell].TabIndex == _TabIndex)
            //{
            //    _canExecute = true;
            //}
            //if (_boolBinding == false) _MatrixCell += (_MatrixCell == 29) ? -29 : 1;
            //return _canExecute;
        }
        #endregion

        #region Matrix StyleCommand
        public ButtonCommand StyleCommand
        {
            get
            {
                _StyleCommand = new ButtonCommand(async(param) => await GetMatrixStyle());
                return _StyleCommand;
            }
            private set
            {
                if (_StyleCommand == value) return; _StyleCommand = value;
            }
        }
        private ButtonCommand _StyleCommand;
        public string StyleCommandText
        {
            get { return _StyleCommandText; }
            private set
            {
                if (_StyleCommandText == value) return; _StyleCommandText = value;
            }
        }
        private string _StyleCommandText;
        private async Task GetMatrixStyle()
        {
            int styleIndex;
            _MatrixStyle += (_MatrixStyle == 3) ? -3 : 1 ;
            _App.MatrixStyle = _MatrixStyle;
            styleIndex = (_MatrixStyle == 3) ? 0 : (_MatrixStyle + 1);
            _StyleCommandText = Helper.MatrixStyleText[styleIndex];
            RefreshProperties("Style");
            MatrixNumeral = Helper.GetNumeral(_MatrixStyle, _MatrixNumber);
            GetMatrix();
            await Task.Run(() => DateTime.Now.AddSeconds(1.0));
            _ = GetNavkarPrayer("0");
        }
        #endregion

        #region Matrix Navigation ButtonCommands
        public string MatrixNumeral { get; private set; }
        public int MatrixNumber
        {
            get { return _MatrixNumber; }
            private set
            {
                if (_MatrixNumber == value) return; _MatrixNumber = value;
            }
        }
        private int _MatrixNumber;
        public ButtonCommand ResetCommand
        {
            get
            {
                _ResetCommand = new ButtonCommand(
                    async(param) => await ChangeMatrix(param as string),
                    (param) => NavigationCommandCanExecute(param as string));
                return _ResetCommand;
            }
            private set { if (_ResetCommand == value) return; _ResetCommand = value; }
        }
        private ButtonCommand _ResetCommand;

        public ButtonCommand PrevCommand
        {
            get
            {
                _PrevCommand = new ButtonCommand(
                    async (param) => await ChangeMatrix(param as string),
                    param => NavigationCommandCanExecute(param as string));
                return _PrevCommand;
            }
            private set { if (_PrevCommand == value) return; _PrevCommand = value; }
        }
        private ButtonCommand _PrevCommand;

        public ButtonCommand NextCommand
        {
            get
            {
                _NextCommand = new ButtonCommand(
                    async (param) => await ChangeMatrix(param as string),
                    (param) => NavigationCommandCanExecute(param as string));
                return _NextCommand;

            }
            private set { if (_NextCommand == value) return; _NextCommand = value; }
        }
        private ButtonCommand _NextCommand;
        private async Task ChangeMatrix(string param)
        {
            switch (param)
            {
                case "0":
                    _MatrixNumber = 1;
                    break;
                case "1":
                    _MatrixNumber += _MatrixNumber == 20 ? -19 : 1;
                    break;
                case "-1":
                    _MatrixNumber -= _MatrixNumber > 1 ? 1 : 0;
                    break;
            }
            MatrixNumeral = Helper.GetNumeral(_MatrixStyle, _MatrixNumber);
            RefreshProperties("Matrix");
            RefreshCanExecutes();
            _App.MatrixNumber = _MatrixNumber;
            await Task.Run(() => DateTime.Now.AddSeconds(1.0));
            GetMatrix();
            _ = GetNavkarPrayer("0");
        }
        private bool NavigationCommandCanExecute(string param)
        {
            bool _canExecute = true;
            int _matrixNumber = (_MatrixNumber + Convert.ToInt32(param));
            switch (param)
            {
                case "0":
                    _canExecute = _matrixNumber > 1;
                    break;
                case "1":
                    _canExecute = _matrixNumber <= 20;
                    break;
                case "-1":
                    _canExecute = _matrixNumber >= 1;
                    break;
            }
            return _canExecute;
        }
        #endregion

        #region PropertyChanged Methods and Handlers
        private void RefreshProperties(string propertyGroup)
        {
            switch (propertyGroup)
            {
                case "Prayer":
                    OnPropertyChanged(nameof(NavkarPrayer));
                    OnPropertyChanged(nameof(PrayerColor));
                    break;
                case "Matrix":
                    OnPropertyChanged(nameof(MatrixNumber));
                    OnPropertyChanged(nameof(MatrixNumeral));
                    break;
                case "Style":
                    OnPropertyChanged(nameof(StyleCommandText));
                    break;
            }
        }
        private void RefreshCanExecutes()
        {
            OnPropertyChanged(nameof(ResetCommand));
            OnPropertyChanged(nameof(PrevCommand));
            OnPropertyChanged(nameof(NextCommand));
        }

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    }
}

ButtonCommand:ICommand

using System;
using System.Diagnostics;
using System.Windows.Input;

namespace Navkar.ViewModels.Commands
{
    public class ButtonCommand : ICommand
    {
        private readonly Action<object> _execute;
        private readonly Func<object, bool> _canExecute;
        public ButtonCommand(Action<object> execute, Func<object, bool> canExecute)
        {
            _execute = execute ?? throw new NullReferenceException("execute");
            _canExecute = canExecute;
        }
        public ButtonCommand(Action<object> execute) : this(execute, null)
        {
        }
        public ButtonCommand(Action execute) : this(O => execute(), null)
        {
        }
        public ButtonCommand(Action execute, Func<bool> canExecute) : this(O => execute(), O => canExecute())
        {
        }
        public event EventHandler CanExecuteChanged
        {
            add { }
            remove { }
        }
        public bool CanExecute(object parameter)
        {
            return _canExecute == null || _canExecute(parameter);
        }
        public void Execute(object parameter)
        {
            _execute.Invoke(parameter);
        }
    }
}

I have tried many different options but unable to achieve the objective as mentioned above. I appreciate all suggestions. Thanks.

Xamarin
Xamarin
A Microsoft open-source app platform for building Android and iOS apps with .NET and C#.
5,294 questions
Universal Windows Platform (UWP)
0 comments No comments
{count} votes

Accepted answer
  1. Nico Zhu (Shanghai Wicresoft Co,.Ltd.) 12,851 Reputation points
    2021-03-25T09:52:01.713+00:00

    Hello, Welcome to Micorosoft Q&A,

    When user clicks on the enabled button: Disable the Clicked button Change Background Color of Clicked Button Enable next Button

    For your scenario, we suggest you add IsEnabled property for model, and pass current datacontext as command parameter for button command. then you could find the index of current button where in the buttons collection. Then get next index with index + 1. And when the end one clicked, make the next index equal to zero. For more detail please refer the following code.

    Code Behind

    public partial class MainPage : ContentPage
    {
        public MainPage()
        {
            InitializeComponent();
    
            var btncommand = new ButtonCommand((s) => ButtonClick(s));
    
            for (int i = 0; i < 15; i++)
            {
                Buttons.Add(new ButtonModel() { IsEnabled = false, Tag = i });
    
            }
            Buttons.First().IsEnabled = true;
            this.BindingContext = this;
        }
    
        public ButtonCommand BtnConmmand
        {
            get
            {
                return new ButtonCommand((s) => ButtonClick(s));
            }
    
        }
    
        private void ButtonClick(object url)
        {
            try
            {
                var btnModel = url as ButtonModel;
    
                btnModel.IsEnabled = false;
                var nextIndex = Buttons.IndexOf(btnModel) + 1;
                if (nextIndex == Buttons.Count)
                {
                    nextIndex = 0;
    
                }
                var nextBtn = Buttons[nextIndex];
    
                nextBtn.IsEnabled = true;
            }
            catch (Exception ex)
            {
    
            }
        }
    
        public ObservableCollection<ButtonModel> Buttons { get; set; } = new ObservableCollection<ButtonModel>();
    }
    public class ButtonModel : INotifyPropertyChanged
    {
        private bool _isEnable;
        public bool IsEnabled
        {
            get { return _isEnable; }
    
    
            set { _isEnable = value; OnPropertyChanged(); }
        }
        public int Tag { get; set; }
    
        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName] string PropertyName = null)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(PropertyName));
            }
        }
    }
    

    Xaml

    <ContentPage.Resources>
        <DataTemplate x:Key="ButtonTemplate">
            <Button
                Margin="10,0,0,0"
                Command="{Binding BtnConmmand, Source={x:Reference RootPage}}"
                CommandParameter="{Binding}"
                CornerRadius="10"
                FontAttributes="Bold"
                FontSize="Title"
                IsEnabled="{Binding IsEnabled}"
                TabIndex="{Binding Tag}"
                Text="ButtonDemo" />
        </DataTemplate>
    </ContentPage.Resources>
    <StackLayout BindableLayout.ItemTemplate="{StaticResource ButtonTemplate}" BindableLayout.ItemsSource="{Binding Buttons}" />
    

    If the response is helpful, please click "Accept Answer" and upvote it.
    Note: Please follow the steps in our [documentation][2] to enable e-mail notifications if you want to receive the related email notification for this thread.

    1 person found this answer helpful.

0 additional answers

Sort by: Most helpful