C# wpf adding children to Layout asynchronously - c#

I have a WPF application where I am running a powershell script that returns a sequence of strings. I want to be able to update my UI asynchronously, but the UI will only update once the method is complete. How do I update the UI asynchronously? I have been reading a lot of other similar examples like this one: WPF User Control children not updating c# Maybe this works, but I could be implementing it incorrectly?
My code:
private void NextButton_Click(object sender, RoutedEventArgs e)
{
RunPreCheck();
}
public void RunPreCheck()
{
var startInfo = GetPowerShellStartInfo();
proc = Process.Start(startInfo);
while (!proc.StandardOutput.EndOfStream)
{
string line = proc.StandardOutput.ReadLine();
var myObject= new MyCustomObject(line);
myGrid.Children.Add(myObject);
}
}

Run RunPreCheck() on a background thread:
private void NextButton_Click(object sender, RoutedEventArgs e)
{
Task.Run(RunPreCheck);
}
public void RunPreCheck()
{
var startInfo = GetPowerShellStartInfo();
proc = Process.Start(startInfo);
while (!proc.StandardOutput.EndOfStream)
{
string line = proc.StandardOutput.ReadLine();
myGrid.Dispatcher.BeginInvoke(
new Action(() => myGrid.Children.Add(new MyCustomObject(line))));
}
}

Related

How to update label when the process is complete?

private void button1_Click_1(object sender, EventArgs e)
{
lbl_startingTest.Text = "Flashing DUT..";
Process fls1 = new Process();
fls1.StartInfo.UseShellExecute = false;
//fls1.StartInfo.RedirectStandardOutput = true;
fls1.StartInfo.FileName = "C:\\test\\test\\bin\\Debug\\flash.bat";
fls1.StartInfo.CreateNoWindow = true;
fls1.Start();
//
fls1.WaitForExit();
}
Tried different methods, but no result achieved. Please suggest. I am trying to update the label "Flashing DUT..." to "Flashing Complete" when fls1 process completes execution.
fsl1 process is a Batch file named flash.bat

How to know if external application was closed?

How can I say if a winform whas closed do ...?
bool isRunning = false;
foreach (Process clsProcess in Process.GetProcesses())
{
if (clsProcess.ProcessName.Contains("Notepad"))
{
isRunning = true;
break;
}
}
The code above always checks if the process exists but the code is slow for what I want it to do.So is there a way to check if the Notepad process was actually closed instead of always looping to see if its there?
You can use Win32_ProcessStopTrace which indicates that a process is terminated.
ManagementEventWatcher watcher;
protected override void OnLoad(EventArgs e)
{
base.OnLoad(e);
watcher = new ManagementEventWatcher("Select * From Win32_ProcessStopTrace");
watcher.EventArrived += new EventArrivedEventHandler(watcher_EventArrived);
watcher.Start();
}
void watcher_EventArrived(object sender, EventArrivedEventArgs e)
{
if ((string)e.NewEvent["ProcessName"] == "notepad.exe")
MessageBox.Show("Notepad closed");
}
protected override void OnFormClosed(FormClosedEventArgs e)
{
watcher.Stop();
watcher.Dispose();
base.OnFormClosed(e);
}
Don't forget to add a reference to System.Management and add using System.Management;
Note
If you want to monitor closing of an specific instance of notepad which you know, you can use such criteria:
if ((UInt32)e.NewEvent["ProcessID"]==knownProcessId)
If you want to check if any instance of notepad is open, you can use such criteria:
if (System.Diagnostics.Process.GetProcessesByName("notepad").Any())
The EventArrived will raise in a different thread than UI thread and if you need to manipulate UI, you need to use Invoke.
Above method notifies you about closing of all processes, regardless of the time they are opened, before or after your application run. If you don't want to notified about the processes which may be opened after your application starts, you can get existing notepad processes and subscribe to their Exited event:
private void Form1_Load(object sender, EventArgs e)
{
System.Diagnostics.Process.GetProcessesByName("notepad").ToList()
.ForEach(p => {
p.EnableRaisingEvents = true;
p.Exited += p_Exited;
});
}
void p_Exited(object sender, EventArgs e)
{
MessageBox.Show("Notepad closed");
}
This should do the trick. It will create a event for you when the process dies. No need to loop through all the process.
public static event EventHandler ProcessDied;
public void CheckForProcess()
{
InitializeComponent();
ProcessDied += new EventHandler(Process_Died);
AttachProcessDiedEvent("notepad", ProcessDied);
}
private void AttachProcessDiedEvent( string processName,EventHandler e )
{
Process isSelectedProcess=null;
foreach (Process clsProcess in Process.GetProcesses())
{
if (clsProcess.ProcessName.Contains(processName))
{
isSelectedProcess = clsProcess;
break;
}
}
if(isSelectedProcess!=null)
{
isSelectedProcess.WaitForExit();
}
if(e!=null)
{
e.Invoke(processName, new EventArgs());
}
}
private void Process_Died(object sender, EventArgs e)
{
//Do Your work
}
Let me know if there are any issues.
you can do it without looping but dont know if its much faster :
bool isRunning = Process.GetProcessesByName("NotePad").FirstOrDefault() != null;
or
bool isRunning = Process.GetProcessesByName("notepad").Any();
I got this from here Check if a specific exe file is running

