Große Funktion in Klasse ausgelagert – nun Fehlermeldung wegen Invoke

  • C#
  • .NET (FX) 4.5–4.8

Es gibt 6 Antworten in diesem Thema. Der letzte Beitrag () ist von Bartosz.

    Große Funktion in Klasse ausgelagert – nun Fehlermeldung wegen Invoke

    Hi all,

    ich habe die Daten-Einlese-Funktion meiner WinForms-Anwendung wegen ihrer Größe (201 Zeilen) in eine separate Klasse ausgelagert. Sie soll asynchron laufen. In der Funktion gibt es mehrere Invoke-Aufrufe, welche allerdings nur auftreten, wenn es einen Fehler beim Einlesen gibt; also à la falsche Datei ausgewählt, String kann nicht zu Integer geparst werden. Da das aber nicht das erwartete Verhalten ist, steckt dieser Code halt in der Funktion, welche schön asynchron durchlaufen kann.

    C#-Quellcode

    1. DateTime LastChangeTime;
    2. bool successful4 = DateTime.TryParse(RAT[i + 4], out LastChangeTime);
    3. if (!successful4)
    4. {
    5. FM.Invoke((Action)(() => MessageBox.Show($"Einlesen nicht erfolgreich.\nIst das die richtige Datei?\n»{FullFileName}«",
    6. "Datenbank – Daten laden",
    7. MessageBoxButtons.OK,
    8. MessageBoxIcon.Hand)));
    9. return;
    10. }


    Sollte das nun doch gebraucht werden, gibt es einen Fehler, wenn ich FM.Invoke() schreibe.


    Unbehandelte Ausnahme: System.Reflection.TargetInvocationException: Ein Aufrufziel hat einen Ausnahmefehler verursacht. ---> System.InvalidOperationException: Invoke oder BeginInvoke kann für ein Steuerelement erst aufgerufen werden, wenn das Fensterhandle erstellt wurde.
    bei System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
    bei System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
    bei System.Windows.Forms.Control.Invoke(Delegate method)
    bei IA.TextfileParser.read_in_data() in C:\Users\...\InventoryApp\IA\TextfileParser.cs:Zeile 34.
    bei IA.FormMain.<>c.<determine_power_of_decision>b__29_0() in C:\Users\...\InventoryApp\IA\FormMain.cs:Zeile 494.
    bei System.Threading.Tasks.Task.InnerInvoke()
    bei System.Threading.Tasks.Task.Execute()


    Dabei habe ich doch von FormMain eine neue Instanz erzeugt! Mit den Worten »wenn das Fensterhandle erstellt wurde« komme ich nicht weiter und mit ergoogelten Forumsbeiträgen im Internet auch nicht.

    In FormMain wird die Funktion wie folgt aufgerufen:

    C#-Quellcode

    1. await Task.Run(() => new TextfileParser().read_in_data());


    in TextileParser.cs:

    C#-Quellcode

    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Text;
    5. using System.Windows.Forms;
    6. namespace IA
    7. {
    8. public sealed class TextfileParser
    9. {
    10. private readonly System.Globalization.CultureInfo Deu = new System.Globalization.CultureInfo("de-DE");
    11. private readonly FormMain FM = new FormMain();
    12. /// <summary>
    13. /// ready-made for asynchronous use
    14. /// </summary>
    15. public void read_in_data()
    16. {
    17. FM.Invoke((Action)(() =>
    18. {
    19. }));
    20. }
    21. }
    22. }





    Edit 21:35 Uhr: Sonst schicke ich ein Event. Fiel mir gerade wieder ein.

    Dieser Beitrag wurde bereits 1 mal editiert, zuletzt von „Bartosz“ ()

    @Bartosz Was passiert, wenn Du die MessageBox im anderen Thread aufrufst?
    Das sollte eigentlich funktionieren.
    Im Ernstfall holst Du die Box nach vorn:

    C#-Quellcode

    1. MessageBox.Show("Text", "Titel", MessageBoxButtons.OK, MessageBoxIcon.Stop, MessageBoxDefaultButton.Button1, MessageBoxOptions.ServiceNotification);
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!

    Bartosz schrieb:

    Dabei habe ich doch von FormMain eine neue Instanz erzeugt!
    Aber vermutlich noch noch angezeigt. Das Handle wird erst erzeugt, wenn es gebraucht wird, also beim anzeigen oder beim drauf zugreifen. Also was ist wenn du dir vorher ​FM.Handle in einer Variable abspeicherst?
    @Bluespide Völlig falsche Herangehensweise!
    Hier würden dann zwei FormMain-Instanzen existieren, das sollte eigentlich nicht vorkommen.
    Die zweite FormMain-Instanzen liefe dann ja in dem anderen Thread, und das geht (programmtechnisch) so schon gar nicht.
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Was passiert, wenn Du die MessageBox im anderen Thread aufrufst?
    Das geht auch, aber ich will's ordentlich machen.

    Lösung mit Event. Ich brauche Hilfe bei der Syntax, wenn ich einen zusätzlichen Parameter übergeben will.

    Ich mache das jetzt mit einem Event. Leider komme ich mit der Syntax nicht zurecht. Ich habe in FormMain eine Prozedur geschrieben:

    C#-Quellcode

    1. private void MessageBox_required(object sender, EventArgs e, string s)
    2. {
    3. MessageBox.Show(s,
    4. "Datenbank – Daten laden",
    5. MessageBoxButtons.OK,
    6. MessageBoxIcon.Error);
    7. }


    Ebenso in FormMain.cs steht:
    private TextfileParser TfP = new TextfileParser();
    und im Form_Load
    TfP.MR += new TextfileParser.MessageBox_required;

    In TextfileParser.cs habe ich geschrieben:

    C#-Quellcode

    1. public delegate void MessageBox_required(object sender, EventArgs e, string Text);
    2. public event MessageBox_required MR;



    Fehler CS1729 "TextfileParser.MessageBox_required" enthält keinen Konstruktor, der 0 Argumente annimm



    Fehler CS1526 Ein new-Ausdruck erfordert nach dem Typ eine Argumentliste oder (), [] oder {}



    Fehler CS7036 Es wurde kein Argument angegeben, das dem formalen Parameter "e" von "TextfileParser.MessageBox_required.Invoke(object, EventArgs, string)" entspricht.


    Bartosz schrieb:

    Lösung mit Event. Ich brauche Hilfe bei der Syntax, wenn ich einen zusätzlichen Parameter übergeben will.
    Mach Dir Deine eigenen EventArgs, eine Klasse, die von EventArgs abgeleitet ist.
    Dort kannst Du alles reinpacken, was Du brauchst:

    C#-Quellcode

    1. public event EventHandler<MyEventArgs> MyEvent = delegate {};

    C#-Quellcode

    1. MyEvent(this, new MyEventArgs(DEINE_PARAMETER));
    Falls Du diesen Code kopierst, achte auf die C&P-Bremse.
    Jede einzelne Zeile Deines Programms, die Du nicht explizit getestet hast, ist falsch :!:
    Ein guter .NET-Snippetkonverter (der ist verfügbar).
    Programmierfragen über PN / Konversation werden ignoriert!
    Lösung und Beispiel-Sourcecode für zukünftige Leser (die von Google kommen)

    Was war das Problem
    Ich habe die Hauptklasse FormMain und eine weitere Klasse. Der Code in ihr soll asynchron laufen, also auf einem anderen Thread. So. Wenn – allgemein gesprochen jetzt – ein Control ge-updated werden soll, muss man ja invoken, das heißt, die Aufgabe vom Hauptthread machen lassen. In so einer Klasse kann man dann aber nicht this.Invoke(…) schreiben. Daher dachte ich zuerst, dass ich eine weitere Instanz von FormMain brauche. Daher habe ich diesen Forumsbeitrag eröffnet. Da es aber nicht gut ist, eine weitere Instanz von FormMain zu kreieren, muss man ein Event schicken. Ich habe das in VB.NET schonmal so gemacht (ich hatte hier im August 2021 einen Beitrag geschrieben), jedoch ist die Syntax in C# eine ganz andere als in VB.NET. Außerdem muss ich sagen: Als Code-Beispiel einen MessageBox-Aufruf zu nutzen war kein gutes Beispiel, da die MessageBox sich ohnehin auf beiden Threads ohne Fehlermeldung aufrufen lässt. Deshalb stelle ich jetzt Sourcecode zur Verfügung, welcher eine ListBox updated.

    Lösung
    in DieAndereKlasse.cs

    C#-Quellcode

    1. public delegate void GUI_Update_required(object sender, EventArgs e);
    2. public event GUI_Update_required GUIUR;


    und in der Funktion

    C#-Quellcode

    1. GUIUR.Invoke(this, EventArgs.Empty); // hauptsächlich zum ListBox aktualisieren.



    In FormMain.cs

    C#-Quellcode

    1. private DieAndereKlasse DAK = new DieAndereKlasse();


    und im Form_Load

    C#-Quellcode

    1. DAK.GUIUR += On_GUI_Update_required;


    Ich habe die Funktion On_GUI_Update_required genannt, damit sie nicht denselben Namen hat wie der Delegat. Diese Funktion ist auch in FormMain!

    C#-Quellcode

    1. private void On_GUI_Update_required(object sender, EventArgs e)
    2. {
    3. this.Invoke((Action)(() => {
    4. update_ListBox();
    5. }));
    6. }

    Bitte beachten: update_ListBox() ist eine weitere Funktion, die ich bereits hatte und die ich des Öfteren aufrufe. Das muss man nicht so machen.

    – – – – – – – – – – – – – – – – – – – – – – – – – – – – – –

    Wenn ihr ein Event raisen wollt, und es geht um eine Funktion mit einem zusätzlich übergebenen Parameter, dann schauen wir uns den Code für die MessageBox an:

    in DieAndereKlasse.cs

    C#-Quellcode

    1. public delegate void MessageBox_required(object sender, EventArgs e, string Text);
    2. public event MessageBox_required MR;


    und in der Funktion

    C#-Quellcode

    1. MR?.Invoke(this, EventArgs.Empty, $"Einlesen nicht erfolgreich.\nIst das die richtige Datei?\n»{FullFileName}«");


    In FormMain.cs

    C#-Quellcode

    1. private DieAndereKlasse DAK = new DieAndereKlasse();


    und im Form_Load

    C#-Quellcode

    1. DAK.MR += OnMessageBoxRequired;


    C#-Quellcode

    1. private void OnMessageBoxRequired(object sender, EventArgs e, string s)
    2. {
    3. MessageBox.Show(s,
    4. "Datenbank – Daten laden",
    5. MessageBoxButtons.OK,
    6. MessageBoxIcon.Error);
    7. }

    Bei der MessageBox ist wie erwähnt kein this.Invoke((Action)(() => { })); nötig.