Why is my Form so shy? - c#

EDIT 2
Okay, based on the advice on the answers below I eliminated my thread approach and now my program looks like this:
program.cs
static void Main(){
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
FrmWWCShell FrmWWCShell = null;
var splash = new FrmSplash();
splash.SplashFormInitialized += delegate
{
FrmWWCShell = new FrmWWCShell();
splash.Close();
};
Application.Run(splash);
Application.Run(FrmWWCShell);
}
And FrmSplash.cs like this:
public partial class FrmSplash : Form
{
public FrmSplash()
{
InitializeComponent();
}
protected override void OnLoad(EventArgs e)
{
splashTimer.Interval = 1;
splashTimer.Tick +=
delegate { if (SplashFormInitialized != null) SplashFormInitialized(this, EventArgs.Empty); };
splashTimer.Enabled = true;
}
public event EventHandler SplashFormInitialized;
}
The problem is that it doesn't work at all now. The splash screen pops up for a split second, the marque progress bar never even initializes, and then disappears while I wait the 10 secs for the dll's and Main Form to show up while staring at nothing....
Color me severely confused now!
ORIGINAL POST--> for reference
I implemented a App Loading splash screen that operates on a seperate thread while all of the dll's are loading and the form is getting "Painted." That works as expected. What is strange is that now when the Splash form exits it sends my main Form to the back, if there is anything else open(i.e. Outlook). I start the thread in Program.cs,
static class Program
{
public static Thread splashThread;
[STAThread]
static void Main()
{
splashThread = new Thread(doSplash);
splashThread.Start();
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new FrmWWCShell());
}
private static void doSplash()
{
var splashForm = new FrmSplash();
splashForm.ShowDialog();
}
}
And then I end it once my FrmSearch_Shown event is fired.
private void FrmSearch_Shown(object sender, EventArgs e)
{
Program.splashThread.Abort();
this.Show();
this.BringToFront();
}
I, as you can see, have tried calling a Show() and/or BringToFront() on FrmSearch and it still "jumps" to the back.
What am I missing?
What else can I try?
Am I doing this so horribly ignorant that it is my process that is causing this?
Should I file for early retirement?
Thanks for any insight!
EDIT 1
I tried setting the TopMost Property on my Main Form to TRUE. This keeps my form from hiding but it also keeps the user from looking at any other app. Seems a little narcissistic of me...

First of all, it's very important that UI work is done on the primary application thread. I'm actually kind of surprised that you're not getting more serious errors already by showing the splash screen on a background thread.
Here's a technique I've used:
Use Application.Run on your splash form rather than your "real" form.
In your splash form, have an initialized event:
public event EventHandler SplashFormInitialized
Create a timer that fires in one millisecond, and triggers that event.
Then in your application run method you can load your real form, then close your splash form and do an Application.Run on the real form
var realForm = null;
var splash = new SplashForm();
splash.SplashFormInitialized += delegate {
// As long as you use a system.windows.forms.Timer in the splash form, this
// handler will be called on the UI thread
realForm = new FrmWWCShell();
//do any other init
splash.Close();
}
Application.Run(splash); //will block until the splash form is closed
Application.Run(realForm);
The splash might include:
overrides OnLoad(...)
{
/* Using a timer will let the splash screen load and display itself before
calling this handler
*/
timer.Interval = 1;
timer.Tick += delegate {
if (SplashFormInitialized != null) SplashFormInitialized(this, EventArgs.Empty);
};
timer.Enabled = true;
}

Try calling Application.DoEvents() right after show.
Warning: do not call DoEvents very often, but this is one of those time.
EDIT: Clyde noticed something I did not: you are threading this. Don't run any UI on another thread. Take out the thread, leave in the Application.DoEvents().

Related

My second form is not showed but the code inside is executed [duplicate]

