Пользовательский элемент управления с привязкой ItemsSource к ObservableCollection в модели представления, не обновляющейся при удалении / добавлении - PullRequest
0 голосов
/ 29 октября 2018

У меня есть PIN-поле на странице. Поле PIN реализовано с помощью пользовательского класса, полученного из макета стека, который добавляет возможности привязки источника к элементу. Его источник элемента привязан к ObservableCollection символов в моей модели представления. Проблема, с которой я сталкиваюсь, заключается в том, что в заголовке указано, что поле вывода не обновляется при добавлении и удалении из коллекции ObservableCollection.

Я читал посты с похожими проблемами для моего. Все их решения утверждают, что свойство ObservableCollection уведомляет свое свойство, измененное через интерфейсный вызов INotifyPropertyChanged. Я сделал это, и он до сих пор не обновляет графический интерфейс. Пожалуйста, помогите!

Вот код:

XAML для поля PIN

<utility:BindableStackLayout HeightRequest="40"
                             Orientation="Horizontal"
                             HorizontalOptions="Center"
                             ItemsSource="{Binding Pin}">
    <utility:BindableStackLayout.ItemDataTemplate>
        <DataTemplate>
            <skia:SKCanvasView PaintSurface="OnPaintSurfacePinDigit"/>
        </DataTemplate>
    </utility:BindableStackLayout.ItemDataTemplate>
</utility:BindableStackLayout>

SignInPage.xaml.cs

using System;
using MNPOS.ViewModel;
using Xamarin.Forms;
using SkiaSharp.Views.Forms;
using SkiaSharp;

namespace MNPOS.View
{
    public partial class SignInPage : CustomNavigationDetailPage
    {
        public SignInPage()
        {
            BindingContext = _viewModel;
            InitializeComponent();
        }

        public void OnPaintSurfacePinDigit(object sender, SKPaintSurfaceEventArgs e)
        {
            ...
        }

        private SignInViewModel _viewModel = new SignInViewModel();
    }
}

SignInViewModel

using System;
using System.Text;
using System.Collections;
using MNPOS.Configuration;
using Xamarin.Forms;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;

namespace MNPOS.ViewModel
{
    public class SignInViewModel : ViewModel
    {
        public SignInViewModel()
        {
            _appendDigitCommand = new Command<string>(AppendDigit);
            _clearDigitCommand = new Command(ClearDigit);
            _signInCommand = new Command(SignIn);
        }

        public void AppendDigit(string entry)
        {
            if (_pin.Count < Constants.MaximumPinLength)
            {
                _pin.Add(entry[0]);
            }
        }

        public void ClearDigit()
        {
            if (_pin.Count > 0)
            {
                _pin.RemoveAt(Pin.Count - 1);
            }
        }

        public void SignIn()
        {

        }

        public Command AppendDigitCommand => _appendDigitCommand;
        public Command ClearDigitCommand => _clearDigitCommand;
        public Command SignInCommand => _signInCommand;
        public ObservableCollection<char> Pin 
        {
            get { return _pin; } 
            set 
            {
                SetProperty<ObservableCollection<char>>(ref _pin, value, nameof(Pin));
            } 
        }

        private readonly Command _appendDigitCommand;
        private readonly Command _clearDigitCommand;
        private readonly Command _signInCommand;
        private ObservableCollection<char> _pin = new ObservableCollection<char>();
    }
}

ViewModel

using System.Collections.Generic;
using System.ComponentModel;
using System.Runtime.CompilerServices;

namespace MNPOS.ViewModel
{
    public abstract class ViewModel : INotifyPropertyChanged
    {
        public event PropertyChangedEventHandler PropertyChanged;

        protected virtual void OnPropertyChanged([CallerMemberName]string propertyName = null)
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs((propertyName)));
        }

        protected bool SetProperty<T>(ref T storage, T value, [CallerMemberName]string propertyName = null)
        {
            if (EqualityComparer<T>.Default.Equals(storage, value))
            {
                return false;
            }
            storage = value;
            OnPropertyChanged(propertyName);

            return true;
       }
   }

}

