Reputation: 2495
I have a windows application where my first windows form is Login. After successful login, it has to open "Home" form. I see "Home" form while debugging, but once the code enters into Dispose method in Home.Designer.cs, my application stops.
My Login page code looks like following:
private void loginbtn_Click(object sender, EventArgs e)
{
String username = "admin";
String password = "admin";
String @uname = Unametxtbox.Text;
String @pass = Passtextbox.Text;
if (@uname.Equals(username) && @pass.Equals(password))
{
MessageBox.Show("Login Successful");
Home home = new Home();
home.Show();
this.Close();
}
else
{
MessageBox.Show("Invalid Credentials!");
}
}
My Home.cs page looks like following:
public partial class Home : Form
{
public Home()
{
InitializeComponent();
}
}
And the Home.Designer.cs has following code:
partial class Home
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
System.ComponentModel.ComponentResourceManager resources = new System.ComponentModel.ComponentResourceManager(typeof(Home));
this.label1 = new System.Windows.Forms.Label();
this.pictureBox1 = new System.Windows.Forms.PictureBox();
this.closebtn = new System.Windows.Forms.PictureBox();
this.groupBox1 = new System.Windows.Forms.GroupBox();
this.Storedbtn = new System.Windows.Forms.Button();
this.Soldbtn = new System.Windows.Forms.Button();
this.Transbtn = new System.Windows.Forms.Button();
this.Supbtn = new System.Windows.Forms.Button();
this.Empbtn = new System.Windows.Forms.Button();
this.Custbtn = new System.Windows.Forms.Button();
((System.ComponentModel.ISupportInitialize)(this.pictureBox1)).BeginInit();
((System.ComponentModel.ISupportInitialize)(this.closebtn)).BeginInit();
this.groupBox1.SuspendLayout();
this.SuspendLayout();
}
#endregion
private System.Windows.Forms.Label label1;
private System.Windows.Forms.PictureBox pictureBox1;
private System.Windows.Forms.PictureBox closebtn;
private System.Windows.Forms.GroupBox groupBox1;
private System.Windows.Forms.Button Storedbtn;
private System.Windows.Forms.Button Soldbtn;
private System.Windows.Forms.Button Transbtn;
private System.Windows.Forms.Button Supbtn;
private System.Windows.Forms.Button Empbtn;
private System.Windows.Forms.Button Custbtn;
}
If I comment this.Close(); in loginbtn_Click, I can see the Home windows form, but the Login Windows form doesn't get closed.
What I am missing here, Thanks in advance.
Upvotes: 3
Views: 1224
Reputation: 9133
Your post states that your objective is to show the login form first, requiring user credentials before showing the main form. One way to achieve this is to force the creation of the main window handle while also preventing it from becoming visible by overriding SetVisibleCore
until the user succeeds in logging in. Exit the app if login is cancelled (or if user validation fails of course). With a valid login, the app proceeds with HomeForm as the main application window as it should be.
public partial class HomeForm : Form
{
public HomeForm()
{
InitializeComponent();
// Ordinarily we don't get the handle until
// window is shown. But we want it now.
_ = Handle;
// Call BeginInvoke on the new handle so as not to block the CTor.
BeginInvoke(new Action(()=> execLoginFlow()));
// Ensure final disposal of login form. Failure to properly dispose of window
// handles is the leading cause of the kind of exit hang you describe.
Disposed += (sender, e) => _loginForm.Dispose();
buttonSignOut.Click += (sender, e) => IsLoggedIn = false;
}
private LoginForm _loginForm = new LoginForm();
protected override void SetVisibleCore(bool value) =>
base.SetVisibleCore(value && IsLoggedIn);
bool _isLoggedIn = false;
public bool IsLoggedIn
{
get => _isLoggedIn;
set
{
if (!Equals(_isLoggedIn, value))
{
_isLoggedIn = value;
onIsLoggedInChanged();
}
}
}
private void onIsLoggedInChanged()
{
if (IsLoggedIn)
{
WindowState = FormWindowState.Maximized;
Text = $"Welcome {_loginForm.UserName}";
Visible = true;
}
else execLoginFlow();
}
private void execLoginFlow()
{
Visible = false;
while (!IsLoggedIn)
{
_loginForm.StartPosition = FormStartPosition.CenterScreen;
if (DialogResult.Cancel == _loginForm.ShowDialog(this))
{
switch (MessageBox.Show(
this,
"Invalid Credentials",
"Error",
buttons: MessageBoxButtons.RetryCancel))
{
case DialogResult.Cancel: Application.Exit(); return;
case DialogResult.Retry: break;
}
}
else
{
IsLoggedIn = true;
}
}
}
}
Login form
public partial class LoginForm : Form
{
public LoginForm()
{
InitializeComponent();
StartPosition = FormStartPosition.Manual;
FormBorderStyle = FormBorderStyle.FixedToolWindow;
textBoxUid.Text = "Admin";
textBoxUid.TextChanged += onOnyTextChanged;
textBoxPswd.TextChanged += onOnyTextChanged;
buttonLogin.Enabled = false;
buttonLogin.Click += (sender, e) => DialogResult = DialogResult.OK;
}
private void onOnyTextChanged(object sender, EventArgs e)
{
buttonLogin.Enabled = !(
(textBoxUid.Text.Length == 0) ||
(textBoxPswd.Text.Length == 0)
);
}
public string UserName => textBoxUid.Text;
protected override void OnVisibleChanged(EventArgs e)
{
base.OnVisibleChanged(e);
if (Visible)
{
textBoxPswd.Clear();
textBoxPswd.PlaceholderText = "********";
}
}
}
Upvotes: 0
Reputation: 449
Without digging too much in the weeds, the lifetime of the application is tied to your main window in WinForms. There are certain things that can extend that lifetime such as detached threads, but I won't go into that.
In your code here, the call to this.Close()
effectively terminates the application because as far as the Windows event loop is concerned, once your primary window is gone you've finished with the app.
There is another issue though, even if you weren't calling this.Close()
you're creating a Home object, which lives inside a local variable within the function. As soon as you reach the end of the if
statement, your last reference to the object has gone. When the garbage collector invokes it won't be able to reach your Home object, which triggers it to be destroyed and freed. The object needs to be stored somewhere that will persist after your function call finishes.
I've worked with WinForms for a good while and as far as I'm aware there's no real way to handle "pages" like this. There are other frameworks such as WPF which do this much better. If I'm wrong about WinForms handling this badly perhaps someone will correct me.
One relatively simple approach (with WinForms) is to use an empty Form as the main page for your application, there's a property called IsMdiContainer
which if you set to true
, will allow this form to contain other forms.
In this main window form, I'll add a function to load a new page which accepts the page to load. It closes down the current page and opens the new one up. There's various different ways to do this depending on what information you need to pass between pages. The crude but easy way of passing info would be to only the most keep important information in the main window, then the children can access it but it does couple your classes together more.
You don't have to use Forms or the "Multiple Document Interface" feature, but I like to have the OnLoad
and OnFormClosing
functions available to me. If you don't need these you can just use a UserControl for your pages instead, and leave IsMdiParent
set to its default of false
.
Upvotes: 0