属性 中实现 INotify属性 的更改未反映在 UI 中
Changes in Property that implements INotifyPropertyChanged not being reflected in UI
我正在尝试实施 MVVM 模式,但遗憾的是花费的时间比预期的要长。
我有一个由 ContactsVm 的 ObservableCollection 填充的 ListView,添加或删除联系人效果很好,当试图通过选择它仅更改此集合中的一个项目时出现问题。
我设置绑定的Xaml:
<ListView ItemsSource="{Binding ContactsToDisplay}"
SelectedItem="{Binding SelectedContact, Mode=TwoWay}"
SeparatorColor="Black"
ItemSelected="OnItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding FirstName}"
Detail="{Binding Id}">
<TextCell.ContextActions>
<MenuItem
Text="Delete"
IsDestructive="true"
Clicked="Delete_OnClicked"
CommandParameter="{Binding .}" />
</TextCell.ContextActions>
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
它的cs:
public ContactBookApp()
{
InitializeComponent();
MapperConfiguration config = new MapperConfiguration(cfg => {
cfg.CreateMap<Contact, ContactVm>();
cfg.CreateMap<ContactVm, Contact>();
});
BindingContext = new ContactBookViewModel(new ContactService(), new PageService(), new Mapper(config));
}
private void AddButton_OnClicked(object sender, EventArgs e)
{
(BindingContext as ContactBookViewModel)?.AddContact();
}
private void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
(BindingContext as ContactBookViewModel)?.SelectContact(e.SelectedItem as ContactVm);
}
private void Delete_OnClicked(object sender, EventArgs e)
{
(BindingContext as ContactBookViewModel)?.DeleteContact((sender as MenuItem)?.CommandParameter as ContactVm);
}
}
我的 ViewModel,这里的 "problematic" 部分是 SelectContact 方法,我将发布其余部分以防有帮助:
public class ContactBookViewModel : BaseViewModel
{
private readonly IContactService _contactService;
private readonly IPageService _pageService;
private readonly IMapper _mapper;
private ContactVm _selectedContact;
public ObservableCollection<ContactVm> ContactsToDisplay { get; set; }
public ContactVm SelectedContact
{
get => _selectedContact;
set => SetValue(ref _selectedContact, value);
}
public ContactBookViewModel(IContactService contactService, IPageService pageService, IMapper mapper)
{
_contactService = contactService;
_pageService = pageService;
_mapper = mapper;
LoadContacts();
}
private void LoadContacts()
{
List<Contact> contactsFromService = _contactService.GetContacts();
List<ContactVm> contactsToDisplay = _mapper.Map<List<Contact>, List<ContactVm>>(contactsFromService);
ContactsToDisplay = new ObservableCollection<ContactVm>(contactsToDisplay);
}
public void SelectContact(ContactVm contact)
{
if (contact == null)
return;
//None of this approaches works:
//SelectedContact.FirstName = "Test";
//contact.FirstName = "Test;
}
}
}
我的联系人class:
public class ContactVm : BaseViewModel
{
private string _firstName;
public int Id { get; set; }
public string FirstName
{
get => _firstName;
set => SetValue(ref _firstName, value);
}
}
BaseViewModel:
public class BaseViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(ref T backingField, T value, [CallerMemberName]string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(backingField, value))
return;
backingField = value;
OnPropertyChanged(propertyName);
}
}
如您所见,我正在尝试更新每个选定的联系人设置其 FirstName = "Test",更改已更新,但不幸的是它们没有反映在 UI 中,希望您能帮我找出我做错了什么。
提前致谢!
我猜 NotifyPropertyChangedInvocator
属性没有正确通知 属性 更改。但我不确定。因为您的 BaseViewModel 没有实现 INotifyPropertyChanged
接口。
下面的代码对我来说工作正常。这就是我在整个项目中使用它的方式。
我直接在 BaseModel
中派生了 INotifyPropertyChanged
接口并实现了 属性 更改。
public class BaseModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class ContactVm : BaseModel
{
private string _firstName;
public int Id { get; set; }
public string FirstName
{
get { return _firstName; }
set
{
this._firstName = value;
NotifyPropertyChanged();
}
}
}
这就是我的回调。
public void SelectContact(ContactVm contact)
{
if (contact == null)
return;
contact.FirstName = "Test";
}
唯一的区别是我也对 ViewModel 中的 ObservableCollection 进行了 属性 更改。
public ObservableCollection<ContactVm> ContactsToDisplay
{
get { return _contactsToDisplay; }
set
{
this._contactsToDisplay = value;
NotifyPropertyChanged();
}
}
请注意,在我的案例中我没有使用您的 SelectedContact 绑定。可能正如您所说,绑定将是问题所在。
希望对您有所帮助。
由于您使用了 MVVM ,当您 select 列表视图中的项目时,您可以直接在 ViewModel 中处理逻辑(您不再需要定义 ItemSelected 事件)。
private ContactVm _selectedContact;
public ContactVm SelectedContact
{
set
{
if (_selectedContact!= value)
{
_selectedContact= value;
SelectedContact.FirstName="Test";
NotifyPropertyChanged("SelectedContact");
}
}
get { return _selectedContact; }
}
并且不要忘记为您的模型和视图模型实现 INotifyPropertyChanged。
您的 BaseViewModel 没有实现 INotifyPropertyChanged 接口。
我正在尝试实施 MVVM 模式,但遗憾的是花费的时间比预期的要长。
我有一个由 ContactsVm 的 ObservableCollection 填充的 ListView,添加或删除联系人效果很好,当试图通过选择它仅更改此集合中的一个项目时出现问题。
我设置绑定的Xaml:
<ListView ItemsSource="{Binding ContactsToDisplay}"
SelectedItem="{Binding SelectedContact, Mode=TwoWay}"
SeparatorColor="Black"
ItemSelected="OnItemSelected">
<ListView.ItemTemplate>
<DataTemplate>
<TextCell Text="{Binding FirstName}"
Detail="{Binding Id}">
<TextCell.ContextActions>
<MenuItem
Text="Delete"
IsDestructive="true"
Clicked="Delete_OnClicked"
CommandParameter="{Binding .}" />
</TextCell.ContextActions>
</TextCell>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
它的cs:
public ContactBookApp()
{
InitializeComponent();
MapperConfiguration config = new MapperConfiguration(cfg => {
cfg.CreateMap<Contact, ContactVm>();
cfg.CreateMap<ContactVm, Contact>();
});
BindingContext = new ContactBookViewModel(new ContactService(), new PageService(), new Mapper(config));
}
private void AddButton_OnClicked(object sender, EventArgs e)
{
(BindingContext as ContactBookViewModel)?.AddContact();
}
private void OnItemSelected(object sender, SelectedItemChangedEventArgs e)
{
(BindingContext as ContactBookViewModel)?.SelectContact(e.SelectedItem as ContactVm);
}
private void Delete_OnClicked(object sender, EventArgs e)
{
(BindingContext as ContactBookViewModel)?.DeleteContact((sender as MenuItem)?.CommandParameter as ContactVm);
}
}
我的 ViewModel,这里的 "problematic" 部分是 SelectContact 方法,我将发布其余部分以防有帮助:
public class ContactBookViewModel : BaseViewModel
{
private readonly IContactService _contactService;
private readonly IPageService _pageService;
private readonly IMapper _mapper;
private ContactVm _selectedContact;
public ObservableCollection<ContactVm> ContactsToDisplay { get; set; }
public ContactVm SelectedContact
{
get => _selectedContact;
set => SetValue(ref _selectedContact, value);
}
public ContactBookViewModel(IContactService contactService, IPageService pageService, IMapper mapper)
{
_contactService = contactService;
_pageService = pageService;
_mapper = mapper;
LoadContacts();
}
private void LoadContacts()
{
List<Contact> contactsFromService = _contactService.GetContacts();
List<ContactVm> contactsToDisplay = _mapper.Map<List<Contact>, List<ContactVm>>(contactsFromService);
ContactsToDisplay = new ObservableCollection<ContactVm>(contactsToDisplay);
}
public void SelectContact(ContactVm contact)
{
if (contact == null)
return;
//None of this approaches works:
//SelectedContact.FirstName = "Test";
//contact.FirstName = "Test;
}
}
}
我的联系人class:
public class ContactVm : BaseViewModel
{
private string _firstName;
public int Id { get; set; }
public string FirstName
{
get => _firstName;
set => SetValue(ref _firstName, value);
}
}
BaseViewModel:
public class BaseViewModel
{
public event PropertyChangedEventHandler PropertyChanged;
[NotifyPropertyChangedInvocator]
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
protected void SetValue<T>(ref T backingField, T value, [CallerMemberName]string propertyName = null)
{
if (EqualityComparer<T>.Default.Equals(backingField, value))
return;
backingField = value;
OnPropertyChanged(propertyName);
}
}
如您所见,我正在尝试更新每个选定的联系人设置其 FirstName = "Test",更改已更新,但不幸的是它们没有反映在 UI 中,希望您能帮我找出我做错了什么。
提前致谢!
我猜 NotifyPropertyChangedInvocator
属性没有正确通知 属性 更改。但我不确定。因为您的 BaseViewModel 没有实现 INotifyPropertyChanged
接口。
下面的代码对我来说工作正常。这就是我在整个项目中使用它的方式。
我直接在 BaseModel
中派生了 INotifyPropertyChanged
接口并实现了 属性 更改。
public class BaseModel : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void NotifyPropertyChanged([System.Runtime.CompilerServices.CallerMemberName] string propertyName = "")
{
if (this.PropertyChanged != null)
{
this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public class ContactVm : BaseModel
{
private string _firstName;
public int Id { get; set; }
public string FirstName
{
get { return _firstName; }
set
{
this._firstName = value;
NotifyPropertyChanged();
}
}
}
这就是我的回调。
public void SelectContact(ContactVm contact)
{
if (contact == null)
return;
contact.FirstName = "Test";
}
唯一的区别是我也对 ViewModel 中的 ObservableCollection 进行了 属性 更改。
public ObservableCollection<ContactVm> ContactsToDisplay
{
get { return _contactsToDisplay; }
set
{
this._contactsToDisplay = value;
NotifyPropertyChanged();
}
}
请注意,在我的案例中我没有使用您的 SelectedContact 绑定。可能正如您所说,绑定将是问题所在。
希望对您有所帮助。
由于您使用了 MVVM ,当您 select 列表视图中的项目时,您可以直接在 ViewModel 中处理逻辑(您不再需要定义 ItemSelected 事件)。
private ContactVm _selectedContact;
public ContactVm SelectedContact
{
set
{
if (_selectedContact!= value)
{
_selectedContact= value;
SelectedContact.FirstName="Test";
NotifyPropertyChanged("SelectedContact");
}
}
get { return _selectedContact; }
}
并且不要忘记为您的模型和视图模型实现 INotifyPropertyChanged。
您的 BaseViewModel 没有实现 INotifyPropertyChanged 接口。