3

I have a main application which runs a console application. The console application is usually started hidden (ProcessWindowStyle.Hidden), but for testing purposes I can run it with the window shown.

Within the console application I can have plugins loaded and executed. One of the plugins tries to open a WinForm dialog. It works fine if the console application is visible, but it doesn't work any more if the console is hidden.

I have tried:

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form());

and I also tried the same in a new thread.

Thread t = new System.Threading.Thread(start);
t.Start();
t.Join();

where start() contains the things before. In addition I tried ShowDialog()

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var f = new Form();
f.ShowDialog();

None of the methods showed the window.

In WinDbg, the native callstack always includes NtUserWaitMessage():

0:000> k
ChildEBP RetAddr  
0038dd58 7b0d8e08 USER32!NtUserWaitMessage+0x15

And the managed callstack always includes WaitMessage(), FPushMessageLoop() and RunMessageLoop():

0:000> !clrstack
OS Thread Id: 0x47c4 (0)
ESP       EIP     
0045e560 76bff5be [InlinedCallFrame: 0045e560] System.Windows.Forms.UnsafeNativeMethods.WaitMessage()
0045e55c 7b0d8e08 System.Windows.Forms.Application+ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32, Int32, Int32)
0045e5f8 7b0d88f7 System.Windows.Forms.Application+ThreadContext.RunMessageLoopInner(Int32, System.Windows.Forms.ApplicationContext)
0045e64c 7b0d8741 System.Windows.Forms.Application+ThreadContext.RunMessageLoop(Int32, System.Windows.Forms.ApplicationContext)
0045e67c 7b5ee597 System.Windows.Forms.Application.RunDialog(System.Windows.Forms.Form)
0045e690 7b622d98 System.Windows.Forms.Form.ShowDialog(System.Windows.Forms.IWin32Window)
0045e71c 7b622faf System.Windows.Forms.Form.ShowDialog()

How can I show a WinForms form from a hidden console window?

SSCCE:

Compile this as a Windows Form application:

public partial class Form1 : Form
{
    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        var startInfo = new ProcessStartInfo("Console.exe");
        startInfo.WindowStyle = ProcessWindowStyle.Hidden;
        Process.Start(startInfo);
    }
}

Compile this as the console application:

class Program
{
    static void Main(string[] args)
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        var mainForm = new Form();
        // Enable next line to make it show
        // mainForm.Visible = true;
        Application.Run(mainForm);
    }
}
Thomas Weller
  • 55,411
  • 20
  • 125
  • 222

1 Answers1

3

Using Winspector Spy I found out that the window is actually available, but it doesn't have the WS_VISIBLE style. Applying that style to the form made it visible and it was shown in the Windows task bar.

The solution was to make the Form visible before showing it, so the following worked:

Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var f = new Form()
f.Visible = true;
Application.Run(f);

Because in my case I wanted to get the return value, I should call ShowDialog(). However, calling ShowDialog() on an already visible form is not allowed, so I sticked to Application.Run(f) and retrieved the result myself:

var answer = configForm.DialogResult;
Thomas Weller
  • 55,411
  • 20
  • 125
  • 222
  • @LRNAB: [Why must I wait 2 days before accepting my own answer?](http://meta.stackexchange.com/questions/6044/why-must-i-wait-2-days-before-accepting-my-own-answer) – Thomas Weller Mar 24 '14 at 10:04
  • 1
    Hmm, no, this answer is nonsense. Application.Run() already sets the Visible property to true. Whether the user can see the window is a rather random outcome, usually not and your window will be covered by whatever window the user is working with. You can try to shove the window into the user's face with BringToFront() but that's not guaranteed to work, Windows actively defeats this if it recently detected input to the foreground window. Be sure to have the ShowInTaskbar property set to *True* so the user sees the blinking taskbar button. A NotifyIcon is the correct solution. – Hans Passant Mar 24 '14 at 10:37
  • @HansPassant: I appreciate your critical feedback. Using Winspector I can see that the window is there, but invisible (I have updated the answer). Using "Bring to front" in Winspector does nothing. Only editing the window styles and applying the WS_VISIBLE flag made the window visible. Doing that also shows it in the task bar. – Thomas Weller Mar 24 '14 at 10:58
  • @HansPassant: I have reduced the code to an SSCCE and updated the question. Maybe you want to run by yourself. While setting Visible to true is a workaround, I'd also like to know the *real* cause and maybe a better solution. – Thomas Weller Mar 24 '14 at 11:16
  • Well, I should but I am not going to. The foreground window Z-order problem is the much more serious issue. – Hans Passant Mar 24 '14 at 11:20
  • +1 I had the same problem. I can confirm this was the only solution that worked (tried BringToFront an other stuff). One funny thing: MessageBox.Show works right out of the box without any tricks. – BudBrot Dec 23 '20 at 10:07