dependency wpf xaml listbox listboxitem visual-tree

wpf - dependency - ListBoxItem.Parent no devuelve nada, no se puede obtener a través de VisualTreeHelper.GetParent tampoco



dependency properties in wpf (4)

El culpable parece ser su función GetParent, que usa la propiedad Parent en lugar de VisualTreeHelper.GetParent. La propiedad Parent devuelve el elemento primario lógico , no el primario visual, y por lo tanto devolverá nulo cuando intente salir de DataTemplate. (Tampoco está claro cómo se implementa GetVisualAncestor, ¿o es un error tipográfico para GetAncestor?) Use VisualTreeHelper.GetParent de forma coherente y olvídese de la propiedad Parent. Por ejemplo, el siguiente código funciona para mí:

XAML:

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

Código detrás:

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)); }

Cuando ejecuto esto, la variable ListBox ( lb ) en el controlador Click se establece en el valor correcto no nulo.

¿Cómo extraigo el contenedor padre de un ListBoxItem? En el siguiente ejemplo puedo ir hasta el ListBoxItem, más alto que el que obtengo 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

Actualizar

Esto es lo que ocurre (tenga en cuenta que la variable ''principal'' sigue siendo nula):


La propiedad Parent devuelve el elemento primario lógico . Debería usar el elemento primario visual , porque en algunos casos el elemento primario lógico será nulo. Por ejemplo, en su caso, la propiedad principal del botón devuelve nulo.

Desde MSDN :

El elemento primario puede ser nulo en los casos en que se creó una instancia de un elemento, pero no se adjunta a ningún árbol lógico que eventualmente se conecte al elemento raíz de nivel de página, o al objeto de la aplicación.

Como la propiedad FrameworkElement.VisualParent está protegida, puede usar el método 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


Es totalmente obvio que algo está eliminando un elemento de lbAddress.ItemsSource durante el clic del botón. La pregunta es, ¿qué ? Una mirada más de cerca a la imagen que publica revela la respuesta. Aquí está el error en tu código:

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

Las primeras dos líneas actualizan su modelo de datos. Esto elimina inmediatamente el ListBoxItem del árbol visual. Cuando al llamar a GetVisualAncestor más tarde para encontrar el ListBox falla porque el ListBoxItem ya no tiene un elemento primario de ningún tipo.

Estoy seguro de que la solución ahora es obvia para usted: simplemente busque el ancestro ListBox antes de eliminar los datos del modelo de datos y estará listo para continuar.


En cuanto a una solución que solo admite hacks XAML, puede usar un setter de Estilo para rellenar el ListBox padre en la propiedad Tag ListBoxItem''s (si no lo está usando para ningún otro propósito):

<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>