views:

868

answers:

2

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?

+1  A: 

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.

What do you think?

Hope this helps, Best regards, Tom.

tommieb75
Thanks for the pointer. Unfortunately, Hidden does not do what I want, since 1.) from what I gather, it is only advisory to the started application and 2.) even if that's not the case, I need the application to be visible to the user if he/she chooses to focus it, just not stealing focus by default.So I _think_ what I really need is equivalent behavior to SW_SHOWMINNOACTIVE or SW_SHOWMA (http://msdn.microsoft.com/en-us/library/ms633548%28VS.85%29.aspx), which the Shell() method does provide, but that unfortunately doesn't give me back a process handle that I can set an exited event on, etc.
psm321
@psm321: First, the code I put in, there is an Exited event handler for the proc object as shown above! Why reinvent the wheel? Secondly, I suspect that is a global system wide setting, perhaps you have an addon somewhere where steal focus by default is a windows behaviour.. Like an auto-raise a window without clicking on it which brings it to the foreground.
tommieb75
And by the way, Handle is the property of the proc object in which you have a process handle...
tommieb75
@tommieb75: sorry, I am not being clear. The Exited handler is actually what I want and am currently using! And the window being brought to the foreground happens because the Process object passes a default flag to ShellExecuteEx and does not let me change it to anything other than a limited set of options. The Microsoft.VisualBasic.Interaction.Shell() method on the other hand, does let me set the proper flag, and this does work exactly how I need, except that it does not give me that Exited event
psm321
+4  A: 

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;
    }
}
Mikael Svenson
I don't have a problem with using the Microsoft.VisualBasic namespace. My problem is with the lack of other functionality in that method (the lack of a Exited event and the ability to abort a process).I did look at the debugging source that Microsoft provides for both methods... that's how I learned about the underlying calls and the flags passed to make this work. It's also how I figured that trying to handle some of the functionality I want by myself would be very painful, as it seems there are complex locking issues, and I can't just copy the code since it would violate the license.
psm321
great idea! I'll try it out on Monday and likely accept the answer. thanks!
psm321
That worked, but there's a complication... it looks like calling Interaction.Shell() from a console application, to run a console application, reuses the same console window rather than launching a new one like Console.Start does. Any idea if it's possible to avoid that?
psm321
I actually ended up using P/invoke to call CreateProcess because that gave me full control over all the options I wanted without all the hacks. I avoided all the mess I was concerned about by then using Process.GetProcessById() to get a Process object once the process was launched and interacting with it through that. I'm marking this response as accepted because you led me down the path and kept providing helpful suggestions :) Thanks!
psm321
It's a good exercise to explore the options at hand. Then you have the argumentation as to why you ended up with a particular solution. P/invoke isn't that bad either once you package it up in a library :)
Mikael Svenson