Accediendo al ScrollViewer de un ListBox desde C #

Me gustaría cambiar las propiedades de un ScrollViewer de un ListBox de C #.

Encontré esta pregunta aquí en Stackoverflow. Tomé el consejo de la respuesta aceptada y expuse el ScrollViewer como una propiedad de una subclase. Sin embargo, esto no parece estar funcionando en un ejemplo que se muestra a continuación. Algunos de los comentarios en esa pregunta también afirman que esta técnica no funcionó.

XAML:

   

DO#:

 using System; using System.Windows; using System.Windows.Controls; namespace StackoverflowListBoxScrollViewer { public class MyListBox : ListBox { public ScrollViewer ScrollViewer { get { return (ScrollViewer)GetTemplateChild("ScrollViewer"); } } } public partial class MainWindow : Window { public MainWindow() { InitializeComponent(); var myListBox = new MyListBox(); Content = myListBox; myListBox.Items.Add(new Button() { Content = "abc" }); myListBox.Items.Add(new Button() { Content = "abc" }); myListBox.Items.Add(new Button() { Content = "abc" }); myListBox.Items.Add(new Button() { Content = "abc" }); myListBox.Items.Add(new Button() { Content = "abc" }); var button = new Button() { Content = "Check ScrollViewer" }; button.Click += (s, e) => { if (myListBox.ScrollViewer == null) Console.WriteLine("null"); }; myListBox.Items.Add(button); } } } 

Cuando hago clic en el botón “Comprobar ScrollViewer”, se imprime “nulo”. Es decir, ScrollViewer no se recuperó.

¿Cómo llego a ese maldito ScrollViewer ? 🙂

Si usará ListBox estándar, entonces puede cambiar su getter a este:

 public class MyListBox : ListBox { public ScrollViewer ScrollViewer { get { Border border = (Border)VisualTreeHelper.GetChild(this, 0); return (ScrollViewer)VisualTreeHelper.GetChild(border, 0); } } } 

puedes probar esta pequeña función auxiliar

uso

 var scrollViewer = GetDescendantByType(yourListBox, typeof(ScrollViewer)) as ScrollViewer; 

función auxiliar

 public static Visual GetDescendantByType(Visual element, Type type) { if (element == null) { return null; } if (element.GetType() == type) { return element; } Visual foundElement = null; if (element is FrameworkElement) { (element as FrameworkElement).ApplyTemplate(); } for (int i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++) { Visual visual = VisualTreeHelper.GetChild(element, i) as Visual; foundElement = GetDescendantByType(visual, type); if (foundElement != null) { break; } } return foundElement; } 

Espero que esto ayude

Modifiqué la gran respuesta de @ punker76 para crear un método de extensión para Visual y proporcionar un tipo de devolución explícito:

  public static class Extensions { public static T GetDescendantByType(this Visual element) where T:class { if (element == null) { return default(T); } if (element.GetType() == typeof(T)) { return element as T; } T foundElement = null; if (element is FrameworkElement) { (element as FrameworkElement).ApplyTemplate(); } for (var i = 0; i < VisualTreeHelper.GetChildrenCount(element); i++) { var visual = VisualTreeHelper.GetChild(element, i) as Visual; foundElement = visual.GetDescendantByType(); if (foundElement != null) { break; } } return foundElement; } } 

Ahora puede llamarlo por SomeVisual.GetDescendantByType y devuelve ya sea un ScrollViewer tipeado correcto o nulo (que es el predeterminado (T))

En cuanto a mí, exponer ScrollViewer como una propiedad es una mala idea. En primer lugar, no hay garantía de que ScrollViewer exista en una plantilla. En segundo lugar, ScrollViewer funciona en sincronización con ItemsPanel y ItemContainerGenerator. Sobrescribir esta es la forma directa de comportamiento poco común.

Los controles WPF usan otro patrón. Sus clases son como mediadores entre el uso lógico externo y la representación visual interna. Su ListBox debe exponer las propiedades que ScrollViewer puede usar en una plantilla, pero no en ScrollViewer. Al hacerlo, se rompen los estándares de WPF, se restringe el control a una plantilla específica y se permite que el código de usuario piratee la implementación interna de ListBox.

Aquí hay otra versión revisada y genérica de la respuesta de @ punker76 para C # 6:

 public static class VisualExtensions { public static T FindVisualDescendant(this Visual element) where T : Visual { if (element == null) return null; var e = element as T; if (e != null) return e; (element as FrameworkElement)?.ApplyTemplate(); var childrenCount = VisualTreeHelper.GetChildrenCount(element); for (var i = 0; i < childrenCount; i++) { var visual = VisualTreeHelper.GetChild(element, i) as Visual; var foundElement = visual.FindVisualDescendant(); if (foundElement != null) return foundElement; } return null; } }