Tezzo
Tezzo

Reputation: 43

Countdown to date in windows phone app

I'm creating a countdown to a specific date. Days left, Hours left and minutes left have to be shown in a textblock.

Made a flat design in XAML, know how to calculate the timespan. Now I'm in need of a live updater while the app is running.

My Xaml

<Grid x:Name="myLayoutGrid"
      Background="CadetBlue" Margin="0,-26.667,0,-0.333"
      >

    <TextBlock x:Name="countDays" 
               HorizontalAlignment="Left" 
               Margin="45,150,0,0" 
               TextWrapping="Wrap" 
               Text="Dagen" 
               VerticalAlignment="Top"
               FontFamily="Tahoma"
               FontSize="34" Loaded="countDays_Loaded"/>
    <TextBlock x:Name="countHours" 
               HorizontalAlignment="Left" 
               Margin="45,200,0,0" 
               TextWrapping="Wrap" 
               Text="Uur" 
               VerticalAlignment="Top" 
               FontFamily="Tahoma"
               FontSize="30" Loaded="countHours_Loaded"/>
    <TextBlock x:Name="countMinutes" 
               HorizontalAlignment="Left"
               Margin="45,250,0,0" 
               TextWrapping="Wrap" 
               Text="Minuten" 
               VerticalAlignment="Top"
               FontFamily="Tahoma"
               FontSize="26" Loaded="countMinutes_Loaded"/>

My code;

private void countDays_Loaded(object sender, RoutedEventArgs e)
    {
        DateTime end = DateTime.Parse("01/01/2016 15:00");
        DateTime start = DateTime.Now;

        TimeSpan ts = end - start;

        countDays.Text = string.Format("{0} Dagen", ts.Days);



    }

    private void countHours_Loaded(object sender, RoutedEventArgs e)
    {
        DateTime end = DateTime.Parse("01/01/2016 15:00");
        DateTime start = DateTime.Now;

        TimeSpan ts = end - start;

        countHours.Text = string.Format("{0} Uur", ts.Hours);

    }

    private void countMinutes_Loaded(object sender, RoutedEventArgs e)
    {

        DateTime end = DateTime.Parse("01/01/2016 15:00");
        DateTime start = DateTime.Now;

        TimeSpan ts = end - start;

        countMinutes.Text = string.Format("{0} Minuten", ts.Minutes);
    }

After i understand the code and why i should use that code, i'd like to clean up my code (put the timer in a class). After that and i studied the HUB app, i will use it in binding element.

Any help would be great.

Upvotes: 1

Views: 438

Answers (2)

Mikko Viitala
Mikko Viitala

Reputation: 8394

You should be using MVVM pattern when ever you develop applications for WP8 using C#/XAML.

This means you create a View (e.g. XAML Window) and then a separated context for it's data, named ViewModel. When ever data is changed you need to notify the view of changes by implementing INotifyPropertyChanged interface.

To make periodic changes you should use Task-based Asynchronous Pattern. This way your UI thread is not blocked. This would be easiest by using a Task.

Of course there's several other ways to do this but this is what I'd recommend.

Then your application could look something like the following.

MainWindow.xaml

<!-- This would be <phone:PhoneApplicationPage in WP8 app -->
<!-- e.g. phone:PhoneApplicationPage x:Class="PhoneApp1.MainPage"-->
<Window x:Class="WpfApplication1.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        xmlns:wpfApplication1="clr-namespace:WpfApplication1"
        Title="MainWindow"
        Height="116"
        Width="250">
    <Window.DataContext>
        <wpfApplication1:MainViewModel />
    </Window.DataContext>

    <StackPanel VerticalAlignment="Center">
        <!-- Bind displayed text to MainViewModel's CountDown property -->
        <!-- This way it automically updates the TextBlock whenever value is changed -->
        <TextBlock Text="{Binding CountDown}" FontSize="24" TextAlignment="Center" />
    </StackPanel>        
</Window>

MainWindow.xaml.cs

// No code added here, this is the initial class structure.
// This needs to be in same namespace as MainWindow/Page XAML (partial)
using System.Windows;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        public MainWindow()
        {
            InitializeComponent();
        }
    }
}

MainViewModel.cs

