Why
- 最近在回顾一些有关于附加属性和依赖属性的相关知识,因为很久没写过这一部分的代码了,所以有一些知识点忘记了,正好做一个简单的附加属性实现
ListView的自滚动记录一下。
How
- 主要的思路是:首先创建一个附加属性(AutoScrollToEnd),然后监听
ListView绑定的内容改变的时候进行滚动到最后一行,比较简单。
- 以下是
Helper内容,至于为啥取名叫TextBoxHelper,这个不重要~
public class TextBoxHelper
{
public static string GetTitle(DependencyObject obj)
{
return (string)obj.GetValue(TitleProperty);
}
public static void SetTitle(DependencyObject obj, string value)
{
obj.SetValue(TitleProperty, value);
}
public static readonly DependencyProperty TitleProperty =
DependencyProperty.RegisterAttached("Title", typeof(string), typeof(TextBoxHelper), new PropertyMetadata(""));
public static bool GetAutoScrollToEnd(DependencyObject obj)
{
return (bool)obj.GetValue(AutoScrollToEndProperty);
}
public static void SetAutoScrollToEnd(DependencyObject obj, bool value)
{
obj.SetValue(AutoScrollToEndProperty, value);
}
public static readonly DependencyProperty AutoScrollToEndProperty =
DependencyProperty.RegisterAttached("AutoScrollToEnd", typeof(bool), typeof(TextBoxHelper), new PropertyMetadata(false, OnAutoScrollToEndChanged));
private static void OnAutoScrollToEndChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (!(d is ListView listView))
{
return;
}
if ((bool)e.NewValue)
{
listView.Loaded += ListView_Loaded;
}
else
{
listView.Loaded -= ListView_Loaded;
}
}
private static void ListView_Loaded(object sender, RoutedEventArgs e)
{
var listView = sender as ListView;
var scrollViewer = FindScrollViewer(listView);
if (scrollViewer == null)
return;
if (listView.Items.SourceCollection is INotifyCollectionChanged collectionChanged)
{
collectionChanged.CollectionChanged += (_, __) =>
{
scrollViewer.ScrollToEnd();
};
}
}
private static void OnScrollCountChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
{
if (d is TextBox box)
{
var title = GetTitle(box);
SetTitle(box, e.NewValue.ToString());
}
else if (d is ListView listView)
{
var scrollViewer = FindScrollViewer(listView);
if (scrollViewer == null)
return;
scrollViewer.ScrollToEnd();
}
}
private static ScrollViewer FindScrollViewer(DependencyObject obj)
{
if (obj is ScrollViewer)
return obj as ScrollViewer;
for (int i = 0; i < VisualTreeHelper.GetChildrenCount(obj); i++)
{
var child = VisualTreeHelper.GetChild(obj, i);
var result = FindScrollViewer(child);
if (result != null)
return result;
}
return null;
}
}
- 可以看到里面如何找到滚动条的方法,网上很多资料都比较复杂。
- 里面的
Title是后面演示附加属性如何绑定用到的。
<StackPanel VerticalAlignment="Center">
<Button Content="Side View" />
<TextBox comm:TextBoxHelper.Title="Hello World!" Text="{Binding RelativeSource={RelativeSource Self}, Path=(comm:TextBoxHelper.Title)}" />
<Button Command="{Binding ChangeCountCommand}" Content="Change Count" />
<ListView
Height="100"
comm:TextBoxHelper.AutoScrollToEnd="True"
ItemsSource="{Binding Names}" />
</StackPanel>
- 注意附加属性绑定的时候一定需要加上
(),固定格式。
- 中间按钮是模仿添加一个名字。
public SideViewModel()
{
Enumerable.Range(0, 100).ToList().ForEach(i => Names.Add(i.ToString()));
}
private ObservableCollection<string> names = new ObservableCollection<string>();
public ObservableCollection<string> Names
{
get { return names; }
set { SetProperty(ref names, value); }
}
public RelayCommand ChangeCountCommand => new RelayCommand(ChangeCount);
private void ChangeCount(object obj)
{
Random random = new Random();
Names.Add(random.Next(100).ToString());
}
Tips
- 很简单,但是涉及到的知识点还算是比较多的,比如附加属性如何绑定,比如寻找滚动条还有
listView.Items.SourceCollection其实可以转换成类型INotifyCollectionChanged。
- 如果想要精确控制,例如某个方法不滚动到最后,那个可以改写
AutoScrollToEnd为int类型,然后只有这个值改变的时候才触发滚动事件即可。