MVVM架构的WPF中实现ListBox内容自动换行和滚动到最下方
程序员文章站
2022-04-12 11:49:41
...
场景
应用程序主界面需要显示程序运行中的log信息,随着log信息的增多,应能自动滚动到最新的一条信息。
针对不同的信息显示不同,比如info
信息正常显示,error
信息标红提醒。
由于使用的MVVM架构,希望xaml文件中只出现Binding LogText
,将显示与逻辑分离。选择ListBox作为显示的控件,存在两个问题:
- 当log信息长度超过ListBox宽度时,不会自动换行;
- 无法通过设置参数(比如
SelectedItem
、SelectedIndex
)使ListBox自动滚动到最下方。
ListBox内容自动换行
基本思路:
- TextBlock中存在属性
TextWrapping = "Wrap"
可以实现自动换行; - 让TextBlock作为ListBox的item,每一行log绑定到Text属性中;
- 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滚动到最下方
基本思路:
- 在WPF中只有通过
ScrollIntoView
来实现滚动到视区; - 当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>