FindVisualChild - 获取 ItemsControl 中命名 UI 元素的属性

FindVisualChild - Get properties of named UI Elements in ItemsControl

我使用 ItemsControl 在 运行 时生成 UI 个元素。 UI 生成成功,但如果我无法获得生成的 UI 项目的任何属性,例如标签的 "Content" 或 [=16= 的 SelectedItem ].我试图通过使用 this tutorial , and these answers 来获取这些属性,但我总是得到 NullReferenceException.

XAML 中的 ItemsControl 看起来像这样:

            <ItemsControl Name="ListOfVideos">
                <ItemsControl.Background>
                    <SolidColorBrush Color="Black" Opacity="0"/>
                </ItemsControl.Background>
                <ItemsControl.ItemTemplate>
                    <DataTemplate>
                        <Grid Margin="0,0,0,10">
                            <Grid.ColumnDefinitions>
                                <ColumnDefinition Width="180"/>
                                <ColumnDefinition Width="400"/>
                                <ColumnDefinition Width="200"/>
                            </Grid.ColumnDefinitions>
                            <Image HorizontalAlignment="Left" Height="100" Width="175" x:Name="VideoThumbnailImage" Stretch="Fill" Source="{Binding VideoThumbnailURL}" Grid.Column="0"></Image>
                            <Label x:Name="VideoTitleLabel" Content="{Binding VideoTitleText}" Foreground="White" Grid.Column="1" VerticalAlignment="Top" FontSize="16" FontWeight="Bold"></Label>
                            <Label x:Name="VideoFileSizeLabel" Content="{Binding VideoTotalSizeText}" Foreground="White" FontSize="14" Grid.Column="1" Margin="0,0,0,35" VerticalAlignment="Bottom"></Label>
                            <Label x:Name="VideoProgressLabel" Content="{Binding VideoStatusText}" Foreground="White" FontSize="14" Grid.Column="1" VerticalAlignment="Bottom"></Label>
                            <ComboBox x:Name="VideoComboBox" SelectionChanged="VideoComboBox_SelectionChanged" Grid.Column="2" Width="147.731" Height="20" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="0,0,0,50" ItemsSource="{Binding VideoQualitiesList}"></ComboBox>
                            <Label Content="Video Quality" Foreground="White" FontSize="14" VerticalAlignment="Top" Grid.Column="2" HorizontalAlignment="Center"></Label>
                            <Label Content="Audio Quality" Foreground="White" FontSize="14" VerticalAlignment="Bottom" HorizontalAlignment="Center" Margin="0,0,0,27" Grid.Column="2"></Label>
                            <Slider x:Name="VideoAudioSlider" Grid.Column="2" HorizontalAlignment="Center" VerticalAlignment="Bottom" Width="147.731" Maximum="{Binding AudioCount}"></Slider>
                        </Grid>
                    </DataTemplate>
                </ItemsControl.ItemTemplate>
            </ItemsControl>

这就是我生成 UI 元素的方式

public class VideoMetadataDisplay
    {
        public string VideoTitleText { get; set; }
        public int AudioCount { get; set; }
        public string VideoThumbnailURL { get; set; }
        public string VideoStatusText { get; set; }
        public string VideoTotalSizeText { get; set; }
        public List<string> VideoQualitiesList { get; set; }
    }

public partial class PlaylistPage : Page
{
private void GetPlaylistMetadata()
        {

            List<VideoMetadataDisplay> newList = new List<VideoMetadataDisplay>();
            //populate the list
            ListOfVideos.ItemsSource = newList;
        }
}

这就是我尝试获取 UI 元素属性的方式

public class Utils
    {
        public childItem FindVisualChild<childItem>(DependencyObject obj)
     where childItem : DependencyObject
        {
            for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
            {
                DependencyObject child = VisualTreeHelper.GetChild(obj, i);
                if (child != null && child is childItem)
                {
                    return (childItem)child;
                }
                else
                {
                    childItem childOfChild = FindVisualChild<childItem>(child);
                    if (childOfChild != null)
                        return childOfChild;
                }
            }
            return null;
        }
    }

