How to update progressbars individually (simultaneous downloads) - c#

I´m currently trying to add parallel downloads to my application but I don´t know how to handle the DownloadProgressChangedEvent to display the progress in multiple progressbars.
I´m using a datagridview with predefined rows for each file the user is able to download and each row has a cell with a progressbar in it.
The problem now is, that I don´t know how to update each progressbar individually, because right now, all selected progressbars are showing the same percentage and they´re just jumping between the progress of download1 & download2.
Here´s the code im using:
To start the downloads:
private void download_button_Click(object sender, EventArgs e)
{
start = DateTime.Now;
download_button.Enabled = false;
Rows = dataGridView1.Rows.Count;
Checked = 0;
CheckedCount = 0;
//count the selected rows
for (i = 0; i < Rows; i++)
{
Checked = Convert.ToInt32(dataGridView1.Rows[i].Cells["checkboxcol"].FormattedValue);
CheckedCount += Checked;
richTextBox3.Text = CheckedCount.ToString();
}
for (int z = 1; z < CheckedCount; z++)
{
_MultipleWebClients = new WebClient();
_MultipleWebClients.DownloadFileCompleted += new AsyncCompletedEventHandler(_DownloadFileCompleted);
_MultipleWebClients.DownloadProgressChanged += new System.Net.DownloadProgressChangedEventHandler(_DownloadProgressChanged);
_MultipleWebClients.DownloadFileAsync(new Uri(_downloadUrlList[z].ToString()), #"F:\test" + z + ".mp4");
}
}
(I´m also unable to download more than two files simultaneously - the third download won´t start until the first two are finished)
DownloadProgressChangedEvent:
private void _DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
for (int c = 0; c < CheckedCount; c++)
{
dataGridView1.Rows[_downloadRowNrList[c]].Cells[3].Value = e.ProgressPercentage;
}
float size = ((e.TotalBytesToReceive / 1024) / 1024);
label1.Text = size.ToString();
double dn = (double)e.BytesReceived / 1024.0 / (DateTime.Now - start).TotalSeconds;
label2.Text = (dn.ToString("n") + " KB/s) " + e.ProgressPercentage);
}
The problem probably is, that all progressbars are using the same DownloadProgressChangedEvent, but I´m not sure how to create multiple of these events without knowing the needed number...
So i hope that someone is able to help me with this,
thanks in advance!

