我将ObservableCollection绑定到WPF ListView。该列表中的数据来自REST服务。所以我从REST服务获取数据并将其放入绑定的ObservableCollection中。 我定期调用REST服务来检查更新的数据,这意味着可以删除,添加数据或更改项目的顺序。如何将这些更改反映到ObservableCollection中?每次从REST服务获取更新数据时,我都不想完全替换ObservableCollection。如果仅针对源数据中更改的条目更改ObservableCollection,则会更加用户友好。因此,当在源数据中添加Item时,我想将此项添加到与源数据(REST-Service)中完全相同的ObservableCollection。删除的项目和使用的项目相同。所以我想更新已更改的项而不是整个Collection。这可能吗?
答案 0 :(得分:1)
更新:因为似乎没有标准方法可以做到这一点我试图自己实现一个解决方案。这绝对没有生产代码,我可能已经忘记了很多用例,但也许这是一个开始? 以下是我提出的建议:
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 ”是调用将已更新的List从Datasource(newList)“同步”到现有ObservableCollection的方法。我确信度假是错误的,所以也许有人可以帮我解决这个问题?另外值得一提的是:集合中的项目必须重写EqualsTo才能按内容而不是通过引用来比较对象。
答案 1 :(得分:0)
我自己也在努力解决这个问题,我恐怕没有找到一个特别优雅的解决方案。如果有人在这里发布更好的解决方案,我也同样感兴趣。但到目前为止我所做的基本上是ObservableCollection的ObservableCollection。
声明如下:
ObservableCollection<ObservableCollection<string>> collectionThatUpdatesAtAppropriatePosition = new
ObservableCollection<ObservableCollection<string>>();
现在的想法是ObservableCollection在其自己的列表中的特定位置始终只有一个数据值在其零[0]位置,因此代表数据。因此,示例更新将如下所示:
if (this.collectionThatUpdatesAtAppropriatePosition.Count > 0)
{
this.collectionThatUpdatesAtAppropriatePosition[0].RemoveAt(0);
this.collectionThatUpdatesAtAppropriatePosition[0].Add(yourData);
}
我知道它不漂亮。我想知道是否有一些用NotificationObjects无法更好地尝试的东西。从理论上讲,我认为anything that implements INotifyPropertyChanged应该这样做。但它确实有效。祝好运。我将密切关注这个问题,看看是否还有其他人想出更复杂的东西。
答案 2 :(得分:0)
你想要完成的任务并不那么容易。您必须根据“旧”集合的相关项检查新集合的每个项目,以查看是否有更改。
然后,出于性能原因,这个解决方案并没有那么有用。
简单的解决方案是将当前集合替换为使用服务中的数据创建的新集合。伪代码就是这样:
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;
答案 3 :(得分:0)
以下是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);
希望它会帮助某人。 欢迎任何优化!
答案 4 :(得分:0)
这是扩展方法的另一种解决方案。 @ n3k的先前解决方案在重复项上失败。
$(document).ready(function(){
$('#click-spanish').click(function() {
$('#spanish-examples').toggle('1000');
//"this" is the click-spanish icon, since you are inside the click handler
//for that element. You just have to use it, not try to find it.
$(this).toggleClass("fa-caret-down fa-caret-up");
});
});
感谢您的任何建议或反馈!
答案 5 :(得分:0)
这是我在设备的ObservableCollection内使用的GET方法,以使用REST异步检索Json对象集合,并将结果合并到现有的ObservableCollection中,该数据也可以由UI DataGrids等使用,使用调用程序。 (),尚未经过生产测试,但到目前为止效果还不错。
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
}
});
}
}