Starting an external process and setting a value after it finishes - c#

I'm working on a WPF app which is set up to always be the topmost window. I've added a button to this app which launches an external program that allows the user to calibrate the touchscreen monitor our users will be interacting with.
I've can turn our mainwindow's topmost setting off and launch my app but I need to be able to set our MainWindow.Topmost to true after this external app exits.
It's been suggested that I add an event handler when starting the process that can reset topmost when the process ends. Event Handlers are new to me so I'm not sure how to do this. Can someone walk me through it?
Here's the code I have that currently disables topmost for my main window and launches my application. There's not much to it so far...
Application.Current.MainWindow.Topmost = false;
System.Diagnostics.Process.Start(#"C:\path\to\app.exe");
Many thanks.
(And I'll be reading up on event handlers and delegates this weekend!)

Create the Process, set EnableRaisingEvents to true and handle the Exited event:
Process p = new Process();
p.StartInfo.FileName = pathToApp;
p.EnableRaisingEvents = true;
p.Exited += OnCalibrationProcessExited; // hooks up your handler to the Process
p.Start();
// Now .NET will call this method when the process exits
private void OnCalibrationProcessExited(object sender, EventArgs e)
{
// set Topmost
}
From the comments thread, the Exited event gets raised on a worker thread, so you will need to do use Dispatcher.BeginInvoke to switch over to the UI thread to set Topmost:
private void OnCalibrationProcessExited(object sender, EventArgs e)
{
Action action = () => { /* set Topmost */ };
Dispatcher.BeginInvoke(DispatcherPriority.Normal, action);
}
(This assumes the code is in your Window class. If not, you will need to write something like Application.Current.MainWindow.Dispatcher.BeginInvoke(...) instead.)
Note I have separated creating and configuring the Process object from starting it. Although this is more verbose, it is necessary to ensure that all the event handling stuff is in place before the process starts -- otherwise the process could exit before you put the handler in place (unlikely, but theoretically possible!) and your handler would never get called.

You can wait for the process to exit via Process.WaitForExit:
Application.Current.MainWindow.Topmost = false;
var process = System.Diagnostics.Process.Start(#"C:\path\to\app.exe");
process.WaitForExit();
Application.Current.MainWindow.Topmost = true;
If you want to provide a timeout value to prevent the process from waiting forever, that is also possible. For example, if you wanted to wait for a maximum of 2 minutes, you could do:
process.WaitForExit(2 * 60000); // 60000ms/minute

Related

Open Winforms screen after every one minute if the form is closed

A form should open only when there is an event if there is no event it should not display on the screen. So Basically i thought of using a timer to do this. An exe will continously be running and after every minute it checks the db to see if there is data and if there is it shows up on the screen and will only be closed manually with user interaction. After a minute it checks again and displays the form if Data is present in the DB.
I used system.threading.Timer in Program.cs file to open a window after every minute.Below is the code
timer = new System.Threading.Timer((s) => {
EL.CustomMessageBox l = new EL.CustomMessageBox();
l.ShowDialog();
}, null, TimeSpan.Zero, 60000);
After certain time I see that this exe is still running in the taskmanager but even though there is data in the DB it stops showing up on the screen. Any help is appreciated.
System.Threading.Timer runs its callback on a threadpool thread. You should never use a threadpool thread for UI work, because:
They don't run a message dispatch loop.
You don't control when the thread gets recycled. UI windows have thread affinity and if their thread exits all the associated windows go poof immediately (you won't even get WM_DESTROY messages).
A normal Application.Run loop on the main thread, with a hidden main window and a UI timer will serve you much better.
I would pass my own custom ApplicationContext to Application.Run() in program.cs.
This will allow you to have NO INTERFACE until your conditions are met. The application will also continue to run (even when you close the Forms) until you explicitly call Application.Exit().
You can keep a reference to your Form at class level. This will help you decide if you need to work with the existing one, or create a new one.
Note that I'm using the System.Windows.Forms.Timer, not the threaded timer.
Something like...
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MyContext());
}
}
public class MyContext : ApplicationContext
{
private EL.CustomMessageBox l = null;
private System.Windows.Forms.Timer timer;
public MyContext()
{
timer = new System.Windows.Forms.Timer();
timer.Interval = (int)TimeSpan.FromMinutes(1).TotalMilliseconds;
timer.Tick += Timer_Tick;
timer.Start();
}
private void Timer_Tick(object sender, EventArgs e)
{
bool result = true; // hit the database and get an answer
if (result)
{
if (l == null || l.IsDisposed)
{
// no form has been created yet, or the previous one was closed
// create a new instance
l = new EL.CustomMessageBox();
l.Show();
}
else
{
// if we get in here, then the previous form is still being displayed
// if your form can be minimized, you might need to restore it
// if (l.WindowState == FormWindowState.Minimized)
// {
// restore the window in here?
// }
}
// update the form "l" with some data?
l.xxx = yyy;
}
}
}
I can't help but think that the other answers, massively technically correct as they are, don't actually solve the problem because they probably don't make sense if you aren't aware of how Windows works. Idle_Mind's is closest to what I'd do, though if the forms designer is familiar I'd go for a solution that basically just uses that - as such I present what I would do to solve the task you're faced with:
Have an app with one form (or make this form an autonomous one within another app, but for now maybe do it as a dedicated app for simplicity) - make a new Windows Forms project
Have a Timer (a Windows Forms timer, out of the toolbox, not a System.Threading timer) with an interval of 60000 and Enabled = true
Have a timer Tick event handler on your form (double click the timer in the tray under the form designer to attach an event handler) that queries the DB and finds if there are any messages
If there are new messages, adds them to a listbox or something, and calls this.Show() to show the form
Have an eventhandler attached to the FormClosing event so when the user clicks X, the form hides instead of closes:
private void MyForm_FormClosing(object sender, FormClosingEventArgs e)
{
if (e.CloseReason == CloseReason.UserClosing)
{
e.Cancel = true;
Hide();
}
}
Maybe have the FormClosing event clear the messages listbox. This way if the form opens and the user is on lunch, the messages will build up and build up, then they can read them and clear them by closing the form. Calling Show on an already-visible form does nothing, so the messages will just accumulate into the listbox if more messages come in and the form is already visible
Good quick rule of thumb; never use System.Threading Timer in a Windows Forms app. Use a timer out of the forms designer toolbox instead. Only use a threading timer if you're writing a service or Console app etc. For stability reasons, Windows controls absolutely must be accessed by the thread that originally created the control. Windows forms timer is aware of this and its Tick event can safely access the controls (a form is a control, showing it requires to access it) in a Forms app
You should call Invoke to execute your delegate on the thread that owns the control's underlying window handle.
Something like this should work:
timer = new System.Threading.Timer((s) => {
EL.CustomMessageBox l = new EL.CustomMessageBox();
l.Invoke((Action) () =>
{
l.ShowDialog();
});
}, null, TimeSpan.Zero, 60000);
Or even better, use this extension method:
public static void InvokeIfRequired(this Control c, MethodInvoker action)
{
if (c.InvokeRequired)
{
c.Invoke(action);
}
else
{
action();
}
}
And call it like this:
l.InvokeIfRequired(() => { l.ShowDialog(); });
Further information can be found at: https://learn.microsoft.com/en-us/dotnet/desktop/winforms/controls/how-to-make-thread-safe-calls-to-windows-forms-controls?view=netframeworkdesktop-4.8