I am trying to start a winForm from a thread, but when i do so, the form show but none of the labels are loaded ( the background where they should be is white ) and the form is frozen.
I've tried it with some other winForms that i know that work just fine and it still doesn't seem to work ? Has anyone encountered this problem ?
I know the question is vague but there isn't really any specific code that I could give to help understand the problem.
That is because the Message Loop runs on UI thread only. And when a control or window is created in any other thread, it cannot access that message loop. And thus, cannot process user input.
To solve this, try creating a window from UI thread and create a thread from that window to do whatever you want to do in different thread.
UI thread is supposed to be one.
Then, I suggest you to open your form calling a method of your original form thread, like in the example below:
(To test it just create an empty form called MainForm and paste this code in it)
public delegate void OpenFormDelegate(string txt);
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
var button1 = new Button();
button1.Text = "Run for 5 secs and open new window";
button1.Dock = DockStyle.Top;
this.Controls.Add(button1);
button1.Click += new EventHandler(button1_Click);
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(Run));
t.Start();
}
public void Run()
{
Thread.Sleep(5000); // sleep for 5 seconds
this.BeginInvoke(new OpenFormDelegate(OpenNewForm), "Hello World !");
}
public void OpenNewForm(string text)
{
Form f = new Form();
f.Text = text;
f.Show();
}
}
It is related to thread access, when the new form is created it will not be able to access the UI thread. Use the main thread to create the form and new thread to process the infomation.

How to ask the GUI thread to create objects?

I've got the following program flow in my Windows Forms application (WPF is not a viable option unfortunately):
The GUI Thread creates a splash screen and a pretty empty main window, both inheriting Form.
The splash screen is shown and given to Application.Run().
The splash screen will send an event which triggers an async Event Handler which performs initialization, using the IProgress interface to report progress back to the GUI. (This works flawlessly.)
At some point during the initialization, I need to dynamically create GUI components based on information provided by certain plugins and add them to the Main Window.
At this point I'm stuck: I know I need to ask the GUI thread to create those components for me, but there is no Control I could call InvokeRequired on. Doing MainWindow.InvokeRequired works neither.
The only idea I could come up with was to fire an event which is connected to a factory in the GUI Thread, and then wait for that factory to fire another event which provides the created controls. However I am pretty sure there is a more robust solution. Does anyone know how to achieve this?
Using the comments on my question, especially the note about the continuation method which made me find this very useful question, I achieved the following:
The first part of initialization is performed asynchronously (no change).
The second part of the initialization (which creates the UI elements) is performed afterwards as a Continuation Task, in the context of the UI thread.
Apart from the rather short GUI initialization part, the Splash Screen is responsive (i.e. the mouse cursor does not change to "Waiting" once it hovers the Splash Screen).
Neither of the initialization routines knows the splash screen at all (i.e. I could easily exchange it).
The core controller only knows the SplashScreen interface and does not even know it is a Control.
There currently is no exception handling. This is my next task but doesn't affect this question.
TL;DR: The code looks somewhat like this:
public void Start(ISplashScreen splashScreen, ...)
{
InitializationResult initializationResult = null;
var progress = new Progress<int>((steps) => splashScreen.IncrementProgress(steps));
splashScreen.Started += async (sender, args) => await Task.Factory.StartNew(
// Perform non-GUI initialization - The GUI thread will be responsive in the meantime.
() => Initialize(..., progress, out initializationResult)
).ContinueWith(
// Perform GUI initialization afterwards in the UI context
(task) =>
{
InitializeGUI(initializationResult, progress);
splashScreen.CloseSplash();
},
TaskScheduler.FromCurrentSynchronizationContext()
);
splashScreen.Finished += (sender, args) => RunApplication(initializationResult);
splashScreen.SetProgressRange(0, initializationSteps);
splashScreen.ShowSplash();
Application.Run();
}
It is much easier to manage multiple forms and display one while the other is working or being constructed.
I suggest you try the following:
When application is started you create splash screen form so your Program.cs is like this
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new SplashForm());
}
Inside the splash form constructor, create a new thread (I will use BackgroundWorker but there are other options like tasks) to build your main form.
public SplashForm()
{
InitializeComponent();
backgroundWorker1.WorkerSupportsCancellation = true;
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.DoWork += new DoWorkEventHandler(backgroundWorker1_DoWork);
backgroundWorker1.ProgressChanged += new ProgressChangedEventHandler(backgroundWorker1_ProgressChanged);
backgroundWorker1.RunWorkerAsync();
}
Now we need to write the SplashForm member functions to tell background worker what to do
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker worker = sender as BackgroundWorker;
// Perform non-GUI initialization - The GUI thread will be responsive in the meantime
// My time consuming operation is just this loop.
//make sure you use worker.ReportProgress() here
for (int i = 1; (i <= 10); i++)
{
if ((worker.CancellationPending == true))
{
e.Cancel = true;
break;
}
else
{
System.Threading.Thread.Sleep(500);
worker.ReportProgress((i * 10));
}
}
SetVisible(false);
MainForm mainForm = new MainForm();
mainForm.ShowDialog();
//instead of
//this.Visible = false;
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
this.progressBar1.Value = e.ProgressPercentage;
}
You might have noticed by now, I am using another member function to hide the splash screen. It is because you are now in another thread and you can't just use this.visible = false;. Here is a link on the matter.
delegate void SetTextCallback(bool visible);
private void SetVisible(bool visible)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetVisible);
this.Invoke(d, new object[] { visible });
}
else
{
this.Visible = visible;
}
}
When I run this sample project it shows the progress bar and then loads the MainForm windows form after hiding the SplashForm.
This way you can put any controls that you might need inside the MainForm constructor. The part you shortened as // Perform GUI initialization afterwards in the UI context should go into MainForm constructor.
Hope this helps.

