我正在编写一个家庭WPF应用程序,它以一个配置的间隔从服务器获取文件。
这是一个基本窗口,有几个标签。我有以下
我想每秒更新主窗口上的持续时间,所以我有以下代码来执行此操作(在单独的类“RunDownloader.cs”中)。
private void StartTickTimer()
{
const double interval = 1000;
if (_tickTimer == null)
{
_tickTimer = new Timer
{
Interval = interval
};
_tickTimer.Elapsed += _ticktimer_Elapsed;
}
_tickTimer.Start();
}
On _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 =
}
我有两个问题。
调用lblTimeStarted.Content.ToString()时出现以下异常;在UpdateTicker()
中 "The calling thread cannot access this object because a different thread owns it."
我不知道如何正确显示来自TimeSpan的lblTimeElapsed.Content的持续时间
提前感谢您的回答。 :d
答案 0 :(得分:5)
在WPF中,您无法从UI线程以外的线程更新UI对象(在UI线程上创建)。
要从其他某个线程(例如计时器线程)更新UI控件,您需要使用Dispatcher在UI线程上运行更新代码。
这Question/answer可以帮助您,或者您可以通过Google搜索“WPF Dispatcher”找到大量信息
一个示例调度程序调用 - lamda代码将被发布以在UI线程上运行:
Dispatcher.BeginInvoke(new Action(() =>
{
text_box.AppendText(formated_msg);
text_box.ScrollToEnd();
}));
或者你可以用DispatchTimer替换现有的计时器 - 与你使用它的计时器不同,确保计时器回调在UI线程上:
使用与System.Timers.Timer相对的DispatcherTimer的原因是DispatcherTimer在与Dispatcher相同的线程上运行,并且可以在DispatcherTimer上设置DispatcherPriority。
答案 1 :(得分:1)
您的计时器正在自己的线程上运行,并从那里调用UpdateTicker()
方法。但是,大多数UI框架(包括WPF)都禁止从创建相应控件的线程以外的线程访问UI控件(后者通常表示为“UI线程”)。这里有两个主要选项:
DispatcherTimer
。这将在您的UI线程上运行并避免任何线程问题,但是,由于您的UpdateTicker()
代码也在此线程上运行,因此在您进行处理时,您的UI将无响应。这可能是也可能不是问题;如果您所做的只是一些字段/属性更改,这很好。this.Dispatcher.Invoke()
或this.Dispatcher.BeginInvoke()
在完成其他处理后调用您的UI更新方法(例如:this.Dispatcher.Invoke((Action) UpdateTicker)
)。这会“调整”对UI更新的正确线程的调用,同时保持数据处理的异步。换句话说,这是一种更有效的方法。 TimeSpan
结构体有一个ToString()
方法,可以接受格式化;或者,如果这不方便,它有几个辅助属性(Days
,Hours
,Minutes
,TotalDays
,TotalHours
,TotalMinutes
等。 )您可以用于显示目的。
答案 2 :(得分:0)
你可以这样做: 在主窗口中:
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();
}
}