Closing parent form from child thread - c#

I've been scratching my head on this one for a while now and despite looking for solutions, im not quite understanding the implementations (several answers on stack overflow have already been looked at)
My program loads a splash page when it is opened, during which it checks for a database connection. If there is a connection, the splash page closes and the main form loads, otherwise it provides an error message then closes completely.
public partial class StartupSplash : Form
{
ThreadStart th;
Thread thread;
public StartupSplash()
{
InitializeComponent();
th = new ThreadStart(DbAvaliable);
thread = new Thread(th);
thread.Start();
}
private void DbAvaliable()
{
Boolean result = false;
using (var connectiontest = new SqlConnection("myConnString"))
try
{
connectiontest.Open();
result = true;
}
catch (Exception ex)
{
result = false;
}
if (result)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new MainWindow());
}
else
{
MessageBox.Show("Unable to establish database connection. Please check your data connection and try again.");
}
}
}
I understand that I can't simply call this.Close() due to cross thread issues. I've read something about invoking methods, but im not too clear how to achieve the result above.
Initially I tried to use form load/shown events instead of alternate threads, but the image on the forms failed to load until after the messagebox had shown the error (rather than displaying, then running the connection check)

Could you set up an event to fire on Form2 with the results of the db check? Subscribe to the event on Form1 and tell it to close if the conditions warrant.
Not sure if it would work or not, but something like:
public Form2 : Form
{
public delegate void DbCheckHandler(object sender, DbEventArgs e);
public event DbCheckHandler DbCheckComplete;
//check the db
DbCheckComplete(this, new DbEventArgs { ShouldClose = true; });
}
public Form1 : Form
{
Form2 form2 = new Form2();
form2.DbCheckComplete += new DbCheckHandler(CheckDbResult);
form2.Show();
private void CheckDbResult(object sender, DbEventArgs e)
{
if(e.ShouldClose)
{
this.Close();
}
}
}

With some help from previous answers posted by Hans Passant (here and here), My solution was as follows:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
new MyApp().Run(args);
}
class MyApp : WindowsFormsApplicationBase
{
protected override void OnCreateSplashScreen()
{
this.SplashScreen = new StartupSplash();
}
protected override void OnCreateMainForm()
{
Boolean result = false;
using (var connectiontest = new SqlConnection("myConnectionString"))
try
{
connectiontest.Open();
result = true;
}
catch (Exception ex)
{
result = false;
}
// pause not needed while checking for db connection as that takes its own amount of time to complete.
if (result)
{
System.Threading.Thread.Sleep(3000); //pause moved here to give the splash some time on screen if db connection available
this.MainForm = new MainWindow();
}
else
{
MessageBox.Show("Unable to connect to the database");
Environment.Exit(1); // shuts down the program if database connection not avaliable.
}
}
}

Related

How to Run a Form Both Visibly and Invisibly based on a parameter with C#

I have a form that is run as a scheduled task. When it is run, nothing shows up, the program executes. I would also like the option of showing the form and manually executing the program with the different options that are available on the main form.
So, using parameters, I am able to run this and it works fine as a scheduled task. As a form, however, I am unable to get the form to show.
Here is my code:
******* MAIN PROGRAM
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
if (args.Length > 0)
{
Application.Run(new FileProcessor(true));
}
else
{
Application.Run(new FileProcessor(false));
}
}
******* INSIDE THE FORM
bool RunOnSchedule = true;
private bool setCore;
public FileProcessor(bool automatic = true)
{
RunOnSchedule = automatic;
if (RunOnSchedule)
{
setCore = true;
SetVisibleCore(false);
}
else
{
SetVisibleCore(true);
}
InitializeComponent();
if (!RunOnSchedule)
{
btnProcess.Visible = false;
}
if (RunOnSchedule)
{
object sender = new object();
EventArgs e = new EventArgs();
FileProcessor_Load(sender, e);
GetDefaultFiles();
BtnProcess_Click(sender, e);
}
}
protected override void SetVisibleCore(bool value)
{
base.SetVisibleCore(setCore ? value : setCore);
}
If it doesn't run as a scheduled task, the form won't show up. I have tried using ShowDialog() instead of .Run() but it still doesn't work.
Any thoughts, ideas or help would be greatly appreciated.

Windows Form Application - Splash screen label not updating

