web-dev-qa-db-ger.com

GUI-Aktualisierung vom UI-Thread erzwingen

Wie erzwinge ich in WinForms ein sofortiges UI-Update vom UI-Thread? 

Was ich mache ist ungefähr:

label.Text = "Please Wait..."
try 
{
    SomewhatLongRunningOperation(); 
}
catch(Exception e)
{
    label.Text = "Error: " + e.Message;
    return;
}
label.Text = "Success!";

Der Beschriftungstext wird vor der Operation nicht auf "Bitte warten ..." gesetzt.

Ich habe das mit einem anderen Thread für die Operation gelöst, aber es wird haarig und ich möchte den Code vereinfachen.

67
dbkk

Zuerst wunderte ich mich, warum das OP nicht bereits eine der Antworten als Antwort markiert hatte, aber nachdem ich es selbst ausprobiert hatte und immer noch nicht funktionierte, grub ich ein wenig tiefer und fand heraus, dass es viel mehr zu diesem Thema gibt, als dass ich zuerst hätte soll. 

Ein besseres Verständnis kann durch Lesen einer ähnlichen Frage erhalten werden: Warum wird Update/Refresh während des Prozesses nicht gesteuert?

Zu guter Letzt konnte ich mein Label aktualisieren lassen, indem ich Folgendes tat:

private void SetStatus(string status) 
{
    lblStatus.Text = status;
    lblStatus.Invalidate();
    lblStatus.Update();
    lblStatus.Refresh();
    Application.DoEvents();
}

Aber nach meinem Verständnis ist dies alles andere als ein eleganter und korrekter Ansatz. Es ist ein Hack, der je nach Belegung des Threads möglicherweise funktioniert oder nicht.

90
Jagd

Rufen Sie label.Invalidate und dann label.Update() auf. Normalerweise wird das Update erst ausgeführt, wenn Sie die aktuelle Funktion beenden. Das Aufrufen von Update erzwingt jedoch die Aktualisierung an dieser bestimmten Stelle im Code . Von MSDN :

Die Invalidate-Methode legt fest, was gemalt oder neu gestrichen wird. Die Aktualisierungsmethode bestimmt, wann das Malen oder Neulackieren erfolgt. Wenn Sie die Methoden Invalidate und Update zusammen verwenden, anstatt Refresh aufzurufen, hängt es von der von Ihnen verwendeten Überladung von Invalidate ab, welche Farben aktualisiert werden. Die Update-Methode zwingt das Steuerelement lediglich dazu, sofort gezeichnet zu werden, aber die Invalidate-Methode legt fest, was gemalt wird, wenn Sie die Update-Methode aufrufen.

14
Dror Helper

Rufen Sie Application.DoEvents() auf, nachdem Sie das Label festgelegt haben. Sie sollten jedoch stattdessen die gesamte Arbeit in einem separaten Thread erledigen, damit der Benutzer das Fenster schließen kann.

13
Scoregraphic

Ich bin gerade über das gleiche Problem gestolpert und habe einige interessante Informationen gefunden. Ich wollte meine zwei Cents hier eintragen und hinzufügen. 

Wie andere bereits erwähnt haben, sollten zunächst lange Threads von einem Thread ausgeführt werden, der ein Hintergrundarbeiter, ein expliziter Thread, ein Thread aus dem Threadpool oder (seit .NET 4.0) eine Aufgabe sein kann: Stackoverflow 570537: Update-Label-While-Verarbeitung in Windows-Formularen , sodass die Benutzeroberfläche weiterhin reagiert. 

Für kurze Aufgaben besteht jedoch kein Grund zum Einfädeln, obwohl es natürlich nicht weh tut. 

Ich habe eine Winform mit einem Button und einem Label erstellt, um dieses Problem zu analysieren:

System::Void button1_Click(System::Object^  sender, System::EventArgs^  e)
{
  label1->Text = "Start 1";
  label1->Update();
  System::Threading::Thread::Sleep(5000); // do other work
}

Meine Analyse bestand darin, den Code zu überschreiten (mit F10) und zu sehen, was passiert ist. Und nachdem ich diesen Artikel gelesen habe Multithreading in WinForms , habe ich etwas Interessantes gefunden. Der Artikel sagt am Ende der ersten Seite, dass der UI-Thread die Benutzeroberfläche nicht neu malen kann, bis die aktuell ausgeführte Funktion beendet ist und das Fenster von Windows als "Nicht-Antworten" markiert wird. Mir ist auch aufgefallen, dass auf meiner Testanwendung von oben zwar durchlaufen wird, jedoch nur in bestimmten Fällen. 

