C# Close processes minimized to tray in a graceful way? - c#

I have an application that has can display a window using a Form. The form is only shown if the application is run using a -debug flag, otherwise it is only shown in tray.
var form = new Form();
if(DebugMode)
form.Show();
The application listens to CloseMainWindow() when run in debug mode, as the form is shown.
How can I make the application also listen to CloseMainWindow() without showing it? I don't want the user to be able to interact with the form if not in debug mode.
I've tried several approaches, like displaying the window but setting the size to 0. This shows a small form, i.e. not hidden.
if (!DebugMode)
{
form.Show();
form.Size = new Size(0, 0);
}
Also showing it, and then hiding it does not work:
if (!DebugMode)
{
form.Show();
form.Hide();
}
Showing it, but started minimized and not shown in taskbar does not work either:
if (!DebugMode)
{
form.Show();
form.WindowState = FormWindowState.Minimized;
form.ShowInTaskbar = false;
}
Am I missing something really obvious here, or is it not possible to close processes minimized to tray in a graceful way?

If i've understood the problem correctly, you want to completely hide the form when not in debug mode (i.e. the window is not seen anywhere but in the task manager) and when someone kills the process via task manager, you want to execute some code for clean-up or just get notified.
Basing my solution on this assumption, the following code should work
public static bool DebugMode = false;
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var form = new Form1();
form.Load += (s, e) =>
{
if (!DebugMode)
{
form.Opacity = 0;
form.ShowInTaskbar = false;
}
};
form.FormClosing += (s, e) =>
{
// Breakpoint hits
};
Application.Run(form);
}

