天天看點

MVVM架構的WPF中實作ListBox内容自動換行和滾動到最下方

場景

應用程式主界面需要顯示程式運作中的log資訊,随着log資訊的增多,應能自動滾動到最新的一條資訊。

針對不同的資訊顯示不同,比如

info

資訊正常顯示,

error

資訊标紅提醒。

由于使用的MVVM架構,希望xaml檔案中隻出現

Binding LogText

,将顯示與邏輯分離。選擇ListBox作為顯示的控件,存在兩個問題:

  1. 當log資訊長度超過ListBox寬度時,不會自動換行;
  2. 無法通過設定參數(比如

    SelectedItem

    SelectedIndex

    )使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