(Für den folgenden Test ist es wichtig, dass Visual Studio nicht auf Vollbild eingestellt ist. Sie müssen in der Lage sein, das kleine Anwendungsfenster gleichzeitig daneben zu sehen. Sie müssen nicht zwischen dem Visual Studio-Fenster und dem Debugging wechseln Anwendungsfenster, um zu sehen, was passiert. Starten Sie die Anwendung, setzen Sie einen Haltepunkt auf label1->Text ..., legen Sie das Anwendungsfenster neben das VS-Fenster und platzieren Sie den Mauszeiger über dem VS-Fenster.) 

  1. Wenn ich nach dem Start der App einmal auf VS klicke (um die Focues dorthin zu setzen und die Schritte zu aktivieren) und durchgehen, OHNE die Maus zu bewegen, wird der neue Text gesetzt und das Label in der Funktion update () aktualisiert. Dies bedeutet, dass die Benutzeroberfläche offensichtlich neu lackiert wird.

  2. Wenn ich die erste Zeile übersteige, dann mit der Maus viel herum fahre und irgendwo klicke, dann schritt weiter, der neue Text ist wahrscheinlich gesetzt und die update () - Funktion wird aufgerufen, aber die Benutzeroberfläche wird nicht aktualisiert und der alte Text nicht neu bleibt dort, bis die Funktion button1_click () beendet ist. Anstelle des Neulackierens wird das Fenster als "nicht ansprechend" markiert! Es hilft auch nicht, this->Update(); hinzuzufügen, um das gesamte Formular zu aktualisieren. 

  3. Durch das Hinzufügen von Application::DoEvents(); erhält die Benutzeroberfläche die Möglichkeit zum Aktualisieren/Aktualisieren. Sie müssen jedoch auf jeden Fall darauf achten, dass der Benutzer keine Tasten drücken oder andere nicht erlaubte Vorgänge auf der Benutzeroberfläche ausführen kann !! Deshalb: Versuchen Sie, DoEvents () zu vermeiden! , benutze lieber Threading (was in .Net ziemlich einfach ist).
    Aber (@Jagd, 2. April 10 um 19:25) können Sie .refresh() und .invalidate() weglassen.

Meine Erklärungen sind folgende: AFAIK winform verwendet weiterhin die WINAPI-Funktion. MSDN-Artikel über System.Windows.Forms Control.Update-Methode verweist auch auf die WINAPI-Funktion WM_Paint. Der MSDN-Artikel zu WM_Paint besagt im ersten Satz, dass der Befehl WM_Paint nur vom System gesendet wird, wenn die Nachrichtenwarteschlange leer ist. Da die Nachrichtenwarteschlange jedoch bereits im zweiten Fall gefüllt ist, wird sie nicht gesendet. Daher werden das Etikett und das Antragsformular nicht neu gezeichnet.

<> joke> Fazit: man muss also nur den Benutzer davon abhalten, die Maus zu benutzen ;-) <>/joke>

4
Tobias Knauss

sie können es versuchen

using System.Windows.Forms; // u need this to include.

MethodInvoker updateIt = delegate
                {
                    this.label1.Text = "Started...";
                };
this.label1.BeginInvoke(updateIt);

Sehen Sie, ob es funktioniert.

3
Rick2047

Starten Sie nach dem Aktualisieren der Benutzeroberfläche eine Aufgabe, die mit der Langzeitoperation ausgeführt werden soll:

label.Text = "Please Wait...";

Task<string> task = Task<string>.Factory.StartNew(() =>
{
    try
    {
        SomewhatLongRunningOperation();
        return "Success!";
    }
    catch (Exception e)
    {
        return "Error: " + e.Message;
    }
});
Task UITask = task.ContinueWith((ret) =>
{
    label.Text = ret.Result;
}, TaskScheduler.FromCurrentSynchronizationContext());

Dies funktioniert in .NET 3.5 und höher.

2
pixelgrease

Es ist sehr verlockend, dieses Problem "beheben" zu wollen und eine Aktualisierung der Benutzeroberfläche zu erzwingen. Die beste Lösung ist jedoch, dies in einem Hintergrund-Thread zu tun und nicht den Benutzeroberflächenthread zu binden, damit er trotzdem auf Ereignisse reagieren kann.

1
Mike Hall
1
Chetan

Ich denke, ich habe die Antwort, die aus den obigen Ausführungen herausgearbeitet wurde, und ein kleines Experimentieren.

progressBar.Value = progressBar.Maximum - 1;
progressBar.Maximum = progressBar.Value;

Ich habe versucht, den Wert zu dekrementieren, und der Bildschirm wurde sogar im Debug-Modus aktualisiert. Dies funktioniert jedoch nicht, wenn Sie progressBar.Value auf progressBar.Maximum setzen, da Sie den Fortschrittsbalkenwert nicht über das Maximum setzen können. Deshalb setze ich zuerst progressBar.Value auf progressBar.Maximum -1 und dann auf progressBar.Maxiumum gleich progressBar.Value. Sie sagen, es gibt mehr als eine Möglichkeit, eine Katze zu töten. Manchmal möchte ich Bill Gates töten oder wer auch immer es ist: o).

Mit diesem Ergebnis schien es mir nicht einmal nötig zu sein, Invalidate(), Refresh(), Update() oder irgendetwas mit der Fortschrittsleiste oder dem Panel-Container oder dem übergeordneten Formular zu tun.

1
Graham Bennett

Ich hatte das gleiche Problem mit der Eigenschaft Enabled und entdeckte einen first chance exception, weil nicht threadsicher ..__ ist. hier https://stackoverflow.com/a/661706/1529139 Und es funktioniert!

0
56ka

Wenn Sie nur ein paar Steuerelemente aktualisieren müssen, ist .update () ausreichend.

btnMyButton.BackColor=Color.Green; // it eventually turned green, after a delay
btnMyButton.Update(); // after I added this, it turned green quickly
0
user3029478