7

The only way to show current time updating regularly I found is to use timer. Of course, I can implement INotifyPropertyChanged and some special property to be used on UI but this implementation AFAIK also needs Timer. For example like here. Are there any better way to display current time?

Edit

To clarify: are there any declarative way to make it work in real time without timer using XAML syntax like this?

<Label Content="{x:Static s:DateTime.Now}" ContentStringFormat="G" />

Nothing stops me from using timer here. I just want to know if there are more elegant and compact way of implementing this.

Vadim Ovchinnikov
  • 13,327
  • 5
  • 62
  • 90

3 Answers3

14

Using Task.Delay can produce an high CPU usage!

In the XAML code write this:

<Label Name="LiveTimeLabel" Content="%TIME%" HorizontalAlignment="Left" Margin="557,248,0,0" VerticalAlignment="Top" Height="55" Width="186" FontSize="36" FontWeight="Bold" Foreground="Red" />

Next in the xaml.cs write this:

[...]
public MainWindow()
{
    InitializeComponent();
    DispatcherTimer LiveTime = new DispatcherTimer();
    LiveTime.Interval = TimeSpan.FromSeconds(1);
    LiveTime.Tick += timer_Tick;
    LiveTime.Start();
}

void timer_Tick(object sender, EventArgs e)
{
    LiveTimeLabel.Content = DateTime.Now.ToString("HH:mm:ss");
}
[...]
Marco Concas
  • 1,665
  • 20
  • 25
  • I have reworked my answer to remove the high CPU usage. Task.Delay isn't the culprit for high CPU usage, the issue is not awaiting the task that the Delay method produces. See my updated answer for reference. – aydjay Sep 12 '19 at 14:51
1

Here is a little code sample that works without a timer:

public DateTime CurrentTime
{
    get => DateTime.Now;
}

public CurrentViewModelTime(object sender, RoutedEventArgs e)
{
    _ = Update(); // calling an async function we do not want to await
}

private async Task Update()
{
    while (true)
    {
        await Task.Delay(100);
        OnPropertyChanged(nameof(CurrentTime)));
    }
}

Of course, this Update() function never returns, but it does its loop on a threadpool thread and does not even block any thread for long.

You can perfectly also implement this in the window directly without a viewmodel.

J S
  • 901
  • 8
  • 15
0

WPF is a static markup language. As far as I am aware there is not a mechanism available in pure XAML to provide the functionally you are looking for.

If you want to avoid using a timer directly you can abstract it away using Tasks.

MainWindow XAML:

<Window x:Class="AsyncTimer.MainWindow"
        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:AsyncTimer"
        mc:Ignorable="d"
        Title="MainWindow" Height="350" Width="525">
    <Grid>
        <Label Content="{Binding CurrentTime}"></Label>
    </Grid>
</Window>

Code behind:

public partial class MainWindow : Window
{
    public MainWindow()
    {
        InitializeComponent();

        DataContext = new CurrentTimeViewModel();
    }
}

public class CurrentTimeViewModel : INotifyPropertyChanged
{
    private string _currentTime;

    public CurrentTimeViewModel()
    {
        UpdateTime();
    }

    private async void UpdateTime()
    {
        CurrentTime = DateTime.Now.ToString("G");
        await Task.Delay(1000);
        UpdateTime();
    }

    public event PropertyChangedEventHandler PropertyChanged;

    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }

    public string CurrentTime
    {
        get { return _currentTime; }
        set { _currentTime = value; OnPropertyChanged(); }
    }
}

This is probably one of the more succinct and certainly as "Modern" WPF you are going to get.

aydjay
  • 858
  • 11
  • 25
  • 1
    Task.Delay() does not cause the current thread to wait for the end of the delay. Task.Delay() returns a Task, and if you really want to pause that long, you have to wait for completion of the task. Because your code example doesn't do that, it spins way too fast, running iteration after iteration with no pause, which is the reason for the below answer "Using Task.Delay can produce an high CPU usage!". For that reason, this solution is actually much more involved and costly than using a Timer. – J S Sep 09 '19 at 04:17
  • Thanks for the comments about the high CPU usage, I have reworked the answer to remove the high load on the CPU. – aydjay Sep 10 '19 at 07:46
  • 1
    OK, but now you have gotten yourself into an infinite recursion. That will throw a StackOverflowException at some point, but because you are only recurse once every second, that might take a while, so you might not notice. – J S Feb 01 '20 at 08:31
  • Clarification: my last comment was referring to a version of the code where Update() is async and calls itself recursively after awaiting the Delay. The compiler should tell you something is wrong here because when Update() is called, it is not awaited, that means the whole thing runs synchronously. But even if you did, it would still have the problem of the infinite recursion. – J S Feb 01 '20 at 08:55