BindableStackLayout

using System.Collections;
using Xamarin.Forms;

namespace MNPOS.View.Utility
{
    public class BindableStackLayout : StackLayout
    {
        public IEnumerable ItemsSource
        {
            get { return (IEnumerable)GetValue(ItemsSourceProperty); }
            set { SetValue(ItemsSourceProperty, value); }
        }
        public static readonly BindableProperty ItemsSourceProperty =
            BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable), typeof(BindableStackLayout),
                                    propertyChanged: (bindable, oldValue, newValue) => ((BindableStackLayout)bindable).PopulateItems());

        public DataTemplate ItemDataTemplate
        {
            get { return (DataTemplate)GetValue(ItemDataTemplateProperty); }
            set { SetValue(ItemDataTemplateProperty, value); }
        }
        public static readonly BindableProperty ItemDataTemplateProperty =
            BindableProperty.Create(nameof(ItemDataTemplate), typeof(DataTemplate), typeof(BindableStackLayout));

        void PopulateItems()
        {
            if (ItemsSource == null) return;
            foreach (var item in ItemsSource)
            {
                var itemTemplate = ItemDataTemplate.CreateContent() as Xamarin.Forms.View;
                itemTemplate.BindingContext = item;
                Children.Add(itemTemplate);
            }
        }
    }
}

1 Ответ

0 голосов
/ 16 ноября 2018

Вам необходимо подписаться на события изменения коллекции в обработчике событий PropertyChanged.

Так что у вас BindableStackLayout Class change

public static readonly BindableProperty ItemsSourceProperty =
        BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable), typeof(BindableStackLayout),
                                propertyChanged: (bindable, oldValue, newValue) => ((BindableStackLayout)bindable).PopulateItems());

к этому:

public static readonly BindableProperty ItemsSourceProperty =
        BindableProperty.Create(nameof(ItemsSource), typeof(IEnumerable), typeof(BindableStackLayout),
                                propertyChanged: (bindable, oldValue, newValue) => ((BindableStackLayout)bindable).PopulateItems(oldValue, newValue));

Затем измените ваши PopulateItems на это:

void PopulateItems(IEnumerable oldValue, IEnumerable newValue)
    {

        if(oldItem != null)
            ((ObservableCollection<char>)oldItem).CollectionChanged -= CollectionChanged;

        if (newValue == null)
        {
            Children.Clear();
            return;
        }

        ((ObservableCollection<char>)newItem).CollectionChanged += CollectionChanged;


        foreach (var item in newItem)
        {
            var itemTemplate = ItemDataTemplate.CreateContent() as Xamarin.Forms.View;
            itemTemplate.BindingContext = item;
            Children.Add(itemTemplate);
        }
    }

Тогда метод CollectionChanged будет выглядеть примерно так:

        private void CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
    {
        switch (e.Action)
        {
            case NotifyCollectionChangedAction.Add:
                {
                    int index = e.NewStartingIndex;
                    foreach (var item in e.NewItems)
                        Children.Insert(index++, GetItemView(item));
                }
                break;
            case NotifyCollectionChangedAction.Move:
                {
                    var item = ObservableSource[e.OldStartingIndex];
                    Children.RemoveAt(e.OldStartingIndex);
                    Children.Insert(e.NewStartingIndex, GetItemView(item));
                }
                break;
            case NotifyCollectionChangedAction.Remove:
                {
                    Children.RemoveAt(e.OldStartingIndex);
                }
                break;
            case NotifyCollectionChangedAction.Replace:
                {
                    Children.RemoveAt(e.OldStartingIndex);
                    Children.Insert(e.NewStartingIndex, GetItemView(ObservableSource[e.NewStartingIndex]));
                }
                break;
            case NotifyCollectionChangedAction.Reset:
                Children.Clear();
                foreach (var item in ItemsSource)
                    Children.Add(GetItemView(item));
                break;
        }
    }

Обратите внимание, что в основном это было напечатано в браузере, поэтому могут быть некоторые опечатки, но это должно привести вас в правильном направлении.

Удачи

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...