通过IDictionary <string,object>属性更改事件处理程序绑定为null

时间:2017-10-29 06:55:40

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

我有一个实现FormItem的类IDictionary<string, object>,当我绑定Value的{​​{1}}属性时,FormItem事件始终为null,但是当我从PropertyChanged中删除IDictionary时,FormItem事件不为空。我想知道为什么在我实现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; }
}

MainPage.xaml.cs中:

<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>

DataPageViewModel.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();
        }
    }
}

1 个答案:

答案 0 :(得分:1)

您所看到的问题是微软拒绝在原始WPF API和WinRT / UWP API(主要来自Silverlight / Windows Phone API,一个愚蠢和重新发明的版本)之间实现奇偶校验的不幸后果。 WPF API)。我的猜测是,如果你问微软,或者至少是那些负责UWP的人,他们会认为这是一个功能,而不是一个bug。

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

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

如果是我,我就不会在我希望访问其他属性的同一对象上实现字典界面。而是更改模型层次结构,以便模型对象严格模型对象。如果您需要某处的字典行为,请使模型对象包含字典,而不是 字典。这将确保您将索引字典项和命名属性保持独立。

然而,在这种情况下还有另一种解决方法。 {x:Bind}语法受设计的限制,会混淆索引器和属性路径语法。缺点是它也不依赖于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属性更改。