【问题标题】:Show current time WPF显示当前时间 WPF
【发布时间】:2018-12-04 23:11:23
【问题描述】:

我发现定期显示当前时间更新的唯一方法是使用计时器。当然,我可以实现INotifyPropertyChanged 和一些在 UI 上使用的特殊属性,但是这个实现 AFAIK 还需要Timer。例如here。有没有更好的方法来显示当前时间?

编辑

澄清一下:是否有任何声明性方式可以使用这样的 XAML 语法使其实时无需计时器

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

没有什么能阻止我在这里使用计时器。我只是想知道是否有更优雅和更紧凑的实现方式。

【问题讨论】:

  • 带有 Tick 处理程序的 DispatcherTimer,可直接更新某些 TextBlock。不需要视图模型。
  • 你的意思是在哪个维度上“更好”?
  • @YurySchkatula 只是想避免使用计时器。也许是声明性的。
  • @Clemens 是的,但您不会在 XAML 中使用声明性字符串格式或其他表示形式。
  • 没有计时器应该如何工作?

标签: c# wpf xaml real-time current-time


【解决方案1】:

使用 Task.Delay 会产生高 CPU 使用率!

在 XAML 代码中这样写:

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

接下来在 xaml.cs 中这样写:

[...]
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");
}
[...]

【讨论】:

  • 我已经修改了我的答案以消除高 CPU 使用率。 Task.Delay 不是高 CPU 使用率的罪魁祸首,问题不在于等待 Delay 方法产生的任务。请参阅我的更新答案以供参考。
【解决方案2】:

WPF 是一种静态标记语言。据我所知,纯 XAML 中没有可用的机制来提供您正在寻找的功能。

如果您想避免直接使用计时器,可以使用任务将其抽象出来。

主窗口 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>

后面的代码:

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(); }
    }
}

这可能是您将获得的更简洁且肯定是“现代” WPF 之一。

【讨论】:

  • Task.Delay() 不会导致当前线程等待延迟结束。 Task.Delay() 返回一个任务,如果你真的想暂停那么久,你必须等待任务完成。因为您的代码示例没有这样做,所以它旋转得太快了,一次又一次地运行迭代而没有暂停,这就是下面回答“使用 Task.Delay 会产生高 CPU 使用率!”的原因。出于这个原因,这个解决方案实际上比使用定时器更复杂且成本更高。
  • 感谢 cmets 关于 CPU 使用率高的问题,我已经修改了答案以消除 CPU 上的高负载。
  • 好的,但是现在你已经让自己陷入了无限递归。这会在某些时候抛出 StackOverflowException,但因为你每秒只递归一次,这可能需要一段时间,所以你可能不会注意到。
  • 澄清:我的最后一条评论是指 Update() 是异步的代码版本,并在等待延迟后递归调用自身。编译器应该告诉你这里出了点问题,因为当 Update() 被调用时,它没有被等待,这意味着整个事情是同步运行的。但即使你这样做了,它仍然存在无限递归的问题。
【解决方案3】:

这是一个无需计时器即可工作的小代码示例:

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)));
    }
}

当然,这个 Update() 函数永远不会返回,但它会在线程池线程上循环,甚至不会长时间阻塞任何线程。

你也可以完美地直接在窗口中实现这一点,无需视图模型。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-11-28
    • 2020-06-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-11
    相关资源
    最近更新 更多