我有一个附加到ItemsControl的行为,每当添加新项时,该控件就会向下滚动到底部。由于我正在开发一个聊天类型的程序,如果用户的滚动条不是最底部,我不希望它滚动,否则会很烦人(有些聊天程序会这样做,这很糟糕)。
我该如何做到这一点?我不知道如何访问包装ScrollViewer,也不知道是否需要将其带入视图。
这是我从StackOverflow上的某个人那里得到的行为类。我自己还在学习行为。
public class ScrollOnNewItem : Behavior<ItemsControl>
{
protected override void OnAttached()
{
AssociatedObject.Loaded += OnLoaded;
AssociatedObject.Unloaded += OnUnLoaded;
}
protected override void OnDetaching()
{
AssociatedObject.Loaded -= OnLoaded;
AssociatedObject.Unloaded -= OnUnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
var incc = AssociatedObject.ItemsSource as INotifyCollectionChanged;
if (incc == null) return;
incc.CollectionChanged += OnCollectionChanged;
}
private void OnUnLoaded(object sender, RoutedEventArgs e)
{
var incc = AssociatedObject.ItemsSource as INotifyCollectionChanged;
if (incc == null) return;
incc.CollectionChanged -= OnCollectionChanged;
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
int count = AssociatedObject.Items.Count;
if (count == 0)
return;
var item = AssociatedObject.Items[count - 1];
var frameworkElement = AssociatedObject.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
if (frameworkElement == null) return;
frameworkElement.BringIntoView();
}
}
}
好吧,这是我自己想出的答案。
我发现有一个用于依赖对象的GetSelfAndAncestors方法。使用它,我可以获得我的AssociatedObject(ItemsControl)的ScrollViewer祖先(如果有的话),并用它来操作它。
所以我把这个字段添加到我的行为中
private ScrollViewer scrollViewer;
private bool isScrollDownEnabled;
在OnLoaded事件处理程序中,我为它分配了以下代码
scrollViewer = AssociatedObject.GetSelfAndAncestors().Where(a => a.GetType().Equals(typeof(ScrollViewer))).FirstOrDefault() as ScrollViewer;
在OnCollectionChanged事件处理程序中,我继续将所有逻辑封装在if语句中,如下所示
if (scrollViewer != null)
{
isScrollDownEnabled = scrollViewer.ScrollableHeight > 0 && scrollViewer.VerticalOffset + scrollViewer.ViewportHeight < scrollViewer.ExtentHeight;
if (e.Action == NotifyCollectionChangedAction.Add && !isScrollDownEnabled)
{
// Do stuff
}
}
总之,代码看起来像下面的
public class ScrollOnNewItem : Behavior<ItemsControl>
{
private ScrollViewer scrollViewer;
private bool isScrollDownEnabled;
protected override void OnAttached()
{
AssociatedObject.Loaded += OnLoaded;
AssociatedObject.Unloaded += OnUnLoaded;
}
protected override void OnDetaching()
{
AssociatedObject.Loaded -= OnLoaded;
AssociatedObject.Unloaded -= OnUnLoaded;
}
private void OnLoaded(object sender, RoutedEventArgs e)
{
var incc = AssociatedObject.ItemsSource as INotifyCollectionChanged;
if (incc == null) return;
incc.CollectionChanged += OnCollectionChanged;
scrollViewer = AssociatedObject.GetSelfAndAncestors().Where(a => a.GetType().Equals(typeof(ScrollViewer))).FirstOrDefault() as ScrollViewer;
}
private void OnUnLoaded(object sender, RoutedEventArgs e)
{
var incc = AssociatedObject.ItemsSource as INotifyCollectionChanged;
if (incc == null) return;
incc.CollectionChanged -= OnCollectionChanged;
}
private void OnCollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (scrollViewer != null)
{
isScrollDownEnabled = scrollViewer.ScrollableHeight > 0 && scrollViewer.VerticalOffset + scrollViewer.ViewportHeight < scrollViewer.ExtentHeight;
if (e.Action == NotifyCollectionChangedAction.Add && !isScrollDownEnabled)
{
int count = AssociatedObject.Items.Count;
if (count == 0)
return;
var item = AssociatedObject.Items[count - 1];
var frameworkElement = AssociatedObject.ItemContainerGenerator.ContainerFromItem(item) as FrameworkElement;
if (frameworkElement == null) return;
frameworkElement.BringIntoView();
}
}
}
}
正如评论中所要求的那样,要使用行为,我只需要在包含我的行为的代码中的区域的xaml文件中添加一个新的xmlns。
xmlns:behaviors="clr-namespace:Infrastructure.Behaviors;assembly=Infrastructure"
然后在控制上,我只是添加了行为。
<ScrollViewer VerticalScrollBarVisibility="Auto">
<ItemsControl Name="Blah" ItemsSource="{Binding Messages}" ItemTemplate="{StaticResource MessageTemplate}">
<i:Interaction.Behaviors>
<behaviors:ScrollOnNewItem />
</i:Interaction.Behaviors>
</ItemsControl>
</ScrollViewer>
i类只是交互性命名空间xmlns:i="clr namespace:System.Windows.Interactivity;assembly=System.Windows.Interactive"
还有另一种方法可以实现这种行为。这种方式比上面更容易。您所要做的就是调用如下方法:
public void AppendText(RichTextBox richTextBox, string data){
richTextBox.AppendText(data);
bool isScrollDownEnabled = richTextBox.VerticalOffset == 0 ||
richTextBox.VerticalOffset + richTextBox.ViewportHeight == richTextBox.ExtentHeight;
if (isScrollDownEnabled)
richTextBox.ScrollToEnd();
}
它也适用于TextBox
。