Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Starting a process without stealing focus (C#)

I need to be able to start processes (both console and windowed) without it stealing focus. The only way within the .NET framework that I found to do this is Microsoft.VisualBasic.Interaction.Shell with Microsoft.VisualBasic.AppWinStyle.[Minimized|Normal]NoFocus (which map to SW_SHOWMINNOACTIVE/SW_SHOWMA being passed through to ShellExecute).

In the current version of my code (which does steal focus), I am using System.Diagnostics.Process, and relying on some of the functionality that gives me, which the Interaction.Shell method does not.

2 questions (one serious, and one venting my frustration that I don't really expect a good answer to)

1.) Am I correct that I have no choice but to wrap CreateProcess or ShellExecuteEx myself, or am I missing some other solution? I was really hoping to avoid this, as Process is such a complete and useful wrapper other than this oversight, and there would be so much functionality to implement, P/Invoke calls to debug, and all sorts of assorted pain.

2.) Why would one team at Microsoft create such a (otherwise) complete wrapper, and then exclude half of the possible values from ProcessWindowStyle, while another team created a similar wrapper that was much less complete, but provided all the useful window styles?

like image 462
psm321 Avatar asked Jan 23 '10 03:01

psm321


2 Answers

The VB.Net team has done much more to ease things for the developer regarding instrumentation of windows, and I see no problem adding a reference to a VB dll and use that in your C# program.

It's two teams with different focus, that's all. And you shouldn't feel bad about using Microsoft.VisualBasic.Interaction.Shell if it solves your issue.

You can also use Reflector to see the actual implementation and implement the code yourself if you don't want to reference the dll.

[Edit - added code example after comment to show you can combine Interaction.Shell and Process]

int pid = Interaction.Shell("notepad.exe", AppWinStyle.NormalFocus);
Process p = Process.GetProcessById(pid);
p.Exited += ((o, e) => Console.WriteLine("Exit"));
p.EnableRaisingEvents = true;
Console.ReadLine();

Here I use the Shell method to kick off the process, get a handle to the process from the pid, and hook on events. You can even do p.Kill() in order to abort the process.

[Edit - workaround for cmd.exe]

It's starting to become a bit hackish to my taste, but it works. Replace "NEWWINDOW" with a random guid or something to make it unique.

Microsoft.VisualBasic.Interaction.Shell(@"cmd.exe /c ""start cmd.exe /k title NEWWINDOW""", AppWinStyle.NormalFocus);
foreach (var process in Process.GetProcessesByName("cmd"))
{
    if (process.MainWindowTitle.EndsWith("NEWWINDOW"))
    {
        process.Exited += ((o, e) => Console.WriteLine("Exit"));
        process.EnableRaisingEvents = true;
    }
}
like image 116
Mikael Svenson Avatar answered Nov 08 '22 20:11

Mikael Svenson


Have a look here:

System.Diagnostics.ProcessStartInfo procInfo = new System.Diagnostics.ProcessStartInfo();
procInfo.CreateNoWindow = true;
procInfo.UseShellExecute = true;
procInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden;
System.Diagnostics.Process proc = new System.Diagnostics.Process();
proc.StartInfo = procInfo;
proc.EnableRaisingEvents = true;
proc.Exited += new EventHandler(proc_Exited);
proc.OutputDataReceived += new DataReceivedEventHandler(proc_OutputDataReceived);
proc.Start(...)
// Do something with proc.Handle...
void  proc_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
   /* Do something here... */
}

void  proc_Exited(object sender, EventArgs e)
{
/* Do something here... */
}

Edit: I have modified the code to show the means of raising events and handling them, also, I have shown the usage of the Handle property which is the handle of the process that is running.

like image 43
t0mm13b Avatar answered Nov 08 '22 22:11

t0mm13b