can't start winForm from a thread

I am trying to start a winForm from a thread, but when i do so, the form show but none of the labels are loaded ( the background where they should be is white ) and the form is frozen.
I've tried it with some other winForms that i know that work just fine and it still doesn't seem to work ? Has anyone encountered this problem ?
I know the question is vague but there isn't really any specific code that I could give to help understand the problem.
That is because the Message Loop runs on UI thread only. And when a control or window is created in any other thread, it cannot access that message loop. And thus, cannot process user input.
To solve this, try creating a window from UI thread and create a thread from that window to do whatever you want to do in different thread.
UI thread is supposed to be one.
Then, I suggest you to open your form calling a method of your original form thread, like in the example below:
(To test it just create an empty form called MainForm and paste this code in it)
public delegate void OpenFormDelegate(string txt);
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
var button1 = new Button();
button1.Text = "Run for 5 secs and open new window";
button1.Dock = DockStyle.Top;
this.Controls.Add(button1);
button1.Click += new EventHandler(button1_Click);
}
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(Run));
t.Start();
}
public void Run()
{
Thread.Sleep(5000); // sleep for 5 seconds
this.BeginInvoke(new OpenFormDelegate(OpenNewForm), "Hello World !");
}
public void OpenNewForm(string text)
{
Form f = new Form();
f.Text = text;
f.Show();
}
}
It is related to thread access, when the new form is created it will not be able to access the UI thread. Use the main thread to create the form and new thread to process the infomation.

Load Data and show a splash screen during that

I got to load some data out of a db4o database which takes 1 or 2 seconds at the startup of my app, the rest has to wait because first of all the data has to be loaded. doing this in an own thread would mean that the rest has to wait for the thread-finishing. I'd like to do a splash screen or something during the data is loaded for what also need an own thread, right? how would you do?
I'm using csharp, .net 3.5 and winforms
Showing a splash screen at startup is easy to do. In your application's Main() method (in Program.cs), put something like this before the Application.Run(...) line:
SplashForm splashy = new SplashForm();
splashy.Show();
Application.Run(new MainForm(splashy));
Modify the code and constructor for your main form so that it looks something like this:
private SplashForm _splashy;
public MainForm(SplashForm splashy)
{
_splashy = splashy;
InitializeComponent();
}
Then at the end of your MainForm's Load event (which presumably contains the database code), put this code:
_splashy.Close();
_splashy.Dispose();
If you choose to do your database access with a separate Thread or BackgroundWorker, then you don't really need a splash screen so much as you need some sort of progress indicator form that appears while the BackgroundWorker is doing its thing. That would be done differently from my answer here.
One way, probably better ways though. Create a new dialog form that will be your progress window/splash screen. Throw a bitmap or whatever on it as the only item. Instantiate the dialog from your main program. Override the Load event for the progress form and from there launch the new thread that will do the background processing work for loading up the data. This way you can just call ShowDialog from your main app.
if you use System.ComponentModel.BackgroundWorker then you can easily wire up events for when the thread completes and automaticaly exit the dialog from that event. Control is returned back to the calling application and you're done.
I've done this sort of thing in an application before and it works fine but I'm sure it's a novice approach. Here's sample code from the Load event in the form that launches the background thread (in my case I'm opening and parsing large files):
private void FileThreadStatusDialog_Load(object sender, EventArgs e)
{
Cursor = Cursors.WaitCursor;
if (m_OpenMode)
{
this.Text = "Opening...";
StatusText.Text = m_FileName;
FileThread = new BackgroundWorker();
FileThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(FileThread_RunWorkerCompleted);
FileThread.DoWork += new DoWorkEventHandler(FileOpenThread_DoWork);
FileThread.WorkerSupportsCancellation = false;
FileThread.RunWorkerAsync();
}
else
{
this.Text = "Saving...";
StatusText.Text = m_FileName;
FileThread = new BackgroundWorker();
FileThread.RunWorkerCompleted += new RunWorkerCompletedEventHandler(FileThread_RunWorkerCompleted);
FileThread.DoWork += new DoWorkEventHandler(FileSaveThread_DoWork);
FileThread.WorkerSupportsCancellation = false;
FileThread.RunWorkerAsync();
}
}
And here's what the work completed method looks like which exist the form:
private void FileThread_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
FileThread = null;
DialogResult = DialogResult.OK;
Close();
}
Here's how I open up the progress dialog from the main dialog:
FileThreadStatusDialog thread = new FileThreadStatusDialog(m_Engine, dlg.FileName, true);
if (thread.ShowDialog(this) == DialogResult.OK)
{
m_Engine = thread.Engine;
FillTree();
}
One might want to force drawing of splashy in MusiGenesis' answer by adding
Application.DoEvents();
immediately after
splashy.Show();

