【问题标题】:Update MainWindow on Timer.Elapsed event在 Timer.Elapsed 事件上更新 MainWindow
【发布时间】:2015-11-02 16:25:00
【问题描述】:

我正在编写一个家庭 WPF 应用程序,它以配置的时间间隔从服务器获取文件。

这是一个基本窗口,带有几个标签。我有以下

  • 开始时间(反映触发“开始”事件的日期时间
  • 持续时间(反映应用运行的时间)
  • 速度(文件的下载速度)

我想每秒更新主窗口上的持续时间,所以我有以下代码来执行此操作(在单独的类“RunDownloader.cs”中)。

    private void StartTickTimer()
    {
        const double interval = 1000;

        if (_tickTimer == null)
        {
            _tickTimer = new Timer
            {
                Interval = interval
            };
            _tickTimer.Elapsed += _ticktimer_Elapsed;
        }

        _tickTimer.Start();
    }

在_ticktimer_Elapsed我调用主窗口中的一个方法_mainWindow.UpdateTicker();

这会执行以下操作。

    public void UpdateTicker()
    {
        var timeStarted = lblTimeStarted.Content.ToString();
        DateTime startTime = DateTime.Parse(timeStarted);
        TimeSpan span = DateTime.Now.Subtract(startTime);

        //ToDo: Output time taken here!
        //lblTimeElapsed.Content =
    }

我有两个问题。

  1. 调用 lblTimeStarted.Content.ToString(); 时出现以下异常;在 UpdateTicker() 中

        "The calling thread cannot access this object because a different thread owns it."
    
  2. 我不太清楚,如何正确显示来自 TimeSpan 的 lblTimeElapsed.Content 的持续时间

提前感谢您的任何回答。 :D

【问题讨论】:

    标签: c# wpf


    【解决方案1】:

    在 WPF 中,您无法从 UI 线程以外的线程更新 UI 对象(在 UI 线程上创建)。
    为了从其他线程(例如计时器线程)更新 UI 控件,您需要使用 Dispatcher 在 UI 线程上运行更新代码。
    这个Question/answer 可能会对您有所帮助,或者您可以通过谷歌搜索“WPF Dispatcher”找到大量信息。
    一个示例调度程序调用 - lamda 代码将被发布以在 UI 线程上运行:

    Dispatcher.BeginInvoke(new Action(() =>
    {
        text_box.AppendText(formated_msg);
        text_box.ScrollToEnd();
    }));
    

    或者,您可以将现有计时器替换为 DispatchTimer - 与您使用的计时器不同,它确保计时器回调位于 UI 线程上:

    使用 DispatcherTimer 而不是 System.Timers.Timer 的原因是 DispatcherTimer 与 Dispatcher 在同一线程上运行,并且可以在 DispatcherTimer 上设置 DispatcherPriority。

    【讨论】:

    • 谢谢您的回答很有效。 UI 现在按预期更新。
    【解决方案2】:
    1. 您的计时器在其自己的线程上运行并从那里调用UpdateTicker() 方法。但是,包括 WPF 在内的大多数 UI 框架都禁止从创建相应控件的线程以外的线程访问 UI 控件(后者通常称为“UI 线程”)。您在这里有两个主要选择:

      • 使用DispatcherTimer。这将在您的 UI 线程上运行并避免任何线程问题,但是由于您的 UpdateTicker() 代码也在此线程上运行,因此您的 UI 在您进行处理时将无响应。这可能是也可能不是问题;如果您所做的只是几个字段/属性更改,那很好。
      • 在您的计时器回调中,使用this.Dispatcher.Invoke()this.Dispatcher.BeginInvoke() 在其他处理完成后调用您的UI 更新方法(例如:this.Dispatcher.Invoke((Action) UpdateTicker))。这将“碰撞”对适当线程的调用以进行 UI 更新,同时保持数据处理的异步性。换句话说,这是一种更有效的方法。
    2. TimeSpan 结构有一个ToString() 接受格式化的方法;或者,如果这不方便,它有几个可用于显示目的的辅助属性(DaysHoursMinutesTotalDaysTotalHoursTotalMinutes 等)。

    【讨论】:

      【解决方案3】:

      你可以这样做: 在主窗口中:

      public void ChangeTime(string time)
              {
                  lblsb.Content = time;
              }
      

      在 RunDownloader 中:

      class RunDownloader
          {
              Timer _tickTimer;
              MainWindow window;
      
              public RunDownloader(MainWindow window)
              {
                  this.window = window;
              }
      
              private delegate void MyDel(string str);
      
              public void StartTickTimer()
              {
                  const double interval = 1000;
      
                  if (_tickTimer == null)
                  {
                      _tickTimer = new Timer
                      {
                          Interval = interval
                      };
                      _tickTimer.Elapsed += (object sender, ElapsedEventArgs e) =>
                          {
                              window.Dispatcher.BeginInvoke(new MyDel(window.ChangeTime), DateTime.Now.ToLongDateString());
                          };
                  }
      
                  _tickTimer.Start();
              }
      
          }
      

      【讨论】:

        猜你喜欢
        • 2013-06-02
        • 1970-01-01
        • 2017-04-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2017-06-18
        • 1970-01-01
        • 2021-11-04
        相关资源
        最近更新 更多