I'm not sure you can do it through Process.CloseMainWindow(). Processes with no visible main window, I seem to recall, have MainWindowHandle set to IntPtr.Zero.
You need some kind of workaround. My advice is to keep track manually of the MainWindow Handles yourself:
static void Main()
{
...
MainWindow mainWindow = new MainWindow();
[HandleRepository] = mainWindow.Handle;
Application.Run(mainWindow);
}
Then when you want to close the process, do it with a workaround:
public void EndProcess()
{
Form mainWindow= (MainWindow)Form.FromHandle([HandleRepository]);
mainWindow.Close();
}
Might not be the most elegant solution but it should work (haven't tested it)

Related

Showing invisible window

I have a window with some process and visualization but I want this window to be hidden on startup but still performing it's work. I've managed to achieve this using simple code
SomeWindow.Show();
SomeWindow.Hide();
But the issue is this code causing startup flickering. I can't fight this neither in Windows Forms, nor in WPF. Is there more elegant way to show hidden/invisible window?
UPDATE
I want the window to show in TaskBar but only when it's visible. Window is performing task that relies on rendering that will be performing in time regardless of visibility and user should be able to see it's state like it was open all the time.
Try this:
SomeWindow.ShowInTaskbar = false; // not shown on taskbar set to true if you want to show form on taskbar
SomeWindow.WindowState = FormWindowState.Minimized; // set window state as minimized
SomeWindow.Show();
You don't even need to hide it.
This is winforms version I did not test it in WPF.
Update:
If Hide() is not done after Show() window is on opened windows list (Alt+Tab). To prevent this do:
SomeWindow.Hide();
Based on Logman's answer I've created extension method to show invisible window
For Windows Forms:
public static class FormHelper
{
public static void ShowInvisible(this Form form)
{
// saving original settings
bool needToShowInTaskbar = form.ShowInTaskbar;
FormWindowState initialWindowState = form.WindowState;
// making form invisible
form.ShowInTaskbar = false;
form.WindowState = FormWindowState.Minimized;
// showing and hiding form
form.Show();
form.Hide();
// restoring original settings
form.ShowInTaskbar = needToShowInTaskbar;
form.WindowState = initialWindowState;
}
}
or for WPF:
public static class WindowHelper
{
public static void ShowInvisible(this Window window)
{
// saving original settings
bool needToShowInTaskbar = window.ShowInTaskbar;
WindowState initialWindowState = window.WindowState;
// making window invisible
window.ShowInTaskbar = false;
window.WindowState = WindowState.Minimized;
// showing and hiding window
window.Show();
window.Hide();
// restoring original settings
window.ShowInTaskbar = needToShowInTaskbar;
window.WindowState = initialWindowState;
}
}
Vadim Ovchinnikov's answer for WPF was a great start, but didn't work for me eventually for two reasons: Show() and Hide() are synchronous methods which is a problem when you want to have that window precreated while no other window is open (for there is no Dispatcher executing these requests); furthermore restoring the original values had to be performed later, otherwise a quick flicker was still noticeable. Then again, I had to restore the value of ShowInTaskbar asynchronously; otherwise the taskbar entry was missing, but curiously only when running in the Visual Studio debugger.
The following helper class does the job for me:
public class InitiallyInvisibleWindow
{
private readonly Window _window;
private bool _origShowActivated;
private bool _origShowInTaskbar;
private WindowState _origWindowState;
public InitiallyInvisibleWindow(Window window)
{
_window = window;
}
public void ShowInvisible()
{
_origShowActivated = _window.ShowActivated;
_origShowInTaskbar = _window.ShowInTaskbar;
_origWindowState = _window.WindowState;
_window.ShowActivated = false;
_window.ShowInTaskbar = false;
_window.WindowState = WindowState.Minimized;
_window.Visibility = Visibility.Visible;
_window.Visibility = Visibility.Hidden;
}
public void RestoreVisible()
{
_window.ShowActivated = _origShowActivated;
_window.Visibility = Visibility.Visible;
Dispatcher.CurrentDispatcher.InvokeAsync(() =>
{
_window.ShowInTaskbar = _origShowInTaskbar;
_window.WindowState = _origWindowState;
});
}
}
Why use a window for this task? Why not just start up a class on another thread and have it do the work?
If a window is really needed just have the window when opened request data from that custom task.
var myClass = new MyClass();
Task.Run(()=>myClass.Start());
This works on my machine without "flicker". As Ed mentioned, the Taskbar button behaves as you would expect without addition settings or code.
//Assuming SomeWindow is System.Windows.Form object
SomeWindow.Opacity = 0.0;
SomeWindow.Show();
SomeWindow.Hide();
//Elsewhere in code when you want to display the window
SomeWindow.Opacity = 1.0;
SomeWindow.Visible = true;

Disallowing interaction with background form

On my application's first run, two forms open. The topmost form needs to take priority, and disallow any interaction with the form in the background. I have tried ShowDialog() as referenced here, however this hides the form in the background which I do not wish to do. Is there a method of accomplishing this?
public Form1()
{
InitializeComponent();
if (!fileexists(#"c:\Management Tools\Absence Tracker\bin\data\tbase.skf"))
{ firstrunactions(); }
}
void firstrunactions()
{
//open the get-started form and invite user to populate serialisable objects
firstrun frwindow = new firstrun();
frwindow.ShowDialog();
}
When you are using .ShowDialog() the execution of the containing method is paused until you close the newly opened window. So make sure to do everthing else before you call .ShowDialog(). Otherwise your program gets stuck in this method. If you are calling .ShowDialog() before the background window is shown will cause problems.
But using .ShowDialog() here is totally correct and has the right functionality.
Example how not to do it (causes the same behavior like in your problem):
public Form1()
{
InitializeComponent();
//this is the wrong place for showing a child window because it "hides" its parent
Form frwindow = new Form();
frwindow.ShowDialog(this);
}
The magical place where it works:
private void Form1_Shown(object sender, EventArgs e)
{
Form frwindow = new Form();
frwindow.ShowDialog(this);
}
Edit: In your case it is enough moving if(!fileexistst...) into the Form1_Shown()-event.
Try with frwindow.ShowDialog(this);
Or instead "this" pass the other form as parameter.
Also move this part if (!fileexists(#"c:\Management Tools\Absence Tracker\bin\data\tbase.skf"))
{ firstrunactions(); }
}
in OnLoad override.

I have a system task tray application - Can I close the main form instead of just hiding it?

I have an application that has a main form and a system task tray icon. In the designer of the main form, I dragged the TrayIcon control on the form, so it is a child of the main form.
At this point, when the user presses the close button on the main form, it actually just hides it so that the application wont terminate, unless the user right clicks the TrayIcon and clicks exit. But, the main form has a lot of controls and resources, and when the main form is hidden, it still uses memory for those resources. My goal is to actually dispose of form so it doesn't take up that memory while it is not being used.
Unless I am mistaken, and when the main form is hidden it doesn't take up that memory anymore, but I don't think that is the case.
I'm no expert on memory, I may even be completely mistaken on how memory management works, and thus this question is invalid.
Anyways, if I am correct in that when the main form is only hidden it still takes up memory that can be freed by fully closing the form, is there a way for me to actually close the main form without the application terminating? If so, I would need to create the TrayIcon with code in the Program class instead of in the class of the main form, correct?
No, that's certainly not necessary. It is encouraged by the convenience of the designer but you can easily create an application that only creates a window on demand. You'll have to write code instead. It doesn't take a heckofalot, there's a sample app with basic functionality. Edit the Program.cs file and make it look similar to this (icon required, I called it "SampleIcon"):
static class Program {
[STAThread]
static void Main() {
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
var cms = new ContextMenuStrip();
cms.Items.Add("Show", null, ShowForm);
cms.Items.Add("Exit", null, ExitProgram);
var ni = new NotifyIcon();
ni.Icon = Properties.Resources.SampleIcon;
ni.ContextMenuStrip = cms;
ni.Visible = true;
Application.Run();
ni.Dispose();
}
private static void ShowForm(object sender, EventArgs e) {
// Ensure the window acts like a singleton
if (MainWindow == null) {
MainWindow = new Form1();
MainWindow.FormClosed += delegate { MainWindow = null; };
MainWindow.Show();
}
else {
MainWindow.WindowState = FormWindowState.Normal;
MainWindow.BringToFront();
}
}
private static void ExitProgram(object sender, EventArgs e) {
Application.ExitThread();
}
private static Form MainWindow;
}

Allow a child program to call Close on a Form in the parent app

I have a small launcher program, it loads a Splash screen on it's own thread and displays it. If a set of conditions are met it needs to launch another application and keep the splash screen visible till the other application says it is ok to close the splash screen.
The Launcher will always have a lifetime that starts before Child App and ends after Child App closes.
Here is some snippets of relevant code
The common DLL:
namespace Example.Common
{
public partial class SplashScreen : Form
{
public SplashScreen()
{
InitializeComponent();
}
static SplashScreen splashScreen = null;
static Thread thread = null;
static public void ShowSplashScreen()
{
// Make sure it is only launched once.
if (splashScreen != null)
return;
thread = new Thread(new ThreadStart(SplashScreen.ShowForm));
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
// A static entry point to launch SplashScreen.
static private void ShowForm()
{
splashScreen = new SplashScreen();
Application.Run(splashScreen);
}
// A static method to close the SplashScreen
static public void CloseForm()
{
splashScreen.Close();
}
}
}
The Inital Launcher:
/// <summary>
/// This application is a small launcher to launch the real graphical launcher. It is small and lightweight and should be rarely be updated.
/// It will call the ProgramLauncher, the program launcher will return in it's status code the PID of the instance it launched or -1
/// if no subsequent program was started.
/// </summary>
[STAThread]
static void Main()
{
//Show the Splash screen;
Example.Common.SplashScreen.ShowSplashScreen();
//(Snip)
if (rights == UserRights.None)
{
SplashScreen.CloseForm();
MessageBox.Show("Your user does not have permission to connect to the server.", "Unable to logon", MessageBoxButtons.OK, MessageBoxIcon.Error);
return;
}
//If the user has full desktop access, give it to them and launch a new instance of the launcher.
else if (rights.HasFlag(UserRights.FullDesktopAccess))
{
Process explorer = new Process();
explorer.StartInfo.FileName = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Windows), "explorer.exe");
if (explorer.Start() == false)
{
MessageBox.Show("Explorer failed to start.");
}
else
{
//Close the splash screen.
SplashScreen.CloseForm();
//If the user can shadow start a new instance of the launcher inside explorer.
if (rights.HasFlag(UserRights.ShadowNormalUser) || rights.HasFlag(UserRights.ShadowDemoUser))
{
//Start a new copy of the program so people can use it to shadow easily.
var shadowProc = new Process();
shadowProc.StartInfo.FileName = "ProgramLauncher.exe";
shadowProc.StartInfo.UseShellExecute = false;
shadowProc.Start();
}
explorer.WaitForExit();
}
}
else
{
Process programLauncher = new Process();
programLauncher.StartInfo.FileName = "ProgramLauncher.exe";
programLauncher.StartInfo.UseShellExecute = false;
//Launch the graphical launcher.
programLauncher.Start();
programLauncher.WaitForExit();
//Check to see if the graphical launcher launched some other process.
if (programLauncher.ExitCode >= 0)
{
//If there was a pid, don't close the micro launcher till after it closes.
Process runningProcess = Process.GetProcessById(programLauncher.ExitCode);
runningProcess.WaitForExit();
}
}
}
What is the easiest way to let ProgramLauncher close the SplashScreen instance MicroLauncher created?
You need to have SplashScreen pass it's window handle (HWND) to ProgramLauncher. Then, ProgramLauncher can use the SendMessage winapi function to send a WM_SYSCOMMAND message to the target window:
public const int WM_SYSCOMMAND = 0x0112;
public const int SC_CLOSE = 0xF060;
SendMessage(hwnd, WM_SYSCOMMAND, SC_CLOSE, 0);
In WinForms, you can get a form's native handle with Handle.
The platform invoke code for SendMessage is here.
At least I don't see an easier way now, but I think it's easier than any IPC mechanism out there.
There are lots of ways of doing this, with pros and cons to each. Possibly the easiest way is to redirect standard output from your ProgramLauncher process and wire it up to an event in the MicroLauncher application (see here for an example). From your ProgramLauncher program, you write a certain message to standard output. When that message is received by MicroLauncher, you close the window.
Another option is to pass the HWND of your splash screen to ProgramLauncher as a command-line parameter, then ProgramLauncher can use SendMessage(WM_SYSCOMMAND, SC_CLOSE) to close the window (see here for an example).
You can also look into methods of IPC, sending custom Windows messages, or probably a thousand other possibilities, but those two ideas may get you started.
Easiest way I can think of: have the child app create a named mutex, and have the parent app wait until someone's created it, checking every now and then.
Not very elegant and open to abuse (where another app intentionally creates a mutex with the same name), but in practice, I doubt that'll be a problem.

Program hangs after leaving screen saver or locking computer

Our program works fine, until someone locks the computer or the screen-saver pops up (but not ctrl+alt+delete). Once the computer is unlocked/the screen saver is closed, the application stops drawing everything except the title bar, and stops responding to input - it displays a mostly-white window which can't be moved or closed.
(Example of application freezing - the mountains are from my desktop background)
If we let it sit for about 5~10 minutes, it comes back to life, and doesn't hang again (even after locking the computer/screen saver popup) until the application is restarted.
It's difficult to debug, because it doesn't happen when the program is started from Visual Studio, only when the .exe is manually opened.
It only happens when the splash-screen is shown - if I remove the code to show the splash-screen, it stops happening. We need the splash-screen, however.
I've tried every suggestion on this page; the only one this doesn't happen with is using Microsoft.VisualBasic.WindowsFormsApplicationBase, but that causes all sorts of other problems.
Information about this on the Internet appears to be scarce - has anyone run into a similar problem before?
Here is the relevant code:
//Multiple programs use this login form, all have the same issue
public partial class LoginForm<TMainForm>
where TMainForm : Form, new()
{
private readonly Action _showLoadingForm;
public LoginForm(Action showLoadingForm)
{
...
_showLoadingForm = showLoadingForm;
}
private void btnLogin_Click(object sender, EventArgs e)
{
...
this.Hide();
ShowLoadingForm(); //Problem goes away when commenting-out this line
new TMainForm().ShowDialog();
this.Close();
}
private void ShowLoadingForm()
{
Thread loadingFormThread = new Thread(o => _showLoadingForm());
loadingFormThread.IsBackground = true;
loadingFormThread.SetApartmentState(ApartmentState.STA);
loadingFormThread.Start();
}
}
Here is an example of one of the _showLoadingForm actions used in one of the programs:
public static bool _showSplash = true;
public static void ShowSplashScreen()
{
//Ick, DoEvents! But we were having problems with CloseSplashScreen being called
//before ShowSplashScreen - this hack was found at
//https://stackoverflow.com/questions/48916/multi-threaded-splash-screen-in-c/48946#48946
using(SplashForm splashForm = new SplashForm())
{
splashForm.Show();
while(_showSplash)
Application.DoEvents();
splashForm.Close();
}
}
//Called in MainForm_Load()
public static void CloseSplashScreen()
{
_showSplash = false;
}
Splash Screen Issues
The DoEvents thing is very undesirable and doesn't necessarily accomplish what you think it does. DoEvents tell the CLR to attend to the windows message loop (for the splash screen), but doesn't necessarily offer up any processing time to other threads. Thread.Sleep() will offer other threads a chance to process, but won't necessarily allow the windows message loop for your splash screen to continue pumping messages. So you really need both if you must use a loop, but in a minute I'm going to recommend getting away from this loop altogether. In addition to that loop issue, I don't see any explicit way the splash thread is being cleaned up. You need some kind of Thread.Join() or Thread.Abort() happening somewhere.
Instead of using a Application.DoEvents() loop, I like to use a ManualResetEvent to synchronize the splash forms start up with the calling thread. That way the ShowSplash() method doesn't return until the splash is shown. Anytime after that we are obviously ok to close it down as we know it was finished being shown.
Here's a thread with a few good examples:.NET Multi-threaded Splash Screens in C#
Here's how I modified my favorite example, that #AdamNosfinger posted, to include a ManualResetEvent to synchronize the ShowSplash method with the splash screen thread:
public partial class FormSplash : Form
{
private static Thread _splashThread;
private static FormSplash _splashForm;
// This is used to make sure you can't call SplashScreenClose before the SplashScreenOpen has finished showing the splash initially.
static ManualResetEvent SplashScreenLoaded;
public FormSplash()
{
InitializeComponent();
// Signal out ManualResetEvent so we know the Splash form is good to go.
SplashScreenLoaded.Set();
}
/// <summary>
/// Show the Splash Screen (Loading...)
/// </summary>
public static void ShowSplash()
{
if (_splashThread == null)
{
// Setup our manual reset event to syncronize the splash screen thread and our main application thread.
SplashScreenLoaded = new ManualResetEvent(false);
// show the form in a new thread
_splashThread = new Thread(new ThreadStart(DoShowSplash));
_splashThread.IsBackground = true;
_splashThread.Start();
// Wait for the splash screen thread to let us know its ok for the app to keep going.
// This next line will not return until the SplashScreen is loaded.
SplashScreenLoaded.WaitOne();
SplashScreenLoaded.Close();
SplashScreenLoaded = null;
}
}
// called by the thread
private static void DoShowSplash()
{
if (_splashForm == null)
_splashForm = new FormSplash();
// create a new message pump on this thread (started from ShowSplash)
Application.Run(_splashForm);
}
/// <summary>
/// Close the splash (Loading...) screen
/// </summary>
public static void CloseSplash()
{
// need to call on the thread that launched this splash
if (_splashForm.InvokeRequired)
_splashForm.Invoke(new MethodInvoker(CloseSplash));
else
Application.ExitThread();
}
}
Main Form Issues
It looks as though you are launching your mainform from your login window using ShowDialog and then closing the login form. Have I understood correctly? This is not good if so. ShowDialog is intended for child windows of your application and wants to have an owner window, if you don't specify an owner form in the method arguments the currently active window is assumed to be the owner. See MSDN
So your main form is assuming the login form is its parent, but you close the login form shortly after showing the main form. So I'm not sure what state the application is left in at that point. You should consider using a standard Form.Show() method instead and simply adjusting the Form properties to appear like a dialog if this is the desired outcome (ex: BorderStyle, MaximizeBox, MinimizeBox, ControlBox, TopMost).
IMPORTANT EDIT: Ok I'm human, I messed up and forgot ShowDialog was a blocking method. While that does negate the owner handle issue, I still recommend not using ShowDialog for your main application form unless you can provide a significant justification for it that is not appearance or threading related (as those should be fixed with other techniques). The advice is still sound, despite the misstep on my part.
Possible Painting Issues
You did not specify which controls you were using or if you were doing any custom painting in your application. But you need to keep in mind some windows handles will be forcibly closed when you lock the computer. For example if you have some custom painted controls and are caching fonts, brushes or other GDI resources you need to have some try { ... } catch { ... } blocks in your code that dispose of and then rebuild the cached GDI resources when an exception is raised during painting. I've run into this before where I was custom painting a list box and caching some GDI objects. If you have any custom painting code anywhere in your app, including in the splash screen, please double check all GDI objects are nicely disposed/cleaned up.
After adding a few lines of code to the code snippets above, I could compile a working program. However, I could not reproduce the problem (Windows 7 Starter). I tried locking the computer, and starting the screen saver, too. I did this while the splash screen was active, and in other situations, but the main window always remained responsive. I think there must be something else going on here, probably during the initialization of the main window.
Here is the code, maybe it helps the others figure out the problem.
using System;
using System.Threading;
using System.Windows.Forms;
public class MainForm : Form
{
//Here is an example of one of the _showLoadingForm actions used in one of the programs:
public static bool _showSplash = true;
public static void ShowSplashScreen()
{
//Ick, DoEvents! But we were having problems with CloseSplashScreen being called
//before ShowSplashScreen - this hack was found at
//http://stackoverflow.com/questions/48916/multi-threaded-splash-screen-in-c/48946#48946
using(SplashForm splashForm = new SplashForm())
{
splashForm.Show();
while(_showSplash)
Application.DoEvents();
splashForm.Close();
}
}
//Called in MainForm_Load()
public static void CloseSplashScreen()
{
_showSplash = false;
}
public MainForm()
{
Text = "MainForm";
Load += delegate(object sender, EventArgs e)
{
Thread.Sleep(3000);
CloseSplashScreen();
};
}
}
//Multiple programs use this login form, all have the same issue
public class LoginForm<TMainForm> : Form where TMainForm : Form, new()
{
private readonly Action _showLoadingForm;
public LoginForm(Action showLoadingForm)
{
Text = "LoginForm";
Button btnLogin = new Button();
btnLogin.Text = "Login";
btnLogin.Click += btnLogin_Click;
Controls.Add(btnLogin);
//...
_showLoadingForm = showLoadingForm;
}
private void btnLogin_Click(object sender, EventArgs e)
{
//...
this.Hide();
ShowLoadingForm(); //Problem goes away when commenting-out this line
new TMainForm().ShowDialog();
this.Close();
}
private void ShowLoadingForm()
{
Thread loadingFormThread = new Thread(o => _showLoadingForm());
loadingFormThread.IsBackground = true;
loadingFormThread.SetApartmentState(ApartmentState.STA);
loadingFormThread.Start();
}
}
public class SplashForm : Form
{
public SplashForm()
{
Text = "SplashForm";
}
}
public class Program
{
public static void Main()
{
var loginForm = new LoginForm<MainForm>(MainForm.ShowSplashScreen);
loginForm.Visible = true;
Application.Run(loginForm);
}
}
Several years later (with the code no longer in front of me), I'll add an answer for anyone else who experiences this problem.
The issue turned out to be exactly as Hans Passant had guessed. The problem was that, due to some incredibly obscure and innocuous bugs in the .Net framework, InvokeRequired can sometimes return false when it should return true, causing code that should run on the GUI thread to run in the background (which, due to some more obscure and innocuous bugs, causes the behavior I was seeing).
The solution is to not rely on InvokeRequired, using a hack similar to this:
void Main()
{
Thread.Current.Name = "GuiThread";
...
}
bool IsGuiThread()
{
return Thread.Current.Name == "GuiThread";
}
//Later, call IsGuiThread() to determine if GUI code is being run on GUI thread
This solution, as well as an extremely in-depth look at the causes of the issue, was found here.
since there is no working example
can you try removing Application.DoEvents(); and inserting a thread.sleep?
Application.DoEvents(); let say can be very evil.
From the quick scan I did of your code, it looks like the key to your problem might be using
Application.Run(_splashForm);
Ideally you would use that inside a thread, but maybe it would work in conjunction with your DoEvents too. Sorry if you are doing that and I just missed it...
In our application we had some similar problems with the splash screen. We wanted to have a splash screen with an animated gif (don't blame on me, it was a management decision). That only works correctly, when the splashScreen has its own message loop. Because I think the DoEvents is the key to your problem, I show you, how we solved it. Hopefully it will help you to solve your problem!
We're going to show the splash screen in that way:
// AnimatedClockSplashScreen is a special form from us, it can be any other!
// Our form is set to be TopMost
splashScreen = new AnimatedClockSplashScreen();
Task.Factory.StartNew(() => Application.Run(splashScreen));
The splash screen is a simple containing the animated gif of a clock. It doesn't have any loop, so it doesn't steel any time.
When the splash needs to be closed, we do it in that way:
if (splashScreen != null)
{
if (splashScreen.IsHandleCreated)
{
try
{
splashScreen.Invoke(new MethodInvoker(() => splashScreen.Close()));
}
catch (InvalidOperationException)
{
}
}
splashScreen.Dispose();
splashScreen = null;
}
remove this line, you don't need it, You are forcing it to a single thread when the default is mta. Take the default.
loadingFormThread.SetApartmentState(ApartmentState.STA);
change the following:
using(SplashForm splashForm = new SplashForm())
{
splashForm.Show();
while(_showSplash)
Application.DoEvents();
splashForm.Close();
}
to:
SplashForm splashForm = new SplashForm())
splashForm.Show();
Change this:
public static void CloseSplashScreen()
{
_showSplash = false;
}
to this:
public static void CloseSplashScreen()
{
splashForm.Close();
}
Here's a shot in the dark: when we idle, we also ask the thread to go to sleep. I'm not sure that this will help, but it's worth a shot:
while(_showSplash) {
System.Threading.Thread.Sleep(500);
Application.DoEvents();
}
Have you tried using a WaitHandle for showing the form in the thread?
Something like:
EventWaitHandle _waitHandle = new AutoResetEvent(false);
public static void ShowSplashScreen()
{
using(SplashForm splashForm = new SplashForm())
{
splashForm.Show();
_waitHandle.WaitOne();
splashForm.Close();
}
}
//Called in MainForm_Load()
public static void CloseSplashScreen()
{
_waitHandle.Set();
}
I think your problem is because you are using Form.ShowDialog, not Application.Run. ShowDialog runs a restricted message loop that runs on top of the main message loop and ignores some windows messages.
Something like this should work:
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault( false );
Application.Run( new MainForm() );
}
}
public partial class MainForm: Form
{
FormSplash dlg = null;
void ShowSplashScreen()
{
var t = new Thread( () =>
{
using ( dlg = new FormSplash() ) dlg.ShowDialog();
}
);
t.SetApartmentState( ApartmentState.STA );
t.IsBackground = true;
t.Start();
}
void CloseSplashScreen()
{
dlg.Invoke( ( MethodInvoker ) ( () => dlg.Close() ) );
}
public MainForm()
{
ShowSplashScreen();
InitializeComponent();
Thread.Sleep( 3000 ); // simulate big form
CloseSplashScreen();
}
}

Categories