Von einem Sub im Modul auf gleichlautende Controls in verschiedenen Formen zugreifen

  • VB.NET

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

    Von einem Sub im Modul auf gleichlautende Controls in verschiedenen Formen zugreifen

    Hi,
    ich benutze 2 Forms, in denen stellenweise identische Textboxen sitzen.
    Dafür möchte ich nur eine sub nutzen, die nach Aufruf ein Ergebnis an jeweilige Textbox sendet.
    Ich dachte es mir eigentlich so:

    Code Modul:

    VB.NET-Quellcode

    1. Public Sub VPN_Set(Status As Integer, frm As Form)
    2. frm.Textbox1.text="xyz"


    Um es dann von einer x-beliebigen Form so aufzurufen:

    VB.NET-Quellcode

    1. VPN_Set(Status, AufrufendeForm)


    Aber selbst, wenn ich im Modul noch "frm = Form_Admin" einfüge, sagt er mit dass Textbox1 kein Member von Form wäre.

    Wo sitzt mein Denkfehler?
    Brauch' ich die?
    frm As Form Ist nun mal für den Compiler nur ein leeres Form. Da existieren eben keine TextBoxen. Schick doch lieber die TextBox an die Sub.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    Ok, klingt logisch. Wäre auch ein Lösungsansatz, wird aber ekelig wenn es mehrere Controls sind, die von der Sub angesprochen werden müssen.

    Aber warum geht selbst dies nicht, das würde es ja ein wenig vereinfachen?

    VB.NET-Quellcode

    1. Public Sub VPN_Set(Status As Integer)
    2. Dim frm As Form = Form_Admin
    3. frm.Textbox1.text="xyz"

    Das sollte doch klappen?!?!

    VB.NET-Quellcode

    1. Form_Admin.Textbox1.text="xyz"

    geht ja schließlich auch.
    Brauch' ich die?
    @Hanuta Gib den Formen eine gemeinsame Basisklasse MyFormBase, in der eine entsprechende Prozedur implementiert ist.
    Mit dem Aufruf

    VB.NET-Quellcode

    1. Public Sub VPN_Set(Status As Integer, frm As MyFormBase)
    sollte das gehen.
    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!
    @Hanuta: Das ist ein Upcasting. Du machst aus einem spezifischen Form ein allgemeines Form, weil Du den Typ mit As Form vorgibst. Und ein allgemeines Form kennt keine TextBox1. Die Zusatzinfos von Form_Admin gehen damit erstmal verloren. Du könntest es zwar wieder downcasten: DirectCast(Form, Form_Admin) und so eben an Dein TextBox kommen, aber ich glaube nicht, dass das ein sinnvoller Weg ist. Denn das Downcasting geht eben nur mit einer Formsorte. Und wenn an ein anderes reingereicht wird, kracht es.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    @VaporiZed Und deswegen BaseClass oder Interface.
    @Hanuta Ein Interface ist da einfacher zu implementieren:

    VB.NET-Quellcode

    1. Public Interface Interface1
    2. Sub DoIt(text As String)
    3. End Interface

    VB.NET-Quellcode

    1. Public Class Form2
    2. Implements Interface1
    3. Public Sub DoIt(text As String) Implements Interface1.DoIt
    4. MessageBox.Show("hier")
    5. End Sub
    6. End Class
    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!
    @Hanuta: Sollten die Forms wirklich sehr ähnlich sein, erwäge nur ein Form zu nehmen und es parametergesteuert vor der Anzeige zu modifizieren, statt zwei Forms zu haben.
    Dieser Beitrag wurde bereits 5 mal editiert, zuletzt von „VaporiZed“, mal wieder aus Grammatikgründen.

    Aufgrund spontaner Selbsteintrübung sind all meine Glaskugeln beim Hersteller. Lasst mich daher bitte nicht den Spekulatiusbackmodus wechseln.
    @RodFromGermany Ich fürchte ich bin ein Banause, Deine Tipps sind "Perlen vor die Säue" :D VB habe ich mir selber beigebracht, bis auf guten alten AppleII-Basic Hintergrund weiß ich nichts. Hab grade mal schnell nach "gemeinsamen Basisklassen" gegoogelt, hat mir leider auch nichts gebracht.
    Mit Modulen fange ich grade an, von Klassen weiß ich noch überhaupt nix, das muss wohl mein nächstes Fortbildungsprojekt werden...

    @VaporiZed Du wirst lachen, das hatte ich bis vor 4 Wochen auch so gehabt. Dann habe ich mir Module angeschaut und fand es für mich günstiger, das Projekt zu trennen. Es besteht aus einem Admin-Teil und Clienten, die halt nur einen eingeschränkten Funktionsumfang bekommen.
    Ich muss bei jedem neuen Admin-Button auch direkt im Formload sagen "wenn client, dann button.dispose". Das ist es, was Du meinst?!
    Deshalb war mein Gedanke, die beiden Teile zu trennen. Aber idealerweise nur die Form, nicht die von beiden Teilen benutzten Subs und Functions. Dann kann ich in der Admin-Form rumaasen und experimentieren, und bei den Clients ändert sich nichts.
    Nur muss ich jetzt die benutzten Subs noch doppelt vorhanden haben, das macht das Pflegen unübersichtlich und unnötig kompliziert.
    Daher der Gedanke, die Subs zusammen zu fassen und von beiden Formen aus nutzen zu können.

    Schreit das vielleicht in euren Ohren nach einer anderen Lösung? Rods Ansatz überblicke ich noch nicht richtig, aber wie ich es bis jetzt verstehe, wäre das was im Hinblick auf meine Erklärung?!
    Brauch' ich die?

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

    Hanuta schrieb:

    "gemeinsamen Basisklassen"
    ist da wohl eher der falsche Suchstring.
    Probier mal VB.NET Form Basisklasse
    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!

    Hanuta schrieb:

    Schreit das vielleicht in euren Ohren nach einer anderen Lösung?
    Kann man wohl sagen.
    Mir sieht das aus, als sollten dieselben Daten an verschiedenen Stellen präsentiert werden.
    Für mich schreit das nach Databinding, sogar formübergreifendes Databinding.
    Aber das ist ein grosses Thema, und du müsstest vmtl. deine ganze Projekt-Architektur umbauen.
    Vermutlich gibts bislang auch gar kein Datenmodell, was für Databinding geeignet wäre.
    @RodFromGermany
    Ich glaube zu verstehen... In der Basisklasse stecken meine Subs, die ich dann mit basisklasse.subname(args) von überall aufrufen kann, weil sie mittels inherite überall hin vererbt werden können. Wäre das so richtig ausgedrückt? Falls ja, habe ich das doch auch mit dem Modul erledigt, nur dass mir dort die Kontrolle fehlen würde, von woher die Subs aufgerufen werden. Das wäre aber unwichtig bei mir.
    Würde das denn etwas an meinem Ausgabeproblem der Subs in der gewünschten Form ändern?

    @ErfinderDesRades
    So halb, Daten im Sinne eines DGVs werden bei den Clienten nicht präsentiert. Dort laufen nur Funktionen ab. Zum Beispiel Formularmasken von Drittanbietern vorauszufüllen, also die Kundenadresse einzutragen um die Abläufe zu beschleunigen. Die Clienten holen sich eine Info aus der DB, z.B. "nächste Nachricht an Kunden" beim Mahnwesen. Beim Clienten wird dann die entsprechende Fremdanbieterapp gestartet und die Adresse des Kunden wird automatisch ausgefüllt. Das Ganze funktioniert auch schon recht gut, aber ich komme an einen Punkt wo die Pflege zuviel Zeit braucht und unbedingt die doppelten Funktionen (also beim Clienten und Admin) verschwinden müssen.

    Ein eventueller Umbau des Projekts wäre kein Problem, noch ist es nicht zu groß dafür. Und wenn es mir auf lange Sicht Arbeit spart...
    Brauch' ich die?
    Also ich bastel mir immer ein typisiertes Dataset und lege darin Tabellen an.
    Diese Tabellen können die Daten aufnehmen, die gebraucht werden.
    Wenn Formular-Eintrags-Daten gebraucht werden muss ich eine entsprechende Tabelle anlegen, die Formular-Eintrags-Daten aufnehmen kann (habich allerdings noch nie gemacht.).
    Bei formübergreifendem Databinding gibt es das Dataset als global zugreifbaren Singleton, und jedes Form bindet seine Controls an dieses eine Dataset.
    Binden nun 2 Forms ihre Controls an denselben Datensatz, so zeigen sie auch immer dasselbe an.
    Keine Methode erforderlich, die iwie den Textbox-Text aus einem Form in ein anderes überträgt.

    Ein so ungefähres Beispiel kannman hier gugge
    form-übergreifendes Databinding
    Ja, ich bin ein großer Fan Deiner Videos und des typisierten Datasets. Es hat nur leider einen großen Nachteil für mich.
    Es gibt mehrere Clients, die im schnellen Rhythmus auf die Daten zugreifen. Also müsste ich vor jedem Zugriff das Dataset neu laden, um eventuelle Änderungen auf dem Server mitzubekommen. Widersprich mir jetzt gerne ;)
    Das wiederum dauert zu lange, also bin ich jetzt dazu übergegangen, nur die sich nicht ändernden Daten im Dataset zu halten. Den Rest lade ich über klassisches SQL direkt vom DB-Server, also z.B. ein hässliches cint(SELECT...) für integer. Ich weiß genau, dass sich Dir grad die Fußnägel hochrollen, aber gibt es eine andere Möglichkeit? Wir reden von ca. 20 Clients (also physikalisch vorhandene Rechner) und Daten, die sich zum Teil im Sekundentakt ändern...


    Aber hier geht es ja inzwischen mehr um den Aufbau des ganzen, Sinn oder Unsinn mehrerer Forms, von Rod erwähnte Basisklassen. Alles halt mit dem Ziel einen einfachen, gut zu wartenden Code zu haben. Änderungen an den Anforderungen gibt es alle paar Tage, also möchte ich das möglichst übersichtlich halten.
    Brauch' ich die?

    Hanuta schrieb:

    Den Rest lade ich über klassisches SQL direkt vom DB-Server, also z.B. ein hässliches cint(SELECT...) für integer.
    Hast du das mal iwie gemessen, dass das ein entscheidend schnelleres Vorgehen sei, als mit einem DataAdapter eine DataTable zu befüllen?
    Ein DataAdapter ist ja nix als ein Wrapper um mehrere Commands, darunter ein Select-Command - welches beim Fill Anwendung findet.

    Btw. den Befehl cint(SELECT...) kenne ich nun garnet - ich nehme an, du redest von einem Command-Objekt (wie ich es kenne) - ah - vermutlich von einem DbDataReader, erzeugt mittels cmd.ExecuteReader().



    Was ja letztlich sogar egal ist.
    Selbst wenn du deine Daten mit cint(SELECT...) herbeischaffst, spricht ja nix dagegen (na, doch - die Umständlichkeit), diese Daten dann als Datensatz in einem typDataset abzulegen, woran alle Forms dann ihre Controls binden können.

    Dieser Beitrag wurde bereits 4 mal editiert, zuletzt von „ErfinderDesRades“ ()

    Ich meinte damit einen normalen Select-Befehl, den ich mittels CINT() in eine Integer wandle.
    Und ja, entschieden schneller, wir reden von 1-3 Sekunden, die das TableAdapter.Fill benötigt. Wobei es eine kleine Tabelle ist, 35 Datensätze 'a ca. 40 Felder.
    Ich brauche meist nur 2-3 Werte aus der DB, da finde ich es komfortabler per Select.

    BTW, die Frage an Dich wollte ich schon lange mal loswerden: Ich habe eine Datatable mit 30 Datensätzen. Jetzt möchte ich aus dem Satz mit der ID 5 den Wert der Spalte XY haben.
    Mein Weg:

    VB.NET-Quellcode

    1. For Each rw In DataSet.Table
    2. If rw.ID = 5 then Variable = rw.XY
    3. Next

    Gefällt mir nicht, ist bestimmt ein Ressourcenfresser weil er sich ja durch alle Datensätze quält, zum andern zu lang und hässlich.
    Per SQL sage ich "SELECT XY FROM Table WHERE ID = 5" - feddich
    Geht das nicht auch ähnlich mit dem Dataset? Habe schon herumgebaselt über die Bindingsource, aber führt alles zu nix vernünftigem.
    Ähnliches Beispiel, ich will die Anzahl der Datensätze wissen, wo XY ="abc" ist:
    count "SELECT FROM Table WHERE XY ="abc"
    Wie könnte ich das ohne eine Schleife und extra Counter bei einem Dataset herausfinden?

    Ich weiß ja, dass Du ein Verfechter des typisierten bist, gefällt mir ja eigentlich auch sehr - gäbe es denn einfache Lösungen für meine Beispiele?
    Brauch' ich die?

    Hanuta schrieb:

    Falls ja, habe ich das doch auch mit dem Modul erledigt
    Ein Modul sollte nix von Formen wissen, da kannst Du z.B. in sich geschlossene Berechnungen rein packen, die von mehreren Klassen aus aufgerufen werden müssen.
    Eine Basisklasse ist OOP, das ist der feine Unterschied.
    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!

    Hanuta schrieb:

    Per SQL sage ich "SELECT XY FROM Table WHERE ID = 5" - feddich
    kannst du mir das mal als Code genau zeigen?
    wie gesagt - etwas "per Sql sagen" geht in .Net überhaupt nicht.
    Hingegen in Linq:

    VB.NET-Quellcode

    1. Dim variable = Dataset.typedTable.First(function(rw)rw.ID=5).XY
    wobei es meist günstiger ist, den ganzen Datensatz als Variable zu nehmen:

    VB.NET-Quellcode

    1. Dim myTypedRow = Dataset.typedTable.First(function(rw)rw.ID=5)
    Da haste nun die ganze Row, kannst .XY von abrufen, und alle anneren Werte auch.



    Hanuta schrieb:

    wir reden von 1-3 Sekunden, die das TableAdapter.Fill benötigt. Wobei es eine kleine Tabelle ist, 35 Datensätze 'a ca. 40 Felder.
    Dann ist da was faul.
    sone Micker-Abfrage darf nix dauern.
    Es kann allerdings sein, dass du da gleich ein DGV angebunden hast, und das reorganisiert sich mit jedem zugekommenen Datensatz - sowas versaut gelegentlich die Performance.
    Das hat aber mit dem DB-Zugriff per DataAdapter nix zu tun, sondern ist das DGV miserabel programmiert.



    Hanuta schrieb:

    Gefällt mir nicht, ist bestimmt ein Ressourcenfresser
    Ich glaub, da machste dir falsche Vorstellungen.
    Eine Db-Abfrage ist resourcen-fressend! Das muss ja iwie eine Inter-Prozess-Kommunikation aufgebaut werden, und dann muss der SqlProvider das Sql analysieren, übersetzen, ausführen, eine Ergebnismenge ausliefern (prozessübergreifend!), die muss in Empfang genommen werden, und alle Daten sind nach .Net zu konvertieren, denn die Sql-Daten-Objekte sind zu .Net-Datentypen höchst unterschiedlich konstruiert.

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

    Ähnliches Beispiel, ich will die Anzahl der Datensätze wissen, wo XY ="abc" ist:
    count "SELECT FROM Table WHERE XY ="abc"


    sollte wohl so aussehen

    VB.NET-Quellcode

    1. cmd = Cn.CreateCommand()
    2. cmd.CommandText = "select count(*) from tbl_xyz Where XY='abc';"
    3. Dr = cmd.ExecuteReader()
    4. Dr.Read()
    5. TextBox1.Text = CStr(Dr.Item(0))'dein Ergebnis


    das hier habe ich noch nie gesehen..CINT(Select... ?
    Den Rest lade ich über klassisches SQL direkt vom DB-Server, also z.B. ein hässliches cint(SELECT...) für integer
    Moin zusammen,
    ich bemühe mich ja wirklich, die Begrifflichkeiten richtig zu benutzen. Aber wie gesagt, mir fehlt leider die Vorbildung um manches 100%ig zu formulieren.

    Kasi
    sollte wohl so aussehen:

    Ja, Du hast recht. Ich wollte das Ganze abkürzen und dabei nur zeigen, wie simpel mMn ein Select funktioniert. Obwohl ich auch noch nicht wusste, dass ich innerhalb von SQL das count schon nutzen kann.
    Ich habe mir 2 Functions gebaut, DB_Read, DB_Write. Und ja, innerhalb dieser Functions arbeite ich genau wie Du schriebst.
    Also kann ich DB_Read("SELECT XY FROM Table WHERE ID = " & ID).First für einen einzelnen Wert, bzw. DB_Read("SELECT XY FROM Table WHERE XY = 'abc' ").count benutzen.
    Dies meinte ich auch mit cint(select) - also komplett: CINT(DB_Read("SELECT XY FROM Table WHERE ID = " & ID).First)

    @ErfinderDesRades
    Dein "Dim myTypedRow = Dataset.typedTable.First(function(rw)rw.ID=5)" ist Gold wert, ich stehe auf schöne Einzeiler, thx! Änderungen (myTypedRow.XY = "def") werden dann auch ins Dataset übernommen nehme ich an?!
    Und wie würdest Du ein count realisieren? Geht das auch so elegant?
    Ja, Du hast Recht, es gibt ein angebundenes DGV. Das ist zu Debugzwecken drin, meist versteckt, wird aber erst beim Öffnen des DGVs mittels " TableDataGridView.DataSource = TableBindingSource" befüllt.
    Ähm, um mal sicher zu gehen, dass wir vom Gleichen reden und ich da nichts vermurkst habe:

    Im Form.load:
    Me.Tbl_WarenTableAdapter.Fill(Me.MeinDataSet.tbl_Waren) um das Dataset zu füllen.

    Um das Dataset zu aktualisieren:
    Me.Tbl_WarenTableAdapter.ClearBeforeFill = True
    Me.Tbl_WarenTableAdapter.Fill(Me.MeinDataSet.tbl_Waren)

    Bei Bedarf die Änderungen auf dem DB-Server speichern:
    Me.Validate()
    Me.WarenBindingSource.EndEdit()
    Me.WarenTableAdapter.Update(MeinDataSet.tbl_Waren)

    Gibt es eigentlich eine Möglichkeit, einen Clienten erkennen zu lassen, ob sich das Dataset, was er geladen hat, auf dem Server in der Zwischenzeit geändert wurde? Damit er es automatisch SOFORT neu laden kann?
    Ich experimentiere grade, um Zeitvergleiche zu haben. Da stolpere ich über etwas längst verdrängtes: Parallelitätsverletzungen...

    VB.NET-Quellcode

    1. Dim st As New Stopwatch
    2. st.Start()
    3. MeinDataSet.MyTable.First(Function(rw) rw.ID = 5).XY = 30
    4. Me.Validate()
    5. Me.MyTableBindingSource.EndEdit()
    6. Me.MyTableTableAdapter.Update(MeinDataSet.MyTable)
    7. Trace.WriteLine("Zeit 1: " & st.ElapsedMilliseconds) Ergebnis: 21ms
    8. Me.MyTableTableAdapter.ClearBeforeFill = True
    9. Me.MyTableTableAdapter.Fill(Me.MeinDataSet.MyTable)
    10. Trace.WriteLine("Zeit 2: " & st.ElapsedMilliseconds) Ergebnis: Differenz für diesen Step 60ms
    11. DB_Write("Update MyTable SET XY = 40 WHERE ID = 5")
    12. Trace.WriteLine("Zeit 3: " & st.ElapsedMilliseconds) Ergebnis: Differenz für diesen Step 6ms


    Unerwarteter Anwendungsfehler:
    Parallelitätsverletzung: Der UpdateCommand hat sich auf 0 der erwarteten 1 Datensätze ausgewirkt.
    Das sollte doch daher kommen, dass in der Zwischenzeit von außerhalb des lokalen Datasets ein Wert in der DB geändert wurde durch mein SQL-Update.
    Dies passiert dann aber auch, wenn einer der Clienten eine Änderung in der DB einpflegt. D.h., ich müsste quasi, bevor ich einen Wert speichere, erstmal das Dataset aktualisieren. Wobei meine Änderung dann ja flöten geht...
    Jetzt erinnere ich mich, das war der letzte Punkt, warum ich von Datasets zum schreiben abgerückt bin...


    @RodFromGermany
    Du meinst also damit, dass aus einem Modul maximal ein Rückgabewert aus einer Function kommen sollte, aber niemals ein Zugriff auf irgendeine Form?
    Wie wäre dann der Aufbau meines Projektes?

    - Komplett auf Module verzichten
    - Basisklasse, in der alle Subs und Funcs liegen, die auch die Controls der Formen ansprechen
    - dann natürlich die 2 Formen, von der aber (abhängig von der Benutzeranmeldung) entweder der Client ODER der Admin gestartet wird. Also, es werden niemals BEIDE Formen gleichzeitig offen sein.
    - Manche Subs müssen leichte Unterschiede je nach geöffneter Form aufweisen. DIe auch in die Basisklasse packen, und dort dann ein "if Form_Admin.visible then dieses else jenes"?


    Puh, ich danke euch für das reichhaltige Feedback! Ich bekomme nur langsam ein schlechtes Gewissen, weil das hier alles so anwächst und ich langsam alle großen Köpfe dieses Forums hier festhalte :huh:
    Brauch' ich die?
    @Hanuta Bereinige Dein Projekt (obj, bin, vs-Ordner weg), zippe das ganze und hänge das an einen Post:
    Erweiterte Antwort => Dateianhänge => Hochladen.
    Dann kannst Du mit konstruktiver Kritik rechnen. :D
    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!