How to update UI form when process is complete? - c#

So my main form has a button that I enable when this really long copy process is complete. This copy process happens every 10 min and checks for updates etc. I'm stuck on how to get the process to notify the mainform that it's finished copying. Here's what I have so far:
public partial class mainForm : Form
{
....//initialize some stuff
private void TimerEventProcessor(object sender, EventArgs e)
{
....
copy.GetNewCopy();
}
}
class Copy
{
private bool IsCopyComplete;
....
public void GetNewCopy()
{
Process proc = new Process();
IsCopyComplete = false;
proc.EnableRaisingEvents = true;
proc.Exited += new EventHandler(myProcess_Exited);
proc.Start();
}
private void myProcess_Exited(object sender, System.EventArgs e)
{
IsCopyComplete = true;
// how to trigger mainform that process is complete?
}
}

You can call Invoke:
private void myProcess_Exited(object sender, System.EventArgs e)
{
IsCopyComplete = true;
button1.Invoke(new Action(() => button1.Enabled = true));
}
Here's an article on making thread-safe calls to the UI.
There are several ways to create a thread. My favorite, due to its built-in functionality for updating the UI, is the BackgroundWorker Thread. (Though it's not the right tool for every situation).
You could pass a reference of the form to the class, though that usually seems wrong somehow. The class shouldn't be aware of your form's UI components.
What I'd do is create an EventHandler that the Exited event can publish to. (You can attach to multiple events, so if you still need to set IsCopyComplete = true, then just leave that event too.) When the Exited event fires, it'll call the "ProcessExited" EventHandler without knowing whether anything else subscribed to it.
class Copy
{
public event EventHandler ProcessExited;
private bool IsCopyComplete;
....
public void GetNewCopy()
{
Process proc = new Process();
IsCopyComplete = false;
proc.EnableRaisingEvents = true;
proc.Exited += ProcessExited;
proc.Exited += new EventHandler(myProcess_Exited);
proc.StartInfo = new ProcessStartInfo("cmd.exe"); // specify your process - replace cmd.exe with whatever's appropriate
proc.Start();
}
private void myProcess_Exited(object sender, System.EventArgs e)
{
IsCopyComplete = true;
}
}
Then in your main form, you can subscribe to the event.
public partial class mainForm : Form
{
....//initialize some stuff
public class mainForm()
{
...
// not sure where you're instantiating `copy` - you may have to move this
copy.ProcessExited += (s, a) =>
button1.Invoke(new Action(() => button1.Enabled = true));
...
}
private void TimerEventProcessor(object sender, EventArgs e)
{
copy.GetNewCopy();
}
}

There are many ways to do this. Here is one
Declare the Handler method in your Mainform and then sunscribe as below
proc.Exited += new EventHandler(MainForm1.myProcess_Exited);

Related

How to pass custom EventArgs to UI controls

Unfortunately I was not able to find relevant answer to my problem. I have a object encoder that has an event "VideoEncoding". It passes custom EncodingEventArgs that include various Properties like Progress, Size etc. I can output this info to Console or write to text file. But when I try to utilize it in WinForms I'm not able to pass that information to UI like label or progress bar. I tried different approaches. Background Worker seems like a good idea, The problem is that Background Worker cannot subscribe to VideoEncoding event, neither it will take my custom EventArgs. This is what i was able to put together. Maybe there is a different way to do it using delegates that would communicate with UI. Any suggestions are welcome. Thank you.
public partial class Form1 : Form
{
private BackgroundWorker bw;
int _progress;
public Form1()
{
InitializeComponent();
this.bw = new BackgroundWorker();
this.bw.DoWork += new DoWorkEventHandler(bw_DoWork);
this.bw.ProgressChanged += new ProgressChangedEventHandler(bw_ProgressChanged);
this.bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bw_RunWorkerCompleted);
this.bw.WorkerReportsProgress = true;
this.button1.Click += new EventHandler(button1_Click);
}
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
this.label1.Text = "The job is: " + e.Result.ToString();
this.button1.Enabled = true;
}
private void bw_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.label2.Text = e.ProgressPercentage.ToString() + "% complete";
}
private void bw_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = (BackgroundWorker)sender;
this.Encode
worker.ReportProgress(_progress);
e.Result = "Completed";
}
private void button1_Click(object sender, EventArgs e)
{
if (!this.bw.IsBusy)
{
this.bw.RunWorkerAsync();
this.button1.Enabled = false;
}
}
public void Encode()
{
var job = new EncodingJob();
//setup encoding job
//subscribe to an event
ffmpeg.VideoEncoding += GetProgress;
ffmpeg.DoWork(job);
}
public void GetProgress(object sender, EncodingEventArgs e)
{
_progress = (int)e.Progress;
}
}
Try to call the background workers ReportProgress in the GetProgress Method. How should the form know your progress if you don't signalize it?

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 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();
}

Why is a disabled button clickable?

