Forum Xamarin.Forms

Announcement:

The Xamarin Forums have officially moved to the new Microsoft Q&A experience. Microsoft Q&A is the home for technical questions and answers at across all products at Microsoft now including Xamarin!

To create new threads and ask questions head over to Microsoft Q&A for .NET and get involved today.

Enable/Disable Command-bound Button from MultiSelect ListView

I've implemented a MultiSelect ListView in a UWP application exactly like this example and now I'm trying to enable a button when one or more of the ListView's switch controls are "Selected" and disable the button when none are "Selected" using an MVVM approach. How can I achieve this?

Best Answer

  • LeonLuLeonLu Member, Xamarin Team Xamurai
    Accepted Answer

    Do you want to achieve the result like following GIF?

    if so, you should foreach the DataList, the add the PropertyChanged property like following code in MyViewModel's constructor. This PropertyChanged perpery could monitor the SelectableData's data change, If the SelectableData's Selected value

           public class MyViewModel: INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected virtual void OnPropertyChanged(string propertyName)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
    
             bool  _addCouldExe ;
            public bool AddCouldExe
            {
                get
                {
                    return _addCouldExe;
                }
    
                set
                {
                    if (_addCouldExe != value)
                    {
    
    
                        _addCouldExe = value;
                        OnPropertyChanged("AddCouldExe");
    
                    }
                }
    
            }
    
    
            public ICommand AddCommand { protected set; get; }
            public MyViewModel()
            {
                DataList = new ObservableCollection<SelectableData<ExampleData>>();
    
    
                DataList.Add(new SelectableData<ExampleData>() {  Data=new ExampleData() { Description="This is test", Name="Name1" }, Selected=false });
                DataList.Add(new SelectableData<ExampleData>() { Data = new ExampleData() { Description = "This is test", Name = "Name1" }, Selected = true });
                DataList.Add(new SelectableData<ExampleData>() { Data = new ExampleData() { Description = "This is test", Name = "Name1" }, Selected = false });
                DataList.Add(new SelectableData<ExampleData>() { Data = new ExampleData() { Description = "This is test", Name = "Name1" }, Selected = false });
                AddCommand = new Command(async () =>
                {
                    DataList.Add(new SelectableData<ExampleData>() { Data = new ExampleData() { Description = "This is test", Name = "Name1" }, Selected = false });
                });
    
    
                foreach(SelectableData<ExampleData> data in DataList)
                {
                    //inite the button state
                    if (data.Selected)
                    {
                        AddCouldExe = true;
                    }
    
                    data.PropertyChanged += Data_PropertyChanged;
    
                }
    
            }
    
            private void Data_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if(e.PropertyName=="Selected")
                {
                    var model = sender as SelectableData<ExampleData>;
                    bool isSelected = model.Selected;
                   if(isSelected)
                      AddCouldExe = true;
    
    
                    //when Selected value changed, foreach the DataList, if data.Selected=true, do not have any change, if all of data.Selected=false, change the AddCouldExe value to false, make the Button cannot be clicked.
                    foreach (SelectableData<ExampleData> data in DataList)
                    {
                        if (data.Selected==true)
                        {
                            return;
                        }
    
    
    
                    }
                    AddCouldExe = false;
    
                }
            }
    
    
    
    
    
            public ObservableCollection<SelectableData<ExampleData>> DataList { get; set; }
    
    
    
        }
        public class ExampleData
        {
            public string Name { get; set; }
            public string Description { get; set; }
        }
        public class SelectableData<T> : INotifyPropertyChanged
        {
            public T Data { get; set; }
    
            bool _selected = false;
            public bool Selected
            {
                get
                {
                    return _selected;
                }
    
                set
                {
                    if (_selected != value)
                    {
    
    
                        _selected = value;
                        OnPropertyChanged("Selected");
    
                    }
                }
    
            }
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected virtual void OnPropertyChanged(string propertyName)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
            // public bool Selected { get; set; }
        }
    }
    
    

    Here is my layout.

     <StackLayout>
            <Button Text="add" Command="{Binding AddCommand}" IsEnabled="{Binding AddCouldExe}"></Button>
    
           <ListView ItemsSource="{Binding DataList}"   Margin="20">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <Grid Margin="0,0,0,10">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*" />
                                    <ColumnDefinition Width="Auto" />
    
                                </Grid.ColumnDefinitions>
                                <StackLayout VerticalOptions="CenterAndExpand">
                                    <Label Text="{Binding Data.Name}"  />
                                    <Label Text="{Binding Data.Description}" FontSize="10" />
                                </StackLayout>
                                <Switch IsToggled="{Binding Selected}"  Grid.Column="1" />
    
                            </Grid>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
    
    
    
    
        </StackLayout>
    

