ItemsSource 绑定到视图模型中的 ObservableCollection 的自定义控件未在 deletion/addition 上更新
Custom control with ItemsSource bound to ObservableCollection in view model not updating upon deletion/addition
我在页面上有一个 PIN 字段。 PIN 字段是使用从堆栈布局派生的自定义 class 实现的,它添加了 item-source 绑定功能。它的项目源绑定到我的 view-model 中的一个 ObservableCollection 字符。我遇到的问题如标题所述,pin 字段不会在添加时更新,从 ObservableCollection 中删除。
我看过与我有类似问题的帖子。他们所有的解决方案都声明要确保 ObservableCollection 属性 通过 INotifyPropertyChanged 接口调用通知其 属性 已更改。我这样做了,但它仍然没有更新 GUI。请帮忙!
代码如下:
PIN 字段的xaml
<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);
}
}
}
}
您需要在 PropertyChanged 事件处理程序中订阅集合更改事件。
所以在你的 BindableStackLayout Class 改变
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;
}
}
请注意,这主要是在浏览器中输入的,因此可能会有一些拼写错误,但它应该会引导您朝着正确的方向前进。
祝你好运
我在页面上有一个 PIN 字段。 PIN 字段是使用从堆栈布局派生的自定义 class 实现的,它添加了 item-source 绑定功能。它的项目源绑定到我的 view-model 中的一个 ObservableCollection 字符。我遇到的问题如标题所述,pin 字段不会在添加时更新,从 ObservableCollection 中删除。
我看过与我有类似问题的帖子。他们所有的解决方案都声明要确保 ObservableCollection 属性 通过 INotifyPropertyChanged 接口调用通知其 属性 已更改。我这样做了,但它仍然没有更新 GUI。请帮忙!
代码如下:
PIN 字段的xaml
<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);
}
}
}
}
您需要在 PropertyChanged 事件处理程序中订阅集合更改事件。
所以在你的 BindableStackLayout Class 改变
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;
}
}
请注意,这主要是在浏览器中输入的,因此可能会有一些拼写错误,但它应该会引导您朝着正确的方向前进。
祝你好运