Forum Xamarin.Forms

Set Button IsVisible="true" only if the ListView is not empty

balauroiuradubalauroiuradu Member ✭✭
edited January 14 in Xamarin.Forms

Hello guys,

I'm struggling to change the Button visibility when the ListView is not empty. I have tried to create an ValueConvertor to return false when my list is empty and true when the list has 1 or more items. The thing is that after the ListView gets 1 or more elements the IsVisible property of the button is not binding anymore. At the start of the application the "Convert" method of the ValueConverter is executed and the button visibility is set to false.

This is my xaml code :

    <ContentPage.BindingContext>
        <viewModels:OrdersPageViewModel/>
    </ContentPage.BindingContext>
    <StackLayout>
        <ListView x:Name="OdersViewList">
            ...
        </ListView>
        <Button Text="Commit" 
        Clicked="Button_CommitClicked" 
        HorizontalOptions="Center"
        IsVisible="{Binding OrderListItems, 
        Converter={StaticResource ListIsNull}}"/>
    </StackLayout>

This is my ViewModel:

    public class OrdersPageViewModel : INotifyPropertyChanged
    {
        public ObservableCollection<OrderListItem> OrderListItems {get; set;}

        public event PropertyChangedEventHandler PropertyChanged;

        public OrdersPageViewModel()
        {
            OrderListItems = new ObservableCollection<OrderListItem>();

            OrderListItems.CollectionChanged += OrderListItems_CollectionChanged;
        }

        void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;

            if (handler != null)
            {
                handler(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        private void OrderListItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
        {
            OnPropertyChanged("OrderListItems");
        }
    }

This is my ValueConverter:

    public class ListIsNullConvert : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            ObservableCollection<OrderListItem> list = (ObservableCollection<OrderListItem>)value;

            if (list.Count == 0)
            {
                return false;
            }

            return true;
        }

        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }

Best Answers

  • LucasZhangLucasZhang Member, Xamarin Team Xamurai
    Accepted Answer

    Your code works fine on my side . So you could provide the code about add the items .

    In addition , you could implement ti without using ValueConverter

    in ViewModel

    public class ViewModel:INotifyPropertyChanged
        {
            public ObservableCollection<productDetails> OrderListItems { get; set; }
    
            bool isVisible=false;
            public bool IsVisible {
    
                get
                {
                    return isVisible;
                }
                set
                {
                    if(isVisible!=value)
                    {
                        isVisible = value;
                        OnPropertyChanged("IsVisible");
    
                    }
                }
    
            }
    
            public ViewModel()
            {
    
                OrderListItems = new ObservableCollection<productDetails>();
                OrderListItems.CollectionChanged += OrderListItems_CollectionChanged;
                Add();
            }
    
    
            async void Add()
            {
               //add items after 3s
                await Task.Delay(3000);
                OrderListItems.Add(new productDetails());
                OrderListItems.Add(new productDetails());
                OrderListItems.Add(new productDetails());
                OrderListItems.Add(new productDetails());
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            void OnPropertyChanged([CallerMemberName] string propertyName = null)
            {
                var handler = PropertyChanged;
    
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(propertyName));
                }
            }
    
            private void OrderListItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
    
                IsVisible = OrderListItems.Count == 0 ? false : true;
    
            }
        }
    

    in xaml

     <Button Text="Commit" 
            Clicked="Button_Clicked" 
            HorizontalOptions="Center"
            IsVisible="{Binding IsVisible}"/>
    
  • LucasZhangLucasZhang Member, Xamarin Team Xamurai
    Accepted Answer

    If you want to set the bindingContext in xaml

     <ContentPage.BindingContext>
        <local:xxxViewModel/>  // local here is the namespace of the project.
     </ContentPage.BindingContext>
    

