Quantcast
Channel: Xamarin.Forms — Xamarin Community Forums
Viewing all articles
Browse latest Browse all 89864

ContentView bindings not working

$
0
0

Hi everyone, I'm trying to create a custom control that allows me to show an horizontal list after having loaded the data from an online resource.
I'm still new to creating custom controls with property binding and this is quite frustrating to be honest :# .

In order to do what I want I derived a ContentView and this are its XAML an code-behind:

<?xml version="1.0" encoding="UTF-8"?>
<ContentView
    xmlns="http://xamarin.com/schemas/2014/forms"
    xmlns:x="http://schemas.microsoft.com/winfx/2009/xaml"
    xmlns:ff="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
    x:Class="AppName.Controls.BrandsCarousel"
    x:Name="brands_carousel"
    HeightRequest="120">
    <ContentView.Content>
        <StackLayout BindingContext="{x:Reference brands_carousel}">
            <ActivityIndicator IsVisible="{Binding IsLoading}" IsRunning="{Binding IsLoading}" />
            <CollectionView IsVisible="{Binding IsNotLoading}" ItemsSource="{Binding Brands}">
                <CollectionView.ItemsLayout>
                    <GridItemsLayout Orientation="Horizontal" Span="1" HorizontalItemSpacing="8" />
                </CollectionView.ItemsLayout>
                <CollectionView.ItemTemplate>
                    <DataTemplate>
                        <ff:CachedImage LoadingPlaceholder="tab_home" Source="{Binding Image}" WidthRequest="150" Aspect="AspectFit" />
                    </DataTemplate>
                </CollectionView.ItemTemplate>
            </CollectionView>
        </StackLayout>
    </ContentView.Content>
</ContentView>


using System.Collections;
using System.Runtime.CompilerServices;
using Xamarin.Forms;

namespace AppName.Controls
{
    public partial class BrandsCarousel : ContentView
    {
        public static readonly BindableProperty BrandsProperty =
            BindableProperty.Create(nameof(Brands), typeof(IEnumerable), typeof(BrandsCarousel), null,
                                    propertyChanged: OnBrandsChanged, defaultBindingMode: BindingMode.OneWay);

        public static readonly BindableProperty IsLoadingProperty =
            BindableProperty.Create(nameof(IsLoading), typeof(bool), typeof(BrandsCarousel), null,
                                    propertyChanged: OnIsLoadingChanged, defaultBindingMode: BindingMode.OneWay);

        private static void OnBrandsChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var control = (BrandsCarousel)bindable;
            control.Brands = newValue as IEnumerable;
        }

        private static void OnIsLoadingChanged(BindableObject bindable, object oldValue, object newValue)
        {
            var control = (BrandsCarousel)bindable;
            control.IsLoading = (bool)newValue;
        }

        public IEnumerable Brands
        {
            get => (IEnumerable)GetValue(BrandsProperty);
            set => SetValue(BrandsProperty, value);
        }

        public bool IsLoading
        {
            get => (bool)GetValue(IsLoadingProperty);
            set
            {
                SetValue(IsLoadingProperty, value);
                OnPropertyChanged(nameof(IsNotLoading));
            }
        }

        public bool IsNotLoading
        {
            get => !(bool)GetValue(IsLoadingProperty);
        }

        public BrandsCarousel()
        {
            InitializeComponent();
        }
    }
}

This is the view in which I'm including the custom control:

<?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:ff="clr-namespace:FFImageLoading.Forms;assembly=FFImageLoading.Forms"
    xmlns:controls="clr-namespace:AppName.Controls"
    xmlns:vm="clr-namespace:AppName.ViewModels"
    x:Class="AppName.Views.HomePage"
    Title="Home">
    <ContentPage.BindingContext>
        <vm:HomeViewModel />
    </ContentPage.BindingContext>
    <ContentPage.Content>
        <StackLayout Spacing="0">
            <Label Text="Our brands" StyleClass="SectionTitleLabel" />
            <controls:BrandsCarousel Brands="{Binding Brands}" IsLoading="{Binding LoadingBrands}" />
        </StackLayout>
    </ContentPage.Content>
</ContentPage>


using Xamarin.Forms;

namespace AppName.Views
{
    public partial class HomePage : ContentPage
    {
        public HomePage()
        {
            InitializeComponent();
        }
    }
}

And this is the view model associated with the view, responsible of loading the data from the online resource

using System.Collections.ObjectModel;
using System.Threading.Tasks;
using AppName.Models;
using AppName.Services;
using AsyncAwaitBestPractices.MVVM;

namespace AppName.ViewModels
{
    public class HomeViewModel : INotifyPropertyChanged
    {
        private ObservableCollection<Brand> _brands;
        public ObservableCollection<Brand> Brands
        {
            get => _brands;
            set => SetProperty(ref _brands, value);
        }

        public IAsyncCommand LoadBrandsCommand { get; set; }

        private bool _loadingBrands;
        public bool LoadingBrands
        {
            get => _loadingBrands;
            set => SetProperty(ref _loadingBrands, value);
        }

        public HomeViewModel()
        {
            LoadBrandsCommand = new AsyncCommand(LoadBrands, _ => !LoadingBrands);
            LoadBrandsCommand.Execute(null);
        }

        private async Task LoadBrands()
        {
            LoadingBrands = true;

            var brands = await BrandsDataStore.GetBrandsAsync();
            Brands = new ObservableCollection<Brand>(brands);

            LoadingBrands = false;
        }

        protected bool SetProperty<T>(ref T backingStore, T value, [CallerMemberName]string propertyName = "", Action onChanged = null)
        {
            if (EqualityComparer<T>.Default.Equals(backingStore, value))
                return false;

            backingStore = value;
            onChanged?.Invoke();
            OnPropertyChanged(propertyName);
            return true;
        }

        #region INotifyPropertyChanged
        public event PropertyChangedEventHandler PropertyChanged;
        protected void OnPropertyChanged([CallerMemberName] string propertyName = "")
        {
            var changed = PropertyChanged;
            if (changed == null)
                return;

            changed.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }
        #endregion
    }
}

The problem I'm facing is that the BrandsCarouse.Brands property gets correctly notified of the change happening inside of HomeViewModel.LoadBrands but the BrandsCarousel.IsLoading does not. What could be the reason?


Viewing all articles
Browse latest Browse all 89864

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>