欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

MVVM架构的WPF中实现ListBox内容自动换行和滚动到最下方

程序员文章站 2022-04-12 11:49:41
...

场景

应用程序主界面需要显示程序运行中的log信息,随着log信息的增多,应能自动滚动到最新的一条信息。
针对不同的信息显示不同,比如info信息正常显示,error信息标红提醒。

由于使用的MVVM架构,希望xaml文件中只出现Binding LogText,将显示与逻辑分离。选择ListBox作为显示的控件,存在两个问题:

  1. 当log信息长度超过ListBox宽度时,不会自动换行;
  2. 无法通过设置参数(比如SelectedItemSelectedIndex)使ListBox自动滚动到最下方。

ListBox内容自动换行

基本思路:

  1. TextBlock中存在属性TextWrapping = "Wrap"可以实现自动换行;
  2. 让TextBlock作为ListBox的item,每一行log绑定到Text属性中;
  3. Logger类中要用ObservableCollection<T>作为ListBox的ItemSource。

ObservableCollection<T>用法和List<T>基本相同,多了当值变化时引发通知的功能。本例中,T需要是一个类,才能让ListBoxItem(即TextBlock)绑定其中的属性。

// 单条Log记录
public class Log
{
    public bool IsError { get; set; }
    public string LogText { get; set; }
}

public class Logger : ObservableObject
{
    private ObservableCollection<Log> _logList;
    public ObservableCollection<Log> LogList
    {
        get => _logList;
        set => Set(() => LogList, ref _logList, value);
    }
}

ViewModel中:

public class LoggerViewModel : ViewModelBase
{
    private Logger _log;
    public Logger Log
    {
        get => _log;
        set => Set(() => Log, ref _log, value);
    }
}

Xaml中:
注意两个设置:ScrollViewer.HorizontalScrollBarVisibility="Disabled"TextWrapping="Wrap"

<ListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled" 
         ItemsSource="{Binding Log.Logger}" >
    <ListBox.ItemTemplate>
        <DataTemplate>
            <StackPanel>
                <TextBlock Text="{Binding LogText}" TextWrapping="Wrap" Margin="-5"/>
            </StackPanel>
        </DataTemplate>
    </ListBox.ItemTemplate>
</ListBox>

参考:Force TextBlock to wrap in WPF ListBox

ListBox滚动到最下方

基本思路:

  1. 在WPF中只有通过ScrollIntoView来实现滚动到视区;
  2. 当Logger中的内容发生变化时,应该RaisePropertyChanged,来触发ScrollIntoView事件。

可以用codeBehind、Behavior来实现,但是最简洁干净的实现方式还是重写一个ScrollingListBox控件,继承自ListBox

public class ScrollingListBox : ListBox
{
    protected override void OnItemsChanged(System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        if (e.NewItems == null) return;
        var newItemCount = e.NewItems.Count;

        if (newItemCount > 0)
            this.ScrollIntoView(e.NewItems[newItemCount - 1]);

        base.OnItemsChanged(e);
    }
}

在对应的XAML中,使用如下:

xmlns:custom="clr-namespace:ScrollingListBoxNamespace"

<custom:ScrollingListBox ScrollViewer.HorizontalScrollBarVisibility="Disabled" 
                         ItemsSource="{Binding Log.Logger}" >
    <ListBox.ItemTemplate>
    ......
    </ListBox.ItemTemplate>
</custom:ScrollingListBox>

还可以在App.xmal中设置该ListBox的样式:

xmlns:custom="clr-namespace:ScrollingListBoxNamespace"

<Application.Resources>
    <ResourceDictionary>
        <Style TargetType="common:ScrollingListBox" BasedOn="{StaticResource MaterialDesignListBox}"/>
    </ResourceDictionary>
</Application.Resources>

参考:ListBox Scroll Into View with MVVM