Combining ObservableCollection<T> and List<T> in a MVC application - c#

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
}

WPF reacts on the PropertyChanged event of the INotifyPropertyChanged interface, so you should implement this interface and raise the event when you change properties in your model.
If you do this, you don't need to use ObservableCollection<T> at all. But be aware that if your property is a List and the only thing you have done is add or remove items, WPF will still think it's the same list and do nothing. Therefore, before you raise the PropertyChanged event, you need to set you property to a new instance of a list, which is easily done like this:
MyList.add(newItem);
MyList = new List<something>(MyList);
#raise the event

Instead of recreating the ObservableCollection with the future alarms on every get, try to update the collection directly when the list changes:
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();
}
You could also register UpdateFutureAlarms as an event handler, if you had an event fired when the List changes.

You would be better off deriving your own class from ObservableCollection<T> and using that instead of trying to encapsulate both existing classes in a combination as you did. As to why:
first, it will be much less painful, since ObservableCollection<T> already implements all interfaces that List<T> supports, so you only need to implement the methods you actually need directly from List<T> and WPF data binding will just work;
second, the only realistic other alternative, the INotifyPropertyChanged approach is cumbersome to implement (you'll effectively rewrite ObservableCollection<T>) or it will result in bad performance with larger collections if you replace them with a new one after every change just to get the binding to update.

Add a property to Alarm
public bool Future
{ get return (DateTime > DateTime.Now
|| (EndRecurrency != null && EndRecurrency > DateTime.Now));
}
When up update Alarms call NotifyPropertyChanged on Future for all (or an appropriate subset).
Then use a DataTrigger or CollectionViewSource Filter to hide it
<DataTrigger Binding="{Binding Path=Future, Mode=OneWay}" Value="False">
<Setter Property="Visibility" Value="Collapsed"/>
</DataTrigger>
Filtering or hiding is kind of at the presentation level so it should leave you Alarm class and Alarms collection whole for for business and data layers.
Since ObservableCollection implements iList is should be compatible.
With you current model FurtureAlarms might as well be a List. Can shorten the syntax
(alarms.Where(a => a.DateTime > DateTime.Now || (a.EndRecurrency != null && a.EndRecurrency > DateTime.Now))).toList();

In WPF, binding to collections correctly needs that the collection bound to implements INotifyCollectionChanged which has the CollectionChanged Event that should be fired whenever an item is added or removed from the collection.
So you're advised to use the ObservableCollection<T> class which already implements that interface for you. And concerning the List<T> variables you use I think it's better that you switch them to the interface type IList<T> instead which is implemented also by ObservableCollection and as an additional benefit the parts of your application that don't need the ObservableCollection notification won't need to add additional references or know about the Observable collection.

Related

Understanding Delegate Command in Prism

I'm struggling to understand the usage of delegate commands (from Prism) and I build a dummmy application in which I intend to do the following.
I have the command as
private readonly DelegateCommand selectAll;
public ICommand SelectAll
{
get { return selectAll; }
}
and use it as
selectAll= new DelegateCommand(SelectAll,CanSelectAll);
private bool CanSelectAll()
{
if (AllSelectedItems.Count()>3)
{
return true;
}
return false;
}
public IList<Student> AllItemsSelected
{
get => m_Items;
set => Set(ref m_Items, value);
}
I can see the button being disabled as expected when my ViewModel gets initialized but after even though sometimes this AllSelectedItems.count > 3, it doesn't seem to update and notify the UI.
What am I doing wrong here?
When you create the command, tell it to observe the property AllItemsSelected, like this:
selectAll= new DelegateCommand(SelectAll,CanSelectAll)
.ObservesProperty(() => AllItemsSelected);
That will make the command's state update every time AllItemsSelected changes.
This function, ObservesProperty is a nice feature of Prism. It lets you set up one-time monitoring of all your properties on which that comand's state depends.
The CanSelectAll method is not called automatically when the collection changes, after all how should the command know when to reevaluate the the condition? You have to explicitly tell it to do so.
An ICommand exposes a CanExecutChanged event that must be raised to notify the element binding the command to call the CanExecute method in order to evaluate if the command can be executed or not. This usually enables or disables the element in the UI, e.g. a Button. When and how this event is raised depends on the concrete implementation of the ICommand interface.
In Prism for DelegateCommands, this can be done in two different ways.
Call the RaiseCanExecuteChanged on the command. This could be done in the setter of your AllItemsSelected property.
public IList<Student> AllItemsSelected
{
get => m_Items;
set
{
Set(ref m_Items, value);
selectAll.RaiseCanExecuteChanged();
}
}
Another way of doing this is using the ObservesProperty method when instantiating the command. You pass a lambda for the property to be observed and the command will automatically raise the CanExecuteChanged event once a PropertyChanged event is raised for it. That means this mechanism only works if your view model implements INotifyPropertyChanged and your property raises PropertyChanged.
selectAll= new DelegateCommand(SelectAll, CanSelectAll).ObservesProperty(() => AllItemsSelected);
Which mechanism you choose is up to you. For your specific case it is important to know how AllItemsSelected changes. If you always assign a new collection once the selection changes, the examples above will work, since then each time the setter of the property is called and PropertyChanged is raised and therefore ObservesProperty will pick up the change and call CanExecutChanged for example.
However, if you reuse the same collection, e.g. only add and delete items from it, this will not work, as the actual collection object does not change, which means no call to the setter and no PropertyChanged. In this case put the call to RaiseCanExecuteChanged into the method that adds, deletes or modifies the collection.
In case the collection is modified somewhere else e.g. items are added through the UI directly to the collection, you would have to use a collection type that supports notifying collection changes like ObservableCollection<T> (through the CollectionChanged event). You could add a handler to CollectionChanged which calls RaiseCanExecuteChanged.
public class MyViewModel : BindableBase
{
private readonly DelegateCommand _selectAll;
public MyViewModel()
{
_selectAll = new DelegateCommand(ExecuteSelectAll, CanExecuteSelectAll);
AllSelectedItems = new ObservableCollection<Student>();
AllSelectedItems.CollectionChanged += OnAllSelectedItemsChanged;
}
public ICommand SelectAll => _selectAll;
public ObservableCollection<Student> AllSelectedItems
{
get => m_Items;
set => Set(ref m_Items, value);
}
private void ExecuteSelectAll()
{
// ...your code.
}
private bool CanExecuteSelectAll()
{
return AllSelectedItems.Count > 3;
}
private void OnAllSelectedItemsChanged(object sender, NotifyCollectionChangedEventArgs e)
{
_selectAll.RaiseCanExecuteChanged();
}
}

DependencyProperty of type ObservableCollection doesn't bind

I am trying to make a custom control with a DependencyProperty.
But I cannot bind my ObservableCollection to the control.
When I use an Enumerable I have no problem. But I need to add items to the collection so my only option is ObservableCollection.
Creating Authorizations:
AuthorizationsDest = new ObservableCollection<Authorization>();
AuthorizationsDest.Add(new Authorization() { Key = "Test1", Description = "Test1", ObjectState = ObjectState.UnModified });
}
The custom control in xaml
<customControls:ListBoxEditLookup ItemsSource="{Binding Authorizations}" DisplayMember="Description" DestinationList="{Binding AuthorizationsDest}" />
The DependencyProperty:
[Description("Binded destination list"), Category("Data")]
public static readonly DependencyProperty ItemsProperty = DependencyProperty.Register("DestinationList", typeof(ObservableCollection<HrdEntity>), typeof(ListBoxEditLookup), new UIPropertyMetadata(null));
public ObservableCollection<HrdEntity> DestinationList
{
get
{
return GetValue(ItemsProperty) as ObservableCollection<HrdEntity>;
}
set { SetValue(ItemsProperty, value); }
}
Based on the comment responses to your question, I think we have arrived at the realization that using a specific concrete collection type on your dependency property is causing issues, and you should consider using an interface such as IEnumerable instead. Read on for a more detailed explanation.
It is generally a good idea to use the IEnumerable interface as the type for collection dependency properties in a custom control. It is the base interface that every collection implements as it allows foreach loops to be run on them. When the dependency property is set, you can inspect the value to see if it implements other interfaces that you care about within your control.
For example, if your control wants to do things like add, remove and insert items and/or index into the collection, check to see if it implements IList. If you want to observe the collection for changes, check to see if it implements INotifyCollectionChanged.
Consider maintaining private references to the collection that are typed as the interfaces you need to access. For example:
private IList mItemsAsList;
private INotifyCollectionChanged mItemsAsObservable;
// Call when the value of ItemsProperty changes
private void OnItemsChanged(IEnumerable newValue)
{
if (mItemsAsObservable != null)
{
mItemsAsObservable.CollectionChanged -= Items_CollectionChanged;
}
mItemsAsList = newValue as IList;
mItemsAsObservable = newValue as INotifyCollectionChanged;
if (mItemsAsObservable != null)
{
mItemsAsObservable.CollectionChanged += Items_CollectionChanged;
}
}
private void Items_CollectionChanged(object sender, NotifyCollectionChangedEventArgs e)
{
// Do stuff in response to collection being changed
}
If there are certain things that are required by your control (not optional), you can always throw an ArgumentException in the property changed callback if those requirements are not met. For example, if you must be able to add new items to the collection:
mItemsAsList = newValue as IList;
if (newValue != null && (mItemsAsList == null || mItemsAsList.IsReadOnly || mItemsAsList.IsFixedSize))
{
throw new ArgumentException("The supplied collection must implement IList, not be readonly, and have a variable size.", "newValue");
}
Once you have specialized references to the collection, you can limit your functionality based on which interfaces are implemented. For example, let's say you want to add a new item:
private void AddItem(object item)
{
// Make sure to check IsFixedSize because some collections, such as Array,
// implement IList but throw an exception if you try to call Add on them.
if (mItemsAsList != null && !mItemsAsList.IsReadOnly && !mItemsAsList.IsFixedSize)
{
mItemsAsList.Add(item);
}
}

