Привязка форм Xamarin Пользовательский элемент управления к ViewModel не работает

Я использую Xamarin Forms 2.4 для создания приложений для iOS и Android. Я использую пользовательский элемент управления ListView, чтобы включить бесконечную загрузку элементов, и в нижней части списка есть индикатор активности. У меня есть BindableProperties с именами IsLoadMorePostsAvailable и IsBusy, но после отладки я вижу, что они устанавливаются только при первой загрузке сообщений, но после того, как больше нет сообщений для загрузки, и я устанавливаю эти значения в false, ничего не меняется.

Вот мой пользовательский элемент управления ListView:

using System;
using System.Collections;
using System.Windows.Input;
using Xamarin.Forms;

namespace SOD_APP_V2.Controls
{
    /// <summary>
    /// A simple listview that exposes a bindable command to allow infinite loading behaviour.
    /// Along Auto Disable functionality
    /// </summary>
    public class InfiniteListView : ListView
    {
        ActivityIndicator ActivityIndicator_LoadingContents;

        public InfiniteListView()
        {                
            ActivityIndicator_LoadingContents = new ActivityIndicator
            {
                Color = Color.FromHex("#FF3300"),
                IsRunning = true,
                HorizontalOptions = LayoutOptions.FillAndExpand,
            };

            // Setting up 'Infinite Scrolling' items
            ItemAppearing += AwesomeListView_ItemAppearing;

            this.Footer = new StackLayout
            {
                Orientation = StackOrientation.Vertical,
                Children = { ActivityIndicator_LoadingContents },
                Padding = new Thickness(0, 5, 0, 5)
            };
        }

        #region UI Control Events

        /// <summary>
        /// List View ItemAppearing event for infinite scrolling
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void AwesomeListView_ItemAppearing(object sender, ItemVisibilityEventArgs e)
        {
            // Checking whether its possible to load more items
            if (!IsLoadMoreItemsPossible)
            {
                //((InfiniteListView)sender).Footer = null;//te,p
                return;
            }

            var items = ItemsSource as IList;

            if (items != null && e.Item == items[items.Count - 1])
            {
                if (LoadMoreInfiniteScrollCommand != null && LoadMoreInfiniteScrollCommand.CanExecute(null)) {}
                    LoadMoreInfiniteScrollCommand.Execute(null);
            }
        }

        #endregion

        #region Bindable Properties

        /// <summary>
        /// Gets or sets the property binding that defines whether the list has reached the 
        /// end of the items source thereby declaring its not possible to load more itmes... 
        /// </summary>
        public static readonly BindableProperty IsLoadMoreItemsPossibleProperty =
            BindableProperty.Create(nameof(IsLoadMoreItemsPossible), typeof(bool), typeof(InfiniteListView), default(bool), BindingMode.TwoWay,
                propertyChanging: (bindable, oldValue, newValue) =>
                {
                    var ctrl = (InfiniteListView)bindable;
                    ctrl.IsLoadMoreItemsPossible = (bool)newValue;
                });


        /// <summary>
        /// Gets or sets the property binding that defines whether the listview is busy 
        /// loading items in the background to the UI
        /// </summary>
        public static readonly BindableProperty IsBusyProperty =
            BindableProperty.Create(nameof(IsBusy), typeof(bool), typeof(InfiniteListView), default(bool), BindingMode.TwoWay,
                propertyChanging: (bindable, oldValue, newValue) =>
                {
                    var ctrl = (InfiniteListView)bindable;
                    ctrl.IsBusy = (bool)newValue;
                });

        /// <summary>
        /// Respresents the command that is fired to ask the view model to load additional data bound collection.
        /// </summary>
        public static readonly BindableProperty LoadMoreInfiniteScrollCommandProperty =
            BindableProperty.Create(nameof(LoadMoreInfiniteScrollCommand), typeof(ICommand), typeof(InfiniteListView), default(ICommand));

        #endregion

        #region Properties

        /// <summary>
        /// Gets or sets the property value that defines whether the list has reached the 
        /// end of the items source thereby declaring its not possible to load more itmes...
        /// </summary>
        public bool IsLoadMoreItemsPossible
        {
            get { return (bool)GetValue(IsLoadMoreItemsPossibleProperty); }
            set
            {
                SetValue(IsLoadMoreItemsPossibleProperty, value);
            }
        }

        /// <summary>
        /// Gets or sets the property value that defines whether listview is busy loading
        /// items in the background or not
        /// </summary>
        public bool IsBusy
        {
            get { return (bool)GetValue(IsBusyProperty); }
            set
            {
                SetValue(IsBusyProperty, value);

                // Setting the value to ActivityIndicator
                if (ActivityIndicator_LoadingContents != null)
                {
                    ActivityIndicator_LoadingContents.IsVisible = value;
                }
            }
        }

