Od jakiegoś czasu dodaję do swoich produkcji splash screen wyświetlany w czasie uruchamiania aplikacji. Do tej pory było to statyczne okienko z jakimś miłym do oka obrazkiem oraz komunikatem proszącym użytkownika o chwilę cierpliwości. Kod wyglądał mniej więcej tak:
static void Main()
{
SplashForm startForm = new SplashForm();
startForm.Show();
Application.DoEvents();
MainForm.Run(startForm);
}
Natomiast w kodzie głównego formularza aplikacji splash form był zamykany:
public partial class MainForm : System.Windows.Forms.Form
{
public MainForm()
{
InitializeComponent();
}
private IDisposable splashObject;
public IDisposable SplashObject
{
set { this.splashObject = value; }
}
public static void Run(IDisposable splashObject)
{
MainForm frm = new MainForm();
frm.SplashObject = splashObject;
Application.Run(frm);
}
private void MainForm_Shown(object sender, EventArgs e)
{
if (splashObject != null)
{
splashObject.Dispose();
}
}
Jak widać otwarty formularz startowy był przekazywany do głównego formularza aplikacji, a dopiero po załadowaniu tegoż był niszczony.
Przy okazji realizowania ostatniej aplikacji zapragnąłem zdynamizowania splash screena. Po paru eksperymentach uzyskałem satysfakcjonujące mnie rozwiązanie, w którym wykorzystałem oddzielny wątek w którym tworzony jest formularz startowy wyposażony w oddzielną pętlę komunikatów:
private static void ShowSplashScreenInOtherThread()
{
ThreadStart starter = delegate
{
SplashForm startForm = new SplashForm();
Application.Run(startForm);
};
new Thread(starter).Start();
}
Teraz pozostało tylko rozwiązać problem jak zamknąć niepotrzebny już formularz startowy z poziomu głównego formularza aplikacji? Próba przekazania do głównego formularza aplikacji referencji do wątku, w którym działa splash screen, a potem użycie Abort - skuteczne ale kilkusekundowa zwłoka.
Rozwiązanie:
Utworzyłem pomocniczy obiekt DisposeTrigger, który implementuje standardowy interfejs IDisposable w ten sposób, że przy Dispose generuje zdarzenie Disposing:
public class DisposeTrigger : IDisposable
{
public event EventHandler Disposing;
protected virtual void OnDisposing(EventArgs e)
{
if (Disposing != null)
{
Disposing(this, e);
}
}
#region IDisposable Members
public void Dispose()
{
OnDisposing(EventArgs.Empty);
}
#endregion
}
Obiekt ten stał się łącznikiem między wątkiem splash screena, a głównym wątkiem aplikacji. W kodzie splash screena dodałem obsługę zdarzenia Disposing:
public partial class SplashForm : Form
{
public SplashForm(DisposeTrigger trigger)
{
InitializeComponent();
trigger.Disposing += new EventHandler(trigger_Disposing);
}
void trigger_Disposing(object sender, EventArgs e)
{
CloseSplashScreen();
}
private delegate void CloseHandler();
private void CloseSplashScreen()
{
if (this.InvokeRequired)
{
CloseHandler handler = CloseSplashScreen;
this.Invoke(handler);
}
else
{
Application.ExitThread();
}
}
}
Natomiast w metodzie Main tworzony jest obiekt DisposeTrigger, przekazywany zarówno do konstruktora splash screena jak i do głównego formularza aplikacji:
static void Main()
{
DisposeTrigger splashDisposing = new DisposeTrigger();
ShowSplashScreenInOtherThread(splashDisposing);
MainForm.Run(splashDisposing);
}
private static void ShowSplashScreenInOtherThread(
DisposeTrigger trigger)
{
ThreadStart starter = delegate
{
SplashForm startForm = new SplashForm(trigger);
Application.Run(startForm);
};
new Thread(starter).Start();
}
Jak widać wcześniej napisane aplikacje korzystające z mojego frameworka będą dalej działały bez konieczności jakiejkolwiek modyfikacji.

0 komentarze:
Prześlij komentarz