Process.StartInfo clarifications c#

I'm trying to run another process from the main one using Process class.
AudioRecordProcess = new Process();
AudioRecordProcess.StartInfo.UseShellExecute = false;
AudioRecordProcess.StartInfo.FileName = #"RecordAudio\RecordAudio";
AudioRecordProcess.StartInfo.CreateNoWindow = true;
The process starts fine but:
Even if I set CreateNoWindow property when I run the code a window is created (the other process is a WPF project with just the main window);
When I try to close the process with closemainwindow it does close nothing. The window just goes under the main process' one.
If I try to kill the process from code it doesn't execute the instructions in my event closing main window, but if I close it from the taskbar it executes that routine. Shouldn't the actions be the same?
Any idea for this strange behavior?
Thanks
Class 'Process' is used to create and/or control and monitor specific process.
Only console application will obey CreateNoWindow = true, while
Windows Forms and WPF applications will ignore it.
Since you have started another process on the system, that process
will continue on its own and will not be terminated once you stop
parent process. However there are some resource lingering (especially
thread and handle related) in the parent process which prevents
parent process termination when its main window is closed.
killing interrupts normal process execution and forces stop as soon
as possible therefore it is normal that closing handlers are not
executed
EDIT:
For test create WPF application and put following code in it:
public MainWindow() {
InitializeComponent();
Loaded += MainWindow_Loaded;
Closed += MainWindow_Closed;
}
private Process process;
private void MainWindow_Closed(object sender, EventArgs e) {
// Log("WpfApp3.log", $"{DateTime.Now} Closed");
}
private void MainWindow_Loaded(object sender, RoutedEventArgs e) {
// Log("WpfApplication3.log", $"{DateTime.Now} Loaded");
process = new Process {
StartInfo = {
UseShellExecute = false,
// FileName = #"..\..\WpfApp2\bin\Debug\WpfApp2.exe",
FileName = #"..\..\CnslApp1\bin\Debug\CnslApp1.exe",
// FileName = #"..\..\WnFrmApp2\bin\Debug\WnFrmApp2.exe",
CreateNoWindow = true
}
};
process.Start();
var result = process.CloseMainWindow();
if (result) {
...
}
}
Then create three new projects; one for each of Console/Windows Forms/WPF. and enter correct path to the executable into 'FileName' paramter of the StartInfo. Then you can play with parameters to see how each will behave.
In my case, process.CloseMainWindow() did had no effect until was moved into another method because process was not started yet. But once moved into a button handler and executed it will terminate all three types (one can be used at the time with this example).
Sole exception was Console application when window was hidden.