What you want to do is use the other DownloadFileAsync method:
http://msdn.microsoft.com/en-us/library/ms144197.aspx
The third parameter is a userToken which gets passed as part of the DownloadProgressChangedEventArgs (it's in the UserState property).
So, when you make the DownloadFileAsync call, pass in a unique token (an integer, or something else) that you can then associate with the progressBar that needs updating.
//(Snip)
//in download_button_Click, pass the row you are updating to the event.
for (int z = 1; z < CheckedCount; z++)
{
_MultipleWebClients = new WebClient();
_MultipleWebClients.DownloadFileCompleted += new AsyncCompletedEventHandler(_DownloadFileCompleted);
_MultipleWebClients.DownloadProgressChanged += new System.Net.DownloadProgressChangedEventHandler(_DownloadProgressChanged);
_MultipleWebClients.DownloadFileAsync(new Uri(_downloadUrlList[z].ToString()), #"F:\test" + z + ".mp4", dataGridView1.Rows[z]);
}
}
private void _DownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
{
var rowToUpdate = (DataGridViewRow)e.UserState;
RowToUpdate["ProgressBar"].Value = e.ProgressPercentage;
RowToUpdate["TextProgress"].Value = e.ProgressPercentage;
RowToUpdate["BytesToRecive"].Value = ((e.TotalBytesToReceive / 1024) / 1024).ToString();
double dn = (double)e.BytesReceived / 1024.0 / (DateTime.Now - start).TotalSeconds;
RowToUpdate["Speed"].Value = (dn.ToString("n") + " KB/s) " + e.ProgressPercentage);
}

Sounds like you need a progress bar for multi-parted progress:
public partial class ProgressBarEx : ProgressBar
{
private readonly Dictionary<Guid, double> _partsProgress =
new Dictionary<Guid, double>();
private readonly Dictionary<Guid, double> _partsSizes =
new Dictionary<Guid, double>();
private double _value;
private double _maximum;
public ProgressBarEx()
{
this.InitializeComponent();
}
public int Parts
{
get { return this._partsSizes.Count; }
}
public new int Minimum { get; private set; }
public new double Maximum
{
get { return this._maximum; }
private set
{
this._maximum = value;
base.Maximum = (int)value;
}
}
public new double Value
{
get { return this._value; }
private set
{
this._value = value;
base.Value = (int)value;
}
}
[Obsolete("Not useable in ProgressBarEx.")]
public new int Step
{
get { return 0; }
}
public Guid AddPart(double size)
{
if (size <= 0)
{
throw new ArgumentException("size");
}
var partId = Guid.NewGuid();
this.Maximum += size;
this._partsSizes.Add(partId, size);
this._partsProgress.Add(partId, 0);
return partId;
}
public bool RemovePart(Guid partId)
{
double size;
if (!this._partsSizes.TryGetValue(partId, out size))
{
return false;
}
this.Maximum -= size;
this._partsSizes.Remove(partId);
this.Value -= this._partsProgress[partId];
this._partsProgress.Remove(partId);
return true;
}
public bool ContainsPart(Guid partId)
{
return this._partsSizes.ContainsKey(partId);
}
public double GetProgress(Guid partId)
{
return this._partsProgress[partId];
}
public void SetProgress(Guid partId, double progress)
{
if (progress < 0 || this._partsSizes[partId] < progress)
{
throw new ArgumentOutOfRangeException("progress");
}
this.Value += progress - this._partsProgress[partId];
this._partsProgress[partId] = progress;
}
public void AddProgress(Guid partId, double progress)
{
this.SetProgress(partId, progress + this._partsProgress[partId]);
}
[Obsolete("Not useable in ProgressBarEx.")]
public new void PerformStep()
{
}
}
Example usage:
public Form1()
{
InitializeComponent();
var pbe = new ProgressBarEx {Location = new Point(100, 100)};
this.Controls.Add(pbe);
for (var i = 0; i < 4; i++)
{
var size = i * 10 + 30;
var partId = pbe.AddPart(size);
var pb = new ProgressBar
{
Maximum = size,
Location = new Point(100, i * 30 + 130)
};
this.Controls.Add(pb);
var timer = new Timer {Interval = 1000 + i * 100};
timer.Tick += (sender, args) =>
{
pb.Value += 5;
pbe.AddProgress(partId, 5);
if (pb.Value == pb.Maximum)
{
timer.Stop();
}
};
timer.Start();
}
}

Related

C# Console program runs faster when button is pressed

I'm creating a procedurally generated platformer in the C# console, out of interest. However, I found that the program runs slower than I would expect unless I hold any button, then it returns to the expected amount entered as timeLapse.
Currently the only button with a functionality is the space bar, which makes you jump. I tested the code as well without the code on line // THIS CODE, and that removes the problem. Does anyone have an explanation or an article about this? Couldn't find a lot myself.
PlatformGenerator code could be added, although I don't think it would be necessary.
Program code:
namespace TestProject.Procedural_Generation
{
public class PlatformManager
{
#region Variables
// Game settings
private static char[,] screen;
private static int screenWidth = 96;
private static int screenHeight = 20;
// Platform generation settings
private static int minGeneration = 4;
private static int maxGeneration = 18;
private static PlatformGenerator pg;
private static List<Platform> platforms = new List<Platform>();
// Physics variables
private static int oldX = 0;
private static int currentX = 0;
private static int currentY = 8;
private static string toSkip = "";
private static Player player;
// Timer variables
private static float timeLapse = 100;
private static float jumpTime = 200;
private static Timer graphicUpdater;
private static Timer physicUpdater;
private static Timer jumpTimer;
private static bool isJumping = false;
private static long score = 0;
#endregion
public PlatformManager()
{
screen = new char[screenHeight, screenWidth];
pg = new PlatformGenerator(minGeneration, maxGeneration);
player = new Player();
// Physics
physicUpdater = new Timer(timeLapse);
physicUpdater.Elapsed += ExecutePhysics;
physicUpdater.AutoReset = true;
physicUpdater.Enabled = true;
jumpTimer = new Timer(jumpTime);
jumpTimer.Elapsed += Land;
jumpTimer.Enabled = true;
// Graphics
graphicUpdater = new Timer(timeLapse);
graphicUpdater.Elapsed += ExecuteGraphics;
graphicUpdater.AutoReset = true;
graphicUpdater.Enabled = true;
while (true)
{
score++;
}
}
private void Land(object source, EventArgs e)
{
isJumping = false;
}
#region Graphics
private void ExecuteGraphics(object source, ElapsedEventArgs e)
{
Console.Clear();
Console.Write(Display());
}
// Makes sure everything is printed correctly
public string Display()
{
string result = "";
for (int y = 0; y < screenHeight; y++)
{
for (int x = 0; x < screenWidth; x++)
{
if (y == player.y && x == player.x)
{
result += "O";
}
else if (screen[y, x] == '=')
{
result += "=";
}
else
{
result += " ";
}
}
result += "\n";
}
return result;
}
#endregion
#region Physics
// Controls platform generation and movement
private void ExecutePhysics(object source, ElapsedEventArgs e)
{
Platformer();
if (player.y == screenHeight - 1)
{
graphicUpdater.Stop();
physicUpdater.Stop();
Console.WriteLine();
Console.WriteLine("Game over! Score: " + Math.Floor(Math.Sqrt(score)));
}
else if (isJumping)
{
player.y -= 1;
}
else if (Console.ReadKey(true).Key == ConsoleKey.Spacebar && screen[player.y + 1, player.x] == '=') // THIS CODE
{
isJumping = true;
jumpTimer.Start();
player.y -= 1;
}
else if (screen[player.y + 1, player.x] != '=' && !isJumping)
{
player.y += 1;
}
}
// Generate a new platform
public void Platformer()
{
Platform newPlatform = pg.Generate(currentX, currentY);
currentY = newPlatform.y;
if (currentX + newPlatform.size + newPlatform.xDif > screenWidth)
{
MoveScreen(newPlatform.size + newPlatform.xDif);
currentX -= newPlatform.size + newPlatform.xDif;
oldX -= newPlatform.size + newPlatform.xDif;
}
while (currentX < oldX + newPlatform.size + newPlatform.xDif)
{
screen[currentY, currentX] = '=';
currentX += 1;
}
oldX = currentX;
}
// Update all rows so the newly added ones fit.
public void MoveScreen(int amount)
{
for (int y = 0; y < screenHeight; y++)
{
for (int x = amount; x < screenWidth; x++)
{
screen[y, x - amount] = screen[y, x];
}
}
for (int y = 0; y < screenHeight; y++)
{
for (int x = screenWidth - amount; x < screenWidth; x++)
{
screen[y, x] = '\0';
}
}
}
#endregion
}
}
Not sure If I understand correclty, but the
Console.ReadKey(true).Key == ConsoleKey.Spacebar
waits for a keypress to happen, so the code will not be processed until a key is actually pressed, if you remove that the programm will obviously move "faster" because it does not wait for a keypress to happen

C#: Change Panel Location via Mouse Location

I've got a User Control consisting of Panels and Labels.
MinPanel, ValuePanel, and MaxPanel.
I'm trying to allow the user to drag the panels much like you would in a TrackBar.
My problem is that when I click and attempt to drag the MinPanel, it jump around starting at 0, then to 200, then to other random values. It seems to be attempting to reset to it's default value each time I drag it.
public partial class ToolboxCustomTrackBar : UserControl
{
private int min = 0;
private int max = 1000;
private int selectedMin = 0;
private int selectedMax = 1000;
private int selectedValue = 400;
private int selectionWidth = 0;
private int labelHeight = 10;
public int Min
{
get { return min; }
set
{
min = value;
Invalidate();
}
}
public int Max
{
get { return max; }
set
{
max = value;
Invalidate();
}
}
public int SelectedMin
{
get { return selectedMin; }
set
{
selectedMin = value;
Invalidate();
}
}
public int SelectedMax
{
get { return selectedMax; }
set
{
selectedMax = value;
Invalidate();
}
}
public int SelectedValue
{
get { return selectedValue; }
set { selectedValue = value; Invalidate(); }
}
public int LabelHeight
{
get { return labelHeight; }
set { labelHeight = value; Invalidate(); }
}
public ToolboxCustomTrackBar()
{
InitializeComponent();
SetStyle(ControlStyles.AllPaintingInWmPaint, true);
SetStyle(ControlStyles.OptimizedDoubleBuffer, true);
backdropPanel.Width = Width;
backdropPanel.Height = Height;
SelectedMin = Min;
SelectedMax = Max;
SelectedValue = (Max - Min) / 2;
selectionWidth = Max - Min;
Invalidate();
backdropPanel.BackColor = Color.LightBlue;
minPanel.BackColor = Color.DarkRed;
maxPanel.BackColor = Color.DarkGreen;
valuePanel.BackColor = Color.Black;
backdropPanel.Location = new Point(Min, backdropPanel.Location.Y);
}
private void ToolboxCustomTrackBar_Paint(object sender, PaintEventArgs e)
{
backdropPanel.Location = new Point(0, LabelHeight);
backdropPanel.Width = Width;
backdropPanel.BackColor = Color.AliceBlue;
minPanel.Location = new Point(SelectedMin * Width / (Max - Min), backdropPanel.Location.Y);
maxPanel.Location = new Point(SelectedMax * Width / (Max - Min), backdropPanel.Location.Y);
valuePanel.Location = new Point((SelectedValue) * Width / (Max - Min), backdropPanel.Location.Y);
minLabel.Location = new Point(SelectedMin - (minLabel.Width / 2) + (minPanel.Width / 2), backdropPanel.Location.Y - LabelHeight);
minLabel.Text = SelectedMin.ToString();
maxLabel.Location = new Point(SelectedMax - (maxLabel.Width / 2) + (maxPanel.Width / 2), backdropPanel.Location.Y - LabelHeight);
maxLabel.Text = SelectedMax.ToString();
valueLabel.Location = new Point(SelectedValue - (valueLabel.Width / 2) + (valuePanel.Width / 2), backdropPanel.Location.Y - LabelHeight);
valueLabel.Text = SelectedValue.ToString();
}
private void minPanel_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return; //ensure user left-clicked
int pointedValue = Min + e.X * (Max - Min) / Width; //selectionWidth?
SelectedMin = pointedValue;
}
}
I've cut some unrelated bits out.
I don't fully understand why, but I need to use += instead of =.
private void minPanel_MouseMove(object sender, MouseEventArgs e)
{
if (e.Button != MouseButtons.Left) return; //ensure user left-clicked
int pointedValue = Min + e.X * (Max - Min) / Width;
SelectedMin += pointedValue;
}

Fft in real time - AsioOut+SampleAggregator

I am currently working on my thesis project, this is an application that writes tablature by ASIO drivers. In short, write what you play. I'm new in all regards Naudio and I'm having poblemas to transform sound into FFT. I am capturing the sound with a device called "GuitarLink" which runs through ASIO4ALL.
So I need to compare frequencies for the chord in real time.
Currently, the program gives me two values, X and Y.
(I am using "SampleAggregator" Class and AsioOut)
MAIN
private SampleAggregator sampleAggregator = new(fftLength);
public MainWindow()
{
InitializeComponent();
dispatcherTimer.Tick += new EventHandler(SoClose);
dispatcherTimer.Interval = new TimeSpan(0, 0, 0, 0, 500);
dispatcherTimer.Start();
}
void SoClose(object sender, EventArgs e)
{
try
{
if (PBass)
{
sampleAggregator.PerformFFT = true;
sampleAggregator.FftCalculated += new EventHandler<FftEventArgs>(FftCalculated);
AsioOut asioOut = new();
BufferedWaveProvider wavprov = new(new WaveFormat(48000, 1));
asioOut.AudioAvailable += new EventHandler<AsioAudioAvailableEventArgs>(asio_DataAvailable);
asioOut.InitRecordAndPlayback(wavprov, 1, 25);
asioOut.Play();
I1E.Text = frecuencia.ToString();
}
}
catch
{
MessageBox.Show("Error de bajo presupuesto", "Obviamente algo anda mal");
}
}
private void FftCalculated(object sender, FftEventArgs e)
{
for (int i = 0; i < e.Result.Length; ++i)
{
A = e.Result[i].X;
B = e.Result[i].Y;
frecuencia = B;
Debug.WriteLine($"FFT: X={e.Result[i].X} Y={e.Result[i].Y}");
}
}
void asio_DataAvailable(object sender, AsioAudioAvailableEventArgs e)
{
byte[] buf = new byte[e.SamplesPerBuffer * 4];
for (int i = 0; i < e.InputBuffers.Length; i++)
{
Marshal.Copy(e.InputBuffers[i], buf, 0, e.SamplesPerBuffer * 4);
Marshal.Copy(buf, 0, e.OutputBuffers[i], e.SamplesPerBuffer * 4);
}
for (int i = 0; i < buf.Length; i = i + 4)
{
float sample = Convert.ToSingle(buf[i] + buf[i + 1] + buf[i + 2] + buf[i + 3]);
sampleAggregator.Add(sample);
}
e.WrittenToOutputBuffers = true;
}
SampleAggregator class
public class SampleAggregator
{
// FFT
public event EventHandler<FftEventArgs> FftCalculated;
public bool PerformFFT { get; set; }
// This Complex is NAudio's own!
private Complex[] fftBuffer;
private FftEventArgs fftArgs;
private int fftPos;
private int fftLength;
private int m;
public SampleAggregator(int fftLength)
{
if (!IsPowerOfTwo(fftLength))
throw new ArgumentException("FFT Length must be a power of two");
this.m = (int)Math.Log(fftLength, 2.0);
this.fftLength = fftLength;
this.fftBuffer = new Complex[fftLength];
this.fftArgs = new FftEventArgs(fftBuffer);
}
public void Add(float value)
{
if (PerformFFT && FftCalculated != null)
{
fftBuffer[fftPos].X = (float)(value * FastFourierTransform.HammingWindow(fftPos, fftLength));
fftBuffer[fftPos].Y = 0; // This is always zero with audio.
fftPos++;
if (fftPos >= fftLength)
{
fftPos = 0;
FastFourierTransform.FFT(true, m, fftBuffer);
FftCalculated(this, fftArgs);
}
}
}
static bool IsPowerOfTwo(int x) => (x & (x - 1)) == 0;
}
public class FftEventArgs : EventArgs
{
public Complex[] Result { get; private set; }
public string resultado = "";
[DebuggerStepThrough]
public FftEventArgs(Complex[] result) => Result = result;
void FftCalculated(object sender, FftEventArgs e)
{
}
}
Well the problem is that when I play a note, the values that are delivered are not affected.

