Ich habe ein SafeInvoke Steuerungserweiterung Verfahren ähnlich den Greg D bespricht hier (minus der IsHandleCreated Prüfung).
Ich nenne es System.Windows.Forms.Form
wie folgt:
public void Show(string text) {
label.SafeInvoke(()=>label.Text = text);
this.Show();
this.Refresh();
}
Manchmal (dieser Aufruf kann von einer Vielzahl von Threads stammen) führt dies zu folgendem Fehler:
System.InvalidOperationException
aufgetreten
Message
= "Invoke oder BeginInvoke können für ein Steuerelement erst aufgerufen werden, wenn das Fensterhandle erstellt wurde."
Source
= "System.Windows.Forms"
StackTrace: at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous) at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args) at System.Windows.Forms.Control.Invoke(Delegate method) at DriverInterface2.UI.WinForms.Dialogs.FormExtensions.SafeInvoke[T](T control, Action`1 action) in C:\code\DriverInterface2\DriverInterface2.UI.WinForms\Dialogs\FormExtensions.cs:line 16
Was ist los und wie behebe ich das? Ich weiß so viel, wie es kein Problem bei der Formularerstellung ist, da es manchmal einmal funktioniert und beim nächsten Mal fehlschlägt. Was könnte das Problem sein?
PS. Ich bin wirklich sehr, sehr schrecklich bei WinForms. Kennt jemand eine gute Artikelserie, die das gesamte Modell erklärt und wie man damit arbeitet?
quelle
IsHandleCreated
Prüfung vorhanden ist. Sie versuchen, eine Eigenschaft eines noch nicht erstellten Steuerelements zu ändern (Nachricht senden an). In dieser Situation können Sie Delegierte in die Warteschlange stellen, die vor der Erstellung des Steuerelements übermittelt wurden, und sie dann imHandleCreated
Ereignis ausführen .Antworten:
Möglicherweise erstellen Sie Ihre Steuerelemente im falschen Thread. Beachten Sie die folgende Dokumentation von MSDN :
Mal sehen, was das für Sie bedeutet. (Dies wäre leichter zu begründen, wenn wir auch Ihre Implementierung von SafeInvoke sehen würden.)
Angenommen, Ihre Implementierung ist mit der referenzierten identisch, mit Ausnahme der Prüfung gegen IsHandleCreated , folgen wir der Logik:
public static void SafeInvoke(this Control uiElement, Action updater, bool forceSynchronous) { if (uiElement == null) { throw new ArgumentNullException("uiElement"); } if (uiElement.InvokeRequired) { if (forceSynchronous) { uiElement.Invoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); }); } else { uiElement.BeginInvoke((Action)delegate { SafeInvoke(uiElement, updater, forceSynchronous); }); } } else { if (uiElement.IsDisposed) { throw new ObjectDisposedException("Control is already disposed."); } updater(); } }
Stellen Sie sich den Fall vor, in dem wir
SafeInvoke
vom Nicht-GUI-Thread nach einem Steuerelement rufen, dessen Handle nicht erstellt wurde.uiElement
ist nicht null, also prüfen wiruiElement.InvokeRequired
. Gemäß den MSDN-Dokumenten (fett gedruckt)InvokeRequired
wird zurückgegeben,false
da das Handle nicht erstellt wurde, obwohl es in einem anderen Thread erstellt wurde! Dies versetzt uns in denelse
Zustand, in dem wirIsDisposed
die übermittelte Aktion überprüfen oder sofort aufrufen ... aus dem Hintergrund-Thread !Zu diesem Zeitpunkt sind alle Wetten für dieses Steuerelement deaktiviert, da sein Handle für einen Thread erstellt wurde, für den keine Nachrichtenpumpe vorhanden ist, wie im zweiten Absatz erwähnt. Vielleicht ist dies der Fall, dem Sie begegnen?
quelle
EndInvoke
nach demBeginInvoke
einfügen?Ich fand das
InvokeRequired
nicht zuverlässig, also benutze ich es einfachif (!this.IsHandleCreated) { this.CreateHandle(); }
quelle
Hier ist meine Antwort auf eine ähnliche Frage :
quelle
Die Methode in dem Beitrag, den Sie mit Aufrufen verknüpfen
Invoke
/BeginInvoke
bevor Sie überprüfen, ob das Handle des Steuerelements erstellt wurde, falls es von einem Thread aufgerufen wird, der das Steuerelement nicht erstellt hat.Sie erhalten also die Ausnahme, wenn Ihre Methode von einem anderen Thread als dem aufgerufen wird, der das Steuerelement erstellt hat. Dies kann durch Remoting von Ereignissen oder in der Warteschlange befindliche Arbeitsbenutzerelemente geschehen ...
BEARBEITEN
Wenn Sie überprüfen
InvokeRequired
undHandleCreated
vor dem Aufruf von invoke, sollten Sie diese Ausnahme nicht erhalten.quelle
Wenn Sie ein
Control
aus einem anderen Thread verwenden möchten, bevor Sie das anzeigen oder andere Dinge mit dem tunControl
, sollten Sie die Erstellung seines Handles im Konstruktor erzwingen. Dies erfolgt über dieCreateHandle
Funktion.In einem Multithread-Projekt, in dem sich die "Controller" -Logik nicht in einer WinForm befindet, ist diese Funktion
Control
für Konstruktoren hilfreich, um diesen Fehler zu vermeiden.quelle
Fügen Sie dies hinzu, bevor Sie method invoke aufrufen:
while (!this.IsHandleCreated) System.Threading.Thread.Sleep(100)
quelle
Verweisen Sie wie folgt auf das Handle des zugeordneten Steuerelements in seinem Ersteller:
Hinweis : Seien Sie vorsichtig mit dieser Lösung. Wenn ein Steuerelement über ein Handle verfügt, ist es viel langsamer, beispielsweise Größe und Position festzulegen. Dies macht InitializeComponent viel langsamer. Eine bessere Lösung besteht darin, nichts in den Hintergrund zu stellen, bevor das Steuerelement einen Griff hat.
quelle
Ich hatte dieses Problem mit dieser einfachen Form:
public partial class MyForm : Form { public MyForm() { Load += new EventHandler(Form1_Load); } private void Form1_Load(Object sender, EventArgs e) { InitializeComponent(); } internal void UpdateLabel(string s) { Invoke(new Action(() => { label1.Text = s; })); } }
Dann habe
n
ich für andere asynchrone Threadsnew MyForm().UpdateLabel(text)
versucht, den UI-Thread aufzurufen, aber der Konstruktor gibt der UI-Thread-Instanz kein Handle, sodass andere Threads andere Instanz-Handles erhalten, die entwederObject reference not set to an instance of an object
oder sindInvoke or BeginInvoke cannot be called on a control until the window handle has been created
. Um dies zu lösen, habe ich ein statisches Objekt verwendet, um das UI-Handle zu halten:public partial class MyForm : Form { private static MyForm _mf; public MyForm() { Load += new EventHandler(Form1_Load); } private void Form1_Load(Object sender, EventArgs e) { InitializeComponent(); _mf = this; } internal void UpdateLabel(string s) { _mf.Invoke((MethodInvoker) delegate { _mf.label1.Text = s; }); } }
Ich denke, es funktioniert soweit gut ...
quelle
var that = this; // this is a form (new Thread(()=> { var action= new Action(() => { something })); if(!that.IsDisposed) { if(that.IsHandleCreated) { //if (that.InvokeRequired) that.BeginInvoke(action); //else // action.Invoke(); } else that.HandleCreated+=(sender,event) => { action.Invoke(); }; } })).Start();
quelle
this
variiert nicht basierend auf dem Aufruf, dass eine Technik im Javascript-Stil unnötig sein sollte.Was ist damit:
public static bool SafeInvoke( this Control control, MethodInvoker method ) { if( control != null && ! control.IsDisposed && control.IsHandleCreated && control.FindForm().IsHandleCreated ) { if( control.InvokeRequired ) { control.Invoke( method ); } else { method(); } return true; } else return false; }
quelle