WPF TreeView-Cómo desplazarse para que la rama expandida sea visible
scroll (5)
En el TreeView, maneje el evento TreeViewItem.Expanded (puede hacer esto en el nivel de TreeView debido al burbujeo del evento). En el controlador Expandido, llame a BringIntoView en el TreeViewItem que provocó el evento.
Es posible que necesite un poco de prueba y error para obtener el TreeViewItem en su código de controlador de eventos. Creo que (no he comprobado) que el argumento del remitente para su controlador de eventos Expandido será el TreeView (ya que ahí es donde se adjunta el controlador de eventos) en lugar del TreeViewItem. Y e.Source o e.OriginalSource pueden ser un elemento en la plantilla de datos de TreeViewItem. Por lo tanto, es posible que deba usar VisualTreeHelper para subir al árbol visual y encontrar el TreeViewItem. Pero si usa el depurador para inspeccionar el remitente y RoutedEventArgs, esto debería ser trivial de averiguar.
(Si puede hacer que esto funcione y desea agruparlo para no tener que adjuntar el mismo controlador de eventos a cada TreeView, debería ser fácil de encapsularlo como un comportamiento adjunto que le permitirá aplicarlo declarativamente, incluso a través de un estilo.)
Cuando amplío elementos en mi vista de árbol para que sea necesario el desplazamiento, aparece una barra de desplazamiento. Sin embargo, no se desplaza hacia abajo para la rama recién expandida de elementos: se recortan en la parte inferior del control. Así que a medida que continúo expandiendo elementos en la parte inferior del árbol, tengo que seguir desplazándome manualmente hacia abajo para ver a los nuevos niños. ¿Alguien tiene alguna sugerencia sobre cómo hacer que se desplace automáticamente para mostrar los elementos recién expandidos?
Gracias a la respuesta de itowlson, aquí está el código de controlador de eventos ampliado que funciona para mis dos árboles
private static void Tree_Expanded(object sender, RoutedEventArgs e)
{
// ignore checking, assume original source is treeviewitem
var treeViewItem = (TreeViewItem)e.OriginalSource;
var count = VisualTreeHelper.GetChildrenCount(treeViewItem);
for (int i = count - 1; i >= 0; --i)
{
var childItem = VisualTreeHelper.GetChild(treeViewItem, i);
((FrameworkElement)childItem).BringIntoView();
}
// do NOT call BringIntoView on the actual treeviewitem - this negates everything
//treeViewItem.BringIntoView();
}
Modifiqué la respuesta de Jared en combinación con la estrategia desde aquí: https://.com/a/42238409/2477582
La principal ventaja es que no hay n
llamadas de BringIntoView () para n
childs. Solo hay una llamada de BringIntoView para un área que cubre todas las alturas del niño.
Adicionalmente, el propósito del tema referido se realiza también. Pero esta parte puede ser removida, si no es deseada.
/// <summary>Prevents automatic horizontal scrolling, while preserving automatic vertical scrolling and other side effects</summary>
/// <remarks>Source: https://.com/a/42238409/2477582 </remarks>
private void TreeViewItem_RequestBringIntoView(object sender, RequestBringIntoViewEventArgs e)
{
// Ignore re-entrant calls
if (m_SuppressRequestBringIntoView)
return;
// Cancel the current scroll attempt
e.Handled = true;
// Call BringIntoView using a rectangle that extends into "negative space" to the left of our
// actual control. This allows the vertical scrolling behaviour to operate without adversely
// affecting the current horizontal scroll position.
m_SuppressRequestBringIntoView = true;
try
{
TreeViewItem tvi = sender as TreeViewItem;
if (tvi != null)
{
// take care of children
int ll_ChildCount = VisualTreeHelper.GetChildrenCount(tvi);
double ll_Height = tvi.ActualHeight;
if (ll_ChildCount > 0)
{
FrameworkElement ll_LastChild = VisualTreeHelper.GetChild(tvi, ll_ChildCount - 1) as FrameworkElement;
ll_Height += ll_ChildCount * ll_LastChild.ActualHeight;
}
Rect newTargetRect = new Rect(-1000, 0, tvi.ActualWidth + 1000, ll_Height);
tvi.BringIntoView(newTargetRect);
}
}
catch (Exception ex)
{
m_Log.Debug("Error in TreeViewItem_RequestBringIntoView: " + ex.ToString());
}
m_SuppressRequestBringIntoView = false;
}
La solución anterior funciona junto con esto:
/// <summary>Correctly handle programmatically selected items (needed due to the custom implementation of TreeViewItem_RequestBringIntoView)</summary>
/// <remarks>Source: https://.com/a/42238409/2477582 </remarks>
private void TreeViewItem_Selected(object sender, RoutedEventArgs e)
{
((TreeViewItem)sender).BringIntoView();
e.Handled = true;
}
Esta parte se ocupa de alternar los elementos en cada clic:
/// <summary>Support for single click toggle</summary>
private void TreeViewItem_MouseUp(object sender, MouseButtonEventArgs e)
{
TreeViewItem tvi = null;
// Source may be TreeViewItem directly, or be a ContentPresenter
if (e.Source is TreeViewItem)
{
tvi = e.Source as TreeViewItem;
}
else if (e.Source is ContentPresenter)
{
tvi = (e.Source as ContentPresenter).TemplatedParent as TreeViewItem;
}
if (tvi == null || e.Handled) return;
tvi.IsExpanded = !tvi.IsExpanded;
e.Handled = true;
}
Finalmente la parte de XAML:
<TreeView>
<TreeView.ItemContainerStyle>
<Style TargetType="TreeViewItem">
<EventSetter Event="RequestBringIntoView" Handler="TreeViewItem_RequestBringIntoView" />
<EventSetter Event="Selected" Handler="TreeViewItem_Selected" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
Puede utilizar un simple EventSetter en el estilo TreeViewItem para invocar un controlador de eventos cuando se selecciona el elemento. Luego llame a BringIntoView para el artículo.
<TreeView >
<TreeView.ItemContainerStyle>
<Style TargetType="{x:Type TreeViewItem}">
<EventSetter Event="Selected" Handler="TreeViewSelectedItemChanged" />
</Style>
</TreeView.ItemContainerStyle>
</TreeView>
private void TreeViewSelectedItemChanged(object sender, RoutedEventArgs e)
{
TreeViewItem item = sender as TreeViewItem;
if (item != null)
{
item.BringIntoView();
e.Handled = true;
}
}
Use una propiedad de dependencia en un disparador IsSelected:
<Style TargetType="{x:Type TreeViewItem}">
<Style.Triggers>
<Trigger Property="IsSelected" Value="True">
<Setter Property="commands:TreeViewItemBehavior.BringIntoViewWhenSelected" Value="True" />
</Trigger>
</Style.Triggers>
Aquí está el código para la propiedad de dependencia:
public static bool GetBringIntoViewWhenSelected(TreeViewItem treeViewItem)
{
return (bool)treeViewItem.GetValue(BringIntoViewWhenSelectedProperty);
}
public static void SetBringIntoViewWhenSelected(TreeViewItem treeViewItem, bool value)
{
treeViewItem.SetValue(BringIntoViewWhenSelectedProperty, value);
}
public static readonly DependencyProperty BringIntoViewWhenSelectedProperty =
DependencyProperty.RegisterAttached("BringIntoViewWhenSelected", typeof(bool),
typeof(TreeViewItemBehavior), new UIPropertyMetadata(false, OnBringIntoViewWhenSelectedChanged));
static void OnBringIntoViewWhenSelectedChanged(DependencyObject depObj, DependencyPropertyChangedEventArgs e)
{
TreeViewItem item = depObj as TreeViewItem;
if (item == null)
return;
if (e.NewValue is bool == false)
return;
if ((bool)e.NewValue)
item.BringIntoView();
}