Updating ObservableCollection using delegates in MVVM?

I have three ViewModels: MainViewModel, PreferencesViewModel and ColourControllerViewModel - the latter 2 are properties of the MainViewModel.
ColourControllerViewModel is used for the 'ColourSelector' view, where various colours can be created and deleted. It contains an ObservableCollection of ColourViewModel, which has a property detailing the colour, and a bool property determining if it should be shown on the preferences tab (DisplayOnPreferences).
PreferencesViewModel is used for the 'Preferences' view, which contains a combo box of colours - this is represent by an ObservableCollection of ColourViewModel, and only those ColourViewModels where DisplayOnPreferences == true should be displayed.
My question is, what's the easiest way to do this? Currently, I am using an Action delegate called UpdateList() which passes the updated list from ColourControllerViewModel to MainViewModel, which in turn updates the PreferencesViewModel. I don't really like this though, it feels like there's a better way.
Should there be a single ObservableCollection of ColourViewModel on MainViewModel that is updated/accessed by either instance?
Here are the classes:
public class MainViewModel : ViewModel
{
private ColourMappingControllerViewModel _colourMappingControllerViewModel;
private PreferencesControllerViewModel _preferencesTabViewModel;
public MainViewModel()
{
// Initialise the database Handler
dbHandler = DatabaseHandler.DbHandlerInstance;
_colourMappingControllerViewModel = new ColourMappingControllerViewModel(dbHandler.GetColourMappingsList(), UpdateColourList);
_preferencesTabViewModel = new PreferencesControllerViewModel(dbHandler.GetPreferences, ColourMappingList)
}
public ObservableCollection<ColourMappingViewModel> ColourMappingList
{
get { return ColourMappingControllerViewModel.ColourMappingList; }
}
public void UpdateColourList(ObservableCollection<ColourMappingViewModel> colourList)
{
PreferencesTabViewModel.UpdateColourList(colourList);
}
}
public class ColourMappingControllerViewModel : ViewModel
{
public ColourMappingControllerViewModel(IEnumerable<ColourMapping> colourMappingsList, Action<ObservableCollection<ColourMappingViewModel>> updateColourListAction)
{
InitialiseCommands();
ColourMappingList = new ObservableCollection<IColourMappingViewModel>(InitialiseColourMappingsList(colourMappingsList));
}
public ICommand AddColourMappingCommand { get; set; }
private void InitialiseCommands()
{
AddColourMappingCommand = new DelegatingCommand(AddColourMapping);
}
private void AddColourMapping() // Attached to Command on View
{
var newColourMapping = new ColourMappingViewModel(
new ColourMapping());
ColourMappingList.Add(newColourMapping);
ColourMappingsCollectionView.MoveCurrentToLast();
UpdateColourMappingList();
}
private void UpdateColourMappingList()
{
UpdateColourListAction.Invoke(ColourMappingList);
}
}
public PreferencesControllerViewModel : ViewModel
{
public PreferencesControllerViewModel(object preferenceInfo, ObservableCollection<ColourMappingViewModel> colourMappingsList)
{
var pciTrendBlocks = pciBlocks;
ColourMappingsList = colourMappingsList;
}
public void UpdateColourList(ObservableCollection<ColourMappingViewModel> colourList)
{
ColourMappingsList = colourList;
}
}
I know the ObservableCollection class is being misused - it's probably not necessary on the Preferences as it will only be updated in ColourMappingController.
I would agree that you need a single ObservableCollection that is shared between views. This effectively becomes your "Model" in MVVM.
You may also want to enforce different access semantics by having a ReadOnlyObservableCollection that can be passed to your preferences VM etc. This ensures that only ColourControllerViewModel (Which gets the underlying ObservableCollection) can actually alter the collection.
In my apps I tend to have a separate data layer, but yes, for now it would be simplest to just add them to MainViewModel.
The alternative would be to have ColourControllerViewModel be the thing that owns the collection (and exposes it as a ReadOnlyObservableCollection), and have you MainViewModel just pass the collection into any other VM's that need it.

