Laden...

[erledigt] InvokeRequired in Basisklasse für Control kapseln

Erstellt von mosspower vor 12 Jahren Letzter Beitrag vor 12 Jahren 1.685 Views
Thema geschlossen
mosspower Themenstarter:in
456 Beiträge seit 2007
vor 12 Jahren
[erledigt] InvokeRequired in Basisklasse für Control kapseln

Hallo,

gibt es eine Möglichkeit die Abfrage (und das Handling) für InvokeRequired in Basisklasse zu kapseln?

Aktuell wird eine Callback-Methode über die Basisklasse registriert:

base.WaitWebBrowserBackground(new WebBrowserWaitBackgroundCallback(LoginNavigateHomepage_CallbackDone), 5000);

Und in der Basisklasse wird dann von einem Thread aus die Callbackmethode aufgerufen, welche wie folgt aussieht:

  private void LoginNavigateHomepage_CallbackDone() {
      if(base.webBrowser.InvokeRequired) {
        this.Invoke(new WebBrowserWaitBackgroundCallback(LoginNavigateHomepage_CallbackDone));
      }
      else {
        base.LogInfo("THIS WORKS!" + base.webBrowser.InvokeRequired);
        HtmlElement loginButton = base.webBrowser.Document.GetElementById("submit-alt");
        loginButton.InvokeMember("click");
      }
    }

Jetzt habe ich schon einiges ausprobiert, aber ich bekomme es einfach nicht hin, das "Low-Level-Gedöns" der Abfrage auf InvokeRequired in der Basisklasse zu kapseln.

Hat jemand Idee oder geht das einfach net?

Für etwaige Antworten schon einmal vielen Dank im Voraus.

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo mosspower,

das "Low-Level-Gedöns" der Abfrage auf InvokeRequired in der Basisklasse zu kapseln.

Mir ist nicht klar was du damit meinst, kannst du das Ziel genauer definieren?

So wie ich die Frage interpretiere, reicht es wenn du eine Methode erstellst der du einen Delegaten übergibts und diese auf das Invoke prüfst, aber mehr ist wohl nicht drin. Siehe auch Eleganteste Art aus Worker-Thread auf Controls zugreifen [generell Kontrollfluss zwischen Threads].

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

mosspower Themenstarter:in
456 Beiträge seit 2007
vor 12 Jahren

Hallo gfoidl,

ich suche nach einem Weg, die Abfrage nach InvokeRequired in die Basisklasse auszulagern.

Es kommt sehr oft vor, dass ich, siehe oben, einen Callback mittels Delegate bei der Basisklasse registriere. Die Basisklasse ruft dann die Methode über den Delegaten über einen Thread auf.

Jetzt habe ich schon viel rumprobiert aber es scheint so zu sein, dass ich immer explizit in der Targetmethode die Abfrage machen muss, was natürlich nervig ist, wenn man mehrere solcher Callbackmethoden hat und immer wieder die Abfrage reinschreiben (reinkopieren) muss.

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo mosspower,

eine Methode zu schreiben, die einen Callback/Delegaten (und bei Bedarf auch das passende Control) als Parameter bekommt und in Abhängigkeit von InvokeRequired den Delegaten per Control.Invoke oder direkt aufruft, ist nun wirklich keine besondere Herausforderung. Wenn du so etwas tun willst, dann los. Dazu brauchst du uns nicht. Sind nicht mehr als vier oder fünf Zeilen (Zeilen, die nur geschweifte Klammern enthalten, nicht gerechnet).

Folgende Punkte solltest du dabei berücksichtigen:*Den Link von gfoidl, in dem diskutiert wird, wer überhaupt für das InvokeRequired/Invoke zuständig ist. *Die Basis-Klasse ist insbesondere dann nicht zuständig, wenn es keine GUI-Klasse ist. *Weil ein Control erforderlich ist (egal, ob nun per Parameter oder nicht) und weil weitere Methoden aus Windows-Forms erforderlich sind (Control.InvokeRequired, Control.Invoke), kommt der ganze Spaß ohnehin nur innerhalb einer GUI-Klasse in Frage. Auf keinen Fall sollte man das innerhalb einer Business-Klasse tun. *Wenn du weißt, dass die Callback-Methode immer aus einem anderen Thread als dem GUI-Thread aufgerufen wird, ist InvokeRequired überflüssig, weil du dann aus den Umständen automatisch weißt, dann Control.Invoke aufgerufen werden muss.