Answers

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    You could try to use a new

    public bool IsButtonVisible {get;set;}
    

    and in your

            private void OrderListItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
                IsButtonVisible = OrderListItems .Count() == 0;
    
            }
    

    then

    <Button Text="Commit" 
            Clicked="Button_CommitClicked" 
            HorizontalOptions="Center"
            IsVisible="{Binding IsButtonVisible}"/>
    
  • LucasZhangLucasZhang Member, Xamarin Team Xamurai
    Accepted Answer

    Your code works fine on my side . So you could provide the code about add the items .

    In addition , you could implement ti without using ValueConverter

    in ViewModel

    public class ViewModel:INotifyPropertyChanged
        {
            public ObservableCollection<productDetails> OrderListItems { get; set; }
    
            bool isVisible=false;
            public bool IsVisible {
    
                get
                {
                    return isVisible;
                }
                set
                {
                    if(isVisible!=value)
                    {
                        isVisible = value;
                        OnPropertyChanged("IsVisible");
    
                    }
                }
    
            }
    
            public ViewModel()
            {
    
                OrderListItems = new ObservableCollection<productDetails>();
                OrderListItems.CollectionChanged += OrderListItems_CollectionChanged;
                Add();
            }
    
    
            async void Add()
            {
               //add items after 3s
                await Task.Delay(3000);
                OrderListItems.Add(new productDetails());
                OrderListItems.Add(new productDetails());
                OrderListItems.Add(new productDetails());
                OrderListItems.Add(new productDetails());
            }
    
            public event PropertyChangedEventHandler PropertyChanged;
    
            void OnPropertyChanged([CallerMemberName] string propertyName = null)
            {
                var handler = PropertyChanged;
    
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(propertyName));
                }
            }
    
            private void OrderListItems_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
            {
    
                IsVisible = OrderListItems.Count == 0 ? false : true;
    
            }
        }
    

    in xaml

     <Button Text="Commit" 
            Clicked="Button_Clicked" 
            HorizontalOptions="Center"
            IsVisible="{Binding IsVisible}"/>
    
  • balauroiuradubalauroiuradu Member ✭✭

    I updated my code and now i'm using an public bool IsButtonVisible property and in the xaml
    <Button Text="Commit" IsVisible="{Binding IsButtonVisible}"/> I also had some problems with the PropertyChangedevent because it was always null so I declared like this public event PropertyChangedEventHandler PropertyChanged = delegate {};. Now the thing is that when the list has 1 or more items the button becomes visible, but when the list count is again 0 and the IsButtonVisible is set to false, the button remains visible.

  • AlessandroCaliaroAlessandroCaliaro ITMember ✭✭✭✭✭

    This is my ViewModel

    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using System.ComponentModel;
    using System.Text;
    using System.Windows.Input;
    using Xamarin.Forms;
    
    namespace TestFody.ViewModel
    {
        public class SecondPageViewModel : INotifyPropertyChanged
        {
            public ObservableCollection<string> Items { get; set; } = new ObservableCollection<string>();
            public bool IsButtonVisible { get; set; }
            public SecondPageViewModel()
            {
    
                Items.CollectionChanged += Items_CollectionChanged;
    
                Items.Add("1");
                Items.Add("2");
                Items.Add("3");
    
    
                DeleteItemCommand = new Command(() => {
    
                    Items.RemoveAt(0);
                });
            }
    
            private void Items_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
            {
                IsButtonVisible = Items.Count == 0 ? false : true;
            }
    
            public ICommand DeleteItemCommand { get; protected set; }
    
            public event PropertyChangedEventHandler PropertyChanged;
        }
    }
    

    This is the xml

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage
        x:Class="TestFody.Page.SecondPage"
        xmlns="http://xamarin.com/schemas/2014/forms"
        xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
        xmlns:viewmodel="clr-namespace:TestFody.ViewModel">
        <ContentPage.Content>
            <StackLayout x:DataType="viewmodel:SecondPageViewModel" Orientation="Vertical">
                <Button
                    Command="{Binding DeleteItemCommand}"
                    IsVisible="{Binding IsButtonVisible}"
                    Text="Delete" />
                <Label
                    HorizontalOptions="CenterAndExpand"
                    Text="Welcome to Xamarin.Forms!"
                    VerticalOptions="CenterAndExpand" />
                <ListView
                    CachingStrategy="RecycleElement"
                    HasUnevenRows="True"
                    ItemsSource="{Binding Items}">
                    <ListView.ItemTemplate>
                        <DataTemplate>
                            <ViewCell>
                                <StackLayout Orientation="Vertical">
                                    <Label
                                        BackgroundColor="PaleVioletRed"
                                        Text="{Binding .}"
                                        TextColor="Black" />
                                </StackLayout>
                            </ViewCell>
                        </DataTemplate>
                    </ListView.ItemTemplate>
                </ListView>
            </StackLayout>
        </ContentPage.Content>
    </ContentPage>
    

    I use PropertyChanged.Fody to execute INPC
    Attached a video

  • JohnHardmanJohnHardman GBUniversity admin

    @balauroiuradu said:
    I updated my code and now i'm using an public bool IsButtonVisible property and in the xaml
    <Button Text="Commit" IsVisible="{Binding IsButtonVisible}"/> I also had some problems with the PropertyChangedevent because it was always null so I declared like this public event PropertyChangedEventHandler PropertyChanged = delegate {};. Now the thing is that when the list has 1 or more items the button becomes visible, but when the list count is again 0 and the IsButtonVisible is set to false, the button remains visible.

    Which of the above posts did you follow, the one from @LucasZhang or the one from @AlessandroCaliaro ?
    It looks like @AlessandroCaliaro is using Fody to add PropertyChanged invocation during the build process, whereas @LucasZhang is not assuming use of Fody. Unless you are using Fody, you will need to invoke PropertyChanged explicitly as per the code from @LucasZhang

  • balauroiuradubalauroiuradu Member ✭✭
    edited January 14

    I made a sample for testing the binding of the Button visibility. Below you can see the whole code. The problem is that the Button is always visible this time, and I can not change its visibility state. The visibility of the second button is changed in the clicked event of the first button.
    If I can make this sample test code work I'm sure I can make the other one work.

    JohnHardman in response to your question, I'm following both posts but I'm also using the INotifyPropertyChanged in my code.

    ViewModel:

    using System.ComponentModel;
    using System.Runtime.CompilerServices;
    
    namespace BindingTest.ViewModels
    {
        public class TestPageViewModel : INotifyPropertyChanged
        {
            private bool isButtonVisible = false;
    
            public bool IsButtonVisible
            {
                get
                {
                    return isButtonVisible;
                }
    
                set
                {
                    isButtonVisible = value;
    
                    OnPropertyChanged();
                }
            }
    
            public event PropertyChangedEventHandler PropertyChanged = delegate {};
    
            void OnPropertyChanged([CallerMemberName] string propertyName = null)
            {
                var handler = PropertyChanged;
    
                if (handler != null)
                {
                    handler(this, new PropertyChangedEventArgs(propertyName));
                }
            }
        }
    }
    

    Xaml:

    <?xml version="1.0" encoding="utf-8" ?>
    <ContentPage xmlns="http://xamarin.com/schemas/2014/forms"
                 xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
                 xmlns:viewmodel="clr-namespace:BindingTest.ViewModels"
                 x:Class="BindingTest.Views.TestPage">
    
        <StackLayout x:DataType="viewmodel:TestPageViewModel">
            <Button Text="Click1" IsVisible="true" Clicked="Button_Clicked"/>
            <Button Text="ClickVisible" IsVisible="{Binding IsButtonVisible}"/>
        </StackLayout>
    </ContentPage>
    

    Xaml.cs:

    using BindingTest.ViewModels;
    using System;
    using Xamarin.Forms;
    using Xamarin.Forms.Xaml;
    
    namespace BindingTest.Views
    {
        [XamlCompilation(XamlCompilationOptions.Compile)]
        public partial class TestPage : ContentPage
        {
            public TestPageViewModel testPageViewModel = new TestPageViewModel();
    
            public TestPage()
            {
                InitializeComponent();
            }
    
            private void Button_Clicked(object sender, EventArgs e)
            {
                testPageViewModel.IsButtonVisible = !testPageViewModel.IsButtonVisible;
            }
        }
    }
    
  • balauroiuradubalauroiuradu Member ✭✭

    I saw that placing this.BindingContext = testPageViewModel; after InitializeComponent works, but I don't want to involve the code here I want to set the BindingContext from the XAML file. Any ideea how I can set the BindingContext of my TestPage.xml to testPageViewModel property of my TestPage.xaml.cs class ?

  • LucasZhangLucasZhang Member, Xamarin Team Xamurai
    Accepted Answer

    If you want to set the bindingContext in xaml

     <ContentPage.BindingContext>
        <local:xxxViewModel/>  // local here is the namespace of the project.
     </ContentPage.BindingContext>
    
Sign In or Register to comment.