通过IDictionary< string,object>进行绑定属性更改的事件处理程序为null [英] Binding through IDictionary<string,object> property changed event handler is null
问题描述
我有一个类 FormItem
,当我绑定<$时,该类实现了 IDictionary< string,object>
FormItem
的c $ c> Value 属性, PropertyChanged
事件始终为空,但是当我从 FormItem
中删除 IDictionary
时,则 FormItem.PropertyChanged
事件不为null。我想知道为什么在实现 IDictionary
时为null以及如何解决该问题。
I have a class FormItem
which implements an IDictionary<string, object>
, when I bind the Value
property of the FormItem
, the PropertyChanged
event is always null, but when I remove the IDictionary
from FormItem
then the FormItem.PropertyChanged
event is not null. I want to know why it is null when I am implementing IDictionary
and how it can be fixed.
示例
public class FormItem : INotifyPropertyChanged, IDictionary<string, object>
{
public int _value;
public int Value
{
get { return _value; }
set
{
_value= value;
OnPropertyChanged("Value");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public bool CanEdit
{
get { return true; }
}
IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
}
public IEnumerator<KeyValuePair<string, object>> GetEnumerator()
{
throw new NotImplementedException();
}
public void Add(KeyValuePair<string, object> item)
{
throw new NotImplementedException();
}
public void Clear()
{
throw new NotImplementedException();
}
public bool Contains(KeyValuePair<string, object> item)
{
throw new NotImplementedException();
}
public void CopyTo(KeyValuePair<string, object>[] array, int arrayIndex)
{
throw new NotImplementedException();
}
public bool Remove(KeyValuePair<string, object> item)
{
throw new NotImplementedException();
}
public int Count { get; }
public bool IsReadOnly { get; }
public void Add(string key, object value)
{
throw new NotImplementedException();
}
public bool ContainsKey(string key)
{
throw new NotImplementedException();
}
public bool Remove(string key)
{
throw new NotImplementedException();
}
public bool TryGetValue(string key, out object value)
{
throw new NotImplementedException();
}
public object this[string key]
{
get { throw new NotImplementedException(); }
set { throw new NotImplementedException(); }
}
public ICollection<string> Keys { get; }
public ICollection<object> Values { get; }
}
<Page
x:Class="App1.MainPage"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:local="using:App1"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d">
<Grid>
<Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">
<TextBox x:Name="text" Text="{Binding FormItem.Val}"></TextBox>
<Button x:Name="button" Visibility="{Binding FormItem.CanEdit}" Content="Hello World" />
</Grid>
</Grid>
</Page>
MainPage.xaml.cs:
using Windows.UI.Xaml.Controls;
// The Blank Page item template is documented at https://go.microsoft.com/fwlink/?LinkId=402352&clcid=0x409
namespace App1
{
/// <summary>
/// An empty page that can be used on its own or navigated to within a Frame.
/// </summary>
public sealed partial class MainPage : Page
{
public MainPage()
{
this.InitializeComponent();
this.DataContext= new DataPageViewModel();
}
}
}
DataPageViewModel.cs :
using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading.Tasks;
namespace App1
{
class DataPageViewModel : INotifyPropertyChanged
{
private FormItem _formItem;
public FormItem FormItem
{
get { return _formItem; }
set
{
_formItem = value;
OnPropertyChanged("FormItem");
}
}
public event PropertyChangedEventHandler PropertyChanged;
public DataPageViewModel()
{
this.FormItem = new FormItem();
}
protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
}
}
推荐答案
您看到的问题是Microsoft拒绝实现原始WPF API和WinRT / UWP API(主要从Silverlight / Windows Phone API(一种精简版本和重新发明的版本)派生而来)之间的均等的不幸结果WPF API)。我的猜测是,如果您问微软或至少是负责UWP的人员,他们会声称这是一项功能,而不是错误。
The issue you are seeing is an unfortunate consequence of Microsoft's refusal to achieve parity between the original WPF API and the WinRT/UWP API (which is derived primarily from the Silverlight/Windows Phone API, a dumbed-down and reinvented version of the WPF API). My guess is that if you asked Microsoft, or at least the folks responsible for UWP, they would claim this as a feature, not a bug.
这是怎么回事当您的类型实现 IDictionary< TKey,TValue>
接口时,UWP决定支持通过属性路径语法和通常的索引器语法为字典建立索引。也就是说,在WPF索引中,字典需要编写类似 Text = {Binding FormItem [SomeKey]}
的内容,而在UWP中,您可以编写 Text = {Binding FormItem.SomeKey}
。当然,这完全破坏了与类中任何实际 properties 的绑定。一旦该类实现了 IDictionary< TKey,TValue>
接口,您就只能访问 词典项目。
What's going on is that when your type implements the IDictionary<TKey, TValue>
interface, UWP decides to support indexing the dictionary via the property path syntax as well as the usual indexer syntax. That is, while in WPF indexing the dictionary would require writing something like Text={Binding FormItem[SomeKey]}
, in UWP you can write Text={Binding FormItem.SomeKey}
. Of course, this completely breaks binding to any actual properties in the class. Once the class implements the IDictionary<TKey, TValue>
interface, you're stuck being able to access only dictionary items.
更糟糕的是,当绑定到索引字典项时,UWP不会费心地订阅 PropertyChanged
事件。在WPF中,这有点棘手,但是您可以使用属性名称 Item引发 PropertyChanged
事件,并绑定到索引值(例如通过字典) )将被刷新。 UWP甚至不包含这种骇客行为。索引的字典项实际上是一次性绑定。
To make matters worse, when binding to an indexed dictionary item, UWP does not bother to subscribe to the PropertyChanged
event. In WPF, it's a bit hacky, but you can raise the PropertyChanged
event with the name of the property as "Item", and bindings to indexed values (such as through a dictionary) will be refreshed. UWP doesn't include even that hacky behavior. Indexed dictionary items are de facto one-time bindings.
如果是我,我只是不会在要访问其他属性的同一对象上实现字典接口。而是更改模型层次结构,以使您的模型对象严格地是 模型对象。如果您需要在某处进行字典操作,则将模型对象包含作为字典,而不是 be 作为字典。这样可以确保将索引的字典项和命名的属性分开。
If it were me, I would just not implement the dictionary interface on the same object where I wanted access to other properties. Instead, change the model hierarchy so that your model objects are strictly model objects. If you need dictionary behavior somewhere, make your model objects contain a dictionary, rather than be a dictionary. This will ensure you keep indexed dictionary items and named properties separate.
但是,在这种情况下,还有另一种解决方法。 {x:Bind}
语法受设计的限制更大,不会不合并索引器和属性路径语法。缺点是它也不依赖 DataContext
。但是,如果您愿意在 Page
类中添加属性,则可以使用 {x:Bind}
语法,它将按您期望的那样工作。例如:
However, there is another work-around in this case. The {x:Bind}
syntax, being more limited by design, does not conflate indexer and property path syntax. The down-side is that it also does not rely on DataContext
. But, if you're willing to add a property to your Page
class, you can use the {x:Bind}
syntax and that will work as you expect. For example:
public sealed partial class MainPage : Page
{
public DataPageViewModel Model { get { return (DataPageViewModel)DataContext; } }
public MainPage()
{
this.InitializeComponent();
this.DataContext = new DataPageViewModel();
}
// I added this to your example so that I had a way to modify
// the property and observe any binding updates
private void button_Click(object sender, RoutedEventArgs e)
{
Model.FormItem.Value++;
}
}
请注意,我包装了新属性中的DataContext
。这使您可以混合使用 {Binding}
和 {x:Bind}
语法。
Note that I wrapped the DataContext
in the new property. This allows you to mix-and-match {Binding}
and {x:Bind}
syntax.
还请注意,您要么需要将 DataPageViewModel
类更改为 public
或(根据我的喜好)将您的 MainPage
类更改为 not 是 public
。否则,您会收到一个编译时错误,抱怨新的 Model
属性的可访问性不一致。
Note also that you'll either need to change your DataPageViewModel
class to be public
or (my preference) change your MainPage
class to not be public
. Otherwise, you'll get a compile-time error complaining about inconsistent accessibility on the new Model
property.
然后,在XAML中:
<TextBox x:Name="text" Text="{x:Bind Model.FormItem.Value, Mode=TwoWay}"/>
请注意, {x:Bind}
是默认情况下是一次性绑定。要在属性更改时获取更新,您需要将模式显式设置为 OneWay
或 TwoWay
。
Note that {x:Bind}
is one-time binding by default. To get updates when the property changes, you need to explicitly set the mode to OneWay
or TwoWay
.
以这种方式实现视图后,您可以保留混合模型+词典设计,并且仍然让UWP观察属性更改。
With your view implemented this way, you can keep the mixed model+dictionary design and still have UWP observe property changes.
这篇关于通过IDictionary< string,object>进行绑定属性更改的事件处理程序为null的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!