        #endregion

        #region Commands

        /// <summary>
        /// <summary>
        /// Gets or sets the command binding that is called whenever the listview is reaching the bottom items area
        /// </summary>
        public ICommand LoadMoreInfiniteScrollCommand
        {
            get { return (ICommand)GetValue(LoadMoreInfiniteScrollCommandProperty); }
            set { SetValue(LoadMoreInfiniteScrollCommandProperty, value); }
        }

        #endregion
    }
}

Как видите, свойство IsBusy должно отключить отображение ActivityIndicator после того, как больше нет сообщений.

Вот мой PostDataViewModel, привязка к ObservableCollection и PostTitle работает:

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Globalization;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Newtonsoft.Json.Linq;
using Xamarin.Forms;
using SOD_APP_V2.Model;

namespace SOD_APP_V2.ViewModel
{
    public class PostDataViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        //Bindable properties
        public ObservableCollection<PostDataModel> PostDataCollection { get; set; }
        public ICommand LoadMorePostsCommand { get; private set; }
        public ICommand RejectPostCommand { get; private set; }

        public static JObject SocialmediaSites { get; set; }
        public object SelectedItem { get; set; }

        bool isLoadMoreEnabled;
        public bool IsLoadMoreEnabled
        {
            get
            {
                return isLoadMoreEnabled;
            }
            set
            {
                if (isLoadMoreEnabled != value)
                {
                    isLoadMoreEnabled = value;
                    OnPropertyChanged((nameof(IsLoadMoreEnabled)));
                }
            }
        }

        bool isBusy;
        public bool IsBusy
        {
            get
            {
                return isBusy;
            }
            set
            {
                if (isBusy != value)
                {
                    isBusy = value;
                    OnPropertyChanged((nameof(IsBusy)));
                }
            }
        }

        string pageTitle;
        public string PageTitle
        {
            get
            {
                return pageTitle;
            }
            set
            {
                if (pageTitle != value)
                {
                    pageTitle = value;
                    OnPropertyChanged(nameof(PageTitle));
                }
            }
        }

        int currentPage = 1;
        public int CurrentPage
        {
            get
            {
                return currentPage; 
            }
            set
            {
                if(currentPage != value)
                {
                    currentPage = value;
                    OnPropertyChanged(nameof(CurrentPage));
                }
            }
        }

        string primaryColor;
        public string PrimaryColor
        {
            get
            {
                return primaryColor;
            }
            set
            {
                if (primaryColor != value)
                {
                    primaryColor = value;
                    OnPropertyChanged(nameof(PrimaryColor));
                }
            }
        }

        string secondaryColor;
        public string SecondaryColor
        {
            get
            {
                return secondaryColor;
            }
            set
            {
                if (secondaryColor != value)
                {
                    secondaryColor = value;
                    OnPropertyChanged(nameof(SecondaryColor));
                }
            }
        }

        public PostDataViewModel()
        {
            PostDataCollection = new ObservableCollection<PostDataModel>();
            SocialmediaSites = default(JObject);

            PrimaryColor = ConfigController.GetPrimaryColor(ApiController.DeploymentDomain);
            SecondaryColor = ConfigController.GetSecondaryColor(ApiController.DeploymentDomain);

            IsBusy = true;
            IsLoadMoreEnabled = true;

            LoadMorePostsCommand =
                new Command(async () => await GetPosts(), () => IsLoadMoreEnabled);

            string deployment = ConfigController.GetDeploymentName(ApiController.DeploymentDomain);

            MessagingCenter.Subscribe<PostsPage, JArray>(this, "translations", (sender, arg) => {
                PageTitle = (arg[0].ToString() != "") ? arg[0].ToString() : "Posts from " + deployment;
            });
            if (deployment != null)
            {
                //TODO: lang packs
                PageTitle = "Posts from " + deployment;
            }
        }

        public async Task<bool> GetPosts()
        {
            JObject posts = await ApiController.GetPostsFromVendor<JObject>(CurrentPage.ToString());

            if (posts != null)
            {
                IsLoadMoreEnabled = true;
                foreach (var entry in posts)
                {
                   //Building PostDataCollection here, binds to PostsPage as expected
                }
                CurrentPage += 1;
                IsBusy = false; // Should hide indicator, but doesn't
                return await Task.Run(() =>
                {
                    Task.Delay(1);
                    return true;
                });
            } else {
                IsLoadMoreEnabled = false; //No more posts to load, does not change the binding, this method still gets called then you scroll list view

                return await Task.Run(() =>
                {
                    Task.Delay(1);
                    return false;
                });
            }
        }

