2010-02-27 71 views
3

这种感觉应该是这样一种简单的解决方案,但我认为我通过考虑WPF术语中的问题而陷入瘫痪。如何使用绑定绑定到Silverlight中的祖父元素?

在我看来模型我有模式,其中一个容器包含项目集合(例如组和用户)。所以我创建了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元素。在WPF中,我会用FindAncestor如:

<TextBlock Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Group}}, Path=GroupName}"></TextBlock> 

如何,在Silverlight中,我可以使绑定到祖父母的财产?我猜测有一种我看不见的简单方法。

(我想我已经学会了Silverlight的第一次,而不是WPF,这样我就不会继续努力在Silverlight应用程序使用WPF具体的解决方案。)

回答

1

呀,SL是伟大的,但它是很难使用它在学习WPF之后,就像你写的一样。

我没有针对一般问题的解决方案。

对于这个特定的一个,既然你有一个视图模型,你可以有一个从用户的组指向一个回指针?如果用户可以属于不同的组,这意味着要为每个UserCollection创建一个特定副本。

+0

我明白你的意思,并会作出合理的变通。仍然希望有一个通用的解决方案,虽然... – Adrian 2010-02-28 23:39:23

+0

100%同意,也许SL 5 ... – Timores 2010-03-01 00:20:58

2

是的,不幸的是,RelativeSource标记扩展在Silverlight中仍然是一种残缺......它所支持的全部是TemplatedParent和Self,如果内存服务的话。而且由于Silverlight不支持用户创建的标记扩展(尚未),所以没有直接模拟FindAncestor语法。

现在认识到这是一个无用的评论,让我们看看我们是否可以找出一个不同的方式来做到这一点。我想直接将WPF中的FindAncestor语法移植到silverlight的问题与Silverlight没有真正的逻辑树的事实有关。我不知道你是否可以使用ValueConverter或附加的行为,以创造“的VisualTree行走”模拟...

(发生一些google搜索)

嘿,看起来像其他人试图在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> 
+0

不完全是我正在寻找,但它开始出现,我想要的东西不存在。这个机制看起来类似于我曾经见过的用于添加属性双向绑定到ASP.NET的机制。当我写这个问题的时候,我想我有一个金发的一天,只是错过了一些微不足道的东西,显然不是! – Adrian 2010-03-04 03:13:18