在WinRT中绑定到XAML中的DynamicObjects

时间:2013-09-04 02:51:10

标签: c# windows-runtime winrt-xaml

我有一个ObservableCollection<dynamic>类,XAML拒绝绑定到包含对象的属性。

我知道我读过XAML支持dynamicDyanmicObject的某个地方所以我对为什么这不起作用感到非常困惑。其他问题,例如这个问题,非常缺乏帮助:

Can i bind against a DynamicObject in WinRT / Windows 8 Store Apps

我在运行时遇到此错误(在设计时设计时将鼠标悬停在{Binding上):

Error: BindingExpression path error: 'DisplayName' property not found on 'PremiseMetro.Light, PremiseMetro, ... BindingExpression: Path='DisplayName' DataItem='PremiseMetro.Light, PremiseMetro, ... target element is 'Windows.UI.Xaml.Controls.TextBlock' (Name='null'); target property is 'Text' (type 'String')

请帮忙!

感谢。

测试ObservableObject课程:

class Light : DynamicObject, INotifyPropertyChanged {
    private readonly Dictionary<string, object> _properties = new Dictionary<string, object>();

    public event PropertyChangedEventHandler PropertyChanged;

    public override bool TryGetMember(GetMemberBinder binder, out object result) {
        string name = binder.Name;
        result = null;
        // If the property name is found in a dictionary, 
        // set the result parameter to the property value and return true. 
        // Otherwise, return false. 
        object prop;
        if (_properties.TryGetValue(name, out prop)) {
            result = prop;
            return true;
        }
        return false;
    }

    // If you try to set a value of a property that is 
    // not defined in the class, this method is called. 
    public override bool TrySetMember(SetMemberBinder binder, object value) {
        string name = binder.Name;

        _properties[name] = value;
        if (CoreApplication.MainView.CoreWindow.Dispatcher.HasThreadAccess)
            OnPropertyChanged(name);
        else
            CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(
                CoreDispatcherPriority.Normal, () => OnPropertyChanged(name));

        // You can always add a value to a dictionary, 
        // so this method always returns true. 
        return true;
    }

    public object GetMember(string propName) {
        var binder = Binder.GetMember(CSharpBinderFlags.None,
                                      propName, GetType(),
                                      new List<CSharpArgumentInfo> {
                                          CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                                      });
        var callsite = CallSite<Func<CallSite, object, object>>.Create(binder);

        return callsite.Target(callsite, this);
    }

    /// <summary>
    ///     Sets the value of a property.
    /// </summary>
    /// <param name="propertyName">Name of property</param>
    /// <param name="val">New value</param>
    /// <param name="fromServer">If true, will not try to update server.</param>
    public void SetMember(String propertyName, object val) {
        var binder = Binder.SetMember(CSharpBinderFlags.None,
                                      propertyName, GetType(),
                                      new List<CSharpArgumentInfo> {
                                          CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null),
                                          CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null)
                                      });
        var callsite = CallSite<Func<CallSite, object, object, object>>.Create(binder);

        callsite.Target(callsite, this, val);
    }

    protected virtual void OnPropertyChanged(string propertyName) {
        PropertyChangedEventHandler handler = PropertyChanged;
        if (handler != null) handler(this, new PropertyChangedEventArgs(propertyName));
    }            
}

我的MainViewMOdel构造函数中的测试:

Light light = new Light();
((dynamic) light).DisplayName = "Test Light";
((dynamic) light).Brightness= "27%";
((dynamic) light).PowerState= false;
Lights = new ObservableCollection<dynamic> {
    light
};

我的测试XAML:     

    <Grid Margin="10" Width="1000" VerticalAlignment="Stretch">
        <ListBox x:Name="GDOList" ItemsSource="{Binding Path=Lights}" >
            <ListBox.ItemTemplate>
                <DataTemplate >
                    <Grid Margin="6">
                        <StackPanel Orientation="Horizontal" >
                            <TextBlock Text="{Binding Path=DisplayName}" Margin="5" />
                            <TextBlock Text="{Binding Path=PowerState}" Margin="5" />
                            <TextBlock Text="{Binding Path=Brightness}" Margin="5" />
                        </StackPanel>
                    </Grid>
                </DataTemplate>
            </ListBox.ItemTemplate>
        </ListBox>
    </Grid>
</Page>

2 个答案:

