0
votes

So I have an ObservableCollection<Term> on a view model, with Term items having some data properties.

Term class:

using SQLite;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace Neomilano.Model
{
    public class Term
    {
        public int ID { get; set; }

        public string Name { get; set; }

        public DateTimeOffset TermStart { get; set; }

        public DateTimeOffset TermEnd { get; set; }

        public string DurationText 
        {
            get 
            {
                return "from " + TermStart.Date.ToString("MMMM d, yyyy") + " to " + TermEnd.Date.ToString("MMMM d, yyyy");
            }        
        }

        public Term()
        {
        }
    }
}

This is the ViewModel in question that has the ObservableCollection (ProjectBaseViewModel implements MVVM Light's ViewModelBase and has few properties that I don't think is necessary here):

using GalaSoft.MvvmLight.Command;
using GalaSoft.MvvmLight.Threading;
using Microsoft.Practices.ServiceLocation;
using Neomilano.Model;
using Neomilano.Services;
using SQLite;
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Input;
using Windows.Foundation;


namespace Neomilano.ViewModel
{
    public partial class TermsViewModel : ProjectBaseViewModel
    {

        public IDialogService DialogService
        {
            get { return ServiceLocator.Current.GetInstance<IDialogService>(); }
        }

#region properties
        private ObservableCollection<Term> _terms;
        public ObservableCollection<Term> Terms {
            get { return this._terms; }
            private set { Set(() => Terms, ref _terms, value); }
        }
#endregion

#region commands

        private RelayCommand<int> _deleteTermCommand;
        public RelayCommand<int> DeleteTermCommand
        {
            get 
            {
                return _deleteTermCommand ??
                    (_deleteTermCommand = new RelayCommand<int>(async id => {
                        var t = "Are you sure you want to delete this term?";
                        var c = "This cannot be undone.";
                        var ct = "delete";
                        bool deleteConfirmed = await DialogService.ShowConfirmationDialogAsync(t, c, ct);

                        if (deleteConfirmed == true)
                            await ExecuteDeleteTermAsync(id);
                    }));
            }
        }

#endregion

        public TermsViewModel()
        {
            if (IsInDesignMode)
            {
                List<Term> t = new List<Term>();

                t.Add(new Term() { ID=1, Name = "Sample Term 1", TermStart = DateTimeOffset.Parse("October 1, 2013"), TermEnd = DateTimeOffset.Parse("December 17, 2013") });
                t.Add(new Term() { ID=2, Name="Sample Term 2", TermStart=DateTimeOffset.Parse("January 1, 2014"), TermEnd=DateTimeOffset.Parse("April 30, 2014") });

                Terms = new ObservableCollection<Term>(t);
            }

        }

        /// <summary>
        /// Gets the list of Terms from the database and adds it to the Terms property of the ViewModel.
        /// </summary>
        /// <returns></returns>
        public async Task GetTermsListAsync()
        {
            IsProgressEnabled = true;

            List<Term> list = new List<Term>();
            await Task.Run(() =>
            {
                using (var db = new SQLiteConnection(app.DBFileName))
                {
                    list = db.Table<Term>().ToList<Term>();
                    db.Close();
                }
            });
            Terms = new ObservableCollection<Term>(list);

            IsProgressEnabled = false;
        }

        /// <summary>
        /// Returns the term ID of the selected term from the view.
        /// </summary>
        /// <param name="clickedItem"></param>
        /// <returns></returns>
        public Term ReturnSelectedTerm(object clickedItem)
        {
            return (Term)clickedItem;
        }

        public async Task ExecuteDeleteTermAsync(int termId)
        {
            IsProgressEnabled = true;

            Term t = new Term();
            await Task.Run(() =>
            {
                using (var db = new SQLiteConnection(app.DBFileName))
                {
                    var q = db.Table<Term>().Where(tr => tr.ID.Equals(termId));
                    t = q.First<Term>();

                    db.Delete<Term>(termId);
                    db.Close();
                }
            });

            var target = Terms.Single<Term>(tr => tr.ID.Equals(termId));
            Terms.Remove(target);

            IsProgressEnabled = false;
        }
    }
}

