wtorek, 5 lutego 2008

Zamykanie splash screena z innego wątku

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: