ListBoxItem.Parent returns nothing, unable to get it thru VisualTreeHelper.GetParent either
Asked Answered
K

4

5

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

Karolynkaron answered 13/4, 2010 at 20:53 Comment(3)
Note that I want the direction to be this way, not searching for the name "lbAddress" from top item.Karolynkaron
Is GetVisualAncestor/GetAncestor an Extension method?Mathers
Yes, but it's just like the snippet above.Karolynkaron
A
4

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.

Atavistic answered 15/4, 2010 at 0:52 Comment(0)
A
3

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.

Astragalus answered 13/4, 2010 at 21:37 Comment(9)
You could avoid the recursion using a loop ;)Tiphane
The example I brought above, is nested in other parent ItemControls. However in my code the ListBoxItem doesn't return its parent. and it should. What other reasons can be? I use Mole (codeproject.com/KB/macros/MoleForVisualStudioEdit.aspx) and above the ListBoxItem I indeed don't see nuttn.Karolynkaron
Anyway, the GetParent function should work fine, watch out the If operator at the end of it, if you don't use VB, than this keywork is used as the csharp's ?? operator.Karolynkaron
Shimmy, what do you get if you run my code? As I say, it definitely works in my test environment.Astragalus
I didn't try your code, but my code does just the same, it returns VisualTreeHelper.GetParent, and this function returns null for the ListBoxItem in my environment.Karolynkaron
Okay, I'm stumped then -- it definitely works for me! Would it help to post your full code? For example, the call in Button_Click to GetAncestor won't compile as written, because you're not passing an argument (I assume you pass sender, like I do); and GetVisualAncestor isn't defined. What if you log each step of the visual parent chain? What does that output? Can you verify (by stepping in the debugger) that the Parent property is never called in practice? What if you replace the .Parent references with VTH.GetParent() calls?Astragalus
1) I just made a mistake when typing here @ SO, I updated GetVisualAncestor, it's supposed to be GetAncestor. 2) I debugged the whole trip, I get the ListBoxItem, but then it fails, VisualTreeHelper.GetParent(lbi) returns null. this is why it sucks. note that the parent listbox is nested in various other ContentControls, ItemControls and DataTemplate.Karolynkaron
Bizarre. I guess it must be something to do with all that additional nesting, though I can't imagine why that would prevent VisualTreeHelper from finding the visual parent of the ListBoxItem. I can only suggest that you try to prune it back to a simple repro and update your question with the full XAML, so that people can try to reproduce the problem you're seeing. Sorry I can't be more helpful. Oh, might also be worth saying which version of the framework you're on -- I only tested on 3.5.Astragalus
I know it's bizarre... Yes I use 3.5. The code is to heavy, hope to find a workaround. If you got any ideas, please let me know.Karolynkaron
T
1

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
Tiphane answered 13/4, 2010 at 21:38 Comment(1)
If you watch my code you can see that VisualTreeHelper is used to get it, it returns nothing, maybe the problem is something else.Karolynkaron
N
1

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>
Nies answered 10/1, 2012 at 8:36 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.