Neuerstellen eines umfangreichen Projektes - aus schlechtem Code mach guten Code

  • VB.NET

Es gibt 213 Antworten in diesem Thema. Der letzte Beitrag () ist von DerSmurf.

    Ganz eindeutig: ich kann es nicht erklären. Das Instanziieren mittels Private WithEvents scheint nicht mit der Handles-Klausel für Shared Events kompatibel zu sein.
    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.

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

    OK. Nehme ich einfach so hin.
    Da ja jetzt aber alles (mein Code) in Ordnung zu sein scheint, würde ich gerne zum nächsten (und vorerst letzten Punkt) kommen.
    Das Abfangen von Tasten Ich würde gerne folgende Tasten abfangen:
    Strg + K - zum kopieren eines Artikels
    Escape - zum löschen der Suche
    Backspace - löschen des letzten Zeichens in der Suche TextBox (wenn diese keine Fokus hat)
    Enter - zum öffnen eines Adressbucheintrags
    Entfernen - zum löschen eines Adressbucheintrags
    alle anderen "normalen" Tasten - zum Beschreiben der Suche TextBox (auch wenn diese keinen Fokus hat)

    Hier würde ich in der Hauptform ein Event erstellen.
    Im KeyPress Event (bekomme ich hier Strg + K und Enter abgefragt?) würde ich das Event dann raisen. Abbonieren tut das Event dann die UCAdressen (und die noch folgenden UserControls), da ja hier die Aktionen ausgeführt werden. Dem Event gebe ich dann den E.KeyChar mit, und reagiere entsprechend. Ist das die richtige Vorgehensweise?
    Und brauche ich dann ein Event auf der Hauptform für jede Usercontrol?
    Denn es müssen ja alle UserControls das Event abbonieren, aber es soll nur das aktive Usercontrol (könnte ich auf der MainForm über die TabPages abfragen) den Code ausführen.
    Also wenn es z.B. UCAdressen und UCArtikel gibt, soll der Tastendruck natürlich nur auf der gerade angezeigten UC ausgeführt werden.

    VaporiZed schrieb:

    Ganz eindeutig: ich kann es nicht erklären. Das Instanziieren mittels Private WithEvents scheint nicht mit der Handles-Klausel für Shared Events kompatibel zu sein.
    Ich weiss nicht, worauf konkret sich das bezieht, aber wenn der EventHandler Shared ist, dann muss die WithEvents-Variable natürlich auch Shared sein.
    Mein Fehler. Es fehlte das New: Private WithEvents clsEmailSenden As New EmailSenden. Dann werden auch die EventHandler angefahren.
    Handles geht (wohl) nur mit Klasseninstanzen und deren Events. Auch wenn es eben aufgrund der Shared-Geschichten unnötig erscheint, braucht der Compiler die Klasseninstanz wohl, um die Verbindungen zwischen Events und den EventHandlern im Betrieb sauber an- und am Ende wieder abmelden zu können.

    Bezüglich der Tastengeschichte: Probier es aus. Da ich sowas grundsätzlich nicht mache, habe ich auch keine Erfahrung damit. Ob allerdings das Hauptform die Tastenanschläge mitbekommt und weiterleitet oder besser die UCs die Tastenanschläge registieren und das HauptForm weiterleiten, solltest Du ausprobieren. Vielleicht ist Plan B der bessere, dann kannst Du Dir das welches-UC-hat-den-Fokus-Heraussuchen sparen.
    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.

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

    VaporiZed schrieb:

    oder besser die UCs die Tastenanschläge registieren und das HauptForm weiterleite

    Wenn das UC die Tastenanschläge mitbekommt, muss ich nichts ans Hauptform weiterleiten.
    Der Code, der im Anschluss auf den Tastendruck folgt, wird immer auf der entsprechenden Userform ausgeführt.
    Aber das UserControl hat keine KeyPreview Eigenschaft. Also die StandartEvents wie
    Private Sub UCAdressen_KeyPress(sender As Object, e As KeyPressEventArgs) Handles Me.KeyPress
    Ich habe im Netz jetzt folgendes gefunden, kann aber natürlich nicht beurteilen, ob das gut ist, oder nicht.
    Funktionieren tut das ganze zumindest.

    VB.NET-Quellcode

    1. Protected Overrides Function ProcessCmdKey(ByRef msg As Message, ByVal keyData As Keys) As Boolean
    2. If keyData = Keys.D Then
    3. MessageBox.Show("Taste d gedrückt")
    4. Return True
    5. Else
    6. Return MyBase.ProcessCmdKey(msg, keyData)
    7. End If
    8. End Function


    Edit: habs jetzt einfach mal zuende geschrieben. Wie findet ihr das?

    VB.NET-Quellcode

    1. Protected Overrides Function ProcessCmdKey(ByRef msg As Message, ByVal keyData As Keys) As Boolean
    2. Select Case keyData
    3. Case = Keys.Control Or Keys.N : CreateNewAddress()
    4. Case = Keys.Control Or Keys.K : CopyAddress()
    5. Case = Keys.Enter : EditAddress()
    6. Case = Keys.Delete : If Not TBAdressenDurchsuchen.Focused Then DeleteAddress()
    7. Case = Keys.Escape : ClearAddressSearch()
    8. Case = Keys.Control Or Keys.C : CopyFullAddressToClipboard()
    9. Case = Keys.Control Or Keys.E : ShowMailForm()
    10. Case = Keys.Back : RemoveLastCharFromSearchTB()
    11. Case Else
    12. If TBAdressenDurchsuchen.Focused Then Return MyBase.ProcessCmdKey(msg, keyData)
    13. Dim PressedKey = IsSearchableKeyPressed(keyData)
    14. If PressedKey.SearchableKeyPressed Then
    15. TBAdressenDurchsuchen.Text &= PressedKey.PressedKey
    16. Else
    17. Return MyBase.ProcessCmdKey(msg, keyData)
    18. End If
    19. End Select
    20. End Function
    21. Private Sub RemoveLastCharFromSearchTB()
    22. If TBAdressenDurchsuchen.Focused Then Return
    23. Dim SearchString = TBAdressenDurchsuchen.Text
    24. If SearchString.Length > 0 Then TBAdressenDurchsuchen.Text = SearchString.Substring(0, SearchString.Length - 1)
    25. End Sub
    26. Public Function IsSearchableKeyPressed(keydata As Keys) As (SearchableKeyPressed As Boolean, PressedKey As String)
    27. If keydata.IsBetween(Keys.A, Keys.Z) Then Return (True, keydata.ToString.ToLower)
    28. If keydata.IsBetween(Keys.D0, Keys.D9) Then Return (True, keydata.ToString.Substring(keydata.ToString.Length - 1))
    29. If keydata.IsBetween(Keys.NumPad0, Keys.NumPad9) Then Return (True, keydata.ToString.Substring(keydata.ToString.Length - 1))
    30. Select Case keydata
    31. Case Keys.Oem1 : Return (True, "ü")
    32. Case Keys.Oem7 : Return (True, "ä")
    33. Case Keys.Oemtilde : Return (True, "ö")
    34. Case Keys.Space : Return (True, " ")
    35. End Select
    36. Return (False, " ")
    37. End Function

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

    Wenn's klappt, dann mach.
    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.
    Nuja, nö, eigentlich wenig auf Anhieb zu sagen. Ok, das Case = weiß ich jetzt nicht, ginge bestimmt auch ohne =.
    RemoveLastCharFromSearchTB - ja gut, Abkürzungen eben.
    Dim PressedKey = IsSearchableKeyPressed(keyData): da würd ich PressedKey umbenennen in … PressedKeyData? PressedKeyDataPack? Steckt ja mehr drin als nur die gedrückte Taste.
    Und wie Du bei Zeile#35-#37 auf die Werte kommst, weiß ich nicht. Aber wenn es klappt …
    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 schrieb:

    Und wie Du bei Zeile#35-#37 auf die Werte kommst

    Ich habe mir mit keydata.tostring ausgeben lassen, welcher Tastendruck da erkannt wird.
    Und das waren eben diese drei.

    Habe nun die Solution mit komplettem Funktionsumfang nochmal hochgeladen.
    Ich wäre dir sehr dankbar, wenn du nochmal rüberschauen könntest, ob du noch was schlechtes / unelegantes findest.
    Wenn nicht, habe ich ja eine sehr gute Vorlage, um die anderen Funktionen hinzuzufügen. (ohne, dass ich permanent nerven muss :o)
    Dateien
    • Theo.zip

      (507,32 kB, 33 mal heruntergeladen, zuletzt: )

    VB.NET-Quellcode

    1. Public Function GetDGVColumnWidthsAsSaveableString(DGV As DataGridView) As String
    2. Dim DGVColwidths = ""
    3. For i = 0 To DGV.Columns.Count - 2
    4. DGVColwidths = DGVColwidths & DGV.Columns.Item(i).Width & ";"
    5. Next
    6. Return DGVColwidths

    ->

    VB.NET-Quellcode

    1. Return String.Join(";", DGV.Columns.Cast(Of DataGridViewColumn).Take(DGV.Columns.Count - 1).Select(Function(x) x.Width.ToString))


    Die Const ColumnThatContainsTelNumber sollte m.E. nicht in der Sub sein. Da kann sie auch ne normale Variable sein. Ne Konstante würd ich klassenweit ganz vorn in der Datei setzen, damit sie bei Änderung auch schnell auffindbar ist.

    Not (_Recipient.Contains("@"): ein guter Grund eine Extensions namens NotContains zu generieren - als Übung zum Umgang mit denen.
    If Not Settings.IsNotEmpty Then Return: ein guter Grund eine Extensions namens IsEmpty zu generieren

    VB.NET-Quellcode

    1. Public Property GetAttachments As List(Of String)
    2. Get
    3. Return _Attachments
    4. End Get
    5. Set
    6. _Attachments = Value
    7. End Set
    8. End Property

    Also ne Property mit Getter und Setter GetAttachments zu nennen, ist schon arg irreführend.
    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. Alles verstanden und geändert.
    Dann fange ich jetzt an die nächsten Module einzubauen.
    Macht es mehr Sinn, das ganze dann nach und nach hier vorzustellen (und würdest du das überhaupt machen?), oder ist es besser das ganze zu Ende zu bringen und hier einmal als gesamtes Projekt vorzustellen?

    Edit: Bei der NotContains Extension habe ich mir das leben einfach gemacht:

    Visual Basic-Quellcode

    1. <Extension()>
    2. Public Function NotContains(Text As String, ContainsString As String) As Boolean
    3. Return Not Text.Contains(ContainsString)
    4. End Function

    Aber ich denke das geht so in Ordnung, denn letztlich geht es ja bei der Extension darum, dass der Aufruf einfach zu verstehen ist:

    Visual Basic-Quellcode

    1. If _Recipient.NotContains("@") OrElse _Recipient.NotContains(".") Then

    Dieser Beitrag wurde bereits 3 mal editiert, zuletzt von „DerSmurf“ ()

    Naja, die Frage ist, was Du Dir davon versprichst. Ich glaube, dass Du langsam ein gutes Gefühl dafür bekommen hast, wie manche Sachen gehandhabt werden und wie nicht. Darauf aufbauend kannst Du Dein Projekt eigentlich fortsetzen und Dich melden, wenn Du nicht weiterkommst. Aber das dann am Ende zu posten? Wird wohl keiner so richtig n Blick draufwerfen. Dein Programm ist (wie auch meine oder die von anderen) sehr spezifisch. Das wird wohl keiner für sich nutzen. Und ob man für ne Projektanalyse sich da jetzt Dein Projekt schnappt, sich den Code reinzieht und dann versucht sich reinzufitzeln, um den Code nachzuvollziehen, glaub ich auch nicht. Da haben wir doch alle genügend mit unseren eigenen Projekten zu kämpfen. Ich schau natürlich gern weiter drüber, so isses nicht. Aber man braucht doch viel Hirnkapazität, um sich da in fremde Projekte reindenken zu können.
    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.
    Alles klar. Dann machen wir das so. Aber ich denke ich werde jetzt erstmal ein bisschen brauchen Hast also erstmal Ruhe vor mir :o)
    Und natürlich schonmal vorab (und für das bisher passierte), vielen vielen Dank an dich!

    Und eine Frage habe ich noch.Es zieht sich so ein hellgrauer Balken um das Programm (auch in der hochgeladenen Demo). Am besten sieht man ihn zwischen MenuStrip und TabControl. Aber er scheint einmal ringsherum zu gehen.Den bekomme ich beim besten Willen nicht weg.
    Bilder
    • Unbenannt.png

      56,11 kB, 1.920×1.080, 43 mal angesehen
    Das ist Teil des TabControls, das bekommst Du nicht weg. Ein Nachteil des TabControls: mit Strg+Tab/Strg+BildAuf/Strg+BildAb kann man trotzdem durchnavigieren :/
    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.
    So mein lieber. Wegen der Hitze fehlte mir etwas Motivation (ich bin -wAs das angeht - ein kleines Mimöschen), aber ich habe nun das nächste "Modul" soweit fertig.
    Die UCKundenbestellung ist neu dazu gekommen.
    Auf dieser UC habe ich allerdings ein Problem, welches ich nicht gelöst bekomme.
    Per Designer ist die BSKundenbestellung ans DGV gebunden. Es gibt noch eine weitere BS - die BSKundenbestellungFiltered.
    Mit dieser Filtered BS möchte ich gerne nur Kundenbestellungen eines bestimmten Lieferanten (steuerbar über CBLieferant / CBalleZeigen) anzeigen.
    Dazu müsste ich aber der BSKundenbestellungFilteres als DataMember die FK_Lieferant_Kundenbestellung zuweisen, damit das automatisch passiert.
    Diese ist aber mit der DataSource BSMain nicht auswählbar.

    Außerdem nutze ich immer wieder:

    VB.NET-Quellcode

    1. Dim Source = DirectCast(DGVKundenbestellung.DataSource, BindingSource)

    um die gerade "aktive" BS anhand des DGV auszulesen. Geht das in Ordnung?
    Oder erstelle ich lieber eine Boolean Eigenschaft der UC wie z.B. FilteredBSActivated, um dann anhand dieser die benötigte BS auszuwählen?
    Also

    VB.NET-Quellcode

    1. Private Sub EditCustomerOrder()
    2. Dim Source = DirectCast(DGVKundenbestellung.DataSource, BindingSource)
    3. If Source.Current Is Nothing Then Return
    4. Source.EditCurrent(Of dlgKundenbestellungBearbeiten)
    5. End Sub

    oder

    VB.NET-Quellcode

    1. Private Sub EditCustomerOrder()
    2. Dim Source As BindingSource
    3. If FilteredBSActivated Then
    4. Source = BSKundenbestellungFiltered
    5. Else
    6. Source = BSKundenbestellung
    7. End If
    8. If Source.Current Is Nothing Then Return
    9. Source.EditCurrent(Of dlgKundenbestellungBearbeiten)
    10. End Sub


    Edit: Ich habe gerade noch einmal neu hochgeladen. Habe vergessen die TODOS abzuarbeiten. Es hat sich aber nicht weltbewegend was verändert (für den einen ,der schon runtergeladen hat).

    Aber ein Problem habe ich noch:(evtl. an @ErfinderDesRades - da ich ein Problem mit seinen Helpers habe)
    Es geht um die dlgKundenbestellungBearbeiten. Welche ich mittels Editcurrent(of ...), bzw. EditNew(of ...) aufrufe.
    Auf dem dlgKundenbestellungBearbeiten verwende ich einen DateTimePicker und zwei NullableDateTimePicker.
    Der erste DTP (DTPDatumKundenBestellt) wird im Form Load Event mit dem heutigen Datum gefüllt, wenn noch kein Datum gesetzt ist:

    VB.NET-Quellcode

    1. If SelectedCustomerOrder.IsDatumKundenbestelltNull Then DTPDatumKundenBestellt.Value = System.DateTime.Now

    Hier ist zunächst die Frage, kann ich auch das heutige Datum als StandartValue im DataTable Designer festlegen? - das wäre natürlich die Beste Lösung.

    Mit meiner jetzigen Lösung ist es so, dass das gesetzte Datum nicht gespeichert wird. Der Wert bleibt DBNull.
    Um das Datum in die Datarow zu speichern, muss ich den DTP einmal anklicken.
    Wie kann ich das lösen?

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

    Da keine Pseudodaten dabei sind, werd ich mich jetzt nicht reinfuchsen, wie ich welche erzeuge, um zu testen, was Dein Plan mit den BindingSources genau ist. Aber Du kannst ja - zumindest, um an die gewünschte Tabellenbeziehung zu kommen - die DataSource der BSFiltered mit BsLieferant besetzen und dann DataMember den gewünschten FK.

    Ich würd bzgl. des 2. Problems mit Deinem Alternativvorschlag arbeiten. Da die DataSource zu casten ist für den Leser nicht ersichtlich. Das FilteredBSActivated-Flag ist da klarer.
    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 schrieb:

    Da keine Pseudodaten dabei sind

    Sorry! Hier hängt ein neuer Upload dran, da gibt es ein paar Kundenbestellungen und zwei Lieferanten. Es kann sofort getestet werden. (Die Adressen und Emailadressen habe ich leer gelassen - hiermit waren wir ja fertig)
    Mein Problem hast du aber trotzdem gelöst :o) Ich denke soweit, sollte es diesmal auch weniger zu meckern geben, als bei meinen Adressen.
    Also es wäre wieder ein Traum, wenn du rüber schaust und mir sagst, was ich schlecht gemacht habe.

    Bei zwei Dingen brauche ich aber auch Hilfe.
    1. auf Kundenbestellungen (der pinke Einkaufskorb mit dem Haken drauf im MenuStrip). Hier den letzten auswählen (Artikel: Schnitzel).
    Dann oben (überm DGV) in der CBLieferant den Lieferant NO1 auswählen und im DGV einen anderen Eintrag auswählen.
    Nun springt beim letzten Eintrag der Lieferant um auf Lieferant NO1.
    Was habe ich hier falsch eingestellt? (Edit: Da ich deine Glaskugel ja schon mit meinem letzten Post ein wenig gestresst habe - die Lieferantenzuordnung soll sich nach dieser Klickreihenfolge in der Kundenbestellung nicht ändern.
    Änderbar soll sie nur über den zugehörigen Dialog sein.)

    2. Die Function CheckIfSupplierExists. Sie kam in gleicher Form in der dlgLieferantBearbeiten und in der dlgKundenbestellungenBearbeiten vor.
    Ich glaube, dass diese Function ins DataSet gehört (ich meine mich zu erinnern, dass @ErfinderDesRades mir mal zu so etwas in einem anderen Thread geraten hat.
    Da ja eben das Dts nicht nur eine stumpfe Datenbank ist (und sein sollte), sondern auch BusinessLogik übernehmen kann (und sollte).
    Da macht es ja Sinn, dass die Table "Lieferant" auch prüfen kann ob in ihr ein Wert schon vorkommt.
    Das habe ich gemacht. Ist das so OK?

    VB.NET-Quellcode

    1. Partial Class DtsDaten
    2. Partial Public Class LieferantDataTable
    3. Public Function CheckIfSupplierExists(SupplierName As String) As Boolean
    4. If Me.Rows.Count < 1 Then Return False
    5. Dim FoundSupplier = Me.FirstOrDefault(Function(z) z.Name = SupplierName)
    6. If FoundSupplier IsNot Nothing Then Return True
    7. Return False
    8. End Function
    9. End Class
    10. End Class

    und die Aufrufe dann entsprechend an beiden Stellen gleich:

    VB.NET-Quellcode

    1. If CreateNewSupplier AndAlso DtsDaten.Lieferant.CheckIfSupplierExists(SupplierName) Then

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

    1. ist einfach: Du hast in der ComboBox bei ausgewählter Wert BSKundenbestellung - LieferantID ausgewählt. Wenn Du den ComboBoxeintrag änderst, ändert sich die zugehörige ID. Das Teil ist nicht ReadOnly, sondern die gemachten ComboBox-Einstellungen snd nun eben so, dass man mithilfe der ComboBox die LieferantenID der gewählten Kundenbestellzeile ändern kann. Du hast also ein Problem mit dem von Mikrosoft gewünschten Verhalten. Wenn das nicht so sein soll, setz die ComboBox (insofern sie dann noch Sinn ergibt) bei Enabled auf False. Alternativ: geh in den ComboBox-Properties bei DataBinding auf Erweitert […], dann auf SelectedValue und wähl dann rechts bei bei Datenquellen-Aktualisierungsmodus Never an. Ist dann nur komisch, weil der User glaubt, mithilfe der ComboBox was verändern zu können, aber es gar nicht kann.

    Oder mach die Anzeige per Code. Aber das nur als Notlösung.

    2. joa, geht schon. Der Code kann aber einfacher gemacht werden:

    VB.NET-Quellcode

    1. Public Function SupplierExists(SupplierName As String) As Boolean
    2. Return Me.Any(Function(z) z.Name = SupplierName)
    3. End Function


    Beachte den geänderten Methodennamen. Es ist keine von der Wortwahl Anweisung mehr (was für eine Sub sprechen würde), sondern eher eine Frage (und damit besser als Function zu erkennen). So fänd ich es stimmiger, wenn ich lese: If CreateNewSupplier AndAlso DtsDaten.Lieferant.SupplierExists(SupplierName) Then
    Aber: Dein Code, Deine Regeln.
    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.
    Ich habe nun das Datenquellen Aktualisierungintervall auf Never gestellt. Das "Problem" ist damit gelöst.
    Aber findest du es nicht erkennbar, dass mit dieser Combobox die Anzeige auf Lieferanten beschränkt werden kann? Was müsste ich ändern, damit es erkennbar ist?

    2. Find ich gut, hab ich einfach so übernommen.

    In der Zwischenzeit habe ich noch die UCSonderpreise hinzugefügt.

    Wenn du die Zeit hättest (natürlich ohne Hetzte), mal die UCKundenbestellung und UCSonderpreis, sowie die zugehörigen Dialoge zu überfliegen, wäre das ein Traum.
    Dateien
    • Theo.zip

      (793,32 kB, 35 mal heruntergeladen, zuletzt: )