【发布时间】:2020-01-16 02:38:46
【问题描述】:
我有一个具有秒表功能的桌面应用程序。我想显示自从我开始跟踪时间以来经过的时间,并用经过的时间更新 UI。我有一个后台计时器,它会在经过的时间内定期提高PropertyChanged,并且用户界面会更新。
问题在于大部分时间 UI 不会更新。我有100ms间隔的计时器,偶尔它会每秒更新几次,通常它会每3-5秒更新一次。有时在 UI 更新前大约需要 25 秒。如果我拖动窗口,UI 在拖动时总是正确更新(我没有任何用于拖动或单击的事件处理程序)。
这是 Windows 上的 dotnet core 3.1。这是精简的项目。
.csproj:
<Project Sdk="Microsoft.NET.Sdk.WindowsDesktop">
<PropertyGroup>
<OutputType>WinExe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
<UseWPF>true</UseWPF>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<DocumentationFile></DocumentationFile>
</PropertyGroup>
</Project>
MainWindow.xaml
<Window x:Class="SkillTracker.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:i="http://schemas.microsoft.com/xaml/behaviors"
mc:Ignorable="d"
Name="TheWindow"
Title="Skill Tracker" Height="200" Width="350">
<Window.Resources>
</Window.Resources>
<Grid>
<StackPanel>
<TextBlock >Total time:</TextBlock>
<TextBlock FontFamily="Lucida Console" Text="{Binding TotalTimeElapsed, UpdateSourceTrigger=Explicit}"></TextBlock>
<TextBlock >Session time:</TextBlock>
<TextBlock FontFamily="Lucida Console" Text="{Binding CurrentSessionTimeElapsed}"></TextBlock>
<Button Command="{Binding StartStopArtCommand}" Content="button" ></Button>
</StackPanel>
</Grid>
</Window>
MainWindow.xaml.cs
using System;
using System.ComponentModel;
using System.Timers;
using System.Windows;
using System.Windows.Input;
namespace SkillTracker
{
public abstract class ViewModelBase : INotifyPropertyChanged
{
public event PropertyChangedEventHandler PropertyChanged;
public void OnPropertyChanged(string propertyName) => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
public class CommandHandler : ICommand
{
private Action _action;
public event EventHandler CanExecuteChanged;
public CommandHandler(Action action)
{
_action = action;
}
public bool CanExecute(object parameter) => true;
public void Execute(object parameter) => _action();
}
public class MainViewModel : ViewModelBase
{
private System.Timers.Timer _uiUpdateTimer;
private DateTime? _sessionStart;
private int _totalDurationSeconds;
public MainViewModel()
{
_totalDurationSeconds = 3;
StartStopArtCommand = new CommandHandler(StartStopArtCommandAction);
_uiUpdateTimer = new System.Timers.Timer();
_uiUpdateTimer.Interval = 100;
_uiUpdateTimer.AutoReset = true;
_uiUpdateTimer.Elapsed += UiUpdateTimer_Elapsed;
}
public ICommand StartStopArtCommand { get; private set; }
public string CurrentSessionTimeElapsed
{
get
{
if (_uiUpdateTimer.Enabled && _sessionStart.HasValue)
{
var ts = DateTime.Now - _sessionStart.Value;
var hours = (ts.Days * 24) + ts.Hours;
return $"{hours:000}:{ts.Minutes:00}:{ts.Seconds:00}.{ts.Milliseconds:000}";
}
else
{
return string.Empty;
}
}
}
public string TotalTimeElapsed
{
get
{
if (_sessionStart.HasValue)
{
var ts = DateTime.Now.AddSeconds(_totalDurationSeconds) - _sessionStart.Value;
var hours = (ts.Days * 24) + ts.Hours;
return $"{hours:000}:{ts.Minutes:00}:{ts.Seconds:00}.{ts.Milliseconds:000}";
}
else
{
return string.Empty;
}
}
}
private void StartStopArtCommandAction()
{
_sessionStart = DateTime.Now;
_uiUpdateTimer.Start();
// trigger initial update.
OnPropertyChanged(nameof(TotalTimeElapsed));
OnPropertyChanged(nameof(CurrentSessionTimeElapsed));
}
private void UiUpdateTimer_Elapsed(object sender, ElapsedEventArgs e)
{
System.Diagnostics.Debug.WriteLine(DateTime.Now.ToString("HH:mm:ss.ffff"));
OnPropertyChanged(nameof(CurrentSessionTimeElapsed));
OnPropertyChanged(nameof(TotalTimeElapsed));
}
}
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
DataContext = new MainViewModel();
}
}
}
这是未更新的 GIF 动画。你可以看到UiUpdateTimer_Elapsed方法被调用,它在右边的调试窗口中打印当前的DateTime。
【问题讨论】:
-
可能是线程问题。对于 UI,最好使用 DispatcherTimer。