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).
Я конвертирую приложение из WPF в UWP и нашел эту тему. Кажется, в Интернете нет хороших решений, поэтому я попытаюсь решить эту проблему с помощью обходного пути.
ПРИМЕЧАНИЕ: следующее непротестировано в 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 для поиска, он должен работать.
В XAML вы можете попробовать использовать RelativeSource, он предоставляет средство для указания источника привязки в терминах относительных отношений в графе объекта времени выполнения. Например, используя TemplatedParent
:
Height="{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