How to run different timer inside a timer without pausing the GUI?

I have a class SendCountingInfo() and it will send a message to server every 5 minutes. The code inside this class are:
public void StartSendCountingInfo()
{
DoStartSendCountingInfo(300000);
}
private void DoStartSendCountingInfo(int iMiSecs)
{
_pingTimer = new System.Timers.Timer(iMiSecs);
_pingTimer.Elapsed += new System.Timers.ElapsedEventHandler(pingTimer_Elapsed);
_pingTimer.Start();
}
void pingTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
PingRemoteHost();
}
When I try to call it in the Windows Form class, it didn't work.
But, when I remove the timer and call PingRemoteHost() directly, it works. However, the form didn't load properly. It shows blank screen but the method PingRemoteHost() work.
Here is the code inside the windows form:
private void Layout_Load(object sender, EventArgs e)
{
tSystemChecker = new System.Timers.Timer(1000);
tSystemChecker.Elapsed += new System.Timers.ElapsedEventHandler(tSystemChecker_Elapsed);
tSystemChecker.Start();
this.WindowState = FormWindowState.Maximized;
}
void tSystemChecker_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
UIThreadWork(this, delegate
{
try
{
SuspendLayout();
DoCheckHardwareStatus();
DoCheckLanguage();
SendCountingInfo sci = new SendCountingInfo();
sci.StartSendCountingInfo();
}
catch (Exception exp)
{
System.Diagnostics.Debug.WriteLine(exp.Message);
System.Diagnostics.Debug.WriteLine(exp.Source);
System.Diagnostics.Debug.WriteLine(exp.StackTrace);
}
ResumeLayout(true);
});
}
Do you have any idea what's wrong?
Use a thread and see if the problem persist
using System.Threading;
//Put this where you want to start the first timer
Thread thread = new Thread(dowork =>
{
public void StartSendCountingInfo();
}
If you are updating the GUI use for your controls
guicontrol.BeginInvoke((MethodInvoker)delegate()
{
guicontrol.Text = "aa";
//etc
});

How to timer is running while process.waiforexit()?

My form contain two controls: button1 and timer1
timer1.Interval=1000; timer1.Enable=true;
While click button1, application on windows will start. Ex:notepad will show.
But timer1 is not running while notepad is showing.
How to timer1 so running ??.
My code:
private void button1_Click(object sender, EventArgs e)
{
Process pro = new Process();
pro.StartInfo.FileName = "notepad";
pro.StartInfo.Arguments = "";
pro.Start();
pro.WaitForExit();
}
private void timer1_Tick(object sender, EventArgs e)
{
DateTime dtime = DateTime.Now;
string date_time = dtime.ToString("dd/MM/yyyy HH:mm:ss");
textBox2.Text = date_time;
}
From Process.WaitForExit:
Instructs the Process component to wait indefinitely for the associated process to exit.
Your timer is trying to invoke timer1_Tick, but your UI Thread is currently stuck waiting for the process to exit, which it wont.
You have two choices to work around this:
Simply remove the call to WaitForExit if you dont really need to wait
If you do need to be notified when the process exits, set Process.EnableRaisingEvents to true and register to the Process.Exited event
The WaitForExit() is "blocking" your interface from refreshing,the call just waits there for the process to exit. As an alternative if you need to do something when the process as exited do this:
private void button1_Click(object sender, EventArgs e)
{
Process pro = new Process();
pro.StartInfo.FileName = "notepad";
pro.StartInfo.Arguments = "";
//if you need to do something when the process exits do this:
pro.EnableRaisingEvents = true;
pro.Exited += new EventHandler(pro_Exited);
pro.Start();
//pro.WaitForExit();
}
void pro_Exited(object sender, EventArgs e)
{
//do what you need here...
}
Instead you could start the process with a BackGroundWorker.
pro.WaitForExit(); makes UI thread to freeze so it can't update.
To stop user from actions, you can disable some controls, while process is running. You can subscribe to process.Exited event and enable your controls, when user closes the process.
private void button1_Click(object sender, EventArgs e)
{
Process pro = new Process();
pro.StartInfo.FileName = "notepad";
pro.StartInfo.Arguments = "";
pro.Start();
button1.Enabled = false;
pro.EnableRaisingEvents = true;
pro.Exited += pro_Exited;
}
void pro_Exited(object sender, EventArgs e)
{
button1.Invoke((MethodInvoker) delegate { button1.Enabled = true; });
}
Update
As another answer suggested you should set EnableRaisingEvents property to true.
Also pro_Exited method will run in a different thread, so you need to use Control.Invoke method to change UI.
Update 2
If can't delete pro.WaitForExit(); you can use another timer, because System.Windows.Forms.Timer is running in UI thread and is blocked with it.
private System.Threading.Timer timer = new System.Threading.Timer(Callback);
public Form()
{
InitializeComponent();
timer.Change(0, 1000);
}
private void Callback(object state)
{
DateTime dtime = DateTime.Now;
string date_time = dtime.ToString("dd/MM/yyyy HH:mm:ss");
button1.Invoke((MethodInvoker)delegate { textBox1.Text = date_time; });
}
It will not update the textBox, when process is opened, but the timer will run and can do some work.
Update 3
In case of multiple processes you can count them and check number of active processes in pro_Exited method.
private volatile int activeProcessCount = 0;
private void pro_Exited(object sender, EventArgs e)
{
activeProcessCount--;
if (activeProcessCount == 0)
{
button1.Invoke((MethodInvoker) delegate { button1.Enabled = true; });
}
}
private void button1_Click(object sender, EventArgs e)
{
//code
activeProcessCount = 2;
pro1.Start();
pro2.Start();
}

Display the current variable value while looping

private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
string IDs = ID.Text;
string[] eachIDs = Regex.Split(IDs, "\n");
foreach (var eachID in eachIDs)
{
getContent(eachID);
titleBox.Text = "Done";
}
}
private void getContent(string value)
{
label1.Text = value;
Thread.Sleep(5000);
}
I will give 4 id's as Input say "IDNUMBER01, IDNUMBER02, IDNUMBER03, IDNUMBER04" each in a new line in Rich Text Box.
The code splits them successfully. I want to show the Value of the ID being used in the current loop in a Label Text.
Problem with my code is it shows only the last ID which goes through the loop.
Probably your UI freezing and you can't see the changes.Try this, use async/await feature:
private async void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
string IDs = ID.Text;
string[] eachIDs = Regex.Split(IDs, "\n");
foreach (var eachID in eachIDs)
{
await getContent(eachID);
titleBox.Text = "Done";
}
}
private async Task getContent(string value)
{
label1.Text = value;
await Task.Delay(5000);
}
This is because the UI is only Updated after the execution of this code, since they are executing in the same thread. You will need to open a thread, run this code, and call the dispatcher (or the Control.BeginInvoke if this app is Winforms) to update the UI.
EDIT
Try this:
private void button1_Click(object sender, EventArgs e)
{
button1.Enabled = false;
string IDs = ID.Text;
new System.Threading.Thread(() =>
{
string[] eachIDs = Regex.Split(IDs, "\n");
foreach (var eachID in eachIDs)
{
getContent(eachID);
titleBox.BeginInvoke((Action) delegate { titleBox.Text = "Done"; });
}
}).Start();
}
private void getContent(string value)
{
label1.BeginInvoke((Action) delegate { label1.Text = value; });
Thread.Sleep(5000);
}
In your example, you'd be better using a timer to display your value text. You're only seeing the last ID because the loop is executing very quickly, and using Thread.Sleep within the foreach isn't going to fly.
You could use Application.DoEvents() before the Thread.Sleep, but a timer is still your better option ... imho.

Categories