从 C# 访问列表框的滚动查看器

本文关键字:滚动 访问 列表 | 更新日期: 2023-09-27 18:32:07

我想从 C# 更改ListBox ScrollViewer的属性。

我在Stackoverflow上发现了这个问题。我接受了接受答案的建议,并将ScrollViewer暴露为子类的属性。但是,这在下面显示的示例中似乎不起作用。该问题中的一些评论还指出,这种技术不起作用。

XAML:

<Window x:Class="StackoverflowListBoxScrollViewer.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
</Window>

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

当我单击"检查滚动查看器"按钮时,它打印"null"。 即,未检索ScrollViewer

我怎么去那个的ScrollViewer? :-)

从 C# 访问列表框的滚动查看器

你可以试试这个小辅助函数

用法

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

帮助程序函数

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

希望这有帮助

如果你将使用标准ListBox,所以你可以把你的getter更改为这个:

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

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

   public static class Extensions
   {
      public static T GetDescendantByType<T>(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<T>();
            if (foundElement != null)
            {
               break;
            }
         }
         return foundElement;
      }
   }

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

对我来说,将 ScrollViewer 公开为一个属性是一个坏主意。首先,不能保证模板中存在滚动查看器。其次,ScrollViewer与ItemsPanel和ItemContainerGenerator同步工作。覆盖这一点是导致不常见行为的直接方法。

WPF 控件使用另一种模式。它们的类就像外部逻辑用法和内部视觉表示之间的中介。列表框应公开可由模板中的滚动查看器使用的属性,但不能公开滚动查看器使用的属性。通过这样做,可以破坏 WPF 标准,将控件限制为特定模板,并允许用户代码破解内部 ListBox 实现。

ScrollViewer 的属性"附加"到 ListBox (参见 https://learn.microsoft.com/en-us/dotnet/framework/wpf/advanced/attached-properties-overview)。您可以通过以下函数获取或设置为依赖项属性:

public object GetValue (System.Windows.DependencyProperty dp);

public void SetValue (System.Windows.DependencyProperty dp, object value);

例如,对于列表框"lb",您可以编写:

lb.GetValue(ScrollViewer.ActualHeightProperty);lb.SetValue(ScrollViewer.HorizontalScrollBarVisibilityProperty, ScrollBarVisibility.Visible);

这是 @punker76 对 C# 6 的答案的另一个重新设计和通用版本:

public static class VisualExtensions
{
    public static T FindVisualDescendant<T>(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<T>();
            if (foundElement != null)
                return foundElement;
        }
        return null;
    }
}