typisierte BindingSource

    • VB.NET
    • .NET (FX) 4.5–4.8

    Es gibt 3 Antworten in diesem Thema. Der letzte Beitrag () ist von VaporiZed.

      typisierte BindingSource

      Ich hatte vor ein paar Monaten schon erste Schritte bzgl. einer typisierten BindingSource (im weiteren tBS) gemacht. Da diese sich bisher ganz gut bewährt hat, hier das, was ich mir bis dato zusammengeschustert habe.
      Hauptdeklaration

      VB.NET-Quellcode

      1. '''<summary>eine typisierte BindingSource</summary>
      2. '''<typeparam name="T">der übergebende Datentyp, der bei New hinterlegt, bei Add benötigt und bei Current zurückgegeben wird</typeparam>
      3. '''<remarks>Um dieses Control auf einem Form zu verwenden, muss von dieser einfach geerbt werden, z.B.:
      4. '''<code>
      5. '''Public Class MyTypedBindingSource : Inherits TypedBindingSource(Of MyOwnClass) : End Class
      6. '''</code>
      7. ''' </remarks>
      8. Public MustInherit Class TypedBindingSource(Of T) : Inherits BindingSource
      9. '''<summary>erstellt eine typisierte BindingSource für den Typ T</summary>
      10. Protected Sub New(Components As ComponentModel.IContainer)
      11. DataSource = GetType(T)
      12. If Components IsNot Nothing Then Components.Add(Me)
      13. End Sub
      14. End Class


      API

      VB.NET-Quellcode

      1. Partial Class TypedBindingSource(Of T)
      2. '''<summary>fügt der Auflistung ein neues Element vom Typ T hinzu</summary>
      3. '''<param name="NewItem">das hinzuzufügende Item</param>
      4. Public Shadows Sub Add(NewItem As T)
      5. MyBase.Add(NewItem)
      6. End Sub
      7. '''<summary>entfernt das momentan ausgewählte Element der Auflistung; danach wird ein BindingReset für das GUI durchgeführt; wenn die Auflistung leer ist, passiert nichts</summary>
      8. Public Shadows Sub RemoveCurrent()
      9. If HasNoCurrent Then Return
      10. MyBase.RemoveCurrent()
      11. ResetBindings(False)
      12. End Sub
      13. '''<summary>ersetzt das ausgewählte Element durch ein anderes; danach wird ein Einzel-BindingReset für das GUI durchgeführt</summary>
      14. '''<param name="Replacement">das Element, welches an der ausgewählten Position den Platz einnehmen soll</param>
      15. Public Sub ReplaceCurrentWith(Replacement As T)
      16. If HasNoCurrent Then Return
      17. MyBase.Item(Position) = Replacement
      18. ResetCurrentItem()
      19. End Sub
      20. '''<summary>fügt der Auflistung die Elemente einer Liste hinzu; danach wird ein BindingReset für das GUI durchgeführt</summary>
      21. '''<param name="List">die Liste der Elemente, die hinzugefügt werden soll</param>
      22. Public Sub AddRange(List As IEnumerable(Of T))
      23. List.ForEach(Sub(x) Add(x))
      24. ResetBindings(False)
      25. End Sub
      26. '''<summary>ersetzt die Auflistung durch eine andere; danach wird ein BindingReset für das GUI durchgeführt</summary>
      27. '''<param name="List">die Liste der Elemente, die danach die TypedBindingSource-Auflistung darstellt</param>
      28. Public Sub ReplaceListBy(List As IEnumerable(Of T))
      29. Uncouple()
      30. DataSource = List
      31. Couple()
      32. End Sub
      33. '''<summary>ersetzt die Auflistungselemente durch andere; danach wird ein BindingReset für das GUI durchgeführt</summary>
      34. '''<param name="List">die Liste der Elemente, die sich danach in der Auflistung befinden</param>
      35. Public Sub ReplaceListContentBy(List As IEnumerable(Of T))
      36. Clear()
      37. AddRange(List)
      38. End Sub
      39. End Class


      InternalFunctions

      VB.NET-Quellcode

      1. Partial Class TypedBindingSource(Of T)
      2. Private Function IndexIsValid(Index As Integer) As Boolean
      3. Return Index.IsBetween(0, Count - 1) AndAlso MyBase.Item(Index) IsNot Nothing
      4. End Function
      5. End Class


      Operations

      VB.NET-Quellcode

      1. Partial Class TypedBindingSource(Of T)
      2. Private Sub Uncouple()
      3. RaiseListChangedEvents = False
      4. SuspendBinding()
      5. End Sub
      6. Private Sub Couple()
      7. ResumeBinding()
      8. RaiseListChangedEvents = True
      9. ResetBindings(False)
      10. End Sub
      11. End Class


      Properties

      VB.NET-Quellcode

      1. Partial Class TypedBindingSource(Of T)
      2. '''<summary>ruft das Element an der angegebenen Position ab oder legt dieses fest; nach der Festlegung wird ein BindingReset für das GUI durchgeführt</summary>
      3. ''' <param name="Index">die Position innerhalb der Liste</param>
      4. ''' <returns>das Element an der angegebenen Position</returns>
      5. Default Public Shadows Property Item(Index As Integer) As T
      6. Get
      7. If Not IndexIsValid(Index) Then Return Nothing
      8. Return DirectCast(MyBase.Item(Index), T)
      9. End Get
      10. Set
      11. If Not IndexIsValid(Index) Then Return
      12. MyBase.Item(Index) = Value
      13. ResetBindings(False)
      14. End Set
      15. End Property
      16. '''<summary>ruft das aktuell ausgewählte Item ab oder legt dieses fest</summary>
      17. Public Shadows Property Current As T
      18. Get
      19. Return DirectCast(MyBase.Current, T)
      20. End Get
      21. Set
      22. If IsBindingSuspended OrElse HasNoCurrent OrElse Value.NotExists Then Return
      23. For i = 0 To Count - 1
      24. If Item(i).Equals(Value) Then Position = i : Exit For
      25. Next
      26. End Set
      27. End Property
      28. '''<summary>gibt wieder, ob die TypedBindingSource befüllt ist</summary>
      29. Public ReadOnly Property HasCurrent() As Boolean
      30. Get
      31. Return Position > -1 AndAlso Not Current.NotExists
      32. End Get
      33. End Property
      34. '''<summary>gibt wieder, ob die TypedBindingSource leer ist</summary>
      35. Public ReadOnly Property HasNoCurrent() As Boolean
      36. Get
      37. Return Not HasCurrent
      38. End Get
      39. End Property
      40. '''<summary>gibt wieder, ob die aktuelle Position 0 ist</summary>
      41. Public ReadOnly Property IsAtListStart() As Boolean
      42. Get
      43. Return Position = 0
      44. End Get
      45. End Property
      46. '''<summary>gibt wieder, ob die aktuelle Position am Ende der Liste ist</summary>
      47. Public ReadOnly Property IsAtListEnd() As Boolean
      48. Get
      49. Return Position = List.Count - 1
      50. End Get
      51. End Property
      52. End Class



      Extensions, die mir auch in anderen Projekten das Leben leichter machen

      VB.NET-Quellcode

      1. <Extension> Public Sub ForEach(Of T)(IEnumerable As IEnumerable(Of T), Action As Action(Of T))
      2. For Each Element In IEnumerable
      3. Action(Element)
      4. Next
      5. End Sub
      6. <Extension> Public Function NotExists([Object] As Object) As Boolean
      7. Return [Object] Is Nothing
      8. End Function
      9. <Extension> Public Function IsBetween(Of T As IComparable)(Thing As T, LowerBoundary As T, UpperBoundary As T) As Boolean
      10. Return Thing.CompareTo(LowerBoundary) >= 0 AndAlso Thing.CompareTo(UpperBoundary) <= 0
      11. End Function



      Einsatzbeispiel:

      Tower-Klasse

      VB.NET-Quellcode

      1. Public Class Tower
      2. Property Damage As Integer
      3. Property RangeInPixels As Integer
      4. Property HitPoints As Integer
      5. End Class


      VB.NET-Quellcode

      1. Public Class BsFileInfos : Inherits TypedBindingSource(Of IO.FileInfo)
      2. Sub New(Components As ComponentModel.IContainer)
      3. MyBase.New(Components)
      4. End Sub
      5. End Class
      6. Public Class BsTowers : Inherits TypedBindingSource(Of Tower)
      7. Sub New(Components As ComponentModel.IContainer)
      8. MyBase.New(Components)
      9. End Sub
      10. End Class


      Mehr an Code ist es im jeweiligen Einsatzprojekt erstmal nicht. Daher kann man beim Einsatz mehrerer tBSs diese auch alle in eine eigene Datei untereinander schreiben, eine tBS pro Zeile.
      Das Projekt kompiliert man und schon tauchen die tBSs in der Toolbox auf und sind einsatzbereit:


      Nun kann man diese zzgl. einem DGV auch gleich auf's Form ziehen, das DGV an eine tBS koppeln und schon sieht man (im Falle der BsFileInfos-Instanz) eine leere FileInfo-Tabelle:


      Der nächste Schritt - wie bei einer normalen BS (die ich im weiteren uBS für untypisierte BindingSource abkürze) - ist die Datenzuordnung:

      VB.NET-Quellcode

      1. BsFileInfos1.DataSource = New IO.DirectoryInfo(Verzeichnispfad).GetFiles.ToList 'ToList, damit man auch mit RemoveCurrent arbeiten kann; ohne geht RemoveCurrent auch bei einer uBS nicht
      2. BsTowers1.Add(New Tower With {.Damage = 80, .HitPoints = 100, .RangeInPixels = 20})


      bei der zweiten Zeile sieht man schon den ersten Vorteil der tBS:

      Man muss ein Objekt vom Typ Tower einfügen. Alles andere wird abgelehnt.
      Bei einer uBS würde man erst zur Laufzeit eine System.InvalidOperationException bekommen.

      Nächster Vorteil: Current
      Will man den aktuell ausgewählten BS-Eintrag im verwenden, muss man bei einer uBS erstmal casten, bei einer tBS natürlich nicht, denn Current ist ja auch schon vom angegebenen Typ:

      VB.NET-Quellcode

      1. MessageBox.Show(DirectCast(FileInfoBindingSource.Current, IO.FileInfo).Name) 'vergessen zu erwähnen: dies ist eine uBS mit IO.FileInfo als hinterlegter Datentyp
      2. MessageBox.Show(BsFileInfos1.Current.Name)


      Die anderen Kleinigkeiten können direkt aus der Codedokumentation oder m.E. gar aus dem Code selbst entnommen werden.
      Das ganze kann man sicherlich auch für tDS-gekoppelte BindingSources weiterspinnen, um sich den Doppelcast von Object->DataRowView->typisierte DataRow zu ersparen. Aber das mach ich vielleicht später.

      ##########

      In der Property HasCurrent() war ein Fehler. Es darf narürlich nicht heißen: AndAlso Current.NotExists, sondern AndAlso Not Current.NotExists; alternativ verwende ich bei mir eine Extra-Extension namens Exists(), aber die kollidiert etwas mit der gleichnamigen Funktion aus dem IO-Namespace.

      ##########

      03.10.2022: Konstruktoren um components ergänzt, damit die tBS automatisch zu den Form-Components hinzugefügt und automatisch disposed wird.
      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 6 mal editiert, zuletzt von „VaporiZed“ ()

      Moin @VaporiZed,

      first of all: "zusammengeschustert" ist finde ich nicht wirklich treffend. Ich mag deinen Programmierstil. Er is klar strukturiert, sauber gekürzt und alles ist sprechend bezeichnet. Auch die Funktionen sind sinnvoll, auch wenn ich für mich persönlich zumindest eine weggelassen hätte. Mir ist egal ob ich

      VB.NET-Quellcode

      1. Element.HasNoCurrent

      oder

      VB.NET-Quellcode

      1. Not Element.HasCurrent


      abfrage, aber im Endeffekt völlig unerheblich und auch keine Kritik, weil Geschmacksache. Jo, Programmierstil hatte ich, Einsatzmöglichkeiten sind enorm und ich werde dein Beispiel definitiv mal in meine Klassensammlung aufnehmen und meinen Bedürfnissen entsprechend anpassen. Gute Idee, top Umsetzung. Danke dir dafür!


      Ein Computer wird das tun, was du programmierst - nicht das, was du willst.
      Die tDS-bezogene tBS sieht schon deutlich konservativer aus, da man Items (die vom Typ DataRowView sind) nicht dazupacken sollte (oder kann?). Das Anlegen neuer Rows macht man am besten über das tDS bzw. die DataTables direkt. Daher fallen die Methoden Add/AddRange weg und die Index-Property ist nun ReadOnly. Die Doppelcasts stecken in den jeweiligen Methoden drin, was das Leben etwas erleichtert.
      das Gesamtpaket ohne Extensions

      VB.NET-Quellcode

      1. Imports System.Runtime.CompilerServices
      2. '''<summary>eine typisierte BindingSource zur Verwendung mit typisierten DataSets</summary>
      3. '''<typeparam name="T">der übergebende Datentyp, der bei New hinterlegt und bei Current zurückgegeben wird; muss vom Typ DataRow oder einer davon abgeleiteten Variante sein</typeparam>
      4. '''<remarks>Um dieses Control auf einem Form zu verwenden, muss von dieser einfach geerbt werden, z.B.:
      5. '''<code>
      6. '''Public Class MyTypedBindingSource : Inherits TdsTypedBindingSource(Of MyDataRow) : End Class
      7. '''</code>
      8. ''' </remarks>
      9. Public MustInherit Class TdsTypedBindingSource(Of T As Data.DataRow) : Inherits BindingSource
      10. '''<summary>erstellt eine typisierte BindingSource für den Typ T</summary>
      11. Protected Sub New(Components As ComponentModel.IContainer)
      12. DataSource = GetType(T)
      13. If Components IsNot Nothing Then Components.Add(Me)
      14. End Sub
      15. '''<summary>entfernt das momentan ausgewählte Element der Auflistung; danach wird ein BindingReset für das GUI durchgeführt; wenn die Auflistung leer ist, passiert nichts</summary>
      16. Public Shadows Sub RemoveCurrent()
      17. If HasNoCurrent Then Return
      18. MyBase.RemoveCurrent()
      19. End Sub
      20. Private Function IndexIsValid(Index As Integer) As Boolean
      21. Return Index.IsBetween(0, Count - 1) AndAlso MyBase.Item(Index) IsNot Nothing
      22. End Function
      23. '''<summary>ruft die typisierte DataRow an der angegebenen Position ab</summary>
      24. '''<param name="Index">die Position innerhalb der Liste</param>
      25. '''<returns>die typisierte DataRow an der angegebenen Position</returns>
      26. Default Public Shadows ReadOnly Property Item(Index As Integer) As T
      27. Get
      28. If Not IndexIsValid(Index) Then Return Nothing
      29. Return DirectCast(DirectCast(MyBase.Item(Index), Data.DataRowView).Row, T)
      30. End Get
      31. End Property
      32. '''<summary>ruft die aktuell ausgewählte DataRow ab oder legt diese fest</summary>
      33. Public Shadows Property Current As T
      34. Get
      35. Return DirectCast(DirectCast(MyBase.Current, Data.DataRowView).Row, T)
      36. End Get
      37. Set
      38. If IsBindingSuspended OrElse HasNoCurrent OrElse Value.NotExists Then Return
      39. For i = 0 To Count - 1
      40. If Item(i).Equals(Value) Then Position = i : Exit For
      41. Next
      42. End Set
      43. End Property
      44. '''<summary>gibt wieder, ob die TdsTypedBindingSource befüllt ist</summary>
      45. Public ReadOnly Property HasCurrent() As Boolean
      46. Get
      47. Return Position > -1 AndAlso Current.Exists
      48. End Get
      49. End Property
      50. '''<summary>gibt wieder, ob die TdsTypedBindingSource leer ist</summary>
      51. Public ReadOnly Property HasNoCurrent() As Boolean
      52. Get
      53. Return Not HasCurrent
      54. End Get
      55. End Property
      56. '''<summary>gibt wieder, ob die aktuelle Position 0 ist</summary>
      57. Public ReadOnly Property IsAtListStart() As Boolean
      58. Get
      59. Return Position = 0
      60. End Get
      61. End Property
      62. '''<summary>gibt wieder, ob die aktuelle Position am Ende der Liste ist</summary>
      63. Public ReadOnly Property IsAtListEnd() As Boolean
      64. Get
      65. Return Position = List.Count - 1
      66. End Get
      67. End Property
      68. End Class


      Mit folgender Tabelle

      ist dann also sowas hier problemlos möglich:

      VB.NET-Quellcode

      1. Public Class BsCars : Inherits TdsTypedBindingSource(Of DataSet1.CarsRow)
      2. Sub New(Components As ComponentModel.IContainer)
      3. MyBase.New(Components)
      4. End Sub
      5. End Class
      6. '…
      7. BsCars.Current = DataSet1.Cars.Last
      8. BsCars.Current = BsCars(1)
      9. MessageBox.Show(BsCars(0).Name)
      10. BsCars.Position = 2 '<- das geht auch mit ner normalen uBS
      11. MessageBox.Show(BsCars.Current.PowerInPS.ToString)


      Da ist zweifellos noch viel Optimierungspotential da, aber da ich nicht (mehr) sonderlich viel mit tDS arbeite, überlasse ich das den anderen.

      Mit ein wenig Mehraufwand kann man sich auch noch die beiden tBS-Varianten For-Each- und LINQ-fähig machen. Aber das geht wohl an dieser Stelle etwas zu weit für mich, da ich dann häufig doch eher mit den zugrundeliegenden Tabellen/Listen arbeite.

      ##########

      03.10.2022: Konstruktoren um components ergänzt, damit die tBS automatisch zu den Form-Components hinzugefügt und automatisch disposed wird.
      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“ ()

      Meine neue, verbesserte TypedBindingSource nutzt zusätzlich eine interne Liste. Was das soll? Bei Arbeiten mit externen DLLs, die v.a. auf Interfaces setzen, kann es vorkommen, dass man mit COM-Objekten zu tun hat, die in der tBS abgespeichert werden sollen. Diese haben zwar fixe Typen, können aber mit der normalen tBS nicht "endgültig"/dauerhaft in der Zieltypart gespeichert werden, was zu Laufzeitfehlern führt, da scheinbar die Typen inkompatibel sind. Nutzt man aber eine interne Liste mit dem Zieltyp, geht alles wieder seinen Gang. Ein Beispiel lässt sich in meinem Programm GuiSpy finden.
      Spoiler anzeigen

      VB.NET-Quellcode

      1. '''<summary>eine typisierte BindingSource mit interner Liste</summary>
      2. '''<typeparam name="T">der übergebende Datentyp, der bei New hinterlegt, bei Add benötigt und bei Current zurückgegeben wird</typeparam>
      3. '''<remarks>Um dieses Control auf einem Form zu verwenden, muss von dieser einfach geerbt werden, z.B.:
      4. '''<code>
      5. '''Public Class MyTypedBindingSource : Inherits TypedBindingSource(Of MyOwnClass) : End Class
      6. '''</code>
      7. ''' </remarks>
      8. Public MustInherit Class TypedBindingSource(Of T) : Inherits BindingSource
      9. Private ReadOnly InternalList As New System.ComponentModel.BindingList(Of T)
      10. '''<summary>erstellt eine typisierte BindingSource für den Typ T</summary>
      11. Protected Sub New()
      12. DataSource = GetType(T)
      13. End Sub
      14. '''<summary>fügt der Auflistung ein neues Element vom Typ T hinzu</summary>
      15. '''<param name="NewItem">das hinzuzufügende Item</param>
      16. Public Overloads Sub Add(NewItem As T)
      17. InternalList.Add(NewItem)
      18. End Sub
      19. '''<summary>entfernt das momentan ausgewählte Element der Auflistung; wenn die Auflistung leer ist, passiert nichts</summary>
      20. Public Overloads Sub RemoveCurrent()
      21. If HasNoCurrent() Then Return
      22. MyBase.RemoveCurrent()
      23. End Sub
      24. '''<summary>ersetzt das ausgewählte Element durch ein anderes</summary>
      25. '''<param name="Replacement">das Element, welches an der ausgewählten Position den Platz einnehmen soll</param>
      26. Public Sub ReplaceCurrentWith(Replacement As T)
      27. If HasNoCurrent() Then Return
      28. MyBase.Item(Position) = Replacement
      29. ResetCurrentItem()
      30. End Sub
      31. '''<summary>fügt der Auflistung die Elemente einer Liste hinzu</summary>
      32. '''<param name="List">die Liste der Elemente, die hinzugefügt werden soll</param>
      33. Public Sub AddRange(List As IEnumerable(Of T))
      34. List.ForEach(Sub(x) Add(x))
      35. End Sub
      36. '''<summary>ersetzt die Auflistungselemente durch andere</summary>
      37. '''<param name="List">die Liste der Elemente, die sich danach in der Auflistung befinden</param>
      38. Public Sub ReplaceListContentBy(List As IEnumerable(Of T))
      39. Clear()
      40. AddRange(List)
      41. End Sub
      42. Private Function IndexIsValid(Index As Integer) As Boolean
      43. Return Index.IsBetween(0, Count - 1) AndAlso MyBase.Item(Index) IsNot Nothing
      44. End Function
      45. Private Sub TypedBindingSource_DataSourceChanged(sender As Object, e As EventArgs) Handles Me.DataSourceChanged
      46. If Diagnostics.Debugger.IsAttached AndAlso DataSource Is GetType(T) Then
      47. DataSource = InternalList
      48. End If
      49. End Sub
      50. '''<summary>ruft das Element an der angegebenen Position ab oder legt dieses fest</summary>
      51. ''' <param name="Index">die Position innerhalb der Liste</param>
      52. ''' <returns>das Element an der angegebenen Position</returns>
      53. Default Public Shadows Property Item(Index As Integer) As T
      54. Get
      55. If Not IndexIsValid(Index) Then Return Nothing
      56. Return DirectCast(MyBase.Item(Index), T)
      57. End Get
      58. Set
      59. If Not IndexIsValid(Index) Then Return
      60. MyBase.Item(Index) = Value
      61. ResetBindings(False)
      62. End Set
      63. End Property
      64. '''<summary>ruft das aktuell ausgewählte Item ab oder legt dieses fest</summary>
      65. Public Shadows Property Current As T
      66. Get
      67. Return DirectCast(MyBase.Current, T)
      68. End Get
      69. Set
      70. If IsBindingSuspended OrElse HasNoCurrent OrElse Value.NotExists Then Return
      71. For i = 0 To Count - 1
      72. If Item(i).Equals(Value) Then Position = i : Exit For
      73. Next
      74. End Set
      75. End Property
      76. '''<summary>gibt wieder, ob die TypedBindingSource befüllt ist</summary>
      77. Public ReadOnly Property HasCurrent() As Boolean
      78. Get
      79. Return Position > -1 AndAlso Current.Exists
      80. End Get
      81. End Property
      82. '''<summary>gibt wieder, ob die TypedBindingSource leer ist</summary>
      83. Public ReadOnly Property HasNoCurrent() As Boolean
      84. Get
      85. Return Not HasCurrent
      86. End Get
      87. End Property
      88. '''<summary>gibt wieder, ob die aktuelle Position 0 ist</summary>
      89. Public ReadOnly Property IsAtListStart() As Boolean
      90. Get
      91. Return Position = 0
      92. End Get
      93. End Property
      94. '''<summary>gibt wieder, ob die aktuelle Position am Ende der Liste ist</summary>
      95. Public ReadOnly Property IsAtListEnd() As Boolean
      96. Get
      97. Return Position = List.Count - 1
      98. End Get
      99. End Property
      100. End Class


      Einen Nachteil hat das Ganze aber, den ich in einem anderen Thread beschrieben habe. Leider habe ich noch keine saubere Lösung dafür.

      ##########

      Mit dem zuletzt genannten Problem konnte ich nun im erwähnten Thread einen besseren/zuverlässigeren Workaround finden.
      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“ ()