THE ISSUE: Sure, adding and removing terms from another page work well. When the Term items get updated, however, only the title gets updated.

Here's what's on the view:

            <ListView 
                x:Name="TermsListView"
                ItemsSource="{Binding Terms}" 
                IsItemClickEnabled="True"
                ItemClick="OnTermItemClick">
                <ListView.ItemTemplate>
                    <DataTemplate>
                        <StackPanel Holding="OnTermItemHold">
                            <FlyoutBase.AttachedFlyout>
                                <MenuFlyout x:Name="TermItemContextMenu">
                                    <MenuFlyoutItem Text="edit" Command="{Binding TermsViewModel.NavigateToFormCommand, Mode=OneWay, Source={StaticResource Locator}}" CommandParameter="{Binding}" />
                                    <MenuFlyoutItem Text="delete" Command="{Binding TermsViewModel.DeleteTermCommand, Mode=OneWay, Source={StaticResource Locator}}" CommandParameter="{Binding ID}" />
                                </MenuFlyout>
                            </FlyoutBase.AttachedFlyout>
                            <TextBlock Text="{Binding Name}"  Style="{ThemeResource ListViewItemTextBlockStyle}"/>
                            <TextBlock Text="{Binding DurationText}" Style="{ThemeResource ListViewItemSubheaderTextBlockStyle}"/>
                        </StackPanel>
                    </DataTemplate>
                </ListView.ItemTemplate>
            </ListView>

Term.DurationText does not update when the two date values have changed. If the edit page is to be seen however, the change is reflected in the DatePickers.

Is there any way that I can make DurationText update accordingly through Binding in such a way that it works with an implementation using MVVM Light?

I have tried this but the error says:

The type 'Neomilano.Model.Term' cannot be used as type parameter 'T' in the generic type or method 'Neomilano.Common.ItemsChangeObservableCollection'. There is no implicit reference conversion from 'Neomilano.Model.Term' to 'System.ComponentModel.INotifyPropertyChanged'.

Project specs: * This is a WinRT app * It's a universal Windows App, but I'm working on Windows Phone first right now. I think this doesn't matter because of the convergence bet. WinRT and WinPRT, but who knows this might be the problem * As I have mentioned, I'm using MVVM Light(libraries only).

So, in general, how can I update the contents of an item of an ObservableCollection in WinRT using MVVM Light?

Thank you for your help!

1

1 Answers

3
votes

You don't need anything in the linked article, you simply need Term to impllement INotifyPropertyChanged. ObservableCollection<T> only notifies the ListView bound to it that the content of the collection has changed, i.e. it notifies add, remove, move, replace, reset etc. It doesn't monitor the items within the collection for changes.

In this case, you can make Term derive from ViewModelBase, as this contains an implementation of INotifyPropertyChanged.

An example is below. Note how the setter for TermStart also raises a change event for DurationText, as this property is dependent.

public class Term : ViewModelBase
{
    private int _id;
    private string _name;
    private DateTimeOffset _termStart;
    private DateTimeOffset _termEnd;

    public int Id
    {
        get { return _id; }
        set
        {
            if (value == _id) return;
            _id = value;
            RaisePropertyChanged("Id");
        }
    }

    public string Name
    {
        get { return _name; }
        set
        {
            if (value == _name) return;
            _name = value;
            RaisePropertyChanged("Name");
        }
    }

    public DateTimeOffset TermStart
    {
        get { return _termStart; }
        set
        {
            if (value.Equals(_termStart)) return;
            _termStart = value;
            RaisePropertyChanged("TermStart");
            RaisePropertyChanged("DurationText");
        }
    }

    public DateTimeOffset TermEnd
    {
        get { return _termEnd; }
        set
        {
            if (value.Equals(_termEnd)) return;
            _termEnd = value;
            RaisePropertyChanged("TermEnd");
            RaisePropertyChanged("DurationText");
        }
    }

    public string DurationText
    {
        get { return "from " + TermStart.Date.ToString("MMMM d, yyyy") + " to " + TermEnd.Date.ToString("MMMM d, yyyy"); }
    }
}