.... es ist aber ziemlich mühselig...
Bin mit Andreas Beispiel angefangen, und landete gleich auf den Bauch:
Ich konnte das Application.Run nicht einbauen, da es schon an anderer Stelle gestartet war, also erzeugte der Thread das Fenster und stoppte am Ende der Init-Funktion. Dadurch zeichnete sich das Fenster nicht mehr neu und ließ sich auch nicht mehr ansprechen.
Also hielt ich den Thread in einer while-Schleife am Leben.
Nach dem Umbau sah der Code dann so aus:
private void InternalShow()
{
lblDescription.Text = Message;
Visible = true;
while (Visible)
{
Refresh();
Thread.Sleep(50);
}
}
/// <summary>
/// Zeigt das Wartefenster mit einer Meldung an
/// </summary>
/// <param name="Text">Der Text, der angezeigt werden soll.</param>
public static void Show( string Text )
{
if (_Instance == null)
{
_Instance = new StatusForm();
_Instance.Message = Text;
new Thread(new ThreadStart(_Instance.InternalShow)).Start();
}
}
public new static void Hide()
{
MethodInvoker UIDelegate = new delegate
{
Close();
}
Invoke( UIDelegate);
}
Die Eingangsroutine ist die statische Methode Show.
Die prüft zunächst, ob schon eine Instanz dieses Fensters erzeugt ist ( wollte sichergehen, das es immer nur ein Fenster gibt ), speichert den zukünftigen Text in der Variable Message. Den Text konnte ich erst innerhalb des Threads zuweisen, da sonst möglicherweise schon ein Fensterhandle im falschen Thread erzeugt wird. Danach wird der Thread gestartet.
Die Threadmethode macht das Fenster sichtbar, weist den Text zu und kurvt in einer Endlosschleife, bis von außen die Methode Hide kommt und das Fenster schließt.
Das ganze dann kompiliert und ausgeführt, das Fenster wurde sichtbar und eine Animation ( die hier nicht weiter von belang ist ) liefen prima. Also Fenster wieder schließen und .... nichts tat sich mehr, das Programm blieb hängen.
Ich fand heraus, das es beim Invoke hängenblieb. Genau weiß ich nicht warum, ich vermute das der Thread gerade schläft, wenn ich das Invoke aufrufe.
Also kam ich auf eine nicht sonderlich elegante Methode: Ich baute Hilfsvariablen wie ShouldClose und ShouldVisible ein, die dann Problemlos aus anderen Threads gesetzt werden konnten und dann in der Threadschleife behandelt wurden. Die Methode Hide sah dann so aus:
/// <summary>
/// Schließt das Statusfenster wieder
/// </summary>
public static new void Hide()
{
if( _Instance!=null )
_Instance._ShouldClose = true;
}
Die Show Routine blieb wie sie war, nur meine Internalshow änderte sich drastisch
private void InternalShow()
{
lblDescription.Text = Message;
Visible = true;
while (_ShouldClose==false)
{
if (lblDescription.Text != Message)
lblDescription.Text = Message;
if (_ShouldTopMost != TopMost)
TopMost = _ShouldTopMost;
if (_ShouldVisible != Visible)
Visible = _ShouldVisible;
Refresh();
Thread.Sleep(50);
}
_Instance = null;
Close();
Dispose();
}
Bei jedem Schleifendurchlauf wurde geprüft, ob sich der Text verändert hatte. TopMost gibt an, das das Fenster immer in Vordergrund bleibt und das ShouldVisible gibt an, ob das Fenster vorübergebend unsichtbar gemacht werden soll. Warum ShouldVisible und TopMost ? Beim Erzeugen des Fensters wird dem Wait-Fenster kein Parent mitgegeben, da es für sich alleine laufen muss und nichts mit der UI-Thread zu tun haben durfte.
Leider hat die Sache nur einen Schönheitsfehler: Da die Fenster nichts miteinander zu tun haben, überdecken sie sich gegenseitig, je nachdem wer den Focus bekommt. Ich konnte das WaitFenster zwar TopMost machen, dadurch war es aber immer sichtbar, auch wenn ein ganz anderes Programm im Vordergrund ist.
Kurzum, so völlig autark konnte das Fenster nun auch wieder nicht laufen, aber den Parent konnte ich dem Fenster ja auch nicht zuweisen.
Die Lösung war dann, das ich die HauptMessage-Schleife des Elternfenster auf relevante Ereignisse abhöre. Das sieht dann so aus
// <summary>
/// Zeigt das Wartefenster mit einer Meldung an
/// </summary>
/// <param name="Text">Der Text, der angezeigt werden soll.</param>
public static void Show( Form Parent, string Text )
{
if (_Instance == null)
{
_Instance = new StatusForm();
_Instance.Message = Text;
_Instance.SetParent(Parent);
new Thread(new ThreadStart(_Instance.InternalShow)).Start();
}
}
Die Methode Show unterscheidet sich zu vorher nur in der zusätzlichen Methode SetParent
private void SetParent(Form NewParent)
{
_ParentWndProc = new WndProcCallBack(Parent_WndProc);
AssignWndProc( NewParent);
_Parent = NewParent;
}
protected virtual void AssignWndProc( Form Parent )
{
_OrgWndProc = Win32.SetWindowLong(Parent.Handle, -4, _ParentWndProc);
}
protected virtual void ReleaseWndProc(Form Parent)
{
Win32.SetWindowLong(Parent.Handle, -4, _OrgWndProc);
}
Die Setparent prüft ruft AssignWndProc auf und speichert den Parent Objektverweis in eine Variable.
AssignWndProc besteht nur aus einer einzigen Zeile, der API Methode SetWindowLong. Diese Methode leitet alle Nachrichten vom Elternfenster in die Methode Parent_WndProc um.
protected IntPtr Parent_WndProc(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam)
{
IntPtr Result = Win32.CallWindowProc(_OrgWndProc, hWnd, Msg, wParam, lParam);
switch (Msg)
{
case Win32.WM_NCACTIVATE:
_ShouldTopMost = (int)wParam == 1 ? true : false;
break;
case Win32.WM_CLOSE:
_ShouldClose = true;
break;
case Win32.WM_WINDOWPOSCHANGED:
Win32.WINDOWPOS wp = new Win32.WINDOWPOS();
wp = ( Win32.WINDOWPOS )Marshal.PtrToStructure(lParam, typeof(Win32.WINDOWPOS));
_ShouldVisible = (wp.x != -32000); // Hauptfenster minimiert
if (_ShouldVisible)
_ShouldTopMost = true;
break;
}
return Result;
}
Diese Funktion horcht nun, ob das Fenster deaktiviert wird ( WM_NCACTIVATE ) und schaltet dann das Topmost ein oder aus.
Wird das Elternfenster geschlossen ( WM_CLOSE ), wird das Flag zum Schließen des Waitfensters gesetzt. Beim Minimieren ( WM_WINDOWPOSCHANGED )
muss natürlich auch das Waitfenster unsichtbar werden ( ShouldVisible )
So, das ganze ist ein bißchen länger geworden. Ich habe das deshalb mal so ausführlich geschrieben, da ich mit dem Design gar nicht zufrieden bin, mir aber auch nichts besseres einfiel. Aber vielleicht hat ja der ein oder andere noch eine Idee...
Gruß
Stefan