herbivore

U
1.688 Beiträge seit 2007
vor 12 Jahren

Hallo,

  private void LoginNavigateHomepage_CallbackDone() {  
      if(base.webBrowser.InvokeRequired) {  
        this.Invoke(new WebBrowserWaitBackgroundCallback(LoginNavigateHomepage_CallbackDone));  
      }  
...  
  

SO ist es ohnehin nicht richtig (auch wenn's funktioniert): Du prüfst base.webBrowser.InvokeRequired und rufst dann this.Invoke auf. Genausogut könntest Du in der Basisklasse webBrowser.InvokeRequired benutzen und webBrowser.Invoke aufrufen.

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo ujr,

was heißt "nicht richtig"? Da üblicherweise alle Controls in einem einzigen GUI-Thread laufen, ist man in der Wahl, welches Control man denn nun verwendet, frei. Das einzelne Control interessiert ja für Control.Invoke und Control.InvokeRequired nicht, sondern nur der Thread, in dem es läuft. So gesehen wird es wohl schon richtig sein. Zumindest nicht wirklich falsch.

Trotzdem stimme ich dir zu, dass der Code leichter zu lesen ist, wenn zumindest bei zusammengehörigen Aufrufen von Control.InvokeRequired und Control.Invoke dasselbe Control verwendet wird.

herbivore

U
1.688 Beiträge seit 2007
vor 12 Jahren

Hallo herbivore,

was heißt "nicht richtig"?

Ich finde es logisch betrachtet inkonsequent. Vielleicht hätte ich die Formulierung in Anführungszeichen setzen können. Meiner Meinung nach rührt daher zumindest der Anlass der Frage. Darum sehe ich den Beitrag an sich auch nicht als off-topic, sondern als Möglichkeit, das erfragte Ziel zu erreichen.

Da üblicherweise alle Controls in einem einzigen GUI-Thread laufen, ist man in der Wahl, welches Control man denn nun verwendet, frei.

Selbstverständlich. Dann sollte man dies aber bei InvokeRequired und Invoke gleichermaßen tun, wie Du auch bzgl. Lesbarkeit schreibst. Ich halte es aber auch für "korrekter", denn "üblicherweise" ist nicht "notwendigerweise" 😉

mosspower Themenstarter:in
456 Beiträge seit 2007
vor 12 Jahren

Ich erklär nochmal kurz die Problematik.

Ich habe eine Basisklasse, in dieser gibt es ein WebBrowser Control.
Jetzt kommt es vor, dass viele Webseiten mit AJAX arbeiten, so dass man bestimmte Controls oder Werte erst nach bestimmter Wartezeit angezeigt bekommt, weil hier funktionieren die WebBrowser-Control-Events DocumentCompleted ect. nicht, da es ja keinen kompletten Server-Roundtrip gibt und das Dokument ja nicht komplett neu geladen wird.

Jetzt habe ich eine Wait-Methode in der Basisklasse eingebaut, die solange "aussetzt", wie Timeout übergeben wird oder bis eine bestimmte ID in dem aktuellen Dokument gefunden wird.

Das Problem scheint zu sein, dass ich in dem Thread der Basisklasse widerum einen Delegaten aufrufe und immer dann ist ja InvokeRequired true.

Was ich jetzt noch mal probiere ist, den Methodennamen als String zu übergeben und in der Basisklasse / Thread der Basisklasse mittels Reflection die Methode in der Subklasse aufzurufen - ich denke, das wird funktionieren. Die Threadmethode prüft natürlich vorher gegen InvokeRequired und ja, es war ein Fehler von mir mit this.InvokeRequired, ich habe natürlich das Ausgangscontrol gemeint aber in der Schnelle noch das alte Zeugs hier reinkopiert - sorry!

U
1.688 Beiträge seit 2007
vor 12 Jahren

Hallo,

InvokeRequired im Thread brauchst Du nicht, eben weil es immer true ist. Du könntest also direkt die Methode Invoke Deines webBrowsers in der Basisklasse aufrufen. Oder wo ist da noch ein Haken? Möglicherweise musst Du einfach mehr Code zeigen.

mosspower Themenstarter:in
456 Beiträge seit 2007
vor 12 Jahren

OK, ich will mich dann nochmal bei allen bedanken.
Ich war irgendwie auf einer völlig falschen Fährte - es klappt jetzt mit der Thread-Methode in der Basisklasse:

private void WebBrowserWaitBackgroundThread(Object threadParams) {
      if(this.webBrowser.InvokeRequired) {
        this.webBrowser.Invoke(new WebBrowserWaitBackgroundThreadDelegate(WebBrowserWaitBackgroundThread), threadParams);
      }
      else {
        Object[] args = (Object[])threadParams;
        WebBrowserWaitBackgroundCallback webBrowserWaitBackgroundCallback = (WebBrowserWaitBackgroundCallback)args[0];
        int timeout = (int)args[1];
        String controlName = (String)args[2];

        if(webBrowserWaitBackgroundCallback != null) {
          if(!String.IsNullOrEmpty(controlName)) {
            // Handle configured control
            int activityCounter = 0;
            int increment = 5;

            // Maximum 15 seconds timeout
            while(activityCounter < TimeSpan.FromSeconds(15).TotalMilliseconds) {
              // Check control exists and has content
              HtmlElement controlElement = null;

              try {
                controlElement = this.webBrowser.Document.GetElementById(controlName);
              }
              catch {
                // Ignore this one, document could be null due to loading process
              }
            
              if(controlElement != null && !String.IsNullOrEmpty(controlElement.InnerText)) {
                break;
              }

              Application.DoEvents();
              Thread.Sleep(increment);
              activityCounter += increment;
            }
          }
          else {
            // Handle configured timeout
            if(timeout > 0) {
              int activityCounter = 0;
              int increment = 5;

              while(activityCounter < timeout) {
                Application.DoEvents();
                Thread.Sleep(increment);
                activityCounter += increment;
              }
            }
          }

          // Invoke callback ==> HIER WIRD DIE Methode in der Subklasse aufgerufen !!! - InvokeRequired-Check nun nicht mehr notwendig in der Subklassen-Methode  :)
          webBrowserWaitBackgroundCallback.Invoke();
        }
      }
    }

Dank euch allen für die Hinweise - Thread solved!

6.911 Beiträge seit 2009
vor 12 Jahren

Hallo mosspower,

Thread solved!

Bis auf das "böse" Application.DoEvents. Das geht noch besser. Siehe [FAQ] Warum blockiert mein GUI?

mfG Gü

Stellt fachliche Fragen bitte im Forum, damit von den Antworten alle profitieren. Daher beantworte ich solche Fragen nicht per PM.

"Alle sagten, das geht nicht! Dann kam einer, der wusste das nicht - und hat's gemacht!"

U
1.688 Beiträge seit 2007
vor 12 Jahren

Bis auf das "böse" Application.DoEvents.

Nun ja - das braucht er wohl, weil sonst die GUI blockiert. Wie man am Code sieht, wird nämlich der gesamte Ablauf per Invoke wieder in den GUI Thread verlagert. Ein typischer Fall für den Abschnitt "Die Falle".

Auch verstehe ich

webBrowserWaitBackgroundCallback.Invoke()

nicht. Funktioniert denn

webBrowserWaitBackgroundCallback()

nicht?

Zudem: wenn WebBrowserWaitBackgroundThread immer in einem Hintergrundthread läuft (wovon aufgrund des Namens ausgegangen werden sollte), braucht man InvokeRequired überhaupt nicht, weil es immer true ist. Aber das steht eigentlich schon oben.

49.485 Beiträge seit 2005
vor 12 Jahren

Hallo mosspower,

für mich sieht das nach komplettem Blödsinn aus. Wie ujr schon sagt, liegt hier wohl für [FAQ] Warum blockiert mein GUI? Abschnitt die "Die Falle" vor. Du startest einen Thread, der sinnlos ist, weil du sofort alles mit Control.Invoke zurück an den GUI-Thread delegierst. Dadurch blockiert natürlich wieder dein GUI, was du wiederum mit DoEvents umgehst. Dabei bräuchtest du nur einen Timer. Dann brauchst du keine Control.InvokeRequired, kein Control.Invoke und kein DoEvents. Steht aber alles in der FAQ, bzw. geht daraus hervor. Diesen Thread hätten wir uns sparen können.

herbivore

Thema geschlossen