Custom IEnumerable as ItemsSource for ListBox

I have a class that defines a custom GetEnumerator() function(by implementing IEnumerable<>). I use it to iterate in a contiguous manner over several ObservableCollection<LogEvent> that are in every TestStep. I have a private ObservableCollection<TestStep> that contains all the needed data.
I would like to use an instance of this class as the ItemsSource of a ListBox. However, the ListBox never gets updated when the underlying data(ObservableCollection<LogEvent>) is updated. Here's a sample of that class:
public class FlatLogViewModel : IEnumerable<LogEvent>
{
public FlatLogViewModel(ObservableCollection<TestStep> subSteps)
{
m_subSteps = subSteps;
}
public IEnumerator<LogEvent> GetEnumerator()
{
foreach (TestStep step in SubSteps)
{
// step.LogEvents is an ObservableCollection<LogEvent>
foreach (LogEvent logEvent in step.LogEvents)
yield return logEvent;
}
IEnumerator IEnumerable.GetEnumerator()
{
return this.GetEnumerator();
}
private ObservableCollection<TestStep> m_subSteps;
}
I'm not sure if I should/can implement INotifyCollectionChanged here. How can I know if ObservableCollection has been modified?
My question is: how can I get the ListBox to display the changes happening in LogEvents(which is of type ObservableCollection<LogEvent>)?
When theObservableCollection changes, how does the ListBox know? You have to implement like you mentioned INotifyCollectionChanged and then update the ItemSource with the new enumerable data inside the event handler.
ObservableCollection is an INotifyCollectionChanged. Use casting
var collectionChanged = yourObCollection as INotifyCollectionChanged;
if( collectionChanged !=null)
{
collectionChanged.CollectionChanged += YourEventHandler;
}
inside the handler do your own logic to update the item source
Have you considered binding to the original collection but running it through a converter in order to pull out the LogEvents?
The converter should be able to simply return subSteps.SelectMany(s => s.LogEvents).

how to provide change notification for a property when a subproperty changes?

This is such a basic question, but I don't think I've done this before despite having bound so many properties. I originally was planning to bind a class called TimeScale to various objects.
In class A we have a dependency property that I want to call change notification on. However, change notification is not done manually through this class.
public TimeScale AxisTimeScale
{
get { return (TimeScale)GetValue(AxisTimeScaleProperty); }
set { SetValue(AxisTimeScaleProperty, value); }
}
public static readonly DependencyProperty AxisTimeScaleProperty =
DependencyProperty.Register("AxisTimeScale",
typeof(TimeScale), typeof(SignalPanel),
new FrameworkPropertyMetadata(new TimeScale()));
this binds to source class B
private class B
{
private TimeScale _GraphTimeScale;
public TimeScale GraphTimeScale
{
get { return _GraphTimeScale; }
set
{
if (value != _GraphTimeScale)
{
_GraphTimeScale = value;
OnPropertyChanged("GraphTimeScale");
}
}
}
}
Looking at it again I guess all I really want is to call propertychanged on a dependency property, but since I didn't implement Inotifypropertychanged, I am wondering how i do that.
I think DependencyObject already implements Inotifypropertychanged, so I have access to this:
OnPropertyChanged(new DependencyPropertyChangedEventArgs(property, old value, new value));
However, inserting the same object into both the old value and new value slots results in the PropertyChanged event not firing (I assume the implementation checks whether the two values are the same before firing the event). I want to avoid creating a new object if possible. I guess one option is to override OnPropertyChanged. Nope that also requires me to have a dependency propertychanged event args.
Update
OnPropertyChanged("TimeScale");
to
OnPropertyChanged("GraphTimeScale");
Or,
you can wrap the TimeScale class with an ObservableObject so that you can subscribe to object change events and raise them from there.
More info: http://msdn.microsoft.com/en-us/library/ff653818.aspx
Subscribe to the PropertyChanged notification of NumberOfUnits, and then raise OnPropertyChanged("GraphTimeScale") in the property changed event handler.
Would be interested if there is a better way though.

Categories

Resources