从C#访问ListBox的ScrollViewer

我想从C#更改ListBoxScrollViewer的属性。

我在Stackoverflow上找到了这个问题 。 我接受了接受的答案的建议,并将ScrollViewer作为子类的属性公开。 但是,这似乎不适用于下面显示的示例。 该问题中的一些评论也表明这种技术不起作用。

XAML:

   

C#:

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

当我单击“检查ScrollViewer”按钮时,它会打印“null”。 即,未检索到ScrollViewer

我如何找到那个令人信服的ScrollViewer ? 🙂

如果你将使用标准的ListBox,那么你可以改变你的getter到这个:

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

你可以试试这个小帮手function

用法

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

辅助function

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

希望这可以帮助

我修改了@ punker76的优秀答案,为Visual创建了一个扩展方法,并提供了显式的返回类型:

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

您现在可以通过SomeVisual.GetDescendantByType调用它,它返回已经是正确类型的ScrollViewer或null(默认为(T))

至于我,将ScrollViewer作为属性公开是个坏主意。 首先,无法保证ScrollViewer存在于模板中。 其次,ScrollViewer与ItemsPanel和ItemContainerGenerator同步工作。 覆盖这是不常见行为的直接方式。

WPF控件使用另一种模式。 他们的类就像外部逻辑用法和内部视觉表现之间的调解者。 ListBox应该公开ScrollViewer可以在模板中使用的属性,但不能暴露ScrollViewer。 通过这样做,您可以破坏WPF标准,将控制权限制为特定模板,并允许用户代码破解内部ListBox实现。

这是@ punker76对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; } }