event properties change does not trigger - c#

So i have created an event, whenever the property ActualVoltage changed, it will update in Form1 but it doesnt. Property ActualVoltage change, when i send a set-voltage command to the machine, then it will send back a number and i assign that number to AcutalVoltage. pls help me, pls show me where is my mistake and explain it for me like i am a 5 years old kid.Here is my event code:
public delegate void ValueChange();
public event ValueChange Change;
public double ActualVoltage
{
get { return actualVoltage; }
set
{
if (actualVoltage == value) return;
else
{
actualVoltage = value;
OnValueChange();
}
}
}
private void OnValueChange()
{
Change?.Invoke();
}
in Form1:
private void Form1_Load(object sender, EventArgs e)
{
ps.Change += ps_change;
}
private void ps_change()
{
lblValueActualVoltage.Text = ps.ActualVoltage.ToString();
lblValueActualCurrent.Text = ps.ActualCurrent.ToString();
lblHexCur.Text = ps.HexActualCurrent1;
lblHexVol.Text = ps.HexActualVoltage1;
}
updated: in class PS2000B
public void GetDeviceStatusInformation(byte[] rawData)
{
remoteMode = ((byte)(rawData[0] & 0b1)).ToString();
outputMode = ((byte)(rawData[1] & 0b1)).ToString();
List<byte> temp = new List<byte>();
foreach (var v in rawData)
temp.Add(v);
byte[] vontageBytes = temp.GetRange(2, 2).ToArray();
HexActualVoltage = BitConverter.ToString(vontageBytes);
Array.Reverse(vontageBytes);
byte[] currentBytes = temp.GetRange(4, 2).ToArray();
HexActualCurrent = BitConverter.ToString(currentBytes);
Array.Reverse(currentBytes);
var a = (BitConverter.ToInt16(vontageBytes, 0));
ActualVoltage =Math.Round( BitConverter.ToInt16(vontageBytes, 0) * nominalVoltage / 25600.0,2);
ActualCurrent = BitConverter.ToInt16(currentBytes, 0) * nominalCurrent / 25600.0;
}
public void RunTest(string safeFileName,string save)
{
Stopwatch st = new Stopwatch();
List<string> timeMeasure = new List<string>();
List<string> CurrentResults = new List<string>();
List<int> Time = new List<int>();
List<string> Voltage = new List<string>();
FileStream file = new FileStream(safeFileName, FileMode.Open, FileAccess.Read);
StreamReader reader = new StreamReader(file);
string strRead = reader.ReadLine();
while (strRead != null)
{
string[] temp = strRead.Split(';');
Voltage.Add(temp[0]);
Time.Add(int.Parse(temp[1]));
strRead = reader.ReadLine();
}
reader.Close();
file.Close();
int n = 0;
st.Start();
for (int i = 0; i < Voltage.Count(); i++)
{
SetVoltage(Voltage[i]);
for (int j = 0; j < Time[i]/300; j++)
{
UpdateStatusInfomationAndActualValue();
CurrentResults.Add(Voltage[i]+";"+0.3*n+";"+ActualCurrent.ToString()+";"+ HexActualCurrent);
n++;
}
}
st.Stop();
FileStream wfile = new FileStream(save +"\\results.txt", FileMode.Create, FileAccess.Write);
StreamWriter writer = new StreamWriter(wfile);
writer.WriteLine("VOlTAGE;TIME;CURRENT");
foreach (var v in CurrentResults)
writer.WriteLine(v);
writer.WriteLine("TOTAL TIME: "+st.Elapsed);
writer.Close();
wfile.Close();
}
public void SetVoltage(string vol)
{
vol = vol.Replace('.', ',');
ToPowerSupply ToPowerSupply = new ToPowerSupply();
var b = Convert.ToInt16(Single.Parse(vol) * 25600 / nominalVoltage);
var input = BitConverter.GetBytes(b);
Array.Reverse(input);
var temp = ToPowerSupply.SendCommand(0b11, ObjectList.SET_U, input, 2);
ComPort.Write(temp, 0, temp.Count());
Thread.Sleep(150);
int bytes = ComPort.BytesToRead;
byte[] rawData = new byte[bytes];
ComPort.Read(rawData, 0, bytes);
}

