UWP中等价于FindAncestor函数的功能是什么?

12

我有一张订单列表,当订单状态为Cancelled时,我想让文字闪烁。到目前为止,我的代码可以工作。但是,有时候会抛出异常:

WinRT information: Cannot resolve TargetName lblOrderStatus

由于某些原因,lblOrderStatus无法被找到。所以,我想使用“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个回答

17

根据我看到的所有解决方案,我认为使用 ElementName 绑定是解决 UWP 没有 RelativeSource AncestorType 绑定选项的最简单方法。

假设您有一个将其 DataContext 设置为具有命令 MyCommand 的视图模型的 Page,并且您希望列表中的每个项目在其按钮被单击时执行该命令:

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

我对这个解决方案的初步问题是你不能将DataTemplate提取出来作为资源在多个屏幕(甚至对话框)上使用;thisPage可能不存在于这些地方中的每一个,或者将根元素命名为"thisPage"可能不合适。

但是,如果您使用一种约定,在使用该DataTemplate的每个屏幕中都包含一个代币UI元素,并通过一致的名称引用它,那么它将起作用。默认情况下,此元素的DataContext将是您的ViewModel(假设根元素也是)

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

然后在你的独立资源 XAML 文件中,你可以像这样编写 DataTemplate:

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

然后,在使用该模板资源的每个页面/屏幕/对话框上,只需放入该矩形(或其他内容),一切都将在运行时正确绑定

这显然是一种 hack 解决方案,但认真考虑后,它感觉并不比在首次使用 WPF 的 AncestorType 时更 hack(必须确保您的祖先类型在使用 DataTemplate 的所有位置始终保持一致)。


2
我正在将一个应用从WPF转换为UWP,并找到了这个线程。网上似乎没有好的解决方案,所以我尝试通过变通方法来“解决”这个问题。
注意:以下内容在UWP中未经测试(但在WPF中有效),因为我正在进行一个大型的非编译端口,但从理论上讲,它应该可以工作...
1、创建一个RelativeSourceBinding附加属性
这个类有两个属性:AncestorType和Ancestor。当AncestorType改变时,我们订阅FrameworkElement.Loaded(处理父级更改),并找到类型的可视化父级并分配给祖先附加属性。
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 在XAML中使用RelativeSourceBinding属性

在WPF中,一些早期的XAML可能如下所示:

<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类型的情况下,它应该可以工作。


1
嗨,我会测试一下并回报情况。我还没有机会研究它。再次感谢! - Sam
1
它可以工作。如果需要,您始终可以将DP重命名为“Ancestor1Type”和“Ancestor1”,然后实现“Ancestor2Type”、“Ancestor2”等等。正如所述,语法并不理想,但它能够满足需求。 - MG-Seattle
如果你喜欢它,就在上面加个+1的戒指:D - Dr. Andrew Burnett-Thompson
我们在Windows社区工具包中为此提供了一个帮助程序,网址是:https://learn.microsoft.com/en-us/windows/communitytoolkit/extensions/frameworkelementextensions#ancestortype。 - Michael Hawker - MSFT

0

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


嗨,Rami,如何在XAML中应用VisualTreeHelper.GetParent?抱歉,我是UWP的新手。 - Sam
VisualTreeHelper类的目的是帮助在对象运行时树中发现你正在查找的对象,但对于你的场景并没有更直接的对象关系API可用。有时候,你可能不知道对象的确切类型或名称。或者,你知道某个特定的对象出现在树的某个地方,但你不知道具体位置。对于这些类型的场景,VisualTreeHelper很有用,因为你可以递归地查找视觉树中的所有对象,然后浏览这个集合,并根据你的标准查找匹配项。 - Rami Sarieddine
嗨Rami,是的,我已经做了研究,现在我知道如何使用GetParent,但我的问题是当状态改变时如何轻松地调用/触发特定的Storyboard。我能想到的唯一方法是循环遍历列表中的所有项,找到DataContext,然后找到Storyboard,然后将Storyboard目标设置为其父级,然后运行/播放Storyboard。这真的很丑陋。我更喜欢在XAML中完成它的方式。我已经完成了一半。但它经常崩溃。 - Sam
所以我的问题是,在XAML中FindAncestor的替代方法是什么。根据我的研究,似乎没有替代方法 :( - Sam
是的,那是我尝试的第一件事情,但它不工作 :( :( :( - Sam
显示剩余7条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接