I am working on a C# WinForms application on .NET 8.0 and facing an issue with form transitions.
I have three forms:
Form1
- Initial screen (main form)Form2
- Password screen (modal dialog)Form3
- Final destination form
Expected flow:
- The user clicks a button on
Form1
, which opensForm2
for password entry - After successful authentication,
Form3
should be opened Form1
should not appear again afterForm2
closes
Actual behavior:
Form1
opensForm2
(password screen) successfully- User enters the password and submits
- Before
Form3
appears,Form1
briefly flickers and appears for a second - After that,
Form3
loads correctly - This brief flicker of Form1 is unexpected and causes a bad user experience
Here is how I'm handling form transitions - code in Form1
(opens Form2
for authentication):
private void btnOpenForm2_Click(object sender, EventArgs e)
{
Form2 passwordForm = new Form2();
if (passwordForm.ShowDialog() == DialogResult.OK) // Blocking call
{
Form3 finalForm = new Form3();
finalForm.Show();
this.Hide(); // Hide Form1
}
}
Code in Form2
(password screen):
private void btnSubmit_Click(object sender, EventArgs e)
{
// Assume authentication is successful
this.DialogResult = DialogResult.OK;this.Close();
}
What I've tried
- Hiding
Form1
before openingForm2
- Used
this.Hide();
beforeShowDialog()
.
Still,Form1
briefly appears beforeForm3
.
SettingTopMost = true
forForm3
- no improvement. - Using
BeginInvoke()
to delayForm3
loading - Explicitly setting
Form2
'sOwner
How do I prevent Form1
from appearing again briefly between Form2
closing and Form3
opening? Is there a way to transition smoothly from Form2 → Form3
without Form1
appearing again?
I am working on a C# WinForms application on .NET 8.0 and facing an issue with form transitions.
I have three forms:
Form1
- Initial screen (main form)Form2
- Password screen (modal dialog)Form3
- Final destination form
Expected flow:
- The user clicks a button on
Form1
, which opensForm2
for password entry - After successful authentication,
Form3
should be opened Form1
should not appear again afterForm2
closes
Actual behavior:
Form1
opensForm2
(password screen) successfully- User enters the password and submits
- Before
Form3
appears,Form1
briefly flickers and appears for a second - After that,
Form3
loads correctly - This brief flicker of Form1 is unexpected and causes a bad user experience
Here is how I'm handling form transitions - code in Form1
(opens Form2
for authentication):
private void btnOpenForm2_Click(object sender, EventArgs e)
{
Form2 passwordForm = new Form2();
if (passwordForm.ShowDialog() == DialogResult.OK) // Blocking call
{
Form3 finalForm = new Form3();
finalForm.Show();
this.Hide(); // Hide Form1
}
}
Code in Form2
(password screen):
private void btnSubmit_Click(object sender, EventArgs e)
{
// Assume authentication is successful
this.DialogResult = DialogResult.OK;this.Close();
}
What I've tried
- Hiding
Form1
before openingForm2
- Used
this.Hide();
beforeShowDialog()
.
Still,Form1
briefly appears beforeForm3
.
SettingTopMost = true
forForm3
- no improvement. - Using
BeginInvoke()
to delayForm3
loading - Explicitly setting
Form2
'sOwner
How do I prevent Form1
from appearing again briefly between Form2
closing and Form3
opening? Is there a way to transition smoothly from Form2 → Form3
without Form1
appearing again?
2 Answers
Reset to default 0Make
Form3
the main formstatic class Program { /// <summary> /// The main entry point for the application. /// </summary> [STAThread] static void Main() { Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new Form3()); } }
When the main form launches open the startup form
public partial class Form3 : Form { public Form3() { InitializeComponent(); } protected override void OnLoad(EventArgs e) { base.OnLoad(e); //Open startup form and wait var entry = new Form1(); if (entry.ShowDialog() != DialogResult.OK) { // if password failed close application Close(); } } }
Make the startup form open the login form when a button is pressed and pass the
DialogResult
value through to the main formpartial class Form1 : Form { public Form1() { InitializeComponent(); } private void okButton_Click(object sender, EventArgs e) { //Open password form and passthrough results to main form var dlg = new Form2(); this.DialogResult = dlg.ShowDialog(); this.Close(); } }
In the password form set the
DialogResult
value based on the authentication
The in the end run the program and the initial form will show. If the button is pressed the login form will show. When ok button is pressed in the login form, the main form shows. If the cancel button is pressed in the login form then the entire application will end.
Note that WinForms run on a single thread started with
Application.Run()
. When this form end, the entire application end. This is the reason you needForm3
as the main form.
Your post describes an onboarding flow that takes the user through two preliminary screens before finally showing the main form. The problem is that you're seeing a flicker - briefly showing an unwanted form. And since you have a password protecting the main form contents, it's a fair and reasonable assumption that sensitive information could be revealed if the main form blinks on (even for a instant) before being authorized to do so.
The key to suppressing this is to override SetVisibleCore
and prevent it from setting the base class visibility to true
until your condition has been met.
protected override void SetVisibleCore(bool value)
{
base.SetVisibleCore(value && OnboardingState == OnboardingState.Authorized);
}
Now you can run the MainForm
of the app in the normal way, and it will no longer be even briefly visible while you do your onboarding sequence.
enum OnboardingState{Screen1, Screen2, Authorized, }
public partial class MainWindow : Form
{
public MainWindow()
{
InitializeComponent();
// IMPORTANT: Make sure that Handle is not null.
_ = Handle;
BeginInvoke(() => OnboardingState = OnboardingState.Screen1);
}
protected override void SetVisibleCore(bool value)
{
base.SetVisibleCore(value && OnboardingState == OnboardingState.Authorized);
}
// Respond to changes of the onboarding state
// enum by showing the corresponding window.
OnboardingState OnboardingState
{
get => _onboardingState;
set
{
if (!Equals(_onboardingState, value))
{
_onboardingState = value;
switch (_onboardingState)
{
case OnboardingState.Screen1:
using (var dlg = new OnboardingForm1())
{
if (DialogResult.OK == dlg.ShowDialog(this))
{
OnboardingState = OnboardingState.Screen2;
}
else Close();
}
break;
case OnboardingState.Screen2:
using (var dlg = new OnboardingForm2())
{
if (DialogResult.OK == dlg.ShowDialog(this))
{
OnboardingState = OnboardingState.Authorized;
}
else Close();
}
break;
case OnboardingState.Authorized:
Show();
break;
}
}
}
}
OnboardingState _onboardingState = (OnboardingState)(-1);
}
First Screen
public partial class OnboardingForm1 : Form
{
public OnboardingForm1()
{
InitializeComponent();
buttonLogin.Click += (sender, e) => DialogResult = DialogResult.OK;
}
}
Second Screen
public partial class OnboardingForm2 : Form
{
public OnboardingForm2()
{
InitializeComponent();
Shown += (sender, e) => ActiveControl = null;
textBoxUid.TextChanged += localUpdateVisibility;
textBoxPswd.TextChanged += localUpdateVisibility;
void localUpdateVisibility(object? sender, EventArgs e)
{
textBoxPswd.Visible = !string.IsNullOrWhiteSpace(textBoxUid.Text);
buttonLogin.Visible = textBoxUid.Visible && !string.IsNullOrWhiteSpace(textBoxPswd.Text);
}
buttonLogin.Click += (sender, e) => DialogResult = DialogResult.OK;
}
}
Form1
isn't to be used again then why are you hiding it rather than closing it? I'm guessing because it's the startup form but that's not a good reason. You should edit theMain
method so thatForm3
is displayed there, so that the app will not close. Set a property inForm1
and check that in theMain
method when it closes. Based on that, either let the app exit or openForm3
. – jmcilhinney Commented Feb 17 at 5:00Main
method, then you can open all forms modally one after another, checking results and anizing "loop" to show either form. If you needApplication.Run
, then just put this logic into ApplicationContext constructor. – Sinatr Commented Feb 17 at 9:54