Make variable method

I am currently working on a XP Leveling system in unity. At the moment my code works (this first part is inside the onclick method):
currentExp = Xp.LevelToXP(combat + 1);
if (combatExp + 5 < currentExp)
{
combatExp += 5;
if (previousExp == 0)
{
float fill = (float)(currentExp) / 100;
XpBar.fillAmount += (5 / fill) / 100;
}
else
{
float fill = (float)(currentExp - previousExp) / 100;
XpBar.fillAmount += (5 / fill) / 100;
}
}
else if (combatExp + 5 == currentExp)
{
combatExp += 5;
combat++;
previousExp = currentExp;
XpBar.fillAmount = 0;
}
else if (combatExp + 5 > currentExp)
{
combatExp += 5;
combat++;
previousExp = currentExp;
XpBar.fillAmount = 0;
float remainingExp = (float)combatExp - currentExp;
XpBar.fillAmount += (remainingExp / currentExp) / 100f;
}
txtCombatLvl.text = "Combat Level: " + combat;
this is what happens when you press the button.
but when i try to put all of this in a method instead of the onclick method
void AddExp (int skill, int skillExp, int expAmount)
{
currentExp = Xp.LevelToXP(skill + 1);
if (skillExp + expAmount < currentExp)
{
skillExp += expAmount;
if (previousExp == 0)
{
float fill = (float)(currentExp) / 100;
XpBar.fillAmount += (expAmount / fill) / 100;
}
else
{
float fill = (float)(currentExp - previousExp) / 100;
XpBar.fillAmount += (expAmount / fill) / 100;
}
}
else if (skillExp + expAmount == currentExp)
{
skillExp += expAmount;
skill++;
previousExp = currentExp;
XpBar.fillAmount = 0;
}
else if (skillExp + expAmount > currentExp)
{
skillExp += expAmount;
skill++;
previousExp = currentExp;
XpBar.fillAmount = 0;
float remainingExp = (float)skillExp - currentExp;
XpBar.fillAmount += (remainingExp / currentExp) / 100f;
}
}
And I try to access it by calling it like this:
AddExp(combat, combatExp, 5);
none of my combat xp and level don't save. Can someone point me in the right direction since none of what i tried worked.
The parameters of a C# method are passed by value and not by references, so changes to them inside a function are local to the function. If you want the values updated then you prefix them with the ref keyword.
void AddExp (ref int skill, ref int skillExp, int expAmount)
Alternate Solution
Alternatively, you could simplify your design with an encapsulation of your concept of a Skill with a class. Here's a possibility, I couldn't quite follow your math and am not familiar with your other classes, so you can adjust this as necessary.
public class Skill
{
private string _name;
private int _currentValue;
private int _currentLevel = 1;
public Skill(string name) {
}
public string Name { get { return _name; } }
public int CurrentXp { get { return _currentValue; } }
public int CurrentLevel { get { return _currentLevel; } }
public int XpRequiredForNextLevel { get { return Xp.LevelToXP(_currentLevel + 1); } }
public int XpRequiredForCurrentLevel { get { return Xp.LevelToXP(_currentLevel); } }
private float CalculateFillPercentage(int xp)
{
var xpInLevel = XpRequiredForNextLevel - XpRequiredForCurrentLevel;
return (float)(xp - XpRequiredForCurrentLevel) / (float)xpInLevel;
}
public void AddXp(int expAmount)
{
_currentValue += expAmount;
int XpInCurrentLevel = XpRequiredForNextLevel - XpRequiredForCurrentLevel;
if (_currentValue > XpRequiredForNextLevel) {
} else {
_currentLevel++;
}
XpBar.fillAmount = CalculateFillPercentage (_currentValue);
}
}
You could use this by holding instances of each skill, for example:
Skill combat = new Skill("combat");
And then later
combat.AddXp(50);
Each skill would be self contained. I'm not sure about the XpBar or how that is controlled between different types of skills, but you could further encapsulate by calculating internally, so the skill provides everything your UI needs to keep up to date.

