【问题标题】:Combining ObservableCollection<T> and List<T> in a MVC application在 MVC 应用程序中组合 ObservableCollection<T> 和 List<T>
【发布时间】:2012-05-02 23:54:42
【问题描述】:

我正在尝试在 WPF ListView 中显示警报列表。为此,我将列表框数据绑定到包含警报列表的属性。由于我使用 MVC 编程范例,因此该属性位于控制器中,并且视图的数据上下文设置为该控制器。

我注意到,当我向列表中添加警报时,视图没有显示新警报。经过一番研究,我发现我需要使用 ObservableCollection 类来正确执行此操作。

但是,显示警报列表并不是唯一需要做的事情,所以我不能/不想将列表的变量类型更改为 ObservableCollection。

我现在尝试创建 ObservableCollection 类型的属性,但这也不起作用。这很正常,因为我没有将警报添加到属性中,而是将其添加到变量中,该变量仍然是 List 类型。

有没有办法在更新列表时告诉属性,或者有其他/更好的方式来显示我的警报并让它们易于用于程序的其他部分?

编辑:

我的解决方法:我通过从我的警报变量中清除 PropertyChanged 事件的事件处理程序中的属性 FutureEvents 来触发 PropertyChanged 事件。

我的代码: cMain 类 { 私有静态易失 cMain 实例; 私有静态对象 syncRoot = new Object();

    ObservableCollection<Alarm> alarms;

    #region properties
    /// <summary>
    /// Returns the list of alarms in the model. Can't be used to add alarms, use the AddAlarm method
    /// </summary>
    public ObservableCollection<Alarm> Alarms
    {
        get
        {
            return alarms;
        }
    }

    /// <summary>
    /// Returns the ObservableCollection of future alarms in the model to be displayed by the vieuw.
    /// </summary>
    public ObservableCollection<Alarm> FutureAlarms
    {
        get
        {
            //Only show alarms in the future and alarm that recure in the future
            var fAlarms = new ObservableCollection<Alarm>(alarms.Where(a => a.DateTime > DateTime.Now || (a.EndRecurrency != null && a.EndRecurrency > DateTime.Now)));
            return fAlarms;
        }
    }

    /// <summary>
    /// Returns a desctription of the date and time of the next alarm
    /// </summary>
    public String NextAlarmDescription
    {
        get
        {
            if (alarms != null)
            {
                return alarms.Last().DateTimeDescription;
            }
            else
            {
                return null;
            }
        }
    }
    #endregion //properties


    #region public

    /// <summary>
    /// Returns the instance of the singleton
    /// </summary>
    public static cMain Instance
    {
        get
        {
            if (instance == null) //Check if an instance has been made before
            {
                lock (syncRoot) //Lock the ability to create instances, so this thread is the only thread that can excecute a constructor
                {
                    if (instance == null) //Check if another thread initialized while we locked the object class
                        instance = new cMain();
                }
            }
            return instance;
        }
    }

    /// <summary>
    /// Shows a new intance of the new alarm window
    /// </summary>
    public void NewAlarmWindow()
    {
        vNewAlarm newAlarm = new vNewAlarm();
        newAlarm.Show();
    }

    public void AddAlarm(Alarm alarm)
    {
        alarms.Add(alarm);            
    }

    public void RemoveAlarm(Alarm alarm)
    {
        alarms.Remove(alarm);
    }

    public void StoreAlarms()
    {
        mXML.StoreAlarms(new List<Alarm>(alarms));
    }

    #endregion //public

    #region private

    //Constructor is private because cMain is a singleton
    private cMain()
    {
        alarms = new ObservableCollection<Alarm>(mXML.GetAlarms());
        alarms.CollectionChanged += new System.Collections.Specialized.NotifyCollectionChangedEventHandler(alarms_CollectionChanged);
    }

    private void alarms_CollectionChanged(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
    {
        FutureAlarms.Clear(); //Needed to trigger the CollectionChanged event of FutureAlarms
        StoreAlarms();
    }


    #endregion //private
}

【问题讨论】:

    标签: c# .net wpf data-binding observablecollection


    【解决方案1】:

    WPF 对INotifyPropertyChanged 接口的PropertyChanged 事件作出反应,因此您应该实现此接口并在您更改模型中的属性时引发该事件。 如果这样做,则根本不需要使用ObservableCollection&lt;T&gt;。但请注意,如果您的属性是 List 并且您所做的唯一事情是添加或删除项目,WPF 仍然会认为它是同一个列表并且什么都不做。因此,在引发 PropertyChanged 事件之前,您需要将属性设置为列表的新实例,这很容易做到:

    MyList.add(newItem);
    MyList = new List<something>(MyList);
    #raise the event
    

    【讨论】:

    • 一个列表没有实现INotifyPropertyChanged,那么如何引发PropertyChanged事件呢?
    • @Bitbored,你的控制器应该实现它。
    【解决方案2】:

    不要在每次获取时使用未来警报重新创建 ObservableCollection,而是尝试在列表更改时直接更新集合:

    public ObservableCollection<Alarm> FutureAlarms { get; private set;} // initialize in constructor
    
    private void UpdateFutureAlarms() {
        fAlarms.Clear();
        fAlarms.AddRange(
            alarms.Where(
                a => a.DateTime > DateTime.Now 
                    || (a.EndRecurrency != null && a.EndRecurrency > DateTime.Now)
            )
        )
    }
    
    //... somewhere else in the code... 
    
    public void Foo () {
        // change the list
        alarms.Add(someAlarm);
        UpdateFutureAlarms();
    }
    

    如果在 List 更改时触发了事件,您还可以将 UpdateFutureAlarms 注册为事件处理程序。

    【讨论】:

      【解决方案3】:

      您最好从ObservableCollection&lt;T&gt; 派生您自己的类并使用它,而不是像您那样尝试将两个现有类封装在一个组合中。至于为什么:

      • 首先,它会少很多痛苦,因为ObservableCollection&lt;T&gt;已经实现了List&lt;T&gt;支持的所有接口,所以你只需要直接从List&lt;T&gt;实现你真正需要的方法,WPF数据绑定就可以了;
      • 第二,唯一现实的另一种选择,INotifyPropertyChanged 方法实施起来很麻烦(您将有效地重写ObservableCollection&lt;T&gt;),或者如果您每次都用新的集合替换它们,则会导致更大的集合性能不佳更改只是为了更新绑定。

      【讨论】:

      • 这个问题是视图只是程序的一小部分。还有很多其他类和很多代码,都使用 List 来处理警报。如果我将主控制器中列表的类更改为 ObservableCollection,最好的情况是我只会失去一致性,最坏的情况是我需要更改很多代码才能使其正常工作。
      • 我也不确定将类型更改为 ObservableCollection 是否有效,因为我将警报添加到控制器的警报变量中,但我将列表视图数据绑定到属性 FutureAlarms,它将处理结果也不会引发新的 PropertyChanged 事件。
      • 我明白了。但是,您的数据绑定问题不会消失,因此您可以尝试以下方法。不是直接使用 List,而是设置一个类别名(例如“使用 AlarmList = List;”)或从 List 派生一个名为 AlarmList 的类,并全面更新所有旧 List 引用.然后开始尝试新的 AlarmList: ObservableCollection 类,知道返回 List 只需几个字符。正如我所说,如果您想要轻松的数据绑定,(派生自)ObservableCollection 是最佳选择。
      【解决方案4】:

      为警报添加属性

      public bool Future 
      {   get return (DateTime > DateTime.Now 
                  || (EndRecurrency != null && EndRecurrency > DateTime.Now));  
      }
      

      当更新警报时,为所有(或适当的子集)在 Future 上调用 NotifyPropertyChanged。

      然后使用 DataTrigger 或 CollectionViewSource 过滤器将其隐藏

      <DataTrigger Binding="{Binding Path=Future, Mode=OneWay}" Value="False">
                                      <Setter Property="Visibility" Value="Collapsed"/>
                                  </DataTrigger> 
      

      过滤或隐藏是一种表示级别,因此它应该为业务和数据层留下警报类和警报集合。

      由于 ObservableCollection 实现 iList 应该是兼容的。

      对于您当前的模型,FurtureAlarms 也可能是一个列表。可以缩短语法

       (alarms.Where(a => a.DateTime > DateTime.Now || (a.EndRecurrency != null && a.EndRecurrency > DateTime.Now))).toList(); 
      

      【讨论】:

        【解决方案5】:

        在 WPF 中,正确绑定到集合需要绑定到的集合实现 INotifyCollectionChanged,该集合具有 CollectionChanged 事件,每当从集合中添加或删除项目时都会触发该事件。

        因此,建议您使用已为您实现该接口的 ObservableCollection&lt;T&gt; 类。关于您使用的 List&lt;T&gt; 变量,我认为最好将它们切换到接口类型 IList&lt;T&gt; ,而不是由 ObservableCollection 实现,并且作为额外的好处,您的应用程序中不需要 ObservableCollection 的部分通知不需要添加额外的引用或了解 Observable 集合。

        【讨论】:

          猜你喜欢
          • 2016-06-28
          • 1970-01-01
          • 2011-07-30
          • 2023-04-05
          • 2012-05-28
          • 1970-01-01
          • 1970-01-01
          • 2016-03-19
          • 1970-01-01
          相关资源
          最近更新 更多