1

I'm trying to display a list of alarms in a WPF ListVieuw. To accomplish this I databinded the Listbox to a property containing the list of alarms. Since I use the MVC programming paradigm the property is located in the controller, and the datacontext of the view is set to that controller.

I noticed that when I added an alarm to the list, the view didn't display the new alarm. After some research I found I need to use the ObservableCollection class to do this correctly.

However, displaying the list of alarms isn't the only thing that needs to be done with it, so I can't / don't want to change the variable type of the list to ObservableCollection.

I now tried to make a property of the type ObservableCollection, but this doesn't work either. This is pretty normal, since I don't add the alarm to the property, I add it to the variable, which is still of the type List.

Is there a way to tell the property when the List is updated, or an other/better way to display my alarms and keep them easy to use for other parts of the program?

Edit:

My workaround: I trigger the PropertyChanged event by clearing my property FutureEvents in the eventhandler of the PropertyChanged event from my alarms variable.

My code: class cMain { private static volatile cMain instance; private static object 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
}
4

5 回答 5

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> 

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

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

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

 (alarms.Where(a => a.DateTime > DateTime.Now || (a.EndRecurrency != null && a.EndRecurrency > DateTime.Now))).toList(); 
于 2012-04-22T13:14:43.673 回答
0

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

MyList.add(newItem);
MyList = new List<something>(MyList);
#raise the event
于 2012-04-21T19:59:34.983 回答
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 注册为事件处理程序。

于 2012-04-21T20:14:24.413 回答
0

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

  • 首先,它会少很多痛苦,因为ObservableCollection<T>已经实现了所有List<T>支持的接口,所以你只需要直接实现你实际需要的方法List<T>,WPF数据绑定就可以工作;
  • 其次,唯一现实的另一种选择,该INotifyPropertyChanged方法实施起来很麻烦(您将有效地重写ObservableCollection<T>),或者如果您在每次更改后将它们替换为新的集合以更新绑定,则会导致更大的集合性能不佳。
于 2012-04-21T20:17:50.717 回答
0

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

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

于 2012-04-22T22:04:39.117 回答