UWP эквивалентная функция FindAncestor в UWP

У меня есть список заказов и когда статус заказаотмененЯ хочу мигать текст. Пока что мой код работает. Тем не мение,иногда это вызовет исключение:

Информация WinRT: не удается разрешить TargetName lblOrderStatus

По какой-то причинеlblOrderStatus может быть найден. Итак, я хочу использовать «FindAncestor», но FindAncestor не существует в UWP.Есть ли эквивалентные функции FindAncestor в UWP?

Вот мой код:

<ItemsControl x:Name="Orders" Grid.Row="1" Background="Transparent">
    ...
    ...
    ...
    <ItemsControl.ItemTemplate>
        <DataTemplate>
            <Grid>
                ...
                ...
                ...
                <Viewbox Grid.Column="3" StretchDirection="DownOnly" HorizontalAlignment="Right">
                    <TextBlock x:Name="lblOrderStatus" Text="{Binding Path=OrderItemStatus, Mode=OneWay}" FontSize="18">
                        <TextBlock.Resources>
                            <Storyboard x:Name="sbBlinking">
                                <DoubleAnimation Storyboard.TargetProperty="(FrameworkElement.Opacity)"
                                                 Storyboard.TargetName="lblOrderStatus"
                                                 From="1" To="0" AutoReverse="True" Duration="0:0:0.5" RepeatBehavior="Forever" />
                            </Storyboard>
                        </TextBlock.Resources>
                        <interactive:Interaction.Behaviors>
                            <core:DataTriggerBehavior Binding="{Binding OrderItemStatus, Converter={StaticResource EnumToStringConverter}}" ComparisonCondition="Equal" Value="Cancelled">
                                <media:ControlStoryboardAction Storyboard="{StaticResource sbBlinking}" />
                            </core:DataTriggerBehavior>
                        </interactive:Interaction.Behaviors>
                    </TextBlock>
                </Viewbox>
            </Grid>
        </DataTemplate>
    </ItemsControl.ItemTemplate>
</ItemsControl>

Ответы на вопрос(3)

ElementName привязка - это самый простой обходной путь, при котором UWP не имеет опции привязки RelativeSource AncestorType.

Если у вас естьPage с этимиDataContext установить для модели представления с помощью командыMyCommandи вы хотите, чтобы каждый элемент в вашем списке выполнял его при нажатии на его кнопку:

<Page Name="thisPage">
    ...
    <ListView ...>
        <ListView.ItemTemplate>
            <DataTemplate>
                <Button Command="{Binding ElementName=thisPage, Path=DataContext.MyCommand}" />
            </DataTemplate>
        </ListView.ItemTemplate>
    </ListView>
</Page>

Моя первоначальная проблема с этим решением состоит в том, что вы не можете извлечь DataTemplate как ресурс, чтобы использовать его на нескольких экранах (или даже в диалоговых окнах);thisPage может не существовать в каждом из этих мест, или может быть неправильным называть корневой элемент «thisPage».

Но если вы используете соглашение, в котором вы включаете элемент пользовательского интерфейса токена в каждый экран, который использует этот DataTemplate, и ссылаетесь на него по непротиворечивому имени, это будет работать. По умолчанию DataContext этого элемента будет вашим ViewModel (при условии, что ваш корневой элемент тоже)

<Rectangle Name="VmDcHelper" Visibility="Collapsed"/> 

... тогда в вашем автономном файле ресурсов XAML вы можете написать свой DataTemplate следующим образом:

<DataTemplate x:Key="MyDataTemplate">
    <Button Command="{Binding ElementName=VmDcHelper, Path=DataContext.MyCommand}" />
</DataTemplate>

Затем на каждой странице / экране / диалоге, который вы используете этот ресурс шаблона, просто вставьте копию этого Rectangle (или чего-то еще), и все будет правильно связываться во время выполнения

Это, безусловно, решение для взлома, но если подумать об этом больше, то это уже не похоже на взлом, чем, во-первых, использование AncestorType в WPF (необходимо убедиться, что тип вашего предка всегда согласован во всех местах, где вы работаете. используйте свой DataTemplate).

в Интернете нет хороших решений, поэтому я попытаюсь решить эту проблему с помощью обходного пути.

НОТА: СледующееUNTESTED в UWP (но работает в WPF), так как я часть пути через большой некомпилируемый порт, но теоретически это должно работать ...

1 Создайте вложенное свойство RelativeSourceBinding

Этот класс имеет два свойства: AncestorType и Ancestor. Когда AncestorType изменяется, мы подписываемся на FrameworkElement.Loaded (для обработки родительских изменений) и находим визуального родителя типа и присваиваем вложенному свойству Ancestor.

