ShowDialog() is not preventing the originating form from being clickable - c#

I have a winform with the following code that opens an external program when the form is opened. If the program closes, a dialog box is suppose to pop up with an option to return that a button was clicked. This will close the dialog box and return to the initial form and then run the function to open the external program again.
Here is the problem, the original form is STILL clickable AND the function is not running. Any ideas?
public Manager()
{
InitializeComponent();
ExternalProgramOpen();
}
private void ExternalProgramOpen()
{
Process startProgram = Process.Start("program.exe", Program.ConnectionArg);
startProgram.EnableRaisingEvents = true;
startProgram.Exited += this.PrematureClose;
}
private void PrematureClose(object sender, EventArgs e)
{
ManagerWarning messagepopup = new ManagerWarning();
messagepopup.ShowDialog();
using (var ManagerWarning = new ManagerWarning())
{
if (ManagerWarning.ShowDialog() == DialogResult.Retry)
{
ExternalProgramOpen();
}
}
}

The reason for this effect is probably that the Exited event is not raised in the same UI thread that started the process.
When you call ShowDialog() from another thread, the new window will not use and block the original UI thread.
To solve this, check if InvokeRequired is true and use Invoke:
private void PrematureClose(object sender, EventArgs e)
{
if (InvokeRequired)
{
Invoke(new Action(() => PrematureClose(sender, e)));
return;
}
// your code here
// ...
}

Related

Show or Show Dialog block other forms C#

I have 2 forms. First is a simple notepad, and i make a find function. Is works very good, but i add this.Close(); to get the resultate. If i don't close the FindForm, the action of Find button not work. I see the first form is blocked and i can't write another text. if i delete this.close() and i close FindForm after i press Find is work.
How i can open the FindForm in a new thread? i use FindForm f = new FindForm(); and f.showDialog();
If i make a Thread Th; and i use this thread to open the FindForm, my function will not work anymore. Is any method to open other form without block first form?
FindForm
public FindDialog()
{
InitializeComponent();
}
private void button_Find_Click_1(object sender, EventArgs e)
{
Form1.FindText = textBox_Text.Text;
this.Close();
}
MainForm
public void findNewToolStripMenuItem_Click(object sender, EventArgs e)
{
FindDialog gtl = new FindDialog();
gtl.Show();
richTextBox1.Select();
if (FindText != null)
{
k = richTextBox1.Find(FindText);
}
}
Calling ShowDialog will block the caller until the dialog is closed (i.e. it is modal) - if you don't want this behaviour then call Show which will open the child form modelessly (i.e. the calling code can continue)
For example, you could do something like:
public void findNewToolStripMenuItem_Click(object sender, EventArgs e)
{
FindDialog gtl = new FindDialog();
gtl.Show(); // Execution will continue immediately
richTextBox1.Select();
if (FindText != null)
{
k = richTextBox1.Find(FindText);
}
}

Animated GIF in Windows Form while executing long process

