Search code examples
c#uwpmvvm-lightfody-propertychanged

UWP app not updating view


A simple exercise: display the current time in a Textblock in an UWP app. I'm using MVVMlight and PropertyChanged.Fody.

As a base for this example I'm using this Article 1 article and the MVVMlight / Fody implementation from here: Article 2

I've got a MainViewModel. Here I'm creating an instance of the DateTimeModel class and I've already added a Debug output if the property changed event is raised (working).

using System.Diagnostics;
using GalaSoft.MvvmLight;
using Logic.Ui.Models.DateTime;
using PropertyChanged;

namespace Logic.Ui
{
    public class MainViewModel : ViewModelBase, INotifyPropertyChanged
    {

        public DateTimeModel DateTimeModel;

        [DependsOn(nameof(DateTimeModel))]
        public DateTime CurrentDateTime => DateTimeModel.CurrentDateTime;

        public MainViewModel()
        {
            DateTimeModel = new DateTimeModel();

            DateTimeModel.PropertyChanged += (s, e) =>
            {
                Debug.WriteLine("DateTime PropertyChanged");
            };
        }

        #region Events

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

    }
}

And a DateTimeModel class where I update the Time using a ThreadPoolTimer:

using System;
using System.ComponentModel;
using System.Diagnostics;
using Windows.System.Threading;
using Windows.UI.Core;

namespace Logic.Ui.Models.DateTime
{

    public class DateTimeModel : INotifyPropertyChanged
    {
        private ThreadPoolTimer _clockTimer;

        public System.DateTime CurrentDateTime { get; set; }
        
        public DateTimeModel()
        {
            _clockTimer = ThreadPoolTimer.CreatePeriodicTimer(ClockTimerTickAsync, TimeSpan.FromMilliseconds(1000));
        }

        private async void ClockTimerTickAsync(ThreadPoolTimer timer)
        {
            await Windows.ApplicationModel.Core.CoreApplication.MainView.CoreWindow.Dispatcher.RunAsync(CoreDispatcherPriority.Normal, () =>
            {
                CurrentDateTime = System.DateTime.Now;
                Debug.WriteLine("Time updated");
            });

        }

        #region Events

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }
}

The XAML code looks like this:

<Page
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    xmlns:local="using:MirrorV2.Ui.Raspberry"
    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
    x:Class="MirrorV2.Ui.Raspberry.MainPage"
    mc:Ignorable="d"
    DataContext="{Binding Main, Source={StaticResource Locator}}">

    <Grid Background="{ThemeResource ApplicationPageBackgroundThemeBrush}">

        <TextBlock Text="{Binding CurrentDateTime}"/>
        
    </Grid>
</Page>

The problem here is that the UI is not updated, while the propertyChanged events are beeing raised. What I'm missing here?

EDIT: If I'm using CurrentDateTime as a standard property:

public DateTime CurrentDateTime { get; set; }

and assigning the current DateTime in the constructor the binding works.

CurrentDateTime = System.DateTime.Now;

Solution

  • Problem you're facing is MainViewModel.CurrentDateTime only gets notified when you assign MainViewModel.DateTimeModel, not when DateTimeModel's properties change.

    This is a known Fody limitation and a guy here found a walkaround that allows you to notify on subproperty changes like so:

    public class MainViewModel : ViewModelBase, INotifyPropertyChanged
    {
    
        // ... snip ...
    
        [DependsOn(nameof(DateTimeModel))]
        [DependsOn("DateTimeModel.CurrentDateTime")]
        public DateTime CurrentDateTime => DateTimeModel.CurrentDateTime;
    }
    

    But I think it's far more elegant to drop the MainViewModel.CurrentDateTime and bind to MainViewModel.DateTimeModel directly

    <TextBlock Text="{Binding DateTimeModel.CurrentDateTime}"/>
    

    This requires changing DateTimeModel to property as suggested by mm8:

    public DateTimeModel DateTimeModel { get; }