Как использовать привязку для привязки к элементу grand-parent в Silverlight? - PullRequest
3 голосов
/ 28 февраля 2010

Такое ощущение, что это должно быть такое простое решение, но я думаю, что я искалечен, думая о проблеме в терминах WPF.

На мой взгляд, модель У меня есть шаблоны, в которых контейнер содержит коллекцию элементов (например, группы и пользователи). Поэтому я создаю 3 класса: «Группа», «Пользователь» и «Коллекция пользователя». В XAML я использую ItemsControl для повторения всех пользователей, например ::100100

<StackPanel DataContext="{Binding CurrentGroup}">
  <ItemsControl ItemsSource="{Binding UsersInGroup}">
    <ItemsControl.ItemTemplate>
      <DataTemplate>
        <TextBlock Text="{Binding UserName"></TextBlock>
      </DataTemplate>
    </ItemsControl.ItemTemplate>
  </ItemsControl>
</StackPanel>

Теперь в DataTemplate я хочу привязать элемент en в CurrentGroup. В WPF я бы использовал FindAncestor, например:

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

Как в Silverlight я могу сделать привязку к собственности бабушки и дедушки? Я предполагаю, что есть простой способ, который я не вижу.

(Хотелось бы, чтобы я сначала изучил Silverlight, а не WPF. Таким образом, я бы не стал пытаться использовать конкретные решения WPF в приложениях Silverlight.)

Ответы [ 2 ]

2 голосов
/ 01 марта 2010

Да, к сожалению, расширение разметки RelativeSource все еще в некотором роде повреждено в Silverlight ... все, что он поддерживает, это TemplatedParent и Self, если память служит. А поскольку Silverlight не поддерживает созданные пользователем расширения разметки (пока), прямого аналога синтаксиса FindAncestor нет.

Теперь, понимая, что это был бесполезный комментарий, давайте посмотрим, сможем ли мы найти другой способ сделать это. Я предполагаю, что проблема с прямым переносом синтаксиса FindAncestor из WPF в silverlight связана с тем фактом, что Silverlight не имеет истинного логического дерева. Интересно, можно ли использовать ValueConverter или Attached Behavior для создания аналога VisualTree-walking ...

(происходит поиск в Google)

Эй, похоже, кто-то еще пытался сделать это в Silverlight 2.0 для реализации ElementName - это может быть хорошим началом для обходного пути: http://www.scottlogic.co.uk/blog/colin/2009/02/relativesource-binding-in-silverlight/

EDIT: Хорошо, вот и все - нужно отдать должное автору вышеупомянутому автору, но я настроил его, чтобы удалить некоторые ошибки и т. Д. - все еще остается масса возможностей для улучшений:

    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 голос
/ 28 февраля 2010

Да, SL великолепен, но его трудно использовать после изучения WPF, точно так же, как вы пишете.

У меня нет решения для общей проблемы.

Для этого конкретного случая, поскольку у вас есть модель представления, можете ли вы иметь обратный указатель на группу от пользователя? Если пользователи могут принадлежать к разным группам, это означает создание определенной копии для каждой коллекции пользователей.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...