我有一个ObservableCollection<dynamic>
类,XAML拒绝绑定到包含对象的属性。
我知道我读过XAML支持dynamic
和DyanmicObject
的某个地方所以我对为什么这不起作用感到非常困惑。其他问题,例如这个问题,非常缺乏帮助:
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>
答案 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}"/>