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