Answers

  • LeonLuLeonLu Member, Xamarin Team Xamurai

    Do you want to achieve the result like this GIF?

    If so, you can make Button to bind same property like following layout.

      <StackLayout>
            <ListView ItemsSource="{Binding DataList}" Margin="20">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <Grid Margin="0,0,0,10">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*" />
                                    <ColumnDefinition Width="Auto" />
                                    <ColumnDefinition Width="Auto" />
                                </Grid.ColumnDefinitions>
                                <StackLayout VerticalOptions="CenterAndExpand">
                                    <Label Text="{Binding Data.Name}"  />
                                    <Label Text="{Binding Data.Description}" FontSize="10" />
                                </StackLayout>
                                <Switch IsToggled="{Binding Selected}" Grid.Column="1" />
                                <Button Text="More" Grid.Column="2" IsEnabled="{Binding Selected}"></Button>
                            </Grid>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
        </StackLayout>
    

    Here is layout's background code.

    public partial class MainPage : ContentPage
        {
            public MainPage()
            {
                InitializeComponent();
                this.BindingContext = new MyViewModel();
            }
        }
    

    In your Model, you should achieve the INotifyPropertyChanged interface for changing at runtime. Here is MyViewModel.cs

     public class MyViewModel
        {
    
    
            public MyViewModel()
            {
                DataList = new List<SelectableData<ExampleData>>();
                DataList.Add(new SelectableData<ExampleData>() {  Data=new ExampleData() { Description="This is test", Name="Name1" }, Selected=true });
                DataList.Add(new SelectableData<ExampleData>() { Data = new ExampleData() { Description = "This is test", Name = "Name1" }, Selected = true });
                DataList.Add(new SelectableData<ExampleData>() { Data = new ExampleData() { Description = "This is test", Name = "Name1" }, Selected = false });
                DataList.Add(new SelectableData<ExampleData>() { Data = new ExampleData() { Description = "This is test", Name = "Name1" }, Selected = false });
    
            }
    
            public class ExampleData
            {
                public string Name { get; set; }
                public string Description { get; set; }
            }
    
            public List<SelectableData<ExampleData>> DataList { get; set; }
    
            public class SelectableData<T>: INotifyPropertyChanged
            {
                public T Data { get; set; }
    
                bool _selected = false;
                public bool Selected
                {
                    get
                    {
                        return _selected;
                    }
    
                    set
                    {
                        if (_selected != value)
                        {
                            _selected = value;
                            OnPropertyChanged("Selected");
    
                        }
                    }
    
                }
                public event PropertyChangedEventHandler PropertyChanged;
    
                protected virtual void OnPropertyChanged(string propertyName)
                {
                    PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
                }
                // public bool Selected { get; set; }
            }
    
        }
    

    Xamarin forums are migrating to a new home on Microsoft Q&A!
    We invite you to post new questions in the Xamarin forums’ new home on Microsoft Q&A!
    For more information, please refer to this sticky post.

    Are there any update for this issue, please reply is helpful, please click the Yes tab under the helpful answer.

  • RcmpAduRcmpAdu Member ✭✭

    Thanks for the reply, I should explain my scenario a little better. There is just a single button that is not part of the ListView control itself, and as per the example, the Switch controls provide a kind of "Multi-Select" functionality to the ListView. So I want that button to become enabled when 1 or more of the Switch controls located inside the ListView itself are set to true, and disabled when none are set to true. This button is bound to a command which allows adding a record when 1 or more ListView rows are "Selected" and disabled when none are "Selected". I hope that makes sense and better clarifies what I'm trying to do.

  • LeonLuLeonLu Member, Xamarin Team Xamurai
    Accepted Answer

    Do you want to achieve the result like following GIF?

    if so, you should foreach the DataList, the add the PropertyChanged property like following code in MyViewModel's constructor. This PropertyChanged perpery could monitor the SelectableData's data change, If the SelectableData's Selected value

           public class MyViewModel: INotifyPropertyChanged
        {
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected virtual void OnPropertyChanged(string propertyName)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
    
             bool  _addCouldExe ;
            public bool AddCouldExe
            {
                get
                {
                    return _addCouldExe;
                }
    
                set
                {
                    if (_addCouldExe != value)
                    {
    
    
                        _addCouldExe = value;
                        OnPropertyChanged("AddCouldExe");
    
                    }
                }
    
            }
    
    
            public ICommand AddCommand { protected set; get; }
            public MyViewModel()
            {
                DataList = new ObservableCollection<SelectableData<ExampleData>>();
    
    
                DataList.Add(new SelectableData<ExampleData>() {  Data=new ExampleData() { Description="This is test", Name="Name1" }, Selected=false });
                DataList.Add(new SelectableData<ExampleData>() { Data = new ExampleData() { Description = "This is test", Name = "Name1" }, Selected = true });
                DataList.Add(new SelectableData<ExampleData>() { Data = new ExampleData() { Description = "This is test", Name = "Name1" }, Selected = false });
                DataList.Add(new SelectableData<ExampleData>() { Data = new ExampleData() { Description = "This is test", Name = "Name1" }, Selected = false });
                AddCommand = new Command(async () =>
                {
                    DataList.Add(new SelectableData<ExampleData>() { Data = new ExampleData() { Description = "This is test", Name = "Name1" }, Selected = false });
                });
    
    
                foreach(SelectableData<ExampleData> data in DataList)
                {
                    //inite the button state
                    if (data.Selected)
                    {
                        AddCouldExe = true;
                    }
    
                    data.PropertyChanged += Data_PropertyChanged;
    
                }
    
            }
    
            private void Data_PropertyChanged(object sender, PropertyChangedEventArgs e)
            {
                if(e.PropertyName=="Selected")
                {
                    var model = sender as SelectableData<ExampleData>;
                    bool isSelected = model.Selected;
                   if(isSelected)
                      AddCouldExe = true;
    
    
                    //when Selected value changed, foreach the DataList, if data.Selected=true, do not have any change, if all of data.Selected=false, change the AddCouldExe value to false, make the Button cannot be clicked.
                    foreach (SelectableData<ExampleData> data in DataList)
                    {
                        if (data.Selected==true)
                        {
                            return;
                        }
    
    
    
                    }
                    AddCouldExe = false;
    
                }
            }
    
    
    
    
    
            public ObservableCollection<SelectableData<ExampleData>> DataList { get; set; }
    
    
    
        }
        public class ExampleData
        {
            public string Name { get; set; }
            public string Description { get; set; }
        }
        public class SelectableData<T> : INotifyPropertyChanged
        {
            public T Data { get; set; }
    
            bool _selected = false;
            public bool Selected
            {
                get
                {
                    return _selected;
                }
    
                set
                {
                    if (_selected != value)
                    {
    
    
                        _selected = value;
                        OnPropertyChanged("Selected");
    
                    }
                }
    
            }
            public event PropertyChangedEventHandler PropertyChanged;
    
            protected virtual void OnPropertyChanged(string propertyName)
            {
                PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
            }
            // public bool Selected { get; set; }
        }
    }
    
    

    Here is my layout.

     <StackLayout>
            <Button Text="add" Command="{Binding AddCommand}" IsEnabled="{Binding AddCouldExe}"></Button>
    
           <ListView ItemsSource="{Binding DataList}"   Margin="20">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <ViewCell>
                            <Grid Margin="0,0,0,10">
                                <Grid.ColumnDefinitions>
                                    <ColumnDefinition Width="*" />
                                    <ColumnDefinition Width="Auto" />
    
                                </Grid.ColumnDefinitions>
                                <StackLayout VerticalOptions="CenterAndExpand">
                                    <Label Text="{Binding Data.Name}"  />
                                    <Label Text="{Binding Data.Description}" FontSize="10" />
                                </StackLayout>
                                <Switch IsToggled="{Binding Selected}"  Grid.Column="1" />
    
                            </Grid>
                        </ViewCell>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>
    
    
    
    
        </StackLayout>
    
  • LeonLuLeonLu Member, Xamarin Team Xamurai

    @RcmpAdu Are there any update for this issue, please reply is helpful, please click the Yes tab under the helpful answer.

  • RcmpAduRcmpAdu Member ✭✭

    @LeonLu thanks for your help, that is the exact result I want to achieve. I have already implemented INotifyPropertyChanged and taken an approach similar to yours but without success, and I will focus on this tomorrow and let you know how I make out.

  • LeonLuLeonLu Member, Xamarin Team Xamurai

    @RcmpAdu Which error did you meet? You could copy my code in an new project, then test it.

  • RcmpAduRcmpAdu Member ✭✭

    @LeonLu I'm not getting an error but the button is not entering a disabled state when I deselect all of the options. I'm going to look at it now.

  • RcmpAduRcmpAdu Member ✭✭

    You could copy my code in an new project, then test it.

    Yes I created a test project using your code and it works as expected. Thanks for your help.

Sign In or Register to comment.