使用ListBox 的另一种解决方案。要实现自动滚动,可以创建自定义控件!
C#
public class LoggingListBox : ListBox
{
///<summary>
///Define the AutoScroll property. If enabled, causes the ListBox to scroll to
///the last item whenever a new item is added.
///</summary>
public static readonly DependencyProperty AutoScrollProperty =
DependencyProperty.Register(
"AutoScroll",
typeof(Boolean),
typeof(LoggingListBox),
new FrameworkPropertyMetadata(
true, //Default value.
FrameworkPropertyMetadataOptions.AffectsArrange |
FrameworkPropertyMetadataOptions.BindsTwoWayByDefault,
AutoScroll_PropertyChanged));
/// <summary>
/// Gets or sets whether or not the list should scroll to the last item
/// when a new item is added.
/// </summary>
[Category("Common")] //Indicate where the property is located in VS designer.
public bool AutoScroll
{
get { return (bool)GetValue(AutoScrollProperty); }
set { SetValue(AutoScrollProperty, value); }
}
/// <summary>
/// Event handler for when the AutoScroll property is changed.
/// This delegates the call to SubscribeToAutoScroll_ItemsCollectionChanged().
/// </summary>
/// <param name="d">The DependencyObject whose property was changed.</param>
/// <param name="e">Change event args.</param>
private static void AutoScroll_PropertyChanged(
DependencyObject d, DependencyPropertyChangedEventArgs e)
{
SubscribeToAutoScroll_ItemsCollectionChanged(
(LoggingListBox)d,
(bool)e.NewValue);
}
/// <summary>
/// Subscribes to the list items' collection changed event if AutoScroll is enabled.
/// Otherwise, it unsubscribes from that event.
/// For this to work, the underlying list must implement INotifyCollectionChanged.
///
/// (This function was only creative for brevity)
/// </summary>
/// <param name="listBox">The list box containing the items collection.</param>
/// <param name="subscribe">Subscribe to the collection changed event?</param>
private static void SubscribeToAutoScroll_ItemsCollectionChanged(
LoggingListBox listBox, bool subscribe)
{
INotifyCollectionChanged notifyCollection =
listBox.Items.SourceCollection as INotifyCollectionChanged;
if (notifyCollection != null)
{
if (subscribe)
{
//AutoScroll is turned on, subscribe to collection changed events.
notifyCollection.CollectionChanged +=
listBox.AutoScroll_ItemsCollectionChanged;
}
else
{
//AutoScroll is turned off, unsubscribe from collection changed events.
notifyCollection.CollectionChanged -=
listBox.AutoScroll_ItemsCollectionChanged;
}
}
}
/// <summary>
/// Event handler called only when the ItemCollection changes
/// and if AutoScroll is enabled.
/// </summary>
/// <param name="sender">The ItemCollection.</param>
/// <param name="e">Change event args.</param>
private void AutoScroll_ItemsCollectionChanged(
object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
int count = Items.Count;
ScrollIntoView(Items[count - 1]);
}
}
/// <summary>
/// Constructor a new LoggingListBox.
/// </summary>
public LoggingListBox()
{
//Subscribe to the AutoScroll property's items collection
//changed handler by default if AutoScroll is enabled by default.
SubscribeToAutoScroll_ItemsCollectionChanged(
this, (bool)AutoScrollProperty.DefaultMetadata.DefaultValue);
}
}
XAML
这是在 XAML 中使用控件的方法:
<tools:LoggingListBox/> <!-- AutoScroll="true" by default. -->
您需要指定访问此控件的方式。这完全取决于您的项目设置。
xmlns:tools="clr-namespace:MyCustomControls;assembly=MyCustomControls"
工作原理
要创建自定义控件,您应该只需要 C# 代码。我们通过扩展ListBox 来做到这一点,并且只添加一个属性AutoScroll。因为它是一个依赖属性,它将参与 WPF 绑定系统,这也使它在 Visual Studio 设计器中可用。
覆盖依赖属性是一个相当大的话题,但对于创建自定义控件是不可或缺的。您可以通过Control Authoring Overview 或Dependency Properties Overview 了解更多信息。
目标是订阅底层项目集合的集合更改事件,以便我们可以在添加新项目时通过滚动到底部来响应。我们必须在两个地方订阅这个事件。
- 只要
AutoScroll 设置为true,我们就需要订阅。 AutoScroll 的值可能随时更改,我们应该能够做出相应的响应。如果设置为 false,我们应该通过取消订阅来指示控件停止滚动到底部。
- 假设
AutoScroll只需要在编译时设置,我们需要一个启动时订阅的方法。这是通过使用控件的构造函数完成的。
为什么要创建自定义控件
首先,我们尽可能合理地简化了 XAML。我们只需要访问控件并可选择指定或绑定到AutoScroll 属性。
它是 MVVM 兼容的。我们的视图模型不需要担心AutoScroll 功能,因为它在控件中是自包含的。同时,视图模型可以提供AutoScroll属性绑定的属性,从而实现视图和视图模型的解耦。
此外,我们避免使用行为。这意味着我们已经从我们的项目中删除了两个依赖项(当然这是首先包含这些依赖项的唯一原因)。我们可以安全地从项目引用中省略 System.Windows.Interactivity 和 Microsoft.Expressions.Interactions。
缺点
这种方法只有一个缺点。底层项目集合必须实现INotifyCollectionChanged。在大多数情况下,这不是问题。如果您使用的是 MVVM,您可能已经将您的项目包装在 ObservableCollection 中,它已经实现了我们所需的接口。
享受吧! :-)