Following Form1.cs handles events properly:
public delegate void ValueChange();
public event ValueChange Change;
private double actualVoltage;
public double ActualVoltage
{
get { return actualVoltage; }
set
{
if (actualVoltage == value) return;
else
{
actualVoltage = value;
OnValueChange();
}
}
}
private void ps_change()
{
//UI updates here
}
private void OnValueChange()
{
Change?.Invoke();
}
private void Form1_Load(object sender, EventArgs e)
{
Change += ps_change;
}
Please note: If your property setters are invoked in non-GUI-thread, then you should marshal GUI-updating code to UI thread as mentioned here

If property setter is invoked on a non-GUI thread, the ps_change method should be as follows:
private void ps_change()
{
if (InvokeRequired)
{
BeginInvoke(new Action(ps_change));
return;
}
lblValueActualVoltage.Text = ps.ActualVoltage.ToString();
lblValueActualCurrent.Text = ps.ActualCurrent.ToString();
lblHexCur.Text = ps.HexActualCurrent1;
lblHexVol.Text = ps.HexActualVoltage1;
}
Why?
i call the properties setter not on the GUI thread.
Controls on the form cannot be manipulated from a non-GUI thread. This is because Windows Forms controls are bound to Windows handles, and Windows handles are owned by threads. If a control (Form or Label in this case) is created on one thread (most likely the main application thread), operations on its handle (e.g. changing the text) cannot be performed on a different thread.
To resolve this, the Control class has methods InvokeRequired, Invoke, and BeginInvoke:
InvokeRequired indicates whether the control belongs to a different thread (true) or to the current thread (false).
When InvokeRequired is true, either Invoke or BeginInvoke must be used. Both these methods marshal another delegate to the GUI thread that owns the control. All control manipulations must be performed from that delegate.
The difference between the two methods is that Invoke blocks the calling thread until the delegate is executed on the GUI thread, whereas BeginInvoke just submits invocation to the queue and returns immediately. The marshaling is performed by a special Windows message sent to a window message queue.
More info here: https://msdn.microsoft.com/en-us/library/system.windows.forms.control.invokerequired(v=vs.110).aspx
The usual pattern of implementing this is check InvokeRequired inside an event handler, and if it's true, call either Invoke or BeginInvoke and supply delegate to this same event handler again. The handler is then re-invoked on the GUI thread, where InvokeRequired is false, and then the code safely manipulates form controls.

I finally figure out the reason. I use Thread.Sleep() on main thread. At the time ActualVoltage changes, the main thread is sleeping, thats why the GUI does not update. To handle this, i use BackGroudWorker and everything is fine now.

Related

Form doesn't animate properly

