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
如果 child
是 Label
类型并且泛型参数类型 childItem
是 ContentPresenter
因此潜在的 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 节点,即参数 obj
为 null
,它将失败并抛出异常。在调用 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);
}
}
我使用 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
如果 child
是 Label
类型并且泛型参数类型 childItem
是 ContentPresenter
因此潜在的 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 节点,即参数 obj
为 null
,它将失败并抛出异常。在调用 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);
}
}