通过IDictionary< string,object>进行绑定属性更改的事件处理程序为null [英] Binding through IDictionary<string,object> property changed event handler is null

查看:73
本文介绍了通过IDictionary< string,object>进行绑定属性更改的事件处理程序为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&lt; string,object&gt;进行绑定属性更改的事件处理程序为null的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