In my application i have two Forms (that's my 1st quite big app)
After clicking start button in parent form i want loading panel to appear, and some logic to be done.
Loading panel (it is just another widowss form) contains bunifu loading circle animation (and some text).
Logic part is responsible for collecting names from directory tree, then replacing some text in Ms.Word files on the tree.
When i open loading panel without executing the logic, loading panel is animated properly and everything works fine.
private void bunifuFlatButton1_Click(object sender, EventArgs e)
{
int x = this.Location.X+this.Width/2-75;
int y = this.Location.Y +this.Height/2-175;
Loader_panel LP = new Loader_panel();
LP.Left = x;
LP.Top = y;
LP.Show();
//System.Threading.Thread.Sleep(5000); \\this doesn't help animation to start
if (FormLogic._dataList.Count > 0) \\Here Logic part starts
{
for (int i = 0; i < FormLogic._dataList.Count; i++)
GetDir.GetTarget(FormLogic._dataList[i]);
/*foreach (var directory in FormLogic._dataList)
GetDir.GetTarget(directory);*/
LogList.Items.Add(DateTime.Now + "List isn't empty");// for testing
FormLogic.ClearData();
}
LP.Close();
}
After enabling logic loading panel appears (appearance isn't smooth), but animation doesn't work (it starts to work only when logic part did the work - i tested it by disabling LP.Close(). What can be reason of this problem ?
Additinal question. In .NET environment the code is compiled to work with multiple processor threads or i have to do it manually ?
EDIT 06/08/2018 7:21 CEST
I cant access LogList from GetDir Method (due to processing it by other thread).
I tried multiple Invoke construction, but none of it seemed to work ;/
I am just too rookie to figure it out. I specified more details in code below:
namespace Docr
{
public partial class DocrForm : Form
{
.....
private async void Button1_Click(object sender, EventArgs e)
{
int x = this.Location.X + this.Width / 2 - 75;
int y = this.Location.Y + this.Height / 2 - 175;
Loader_panel LP = new Loader_panel();
LP.Left = x;
LP.Top = y;
LP.Show(); //animation
int count = FormLogic._dataList.Count;
var list = FormLogic._dataList;
await Task.Run(() =>// processing logic during showing animation
{
if (count > 0)
{
for (int i = 0; i < count; i++)
{
GetDir.GetTarget(list[i],LogList); // Passing LogList as and argument
}
Invoke((Action)(() => { LogList.Items.Add(DateTime.Now + "Hi LogList"); }));\\ works fine
}
});
FormLogic.ClearData();
LP.Close();
}
....
}
namespace DocrLogic
{
class GetDir
{
.....
static public void GetTarget(string UserDirectory, ListBox List)// passing ListBox as an Argument
{
var path = UserDirectory;
var TargetDir = new DirectoryInfo(path);
var AllDocs1 = TargetDir.GetFiles("*.doc*", SearchOption.AllDirectories);
var ProperPrefixes = new List<string> { };
Invoke((Action)(() => { List.Items.Add(DateTime.Now + "Hi Log List, GetDir here"); })); // THIS DOESN'T WORK
....
}
.....
}
}
You need to make the method async, then use await to wait for the logic to complete. This way the LP form won't be interrupted by the heavy logic.
private async void bunifuFlatButton1_Click(object sender, EventArgs e)
{
int x = this.Location.X+this.Width/2-75;
int y = this.Location.Y +this.Height/2-175;
Loader_panel LP = new Loader_panel();
LP.Left = x;
LP.Top = y;
LP.Show();
int count = FormLogic._dataList.Count;
var list = FormLogic._dataList;
await Task.Run(()=>
{
if(count > 0)
{
for (int i = 0; i < count; i++)
{
GetDir.GetTarget(list[i]);
}
this.Invoke(() => { LogList.Items.Add(DateTime.Now + "List isn't empty"); });
}
});
FormLogic.ClearData();
LP.Close();
}
You have to make your code thread-safe by using Invoke to access not-thread-safe objects such as UI objects, otherwise, it will throw a System.Threading.ThreadAbortException or a System.InvalidOperationException.
Invoke syntax may differ based on your project but you may
see this post to understand the proper ways of using Invoke()
update
You must never try to access a UI object outside invoke. invoke is provided by System.Windows.Forms.Control and depends on the thread which originally created that control. therefore having Invoke on some other random class simply does not work.
In the second part, you need to change
public static void GetTarget(string UserDirectory, ListBox List)// passing ListBox as an Argument
{
...
Invoke((Action)(() => { List.Items.Add(DateTime.Now + "Hi Log List, GetDir here"); })); // THIS DOESN'T WORK
}
to
(you need to send the whole invoke line as the action parameter)
public static void GetTarget(string UserDirectory, Action action)// passing the action as an Argument
{
...
action();
}
or
(you need to set Dispatcher to LogList before starting the Task)
public static Control Dispather;
public static void GetTarget(string UserDirectory)// passing the action as an Argument
{
...
Dispather.Invoke((Action)(() => { List.Items.Add(DateTime.Now + "Hi Log List, GetDir here"); }));
}

"Collection was modified; enumeration operation may not execute" when running the same background worker on 2+ instances of a form simultaneously

I have a c# windows form where the user enters a set of parameters, and those parameters are then analyzed against a set of data to return a result. This analysis takes place on a background worker, by initializing a Backtest object and iterating over a string list symbolParams built from the values passed in through the form. When running the worker on one form, it works properly.
However, if I open up a second form, put in a new set of parameters, and run the worker on that form while the worker on the first form is still running, I get a "Collection was modified" error on the string list.
Seems as though the two background workers are affecting each other's symbolParams list somehow. What's happening? How can this be fixed to allow multiple instances of this form to run this background worker simultaneously?
OptimizerForm.cs
public partial class OptimizerForm : Form
{
public static List<List<String>> backtestSymbolParams = new List<List<String>> { };
private void button15_Click(object sender, EventArgs e)
{
//Get parameters from the form
//Make a list for every param
string[] startEndTimes = textBox3.Text.Split(',');
string[] incrementPrices = textBox4.Text.Split(',');
string[] incrementSizes = textBox5.Text.Split(',');
string[] autoBalances = textBox6.Text.Split(',');
string[] hardStops = textBox7.Text.Split(',');
//Add every combo to symbol test params
for (int a = 0; a < startEndTimes.Length; a++)
{
for (int b = 0; b < incrementPrices.Length; b++)
{
for (int c = 0; c < incrementSizes.Length; c++)
{
for (int d = 0; d < autoBalances.Length; d++)
{
for (int f = 0; f < hardStops.Length; f++)
{
backtestSymbolParams.Add( new List<string> { symbol, startEndTimes[a].Split('-')[0], startEndTimes[a].Split('-')[1], incrementPrices[b],
incrementSizes[c], autoBalances[d], hardStops[f] });
}
}
}
}
}
backgroundWorker1.RunWorkerAsync();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
//Initialize Backtest instance with parameters gathered from the form
Backtest backtest = new Backtest(backtestSymbolParams, backtestSymbolDates, sender as BackgroundWorker);
TestResult testResult = new TestResult();
//Run the analysis
testResult = backtest.Run();
e.Result = testResult;
}
}
Backtest.cs
//Backtest Constructor
public Backtest(List<List<string>> _symbolParams, Dictionary<string, List<string>> _symbolDates, BackgroundWorker _bw)
{
symbolParams = _symbolParams;
symbolDates = _symbolDates;
bw = _bw;
}
//Backtest.Run()
public TestResult Run()
{
int symbolCount = 1;
//Collection modified exception occurs here
foreach (List<string> symbolParam in symbolParams) {
//do stuff
}
}
It seems like your symbolParams variable in Backtest class is static, so simply mark it as private field for your class.
Also you have some issues with naming standards - method parameters should not start with _, though fields should, so your constructor should looks like:
private List<List<String>> _symbolParams = new List<List<String>> { };
//Backtest Constructor
public Backtest(List<List<string>> symbolParams,
Dictionary<string, List<string>> symbolDates,
BackgroundWorker bw)
{
_symbolParams = symbolParams;
_symbolDates = symbolDates;
_bw = bw;
}
And, as far as I can see, you're using BackgroundWorker as Task so probably you should use TPL itself, without old legacy classes

Sync a progressbar with function C#

First of all, My code is written in Windows Form Application - C#.
I need to execute a method (which is very modular and it's runtime is depends on how much physical memory you have used in your system), and while this method is running, I want to present to the user a progressbar. I don't know how to sync the progressbar, with the function's runtime.
EDIT: HERE IS MY CODE:
public SystemProp()
{
// Getting information about the volumes in the system.
this.volumes = getVolumes();
for (int i = 0; i <volumes.Length; i++)
{
// Create a txt file for each volume.
if (!System.IO.File.Exists(dirPath + volumes[i].Name.Remove(1) + #".txt"))
{
using (FileStream fs = File.Create(dirPath + volumes[i].Name.Remove(1) + #".txt"))
{
}
}
// Treescan function for each Volume.
TreeScan(volumes[i].Name);
}
}
private bool isSafe()
{ return true; }
private DriveInfo[] getVolumes()
{
DriveInfo[] drives = DriveInfo.GetDrives();
return drives;
}
private void TreeScan(string sDir)
{
try
{
foreach (string f in Directory.GetFiles(sDir))
{
using (FileStream aFile = new FileStream(dirPath + sDir.Remove(1) + #".txt", FileMode.Append, FileAccess.Write))
using (StreamWriter sw = new StreamWriter(aFile)) { sw.WriteLine(f); }
}
foreach (string d in Directory.GetDirectories(sDir))
{
TreeScan(d);
}
}
catch (Exception)
{ }
}
The function is the treescan.
I would appriciate any kind of help,
Thank You Very Much!!
You should calculate progress and set ProgressBar.Value inside the method.
For example you have a for loop from 1 to 100.
for (int i = 0; i < 100; i ++)
{
//...
progressBar.Value = i;
}
You can also set a maximum value of progress using Maximum property.
So for a for loop from 1 to 10 you can set Maximum to 10 and don't calculate a progress.
progressBar.Maximum = 10;
for (int i = 0; i < 10; i ++)
{
//...
progressBar.Value = i;
}
If you can't split you method in different stages where you can change progress value, you can create a timer that ticks every one second and change the progress value in Tick event handler.
In order to set progress value based on runtime you can use Stopwatch.
Timer and Stopwatch should be started in the beginning of the method.
Timer timer = new Timer();
Stopwatch stopwatch = new Stopwatch();
void Method()
{
timer.Start();
stopwatch.Start();
//...
}
private void Timer_Tick(object sender, EventArgs e)
{
var progress = CalculateProgress (); // calculate progress
progressBar.Value = progress;
// or
progressBar.Value = stopwatch.Elapsed.Seconds;
}

C# winforms GUI will not accept data from other threads

Below is a function I have running in a while(true) loop in a thread running a Winforms GUI.
I have a button set to put text data into the inBuffer object. this always works, however when I place into the buffer object from a different thread, the data is detected, pulled, and printed out in the Console.out.WriteLine statement, however it never shows up in the Display (textBox) object
public void put()
{
string strDisplayMe = ModemKind.MainClass.inBuffer.MkRequest();
if (strDisplayMe != "")
{
Console.Out.WriteLine("FOUND SOMETHING IN BUFFER: " + strDisplayMe);
char[] DisplayMeArr = strDisplayMe.ToCharArray ();
for (int i = 0; i <= DisplayMeArr.Length -1; ++i)
{
this.display.Text += DisplayMeArr [i];
Thread.Sleep (100);
}
this.display.Text += '\n';
}
}
EDIT: this is a separate class from what is feeding it data through the static buffer objects
Only the main window thread can access/change controls... if you want update the UI from the thread that runs the loop, you need to sync the call with the main thread using the Control.Invoke method.
For instance...
public void put()
{
string strDisplayMe = ModemKind.MainClass.inBuffer.MkRequest();
if (strDisplayMe != string.Empty)
{
char[] DisplayMeArr = strDisplayMe.ToCharArray();
for (int i = 0; i <= DisplayMeArr.Length -1; ++i)
{
this.UpdateUI(() => { this.display.Text += DisplayMeArr[i]; });
Thread.Sleep(100);
}
this.UpdateUI(() => { this.display.Text += '\n'; });
}
}
private void UpdateUI(Action handler)
{
this.Invoke(handler);
}
You can use MethodInvoker to access TextBox from other thread then GUI thread. Make a method to access the TextBox using MethodInvoker. you are assigning text multiple times consider assigning it once for performance, and use StringBuilder for string concatenation.
public void put()
{
string strDisplayMe = ModemKind.MainClass.inBuffer.MkRequest();
if (strDisplayMe != "")
{
Console.Out.WriteLine("FOUND SOMETHING IN BUFFER: " + strDisplayMe);
char[] DisplayMeArr = strDisplayMe.ToCharArray ();
for (int i = 0; i <= DisplayMeArr.Length -1; ++i)
{
AssignToTextBox(this.display, this.display.Text + DisplayMeArr [i]);
Thread.Sleep (100);
}
AssignToTextBox(this.display, this.display.Text + '\n');
}
}
void AssignToTextBox(TextBox txtBox, string value)
{
if(txtBox.InvokeRequired)
{
txtBox.Invoke(new MethodInvoker(delegate { txtBox.text = value; }));
}
}
Edit You can use BeginInvoke instead of Invoke to call it asynchronously. Read more about the Invoke and BeginInvoke in this question.
void AssignToTextBox(TextBox txtBox, string value)
{
txtBox.BeginInvoke(new Action(()=>txtBox.Text = value ));
}

Unit Testing NAudio with ManualResetEvent Locks Up Test

Alright, I'm trying to unit test NAudio against a wrapper I created for a recording session, here is the code that starts and stops a recording session ...
public void StartRecording(string claimNo, string ip_no, string ip_name)
{
if (this.IsRecording)
{
return;
}
this.Recordings.Add(new RecordingTrack(claimNo, ip_no, ip_name));
if (this.MicrophoneLevel == default(float))
{
this.MicrophoneLevel = .75f;
}
_aggregator.Reset();
_input = new WaveIn();
_input.WaveFormat = _waveFormat;
_input.DataAvailable += (s, args) =>
{
_writer.Write(args.Buffer, 0, args.BytesRecorded);
byte[] buffer = args.Buffer;
for (int index = 0; index < args.BytesRecorded; index += 2)
{
short sample = (short)((buffer[index + 1] << 8) | buffer[index + 0]);
float sample32 = sample / 32768f;
_aggregator.Add(sample32);
}
if (this.DataAvailable != null)
{
this.DataAvailable(s, args);
}
if (!this.IsRecording)
{
_writer.Close();
_writer.Dispose();
_writer = null;
}
};
_input.RecordingStopped += (s, args) =>
{
_input.Dispose();
_input = null;
if (this.RecordingStopped != null)
{
this.RecordingStopped(s, args);
}
};
_writer = new WaveFileWriter(this.CurrentRecording.FileName, _input.WaveFormat);
_input.StartRecording();
this.IsRecording = true;
}
public void StopRecording()
{
if (!this.IsRecording)
{
return;
}
this.CurrentRecording.Stop();
this.IsRecording = false;
_input.StopRecording();
}
... and below is my unit test. I'm using a ManualResetEvent to assert the success of the event being fired and it's declared like this ...
private ManualResetEvent _eventRaised = new ManualResetEvent(false);
... however, the issue is that the test below simply locks up and the event is never fired. Can you confirm that the issue is that the WaitOne is not allowing the event to fire because it's locking the same thread?
bool success = false;
_eventRaised.Reset();
var target = new RecordingSession();
target.StartRecording("1", "01", "Test Name");
target.RecordingStopped += (s, args) =>
{
success = (target.CurrentRecording.Duration.TotalSeconds > 4);
_eventRaised.Set();
};
Thread.Sleep(4000);
target.StopRecording();
_eventRaised.WaitOne();
Assert.IsTrue(success);
And if so, can you help me with this test? I need some enlightenment.
I've used the ManualResetEvent many times to test events on other classes and it's worked, but something is different here.
You'll never get an event because the default constructor of WaveIn uses windowed callbacks, and you are not running your unit test on a GUI thread. You should use WaveInEvent instead to work on a non-gui thread. In the very latest NAudio code an InvalidOperationException should be thrown to prevent you from making this mistake.
It is possible that the event is fired before you have connected to it if the _input member is working in its own thread. Hence, the manual reset event will never get set, causing it to wait forever/lock up your unit test.
So perhaps try re-order your definition to:
target.RecordingStopped += (s, args) =>
{
success = (target.CurrentRecording.Duration.TotalSeconds > 4);
_eventRaised.Set();
};
target.StartRecording("1", "01", "Test Name");

Categories

Resources