调用不会适当地更新 GUI
本文关键字:更新 GUI 调用 | 更新日期: 2023-09-27 18:32:11
我为气象课写了这篇文章。由于某种原因,文本框没有在 GUI 上正确更新大量(大量光子)。计算完成,但文本框不会更新。
我怀疑问题出在调用 Invoke() 上,但我一辈子都看不出出了什么问题。我尝试使用Invoke()和BeginInvoke()都有类似的结果。
谁能帮助弄清楚我哪里出了问题?
谢谢!
PS>请原谅全局变量。打算以后再清理它们...
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace CloudTransmittance
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void buttonCalculate_Click(object sender, EventArgs e)
{
Thread t = new Thread(calculateModel);
t.Start();
}
//number of photons that have gone to albedo, direct, or diffuse transmittance
private ulong top = 0;
private ulong direct = 0;
private ulong diffuse = 0;
private ulong absorbed = 0;
private ulong failed = 0;
private ulong photons = 0;
private void calculateModel()
{
//model variables
double theta = 0;
double tauStar = 0;
double omega = 0;
double g = 0;
photons = 0;
//Get data from form
theta = Convert.ToDouble(textBoxTheta.Text);
tauStar = Convert.ToDouble(textBoxTau.Text);
omega = Convert.ToDouble(textBoxOmega.Text);
g = Convert.ToDouble(textBoxG.Text);
photons = Convert.ToUInt64(textBoxPhotons.Text);
//Clear the progress bar and set its limits
this.progressBar1.BeginInvoke(
(MethodInvoker)delegate()
{
this.progressBar1.Minimum = 0;
this.progressBar1.Value = 0;
this.progressBar1.Maximum = (int)photons;
this.progressBar1.Step = 1;
});
//Clear the text boxes
this.textBoxAlbedo.Invoke(
(MethodInvoker)delegate()
{
this.textBoxAlbedo.Text = "";
});
this.textBoxDirect.Invoke(
(MethodInvoker)delegate()
{
this.textBoxDirect.Text = "";
});
this.textBoxDiffuse.Invoke(
(MethodInvoker)delegate()
{
this.textBoxDiffuse.Text = "";
});
this.textBox1.Invoke(
(MethodInvoker)delegate()
{
this.textBox1.Text = "";
});
this.textBox2.Invoke(
(MethodInvoker)delegate()
{
this.textBox2.Text = "";
});
//convert theta to radians from degrees
theta *= Math.PI / 180;
//number of photons that have gone to albedo, direct, or diffuse transmittance
top = 0;
direct = 0;
diffuse = 0;
absorbed = 0;
failed = 0;
//Random number generator
Random r = new Random();
double randomValue = 0;
int count = 1000; //number of iterations of the problem...
double delta = 0.00001; //close enough to "1" for calculations, since C# random goes from [0, 1) instead of [0, 1]
//Calculate transmittance
for (ulong photonCount = 0; photonCount < photons; photonCount++)
{
bool scattered = false;
double newTheta = theta; //needed for looping
int i = 0; //counting variable used to prevent infinite looping
for (i = 0; i < count; i++)
{
double length = calculateTauP(); //length of the photon's travel
double newTau = calculateTau(newTheta, length);
if (newTau < 0)
{
top++; //photon has exited through the top
break; //move to the next photon
}
else if (newTau > tauStar)
{
//exited through the bottom of the cloud
if (scattered == false)
{
//direct transmittance
direct++;
}
else
{
//diffuse transmittance
diffuse++;
}
break;
}
else
{
//photon is either scattered or absorbed
randomValue = r.NextDouble();
if (randomValue >= omega) // || ((omega == 1) && (randomValue >= (omega - delta)) )
{
//photon absorbed, no longer of interest
absorbed++;
break;
}
else
{
//photon scattered, determine direction
scattered = true;
newTheta = calculateNewAngle(newTau, newTheta, g, randomValue);
}
}
}
if (i >= count)
{
failed++;
}
this.progressBar1.BeginInvoke(
(MethodInvoker)delegate()
{
this.progressBar1.PerformStep();
});
}
//Update Form values
displayData();
}
private void displayData()
{
if (this.textBoxAlbedo.InvokeRequired)
{
this.textBoxAlbedo.Invoke(
(MethodInvoker)delegate()
{
this.textBoxAlbedo.Text = ((double)top / (double)photons).ToString();
});
}
else
{
textBoxAlbedo.Text = ((double)top / (double)photons).ToString();
}
if (this.textBoxDirect.InvokeRequired)
{
this.textBoxDirect.Invoke(
(MethodInvoker)delegate()
{
this.textBoxDirect.Text = ((double)direct / (double)photons).ToString();
});
}
else
{
textBoxDirect.Text = ((double)direct / (double)photons).ToString();
}
if (this.textBoxDiffuse.InvokeRequired)
{
this.textBoxDiffuse.Invoke(
(MethodInvoker)delegate()
{
this.textBoxDiffuse.Text = ((double)diffuse / (double)photons).ToString();
});
}
else
{
textBoxDiffuse.Text = ((double)diffuse / (double)photons).ToString();
}
if (this.textBox1.InvokeRequired)
{
this.textBox1.Invoke(
(MethodInvoker)delegate()
{
this.textBox1.Text = absorbed.ToString();
});
}
else
{
textBox1.Text = absorbed.ToString();
}
if (this.textBox2.InvokeRequired)
{
this.textBox2.Invoke(
(MethodInvoker)delegate()
{
this.textBox2.Text = failed.ToString();
});
}
else
{
textBox2.Text = failed.ToString();
}
}
private double calculateNewAngle(double length, double angle, double g, double randomNumber)
{
double newAngle = 0;
double cos = (1 / (2 * g)) * (1 + Math.Pow(g, 2) - Math.Pow(((1 - Math.Pow(g, 2)) / (1 + g * (2 * randomNumber - 1))), 2));
newAngle += angle + cos;
while (newAngle >= 2 * Math.PI)
{
newAngle -= 2 * Math.PI; //normalize the angle to 0 <= angle < 2PI
}
return newAngle;
}
private double calculateTauP()
{
Random r = new Random();
double distance = -1 * Math.Log(1 - r.NextDouble());
return distance;
}
private double calculateTau(double angle, double tauP)
{
double tau = tauP * Math.Cos(Math.PI/2 - angle);
return tau;
}
}
}
停止使用 Invoke
和 BeginInvoke
来更新 UI。尽管您可能已经被告知,但这并不是那么好的解决方案。实际上,在这种情况下,您要做的就是使用进度信息更新UI,这可能是最糟糕的解决方案。相反,请让工作线程将其进度信息发布到可与 UI 线程共享的不可变数据结构。然后使用 System.Windows.Forms.Timer
在合理的时间间隔内轮询您的 UI 线程。
public class YourForm : Form
{
private class ProgressInfo
{
public ProgressInfo(ulong top, ulong direct, ulong diffuse, ulong absorbed, ulong failed, ulong photons)
{
// Set properties here.
}
public ulong Top { get; private set; }
public ulong Direct { get; private set; }
public ulong Diffuse { get; private set; }
public ulong Dbsorbed { get; private set; }
public ulong Failed { get; private set; }
public ulong Photons { get; private set; }
}
private volatile ProgressInfo progress = null;
private void calculateModel()
{
for (ulong photonCount = 0; photonCount < photons; photonCount++)
{
// Do your calculations here.
// Publish new progress information.
progress = new ProgressInfo(/* ... */);
}
}
private void UpdateTimer_Tick(object sender, EventArgs args)
{
// Get a local reference to the data structure.
// This is all that is needed since ProgressInfo is immutable
// and the member was marked as volatile.
ProgressInfo local = progress;
this.textBoxAlbedo.Text = ((double)local.Top / (double)local.Photons).ToString();
this.textBoxDirect.Text = ((double)local.Direct / (double)local.Photons).ToString();
this.textBoxDiffuse.Text = ((double)local.Diffuse / (double)local.Photons).ToString();
this.textBox1.Text = local.Absorbed.ToString();
this.textBox2.Text = local.Failed.ToString();
}
请注意这里的几件事。
- 代码更容易理解和遵循。
- UI 线程可以决定何时以及多久更新一次其控件。
- 您可以在工作线程上获得更多吞吐量,因为它不必像
Invoke
那样等待来自 UI 的响应。
我经常扯掉这些Invoke
和BeginInvoke
解决方案,因为在许多情况下它们是糟糕的解决方案。使用BackgroundWorker
稍微好一些,但它仍然迫使您采用更新 UI 的推送方法(尽管如此,通过幕后的相同封送处理技术)。拉动方法可以(并且通常是)更优雅的解决方案,并且通常更有效。