This case is using C# WPF. I want to instantly disable a button after clicking it to prevent clicking it twice in short succession. I disabled the button in OnClick_Event but still clickable.
Part of source is as below.
private void Button_Click_UpdateBurndownChart(object sender, RoutedEventArgs e)
{
if(threadNotWorking)
{
updateButton.IsEnabled = false;
startWorkThread();
}
}
private void startWorkThread()
{
... ...
//after finish required process
updateButton.IsEnabled = true;
}
Is there any way to accomplish this?
you may want to use a dispatcher, there is probably a threading problem (callback function running on seperate thread and trying to access ui which runs on another thread). try this . .
updateButton.Dispatcher.BeginInvoke(
new ThreadStart(() => updateButton.IsEnabled = false),
System.Windows.Threading.DispatcherPriority.Input, null);
instead of
updateButton.IsEnabled = false;
What happens if you were instead to change the order of your events from:
updateButton.IsEnabled = false;
startWorkThread();
To
startWorkThread();
updateButton.IsEnabled = false;
Let me know how this goes.
What it looks like is that you are starting your thread then immediatly enabling your button before your thread has finished. You would be better off using a BackgroundWorker and enable your Button in the RunWorkerCompleted Event. Though you can do something similar by enabling your button using a BeginInvoke at the end of your Process.
public void doWork()
{
System.Threading.Thread.Sleep(10000); //Simulating your Process
Dispatcher.BeginInvoke(new System.Threading.ThreadStart(delegate() { updateButton.IsEnabled = true; }), System.Windows.Threading.DispatcherPriority.Background);
}
Example with BackgroundWorker
using System.ComponentModel;
public partial class MainWindow : Window
{
BackgroundWorker bgw;
public MainWindow()
{
InitializeComponent();
bgw = new BackgroundWorker();
bgw.DoWork += new DoWorkEventHandler(bgw_DoWork);
bgw.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bgw_RunWorkerCompleted);
}
void bgw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
updateButton.IsEnabled = true;
}
void bgw_DoWork(object sender, DoWorkEventArgs e)
{
System.Threading.Thread.Sleep(10000); //Simulating your work
}
private void startWorkThread()
{
bgw.RunWorkerAsync();
}
private void updateButton_Click(object sender, RoutedEventArgs e)
{
if (bgw.IsBusy != true)
{
updateButton.IsEnabled = false;
startWorkThread();
}
}
}

cross-thread calls?

This is mt first time trying to write a not web based program, and my first time writing anything in C#.
I need a program that monitors folders, but I can't get it to work.
I have used the example from this post Using FileSystemWatcher with multiple files but is trying to make it a form.
My current problem comes in the ProcessQueue function where fileList apparently is defined in another thread.
Whenever a file is actually submitted to the watched folder I get an error that using fileList is a cross thread call
Can anyone explain this error to me, and how to fix it?
namespace matasWatch
{
public partial class Form1 : Form
{
private int n = 1;
private bool isWatching = false;
private List<string> filePaths;
private System.Timers.Timer processTimer;
private string watchedPath;
private FileSystemWatcher watcher;
public Form1()
{
filePaths = new List<string>();
watchedPath = "C:\\Users\\username\\Desktop\\test";
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
if (!isWatching)
{
button1.Text = "Stop";
isWatching = true;
watcher = new FileSystemWatcher();
watcher.Filter = "*.*";
watcher.Created += Watcher_FileCreated;
watcher.Error += Watcher_Error;
watcher.Path = watchedPath;
watcher.IncludeSubdirectories = true;
watcher.EnableRaisingEvents = true;
}
else {
button1.Text = "Watch";
isWatching = false;
watcher.EnableRaisingEvents = false;
watcher.Dispose();
watcher = null;
}
}
private void Watcher_Error(object sender, ErrorEventArgs e)
{
// Watcher crashed. Re-init.
isWatching = false;
button1_Click(sender, e);
}
private void Watcher_FileCreated(object sender, FileSystemEventArgs e)
{
filePaths.Add(e.FullPath);
if (processTimer == null)
{
// First file, start timer.
processTimer = new System.Timers.Timer(2000);
processTimer.Elapsed += ProcessQueue;
processTimer.Start();
}
else{
// Subsequent file, reset timer.
processTimer.Stop();
processTimer.Start();
}
}
private void ProcessQueue(object sender, ElapsedEventArgs args)
{
try
{
fileList.BeginUpdate();
foreach (string filePath in filePaths)
{
fileList.Items.Add("Blaa");
}
fileList.EndUpdate();
filePaths.Clear();
}
finally
{
if (processTimer != null)
{
processTimer.Stop();
processTimer.Dispose();
processTimer = null;
}
}
}
}
}
I assume that fileList is a windows forms control. The ProcessQueue method is called from a timer thread which is by default a background thread. The fileList control resides in the UI thread. You need to use the Invoke() method of the form passing it in a delegate the updates the fileList control.
Invoke(new Action(() =>
{
fileList.BeginUpdate();
foreach (string filePath in filePaths)
{
fileList.Items.Add("Blaa");
}
fileList.EndUpdate();
filePaths.Clear();
}));
Try using System.Windows.Forms.Timer instead of System.Timers.Timer so the timer tick event is executed on the UI thread.
See here for more details.

Categories