在MVC应用程序中组合ObservableCollection <t>和List <t> </t> </t>

时间:2012-04-21 19:52:01

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

我正在尝试在WPF ListVieuw中显示警报列表。为了实现这一点,我将Listbox数据绑定到包含警报列表的属性。由于我使用MVC编程范例,属性位于控制器中,视图的datacontext设置为该控制器。

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

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

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

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

编辑:

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

我的代码:     等级cMain     {         private static volatile cMain instance;         私有静态对象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
}

5 个答案:

答案 0 :(得分:0)

WPF对INotifyPropertyChanged接口的PropertyChanged事件做出反应,因此您应该在更改模型中的属性时实现此接口并引发事件。 如果您这样做,则根本不需要使用ObservableCollection<T>。但请注意,如果您的属性是List,并且您唯一要做的就是添加或删除项目,WPF仍会认为它是相同的列表并且什么都不做。因此,在引发PropertyChanged事件之前,您需要将属性设置为列表的新实例,这可以通过以下方式轻松完成:

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

答案 1 :(得分:0)

尝试在列表更改时直接更新集合,而不是使用每次获取的未来警报重新创建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注册为事件处理程序。

答案 2 :(得分:0)

最好从ObservableCollection<T>派生自己的类并使用它,而不是像你一样尝试将两个现有类封装在一起。至于原因:

  • 首先,它会减少痛苦,因为ObservableCollection<T>已经实现了List<T>支持的所有接口,所以你只需要直接从List<T>和WPF实现你真正需要的方法数据绑定才会起作用;
  • 第二,唯一现实的其他选择,INotifyPropertyChanged方法实施起来很麻烦(你会有效地重写ObservableCollection<T>),否则如果用一个更大的集合替换它们会导致性能不佳每次更改后都需要新的一个,以便更新绑定。

答案 3 :(得分:0)

向警报添加属性

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> 

过滤或隐藏在表示层面是一种类型,因此它应该为业务和数据层留下整个Alarm类和Alarms集合。

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

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

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

答案 4 :(得分:0)

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

因此建议您使用已经为您实现该接口的ObservableCollection <T>类。关于你使用的List <T>变量,我认为最好将它们切换到IList <T>接口类型,而不是由ObservableCollection实现,另外一个好处是应用程序的部分内容需要ObservableCollection通知不需要添加额外的引用或了解Observable集合。