Invoke doesn't update GUI appropriately

I wrote this for a meteorology class. For some reason, the textboxes aren't updating on the GUI correctly for large numbers (large numbers of photons). The calculation completes, but the textboxes don't update.
I suspect the problem is with calling Invoke(), but I can't for the life of me see what's going wrong. I've tried using both Invoke() and BeginInvoke() with similar results.
Can anyone help figure out where I'm going wrong?
Thanks!
PS> Please forgive the global variables. Was planning on cleaning them up later...
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;
}
}
}
Stop using Invoke and BeginInvoke to update the UI. Despite what you may have been told it is not that great of solution. Actually, in situations like these where all you want to to do is update the UI with progress information it is probably the worst solution. Instead, have your worker thread publish its progress information to an immutable data structure that can be shared with the UI thread. Then have your UI thread poll for it on a reasonable interval using a System.Windows.Forms.Timer.
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();
}
Notice several things here.
The code is a lot easier to understand and follow.
The UI thread gets to decide when and how often its controls should be updated.
You get more throughput on the worker thread since it does not have to wait for a response from the UI as would occur with Invoke.
I rip on these Invoke and BeginInvoke solutions a lot because in many cases they are terrible solutions. Using a BackgroundWorker is a little better, but it still forces you into the push method of updating the UI (via the same marshaling techniques behind the scenes nonetheless). The pull method can be (and often is) a more elegant solution and usually more efficient.

Categories