How do I know if Process.WaitForExit() expired due to main application close

I have a C# console program "A" to monitor 4 instances of another application "B".
The console program creates one thread per instance of "B", and each thread launches "B" using Process.Start(). Then the thread waits 3 seconds using Process.WaitForExit(3000);
After that time, it checks if the instance of the application "B" is working. If it is ok, then waits again. Otherwise, if it doesn't work or if it has finished, it relaunches it. When the user closes the console program, all applications are expected to end.
However, when the application "A" is closed using the console's close button, WaitForExit() is resumed in all threads and it causes the applications "B" to be relaunched.
I would like to detect if WaitForExit() resumed due to a failure of the monitored application "B", or because the main app "A" is exiting. In this case, the application "B" won't be relaunched, and the problem will be solved.
I tried to capture the closing event using:
[DllImport("Kernel32")]
public static extern bool SetConsoleCtrlHandler(HandlerRoutine Handler, bool Add);
The problem was that WaitForExit() was resumed before the handler routine is called.
More information:
The Main() method launches the Threads:
for(int i = 0; i < 4; i++)
{
Thread t = new Thread(() => MonitorizeApp(arguments));
t.IsBackground = true;
t.Start();
}
I also tried to change the threads from background to foreground, so that main app doesn't exit, but the result is the same.
Each thread launches and monitorizes one instance of the 2nd application:
MonitorizeApp(string arguments)
{
LaunchProcess(arguments);
while(!_closing)
{
appProcess.WaitForExit(3000);
if (!_closing)
{
if ((appProcess != null) && (!appProcess.HasExited))
DoSomeMonitoring();
else
{
Console.WriteLine("WARNING: The application finished");
Thread.Sleep(500);
if (!_closing)
{
Console.WriteLine("Re-launch");
LaunchProcess(arguments);
inicial = true;
}
}
}
}
Console.WriteLine("\nClosing...\n");
if(appProcess != null)
{
if(!appProcess.HasExited)
appProcess.Kill();
appProcess.Dispose();
}
}
_closing is a variable set from the main thread, when the console is closed.
I did this using SetConsoleCtrlHandler:
[DllImport("Kernel32")]
public static extern bool SetConsoleCtrlHandler(HandlerRoutine Handler, bool Add);
public delegate bool HandlerRoutine(CtrlTypes CtrlType);
private static bool ConsoleCtrlCheck(CtrlTypes ctrlType)
{
MonitoredApp.closing = true;
Thread.Sleep(1000);
closing = true;
Environment.Exit(-1);
return true;
}
I suspect your only option is to have the main program's exit routine check to see if any of the child processes are running and, if so, terminate them. I'm a little surprised that your 500 ms sleep followed by the _closing check doesn't catch this. Although, looking at your code example I don't see a _closing flag being set (unless _closing is the backing field for MonitoredApp.closing).
You might consider changing that _closing boolean to a ManualResetEvent that the main program sets. It's quite possible that the compiler is optimizing away the check of _closing. You could also mark _closing as volatile, which might change the behavior. Remember, this stuff is all happening asynchronously, so you have to worry about race conditions.
Even with that, you'll likely still run into problems and you'll have to make sure that the main application closes all existing child processes before it exits. You simply cannot guarantee in what order the operating system is going to shut things down.
You could avoid the problem by disabling the close button. See my blog entry, http://blog.mischel.com/2008/07/14/going-too-far-back/, for an example. If you delete the Close menu item, clicking the X won't close the window.
Of course, then your users will have to type the exit command in your window. And you might still have problems if the user closes the window from Task Manager or some other such application.