        protected virtual void OnPropertyChanged(String propertyName)
        {
            if (PropertyChanged != null)
            {
                PropertyChanged(this, new PropertyChangedEventArgs(nameof(propertyName)));
            }
        }
    }
}

А вот PostsPage.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:local="clr-namespace:SOD_APP_V2"
    xmlns:view="clr-namespace:SOD_APP_V2.View;assembly=SOD_APP_V2"
    xmlns:viewModel="clr-namespace:SOD_APP_V2.ViewModel;assembly=SOD_APP_V2"
    xmlns:controls="clr-namespace:SOD_APP_V2.Controls;assembly=SOD_APP_V2"
    x:Class="SOD_APP_V2.PostsPage" x:Name="PostsPage" BackgroundColor="White" Padding="0"
    >
    <ContentPage.BindingContext>
        <viewModel:PostDataViewModel x:Name="postDataViewModel"/>
    </ContentPage.BindingContext>
    <ContentPage.Content>
        <StackLayout Spacing="0" Padding="0" Margin="0" VerticalOptions="FillAndExpand">
            <StackLayout Orientation="Vertical" Padding="10, 5, 10, 5" VerticalOptions="FillAndExpand"> 
                <Label x:Name="titleLabel" Text="{Binding PageTitle}"
                    VerticalOptions="Start"
                    HorizontalTextAlignment="Center"
                    VerticalTextAlignment="Center"
                    BackgroundColor="Transparent"
                    HorizontalOptions="CenterAndExpand" />
                <controls:InfiniteListView x:Name="listView"
                    SelectedItem="{Binding SelectedItem,Mode=TwoWay}"
                    IsLoadMoreItemsPossible="{Binding Path=BindingContext.IsLoadMoreEnabled, Source={x:Reference listView}}"
                    LoadMoreInfiniteScrollCommand="{Binding LoadMorePostsCommand}"
                    IsEnabled="true"
                    IsBusy="{Binding IsBusy, Mode=TwoWay}"
                    HasUnevenRows="true"
                    ItemsSource="{Binding PostDataCollection}"
                    SeparatorVisibility="None"
                    VerticalOptions="FillAndExpand">
                    <controls:InfiniteListView.ItemTemplate>
                        <DataTemplate>
                            <ViewCell>
                                <ContentView>
                                    <ContentView.Content>
                                        <Frame HasShadow="false" CornerRadius="5" IsClippedToBounds="true" OutlineColor="{Binding Path=BindingContext.PrimaryColor, Source={x:Reference listView}}" Padding="0" Margin="10">
                                            <!-- Grid lives here with posts, bindings to post model works with no problems-->
                                        </Frame>
                                    </ContentView.Content>
                                </ContentView>
                            </ViewCell>
                        </DataTemplate>
                    </controls:InfiniteListView.ItemTemplate>
                </controls:InfiniteListView>
            </StackLayout>
            <StackLayout HorizontalOptions="FillAndExpand"
                VerticalOptions="End">
                <Label x:Name="infoLabel" Text="test"
                    Opacity="0"
                    TextColor="White"
                    BackgroundColor="#337ab7"
                    HorizontalTextAlignment="Center">
                </Label>
            </StackLayout>
        </StackLayout>
    </ContentPage.Content>
</ContentPage>

Итак, завершение

 <controls:InfiniteListView x:Name="listView"
                        SelectedItem="{Binding SelectedItem,Mode=TwoWay}"
                        IsLoadMoreItemsPossible="{Binding 
       IsLoadMoreEnabled}"
                        LoadMoreInfiniteScrollCommand="{Binding 
       LoadMorePostsCommand}"
                        IsEnabled="true"
                        IsBusy="{Binding IsBusy, Mode=TwoWay}"
                        HasUnevenRows="true"
                        ItemsSource="{Binding PostDataCollection}"
                        SeparatorVisibility="None"
                        VerticalOptions="FillAndExpand">

Привязка PostDataColection работает, но IsBusy и IsLoadMoreItemsPossible не пытались изменить на Path = BindingContext.IsLoadMorePossible, по-прежнему безрезультатно.

ОБНОВЛЕНИЕ: я нашел решение, которое, кажется, работает, но это очень некрасиво. Если я использую MessagingCenter в своей ViewModel и вместо того, чтобы устанавливать для привязываемого свойства значение false, я отправляю сообщение в PostsPage и устанавливаю значение false в событии подписки, но это не похоже на правильное решение.

0 ответов

Другие вопросы по тегам