public class RelativeSourceBinding
{
    public static readonly DependencyProperty AncestorTypeProperty = DependencyProperty.RegisterAttached("AncestorType", typeof(Type), typeof(RelativeSourceBinding), new PropertyMetadata(default(Type), OnAncestorTypeChanged));

    public static void SetAncestorType(DependencyObject element, Type value)
    {
        element.SetValue(AncestorTypeProperty, value);
    }

    public static Type GetAncestorType(DependencyObject element)
    {
        return (Type)element.GetValue(AncestorTypeProperty);
    }

    private static void OnAncestorTypeChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
    {
        ((FrameworkElement)d).Loaded -= OnFrameworkElementLoaded;

        if (e.NewValue != null)
        {
            ((FrameworkElement)d).Loaded += OnFrameworkElementLoaded;
            OnFrameworkElementLoaded((FrameworkElement) d, null);
        }
    }

    private static void OnFrameworkElementLoaded(object sender, RoutedEventArgs e)
    {
        var ancestorType = GetAncestorType((FrameworkElement) sender);
        if (ancestorType != null)
        {
            var findAncestor = ((FrameworkElement) sender).FindVisualParent(ancestorType);
            RelativeSourceBinding.SetAncestor(((FrameworkElement)sender), findAncestor);
        }
        else
        {
            RelativeSourceBinding.SetAncestor(((FrameworkElement)sender), null);
        }
    }

    public static readonly DependencyProperty AncestorProperty = DependencyProperty.RegisterAttached("Ancestor", typeof(UIElement), typeof(RelativeSourceBinding), new PropertyMetadata(default(FrameworkElement)));

    public static void SetAncestor(DependencyObject element, UIElement value)
    {
        element.SetValue(AncestorProperty, value);
    }

    public static UIElement GetAncestor(DependencyObject element)
    {
        return (UIElement)element.GetValue(AncestorProperty);
    }
}

Где FindVisualParent - метод расширения, определенный как

public static UIElement FindVisualParent(this UIElement element, Type type) 
{
    UIElement parent = element;
    while (parent != null)
    {
        if (type.IsAssignableFrom(parent.GetType()))
        {
            return parent;
        }
        parent = VisualTreeHelper.GetParent(parent) as UIElement;
    }
    return null;
}
2 Примените свойство RelativeSourceBinding в XAML

некоторые ДО xaml в WPF будут выглядеть так

<Style x:Key="SomeStyle" TargetType="local:AClass">
    <Style.Setters>
        <Setter Property="SomeProperty" Value="{Binding Foo, RelativeSource={RelativeSource AncestorType=local:AnotherClass}}" />
    </Style.Setters>
</Style>

и ПОСЛЕ xaml

<Style x:Key="SomeStyle" TargetType="local:AClass">
    <Style.Setters>
        <Setter Property="apc:RelativeSourceBinding.AncestorType" Value="local:AnotherClass"/>
        <Setter Property="Foreground" Value="{Binding Path=(apc:RelativeSourceBinding.Ancestor).Foo, RelativeSource={RelativeSource Self}}" />
    </Style.Setters>
</Style>

Это немного грязно, но в случае, когда у вас есть только один тип RelativeSource FindAncestor для поиска, он должен работать.

 Sam25 апр. 2017 г., 13:55
Привет, я проверю это и сообщу. У меня нет шанса изучить это. Еще раз спасибо!
 SuperJMN28 нояб. 2017 г., 23:17
Может кто-нибудь подтвердить, что это работает для UWP?
 MG-Seattle30 нояб. 2018 г., 21:21
Это работает. И если вам нужно, вы всегда можете переименовать ДП вAncestor1Type а такжеAncestor1 а затем реализоватьAncestor2Type, Ancestor2, и так далее. Как уже отмечалось, синтаксис не идеален, но он служит потребности.
 Dr. ABT01 дек. 2018 г., 12:13
Если вам нравится, положите на него кольцо +1: D

В XAML Вы можете попробовать использовать RelativeSource, он предоставляет средство для указания источника привязки в терминах относительных отношений в графе объектов времени выполнения. Например, используяTemplatedParent:

{Binding RelativeSource={RelativeSource TemplatedParent}, 
          Path=Parent.ActualHeight}

или же

<Binding RelativeSource="{RelativeSource TemplatedParent}" ></Binding>

В коде вы пытаетесь использовать метод VisualTreeHelper.GetParent.https://msdn.microsoft.com/en-us/library/windows/apps/windows.ui.xaml.media.visualtreehelper.getparent

что-то вроде следующего, вот пример функции полезности

