I have a problem with changing my UI strings after the user has changed the language in the option window. To change the UI strings of the main form, I have to restart the program every time, so that changes take effect, but that's annoying. So I tried it with a delegate to call the function, which loads the strings for the main window in the option window after saving the new settings. The function is called in the option window, but it doesn't change the strings of the main window.
Code in the main window
public delegate void CallLoadUIStrings(SupportedLanguages lang);
public CallLoadUIStrings callLoadUIStrings;
public Renamer()
{
callLoadUIStrings = new CallLoadUIStrings(LoadUIStrings);
}
public void LoadUIStrings(SupportedLanguages lang)
{
try
{
switch (lang)
{
#region "DE/JA/FR/ES/NL"
case SupportedLanguages.De:
case SupportedLanguages.Ja:
case SupportedLanguages.Fr:
case SupportedLanguages.Es:
case SupportedLanguages.Nl:
// reads the language file where the ui strings are stored
langHelper.Read(RenamerLangOpener.RenamerMainWindow);
this.mnuFile.Text = langHelper.Files;
this.mnuClose.Text = langHelper.Close;
this.mnuEdit.Text = langHelper.Edit;
this.mnuUndo.Text = langHelper.Undo;
this.mnuCut.Text = langHelper.Cut;
this.mnuCopy.Text = langHelper.Copy;
this.mnuPaste.Text = langHelper.Paste;
this.mnuDelete.Text = langHelper.Delete;
this.mnuSelectAll.Text = langHelper.SelectAll;
#endregion
}
}
catch (Exception ex) { //exception handling }
}
private void mnuOpt_Click(object sender, EventArgs e)
{
Preferences opt = new Preferences(this);
opt.ShowDialog();
}
Code in the option window
internal Renamer instance = null;
public Preferences(Renamer form)
{
instance = form;
}
public void UpdateUI()
{
langHelper.ReadSettingsValues();
instance.BeginInvoke(instance.callLoadUIStrings,new object[] { langHelper.GetLang});
}
Since I've never worked with delegates I don't have a clue where the mistake is.
I've googled so much to find a solution for a similar problem, but I haven't found something that matched my problem.
I assume this is winforms and not WPF question, and that you have one main form that is open from app's Main function. My solution to changing a language is to open this one form in a loop, and continue the loop as long as the form has a property set to some language identifier. If this property is set then I change the language to that value, and go for another loop iteration. I copy all other properties that need be copied form one form to another, with main being the form's position.
If the form is closed without the language ID being set then we break the loop and exit application as usual.
Related
I'm having trouble manipulating forms when from another thread.
I've overcome the issue by loading the form at runtime, showing it then hiding it. This means the form is created on the right thread and can be manipulated using invokes.
This is not the right way to do it. I have 3 problems that come from using this method
I can't spawn another popup box I have to use the one I created at runtime
The forms flash briefly on load - now that I have 3 forms its pretty obvious what I'm doing.
I have to use a variable bool to hold if the popup is open or not.
If anyone could point me in the right direction It would be much appreciated. Currently my code looks like:
On Main form Load:
CallerIDfrm = new frmCallerID();
CallerIDfrm.Show();
CallerIDfrm.Hide();
to manipulate the popup Im using
delegate void StringArgReturningVoidDelegate1(string CallerIDnum, string CallerIDname, string ContactID);
private void CallerID(string CallerIDnum, string CallerIDname, string ContactID)
{
if (CallerIDfrm.InvokeRequired)
{
StringArgReturningVoidDelegate1 d = new StringArgReturningVoidDelegate1(CallerID);
CallerIDfrm.Invoke(d, new object[] { CallerIDnum, CallerIDname, ContactID });
}
else
{
if (ContactID != null || ContactID != "0")
{
CallerIDfrm.ContactID = ContactID;
}
CallerIDfrm.Mainfrm = this;
CallerIDfrm.TopLevel = true;
CallerIDfrm.TopMost = true;
CallerIDfrm.lblCallerIDname.Text = CallerIDname;
CallerIDfrm.lblCallerIDnum.Text = CallerIDnum;
CallerIDfrm.Show();
CallerIDOpen = true;
}
}
To Hide the popup until required again im using:
delegate void StringArgReturningVoidDelegate2();
private void CallerIDClose()
{
if (CallerIDfrm.InvokeRequired)
{
StringArgReturningVoidDelegate2 d = new StringArgReturningVoidDelegate2(CallerIDClose);
CallerIDfrm.Invoke(d, new object[] { });
}
else
{
try
{
CallerIDfrm.Hide();
CallerIDOpen = false;
}
catch
{
}
}
}
I've tried otherways but the Popup loads as if it is not responding and I loose access to the popup.
Ultimately I'd like to be able to spawn multiple popups and have the ability to close them from the Main Form.
What I gather from your question: You have an caller api/lib/class and you like to show CallerId on a popup form when a call is received. Have a look at Events and Event Driven programming.
The following codes has not been tested, I wrote it from top of my head. Might not compile, they are here to show an example:
Create an CallReceived event in api/lib class as follows:
public event EventHandler<CallReceivedEventArgs> CallReceived;
protected void OnCallReceived(EventArgs e)
{
var handler = CallReceived;
if (handler != null)
handler(this, e);
// Note: For C# 6.0 and later, above statements can be simplified to
// CallReceived?.Invoke(this, e);
}
Note: If you don't have access to this api/lib code, create a Gateway class and put your event in there along with mechanism to trigger it.
Also create a CallReceivedEventArgs, this will be used to transfer event data:
public class CallReceivedEventArgs : EventArgs
{
public string CallerIDnum {get; set;}
public string CallerIDname {get; set;}
public string ContactID {get; set;}
}
Now, in your api/lib class raise this event whenever a call is received:
// a call received, replace dummy values with actual values
OnCallReceived(new CallReceivedEventArgs() { CallerIDnum="5554443322", CallerIDname="SOME_NAME", ContactID="SOME_CONTACT" });
Finally in your GUI form, register to said event and process accordingly.
// inside your main form class
private CallerAPI callerApi = new CallerAPI();
// somewhere inside you main form class, register to event
// from your use case, I would place it inside Main Form's constructor
callerApi.CallReceived += callerApi_Callreceived;
// receive event
void callerApi_Callreceived(object sender, CallReceivedEventArgs e)
{
var callerIDnum = e.CallerIDnum;
// etc.
// show callerId form with details
// you need to change frmCallerID's constructor accordingly
CallerIDfrm = new frmCallerID(e.CallerIDnum, CallerIDname, ContantID);
// to be able to track opened popups, you can store them inside a list
// private List<Form> openPopupList = new List<Form>();
//
// alternatively, you can assign CallerIDnum to form's name property
// and store these inside a List<string> instead of List<Form>
openPopupList.add(CallerIDfrm);
CallerIDfrm.Show();
}
Don't forget to unregister from event.
callerApi.CallReceived -= callerApi_Callreceived;
To wrap it up:
I can't spawn another popup box I have to use the one I created at runtime
You can create and show multiple frmCallerID, independent from each other.
The forms flash briefly on load - now that I have 3 forms its pretty obvious what I'm doing.
Since new approach creates CallerID forms based on events, you won't see these form flashing. It'll open whenever a CallReceived event is received.
I have to use a variable bool to hold if the popup is open or not.
A better approach would be: Register to forms FormClosed event, and remove from openPopupList accordingly.
frmCallerID.FormClosed += frmCallerID_FormClosed;
void frmCallerID_FormClosed(object sender, EventArgs e)
{
// remove form from openPopupList
frmCallerID closedPopup = (frmCallerID) sender;
openPopupList.remove(closedPopup);
}
I'm trying to pass a variable from one form to another form textbox. The 'variable' is a result of a calculation based on the user inputs.
Below is the code for the parent form(RuleInsertForm) where I'm calling the subform(Helpformula) to get the user inputs.
public partial class RuleInsertForm : Form
{
public string helpformulainputs;
}
private void RuleInsertForm_Load(object sender,EventArgs e)
{
if (helpformulainputs=="")
{
textBox_Inputs.Text = "";
}
else
{
textBox_Inputs.Text = helpformulainputs;
}
}
Below is the code for the subform(Helpformula) where i'm passing the result variable(formulainputs) to the parent form(RuleInsertForm).
public partial class HelpFormula : Form
{
public string formulainputs = string.Empty;
private void button_generateformula_Click(objectsender, EventArgs e)
{
using (RuleInsertForm insertform = new RuleInsertForm())
{
insertform.helpformulainputs = formulainputs;
this.Close();
insertform.Show();
}
}
}
Problem:
The values are getting passed to the text box but in the UI its not getting dispalyed.
so far I tried to push data back to parent form and then tried to display the data in the textbox where I failed.(I dont know where it went wrong suggest me if I can resolve the below one)
Now I need an alternative method to this for eg: instead of pushing the data back to parent form i need to make the variable available for all the forms trying to use the subform(formulainputs)
How can I acheive this process ? any suggestions are much appreciated.
The problem seems to be that insertForm.Show() does not block the execution of your button handler. Show opens the insertform as non-modal.
So after insertform is opened, the execution is continued in button_generateformula_Click and when you exit the using block, the insertform is disposed and therefore closed.
To solve this you may call insertForm.ShowDialog() instead.
For different ways of communicating between Forms look here or simply type communicate between forms into the SO search box.
I created a WPF application and then converted it into a DLL by removing the app.xaml and setting the build to Class Library. I'm using a C# Windows Forms to test the DLL. The WPF DLL is preloaded so it can later on be called to show and display instantly without having to wait for a load. What I am trying to accomplish is to call the Show(ShowWPFApp) and have the code wait until a boolean is flipped by calling WPFAppResponse (this action is passed in via the initial load). The way I have it right now causes the UI to freeze up. Any idea on how i can get it to wait without the UI freezing up?
Windows Form calling WPF DLL
namespace WindowsFormsDLLTest
{
public partial class Form1 : Form
{
WPFDLL.LoadWPFApp wpfApp = null;
public Form1()
{
InitializeComponent();
}
private void btnLoadWPFApp_Click(object sender, EventArgs e)
{
wpfApp = new WPFDLL.LoadWPFApp();
try
{
wpfApp.Load();
}
catch (Exception ex)
{
}
}
private void btnShowWPFApp_Click(object sender, EventArgs e)
{
try
{
string result = null;
result = wpfApp.ShowWPFApp("John Doe");
}
catch (Exception ex)
{
}
}
}
}
WPF DLL Application
namespace WPFDLL
{
public class LoadWPFApp
{
private Application application = null;
private MainWindow mainWindow = null;
private bool waitOnShowWindow {get; set;}
private string returnResults = null;
public void Load()
{
StartLoadingWPFApp();
}
[STAThread]
private void StartLoadingWPFApp()
{
application = new Application();
SplashScreenWindow splashWindow = new SplashScreenWindow();
splashWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
splashWindow.Show();
try
{
mainWindow = new MainWindow(WPFAppResponse);
mainWindow.WindowStartupLocation = WindowStartupLocation.CenterScreen;
splashWindow.Close();
application.Run();
}
catch (Exception ex)
{
splashWindow.Close();
MessageBox.Show("Error starting application:" + Environment.NewLine + ex.ToString(), "WPF App Error", MessageBoxButton.OK, MessageBoxImage.Error);
mainWindow = null;
}
}
public string ShowWPFApp(string person)
{
returnResults = null;
mainWindow.LoadPerson(person);
mainWindow.Show();
while(waitOnShowWindow)
{
//Code waits until bool is set to false
}
return returnResults;
}
public void WPFAppResponse(string person)
{
returnResults = person;
waitOnShowWindow = false;
mainWindow.Hide();
}
}
}
Launching a WPF app from Windows forms is messy. You have stumbled onto a rather complex threading problem. The general recommendation is to instead create that WPF application as a WPF control library. However, I see that this may not resolve the slow loading issue that you have, which is why you made the lightweight WinForms wrapper app.
The problem is your loop:
while(waitOnShowWindow)
{
//Code waits until bool is set to false
}
That loop is running on the UI thread, blocking it from processing windows messages. (If that concept is new to you, go look it up as it is important for Windows UI stuff.) For the UI to respond, it must be running a Windows message loop. I see two solutions:
Create your own message loop.
Return immediately, then get the result later.
Solution 1:
To create your own message loop, you need to use something like Dispatcher.Run() or Dispatcher.PushFrame(). Try this and see if it works:
public string ShowWPFApp(string person)
{
returnResults = null;
mainWindow.LoadPerson(person);
mainWindow.Show();
Dispatcher.CurrentDispatcher.Run();
// do whatever to get the results
return returnResults;
}
If that doesn't work, you might need to use PushFrame() instead. Here are some more in depth articles on that topic in case Dispatcher.Run() doesn't work.
http://www.codeproject.com/Articles/152137/DispatcherFrame-Look-in-Depth
http://reedcopsey.com/2011/11/28/launching-a-wpf-window-in-a-separate-thread-part-1/
Solution 2:
public void ShowWPFApp(string person)
{
returnResults = null;
mainWindow.LoadPerson(person);
mainWindow.Show();
}
Now, the call will not block the UI thread and the WPF window will appear. The windows forms application is running a message loop, so the WPF can now run. But how do you get the result!? Since it is now running asynchronously, you will have to find some other way to get the return value back. I think an event on the MainWindow class would be the easiest way.
Also: That [STAThread] attribute isn't doing anything. [STAThread] only has meaning on the entry point of the app. Fortunately, your Windows forms app already puts [STAThread] on the Main() method, so your thread is an STA thread.
A work around could be using
await Task.Delay(1000);
inside your while loop. This might delay every run of your while loop and the UI will not freeze up. Am not sure if this would work for your case. Try and let me know. Hope this helps.
You will need to give execution back to the UI thread's event loop so that the UI doesn't freeze up. In a forms app, you can do this as follows:
while(waitOnShowWindow)
{
//Code waits until bool is set to false
System.Windows.Forms.Application.DoEvents();
}
Edit:
As Pointed out by Eric, there are potential problems with using DoEvents(), so don't go wild with it. See How to use DoEvents() without being "evil"?.
In a test app like this, it allows the code to work. However, a better solution would be to re-structure the application so that the call is unnecessary, using multi-threading if needed.
Im currently facing the problem that when i try to set focus on some control (textBox), nothing happens, maybe i just overlooked something.(somewhere i found that focus is "low-level" method and that select() should be used instead, however, it doesnt work as well)
From form Login, i launch new instance of EncryptPSW form
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
EncryptPSW ePSW = new EncryptPSW();
ePSW.setOsLog(false, this);
ePSW.ShowDialog();
}
On Button(which is located on EncryptPSW form ) click event i call fill method
public void fill()
{
if (textBoxPSW.Text.Length == 8)//psw has to be 8 chars long
{
if (save)//determinating whether save or fetch of data should be done
{ login.launchSave(textBoxPSW.Text,this); }
else { login.launchOpen(textBoxPSW.Text,this); }
}
else { MessageBox.Show("The password must contain 8 characters");}
}
Which launches either save or open method from Login (my problem is just with open, since during save i dont need to do anything with Focus)
public void launchOpen(string psw,EncryptPSW ePSW)
{
ePSW.Close();
Encryptor.DecryptFile("loggin.bin", psw, this); //decrypting data and setting textBoxes Text property into the fetched ones
setFocus();
}
After all the work is done, setFocus() should be called in order to set focus and other properties.
public void setFocus()
{
textBoxDatabase.Focus();
textBoxDatabase.SelectionStart = textBoxDatabase.TextLength - 1;
textBoxDatabase.SelectionLength = 0;
}
I tried so many different ways, like:
Calling setFocus() from within EncryptPSW_FormClosed
Calling whole open process after the EncryptPSW is closed (from within EncryptPSW_FormClosed)
and many more, however i dont remember it all.
In the case of Form_Closed the weird thing is, that when i tried to show a message box from there instead of setting focus (just to see where the problem might be), it's showed before the EncryptPSW form is closed.
My only guess about this is that the instance of EncryptPSW is somehow blocking Login form and it's controls
I hoped i described my problem well enough and that it makes at least a bit of sense ;]
Thanks in advance,
Regards,
Releis
Since the textbox is in the login form, and you are opening the EcryptPWS from it as a dialog (child), your login form will not be able to set focus to anything. You will need to set focus after it is closed. You can do this:
private void openToolStripMenuItem_Click(object sender, EventArgs e)
{
using(EncryptPSW ePSW = new EncryptPSW())
{
ePSW.setOsLog(false, this);
if (ePSW.ShowDialog() == DialogResult.OK)
{
textBoxDatabase.Focus();
}
}
}
public void launchOpen(string psw,EncryptPSW ePSW)
{
ePSW.DialogResult = DialogResult.OK;
ePSW.Close();
Encryptor.DecryptFile("loggin.bin", psw, this); //decrypting data and setting textBoxes Text property into the fetched ones
}
OK this maybe the ugliest thing I saw round this but.
using
public void setFocus()
{
textBoxDatabase.Focus();
textBoxDatabase.SelectionStart = textBoxDatabase.TextLength - 1;
textBoxDatabase.SelectionLength = 0;
}
Change your code at
public void launchOpen(string psw,EncryptPSW ePSW)
{
ePSW.Close();
Encryptor.DecryptFile("loggin.bin", psw, this); //decrypting data and setting textBoxes Text property into the fetched ones
setFocus();
}
to
delegate void settingfocus();
public void launchOpen(string psw,EncryptPSW ePSW)
{
ePSW.Close();
Encryptor.DecryptFile("loggin.bin", psw, this); //decrypting data and setting textBoxes Text property into the fetched ones
settingfocus sf = new settingfocus(setFocus);
this.BeginInvoke(sf);
}
This worked for me
(Sorry for apparently thinking insert "this" before procedure, and change line x to this was legable)
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();
}
}