【问题标题】:Update an ObservableCollection from another collection从另一个集合更新 ObservableCollection
【发布时间】:2013-10-24 06:34:26
【问题描述】:

我将 ObservableCollection 绑定到 WPF ListView。该列表中的数据来自 REST 服务。所以我从 REST-Service 获取数据并将其放入绑定的 ObservableCollection。 我定期调用 REST-Service 来检查更新的数据,这意味着可以删除、添加数据或更改项目的顺序。如何将这些更改反映到 ObservableCollection 中?每次从 REST 服务获取更新数据时,我都不想完全替换 ObservableCollection。如果 ObservableCollection 仅针对源数据中更改的条目进行更改,则会更加用户友好。因此,当在源数据中添加一个项目时,我想将此项目添加到 ObservableCollection 中与源数据(REST 服务)中完全相同的位置。对于已删除的项目和重新使用的项目也是如此。所以我只想更新更改的项目而不是整个集合。这可能吗?

【问题讨论】:

  • 将 OnpropertyChanged 事件与属性一起使用。
  • 用户友好是什么意思?是性能更高还是这样的 API 更容易使用?如果您必须自己实施该解决方案,这绝对不容易,因此不友好。如果您关心性能,则必须确定替换整个集合更好的阈值是多少。因此,对该决策本身进行评估也不是用户友好的任务。直到您想到某些特定方面,这将受益于逐项更新集合,只需替换它即可。它足够用户友好。
  • 对于“用户友好”,我的意思是如果只有一项更改,用户不应该看到完整列表的刷新。所以用新集合完全替换旧集合对于最终用户来说并不是一个很好的 UI 体验。
  • 您必须考虑另一个问题:您如何找到您收到的物品在收藏中?他们是否实现了 IEquitable 接口?

标签: c# wpf observablecollection


【解决方案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”是调用将更新的列表从数据源(newList)“同步”到现有的 ObservableCollection 的方法。我确定使用方法是错误的,所以也许有人可以帮助我解决这个问题?另外值得一提的是:Collections 中的 Items 必须覆盖 EqualsTo 以便按内容而不是按引用比较对象。

【讨论】:

  • 不要担心它是“生产”代码。你必须做任何你必须做的事情才能完成工作。我相信你可以让你的方法发挥作用,而且可能会很好,但是有一些方法可以使用 .Net 中提供的内置功能来做到这一点。我的解决方案就是一个例子,虽然不是很漂亮。类似于可观察的对象集合本身发送通知的东西是完全可能的。我的回答和提供的链接利用了这个。一旦您对排序进行排序,您的方法仍然应该为将来生成一个方便的类。
【解决方案2】:

我自己也为此苦苦挣扎,恐怕我还没有找到一个特别优雅的解决方案。如果有人在这里发布更好的解决方案,我也很感兴趣。但是到目前为止我所做的基本上是拥有 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 应该这样做。但它确实有效。祝你好运。我会密切关注这个问题,看看是否有其他人想出更复杂的东西。

【讨论】:

    【解决方案3】:

    您要完成的任务并不容易。您必须检查新集合的每个项目与“旧”集合的相关项目,看看是否发生了变化。

    那么,出于性能原因,这个解决方案就没那么有用了。

    简单的解决方案是将当前集合替换为使用服务中的数据创建的新集合。伪代码是这样的:

    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;
    

    【讨论】:

      【解决方案4】:

      这是我对 ObservableCollection 扩展的实现,用于添加带有任何 IEnumerableUpdateCollection 方法

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

      希望它会帮助某人。 欢迎任何优化!

      【讨论】:

        【解决方案5】:

        这是我在设备的 ObservableCollection 中使用的 GET 方法,用于异步检索 Json 对象集合,使用 REST,并将结果合并到现有的 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
                    }
                });
            }
        }
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2017-01-04
          • 1970-01-01
          • 2017-05-24
          • 2011-08-10
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多