private void VideoComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
        {
            UIElement CurrentItem = (UIElement)ListOfVideos.ItemContainerGenerator.ContainerFromItem(ListOfVideos.Items.CurrentItem);
            Utils utils = new Utils();
            ContentPresenter CurrentContentPresenter = utils.FindVisualChild<ContentPresenter>(CurrentItem);
            DataTemplate CurrentDataTemplate = CurrentContentPresenter.ContentTemplate;
            Label VideoTitle = (Label)CurrentDataTemplate.FindName("VideoTitleLabel", CurrentContentPresenter);
            string VideoTitleText = VideoTitle.Content.ToString();
            MessageBox.Show(VideoTitleText);
        }

每次我尝试 运行 时,FindVisualChild 总是 returns 标签之一 (VideoTitleLabel) 而不是返回 ContentPresenter当前活跃的项目。 CurrentDataTemplate 然后为空,我无法从中获取任何 UI 元素。

FindVisualChild<ContentPresenter>return不可能是Label实例。 FindVisualChild 将结果转换为 ContentPresenter。由于 Label 而不是 一个 ContentPresenter,这将抛出一个 InvalidCastException。但在此之前,child is childItem 会 return false 如果 childLabel 类型并且泛型参数类型 childItemContentPresenter 因此潜在的 null 是 returned.

精简版

访问 DataTemplate 或查找控件只是为了获取它们的绑定数据总是太复杂了。直接访问数据源总是更容易。
ItemsControl.SelectedItem 将 return 所选项目的数据模型。您通常对容器不感兴趣。

private void VideoComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
  var listView = sender as ListView;
  var item = listView.SelectedItem as VideoMetadataDisplay;
  MessageBox.Show(item.VideoTitleText);
}

您的版本(FindVisualChild 改进)

FindVisualChild 执行不力。如果遍历遇到没有 children 的 child 节点,即参数 objnull,它将失败并抛出异常。在调用 VisualTreeHelper.GetChildrenCount(obj) 之前,您必须检查参数 obj for null,以避免 null 引用。

此外,您不需要通过访问模板来搜索元素。可以直接在可视化树中查找。
我已修改您的 FindVisualChild 方法以按名称搜索元素。为了方便起见,我也把它变成了一个扩展方法:

扩展方法

public static class Utils
{
  public static bool TryFindVisualChildByName<TChild>(
    this DependencyObject parent,
    string childElementName,
    out TChild childElement,
    bool isCaseSensitive = false)
    where TChild : FrameworkElement
  {       
    childElement = null;

    // Popup.Child content is not part of the visual tree.
    // To prevent traversal from breaking when parent is a Popup,
    // we need to explicitly extract the content.
    if (parent is Popup popup)
    {
      parent = popup.Child;
    }

    if (parent == null)
    {
      return false;
    }

    var stringComparison = isCaseSensitive 
      ? StringComparison.Ordinal
      : StringComparison.OrdinalIgnoreCase;

    for (int i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
    {
      DependencyObject child = VisualTreeHelper.GetChild(parent, i);
      if (child is TChild resultElement 
        && resultElement.Name.Equals(childElementName, stringComparison))
      {
        childElement = resultElement;
        return true;
      }

      if (child.TryFindVisualChildByName(childElementName, out childElement))
      {
        return true;
      }
    }

    return false;
  }
}

例子

private void VideoComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
  var listView = sender as ListView;
  object item = listView.SelectedItem;
  var itemContainer = listView.ItemContainerGenerator.ContainerFromItem(item) as ListViewItem;

  if (itemContainer.TryFindVisualChildByName("VideoTitleLabel", out Label label))
  {
    var videoTitleText = label.Content as string;
    MessageBox.Show(videoTitleText);
  }
}