I have a windows form application which is supposed to show a splash screen with a label field that I want to update as the main form (called welcome.cs) loads in the background. The splash screen shows & hides just fine, but the label doesn't update.
I've done a lot of research but haven't quite found the solution.
Program.cs
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
using (new SingleGlobalInstance(1000))
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
SplashScreen splashscreen = new SplashScreen();
splashscreen.ShowSplashScreen();
Welcome welcome = new Welcome(splashscreen); //Takes some time to load
splashscreen.CloseForm();
Application.Run(welcome);
}
}
Splashscreen.cs
public partial class SplashScreen : Form
{
//Delegate for cross thread call to close
private delegate void CloseDelegate();
private delegate void UpdateStatusDelegate(string status);
private static SplashScreen splashScreen;
private Thread thread = null;
public SplashScreen()
{
InitializeComponent();
}
public void ShowSplashScreen()
{
// Make sure it is only launched once.
if (splashScreen != null)
return;
thread = new Thread(ShowForm);
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
static private void ShowForm()
{
splashScreen = new SplashScreen();
Application.Run(splashScreen);
}
public void CloseForm()
{
splashScreen.Invoke(new CloseDelegate(CloseFormInternal));
}
static private void CloseFormInternal()
{
splashScreen.Close();
}
public void UpdateStatus(string status)
{
splashScreen.Invoke(new UpdateStatusDelegate(UpdateStatusInternal), status);
}
private void UpdateStatusInternal (string status)
{
if (splashScreen != null && splashScreen.IsHandleCreated)
{
lblStatus.Text = status;
}
}
}
Welcome.cs
public Welcome(Splashscreen splashscreen)
{
InitializeComponent();
//Code to log the user into the system
splashScreen.UpdateStatus("Logging in...");
//my expectation is that UpdateStatus call will update the label displayed on the splash screen but it doesn't.
//Do more stuff.....
}
Does it have something to do with multi-threading or is it because im creating a new instance of splashscreen in welcome.cs before calling UpdateStatus? How would I get around this?
You could do the following
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
string[] args = Environment.GetCommandLineArgs();
// Creates the Splash
splash = new FrmSplash();
//Opens the Splash in a new Thread, this way any gifs, progress bars, lablels changes will work because the main thread isnt blocked
var t = Task.Factory.StartNew(() =>
{
splash.ShowDialog();
});
while (!splash.Created) // wait the splash screen form load process
System.Threading.Thread.Sleep(300);
UpdateSplashMessage("Loading the program... Please wait");
// Some slow initialization code.
// ...
//Close splash screen
CloseSplash();
Application.Run(args);
}
static void CloseSplash()
{
splash.Invoke(new MethodInvoker(() =>
{
splash.Close(); // Closes the splash that is running in the other thread
}));
}
static void UpdateSplashMessage(string msg)
{
splash.Invoke(new MethodInvoker(() =>
{
splash.AtualizarMensagem(msg);
}));
}
Note that you will need to create a method called AtualizarMensagem(string str) in your splash screen form, like this
public void AtualizarMensagem(string novaMsg)
{
lblCarregando.Text = novaMsg;
}
I have this code in my "useful snnipets" folder, it always works for me.
Hope this helps.

Form freezes when I run my program

my form freezes immediately when I run my code. I'm not sure why, but please take a look at the code below and see the screenshot. Basically when I run my code, the form freezes once my code loads, and it just says "not responding" what could it be doing?
namespace MySample
{
public class Driver
{
static void Main(string[] args)
{
log4net.Config.XmlConfigurator.Configure();
Form1 form = new Form1();
form.Show();
try
{
StartModbusSerialRtuSlave();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
}
public static void StartModbusSerialRtuSlave()
{
using (SerialPort slavePort = new SerialPort("COM1"))
{
// configure serial port
slavePort.BaudRate = 38400;
slavePort.DataBits = 8;
slavePort.Parity = Parity.Odd;
slavePort.StopBits = StopBits.One;
slavePort.Open();
byte unitId = 1;
// create modbus slave
ModbusSlave slave = ModbusSerialSlave.CreateRtu(unitId, slavePort);
slave.DataStore = DataStoreFactory.CreateDefaultDataStore();
slave.Listen();
}
}
}
CODE ON FORM
namespace MySample
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
}
}
The function StartModbusSerialRtuSlave is not returning. The call to Listen likely blocks, which is normally fine, but its on the UI thread.
Because it isn't executing on its own thread (separate from the UI), it causes the application to "lock up" and deliver the error message you see.
Simple fix, don't perform long-running operations on the UI thread. Start things like I/O on their own thread.
For example:
log4net.Config.XmlConfigurator.Configure();
try
{
new Thread((p) => StartModbusSerialRtuSlave()).Start();
}
catch (Exception e)
{
Console.WriteLine(e.Message);
}
//Start your form the right way!
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());

Using MessageBox to show exception information in multithreaded application