C# winforms startup (Splash) form not hiding

I have a winforms application in which I am using 2 Forms to display all the necessary controls. The first Form is a splash screen in which it tells the user that it it loading etc. So I am using the following code:
Application.Run( new SplashForm() );
Once the application has completed loading I want the SplashForm to hide or me sent to the back and the main from to be show. I am currently using the following:
private void showMainForm()
{
this.Hide();
this.SendToBack();
// Show the GUI
mainForm.Show();
mainForm.BringToFront();
}
What I am seeing is that the MainForm is shown, but the SplashForm is still visible 'on top'. What I am currently doing is clicking on the MainForm to manually bring it to the front. Any ideas on why this is happening?
Probably you just want to close the splash form, and not send it to back.
I run the splash form on a separate thread (this is class SplashForm):
class SplashForm
{
//Delegate for cross thread call to close
private delegate void CloseDelegate();
//The type of form to be displayed as the splash screen.
private static SplashForm splashForm;
static public void ShowSplashScreen()
{
// Make sure it is only launched once.
if (splashForm != null)
return;
Thread thread = new Thread(new ThreadStart(SplashForm.ShowForm));
thread.IsBackground = true;
thread.SetApartmentState(ApartmentState.STA);
thread.Start();
}
static private void ShowForm()
{
splashForm = new SplashForm();
Application.Run(splashForm);
}
static public void CloseForm()
{
splashForm.Invoke(new CloseDelegate(SplashForm.CloseFormInternal));
}
static private void CloseFormInternal()
{
splashForm.Close();
splashForm = null;
}
...
}
and the main program function looks like this:
[STAThread]
static void Main(string[] args)
{
SplashForm.ShowSplashScreen();
MainForm mainForm = new MainForm(); //this takes ages
SplashForm.CloseForm();
Application.Run(mainForm);
}
This is crucial to prevent your splash screen from stealing your focus and pushing your main form to the background after it closes:
protected override bool ShowWithoutActivation {
get { return true; }
}
Add this to you splash form class.
If I have understood correctly, you should just use Application.Run on your main form. So either show your splash first just by using something like:
using(MySplash form = new MySplash())
form.ShowDialog();
And then close it manually in MySplash whenever you want to.
Or show it in your main forms Load event handler and then wait for it to close or whatever until you let the Load method complete. (Possibly setting Visible to false before you show it and back to true afterwards.
I believe that it might be a design flaw in my current design!
I think the best Way to achieve what I need is to have everything controled from the MainForm. So I can use:
Application.Run(new MainForm());
This will then be responsable for showing/updating/hiding the SplashScreen. This way I can have the necessary complex intactions with the rest of the system managed by the MainForm.

Categories

Resources