我有一个ObservableCollection绑定到ListView约190项。每一项都是从一个websocket更新的,这意味着新的信息不断地到来。当我用鼠标浏览列表时,ListView闪烁,如果我用键盘滚动列表,它在每次更新后再次从第一个项目开始。我的假设是,每次更新来了,我创建了一个新的ObservableCollection,所以ListView从满到空(闪烁)再到满,但当我点击一个项目,它的焦点和焦点保持不闪烁,它按预期更新。是什么导致的呢?下面是我的代码:
ViewModel
public List<Tickers> ListTickers = new List<Tickers>();
private WSFuturesResponse _futuresResponse;
public WSFuturesResponse FuturesResponse
{
get
{
return _futuresResponse;
}
set
{
_futuresResponse = value;
OnPropertyChanged("FuturesResponse");
foreach (var future in ListTickers)
{
if (future.Market == FuturesResponse.market)
{
future.Price = FuturesResponse.data.last;
}
}
Tickers = new ObservableCollection<Tickers>(ListTickers);
}
}
private ObservableCollection<Tickers> _tickers;
public ObservableCollection<Tickers> Tickers
{
get
{
return _tickers;
}
set
{
_tickers = value;
OnPropertyChanged("Tickers");
}
}
// the method that fills the ListTickers
private async void StartConnection(Ticker wsApi, Client client, FtxRestApi api)
{
// get all futures data from the API
var futures = await api.GetAllFuturesAsync();
// parse the data
ApiFuturesResponse all_futures = JsonConvert.DeserializeObject<ApiFuturesResponse>(futures);
wsApi.OnWebSocketConnect += () =>
{
wsApi.SendCommand(FtxWebSocketRequestGenerator.GetAuthRequest(client));
foreach (ApiFuturesData future in all_futures.result)
{
if (future.name.Contains("PERP"))
{
ListTickers.Add(new Tickers(future.name));
wsApi.SendCommand(FtxWebSocketRequestGenerator.GetSubscribeRequest("ticker", future.name));
}
}
};
await wsApi.Connect();
}
// the event that updates the properties
public void WebsocketOnMessageReceive(object o, MessageReceivedEventArgs messageReceivedEventArgs)
{
FuturesResponse = JsonConvert.DeserializeObject<WSFuturesResponse>(messageReceivedEventArgs.Message);
}
XAML
<ListView Grid.Row="2"
Grid.Column="1"
ItemsSource="{Binding Tickers}">
<ListView.ItemTemplate>
<DataTemplate>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock x:Name="TextBlockMarket" Grid.Column="0" Width="100" Text="{Binding Market}"/>
<TextBlock x:Name="TextBlockPrice" Grid.Column="1" Width="100" Text="{Binding Price}"/>
</Grid>
</DataTemplate>
</ListView.ItemTemplate>
</ListView>
-
更换
ListView
的数据源导致的不良体验。将一个新实例分配给source属性总是会导致ItemsControl
的完全重置。如果集合足够大或者ItemsControl
不支持UI虚拟化,您将体验到严重的性能影响,如缓慢的UI甚至会冻结。
使用ObservableCollection
的原因是它允许修改源集合,而数据绑定引擎可以侦听更改以触发ItemsControl
更新自己。如果您替换完整的集合实例,那么ObservableCollection
实际上是冗余的。 -
此外,由于您已经在
foreach
中枚举了新的源集合ListTickers
,因此将每个项直接添加到Tickers
集合将进一步提高性能,因为在将集合传递给ObservableCollection
的构造函数时消除了第二个枚举。因此,直接修改Tickers
集合(在同一迭代中)将解决两个与性能相关的问题。 -
一个严重的bug来源是你的
async
方法StartConnection
返回void
。async
方法必须总是返回一个Task
,这样它们才能被正确地等待。唯一的异常是事件处理程序,因为它们必须是void
.
因为您当前的实现返回void
,您显然没有await
它。但是,您必须始终等待async
方法,以避免意外行为。 -
此外,保持属性不受长时间运行任务或枚举的影响。在您的示例中,请考虑将
ListTickers
集合的枚举和Tickers
集合的更新移动到WebsocketOnMessageReceive
事件处理程序。 -
你总是想保持项目的视觉树,即
ItemTemplate
简单,以提高渲染性能。当前定义了三列,但只使用了两列。考虑使用HorizontalAlignment
代替。Grid
也是一种昂贵的面板。DockPanel或StackPanel
都比较便宜。还要考虑从TextBlock
元素中删除元素名,因为它们强制隐式地创建字段。 -
不清楚您正在使用什么API发送请求。但它看起来像你在混合api。很难相信异步API要求您使用事件来获得结果。也许你该看看这个。等待
Connect()
应该取代OnWebSocketConnect
事件。 -
使用。net
JsonSerializer.DeserializeAsync
或System.Text.Json
命名空间通常会给你异步JSON处理:如何在。net中序列化和反序列化(封送和反封送)JSON
始终修改现有的ObservableCollection
实例,而不是替换它:
public WSFuturesResponse FuturesResponse
{
get => _futuresResponse;
set
{
_futuresResponse = value;
// Use 'nameof' to avoid typos and to enable the usage of refactoring tools
// e.g. to rename the property.
OnPropertyChanged(nameof(FuturesResponse));
// Clear the data source and reuse it
Tickers.Clear();
int preloadCount = 20;
// Take as much as possible, but not more than 'preloadCount'
AddTickers(ListTickers.Take(preloadCount));
// Defer adding the remaining items until the ItemsSource has been rendered.
Application.Current.Dispatcher.InvokeAsync(
() => AddTickers(ListTickers.Skip(preloadCount)),
DispatcherPriority.Background);
}
private void AddTickers(IEnuemrable<Tickers> tickers)
{
foreach (var future in tickers)
{
if (future.Market == FuturesResponse.market)
{
future.Price = FuturesResponse.data.last;
}
Tickers.Add(future);
}
}
}