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, Fra开发者_StackOverflow社区meworkContentElement).Parent
End If
Return If(parent, VisualTreeHelper.GetParent(reference))
End Function
Update
This is what happens (note that the 'parent' variable remains null):
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.
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.
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
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>
精彩评论