从C#访问ListBox的ScrollViewer

18

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

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

当我点击“检查ScrollViewer”按钮时,它会打印出“null”。也就是说,无法检索到ScrollViewer
我该如何访问那个可恶的ScrollViewer呢? :-)

请查看以下链接:https://dev59.com/FVHTa4cB1Zd3GeqPPjvB - Klaus78
...你真的不应该将你的ScrollViewer属性命名为"ScrollViewer"。 - basti
2
@chiffre:为什么不呢?这实际上是.NET属性命名指南中的一条建议:考虑将属性命名为其类型的名称。(http://msdn.microsoft.com/en-us/library/ms229012.aspx) - Avner Shahar-Kashtan
@AvnerShahar-Kashtan:你引用的链接只谈到了枚举。仅仅是枚举。你可以争论这是否是一个好的实践。但我认为,使用完全相同的名称作为类型和属性会让我发疯... - basti
5
我知道它只明确提到了枚举类型,但我觉得这个想法适用于其他类型。对于ListBox的ScrollViewer,我想不出比"ScrollViewer"更好的名称了。你可以将其转变为"ScrollContainer"、"ScrollProvider"或者"ScrollPane",但这并不会让它更清晰易懂。 - Avner Shahar-Kashtan
6个回答

28

你可以尝试这个小帮助函数

使用方法

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

希望这可以帮到你


哇,这是一个很好的解决方案@punker76。它不需要我子类化ListBox就可以访问ScrollViewer。我很想将其标记为被接受的答案。欢迎辩论! :-) - dharmatech
非常好的主题不可知版本。我提供了一个稍微修改过的扩展方法,保证类型安全。 (我知道这个话题很老) - Samuel
public static TDescendant GetDescendant<TDescendant>(this Visual element) 看起来这里非常漂亮 ;D - Davi Fiamenghi

14

如果您使用标准的ListBox,那么可以将getter更改为以下内容:

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

            return (ScrollViewer)VisualTreeHelper.GetChild(border, 0);
        }
    }
}

2
你真的不应该把你的ScrollViewer属性命名为“ScrollViewer”。 - basti
我希望@dharmatech也能在这个评论中看到它。但是:“完成。” ;) - basti
@stukselbax:看起来你需要在 get 子句周围加上大括号? - dharmatech
5
按照类型来命名属性并没有什么问题。事实上,这是推荐的做法:http://msdn.microsoft.com/en-us/library/ms229012.aspx。 - Jeff

9
我修改了@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)。

1

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


我说YES!使用WPF的附加属性功能,非常干净。谢谢。 - Elo

1

对我而言,将ScrollViewer公开为属性是一个不好的想法。首先,在模板中并没有保证存在ScrollViewer。其次,ScrollViewer与ItemsPanel和ItemContainerGenerator同步工作。覆盖这些内容会导致不常见的行为。

WPF控件使用另一种模式。它们的类就像外部逻辑使用和内部视觉表示之间的中介者。您的ListBox应该公开可以在模板中由ScrollViewer使用的属性,而不是ScrollViewer本身。通过这样做,您会打破WPF标准,限制您的控件到特定的模板,并允许用户代码来篡改内部ListBox实现。


0

这是另一个重新编写的、通用的 C# 6 版本,基于 @punker76 的答案:

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

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接