Synchronization between two processes in C#

I have a WPF application, on a button click I starts 2 process (P1 and P2), both runs on command propmt and I am able to redirect the output of both process to the TextBox in my WPF application.
(reading output asynch).
But the output is getting mix because I am not doing WaitForProcess neither for P1 nor for P2 (I dont want UI to get hang).
I want P2 to start after P1 gets over.
For That I am checking if Process P1 exists or not then only start Process P2.
But my UI gets hangs and nothing is coming up.
in the Windows Task bar
Process P1 is running but Textbox is not getting updated.
When you initialise a Process object, you can register a handler for the Process.Exited event. As you might imagine, this event gets raised when the relevant process gets terminated, so you can start your second Process from there:
Process process = new Process();
process.EnableRaisingEvents = true;
...
process.Exited += Process_Exited;
...
private void Process_Exited(object sender, EventArgs e)
{
// First Process has completed - start second process here
}

Application Startup in C#

We have a little C# startup appplication that users launch to run the latest version of our main C# WinForms application from a network share. It's kind of a simplified ClickOnce arrangement (our IT folks won't allow us to use ClickOnce).
The startup application exits after calling Process.Start("MainApplication.exe"), but the main application can take several seconds to display, leaving the user with a blank screen.
Is there a way that the starup application can poll the OS to find out if the main aplication is running before it exits? Or some other way to handle this?
You can use Process.WaitForInputIdle() to wait until your application enteres the Idle state.
Process appProcess = Process.Start("MainApplication.exe");
appProcess.WaitForInputIdle();
From MSDN:
...you have just started a process and
want to use its main window handle,
consider using the WaitForInputIdle
method to allow the process to finish
starting, ensuring that the main
window handle has been created
Remarks Section from Process.MainWindowHandle property.
You can call Process.GetProcessByName to see if the new process has been created. The other option would be to have your main application kill the startup application once it has finished loading.
Use Davids' suggestion or alternatively you can put a splash screen in your main application. It will be just a simple Form with an image running on a separate worker thread. Put this as the first item invoked on start up. Your app can continue initializing on the main thread & after some seconds or just before your Main app finishes initialization kill the worker thread.
One way to solve this easily is to use a global event to signal the startup application that the main app has reached a predetermined state. To do this create a named event handle in the startup application and wait for it to be signaled:
static void Main(string[] args)
{
const string globalName = "MyProgramName";//something unique
bool isNew = false;
ManualResetEvent mreExited = new ManualResetEvent(false);
EventWaitHandle mreStarted = new EventWaitHandle(false, EventResetMode.ManualReset, globalName, out isNew);
try
{
if (!isNew)//already running, just exit?
return;
//start and monitor for exit
Process pstarted = Process.Start("...");
pstarted.Exited += delegate(object o, EventArgs e) { mreExited.Set(); };
pstarted.EnableRaisingEvents = true;
int index = WaitHandle.WaitAny(new WaitHandle[] { mreExited, mreStarted });
if (index == 0)//mreExited signaled
throw new ApplicationException("Failed to start application.");
}
finally
{
mreExited.Close();
mreStarted.Close();
}
}
Then in the main program you signal the event once your ready for the startup application to quit:
static void Main(string[] args)
{
const string globalName = "MyProgramName";//same unique name
bool isNew = false;
EventWaitHandle mreStarted = new EventWaitHandle(false, EventResetMode.ManualReset, globalName, out isNew);
try
{
if (!isNew)
mreStarted.Set();
Application.Run(new Form());
}
finally
{
mreStarted.Close();
}
}
I think David's second option is the way to go here. The process may be running with no visible UI yet. So, once the main form is displayed in the second app, kill the first process. You could even, conceivably, pass the process id of the original process in via StartInfo when you start the new process.
There are many trade-offs involved in this, but you may be able to make start-up time fast enough to make this issue moot by using NGEN.
My suggestion is to put Splash Screen in your application it gives you a good feel rather than if you dont want to use Splash screen then kill the process when loading is finished. Or you can use Process.GetProcessByName()
For Splash Screen just make a Window Screen and in startup class just inherit the class from Microsoft.VisualBasic.ApplicationServices.WindowsFormsApplicationBase that gives you a method OnCreateSplashScreen() Override this method like this :-
protected override void OnCreateSplashScreen()
{
base.OnCreateSplashScreen();
//yourSplashScreen is the screen you made as a window form either it would be a image or form
SplashScreen = yourSplashScreen();
}
Thanks ...Saurabh Singh Happy To Code

Categories