ListBoxItem.Parent 什么也不返回,也无法通过 VisualTreeHelper.GetParent 获取它

发布于 2024-08-28 23:05:24 字数 1446 浏览 14 评论 0原文

如何提取 ListBoxItem 的父容器? 在下面的示例中,我可以转到 ListBoxItem,高于此值时我什么也得不到:

<ListBox Name="lbAddress">
  <ListBox.ItemTemplate>
    <DataTemplate>
      <Button Click="Button_Click"/>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

Private Sub Button_Click(sender As Button, e As RoutedEventArgs)
  Dim lbAddress = GetAncestor(Of ListBox) 'Result: Nothing
End Sub

Public Shared Function GetAncestor(Of T)(reference As DependencyObject) As T
  Dim parent = GetParent(reference)

  While parent IsNot Nothing AndAlso Not parent.GetType.Equals(GetType(T))
    parent = GetAncestor(Of T)(parent)
  End While

  If parent IsNot Nothing Then _
    Return If(parent.GetType Is GetType(T), parent, Nothing) 

  Return Nothing    
End Sub

Public Function GetParent(reference As DependencyObject) As DependencyObject
  Dim parent As DependencyObject = Nothing

 If TypeOf reference Is FrameworkElement Then
    parent = DirectCast(reference, FrameworkElement).Parent
  ElseIf TypeOf reference Is FrameworkContentElement Then
    parent = DirectCast(reference, FrameworkContentElement).Parent
  End If

  Return If(parent, VisualTreeHelper.GetParent(reference))
End Function

更新

这就是发生的情况(请注意,“父”变量保持为空):

这就是它的样子

How do I extract the parent container of a ListBoxItem?
In the following example I can go till the ListBoxItem, higher than that I get Nothing:

<ListBox Name="lbAddress">
  <ListBox.ItemTemplate>
    <DataTemplate>
      <Button Click="Button_Click"/>
    </DataTemplate>
  </ListBox.ItemTemplate>
</ListBox>

Private Sub Button_Click(sender As Button, e As RoutedEventArgs)
  Dim lbAddress = GetAncestor(Of ListBox) 'Result: Nothing
End Sub

Public Shared Function GetAncestor(Of T)(reference As DependencyObject) As T
  Dim parent = GetParent(reference)

  While parent IsNot Nothing AndAlso Not parent.GetType.Equals(GetType(T))
    parent = GetAncestor(Of T)(parent)
  End While

  If parent IsNot Nothing Then _
    Return If(parent.GetType Is GetType(T), parent, Nothing) 

  Return Nothing    
End Sub

Public Function GetParent(reference As DependencyObject) As DependencyObject
  Dim parent As DependencyObject = Nothing

 If TypeOf reference Is FrameworkElement Then
    parent = DirectCast(reference, FrameworkElement).Parent
  ElseIf TypeOf reference Is FrameworkContentElement Then
    parent = DirectCast(reference, FrameworkContentElement).Parent
  End If

  Return If(parent, VisualTreeHelper.GetParent(reference))
End Function

Update

This is what happens (note that the 'parent' variable remains null):

