问题
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