I'm looking on an advice on how to resolve a problem I've encountered while implementing a functionality similar to the "Wireshark" trace list view using WPF and MVVM (currently using the MVVM Light framework).
At the moment what I did was adding a ListView into my View, do the binding of "ItemsSource" to an ObservableCollection on the ViewModel and implement the "autoscroll" by adding an event handler on the "CollectionChanged" event of the ListView.Items collection.
This is the XAML of the ListView used on the View:
<ListView x:Name="listView" ItemsSource="{Binding TraceItems}">
<ListView.View>
<GridView>
<GridViewColumn Header="Col1" Width="60" DisplayMemberBinding="{Binding Col1Data}" />
<GridViewColumn Header="Col2" Width="60" DisplayMemberBinding="{Binding Col2Data}" />
<GridViewColumn Header="Col3" Width="150" DisplayMemberBinding="{Binding Col3Data}" />
</GridView>
</ListView.View>
</ListView>
This is the code behind of the View:
public partial class TraceView : UserControl
{
public TraceView()
{
InitializeComponent();
((INotifyCollectionChanged)listView.Items).CollectionChanged += ListView_CollectionChanged;
}
private void ListView_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
listView.ScrollIntoView(listView.Items[listView.Items.Count - 1]);
}
}
}
The problem I've encountered with this solution is that performance is really bad. Every time an object is added in the observable collection the event handler is called, and so the "ScrollIntoView" method of the ListView. After a few minutes of continuous addition of elements, the UI stops responding and memory usage gets really high (an element is added into the collection about every 100 ms).
What could I do to solve the problem? Thank you.
UPDATE 1
Adding an example to reproduce the issue. With this example, on my computer the program gets stuck after around 10 minutes.
NOTE: For a typo, I wrote above that one item is added to the list every 100ms when I meant 10ms instead.
The ListView is used to visualize network communication trace so items are added very quickly. Maybe I could handle a kind of "queue" system where trace items are not inserted immediately in the list view but in a delayed manner? The problem with this solution is that the time gap between the real trace and the visualized one will always increase..
MainWindow.xaml
<Window x:Class="WpfListViewIssue.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
mc:Ignorable="d"
Title="MainWindow" Height="800" Width="1200">
<Grid>
<ListView x:Name="listView" ItemsSource="{Binding TraceItems}" >
<ListView.View>
<GridView>
<GridViewColumn Header="Col1" Width="80" DisplayMemberBinding="{Binding Col1Data}" />
<GridViewColumn Header="Col2" Width="80" DisplayMemberBinding="{Binding Col2Data}" />
<GridViewColumn Header="Col3" Width="80" DisplayMemberBinding="{Binding Col3Data}" />
<GridViewColumn Header="Col4" Width="1000" DisplayMemberBinding="{Binding Col4Data}" />
</GridView>
</ListView.View>
</ListView>
</Grid>
</Window>
MainWindow.xaml.cs
using System.Collections.Specialized;
using System.Windows;
using GalaSoft.MvvmLight.Threading;
namespace WpfListViewIssue
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
DispatcherHelper.Initialize();
InitializeComponent();
((INotifyCollectionChanged)listView.Items).CollectionChanged += ListView_CollectionChanged;
DataContext = new MainViewModel();
}
private void ListView_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
if (e.Action == NotifyCollectionChangedAction.Add)
{
listView.ScrollIntoView(listView.Items[listView.Items.Count - 1]);
}
}
}
}
MainViewModel.cs
using System;
using System.Collections.ObjectModel;
using System.Threading;
using System.Threading.Tasks;
using GalaSoft.MvvmLight;
using GalaSoft.MvvmLight.Threading;
namespace WpfListViewIssue
{
public class TraceItem
{
public string Col1Data { get; set; }
public string Col2Data { get; set; }
public string Col3Data { get; set; }
public string Col4Data { get; set; }
}
public class MainViewModel : ViewModelBase
{
private ObservableCollection<TraceItem> _traceItems;
public ObservableCollection<TraceItem> TraceItems
{
get { return _traceItems; }
set
{
if (value != _traceItems)
{
_traceItems = value;
RaisePropertyChanged();
}
}
}
public MainViewModel()
{
TraceItems = new ObservableCollection<TraceItem>();
AddItems();
}
private async void AddItems()
{
await Task.Factory.StartNew(() =>
{
var randBuffer = new byte[50];
var random = new Random();
for (int i = 0; i < Int32.MaxValue; ++i)
{
random.NextBytes(randBuffer);
TraceItem item = new TraceItem()
{
Col1Data = "Item1 N." + i,
Col2Data = "Item2 N." + i,
Col3Data = "Item3 N." + i,
Col4Data = "RandomData: " + BitConverter.ToString(randBuffer).Replace("-", "")
};
DispatcherHelper.CheckBeginInvokeOnUI(() => TraceItems.Add(item));
Thread.Sleep(10);
}
});
}
}
}