【问题标题】:WPF sometimes ignores PropertyChanged eventWPF 有时会忽略 PropertyChanged 事件
【发布时间】: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。

标签: c# wpf .net-core


【解决方案1】:

就像提到的 cmets 一样,切换到 DispatcherTimer 会给您带来更好的结果,因为它的 Tick 事件会在 UI 线程上触发。 System.Timer 在不同的线程池中运行,可能会导致问题。

无论如何,您的示例将在调试模式下运行得更粗略一些,因为您在每次滴答时都会写入输出窗口。

要实现 DispatcherTimer,请使用以下更改更新您的代码:

private DispatcherTimer _uiUpdateTimer;

...

_uiUpdateTimer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(100) };
_uiUpdateTimer.Tick += OnTick;

...

if(_uiUpdateTimer.IsEnabled && _sessionState.HasValue)
{
    ... Your code goes here
}

...

private void OnTick(object sender, EventArgs e)
{
    ... Your code goes here
}

我希望这会有所帮助。

【讨论】:

    【解决方案2】:

    单独使用System.Windows.Threading.DispatcherTimer 是不够的,我还必须指定一个高优先级(在构造函数中),以便 UI 能够正常更新。

    _uiUpdateTimer = new DispatcherTimer(DispatcherPriority.Normal);
    _uiUpdateTimer.Interval = new TimeSpan(0, 0, 0, 0, 100);
    _uiUpdateTimer.Tick += UiUpdateTimer_Elapsed;
    

    【讨论】:

      猜你喜欢
      • 2010-10-23
      • 2010-11-23
      • 2013-03-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-11-26
      • 1970-01-01
      相关资源
      最近更新 更多