0

I am developing a CLI-based console application in which most of the heavy-lifting work is performed via applets, scripts and command-line instructions.

SOME applets, however, do utilise WinForms, and this functionality has been added and works reasonably well, so long as the forms are activated via "Form.ShowDialog()" (as opposed to just "Form.Show()").

I assume that this is because the created form is sharing the messaging thread with the base (console-centric) application and the two aren't playing nice together.

After hashing it out, we've determined that we DON'T want to pause the CLI when a form is open (not the least because we want to be able to open multiple independent forms), so my guess is that I need to instantiate new forms in their own threads, but this idea is screaming "TOO COMPLEX!" at me, so I'm wondering if there's a more effective / streamlined means to accomplish this?

Can I somehow spawn a single thread, separate from the base one, on which all the forms can share (like in a standard Windows Forms application?) If so, how would I go about implementing that?

Currently, there are already three threads up and running -->

  1. The original (base) Console thread that I get when the EXE is originally executed.
  2. A thread that waits on Console.KeyAvailable and captures/manages all keystrokes and input parsing, as well as prompt display and instantiating invoked commands.
  3. A thread that manages a List of commands and processes any new commands that appear in that list (these can be added from the keyboard entry thread, or by an applet, or a script).

FWIW, "commands" are simply strings that instantiate an operation (runs an applet, accesses a built-in function etc).

When the "exit" function is called, it terminates the keyboard listening-thread, and then releases the command execution thread (on which it's running) thereby allowing it to exit and return control back to the operating system.

Currently, other than some occasionally sluggish responsiveness on the keyboard thread, everything works exactly as intended, except, as previously noted, when I try to instantiate a WinForm via .Show instead of .ShowDialog

Any insights, suggestions or pointers would be greatly appreciated!

Also: I read through some threads prior to posting this while looking for answers (i.e. this -> how to run a winform from console application? ) but they seem to all presuppose that the console pane will be used as it's native purpose (i.e. a Windows Console) as ooposed to being co-opted as am independent CLI interface, so I didn't find anything that really addressed this particular use-case, leaving me still somewhat in the dark at the moment! (and REALLY not wanting to contemplate having to write an underlying management engine for spawning/closing unique, independent threads for every form that gets opened!).

Thanks!

NetXpert
  • 511
  • 5
  • 14

1 Answers1

0

It's because the Form.ShowDialog() executes it's own runloop, but the Form.Show() does not. You need to execute the runloop yourself by calling Application.Run().

However, you have to establish some kind of communication between threads. In the following example, I used hiddenForm.BeginInvoke(), which posts invocation of the method into the Application's message queue. This is the only reason for existence of the hiddenForm.

Of course you can use Task, Thread or whatever is appropriate for you instead of the BackgroundWorker.

using System;
using System.ComponentModel;
using System.Windows.Forms;

namespace ConsoleWithForms
{
    class Program
    {
        static BackgroundWorker worker;
        static MyForm hiddenForm; 

        static void Main(string[] args)
        {
            worker = new BackgroundWorker();
            worker.DoWork += DoWork;
            worker.RunWorkerAsync();

            Console.WriteLine("Type 'form' or 'quit'...");

            for (var quit = false; !quit;)
            {
                var line = Console.ReadLine().ToLower();
                switch (line)
                {
                    case "quit": quit = true; InvokeCommand("quit"); break;
                    case "form": InvokeCommand("form"); break;
                    default: break;
                }
            }
        }

        private static void DoWork(object sender, DoWorkEventArgs e)
        {
            Application.EnableVisualStyles();
            hiddenForm = new MyForm();
            hiddenForm.Show();
            hiddenForm.Hide();
            Application.Run();
        }

        private static void InvokeCommand(string command)
        {
            hiddenForm.BeginInvoke((Action)delegate {
                hiddenForm.PerformCommand(command);
            });
        }
    }

    class MyForm : Form
    {
        public MyForm()
        {
            Text = "MyForm";
            var button = new Button { Text = "New Form", AutoSize = true };
            button.Click += (sender, args) => { new MyForm().Show(); };
            Controls.Add(button);
        }

        public void PerformCommand(string command)
        {
            switch(command)
            {
                case "form": new MyForm().Show(); break;
                case "quit": Application.ExitThread(); break;
                default: break;
            }
        }
    }
}
Jiri Volejnik
  • 1,034
  • 6
  • 9
  • This seems like a one-off solution, is there a way it could be worked into a derivative of the Form class such that any form based on that derivative could spawn it's a messaging thread for itself? Or maybe by using a factory? – NetXpert Apr 17 '19 at 18:54
  • In WinForms, you can call methods, getters and setters of a control safely only from the thread that the control was created in. But you probably can create controls from different threads, run them in their own run loops within their threads and develop some mechanism for simplifying that. But I haven't tried, so far. – Jiri Volejnik Apr 17 '19 at 20:00
  • It took quite a bit of bodging, and ended up being quite a bit more complicated, but your code did provide me with a foundation for a solution, thanks! – NetXpert Apr 20 '19 at 18:45