答案 0 :(得分:1)

您可以尝试将ViewModel更改为:

dynamic light = new ExpandoObject();
light.DisplayName = "Test Light";
light.Brightness = "27%";
light.PowerState = false;
var objs = new ObservableCollection<dynamic> { light };

并查看它是否在您的lib中正常工作?

答案 1 :(得分:0)

简短答案:否,不支持在UWP中绑定到DynamicObject实例上的动态属性。

但是,确实存在使用ICustomPropertyProvider做类似事情的方法。

假设您的class看起来像这样:

public class SomeClass : DynamicObject, INotifyPropertyChanged {
    private string _StaticStringProperty;
    public string StaticStringProperty { get => _StaticStringProperty; set => SetField(ref _StaticStringProperty, value); }

    private Dictionary<string, object> _DynamicProperties = new Dictionary<string, object>();

    public override IEnumerable<string> GetDynamicMemberNames()
    {
        yield return "DynamicStringProperty";
        yield return "DynamicIntegerProperty";
    }

    public override bool TryGetMember(GetMemberBinder binder, out object result)
    {
        result = _DynamicProperties.GetValueOrDefault(binder.Name, null);
        return true;
    }

    public override bool TrySetMember(SetMemberBinder binder, object value)
    {
        _DynamicProperties[binder.Name] = value;
        RaisePropertyChanged(binder.Name);
        return true;
    }

    public event PropertyChangedEventHandler PropertyChanged;
    protected virtual void RaisePropertyChanged(string name) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(name));
    protected bool SetField<T>(ref T target, T value, [CallerMemberName]string caller = null)
    {
        if (EqualityComparer<T>.Default.Equals(target, value))
            return false;
        target = value;
        RaisePropertyChanged(caller);
        return true;
    }
}

现在让它实现ICustomPropertyProvider

public class SomeClass : DynamicObject, ICustomPropertyProvider, INotifyPropertyChanged {
    ...
    public Type Type => GetType();
    public string GetStringRepresentation() => ToString();

    public ICustomProperty GetCustomProperty(string name)
    {
        switch (name)
        {
            // the caveat is that you have to provide all the static properties, too...
            case nameof(StaticStringProperty):
                return new DynamicCustomProperty<SomeClass, string>()
                {
                    Name = name,
                    Getter = (target) => target.StaticStringProperty,
                    Setter = (target, value) => target.StaticStringProperty = value,
                };
            case "DynamicStringProperty":
                return new DynamicCustomProperty<SomeClass, string>()
                {
                    Name = name,
                    Getter = (target) => target.DynamicStringProperty,
                    Setter = (target, value) => target.DynamicStringProperty = value,
                };
            case "DynamicIntegerProperty":
                return new DynamicCustomProperty<SomeClass, int>()
                {
                    Name = name,
                    Getter = (target) => target.DynamicIntegerProperty,
                    Setter = (target, value) => target.DynamicIntegerProperty = value,
                };
            }
        }
        throw new NotImplementedException();
    }
    ...
}

并能够提供DynamicCustomProperty

public class DynamicCustomProperty<TOwner, TValue> : ICustomProperty
{
    public Func<dynamic, TValue> Getter { get; set; }
    public Action<dynamic, TValue> Setter { get; set; }
    public Func<dynamic, object, TValue> IndexGetter { get; set; }
    public Action<dynamic, object, TValue> IndexSetter { get; set; }

    public object GetValue(object target) => Getter.Invoke(target);
    public void SetValue(object target, object value) => Setter.Invoke(target, (TValue)value);
    public object GetIndexedValue(object target, object index) => IndexGetter.Invoke(target, index);
    public void SetIndexedValue(object target, object value, object index) => IndexSetter.Invoke(target, index, (TValue)value);

    public bool CanRead => Getter != null || IndexGetter != null;
    public bool CanWrite => Setter != null || IndexSetter != null;
    public string Name { get; set; }
    public Type Type => typeof(TValue);
}

最终,我们可以将它们绑定到XAML中:

<TextBox Header="Static String" Text="{Binding StaticStringProperty, Mode=TwoWay}"/>
<TextBox Header="Dynamic String" Text="{Binding DynamicStringProperty, Mode=TwoWay}"/>
<TextBox Header="Dynamic Integer" Text="{Binding DynamicIntegerProperty, Mode=TwoWay}"/>