这感觉应该是一个如此简单的解决方案,但我认为我在考虑WPF术语中的问题时已经瘫痪了。
在我的视图中,模型I具有容器具有项集合(例如,组和用户)的模式。所以我创建了3个类,“Group”,“User”和“UserCollection”。在XAML中,我使用ItemsControl来重复所有用户,例如:
<StackPanel DataContext="{Binding CurrentGroup}">
<ItemsControl ItemsSource="{Binding UsersInGroup}">
<ItemsControl.ItemTemplate>
<DataTemplate>
<TextBlock Text="{Binding UserName"></TextBlock>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</StackPanel>
现在,在DataTemplate中,我想绑定到CurrentGroup中的en元素。在WPF中,我会使用FindAncestor,例如:
<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Group}}, Path=GroupName}"></TextBlock>
在Silverlight中,我如何能够绑定祖父母的财产?我猜想有一种我看不到的简单方法。
(我希望我首先学习Silverlight而不是WPF。这样我就不会继续尝试在Silverlight应用程序中使用WPF特定的解决方案。)
答案 0 :(得分:2)
是的,遗憾的是,在Silverlight中,RelativeSource标记扩展仍然有点瘫痪...它支持的是TemplatedParent和Self,如果内存服务的话。由于Silverlight不支持用户创建的标记扩展(尚未),因此与FindAncestor语法没有直接的类比。
现在意识到这是一个无用的评论,让我们看看我们是否可以找出一种不同的做法。我想象直接将FindAncestor语法从WPF移植到silverlight的问题与Silverlight没有真正的逻辑树有关。我想知道你是否可以使用ValueConverter或附加行为来创建一个“VisualTree-walking”模拟...
(发生一些谷歌搜索)
嘿,看起来像其他人试图在Silverlight 2.0中实现这一点来实现ElementName - 这可能是一个解决方法的良好开端: http://www.scottlogic.co.uk/blog/colin/2009/02/relativesource-binding-in-silverlight/
编辑: 好的,在这里你去 - 应该给予上述作者适当的信誉,但我已经调整它以消除一些错误等 - 仍然有很大的改进空间:
public class BindingProperties
{
public string SourceProperty { get; set; }
public string ElementName { get; set; }
public string TargetProperty { get; set; }
public IValueConverter Converter { get; set; }
public object ConverterParameter { get; set; }
public bool RelativeSourceSelf { get; set; }
public BindingMode Mode { get; set; }
public string RelativeSourceAncestorType { get; set; }
public int RelativeSourceAncestorLevel { get; set; }
public BindingProperties()
{
RelativeSourceAncestorLevel = 1;
}
}
public static class BindingHelper
{
public class ValueObject : INotifyPropertyChanged
{
private object _value;
public object Value
{
get { return _value; }
set
{
_value = value;
OnPropertyChanged("Value");
}
}
public event PropertyChangedEventHandler PropertyChanged;
protected virtual void OnPropertyChanged(string propertyName)
{
if (PropertyChanged != null)
{
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
}
}
public static BindingProperties GetBinding(DependencyObject obj)
{
return (BindingProperties)obj.GetValue(BindingProperty);
}
public static void SetBinding(DependencyObject obj, BindingProperties value)
{
obj.SetValue(BindingProperty, value);
}
public static readonly DependencyProperty BindingProperty =
DependencyProperty.RegisterAttached("Binding", typeof(BindingProperties), typeof(BindingHelper),
new PropertyMetadata(null, OnBinding));
/// <summary>
/// property change event handler for BindingProperty
/// </summary>
private static void OnBinding(
DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
FrameworkElement targetElement = depObj as FrameworkElement;
targetElement.Loaded += new RoutedEventHandler(TargetElement_Loaded);
}
private static void TargetElement_Loaded(object sender, RoutedEventArgs e)
{
FrameworkElement targetElement = sender as FrameworkElement;
// get the value of our attached property
BindingProperties bindingProperties = GetBinding(targetElement);
if (bindingProperties.ElementName != null)
{
// perform our 'ElementName' lookup
FrameworkElement sourceElement = targetElement.FindName(bindingProperties.ElementName) as FrameworkElement;
// bind them
CreateRelayBinding(targetElement, sourceElement, bindingProperties);
}
else if (bindingProperties.RelativeSourceSelf)
{
// bind an element to itself.
CreateRelayBinding(targetElement, targetElement, bindingProperties);
}
else if (!string.IsNullOrEmpty(bindingProperties.RelativeSourceAncestorType))
{
Type ancestorType = Assembly.GetExecutingAssembly().GetTypes().FirstOrDefault(
t => t.Name.Contains(bindingProperties.RelativeSourceAncestorType));
if(ancestorType == null)
{
ancestorType = Assembly.GetCallingAssembly().GetTypes().FirstOrDefault(
t => t.Name.Contains(bindingProperties.RelativeSourceAncestorType));
}
// navigate up the tree to find the type
DependencyObject currentObject = targetElement;
int currentLevel = 0;
while (currentLevel < bindingProperties.RelativeSourceAncestorLevel)
{
do
{
currentObject = VisualTreeHelper.GetParent(currentObject);
if(currentObject.GetType().IsSubclassOf(ancestorType))
{
break;
}
}
while (currentObject.GetType().Name != bindingProperties.RelativeSourceAncestorType);
currentLevel++;
}
FrameworkElement sourceElement = currentObject as FrameworkElement;
// bind them
CreateRelayBinding(targetElement, sourceElement, bindingProperties);
}
}
private static readonly BindingFlags dpFlags = BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy;
private struct RelayBindingKey
{
public DependencyProperty dependencyObject;
public FrameworkElement frameworkElement;
}
/// <summary>
/// A cache of relay bindings, keyed by RelayBindingKey which specifies a property of a specific
/// framework element.
/// </summary>
private static Dictionary<RelayBindingKey, ValueObject> relayBindings = new Dictionary<RelayBindingKey, ValueObject>();
/// <summary>
/// Creates a relay binding between the two given elements using the properties and converters
/// detailed in the supplied bindingProperties.
/// </summary>
private static void CreateRelayBinding(FrameworkElement targetElement, FrameworkElement sourceElement,
BindingProperties bindingProperties)
{
string sourcePropertyName = bindingProperties.SourceProperty + "Property";
string targetPropertyName = bindingProperties.TargetProperty + "Property";
// find the source dependency property
FieldInfo[] sourceFields = sourceElement.GetType().GetFields(dpFlags);
FieldInfo sourceDependencyPropertyField = sourceFields.First(i => i.Name == sourcePropertyName);
DependencyProperty sourceDependencyProperty = sourceDependencyPropertyField.GetValue(null) as DependencyProperty;
// find the target dependency property
FieldInfo[] targetFields = targetElement.GetType().GetFields(dpFlags);
FieldInfo targetDependencyPropertyField = targetFields.First(i => i.Name == targetPropertyName);
DependencyProperty targetDependencyProperty = targetDependencyPropertyField.GetValue(null) as DependencyProperty;
ValueObject relayObject;
bool relayObjectBoundToSource = false;
// create a key that identifies this source binding
RelayBindingKey key = new RelayBindingKey()
{
dependencyObject = sourceDependencyProperty,
frameworkElement = sourceElement
};
// do we already have a binding to this property?
if (relayBindings.ContainsKey(key))
{
relayObject = relayBindings[key];
relayObjectBoundToSource = true;
}
else
{
// create a relay binding between the two elements
relayObject = new ValueObject();
}
// initialise the relay object with the source dependency property value
relayObject.Value = sourceElement.GetValue(sourceDependencyProperty);
// create the binding for our target element to the relay object, this binding will
// include the value converter
Binding targetToRelay = new Binding();
targetToRelay.Source = relayObject;
targetToRelay.Path = new PropertyPath("Value");
targetToRelay.Mode = bindingProperties.Mode;
targetToRelay.Converter = bindingProperties.Converter;
targetToRelay.ConverterParameter = bindingProperties.ConverterParameter;
// set the binding on our target element
targetElement.SetBinding(targetDependencyProperty, targetToRelay);
if (!relayObjectBoundToSource && bindingProperties.Mode == BindingMode.TwoWay)
{
// create the binding for our source element to the relay object
Binding sourceToRelay = new Binding();
sourceToRelay.Source = relayObject;
sourceToRelay.Path = new PropertyPath("Value");
sourceToRelay.Converter = bindingProperties.Converter;
sourceToRelay.ConverterParameter = bindingProperties.ConverterParameter;
sourceToRelay.Mode = bindingProperties.Mode;
// set the binding on our source element
sourceElement.SetBinding(sourceDependencyProperty, sourceToRelay);
relayBindings.Add(key, relayObject);
}
}
}
你这样使用它:
<TextBlock>
<SilverlightApplication1:BindingHelper.Binding>
<SilverlightApplication1:BindingProperties
TargetProperty="Text"
SourceProperty="ActualWidth"
RelativeSourceAncestorType="UserControl"
Mode="OneWay"
/>
</SilverlightApplication1:BindingHelper.Binding>
</TextBlock>
答案 1 :(得分:1)
是的,SL很棒,但在学习WPF后很难使用它,就像你写的一样。
我没有针对一般问题的解决方案。
对于这个特定的一个,既然你有一个视图模型,你可以从用户那里得到一个指向该组的指针吗?如果用户可以属于不同的组,则意味着为每个UserCollection创建特定的副本。