【问题标题】:How to use binding to bind to the grand-parent element in Silverlight?如何使用绑定绑定到 Silverlight 中的祖父元素?
【发布时间】:2011-01-21 21:27:26
【问题描述】:

感觉这应该是一个如此简单的解决方案,但我认为我在用 WPF 术语思考这个问题时已经瘫痪了。

在我的视图模型中,我有一个容器具有一组项目(例如组和用户)的模式。所以我创建了 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 特定的解决方案。)

【问题讨论】:

    标签: silverlight data-binding


    【解决方案1】:

    是的,不幸的是,Silverlight 中的 RelativeSource 标记扩展仍然有点残缺……它只支持 TemplatedParent 和 Self,如果有记忆的话。而且由于 Silverlight 不支持用户创建的标记扩展(目前),因此没有与 FindAncestor 语法直接类似的东西。

    现在意识到这是一个无用的评论,让我们看看我们是否可以找到一种不同的方式来做这件事。我想将 FindAncestor 语法从 WPF 直接移植到 silverlight 的问题与 Silverlight 没有真正的逻辑树这一事实有关。我想知道您是否可以使用 ValueConverter 或 Attached Behavior 来创建“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>
    

    【讨论】:

    • 不完全是我想要的,但开始出现我想要的东西并不存在。这种机制看起来类似于我见过的一些将属性双向绑定添加到 ASP.NET 的机制。当我写这个问题时,我以为我度过了一个金发碧眼的日子,只是错过了一些微不足道的事情,显然不是!
    【解决方案2】:

    是啊,SL很棒,但是学了WPF之后就很难用了,和你写的一模一样。

    我没有解决一般问题的方法。

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

    【讨论】:

    • 我明白你的意思,这将是一个合理的解决方法。不过仍然希望有一个通用的解决方案......
    猜你喜欢
    • 2010-12-11
    • 1970-01-01
    • 2011-01-27
    • 1970-01-01
    • 2013-09-20
    • 2011-01-25
    • 2013-09-22
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多