Hi I'm working with winform and trying to use MessageBox for exception handling.
The weird thing here is, the MessageBox appears only after the main form ("Form1" in the code below) is closed.
public class Worker {
/* edited (see below)
public void doWork() {
try {
// do something
client.Connect(serverAddress);
stream = client.GetStream();
}
catch(Exception e) {
MessageBox.Show(e.ToString(),
"This will not show up until Form1 is closed");
}
}
*/
}
public class Form1 {
/* edited (see below)
* public void threadProc() {
* Worker worker = new Worker();
* worker.doWork();
* }
*/
void button_Click(object sender, EventArgs e) {
// create a thread that will end up throwing an exception
Thread thread = new Thread(threadProc);
thread.Start();
}
}
What could be a better way to use MessageBox for exception handling?
...So I added some codes for MessageBox-ing in the UI thread, but the problem remains.
public class WorkExceptionArgs : EventArgs {
public Exception e;
public WorkExceptionArgs (Exception e) { this.e = e; }
}
public partial class Worker1 { // renamed (Worker->Worker1)
/* (edited) Now Worker1 doesn't trigger any event (see below)
public event EventHandler<WorkExceptionArgs> workException;
*/
public void doWork() {
try {
// do something
client.Connect(serverAddress);
stream = client.GetStream();
}
catch(Exception e) {
/* (edited) suppose Worker1 never throws any exception (see below)
* // trigger event that will cause MessageBox-ing by UI thread
* workException(this, new WorkExceptionArgs(e));
*/
}
}
}
public partial class Form1 {
public void threadProc() {
Worker1 worker1 = new Worker();
/* (edited) Now Worker1 never throws any exception
* worker.workException += new EventHandler<WorkException>(worker_WorkException);
*/
worker1.doWork();
// (added) After doWork() is done, Form1 creates Worker2
Worker2 w2 = new Worker2(this, this.form2);
w2.workException += new EventHandlerArgs<WorkExceptionArgs>(form2.worker2_WorkException);
w2.doSomeOtherWork();
}
/* public void worker_WorkException(object sender, WorkExceptionArgs eArg) {
* MessageBox.Show(eArg.e.ToString(), "Still not showing");
* } */
Form2 form2 = new Form2(); // (added) At first form2 is hidden (see below)
}
Actually there have been another form and another worker. Once Worker(Worker1) made connection to the server, Form1 hides (.Hide()), Form2 shows (.Show()), and Worker2 starts working with the connection Worker1 made.
public class Worker2 {
Worker2(Worker1 w1, Form2 frm2) { this.w1=w1; this.frm2=frm2; }
public Worker1 w1;
public Form2 frm2;
public event EventHandler<WorkExceptionArgs> workException;
public void doSomeOtherWork() { // do some other, using data in Worker 1.
try { // This will throw an exception
BinaryFormatter formatter = new BinaryFormatter();
MyObj mo = (MyObj)formatter.Deserialize(w1.getStream());
}
catch(Exception e) {
workException(this, new WorkExceptionArgs(e));
}
}
}
public class Form2 {
public Form2(Form1 frm1) { // to switch from frm1 to frm2
InitializeComponent();
this.frm1 = frm1;
}
public Frm1 frm1 {get;set;}
public void worker2_WorkException(object sender, WorkExceptionArgs ea) {
MessageBox.Show(this, ea.e.ToString(), "SHOWS ONLY IF FORM2 IS CLOSED");
}
}
public partial class Form1 {
delegate void switchWindow_Callback();
public void switchWindow() { this.Hide(); form2.Show(); }
public void switchWindowCb(object sender, EventArgs e) {
if(this.InvokeRequired) {
SwitchWindow_Callback hcb = new SwitchWindow_Callback(switchWindow);
this.Invoke(hcb, new object[] {});
}
else { this.switchWindow(); }
}
}
Actually I'll bet the MessageBox is appearing behind the main form, and you just don't see it until you close it.
You'd be much better off letting the UI thread (the one that created and owns Form1) do the MessageBox-ing. You either want to make events, or otherwise have an error callback delegate in your worker class.
However, BackgroundWorker may be worth checking out here rather than trying to roll your own. Assuming it's a fatal exception, you can save and retrieve the error state, and you get an event automatically called when the thread finishes.
You should really be locking down the doWork method so that multiple threads cant access it at the same time, they need to queue.
Look into "Joining Threads". Imagine if you get two exception at the same time. Your app will fall over. Locking down the area of code that will be duplicated repeatedly will form a queue for your threads to access the area of code that handles the exception.
As lc stated, it's quite likely that your message box is appearing behind the main form, and therefore you only see it when the main form is closed.
The model I use to deal with unhandled exceptions in a Windows Forms app looks like this:
// Switch-off the Windows Forms default handler for unhandled exceptions.
// NB From .NET 4 upwards, this won't work if the process state is corrupted.
Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);
// Setup event handler to intercept an unhandled exception on a UI thread .
// NB The exception will still terminate the application.
// But you can show a MessageBox in the event handler and log the exception.
Application.ThreadException +=
new ThreadExceptionEventHandler(App_UiThreadException);
// Setup event handler to intercept an unhandled exception on a non-UI thread.
AppDomain.CurrentDomain.UnhandledException += new
UnhandledExceptionEventHandler(App_NonUiThreadException);
// Run the application (open main form etc).
The first line says that you want to catch any unhandled exception and deal with it yourself rather than letting the WinForms infrastructure deal with it. Note that from .NET 4 onwards, this setting won't work for an exception that corrupts process state (for example OutOfMemory).
If you have an unhandled exception on a UI thread, the second line will trigger a procedure you create called *App_UiThreadException*. That's where your MesssageBox code should go.
If you have an unhandled exception on a non-UI thread, the last line will trigger a procedure you create called *App_NonUiThreadException*. That's where your MesssageBox code should go.

