与计时器一起玩。 上下文:带有两个标签的winforms。
我想看看System.Timers.Timer
是如何工作的,所以我没有使用Forms计时器。
我知道表单和myTimer现在将在不同的线程中运行。
是否有一种简单的方法可以用以下形式表示lblValue
上的已用时间?
我在MSDN看过这里,但有更简单的方法!
这是winforms代码:
using System.Timers;
namespace Ariport_Parking
{
public partial class AirportParking : Form
{
//instance variables of the form
System.Timers.Timer myTimer;
int ElapsedCounter = 0;
int MaxTime = 5000;
int elapsedTime = 0;
static int tickLength = 100;
public AirportParking()
{
InitializeComponent();
keepingTime();
lblValue.Text = "hello";
}
//method for keeping time
public void keepingTime() {
myTimer = new System.Timers.Timer(tickLength);
myTimer.Elapsed += new ElapsedEventHandler(myTimer_Elapsed);
myTimer.AutoReset = true;
myTimer.Enabled = true;
myTimer.Start();
}
void myTimer_Elapsed(Object myObject,EventArgs myEventArgs){
myTimer.Stop();
ElapsedCounter += 1;
elapsedTime += tickLength;
if (elapsedTime < MaxTime)
{
this.lblElapsedTime.Text = elapsedTime.ToString();
if (ElapsedCounter % 2 == 0)
this.lblValue.Text = "hello world";
else
this.lblValue.Text = "hello";
myTimer.Start();
}
else
{ myTimer.Start(); }
}
}
}
答案 0 :(得分:35)
我猜你的代码只是一个测试,所以我不会讨论你用你的计时器做什么。这里的问题是如何使用计时器回调中的用户界面控件执行某些操作。
大多数Control
的方法和属性只能从UI线程访问(实际上,只能从创建它们的线程访问它们,但这是另一个故事)。这是因为每个线程都必须有自己的消息循环(GetMessage()
按线程筛选出消息)然后用Control
做一些事情,你必须从你的线程向 main发送消息线程。在.NET中很容易,因为每个Control
都为此目的继承了几种方法:Invoke/BeginInvoke/EndInvoke
。要知道执行线程是否必须调用那些具有属性InvokeRequired
的方法。只需更改代码即可使其正常工作:
if (elapsedTime < MaxTime)
{
this.BeginInvoke(new MethodInvoker(delegate
{
this.lblElapsedTime.Text = elapsedTime.ToString();
if (ElapsedCounter % 2 == 0)
this.lblValue.Text = "hello world";
else
this.lblValue.Text = "hello";
}));
}
请查看MSDN以获取您可以从任何主题调用的方法列表,作为参考,您始终可以调用Invalidate
,BeginInvoke
,EndInvoke
,Invoke
方法和阅读InvokeRequired
财产。通常,这是一种常见的使用模式(假设this
是从Control
派生的对象):
void DoStuff() {
// Has been called from a "wrong" thread?
if (InvokeRequired) {
// Dispatch to correct thread, use BeginInvoke if you don't need
// caller thread until operation completes
Invoke(new MethodInvoker(DoStuff));
} else {
// Do things
}
}
请注意,当前线程将阻塞,直到UI线程完成方法执行。如果线程的时间很重要,这可能是一个问题(不要忘记UI线程可能忙或暂停一点)。如果您不需要方法的返回值,则只需将Invoke
替换为BeginInvoke
,对于WinForms,您甚至不需要后续调用EndInvoke
:
void DoStuff() {
if (InvokeRequired) {
BeginInvoke(new MethodInvoker(DoStuff));
} else {
// Do things
}
}
如果您需要返回值,则必须处理通常的IAsyncResult
接口。
GUI Windows应用程序基于窗口过程及其消息循环。如果你用普通的C写一个应用程序,你会有这样的事情:
MSG message;
while (GetMessage(&message, NULL, 0, 0))
{
TranslateMessage(&message);
DispatchMessage(&message);
}
使用这几行代码,您的应用程序会等待消息,然后将消息传递给窗口过程。窗口过程是一个很大的开关/ case语句,你检查你知道的消息(WM_
)并以某种方式处理它们(你为WM_PAINT
绘制窗口,你退出了{{1}的应用程序等等)。
现在假设你有一个工作线程,你怎么能调用主线程?最简单的方法是使用这个底层结构来完成这个技巧。我过度简化了任务,但这些步骤是:
WPF和WinForms都使用此方法将消息从线程传递(分派)到UI线程。看看this article on MSDN有关多线程和用户界面的更多详细信息,WinForms隐藏了很多这些细节而你不需要处理它们,但是你可以看看它是如何在引擎盖下工作的
答案 1 :(得分:7)
就个人而言,当我在一个使用UI中的线程的应用程序中工作时,我通常会写这个小片段。
private void InvokeUI(Action a)
{
this.BeginInvoke(new MethodInvoker(a));
}
当我在不同的线程中执行异步调用时,我总是可以使用回调。
InvokeUI(() => {
Label1.Text = "Super Cool";
});
简单干净。
答案 2 :(得分:2)
正如所提出的,这是我的答案,它检查交叉线程调用,同步变量更新,不停止和启动计时器,并且不使用计时器来计算经过的时间。
编辑已修复BeginInvoke
来电。我使用泛型Action
完成了跨线程调用,这允许传递发送方和eventargs。如果它们未被使用(因为它们在这里),使用MethodInvoker
会更有效,但我怀疑需要将处理转移到无参数方法。
public partial class AirportParking : Form
{
private Timer myTimer = new Timer(100);
private int elapsedCounter = 0;
private readonly DateTime startTime = DateTime.Now;
private const string EvenText = "hello";
private const string OddText = "hello world";
public AirportParking()
{
lblValue.Text = EvenText;
myTimer.Elapsed += MyTimerElapsed;
myTimer.AutoReset = true;
myTimer.Enabled = true;
myTimer.Start();
}
private void MyTimerElapsed(object sender,EventArgs myEventArgs)
{
If (lblValue.InvokeRequired)
{
var self = new Action<object, EventArgs>(MyTimerElapsed);
this.BeginInvoke(self, new [] {sender, myEventArgs});
return;
}
lock (this)
{
lblElapsedTime.Text = DateTime.Now.SubTract(startTime).ToString();
elapesedCounter++;
if(elapsedCounter % 2 == 0)
{
lblValue.Text = EvenText;
}
else
{
lblValue.Text = OddText;
}
}
}
}
答案 3 :(得分:1)
首先,在Windows窗体(和大多数框架)中,只能通过UI线程访问控件(除非记录为&#34;线程安全&#34;)。
因此回调中的this.lblElapsedTime.Text = ...
是完全错误的。看看Control.BeginInvoke。
其次,您应该使用System.DateTime和System.TimeSpan进行时间计算。
未测试:
DateTime startTime = DateTime.Now;
void myTimer_Elapsed(...) {
TimeSpan elapsed = DateTime.Now - startTime;
this.lblElapsedTime.BeginInvoke(delegate() {
this.lblElapsedTime.Text = elapsed.ToString();
});
}
答案 4 :(得分:0)
使用以下内容结束。这是给出的建议的组合:
using System.Timers;
namespace Ariport_Parking
{
public partial class AirportParking : Form
{
//>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>
//instance variables of the form
System.Timers.Timer myTimer;
private const string EvenText = "hello";
private const string OddText = "hello world";
static int tickLength = 100;
static int elapsedCounter;
private int MaxTime = 5000;
private TimeSpan elapsedTime;
private readonly DateTime startTime = DateTime.Now;
//<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<
public AirportParking()
{
InitializeComponent();
lblValue.Text = EvenText;
keepingTime();
}
//method for keeping time
public void keepingTime() {
using (System.Timers.Timer myTimer = new System.Timers.Timer(tickLength))
{
myTimer.Elapsed += new ElapsedEventHandler(myTimer_Elapsed);
myTimer.AutoReset = true;
myTimer.Enabled = true;
myTimer.Start();
}
}
private void myTimer_Elapsed(Object myObject,EventArgs myEventArgs){
elapsedCounter++;
elapsedTime = DateTime.Now.Subtract(startTime);
if (elapsedTime.TotalMilliseconds < MaxTime)
{
this.BeginInvoke(new MethodInvoker(delegate
{
this.lblElapsedTime.Text = elapsedTime.ToString();
if (elapsedCounter % 2 == 0)
this.lblValue.Text = EvenText;
else
this.lblValue.Text = OddText;
}));
}
else {myTimer.Stop();}
}
}
}