// This would be plain C# class in WP8, too
using System;
using System.ComponentModel;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;

namespace WpfApplication1
{
    public class MainViewModel : INotifyPropertyChanged
    {
        // This event takes care of notifying the page so it updates
        public event PropertyChangedEventHandler PropertyChanged;
        private string _countDown;

        public MainViewModel()
        {
            // Day to countdown to
            DateTime targetDate = DateTime.Now.AddDays(5d);

            // Start new thread
            Task.Factory.StartNew(() =>
                {
                    // Loop until target date and update value every second
                    while (DateTime.Now <= targetDate)
                    {
                        // Format and set new value
                        CountDown = (targetDate - DateTime.Now).ToString("d'd 'h'h 'm'm 's's'");
                        Thread.Sleep(1000);
                    }
                    // Final value
                    CountDown = "It's tiem!";
                });
        }

        // Value displayed in Page's TextBlock
        public string CountDown
        {
            get { return _countDown; }
            set { _countDown = value; OnPropertyChanged();}
        }

        // This is INotifyPropertyChanged implementation
        protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
        {
            var handler = PropertyChanged;
            if (handler != null) 
                handler(this, new PropertyChangedEventArgs(propertyName));
        }
    }
}

This gives you counter looking like below, which updates once per second and does not freeze UI.

enter image description here (Example is for .NET 4.5 WPF application but it should be 99%...100% same for Windows Phone apps).

Instead of simple while loop you could take the longer road and replace it with a timer, if you wish, by replacing the Task.Factory.StartNew block with

var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(1) };
timer.Tick += (sender, args) =>
    {
        DateTime now = DateTime.Now;
        int difference = (int) (targetDate - now).TotalSeconds;
        CountDown = difference >= 0 ? 
                    (targetDate - now).ToString("d'd 'h'h 'm'm 's's'") : 
                    "It's tiem!";
        if (difference < 0)
            timer.Stop();
    };
timer.Start();

Upvotes: 1

Jan K&#246;hler
Jan K&#246;hler

Reputation: 6032

You'll need some sort of timer to periodically refresh the remaining time. Try the follwoing approach:

public partial class MainWindow : Window
{
    private readonly DateTime _endDate;
    private readonly DispatcherTimer _timer;

    public MainWindow()
    {
        InitializeComponent();

        _endDate = new DateTime(2016, 1, 1, 15, 0, 0);
        _timer = new DispatcherTimer();
        _timer.Tick += CountDown;
        _timer.Interval = TimeSpan.FromMinutes(1);
        _timer.Start();
    }

    private void CountDown(object sender, EventArgs e)
    {
        var remainingTime = _endDate.Subtract(DateTime.Now);

        countDays.Text = string.Format("{0} Dagen", remainingTime.Days);
        countHours.Text = string.Format("{0} Uur", remainingTime.Hours);
        countMinutes.Text = string.Format("{0} Minuten", remainingTime.Minutes);
    }
}

To make the code compile remove the Loaded="countX_Loaded" eventhandlers from your textblocks in your XAML.

<Window x:Class="Stackoverflow28009341.MainWindow"
        xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
        xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
        Title="MainWindow" Height="350" Width="525">
    <Grid x:Name="myLayoutGrid" Background="CadetBlue" Margin="0,-26.667,0,-0.333">
        <TextBlock x:Name="countDays" 
               HorizontalAlignment="Left" 
               Margin="45,150,0,0" 
               TextWrapping="Wrap" 
               Text="Dagen" 
               VerticalAlignment="Top"
               FontFamily="Tahoma"
               FontSize="34"/>
        <TextBlock x:Name="countHours" 
               HorizontalAlignment="Left" 
               Margin="45,200,0,0" 
               TextWrapping="Wrap" 
               Text="Uur" 
               VerticalAlignment="Top" 
               FontFamily="Tahoma"
               FontSize="30"/>
        <TextBlock x:Name="countMinutes" 
               HorizontalAlignment="Left"
               Margin="45,250,0,0" 
               TextWrapping="Wrap" 
               Text="Minuten" 
               VerticalAlignment="Top"
               FontFamily="Tahoma"
               FontSize="26"/>
    </Grid>
</Window>

Upvotes: 0

Related Questions