Run one instance of program

i have one problem with this ?!
i use this way for run only one instance of program.
it's do very good.but when i use this way in other app .
when i run one of them programs via shortcut from desktop , both programs invoke and show in desktop.
note : both programs run in windows system try .
static bool ok;
static Mutex mutex = new Mutex(true, "{123Newsoft-Cleaner Portable Program123}",out ok);
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
//Application.EnableVisualStyles();
//Application.SetCompatibleTextRenderingDefault(false);
//Application.Run(new Form1());
if (mutex.WaitOne(TimeSpan.Zero, true))
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var mainForm = new Form1c();
try
{
mainForm.Visible = false;
mainForm.WindowState = FormWindowState.Normal;
}
catch (Exception ex)
{
MessageBox.Show(ex.Message, "error", MessageBoxButtons.OK, MessageBoxIcon.Error);
}
Application.Run(mainForm);
}
else
{
NativeMethods.PostMessage((IntPtr)NativeMethods.HWND_BROADCAST, NativeMethods.WM_SHOWME, IntPtr.Zero, IntPtr.Zero);
}
//---------------- in main form
protected override void WndProc(ref Message M_C)
{
if (M_C.Msg == NativeMethods.WM_SHOWME)
{
ShowMe();
}
base.WndProc(ref M_C);
}
//*************
private void ShowMe()
{
if (WindowState == FormWindowState.Minimized)
{
Show();
WindowState = FormWindowState.Normal;
}
// get our current "TopMost" value (ours will always be false though)
bool top = TopMost;
// make our form jump to the top of everything
TopMost = true;
// set it back to whatever it was
TopMost = top;
}
This is already well supported by the .NET Framework. You want to use the WindowsFormsApplicationBase class. Set the IsSingleInstance property to true. You can override the OnStartupNextInstance method to do anything you like when another instance gets started. Like restoring the window of the first instance. Rewrite your Program.cs file to look like this:
using System;
using System.Windows.Forms;
using Microsoft.VisualBasic.ApplicationServices; // Add reference to Microsoft.VisualBasic
namespace WindowsFormsApplication1 {
class Program : WindowsFormsApplicationBase {
[STAThread]
static void Main(string[] args) {
var app = new Program();
app.Run(args);
}
public Program() {
this.IsSingleInstance = true;
this.EnableVisualStyles = true;
this.MainForm = new Form1();
}
protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs) {
if (this.MainForm.WindowState == FormWindowState.Minimized) this.MainForm.WindowState = FormWindowState.Normal;
this.MainForm.Activate();
}
}
}
To add to what Hans Passant wrote, I added an extra method on the main form that handles restoring the window and activating it. This was to wrap the invoke required condition of the form.
So the added method on the form is:
/// <summary>
/// Recovers this instance of the form.
/// </summary>
public void RestoreFromTray()
{
if(this.InvokeRequired)
{
this.Invoke(new Action(RestoreFromTray) );
return;
}
this.Visible = true;
this.WindowState = FormWindowState.Normal;
this.Activate();
}
Then in Hans' method, I changed the override to simply:
protected override void OnStartupNextInstance(StartupNextInstanceEventArgs eventArgs)
{
((formClassName)this.MainForm).RestoreFromTray();
}
Where formClassName is the class name of the form.

Categories

Resources