I have developed a simple windows Application(MDI) in C# which exports the data from SQL to Excel.
I am using ClosedXML to achieve this successfully.
When the process is executed, I want to show a picturebox containing a animated GIF image.
I am a beginner and don't know how to achieve this, the picturebox appears after the process is completed.
I saw lot of posts which says to use backgroundworker or threading which I have never used and finding it hard to implement.
Can I have an step by step example with explanation.
The two functions which I have created which I call before and after I execute the code.
private void Loading_On()
{
Cursor.Current = Cursors.WaitCursor;
pictureBox2.Visible = true;
groupBox1.Enabled = false;
groupBox5.Enabled = false;
groupBox6.Enabled = false;
Cursor.Current = Cursors.Arrow;
}
private void Loading_Off()
{
Cursor.Current = Cursors.Arrow;
pictureBox2.Visible = false;
groupBox1.Enabled = true;
groupBox5.Enabled = true;
groupBox6.Enabled = true;
Cursor.Current = Cursors.WaitCursor;
}
It is not that hard to add a BackgroundWorker
Open your form in the designer
Open the Toolbox (ctrl+alt+X)
Open the category Components
Drag the Backgroundworker on your From
You will end-up with something like this:
You can now switch to the event view on the Properties tab and add the events for DoWork and RunWorkerCompleted
The following code goes in these events, notice how DoWork use the DowWorkEventArgs Argument property to retrieve the value that is supplied in RunWorkerAsync.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
// start doing what ever needs to be done
// get the argument from the EventArgs
string comboboxValue = (string) e.Argument; // if Argument isn't string, this breaks
// remember that this is NOT on the UI thread
// do a lot of work here that takes forever
System.Threading.Thread.Sleep(10000);
// afer this the completed event is fired
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
// this runs on the UI thread
Loading_Off();
}
Now you only need to start the background job, for example from a button click event to call RunWorkerAsync
private void button1_Click(object sender, EventArgs e)
{
Loading_On();
backgroundWorker1.RunWorkerAsync(comboBox1.SelectedItem); // pass a string here
}
Done! You have successfully added a backgroundworker to your form.
The best way to achieve that is running the animation in a async task, but accordingly some limitations is it possible to do that on windows forms using a Thread Sleep.
eg: In your constructor,
public partial class MainMenu : Form
{
private SplashScreen splash = new SplashScreen();
public MainMenu ()
{
InitializeComponent();
Task.Factory.StartNew(() => {
splash.ShowDialog();
});
Thread.Sleep(2000);
}
It is very important to put the Thread Sleep after have started a new one, don't forget that every action you did on this thread you need ot invoke, for example
void CloseSplash(EventArgs e)
{
Invoke(new MethodInvoker(() =>
{
splash.Close();
}));
}
Now your gif should work!

I want to run a function when ever the form is clicked on taskbar and it opens

The issue doing so is i have used tips and tricks here but the seems to run in a loop and wont give the results.
Basically i run a BackgroundWorker to hit a url get result and paste result to some Labels.
I have used Form_Activated but it just keeps on running in a loop and wont stop ever reached to the BackgroundWorker completed event .
MAIN CODE BLOCKS:
On Form_Load I Run the Function and get the results and show:
private void Form1_Load(object sender, EventArgs e)
{
pictureBox1.Show();
if (!backgroundWorker1.IsBusy)
{
backgroundWorker1.RunWorkerAsync();
}
else
{
MessageBox.Show("Thread already running....");
}
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
loadData(); // scrape a URL and paste info to Labels ....
}
This is it, now the user will minimize the application , now whenever he hits the the taskbar icon the form should rerun the same as in Form_Load. I hope that make sense , i have been able to do that using Form_Activate but it keeps going on .
Any suggestion how to get it done ?
I would store a boolean to remember if the form was minimized at the last FormResized event, and then if it was and if the form isn't currently minimized then call your method.
private bool minimized = false;
public void FormResized(object sender, EventArgs e)
{
if (this.WindowState == FormWindowState.Minimized)
{
minimized = true;
}
if (minimized && this.WindowState != FormWindowState.Minimized)
{
minimized = false;
MyMethod();
}
}

Windows.Form topmost called from a workerthread doesnt work when ClickOnce is enabled and published

I have a main thread that is a form, that starts another application, in this case Notepad, then I spawn off a BackgroundWorker that waits for Notepad to be closed. When its closed, the BackgroundWorker shows another Form to display, topmost, to the user. This Form needs to be non-modal, so that the user can click on some buttons on the main thread dialog. The problem is this form (Form2, from the BackgroundWorker) is NOT TopMost, even though I set it to true. It works when I hit F5, but when I publish, as a ClickOnce application, to my server, form2 is no longer TopMost. I have tired Form2.Topmost = true, BringToFront, Activate, "MakeTopMost" from What is powerful way to force a form to bring front? .... nothing seems to work.
I even tried to get the handle of the main form, and use that as the parent of form2, but I'm getting "InvalidOperationException: Cross-thread operation not valid: Control 'Form2' accessed from a thread other than the thread it was created on."
Here is a code snippet:
public partial class Form1 : Form
{
System.Diagnostics.Process p = new System.Diagnostics.Process();
private BackgroundWorker endApplicationBackgroundWorker= new BackgroundWorker();
public Form1(string[] args)
{
endApplicationBackgroundWorker.DoWork += new DoWorkEventHandler(endApplicationBackgroundWorker_DoWork);
p.StartInfo.FileName = "notepad";
p.Start();
endApplicationBackgroundWorker.RunWorkerAsync();
//Quit here so we can accept user inputs (button pushes ..)
}
private void endApplicationBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
p.WaitForExit();
Form2 form2 = new Form2();
form2.TopMost = true;
System.Diagnostics.Process[] procs = System.Diagnostics.Process.GetProcessesByName(form1ProcessName);
if (procs.Length != 0)
{
IntPtr hwnd = procs[0].MainWindowHandle;
if (form2.ShowDialog(new WindowWrapper(hwnd)) == DialogResult.OK)
{
// process stuff
}
}
this.Close();
}
}
Any other ideas? Or can someone fix my code above? I have been dealing with this issue for weeks now and getting flustered.
Thanks!
Any work you do in a BackgroundWorker's DoWork method after calling the RunWorkerAsync procedure is NOT running on the UI thread, but your code is creating a form in the background.
Forms are UI elements, so this won't work:
private void endApplicationBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
Form2 form2 = new Form2();
form2.TopMost = true;
// etc..
if (form2.ShowDialog(new WindowWrapper(hwnd)) == DialogResult.OK)
{
// process stuff
}
}
From the comments, you should subscribe to the RunWorkerCompleted event to show your second form. Also, you can't call the Close method either since you are trying to keep Form2 alive without a ShowDialog call, so try subscribing to the Form_Closing() event of the second form to notify when the main form should be closed, too:
public Form1(string[] args)
{
endApplicationBackgroundWorker.DoWork +=
new DoWorkEventHandler(endApplicationBackgroundWorker_DoWork);
endApplicationBackgroundWorker.RunWorkerCompleted +=
new RunWorkerCompletedEventHandler(endApplicationBackgroundWorker_RunWorkerCompleted);
p.StartInfo.FileName = "notepad";
endApplicationBackgroundWorker.RunWorkerAsync();
}
private void endApplicationBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
p.Start();
p.WaitForExit();
}
private void endApplicationBackgroundWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Form2 form2 = new Form2();
form2.TopMost = true;
form2.FormClosing += new FormClosingEventHandler(form2_FormClosing);
form2.Show(this);
}
private void form2_FormClosing(object sender, FormClosingEventArgs e)
{
this.BeginInvoke(new MethodInvoker(delegate { this.Close(); }));
}
Another workaround can be, if you set the topmost to false and then set it back to true. It is strange, but it works. So the code could be the following for a form to be show with descending oppacity in a simple background thread:
for (int i = 0; i < 50; i++)
{
ppaForm.SetBitmap(bitmap, (byte)(255 - i * 5));
ppaForm.Show();
ppaForm.TopMost = false;
ppaForm.TopMost = true;
Thread.Sleep(6);
}
In this case ppaForm is a PerPixelAlphaForm, for which you can find a description here.

