2

Having some trouble finding an answer to this specific situation. I need to create a temporary form (that will later be destroyed) that is in a separate thread from the main form.

This form is used for displaying account login information to the user. At the same time this form is on screen, a modal input box is also displayed to the user. The presence of the modal input box prevents any interaction with the login info form (copying/pasting), which is necessary functionality for the user.

How can I:

A) Create and display a new Form on an entirely separate thread from the main Form?

B) Destroy that Form from the main Form's thread once the user has entered input into the modal dialog box?

Note: I have already explored MainForm.Invoke/BeginInvoke and this does not give the results I need, as some other posts have claimed it should.

The code for the modal InputBox:

class InputBox
{
    public static DialogResult Show(string prompt, bool hideInput, out string userInput, Form parent = null)
    {
        InputBoxForm frm = new InputBoxForm(prompt, hideInput);

        if (parent != null)
            frm.ShowDialog(parent);
        else
            frm.ShowDialog();

        if (frm.DialogResult == DialogResult.OK)
        {
            userInput = frm.txtInput.Text;
            frm.Dispose();
            return DialogResult.OK;
        }
        else
        {
            userInput = "";
            frm.Dispose();
            return DialogResult.Cancel;
        }
    }
}

And the code as it is used in the program:

Form loginDisplay = LoginInfoForm(user, pass);
loginDisplay.Show(null);
string input = "";
InputBox.Show("Enter info:", false, out input, parent: this);

The LoginInfoForm is just a function that dynamically creates a form and formats it a bit.

Jdinklage Morgoone
  • 770
  • 1
  • 7
  • 20
  • I appreciate your perspective and would normally agree, but you are wrong in this case. The solution that was demonstrated required *significant* adaptation to my code in order to work, and the original code is immaterial. Read my comments on the answer. @Servy – Jdinklage Morgoone Mar 12 '14 at 19:23
  • @Servy, It provides enough information, but requires adaptation. Again, read my comments on the solution. Multiple parts of the provided code did not work, but it led me to the answer, so it is the answer. Posting my own answer and marking it would not give credit to Noseratio for leading me to the answer. But whatever, clearly you're just going to be a douche about it so I'll follow a guideline that anyone can see is dumb. – Jdinklage Morgoone Mar 12 '14 at 19:31
  • @Servy, And the best part is that I can't even accept my own answer. GJ. – Jdinklage Morgoone Mar 12 '14 at 19:35
  • 1
    @JdinklageMorgoone If you want to maintain the other answer as the accepted answer you have every right to. If you want to accept your own answer, you can, you just need to wait a certain amount of time. Again, that someone else's answer wasn't adequate for you is entirely valid grounds for posting your own answer. It is completely invalid grounds for editing an answer into the question. Once again, the question is where you post your question. It is not where you post your solution. Answers are where you post solutions. – Servy Mar 12 '14 at 19:38

2 Answers2

4

This is a bit contrived situation, IIUIC. Why do you need a new form on a separate thread?

You still can have a modal dialog (parented by the main form) and a modeless pop-up form at the same time on the main UI thread. The user will be able to interact with both independently.

Just specify the corrent parent to the dialog: dialogForm.ShowDialog(mainForm), and no parent to the modeless form: form.Show(null).

In either case, this kind of UI might be confusing to the user.

Updated, below is an example of what I've described, with one important amendment. Indeed, Form.ShowDialog disables all top-level visible and enabled windows owned by the same thread (rather than disabling only the direct parent window of the dialog, as its Win32 counterpart DialogBox does).

Admittedly, this is quite an unexpected behavior for me, although I see the reason behind it: the consistent UI experience I mentioned above. For more details, refer to the implementation of ModalApplicationContext.DisableThreadWindows.

The workaround is very simple: if the popup form is currently visible, disable it before showing displaying the dialog, and re-enable it when the dialog is shown. Note it's all done on the same thread:

var dialog = new ModalDialog { Width = 200, Height = 100 };

if (popup != null)
{
    popup.Enabled = false;
    dialog.Load += delegate { 
        popup.Enabled = true; };
}

dialog.ShowDialog(this);

The complete WinForms app:

using System;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsForms_22340190
{
    public partial class MainForm : Form
    {
        public MainForm()
        {
            InitializeComponent();

            var cts = new CancellationTokenSource();

            this.Load += async (s, e) =>
            {
                // start the background thread in 1s
                await Task.Delay(1000);

                Form popup = null;

                var task = Task.Run(() => 
                {
                    // background thread
                    this.Invoke(new Action(() => 
                    {
                        // create a popup on the main UI thread
                        popup = new Popup { Width = 300, Height = 200 };
                        popup.Show(this);
                    }));

                    // imitate some work
                    var i = 0;
                    while (true)
                    {
                        Thread.Sleep(1000);
                        cts.Token.ThrowIfCancellationRequested();

                        var n = i++;
                        this.BeginInvoke(new Action(() =>
                        {
                            // update the popup UI on the main UI thread
                            popup.Text = "Popup, step #" + n;
                        }));
                    }
                });

                // wait 2s more and display a modal dialog
                await Task.Delay(2000);

                var dialog = new ModalDialog { Width = 200, Height = 100 };

                if (popup != null)
                {
                    popup.Enabled = false;
                    dialog.Load += delegate { 
                        popup.Enabled = true; };
                }

                dialog.ShowDialog(this);
            };

            this.FormClosing += (s, e) =>
                cts.Cancel();
        }
    }

    public partial class ModalDialog : Form
    {
        public ModalDialog() 
        { 
            this.Text = "Dialog";
            this.Controls.Add(new TextBox { Width = 50, Height = 20 });
        }
    }

    public partial class Popup : Form
    {
        public Popup() 
        { 
            this.Text = "Popup";
            this.Controls.Add(new TextBox { Width = 50, Height = 20 });
        }
    }
}
Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
noseratio
  • 59,932
  • 34
  • 208
  • 486
  • I just tried this, and it did not work. I modified my function for the Input Form so I can feed it a parent form for its `ShowDialog()` and used `.Show(null)` for the login display form. When the modal Input Form appears, the login display form cannot be interacted with. @Noseratio – Jdinklage Morgoone Mar 12 '14 at 01:54
  • Also, it needs to be on a separate thread because the display form needs to be interactable while a modal dialog box is blocking the main thread from proceeding while it waits for an answer from the user, and I thought that couldn't be done without using a separate thread. @Noseratio – Jdinklage Morgoone Mar 12 '14 at 02:20
  • One moment. @Noseratio – Jdinklage Morgoone Mar 12 '14 at 02:23
  • I've added what should be the relevant code. @Noseratio – Jdinklage Morgoone Mar 12 '14 at 02:31
  • 1
    A number of parts of your sample code could not be used (`Task.Run`, `Task.Delay` and `await` were not recognized), but I was able to adapt what you did to my code and this worked perfectly. Thank you very much for your help! – Jdinklage Morgoone Mar 12 '14 at 18:36
  • @JdinklageMorgoone, those parts are used only as a mock, to simulate the threading model described in your question for testing. They are *not* the part of the actual solution, which is essentially the first code fragment. – noseratio Mar 12 '14 at 22:18
0

Updated code for InputBox class:

    public static DialogResult Show(string prompt, bool hideInput, out string userInput, Form parent = null, Form enable = null)
    {
        InputBoxForm frm = new InputBoxForm(prompt, hideInput);

        if (enable != null)
        {
            frm.Load += delegate { enable.Enabled = true; };
        }

        if (parent != null)
            frm.ShowDialog(parent);
        else
            frm.ShowDialog();

        if (frm.DialogResult == DialogResult.OK)
        {
            userInput = frm.txtInput.Text;
            frm.Dispose();
            return DialogResult.OK;
        }
        else
        {
            userInput = "";
            frm.Dispose();
            return DialogResult.Cancel;
        }
    }

Updated code within the program:

                Form loginDisplay = null; 

                this.Invoke(new Action(() =>
                    {
                        loginDisplay = LoginInfoForm(user, pass);
                        loginDisplay.Show(this);
                    }));

                if (loginDisplay2 != null)
                {
                    loginDisplay2.Enabled = false;
                }

                string input = "";
                InputBox.Show("Input info", false, out input, parent: this, enable: loginDisplay);

Thanks to @Noseratio for the code that led me to the solution.

Jdinklage Morgoone
  • 770
  • 1
  • 7
  • 20