I'm doing a small project to store movies. I'm using three windows form, a intro form, a main form and a new movie form. I also have a class that I call MovieManager that is the heart of the application. My problem is that I'm not sure how I should handle this windows.
Let's say I want the application to start with the intro form and when the user click on the OK-button the main form should appear. What is the best way to do this? Should I in the Program.cs create an object of the MovieManager class that show and hide the different windows form or should I in the Program.cs just start by showing the intro form?
You can simply do all staff in Program.cs when starting application. Show your IntroForm as dialog. If user clicks OK, then start main application form, otherwise close application.
static void Main()
{
IntroForm introForm = new IntroForm();
if (introForm.ShowDialog() != DialogResult.OK)
{
Application.Exit();
return;
}
Application.Run(new MainForm());
}
If you need single MovieManager instance for all these forms, then you can create it in Main method and pass same instance to IntroForm and MainForm:
MovieManager movieManager = new MovieManager();
IntroForm introForm = new IntroForm(movieManager);
if (introForm.ShowDialog() != DialogResult.OK)
{
Application.Exit();
return;
}
Application.Run(new MainForm(movieManager));
Also you can implement MovieManager as Singleton, which will be accessible everywhere via static property MovieManager.Instance.
What you call intro form, I should call a splash screen. In the program.cs I should just pop-up the splash screen (with a logo, title and info about the application, version number, etc.). The splash screen is shown for a certain amount of time (use a timer for this, or Thread.Sleep is alos possible altough a bit heavy).
When the splash screen closes show the MainForm is shown a from there you can instantiate a MovieManager or use a static MovieManager (it depends on its use). From the mainform you can then just instantiate and show new movie form(s).
We use a piece of code something like this:
static void Main(string[] args)
{
try
{
SplashScreen.ShowSplashScreen();
Application.DoEvents();
SplashScreen.WaitForVisibility(.5);
bool starting = true;
while (starting)
{
try
{
SplashScreen.SetStatus("Initialize mainform...");
starting = false;
Application.Run(new MainForm());
}
catch (Exception ex)
{
if (starting)
starting = XtraMessageBox.Show(ex.Message, "Fout bij initialiseren", MessageBoxButtons.RetryCancel, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1) == DialogResult.Retry;
else
throw (ex);
}
}
}
catch (Exception ex)
{
if (ex is object)
XtraMessageBox.Show(ex.Message, "Algemene fout", MessageBoxButtons.OK, MessageBoxIcon.Error, MessageBoxDefaultButton.Button1);
}
}
And code in the splashscreen (excerpt) looks like this:
if (_splashForm == null)
{
ThreadPool.QueueUserWorkItem(RunSplashForm, null);
while (_splashForm == null || _splashForm.IsHandleCreated == false)
{
System.Threading.Thread.Sleep(50);
}
}
Maybe these links will also provide you some usefull information:
http://www.reflectionit.nl/Articles/Splash.aspx
And we used this as the basis for our own code:
http://www.codeproject.com/Articles/5454/A-Pretty-Good-Splash-Screen-in-C
There are different ways to show a new form. you can use MdiWinForm first you must change IsMdiContainer property to true then use these codes in the MainForm :
Form2 f2;
private void button1_Click(object sender, EventArgs e)
{
if (f2 == null) {
f2 = new Form2();
f2.MdiParent = this;
f2.FormClosed += delegate { f2 = null; };
f2.Show();
}
else {
f2.Activate();
}
}
Related
My app starts with a LoginForm where the user will enter their credentials. I need to close the LoginForm and display the DashboardForm. I'm able to do this:
public static bool OpenDashboardFormOnClose { get; set; }
public static bool OpenLoginFormOnClose { get; set; }
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
OpenDashboardFormOnClose = false;
OpenLoginFormOnClose = false;
Application.Run(new LoginForm());
if (OpenDashboardFormOnClose)
{
Application.Run(new DashboardForm());
}
if (OpenLoginFormOnClose)
{
Application.Run(new LoginForm());
}
}
The LoginForm login method:
Program.OpenDashboardFormOnClose = true;
this.Close();
The DashboardForm logout method:
Program.OpenLoginFormOnClose = true;
this.Close();
This works great when the user just logs in and then logs out.
The issue is that when a user has just logged out, and they try to log in again, the app closes instead of displaying the DashboardForm.
How do I achieve this? The user should be able to login and out as many times as they want...
Any help is appreciated.
I don't really recommend to use this approach of using many Application.Run one after another to show consecutive forms. That method starts the main message loop of the program, and generally is run once at program start and returns only once, at program exit.
Instead, make the forms open each other as needed, and leave the main message loop alone. Let's modify it to return to its almost default state:
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
//Open the initial formm I'm assuming it's the login
LoginForm loginForm = new LoginForm();
loginForm.Show();
//Start the main message loop for the whole lifetime of the program
Application.Run();
}
Now, on the login form, when it's time to dismiss it and launch the main form, it does it itself instead of delegating:
//Close itself
this.Close();
//Launch the next form
DashboardForm dashboardForm = new DashboardForm();
dashboardForm.Show();
Similarly, on the dashboard when logging out you'll need to relaunch the login:
//Close itself
this.Close();
//Show the login again
LoginForm loginForm = new LoginForm();
loginForm.Show();
While all this work, this generates a problem not present before. As the main Application.Run isn't tied to a form, it won't stop running once the initial login exists, but only when explictly instructed to do so, with the net result of the process hanging after the window being closed.
To fix it, when it's time to close the application for good (not launch another form), call Application.Exit();, maybe in the Form.Closing event.
Just use a while loop to display the login and dashboard forms repeatedly. You won't need the "OpenLoginFormOnClose" flag anymore, but will need one to track whether the application should exit instead:
static class Program
{
public static bool OpenDashboardFormOnClose;
public static bool ExitApplication = false;
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
while(!ExitApplication)
{
OpenDashboardFormOnClose = false;
Application.Run(new LoginForm());
if (OpenDashboardFormOnClose)
{
Application.Run(new DashboardForm());
}
}
}
}
Then add an "exit" button to your form(s) that does:
private void btnExit_Click(object sender, EventArgs e)
{
Program.ExitApplication = true;
this.Close();
}
So now the while loop will drop out and your program will end.
this.Close or Close just closes the current form, but if it's the main form it
closes all of them.
Application.Exit() Closes all the windows from the same program.
There is no possible way to just close the main form. if you do it all the windows
opened from it will close. You can only hide the form.
To hide the form, check the code below ▼
private void Example_Click(object sender, EventArgs, e)
{
this.Hide();
this.ShowInTaskbar = false;
}
if you're on the dashboard form, and want to close all remaining windows with/or from it use ▼
Application.Exit();
Another even simpler way many people don't use to open a form ▼
new DashBoardForm().Show();
I am having an odd problem with protecting a section of code. My application is a tray app. I create a NotifyIcon inside my class (ApplicationContext). I have assigned a balloon click handler and a double click handler to the NotifyIcon object. there is also a context menu but I am not showing all code. Only important pieces.
public class SysTrayApplicationContext: ApplicationContext
{
private NotifyIcon notifyIcon;
private MainForm afDashBoardForm;
public SysTrayApplicationContext()
{
this.notifyIcon = new NotifyIcon();
this.notifyIcon.BalloonTipClicked += notifyIcon_BalloonTipClicked;
this.notifyIcon.MouseDoubleClick += notifyIcon_MouseDoubleClick;
// ... more code
}
Both handlers launch or create/show my form:
private void notifyIcon_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (e.Button == MouseButtons.Left)
{
openDashboard();
}
}
private void notifyIcon_BalloonTipClicked(object sender, EventArgs e)
{
openDashboard();
}
private void openDashboard()
{
if (dashBoardForm != null)
{
log.Debug("Dashboard form created already, so Activate it");
dashBoardForm.Activate();
}
else
{
log.Debug("Dashboard form does not exist, create it");
dashBoardForm = new MainForm();
dashBoardForm.Show();
}
}
There is a problem with the above code. Maybe more than 1. Issue: it is possible to display 2 dashboard forms which is not what I want. If user double clicks on tray icon while balloon message is displaying causes a race condition in openDashboard. I can reproduce this easily. So I added a lock around the code in openDashboard code and, to my surprise, that did NOT prevent 2 dashboard forms from displaying. I should not be able to create 2 MainForms. Where am I going wrong here?
here is the updated code with lock statement:
private void openDashboard()
{
lock (dashBoardFormlocker)
{
if (dashBoardForm != null)
{
log.Debug("Dashboard form created already, so Activate it");
dashBoardForm.Activate();
}
else
{
log.Debug("Dashboard form does not exist, create it");
dashBoardForm = new MainForm();
dashBoardForm.Show();
}
}
}
Note: lock object was added to the class and initialized in constructor.
private object dashBoardFormlocker;
UPDATE: Showing more code. this is how code gets started :
static void Main()
{
if (SingleInstance.Start())
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
XmlConfigurator.Configure();
// For a system tray application we don't want to create
// a form, we instead create a new ApplicationContext. The Run method takes
Application.Run(new SysTrayApplicationContext());
SingleInstance.Stop();
SingleInstance.Dispose();
}
}
}
UPDATE 2: Provide more code for clarity
public partial class MainForm : Form
{
public MainForm()
{
log.Trace("MainForm constructor...");
InitializeComponent();
// ... code not shown
this.label_OSVersion.Text = getOSFriendlyName();
// .. more code
}
private string getOSFriendlyName()
{
try
{
string result = string.Empty;
var mgmtObj = (from x in new ManagementObjectSearcher("SELECT Caption FROM Win32_OperatingSystem").Get().OfType<ManagementObject>()
select x.GetPropertyValue("Caption")).FirstOrDefault();
result = mgmtObj != null ? mgmtObj.ToString() : string.Empty;
OperatingSystem os = Environment.OSVersion;
String sp = os.ServicePack ?? string.Empty;
return !string.IsNullOrWhiteSpace(result) ? result + sp : "Unknown";
}
catch (System.Exception ex)
{
log.Error("Error trying to get the OS version", ex);
return "Unknown";
}
}
}
The main UI thread must always pump a message loop to support communication from COM components.
So when you do a blocking operation from the UI thread like locking or joining a thread, (EDIT: edited based on Peter Duniho's fix) the UI thread will enter an 'alertable' state, allowing COM to dispatch certain type of messages, which in turn can cause re-entrancy issues like in your scenario.
Look at the answer to this question (Why did entering a lock on a UI thread trigger an OnPaint event?) for a much more accurate explanation.
Looking at the source code of ManagementObjectSearcher.Get there is a lock (inside Initialize), and since you call it from the constructor of your form, it may lead to the second event triggering while the form's constructor has not finished. The assignment to the dashBoardFormlocker variable only happens after the constructor finishes, so that would explain why it was null on the second entry.
The moral of the story is never do blocking operations on the UI thread.
Without a good, minimal, complete code example that reliably reproduces the problem, it's impossible to know for sure what the problem is. But the guess by answerer tzachs seems reasonable. If so, you can fix your problem by changing your method to look like this:
private bool _dashboardOpen;
private void openDashboard()
{
if (_dashboardOpen)
{
if (dashBoardForm != null)
{
log.Debug("Dashboard form created already, so Activate it");
dashBoardForm.Activate();
}
}
else
{
log.Debug("Dashboard form does not exist, create it");
_dashboardOpen = true;
dashBoardForm = new MainForm();
dashBoardForm.Show();
}
}
In that way, any re-entrant attempt to open the window will be detected. Note that you still need the check for null before actually activating; you can't activate a window that hasn't actually finished being created yet. The subsequent call to Show() will take care of activation anyway, so ignoring the activation in the re-entrant case shouldn't matter.
I have a list of report names displayed as Tree hierarchy in ReportViewer control. When user clicks on a report name, an input form loads, user enters some values and presses OK. At this point, Splash screen should load while the backend process is happening (connecting to DB, retrieving values etc). Once the report is loaded in Reportviewer editor, the splashscreen should close.
So far, I am able to display the splash screen however it gets stuck at that point, the actual report does not load and the splash screen stays on forever.
Is it possible to use splashscreen in the middle of application, not at app launch? If so, how do I continue with loading report?
static class Program
{
[STAThread]
static void Main(string[] args)
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new SnapPlusReports());
//new SplashScreenApp().Run(args);
}
}
public class SplashScreenApp : WindowsFormsApplicationBase
{
private static SplashScreenApp _application;
public static void Run(Form form)
{
_application = new SplashScreenApp { MainForm = form };
_application.Run(Environment.GetCommandLineArgs());
}
protected override void OnCreateSplashScreen()
{
this.SplashScreen = new ShowProgress();
base.OnCreateSplashScreen();
}
}
I have done this before by making a new form at run time dynamically with code. Make sure you set all the options up, especially FormBorderStyle to none, or something like that so the user can't close it. Then simply manipulate labels that appear on that form, and eventually close it once your process is complete.
This way you don't have to worry about threading and a nice side effect is that the initial form won't be clickable.
For example I have an about form that pops up during run time (granted I don't change anything on it but the idea is there:
AboutForm aboutForm = new AboutForm();
aboutForm.StartPosition = FormStartPosition.CenterParent;
Label lblAbout = new Label();
Version applicationVersion = System.Reflection.Assembly.GetExecutingAssembly().GetName().Version;
lblAbout.Text = applicationVersion.ToString();
lblAbout.Location = new Point(145,104);
aboutForm.Controls.Add(lblAbout);
aboutForm.ShowDialog();
This shows the current programs version number, etc. There are other labels that already exist on the form (I created it visually first and then called an instance of it).
Hope this helps!
...Catch other instances and gracefully exit if you need only one copy of your app in memory at a given time
static void Main()
{
Application.EnableVisualStyles();
bool exclusive;
System.Threading.Mutex appMutex = new System.Threading.Mutex(true, "MY_APP", out exclusive);
if (!exclusive)
{
MessageBox.Show("Another instance of xxxx xxxBuilder is already running.","MY_APP",
MessageBoxButtons.OK,
MessageBoxIcon.Exclamation );
return;
}
Application.SetCompatibleTextRenderingDefault(false);
xxxWindowsApplication.InitializeApplication();
Application.Run(new frmMenuBuilderMain());
GC.KeepAlive(appMutex);
}
In the main form load you could do something like:
private void frmMenuBuilderMain_Load(object sender, EventArgs e)
{
//Show Splash with timeout Here--
if(!SystemLogin.PerformLogin())
{
this.Close();
return;
}
tmrLoad.Enabled = true;
}
I get this error:
Starting a second message loop on a single thread is not a valid operation. Use Form.ShowDialog instead.
When trying to exit my app after disposing a login form. To clarify, this is what I'm doing in my form:
public frmMainMDI()
{
InitializeComponent();
frmLogin frmLogin_ = new frmLogin(); //create new login form
frmLogin_.ShowDialog(); //show i
if (frmLogin_.DialogResult == DialogResult.Cancel) //if user pressed cancel
{
frmLogin_.Dispose(); //dispose login form
Application.Exit(); //Exit application. If I used this line, it throws the error stated above in Program.cs
//this.Dispose(); //If I try to use this one instead, it throws an 'already disposed' error
//this.Close(); //same error as .dispose
}
else
{
intCurrentLoggedInStaffID = frmLogin_.intStaffID;
}
}
And this is in my program.cs:
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new frmMainMDI()); //this is the line that bugs out
}
I don't quite get it. It almost looks like the app is trying to restart itself when I try to .Exit it. Am I missing something pretty fundamental here? (I'm guessing the answer is 'yes')
I can't quite explain why you are getting this error, but I can suggest a better way to do it. If I understand your intentions correctly, you want to show a login form before the main MDI window is shown and exit your application, if the user pressed cancel in the login form.
Your program.cs:
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
LoginForm loginForm = new LoginForm();
if (loginForm.ShowDialog() != DialogResult.Cancel)
{
MainMdiForm mainMdiForm = new MainMdiForm();
mainMdiForm.intCurrentLoggedInStaffID = loginForm.intStaffID;
loginForm.Dispose();
loginForm = null;
Application.Run(mainMdiForm);
}
}
Your constructor in the MainMdiForm should only contain the call to InitializeComponents.
You're attempting to exit the application (which means shut down the main message loop) in the middle of a constructor (which is bad in general) which is being run in the method which is supposed to be starting up that message loop in the first place. So you have contradictory operations going here.
What I would suggest is that you move your login form/dialog code into your program.cs/Main() function. So something like this:
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
using(frmLogin frmLogin_ = new frmLogin()) { //create new login form
frmLogin_.ShowDialog(); //show i
if (frmLogin_.DialogResult == DialogResult.Cancel) //if user pressed cancel
{
return; // This exits your application
}
}
Application.Run(new frmMainMDI()); //this is the line that bugs out
}
I have some trouble showing a disabled Form in non-modal state. Here is the example code:
public partial class Form1 : Form
{
// ....
private void button1_Click(object sender, EventArgs e)
{
try
{
Form2 form = new Form2();
form.Enabled = false;
form.Show(); // works, but form has no owner
// form.Show(this); // gives an System.InvalidOperationException
// ...
// ... my program here shows a message box, ask user for something
// ... while 'form' is shown in the background
form.Close();
}
catch (Exception ex)
{
MessageBox.Show(ex.Message);
}
}
}
Any idea why Show() (without parameter) works, but Show(this) gives an exception? In my scenario, form must know its owner to be shown correctly, so I can do the following:
form.Enabled = false;
form.Owner=this;
form.Show();
but is this really a good solution?
EDIT: Thanks for the quick answers. Seems that we really found a bug in the framework here. In spite of your suggestions, I think I will keep with my solution, since disabling the form after the 'Show' gives an ugly visible effect to the user.
It's a classic cut-and-paste bug. Looks like they copied the code from ShowDialog(), it is indeed invalid to show a disabled form as a dialog. The user would be stuck and can't do anything anymore. But they forgot to remove the test in the Show() method. Just disable it after the Show() call.
From Microsoft's reference source:
public void Show(IWin32Window owner)
{
if (owner == this)
{
throw new InvalidOperationException(SR.GetString("OwnsSelfOrOwner", new object[] { "Show" }));
}
if (base.Visible)
{
throw new InvalidOperationException(SR.GetString("ShowDialogOnVisible", new object[] { "Show" }));
}
// Here!!!
if (!base.Enabled)
{
throw new InvalidOperationException(SR.GetString("ShowDialogOnDisabled", new object[] { "Show" }));
}
if (!this.TopLevel)
{
throw new InvalidOperationException(SR.GetString("ShowDialogOnNonTopLevel", new object[] { "Show" }));
}
if (!SystemInformation.UserInteractive)
{
throw new InvalidOperationException(SR.GetString("CantShowModalOnNonInteractive"));
}
if (((owner != null) && ((((int) UnsafeNativeMethods.GetWindowLong(new HandleRef(owner, Control.GetSafeHandle(owner)), -20)) & 8) == 0)) && (owner is Control))
{
owner = ((Control) owner).TopLevelControlInternal;
}
By the way, there is a MS Connect bug declared.
That or call Show(this) and then disable it are the only two ways that I can think of.