Is CancelAsync working or not?

I've made a small app where Form is threaded (using BackgroundWorker), and in the form I'm calling a function QuitApplication in Program class when I want to quit.
The DoWork looks like this:
static void guiThread_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
while (true)
{
if (worker.CancellationPending == true)
{
e.Cancel = true;
break;
}
if (Program.instance.form != null)
{
Program.instance.form.UpdateStatus(Program.instance.statusText, Program.instance.statusProgress);
}
Thread.Sleep(GUI_THREAD_UPDATE_TIME);
}
}
and in the Form1 class i have this method attached to the closing of the window:
void Form1_FormClosed(object sender, FormClosedEventArgs e)
{
Program.instance.SetStatus("Closing down...", 0);
Program.QuitApplication();
}
So what i want is to ensure that everything quits when I press the X on the window. However, the if( worker.CancellationPending == true ) never hits... why is this?
QuitApplication looks like this:
public static void QuitApplication()
{
Program.instance.guiThread.CancelAsync();
Application.Exit();
}
And Im using guiThread.WorkerSupportsCancellation = true
CancelAsync is setting the CancellationPending property, but then you immediately quit the application without giving the background thread a chance to detect that and shut down. You need to change your UI code to wait for the background thread to finish.
Personally, when I write apps like this, I make the form close button act like a Cancel button rather than quit immediately. It's a lot safer for the end user. For example:
private void abortButton_Click(object sender, EventArgs e) {
// I would normally prompt the user here for safety.
worker.CancelAsync();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e) {
if(worker.IsBusy) {
// If we are still processing, it's not very friendly to suddenly abort without warning.
// Convert it into a polite request instead.
abortButton.PerformClick();
e.Cancel = true;
}
}

Categories