This is how it looks like

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(4

猫烠⑼条掵仅有一顆心 2024-09-04 23:05:24

很明显,在单击按钮期间,某些东西正在从 lbAddress.ItemsSource 中删除项目。问题是,什么?仔细看看您发布的图片就会找到答案。这是代码中的错误:

My.Context.DeleteObject(context)
My.Context.SaveChanges()

   ...

btn.GetVisualAncestor(...)

前两行更新您的数据模型。这会立即从可视化树中删除 ListBoxItem。当您稍后调用 GetVisualAncestor 查找 ListBox 时,它会失败,因为 ListBoxItem 不再具有任何类型的父项。

我相信解决方案现在对您来说是显而易见的:只需从数据模型中删除数据之前找到 ListBox 祖先,然后就可以开始了。

It is totally obvious something is removing an item from lbAddress.ItemsSource during the button click. The question is, what? A closer look at the image you posted reveals the answer. Here's the bug in your code:

My.Context.DeleteObject(context)
My.Context.SaveChanges()

   ...

btn.GetVisualAncestor(...)

The first two lines update your data model. This immediately removes the ListBoxItem from the visual tree. When when you call GetVisualAncestor later to find the ListBox it fails because the ListBoxItem no longer has a parent of any kind.

I'm sure the solution is now obvious to you: Simply find the ListBox ancestor before you delete the data from the data model and you'll be good to go.

寂寞笑我太脆弱 2024-09-04 23:05:24

罪魁祸首似乎是您的 GetParent 函数,它使用 Parent 属性而不是 VisualTreeHelper.GetParent。 Parent 属性返回逻辑父级,而不是可视父级,因此在尝试遍历 DataTemplate 时将返回 null。 (也不清楚 GetVisualAncestor 是如何实现的——或者这是 GetAncestor 的拼写错误?)一致地使用 VisualTreeHelper.GetParent,并忘记 Parent 属性。例如,以下代码适用于我:

XAML:

<DataTemplate x:Key="SimpleItemTemplate">
  <Button Click="Button_Click">In DataTemplate</Button>
</DataTemplate>

代码隐藏:

private void Button_Click(object sender, RoutedEventArgs e)
{
  Button btn = (Button)sender;
  ListBox lb = FindAncestor<ListBox>(btn);
  Debug.WriteLine(lb);
}

public static T FindAncestor<T>(DependencyObject from)
  where T : class
{
  if (from == null)
  {
    return null;
  }

  T candidate = from as T;
  if (candidate != null)
  {
    return candidate;
  }

  return FindAncestor<T>(VisualTreeHelper.GetParent(from));
}

当我运行此代码时,Click 处理程序中的 ListBox 变量 (lb) 将设置为正确的非空值。

The culprit appears to be your GetParent function, which uses the Parent property instead of VisualTreeHelper.GetParent. The Parent property returns the logical parent, not the visual parent, and will therefore return null when trying to traverse out of a DataTemplate. (It's also not clear how GetVisualAncestor is implemented -- or is this a typo for GetAncestor?) Use VisualTreeHelper.GetParent consistently, and forget about the Parent property. For example, the following code works for me:

XAML:

<DataTemplate x:Key="SimpleItemTemplate">
  <Button Click="Button_Click">In DataTemplate</Button>
</DataTemplate>

Code behind:

private void Button_Click(object sender, RoutedEventArgs e)
{
  Button btn = (Button)sender;
  ListBox lb = FindAncestor<ListBox>(btn);
  Debug.WriteLine(lb);
}

public static T FindAncestor<T>(DependencyObject from)
  where T : class
{
  if (from == null)
  {
    return null;
  }

  T candidate = from as T;
  if (candidate != null)
  {
    return candidate;
  }

  return FindAncestor<T>(VisualTreeHelper.GetParent(from));
}

When I run this, the ListBox variable (lb) in the Click handler is set to the correct non-null value.

独夜无伴 2024-09-04 23:05:24

Parent 属性返回逻辑父级。您应该使用视觉父级,因为在某些情况下逻辑父级将为空。例如,在您的情况下,按钮的 Parent 属性返回 null。

来自 MSDN

在以下情况下,父级可能为空:
元素已实例化,但尚未实例化
附加到任何逻辑树
最终连接到页面级别
根元素,或应用程序
对象。

由于 FrameworkElement.VisualParent 属性受到保护,因此您可以使用 VisualTreeHelper.GetParent 方法:

<System.Runtime.CompilerServices.Extension> _
Public Shared Function FindAncestor(Of T As DependencyObject)(ByVal obj As DependencyObject) As T
    Return TryCast(obj.FindAncestor(GetType(T)), T)
End Function

<System.Runtime.CompilerServices.Extension> _
Public Shared Function FindAncestor(ByVal obj As DependencyObject, ByVal ancestorType As Type) As DependencyObject
    Dim tmp = VisualTreeHelper.GetParent(obj)
    While tmp IsNot Nothing AndAlso Not ancestorType.IsAssignableFrom(tmp.[GetType]())
        tmp = VisualTreeHelper.GetParent(tmp)
    End While
    Return tmp
End Function

The Parent property returns the logical parent. You should use the visual parent, because in some cases the logical parent will be null. For instance, in your case the Parent property of the button returns null.

From MSDN :

Parent may be null in cases where an
element was instantiated, but is not
attached to any logical tree that
eventually connects to the page level
root element, or the application
object.

Since the FrameworkElement.VisualParent property is protected, you can use the VisualTreeHelper.GetParent method :

<System.Runtime.CompilerServices.Extension> _
Public Shared Function FindAncestor(Of T As DependencyObject)(ByVal obj As DependencyObject) As T
    Return TryCast(obj.FindAncestor(GetType(T)), T)
End Function

<System.Runtime.CompilerServices.Extension> _
Public Shared Function FindAncestor(ByVal obj As DependencyObject, ByVal ancestorType As Type) As DependencyObject
    Dim tmp = VisualTreeHelper.GetParent(obj)
    While tmp IsNot Nothing AndAlso Not ancestorType.IsAssignableFrom(tmp.[GetType]())
        tmp = VisualTreeHelper.GetParent(tmp)
    End While
    Return tmp
End Function
橘寄 2024-09-04 23:05:24

对于公认的纯 XAML 解决方案,您可以使用样式设置器将父 ListBox 填充到 ListBoxItem 的 Tag 属性中(如果您没有将其用于任何其他目的):

<ListBox Name="w_listbox" ItemsSource="{Binding MyItems}">
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem">
            <Setter Property="Tag" Value="{Binding ElementName=w_listbox}" />
        </Style>
    </ListBox.ItemContainerStyle>
    <!-- etc, i.e.... !-->
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" Text="{Binding MyFoo}"></TextBlock>
            <TextBlock Grid.Column="1" Text="{Binding MyBar}"></TextBlock>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

As for an admittedly-hacky XAML-only solution, you can use a Style setter to stuff the parent ListBox into the ListBoxItem's Tag property (if you're not using it for any other purpose):

<ListBox Name="w_listbox" ItemsSource="{Binding MyItems}">
    <ListBox.ItemContainerStyle>
        <Style TargetType="ListBoxItem">
            <Setter Property="Tag" Value="{Binding ElementName=w_listbox}" />
        </Style>
    </ListBox.ItemContainerStyle>
    <!-- etc, i.e.... !-->
    <ListBox.ItemTemplate>
        <DataTemplate>
            <Grid.ColumnDefinitions>
                <ColumnDefinition />
                <ColumnDefinition />
            </Grid.ColumnDefinitions>
            <TextBlock Grid.Column="0" Text="{Binding MyFoo}"></TextBlock>
            <TextBlock Grid.Column="1" Text="{Binding MyBar}"></TextBlock>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文