Reputation: 810
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.
Upvotes: 2
Views: 1482
Reputation: 810
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.
Upvotes: 0
Reputation: 61666
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 });
}
}
}
Upvotes: 4