internal static void FindChildren<T>(List<T> results, DependencyObject startNode)
  where T : DependencyObject
{
    int count = VisualTreeHelper.GetChildrenCount(startNode);
    for (int i = 0; i < count; i++)
    {
        DependencyObject current = VisualTreeHelper.GetChild(startNode, i);
        if ((current.GetType()).Equals(typeof(T)) || (current.GetType().GetTypeInfo().IsSubclassOf(typeof(T))))
        {
            T asType = (T)current;
            results.Add(asType);
        }
        FindChildren<T>(results, current);
    }
}

В следующем примере показан код, который проверяет родительский элемент

((StackPanel)LinePane.Parent).ActualWidth;

Кроме того, вот хорошее сообщение в блоге, показывающее этот класс в действии.http://blog.jerrynixon.com/2012/09/how-to-access-named-control-inside-xaml.html

 Rami Sarieddine27 июн. 2016 г., 12:30
@ Сэм, ты пробовал, RelativeSource в XAML?msdn.microsoft.com/en-us/windows/uwp/xaml-platform/...
 Sam27 июн. 2016 г., 09:13
Итак, мой вопрос, что является заменой FindAncestor в XAML. Из того, что я исследовал, нет ни одного :(
 Sam26 июн. 2016 г., 07:29
Привет Рами, как применить VisualTreeHelper.GetParent в xaml? Извините, я новичок в UWP.
 Rami Sarieddine27 июн. 2016 г., 07:02
@ Сэм, надеюсь, что выше было полезно.
 Sam27 июн. 2016 г., 12:06
Привет Рами, вернемся к моему первоначальному вопросу, мне нужно установить это в XAML. Если вы посмотрите на то, чего я пытаюсь достичь, это слишком грязно, чтобы делать это в коде c #. Я отмечу это как ответ, если вы знаете, как сделать эквивалент FindAncestor в XAML (только в XAML). В противном случае, я буду ждать, пока другие ответят. Спасибо друг...
 Sam27 июн. 2016 г., 12:39
да, это первое, что я попробовал, но это не работает :( :( :(
 Rami Sarieddine27 июн. 2016 г., 09:28
@ Сэм, я просто добавил еще один пример к своему ответу. ((StackPanel) LinePane.Parent) .ActualWidth; возможно это могло бы помочь. а когда вызывать? Вы должны вызывать на основе события, которое вы хотите, чтобы изменения произошли (то есть, статус изменился)
 Sam27 июн. 2016 г., 09:11
Привет, Рами, да, я провел исследование и теперь я знаю, как использовать GetParent, но моя проблема в том, что я не знаю, как легко вызывать / поднимать конкретную раскадровку, когда статус изменился. Единственный способ, о котором я могу подумать, - это просмотреть все эти элементы в списке, найти DataContext, затем найти раскадровку, затем установить для цели Storyboard его родителя, а затем запустить / воспроизвести раскадровку. Это действительно безобразно. Я предпочитаю способ сделать это в XAML. Я на полпути через это. Но это разбилось время от времени.
 Rami Sarieddine27 июн. 2016 г., 09:56
Привет @ Сэм, если не возражаешь. если выше было полезно. Вы можете пометить его как ответ, чтобы другие могли увидеть его как ответ? Спасибо
 Rami Sarieddine27 июн. 2016 г., 08:40
Привет @Sam, в этом посте есть пример того, как использовать GetChild (), который объясняет то же самое для GetParent ()blog.jerrynixon.com/2012/09/...
 Sam27 июн. 2016 г., 07:31
Привет Рами, если я использую код C # (не XAML); Я просто не могу найти хороший способ сделать это (т.е. связать OrderItemStatus и вызвать раскадровку для этого конкретного TextBlock). Как видите, элементы находятся в списке, и когда один из статусов элемента изменился на Отмененный, я хочу, чтобы он мигал только для этого конкретного элемента (не для всех элементов, а только для этого конкретного элемента). Я хочу использовать VisualTreeHelper.GetParent, но просто не знаю как.
 Rami Sarieddine26 июн. 2016 г., 12:49
Цель класса VisualTreeHelper - помочь в обнаружении объектов, которые вы ищете в дереве времени выполнения объектов, но для вашего сценария не существует более прямого API-интерфейса объектных отношений. Иногда вы не будете знать точный тип или название объекта. Или, возможно, вы знаете, что где-то в дереве появляется конкретный объект, но вы не знаете точную позицию. Для этих типов сценариев VisualTreeHelper полезен, потому что вы можете рекурсивно найти все объекты в визуальном дереве, а затем просмотреть этот набор и найти соответствие на основе ваших критериев.

Ваш ответ на вопрос