Zugriff auf MainForm aus Konstruktor eines UserControls

  • VB.NET

Es gibt 26 Antworten in diesem Thema. Der letzte Beitrag () ist von ErfinderDesRades.

    Zugriff auf MainForm aus Konstruktor eines UserControls

    Hallo Forum,

    ich beschäftige mich derzeit mit UserControls.

    Im Konstruktor (Sub New) der UserControls führe ich diversen Code aus. Sobald ich aber versuche aus diesem Konstruktor heraus auf irgendwelche Eigenschaften meines einzigen Forms (frm_Main) zuzugreifen, bekomme ich eine Fehlermeldung.

    Den Zugriff auf mein Form versuche ich aus dem Konstruktor wie folgt:

    VB.NET-Quellcode

    1. ...
    2. Sub New()
    3. InitializeComponent()
    4. 'irgendwelcher Code
    5. frm_Main.irgendwas
    6. End Sub
    7. ...



    Hier die Fehlermeldung:

    A first chance exception of type 'System.InvalidOperationException' occurred in SPOK.exe

    Additional information: Das Formular hat während der Erstellung ausgehend von einer Standardinstanz auf sich selbst verwiesen. Dies führte zu einer Endlosschleife. Verweisen Sie im Konstruktor des Formulars mithilfe von "Me" auf das Formular.


    Was der Fehler besagt, ist mir klar (hoffe ich). Ich habe jedoch keine Ahnung wie ich das Problem löse. Es muss doch eine Möglichkeit geben aus dem Konstruktor heraus auf frm_Main zuzugreifen??!!

    lg

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

    Es ist übrigens normalerweise nicht nötig, einem UserControl eine Referenz auf die Form zu geben.
    Kannst Du vielleicht etwas mehr von Deinem Code posten, und es auch erklären, damit wir wissen worum's geht? Dann könnten wir Dir eventuell ein paar Tipps geben, wie's anders geht.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils

    cl10k schrieb:

    Es muss doch eine Möglichkeit geben aus dem Konstruktor heraus auf frm_Main zuzugreifen??!!
    Welchen Sinn sollte das haben?
    Informationen, die das Control braucht, werden per Property übergeben, Informationen, die das Hauptfenster braucht, werden per Event übertragen.
    Alles zu seiner Zeit. Im Konstruktor hat das nix zu suchen!
    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!
    Vorab: Ich bin ein ziemlicher Anfänger...

    Ich erstelle in frm_Main ein paar Klasseninstanzen. Diese gehören somit zum Namespace von frm_Main.

    Auf dem Form erzeuge ich per Buttonklick dynamisch neue UserControls. Diese neuen UserControls trage ich u.a. in eine der Instanzen in eine List (of T) ein.

    Aus dem Konstruktor der UserControls versuche ich nun z.B. folgendes:

    frm_Main.irgendeineInstanz.irgendeineListe.Add(Me)

    Tatsächlich ist das ganze Problem ein bisschen komplexer. Sollte obige Erklärung nicht ausreichen, werde ich versuchen ein entsprechendes Minimalbeispiel zu erstellen...
    Diese gehören somit zum Namespace von frm_Main.

    Diese Aussage ist einfach Schwachsinn. Nur weil du auf ein paar Objekte referenzierst gehören diese noch lange nicht zum Namespace. Desweiteren können sowieso nur Klassen einem Namespace angehören, keine Objekte.

    Außerdem kannst du das genau wie Rod geschrieben hatte hervorragend mit Events lösen. Das Usercontrol löst ein Event aus, welches alle nötigen Daten zur Verfügung stellt, und die Form kümmert sich dann um die Erstellung der Steuerelemente.

    cl10k schrieb:

    Tatsächlich ist das ganze Problem ein bisschen komplexer.
    Vielleicht erklärst Du Dein Problem an ein paar Zeilen Code.
    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!
    Diese Aussage ist einfach Schwachsinn.


    Wie ich bereits weiter oben schrieb, bin ich Anfänger und befinde mich hier im Grundlagenbereich des Forums. Achte bitte auf deine Formulierung!


    Aus euren Zeilen lese ich folgendes heraus:

    Neues Event für frm_Main erstellen, welches anspringt sobald ein UserControl hinzugefügt wird. Das Event kümmert sich dann um den Rest?

    Daran finde ich einen Aspekt unschön: Derzeit muss ich an 2 Stellen (UserControl und KlassenInstanz) rumfummeln wenn ich etwas ändern möchte. Nun müsste ich auch noch an frm_Main fummeln.

    Ich fand es eigentlich ganz schön (wartungsfreundlicher), die gesamte Kommunikation zwischen UserControls und Klasseninstanzen an zwei zentralen Orten durchzuführen. Wenn ich für mein Vorhaben statt New den Load-Event des UserControls benutze, funktioniert es auch...

    EDIT @Rod: Ich erstelle mal schnell ein Minimalbeispiel...

    cl10k schrieb:

    Derzeit muss ich an 2 Stellen (UserControl und KlassenInstanz) rumfummeln wenn ich etwas ändern möchte.
    Wir versuchen Dir hier ein paar Grundlagen zu vermitteln, und das Ergebnis soll ja auch vernünftig werden.
    Das Usercontrol stellt das Event bereit, das Hauptprogramm aboniert es.
    Wie beim Button: Der Button stellt das Event Click bereit, im Hauptprogramm wird das Event Click mit der Prozedur Button_Click( aboniert.
    Feddich.
    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!
    Wir versuchen Dir hier ein paar Grundlagen zu vermitteln, und das Ergebnis soll ja auch vernünftig werden.

    Das ist genau der Grund warum ich hier nachfrage. :)

    Um noch einmal meine Bedenken klar zu formulieren: Ich habe bisher quasi keine Funktionalität auf meinem MainForm. Ausser meine UserControls anzuzeigen, kann das Ding nichts. Eigene normale Controls besitzt frm_Main überhaupt nicht - dementsprechend gibt es dort auch nicht den ganzen Wust aus Events, Subs, etc...

    Die gesamte Programmlogik spielt sich gekapselt in Datenklassen und UserControls ab. Eigentlich würde ich diesen "Ansatz" der Trennung von GUI/Rest gern weiterverfolgen. Es stört mich wenn ich nun für so eine Kleinigkeit doch wieder Programmlogik in frm_Main auslagern muss...


    Ich kriege jetzt in aller Eile kein Minimalbeispiel zusammen was dem Problem gerecht wird. Ich glaube aber das sich mein Problem mit Grundlagenwissen lösen lässt. Darum hier noch einmal der Versuch mein Problem exakter zu beschreiben:



    Im Kern geht es um dieses UserControl:



    Per +/- kann man neue Instanzen des Controls dem Mainform hinzufügen:



    Jedes neu erzeugte UserControl trägt sich in mehrere unterschiedliche Listen (beherbergt in unterschiedlichen Klassen) ein:

    Bis auf eine Ausnahme sind diese Klassen statisch. (Ich weiss was du davon hälst, aber diese Klassen übernehmen global das Management bestimmter Funktionen und kommen nur einmal vor. Ich sehe keinen Sinn darin, dafür eine Instanz zu erstellen). Ich greife ohne Probleme aus dem Konstruktor meines UserControls auf deren Properties zu und trage mich in die entsprechenden Listen ein.

    Die verbliebene Klasse ist nun aber eine richtige Instanz die ich in frm_Main erzeuge:

    VB.NET-Quellcode

    1. Public Class frm_Main
    2. Public Mission As New cls_Mission
    3. End Class


    Der Konstruktor meines UserControls sieht so aus:

    VB.NET-Quellcode

    1. Sub New()
    2. InitializeComponent()
    3. 'Place Controls
    4. 'Code...
    5. statischeKlasse1.Liste.Add(Me)
    6. statischeKLasse2.Liste.Add(Me)
    7. frm_Main.Mission.Liste.Add(Me) '### hier dann der Fehler
    8. End Sub


    Wenn ich das Hinzufügen zu den ganzen Listen im Load-Event des UserControls ausführe, funktioniert es allerdings...

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „cl10k“ ()

    Dann würde ich mir einen MissionManager (oder so) basteln, damit Du nicht over 9000 Listen hast, in die sich die UserControls eintragen müssen, sondern nur eine.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Wie gesagt:
    Es ist übrigens normalerweise nicht nötig, einem UserControl eine Referenz auf die Form zu geben.

    Mir stellt sich die Frage, warum die MainForm eine Mission haben muss. Ist das die Mission, die gerade angezeigt wird?

    Das Problem ist die Standardinstanz. Diese treibt viele VB-Programmierer in den Wahnsinn. Und die C#-Programmierer lachen dann über uns.

    Es gibt einen Unterschied zwischen Klassen und Instanzen von Klassen. Oder anders ausgedrückt: Eine Klasse ist ein Bauplan, eine Instanz einer Klasse ist ein Auto, das nach dem Bauplan gebaut wurde.
    Was Du versuchst, ist Bauplan.Herumfahren() (frm_Main.Mission)
    Das geht aber nicht. Ist auch logisch.
    Was der VB-Compiler Dir da hinkompilliert ist folgendes:

    VB.NET-Quellcode

    1. DirectCast(Application.OpenForms("frm_Main"), frm_Main).Mission

    Das nennt sich Standardinstanz.

    Das Problem ist halt eben, dass die Aufrufreihenfolge so aussieht:

    Quellcode

    1. frm_Main: Konstruktor
    2. frm_Main: InitializeComponent()
    3. UserControl: Konstruktor
    4. Application.OpenForms("frm_Main")

    Das bedeutet, noch während der Konstruktor der Form läuft, möchtest Du die Standardinstanz abrufen. Das kann aber nicht funktionieren, weil diese Instanz noch garnicht vorhanden ist (sie wird gerade erstellt... der Konstruktor wird ja gerade ausgeführt).



    Was teilweise funktioniert:
    Ändere den Konstruktor des UserControls:

    VB.NET-Quellcode

    1. Public Sub New(MainForm As frm_Main)
    2. '...
    3. MainForm.Mission....
    4. End Sub

    Das hat drei Probleme:
    Erstens musst Du sicherstellen, dass zu diesem Zeitpunkt auf das Mission-Feld zugegriffen werden darf.
    Zweitens gibt das Probleme mit dem Designer. Eine Möglichkeit wäre, zwei Konstruktoren anzulegen. Einen ohne Parameter und einen mit.
    Das dritte Problem ist, dass Du in der InitializeComponent-Methode alle Aufrufe an den Konstruktor (... New UserControl) anpassen musst (... New UserControl(Me)). Das sollte aber kein Problem sein.



    Was funktioniert, möglicherweise aber einige Anpassungen erfordert:
    Du fügst die UserControls erst später in die Listen ein. Also sobald die MainForm existiert.
    "Luckily luh... luckily it wasn't poi-"
    -- Brady in Wonderland, 23. Februar 2015, 1:56
    Desktop Pinner | ApplicationSettings | OnUtils
    Danke für diese ungemein hilfreiche Erklärung! Ich muss mir das mal ein bisschen durch den Kopf gehen lassen. Aber du hast mir ja genug Ansatzpunkte mitgegeben, welche ich erstmal nachschlagen kann...


    Die Mission ist einfach nur eine von zahlreichen Instanzen die ich zentral im Load Event des Forms erstellen lasse. Mir ist kein besserer Ort eingefallen an dem ich meine Datenklassen unterbringe...
    Nachdem ich mich eine Weile durch Pfusch über Wasser halten konnte, haben mich die Events nun doch noch eingeholt... (Hallo Rod :S )

    Ich habe nun versucht mich an das Tutorial von EDR zu halten, habe aber noch nicht alles verstanden! Ich versuche mich nun langsam voranzuarbeiten und dabei meine Implementierung sukzessive zu verbessern. Mir geht es derzeit vor allem um grundsätzliche Fragen zur "Best Practice"...

    Derzeit habe ich innerhalb meines UserControls 2 Events definiert; "Raise" der Beiden erfolgt dann jeweils bei Erstellung und Zerstörung des Controls.

    An die Evente möchte ich per Add-/RemoveHandler verschiedene Methoden in unterschiedlichen Klassen binden. Leider verlangen diese Methoden alle eine andere Signatur.

    Wie macht man es richtig? Definiert man mehrere Events jeweils mit einer passenden Signatur oder kann ich die Signatur doch in irgendeiner Form modifizieren? Ich könnte die Signatur der Events natürlich so erweitern das immer alle benötigten Informationen vorhanden sind und dann nur die gerade benötigten in den Methoden auswerten, aber das ist ja auch blöd. Alle Methoden bräuchten dann ja eine entsprechende Signatur und wenn jemand später dort rein schaut, ist vollkommen unklar warum ich Parameter übergebe und dann nie etwas damit anstelle...

    Edit: Überladen kann man die Events ja auch nicht?

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „cl10k“ ()

    Standard wäre hier die EventArgs also

    VB.NET-Quellcode

    1. Public Event Blub As EventHandler
    2. .
    3. .
    4. RaiseEvent Blub(Me, EventArgs.Empty)


    Nun brauchste noch ein Sub mit der entsprechenten Signaturen

    VB.NET-Quellcode

    1. Public BlubInstanz As New BlubClass
    2. AddHandler BlubInstanz.Blub, AddressOf Blubber
    3. Sub Blubber (ByVal sender As Object, ByVal e As EventArgs)
    4. End Sub

    Oder direkt mit WithEvents deklarieren, dass empfehl ich immer wenns nicht dynamisch ist
    EventArgs? Verdammt ich habe die Dinger noch nicht kapiert und darum bisher gemieden.

    Bislang mache ich es so:

    VB.NET-Quellcode

    1. Public Event PhaseBoxCreated(ByVal sender As uc_PhaseBox)
    2. Sub OnPhaseBoxCreated(ByVal tmpPhaseBox As uc_PhaseBox)
    3. RaiseEvent PhaseBoxCreated(tmpPhaseBox)
    4. End Sub


    In der entsprechenden Methode meines Controls (derzeit New):

    VB.NET-Quellcode

    1. AddHandler Me.PhaseBoxCreated, AddressOf scls_ControlManager.PhaseBoxAdd
    2. OnPhaseBoxCreated(Me)
    3. RemoveHandler Me.PhaseBoxCreated, AddressOf scls_ControlManager.PhaseBoxAdd


    Edit: Mich graut es schon, wenn ich daran denke das der Kram früher oder später auch noch vererbt werden muss...

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „cl10k“ ()

    Was hast du denn da vor ? Die Events hookst du nicht in der Klasse, dass kommt erst in der Klasse in der sie Instanziert werden. Also in deiner Form. Desweiteren kannst du wenn du eigene EventArgs haben willst einfach n EventHandler(Of T) benutzen und die genutzte Klasse muss nur von EventArgs erben.
    Der Sinn von Events ist die kommunikation zwischen der Instanz und der Instanzierenden Klasse. Also überdenk deine Struktur nochmal
    Hierzu mal VeryBasics Teil "Prinzipieller Aufbau eines Programms / Kommunikation der Objekte"

    Dieser Beitrag wurde bereits 2 mal editiert, zuletzt von „Gonger96“ ()