我将 ObservableCollection 绑定到 WPF ListView。该列表中的数据来自 REST 服务。所以我从 REST-Service 获取数据并将其放入绑定的 ObservableCollection。我定期调用 REST-Service 来检查更新的数据,这意味着可以删除、添加数据或更改项目的顺序。如何将这些更改反映到 ObservableCollection 中?每次从 REST 服务获取更新数据时,我都不想完全替换 ObservableCollection。如果 ObservableCollection 仅针对源数据中更改的条目进行更改,则会更加用户友好。因此,当在源数据中添加一个项目时,我想将此项目添加到 ObservableCollection 中与源数据(REST 服务)中完全相同的位置。对于已删除的项目和重新使用的项目也是如此。所以我只想更新更改的项目而不是整个集合。那可能吗?
5 回答
更新:似乎没有标准的方法可以做到这一点,我试图自己实现一个解决方案。这绝对不是生产代码,我可能已经忘记了很多用例,但也许这是一个开始?这是我想出的:
public class ObservableCollectionEx<T> : ObservableCollection<T>
{
public void RecreateCollection( IList<T> newList )
{
// nothing changed => do nothing
if( this.IsEqualToCollection( newList ) ) return;
// handle deleted items
IList<T> deletedItems = this.GetDeletedItems( newList );
if( deletedItems.Count > 0 )
{
foreach( T deletedItem in deletedItems )
{
this.Remove( deletedItem );
}
}
// handle added items
IList<T> addedItems = this.GetAddedItems( newList );
if( addedItems.Count > 0 )
{
foreach( T addedItem in addedItems )
{
this.Add( addedItem );
}
}
// equals now? => return
if( this.IsEqualToCollection( newList ) ) return;
// resort entries
for( int index = 0; index < newList.Count; index++ )
{
T item = newList[index];
int indexOfItem = this.IndexOf( item );
if( indexOfItem != index ) this.Move( indexOfItem, index );
}
}
private IList<T> GetAddedItems( IEnumerable<T> newList )
{
IList<T> addedItems = new List<T>();
foreach( T item in newList )
{
if( !this.ContainsItem( item ) ) addedItems.Add( item );
}
return addedItems;
}
private IList<T> GetDeletedItems( IEnumerable<T> newList )
{
IList<T> deletedItems = new List<T>();
foreach( var item in this.Items )
{
if( !newList.Contains( item ) ) deletedItems.Add( item );
}
return deletedItems;
}
private bool IsEqualToCollection( IList<T> newList )
{
// diffent number of items => collection differs
if( this.Items.Count != newList.Count ) return false;
for( int i = 0; i < this.Items.Count; i++ )
{
if( !this.Items[i].Equals( newList[i] ) ) return false;
}
return true;
}
private bool ContainsItem( object value )
{
foreach( var item in this.Items )
{
if( value.Equals( item ) ) return true;
}
return false;
}
}
方法“ RecreateCollection ”是调用将更新的列表从数据源(newList)“同步”到现有的 ObservableCollection 的方法。我确定使用方法是错误的,所以也许有人可以帮助我解决这个问题?另外值得一提的是:Collections 中的 Items 必须覆盖 EqualsTo 以便按内容而不是按引用比较对象。
我自己也为此苦苦挣扎,恐怕我还没有找到一个特别优雅的解决方案。如果有人在这里发布更好的解决方案,我也很感兴趣。但到目前为止我所做的基本上是拥有一个 ObservableCollection 的 ObservableCollection。
所以声明看起来像:
ObservableCollection<ObservableCollection<string>> collectionThatUpdatesAtAppropriatePosition = new
ObservableCollection<ObservableCollection<string>>();
现在这里的想法是,ObservableCollection 在列表中它自己的特定位置总是只有在其 zero[0] 位置有一个数据值,因此表示数据。因此,示例更新如下所示:
if (this.collectionThatUpdatesAtAppropriatePosition.Count > 0)
{
this.collectionThatUpdatesAtAppropriatePosition[0].RemoveAt(0);
this.collectionThatUpdatesAtAppropriatePosition[0].Add(yourData);
}
我知道它不漂亮。我想知道 NotificationObjects 是否有什么不能更好地尝试的东西。理论上,我认为任何实现 INotifyPropertyChanged 的事情都应该做。但它确实有效。祝你好运。我将密切关注这个问题,看看是否有其他人想出更复杂的东西。
你要完成的任务并不容易。您必须根据“旧”集合的相关项目检查新集合的每个项目,以查看是否发生了变化。
然后,出于性能原因,此解决方案不是那么有用。
简单的解决方案是将当前集合替换为使用服务数据创建的新集合。伪代码是这样的:
ObservableCollection<DataItem> baseCollection = new ObservableCollection<DataItem>();
// adding/removing items
ObservableCollection<DataItem> serviceCollection = new ObservableCollection<DataItem>();
// adding/removing items
baseCollection.Clear();
// replacing old collection with the new one
baseCollection = serviceCollection;
这是我在设备的 ObservableCollection 中使用的 GET 方法,使用 REST 异步检索 Json 对象集合,并将结果合并到现有的 ObservableCollection 中,UI DataGrids 等也可以使用 caller.BeginInvoke(),未经生产测试,但到目前为止似乎工作正常。
public class tbDevices
{
public tbDevices()
{
this.Items = new ObservableCollection<tbDevice>();
}
public ObservableCollection<tbDevice> Items { get; }
public async Task<IRestResponse> GET(Control caller, int limit = 0, int offset = 0, int timeout = 10000)
{
return await Task.Run(() =>
{
try
{
IRestResponse response = null;
var request = new RestRequest(Globals.restDevices, Method.GET, DataFormat.Json);
if (limit > 0)
{
request.AddParameter("limit", limit);
}
if (offset > 0)
{
request.AddParameter("offset", offset);
}
request.Timeout = timeout;
try
{
var client = new RestClient(Globals.apiProtocol + Globals.apiServer + ":" + Globals.apiPort);
client.Authenticator = new HttpBasicAuthenticator(Globals.User.email.Trim(), Globals.User.password.Trim());
response = client.Execute(request);
}
catch (Exception err)
{
throw new System.InvalidOperationException(err.Message, response.ErrorException);
}
if (response.ResponseStatus != ResponseStatus.Completed)
{
throw new System.InvalidOperationException("O servidor informou erro HTTP " + (int)response.StatusCode + ": " + response.ErrorMessage, response.ErrorException);
}
// Will do a one-by-one data refresh to preserve sfDataGrid UI from flashing
List<tbDevice> result_objects_list = null;
try
{
result_objects_list = JsonConvert.DeserializeObject<List<tbDevice>>(response.Content);
}
catch (Exception err)
{
throw new System.InvalidOperationException("Não foi possível decodificar a resposta do servidor: " + err.Message);
}
// Convert to Dictionary for faster DELETE loop
Dictionary<string, tbDevice> result_objects_dic = result_objects_list.ToDictionary(x => x.id, x => x);
// Async update this collection as this may be a UI cross-thread call affecting Controls that use this as datasource
caller?.BeginInvoke((MethodInvoker)delegate ()
{
// DELETE devices NOT in current_devices
for (int i = this.Items.Count - 1; i > -1; i--)
{
result_objects_dic.TryGetValue(this.Items[i].id, out tbDevice found);
if (found == null)
{
this.Items.RemoveAt(i);
}
}
// UPDATE/INSERT local devices
foreach (var obj in result_objects_dic)
{
tbDevice found = this.Items.FirstOrDefault(f => f.id == obj.Key);
if (found == null)
{
this.Items.Add(obj.Value);
}
else
{
found.Merge(obj.Value);
}
}
});
return response;
}
catch (Exception)
{
throw; // This preserves the stack trace
}
});
}
}
这是我的ObservableCollection
扩展实现,添加UpdateCollection
任何方法IEnumerable
using System.Collections.ObjectModel;
using System.Collections.Generic;
using System.Linq;
namespace MyApp.Extensions
{
/// <summary>
/// Observable collection extension.
/// </summary>
public static class ObservableCollectionExtension
{
/// <summary>
/// Replaces the collection without destroy it
/// Note that we don't Clear() and repopulate collection to avoid and UI winking
/// </summary>
/// <param name="collection">Collection.</param>
/// <param name="newCollection">New collection.</param>
/// <typeparam name="T">The 1st type parameter.</typeparam>
public static void UpdateCollection<T>(this ObservableCollection<T> collection, IEnumerable<T> newCollection)
{
IEnumerator<T> newCollectionEnumerator = newCollection.GetEnumerator();
IEnumerator<T> collectionEnumerator = collection.GetEnumerator();
Collection<T> itemsToDelete = new Collection<T>();
while( collectionEnumerator.MoveNext())
{
T item = collectionEnumerator.Current;
// Store item to delete (we can't do it while parse collection.
if( !newCollection.Contains(item)){
itemsToDelete.Add(item);
}
}
// Handle item to delete.
foreach( T itemToDelete in itemsToDelete){
collection.Remove(itemToDelete);
}
var i = 0;
while (newCollectionEnumerator.MoveNext())
{
T item = newCollectionEnumerator.Current;
// Handle new item.
if (!collection.Contains(item)){
collection.Insert(i, item);
}
// Handle existing item, move at the good index.
if (collection.Contains(item)){
int oldIndex = collection.IndexOf(item);
collection.Move(oldIndex, i);
}
i++;
}
}
}
}
用法 :
using MyApp.Extensions;
var _refreshedCollection = /// You data refreshing stuff
MyObservableExistingCollection.UpdateCollection(_refreshedCollection);
希望它会帮助某人。欢迎任何优化!