【问题标题】:UWP equivalent function to FindAncestor in uwpUWP 等效于 uwp 中 FindAncestor 的函数
【发布时间】:2016-10-24 16:26:11
【问题描述】:

我有一个订单列表,当订单状态为已取消时,我想闪烁文本。到目前为止,我的代码有效。但是,有时会抛出异常:

WinRT 信息:无法解析 TargetName lblOrderStatus

由于某种原因,可以找到 lblOrderStatus。所以,我想使用“FindAncestor”,但 UWP 中不存在 FindAncestor。 uwp中是否有与FindAncestor等价的功能?

这是我的代码:

<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>

【问题讨论】:

    标签: c# uwp win-universal-app windows-10-universal uwp-xaml


    【解决方案1】:

    考虑到我见过的所有解决方案,我觉得使用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 的屏幕中包含一个令牌 UI 元素,并以一致的名称引用它,它就会起作用。默认情况下,此元素的 DataContext 将是您的 ViewModel(假设您的根元素也是如此)

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

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

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

    然后,在您使用该模板资源的每个页面/屏幕/对话框上,只需放入该 Rectangle(或其他)的副本,所有内容都会在运行时正确绑定

    这显然是一个 hack 解决方案,但是在考虑了更多之后,它并不比首先使用 WPF 的 AncestorType 更像是一个 hack(必须确保您的祖先类型始终保持一致)您使用 DataTemplate 的地方)。

    【讨论】:

      【解决方案2】:

      我正在将应用程序从 WPF 转换为 UWP,并找到了这个线程。网络上似乎没有好的解决方案,所以这是我通过变通方法“解决”这个问题的尝试。

      注意: 以下是 UWP 中的 UNTESTED(但适用于 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 在 XAML 中应用 RelativeSourceBinding 属性

      WPF 中的一些 BEFORE 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 类型要查找,它应该可以工作。

      【讨论】:

      • 嗨,我会对此进行测试并报告。我没有机会研究它。再次感谢!
      • 它确实有效。如果需要,您可以随时将 DP 重命名为 Ancestor1TypeAncestor1,然后实现 Ancestor2TypeAncestor2 等。如前所述,语法并不理想,但可以满足需求。
      • 如果你喜欢它,请在上面加上 +1 环:D
      • 我们在此处的 Windows 社区工具包中有一个帮助器:docs.microsoft.com/en-us/windows/communitytoolkit/extensions/…
      【解决方案3】:

      在 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 新手。
      • VisualTreeHelper 类的目的是帮助发现您在对象的运行时树中寻找的对象,但没有更直接的对象关系 API 可用于您的方案。有时,您不会知道对象的确切类型或名称。或者您可能知道某个特定对象出现在树中的某个位置,但您不知道确切的位置。对于这些类型的场景,VisualTreeHelper 很有帮助,因为您可以递归地查找可视化树中的所有对象,然后查看此集合并根据您的条件查找匹配项。
      • @Sam 希望以上内容对您有所帮助。
      • 嗨 Rami,如果我使用 c# 代码(不是 XAML);我只是找不到一个好的方法(即绑定 OrderItemStatus 并为那个特定的 TextBlock 调用 Storyboard)。如您所见,这些项目在列表中,并且当其中一个项目状态更改为已取消时,我希望它仅针对该特定项目闪烁(不是所有项目,但仅针对该特定项目)。我想使用 VisualTreeHelper.GetParent,但不知道如何。
      • 嗨@Sam,在这篇文章中有一个如何使用GetChild() 的示例,它解释了GetParent() 的相同blog.jerrynixon.com/2012/09/…
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2020-07-05
      • 2017-04-23
      • 1970-01-01
      • 1970-01-01
      • 2017-12-02
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多