Accessing the ScrollViewer of a ListBox from C#

此生再无相见时 提交于 2019-11-26 17:48:00

问题


I'd like to change the properties of a ScrollViewer of a ListBox from C#.

I found this question here on Stackoverflow. I took the accepted answer's advice and exposed the ScrollViewer as a property of a subclass. However, this doesn't appear to be working in an example shown below. Some of the comments in that question also state that this technique didn't work.

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

When I click the "Check ScrollViewer" button, it prints "null". I.e., the ScrollViewer wasn't retrieved.

How do I get to that darn ScrollViewer? :-)


回答1:


If you will use standard ListBox, so you can change yours getter to this one:

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

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



回答2:


you can try this little helper function

usage

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

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

Hope this helps




回答3:


I've modified the great answer of @punker76 to create an extension method for Visual and provide explicit return type:

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

   }

You can now call it by SomeVisual.GetDescendantByType and it returns either already a correct typed ScrollViewer or null (which is default(T))




回答4:


As for me, exposing ScrollViewer as a property is a bad idea. Firstly, there is no guarantee that ScrollViewer exists in a template. Secondly, ScrollViewer works in sync with ItemsPanel and ItemContainerGenerator. Overriding this is the straight way to uncommon behavior.

WPF controls use another pattern. Their classes are like mediators between outer logical usage and inner visual representation. Your ListBox should expose properties which can be used by ScrollViewer in a template, but not ScrollViewer. By doing this, you break WPF standards, restrict your control to specific template, and allows user code to hack internal ListBox implementation.




回答5:


Here's another reworked and generic version of @punker76's answer for 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;
    }
}



回答6:


The properties of the ScrollViewer are "attached" to the ListBox (cf. https://docs.microsoft.com/en-us/dotnet/framework/wpf/advanced/attached-properties-overview). You can get or set it as for a dependency property by the functions:

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

and

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

For example, for the listbox 'lb', you can write:

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



来源:https://stackoverflow.com/questions/10293236/accessing-the-scrollviewer-of-a-listbox-from-c-sharp

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!