Execute specified function every defined date and time

Sarah 186 Reputation points
2022-06-07T10:45:22.717+00:00

I've been trying to implement a "CreateReport" function for a few weeks now. Unfortunately I did not succeed.
The function should be called on a certain date and time (every month on the last Friday at 5pm) and generate the data (if the app is not running, the date and time should be checked at the next startup and the function should be called).
I have posted the question here before. But unfortunately I could not implement the suggested solution of @Peter Fleischer (former MVP) and @Hui Liu-MSFT . I lack the experience to program something like this(I started learning C# and WPF few months ago).
Because I use the MVVM pattern for my app, it has become even more difficult to implement the function. Searching in google didn't help me either.

Can someone show a small demo on how I can implement the function using MVVM?
For the experienced and experts of C# it should be a simple thing I think! Thank you!

Windows Presentation Foundation
Windows Presentation Foundation
A part of the .NET Framework that provides a unified programming model for building line-of-business desktop applications on Windows.
2,670 questions
{count} votes

2 answers

Sort by: Most helpful
  1. Peter Fleischer (former MVP) 19,231 Reputation points
    2022-06-08T09:39:15.137+00:00

    Hi Sarah,
    try following demo. In this demo is only one event for one Report:

    <Window x:Class="WpfApp1.Window024"  
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"  
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"  
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"  
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"  
            xmlns:local="clr-namespace:WpfApp024"  
            mc:Ignorable="d"  
            Title="Window024" Height="450" Width="800">  
      <Window.DataContext>  
        <local:ViewModel/>  
      </Window.DataContext>  
        <Grid>  
        <ListBox ItemsSource="{Binding View}"/>  
      </Grid>  
    </Window>  
      
    

    ----------

    using System;  
    using System.Collections.ObjectModel;  
    using System.IO;  
    using System.Threading;  
    using System.Windows;  
    using System.Xml;  
    using System.Xml.Serialization;  
      
    namespace WpfApp024  
    {  
      public class ViewModel  
      {  
        /// <summary>  
        /// constructor: initiate object for checking next time  
        /// </summary>  
        public ViewModel()  
        {  
          TimeModel tm = new TimeModel();  
          tm.NextReport += Tm_NextReport;  
        }  
      
        /// <summary>  
        /// Action to execute time event  
        /// </summary>  
        private void Tm_NextReport(object? sender, EventArgs e) => View.Add(DateTime.UtcNow.ToLongTimeString());  
      
        /// <summary>  
        /// for demo purposes  
        /// </summary>  
        public ObservableCollection<string> View { get; set; } = new ObservableCollection<string>();  
      }  
      
      public class TimeModel : IDisposable  
      {  
        public event EventHandler<EventArgs> NextReport;  
        private SynchronizationContext? sc = SynchronizationContext.Current;  
        Timer t = null;  
      
        public TimeModel() => NextEvent();  
      
        private void NextEvent()  
        {  
          DateTime nextTime = NextTimeData.NextTime;  
          if (nextTime < DateTime.UtcNow) RaiseEvent(null);  
          else  
          {  
            double waitTime1 = nextTime.Subtract(DateTime.UtcNow).TotalMilliseconds;  
            int waitTime2 = 24 * 3600 * 1000; // wait 1 day maximum  
            if (waitTime1 < waitTime2) waitTime2 = (int)waitTime1;  
            t = new Timer(RaiseEvent, t, waitTime2, Timeout.Infinite);  
          }  
        }  
      
        private void RaiseEvent(object? state)  
        {  
          var t = state as Timer;  
          if (t != null) t.Dispose(); // stop timer if exists  
          DateTime nextTime = NextTimeData.NextTime;  
          double waitTime1 = nextTime.Subtract(DateTime.UtcNow).TotalMilliseconds;  
          if (waitTime1 <= 0)  
          {  
            sc?.Send(new SendOrPostCallback((o) => NextReport?.Invoke(this, new EventArgs())), null);  
            NextTimeData.NextTime = GetNextTime();  
          }  
          NextEvent();  
        }  
      
        private DateTime GetNextTime()  
        {  
          var dt = DateTime.UtcNow;  
          return dt.AddSeconds(3);  
        }  
      
        private TimeData _nextTimeData;  
        private TimeData NextTimeData  
        {  
          get  
          {  
            if (_nextTimeData == null)  
            {  
              _nextTimeData = new TimeData() { NextTime = new DateTime(1, 1, 1) };  
              if (File.Exists(SavedTimePath))  
                using (XmlReader rdr = XmlReader.Create(SavedTimePath)) _nextTimeData = (TimeData)(new XmlSerializer(typeof(TimeData))).Deserialize(rdr);  
            }  
            return _nextTimeData;  
          }  
          set  
          {  
            _nextTimeData = value;  
            using (XmlWriter wrt = XmlWriter.Create(SavedTimePath)) new XmlSerializer(value.GetType()).Serialize(wrt, value);  
          }  
        }  
      
        internal string SavedTimePath  
        {  
          get  
          {  
            var p0 = Environment.GetFolderPath(Environment.SpecialFolder.ApplicationData);  
            var p1 = "Data.xml";  
            return System.IO.Path.Combine(p0, p1);  
          }  
        }  
      
        private DateTime LoadNextTime()  
        {  
          return DateTime.UtcNow;  
        }  
      
        #region IDisposable  
        private bool _disposed; // To detect redundant calls  
        public void Dispose()  
        {  
          Dispose(true); // Dispose of unmanaged resources.  
          GC.SuppressFinalize(this); // Suppress finalization.  
        }  
        protected virtual void Dispose(bool disposing)  
        {  
          if (_disposed) return; // redundant call  
          t?.Dispose(); // stop timer  
          _disposed = true; // protect against redundant calls  
        }  
        ~TimeModel() => Dispose(false); // in case if Dispose was not called  
        #endregion  
      }  
      
      public class TimeData  
      {  
        public DateTime NextTime { get; set; }  
      }  
    }  
    

  2. Sarah 186 Reputation points
    2022-09-27T11:48:45.74+00:00

    Hi @Peter Fleischer (former MVP) , In my app I have several UserControls. Each UserControl opens via a button. Your implementation works, only when I click CreateReport button(here open a UserControl. where I can configure where the report is saved).
    Is there any way to make your implementation run directly when I start the app?

    0 comments No comments