【问题标题】:Binding through IDictionary<string,object> property changed event handler is null通过 IDictionary<string,object> 属性更改的事件处理程序绑定为 null
【发布时间】:2018-04-10 09:22:53
【问题描述】:

我有一个实现IDictionary&lt;string, object&gt; 的类FormItem,当我绑定FormItemValue 属性时,PropertyChanged 事件始终为空,但是当我从@ 中删除IDictionary 987654327@ 那么FormItem.PropertyChanged 事件不为空。我想知道我在实现IDictionary 时为什么它为空以及如何修复它。

例子

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));
        }
    }
}

【问题讨论】:

    标签: c# data-binding uwp uwp-xaml inotifypropertychanged


    【解决方案1】:

    您看到的问题是 Microsoft 拒绝实现原始 WPF API 和 WinRT/UWP API(主要源自 Silverlight/Windows Phone API,一个简化和重新发明的WPF API)。我的猜测是,如果你问微软,或者至少是负责 UWP 的人,他们会声称这是一项功能,而不是错误。

    发生的情况是,当您的类型实现 IDictionary&lt;TKey, TValue&gt; 接口时,UWP 决定支持通过属性路径语法以及通常的索引器语法对字典进行索引。也就是说,虽然在 WPF 索引字典中需要编写类似 Text={Binding FormItem[SomeKey]} 的内容,但在 UWP 中您可以编写 Text={Binding FormItem.SomeKey}。当然,这完全破坏了对类中任何实际 properties 的绑定。一旦该类实现了IDictionary&lt;TKey, TValue&gt; 接口,您将无法访问only 字典项。

    更糟糕的是,当绑定到索引字典项时,UWP 不会费心订阅PropertyChanged 事件。在 WPF 中,这有点 hacky,但您可以引发 PropertyChanged 事件,属性名称为“Item”,并且与索引值的绑定(例如通过字典)将被刷新。 UWP 甚至不包括那种骇人听闻的行为。索引字典项实际上是一次性绑定。

    如果是我,我不会在我想要访问其他属性的同一个对象上实现字典接口。相反,请更改模型层次结构,以便您的模型对象是严格模型对象。如果您在某处需要字典行为,请让您的模型对象包含 字典,而不是成为 字典。这将确保您将索引字典项和命名属性分开。

    但是,在这种情况下还有另一种解决方法。 {x:Bind} 语法受到设计的更多限制,not 将索引器和属性路径语法混为一谈。缺点是它也不依赖DataContext。但是,如果您愿意为您的 Page 类添加一个属性,您可以使用 {x:Bind} 语法,这将按您的预期工作。例如:

    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} 语法。

    另请注意,您需要将DataPageViewModel 类更改为public 或(我的偏好)将您的MainPage 类更改为public。否则,您将收到一个编译时错误,抱怨新的 Model 属性的可访问性不一致。

    然后,在 XAML 中:

    <TextBox x:Name="text" Text="{x:Bind Model.FormItem.Value, Mode=TwoWay}"/>
    

    注意{x:Bind}默认是一次性绑定。要在属性更改时获取更新,您需要将模式显式设置为OneWayTwoWay

    以这种方式实现您的视图,您可以保持混合模型+字典的设计,并且仍然让 UWP 观察属性变化。

    【讨论】:

    • 感谢@PeterDuniho 的回复。但是如果我像这样使用 x:bind &lt;Button x:Name="button" Visibility="{x:Bind Model.FormItem.VisibilityProvider[key],Converter={StaticResource BoolToVisibilityConverter}}" Content="Hello World"/&gt; ,我会得到无效的路径。 VisibilityProvider 实现 IPropertyStateProvider 和 IPropertyStateProvider 有像这样的索引器bool this[string key] { get; }
    • 添加 VisibilityProvider 类。public class VisibilityProvider : IPropertyStateProvider { private FormItem FormItem; public VisibilityProvider(FormItem formItem) { FormItem = formItem; } public bool this[string key] { get { return GetVisibilityValue(key); } } private bool GetVisibilityValue(string key) { bool result = true; return result; } public event PropertyChangedEventHandler PropertyChanged;}
    • 请不要尝试在 cmets 中放入大量代码。没有足够的空间或格式化功能来充分表达这一点。上面发布的答案有效,并根据您在问题中发布的代码进行了测试。如果您更改了场景,例如尝试对索引器使用相同的技术,那么您已经更改了问题并且应该将其作为一个新问题发布,而不是试图捎带这个问题。
    猜你喜欢
    • 1970-01-01
    • 2019-09-08
    • 2015-01-22
    • 2017-11-27
    • 1970-01-01
    • 2020-05-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多