Доступ к ScrollViewer из ListBox из С#
Я хотел бы изменить свойства ScrollViewer
a ListBox
на С#.
Я нашел этот вопрос здесь, в 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>
С#:
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". I.e., ScrollViewer
не был извлечен.
Как мне добраться до этого darn ScrollViewer
?: -)
Ответы
Ответ 1
Если вы будете использовать стандартный ListBox, чтобы вы могли изменить свой getter на этот:
public class MyListBox : ListBox
{
public ScrollViewer ScrollViewer
{
get
{
Border border = (Border)VisualTreeHelper.GetChild(this, 0);
return (ScrollViewer)VisualTreeHelper.GetChild(border, 0);
}
}
}
Ответ 2
вы можете попробовать эту небольшую вспомогательную функцию
Использование
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;
}
Надеюсь, что это поможет
Ответ 3
Я изменил отличный ответ @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))
Ответ 4
Что касается меня, то использование ScrollViewer в качестве свойства - плохая идея. Во-первых, нет гарантии, что ScrollViewer существует в шаблоне. Во-вторых, ScrollViewer работает синхронно с ItemsPanel и ItemContainerGenerator. Переопределение это прямой путь к необычному поведению.
Элементы управления WPF используют другой шаблон. Их классы подобны посредникам между внешним логическим использованием и внутренним визуальным представлением. Ваш ListBox должен предоставлять свойства, которые могут быть использованы ScrollViewer в шаблоне, но не ScrollViewer. Делая это, вы нарушаете стандарты WPF, ограничиваете свой контроль определенным шаблоном и позволяете коду пользователя взломать внутреннюю реализацию ListBox.
Ответ 5
Здесь другая переработанная и родовая версия @punker76 отвечает за С# 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
Свойства ScrollViewer "прикреплены" к ListBox (см. https://docs.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);