Datenbank Threads
Datenbank Threads
Hi Leute,
dies wird ein weiteres Thread-Thema.
Was mich noch brennend interessiert ist:
Ist es irgend wie möglich MySql / SQLLite -Datenbankanweisungen und GUI (GTK) -Zugriff gleichzeitig zu machen ?
_______________________________
Unter Windows ist das ja kein Problem, nur läuft der Datenbankzugriff immer im gleichen Thread wie der GUI-Zugriff.
Hab bis jetzt noch keine Lösung für das Problem gefunden.
Danke für jede hilfreiche Antwort.
dies wird ein weiteres Thread-Thema.
Was mich noch brennend interessiert ist:
Ist es irgend wie möglich MySql / SQLLite -Datenbankanweisungen und GUI (GTK) -Zugriff gleichzeitig zu machen ?
_______________________________
Unter Windows ist das ja kein Problem, nur läuft der Datenbankzugriff immer im gleichen Thread wie der GUI-Zugriff.
Hab bis jetzt noch keine Lösung für das Problem gefunden.
Danke für jede hilfreiche Antwort.
Betriebssysteme: div. Windows, Linux, Unix - Systeme
no Keyboard, press any key
no mouse, you need a cat
no Keyboard, press any key
no mouse, you need a cat
Re: Datenbank Threads
Geht über den Umweg PostEvent oder über das Modul ThreadToGUI.
Link: http://www.purebasic.fr/german/viewtopi ... =8&t=29728
GetGadgetText, GetGadgetXY geht auch aus einem Thread. Änderungen an Gadget immer an den Mainscope senden
P.S. Wie man Threads steuern könnte kann man sich hier anschauen.
Link: http://www.purebasic.fr/german/viewtopi ... ol#p330743
Link: http://www.purebasic.fr/german/viewtopi ... =8&t=29728
GetGadgetText, GetGadgetXY geht auch aus einem Thread. Änderungen an Gadget immer an den Mainscope senden
P.S. Wie man Threads steuern könnte kann man sich hier anschauen.
Link: http://www.purebasic.fr/german/viewtopi ... ol#p330743
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Re: Datenbank Threads
Hi mk-soft !
Irgend wie funktioniert das mit PostEvent oder ThreadToGUI mit SQLLite nicht, oder ich mache etwas falsch.
Bei mir friert das Fenster immer ein und meldet sich nach Erfolg wieder.
Außerdem kann ich keine Datenbankbefehle in einem Extra-Thread nutzen.
Da stürzt PB ab.
Ich werde aber noch ein bisschen probieren.
Unter Windows gibt da ja keine Probleme, aber Linux macht da mal wieder Ärger.
Außerdem sind die SQL-Abfragen unter Linux mindestens doppelt bis dreimal so langsam wie unter Windows 10.
Getestet unter 2 verschiedene Systeme und Linux-Distros.
Irgend wie funktioniert das mit PostEvent oder ThreadToGUI mit SQLLite nicht, oder ich mache etwas falsch.
Bei mir friert das Fenster immer ein und meldet sich nach Erfolg wieder.
Außerdem kann ich keine Datenbankbefehle in einem Extra-Thread nutzen.
Da stürzt PB ab.
Ich werde aber noch ein bisschen probieren.
Unter Windows gibt da ja keine Probleme, aber Linux macht da mal wieder Ärger.
Außerdem sind die SQL-Abfragen unter Linux mindestens doppelt bis dreimal so langsam wie unter Windows 10.
Getestet unter 2 verschiedene Systeme und Linux-Distros.
Betriebssysteme: div. Windows, Linux, Unix - Systeme
no Keyboard, press any key
no mouse, you need a cat
no Keyboard, press any key
no mouse, you need a cat
Re: Datenbank Threads
Wie könnte ich hier die Statusbarabfrage asynchron machen?
Code: Alles auswählen
Protected.s SQL1, SQL2, sql9, id_name
Protected size, *mem, size2, *mem2
If CreateFile(1, fili$)
CloseFile(1)
If OpenDatabase(#CopyDB, fili$, "", "")
SQL1 = "CREATE TABLE datenbank (id INTEGER PRIMARY KEY, name TEXT, gruppe TEXT, version TEXT, beschreibung MEMO, tabelle longblob, picname TEXT, fotodata longblob, extdata longblob, extname TEXT)"
c = 0
v.f = 0
If DatabaseUpdate(#CopyDB, SQL1) = #False
Debug DatabaseError()
EndIf
If DatabaseQuery(#DB, "SELECT * FROM datenbank ORDER BY gruppe")
While NextDatabaseRow(#DB)
id_name = GetDatabaseString(#DB, 1)
size = DatabaseColumnSize(#DB, 7)
size2 = DatabaseColumnSize(#DB, 8)
Debug size
If size And Not size2
*mem = AllocateMemory(size)
If *mem
SQL2 = "INSERT INTO datenbank (name, gruppe, version, beschreibung, tabelle, picname, fotodata) "
SQL2 + "VALUES ('"
SQL2 + id_name + "','"
SQL2 + GetDatabaseString(#DB, 2) + "','"
SQL2 + GetDatabaseString(#DB, 3) + "','"
SQL2 + GetDatabaseString(#DB, 4) + "','"
SQL2 + GetDatabaseString(#DB, 5) + "','"
SQL2 + GetDatabaseString(#DB, 6) + "', "
SQL2 + "? )"
If GetDatabaseBlob(#DB, #row_FotoData, *mem, size)
SetDatabaseBlob(#CopyDB, 0, *mem, size)
DatabaseUpdate(#CopyDB, SQL2)
FreeMemory(*mem)
EndIf
EndIf
ElseIf size2 And Not size
*mem2 = AllocateMemory(size2)
If *mem2
SQL2 = "INSERT INTO datenbank (name, gruppe, version, beschreibung, tabelle, picname, extdata, extname) "
SQL2 + "VALUES ('"
SQL2 + id_name + "','"
SQL2 + GetDatabaseString(#DB, 2) + "','"
SQL2 + GetDatabaseString(#DB, 3) + "','"
SQL2 + GetDatabaseString(#DB, 4) + "','"
SQL2 + GetDatabaseString(#DB, 5) + "','"
SQL2 + GetDatabaseString(#DB, 6) + "', "
SQL2 + "? ,'"
SQL2 + GetDatabaseString(#DB, 9) + "')"
If GetDatabaseBlob(#DB, 8, *mem2, size2)
SetDatabaseBlob(#CopyDB, 0, *mem2, size2)
DatabaseUpdate(#CopyDB, SQL2)
FreeMemory(*mem2)
EndIf
EndIf
ElseIf size And size2
*mem = AllocateMemory(size)
*mem2 = AllocateMemory(size2)
If *mem And *mem2
SQL2 = "INSERT INTO datenbank (name, gruppe, version, beschreibung, tabelle, picname, fotodata, extdata, extname) "
SQL2 + "VALUES ('"
SQL2 + id_name + "','"
SQL2 + GetDatabaseString(#DB, 2) + "','"
SQL2 + GetDatabaseString(#DB, 3) + "','"
SQL2 + GetDatabaseString(#DB, 4) + "','"
SQL2 + GetDatabaseString(#DB, 5) + "','"
SQL2 + GetDatabaseString(#DB, 6) + "', "
SQL2 + "? , ? ,'"
SQL2 + GetDatabaseString(#DB, 9) + "')"
If GetDatabaseBlob(#DB, 7, *mem, size) And GetDatabaseBlob(#DB, 8, *mem2, size2)
SetDatabaseBlob(#CopyDB, 0, *mem, size)
SetDatabaseBlob(#CopyDB, 1, *mem2, size2)
DatabaseUpdate(#CopyDB, SQL2)
FreeMemory(*mem)
FreeMemory(*mem2)
EndIf
; Mit GetDatabaseBlob laden wir nun die Bilddaten in unseren Speicher.
EndIf
Else
SQL2 = "INSERT INTO datenbank (name, gruppe, version, beschreibung, tabelle, picname) "
SQL2 + "VALUES ('"
SQL2 + id_name + "','"
SQL2 + GetDatabaseString(#DB, 2) + "','"
SQL2 + GetDatabaseString(#DB, 3) + "','"
SQL2 + GetDatabaseString(#DB, 4) + "','"
SQL2 + GetDatabaseString(#DB, 5) + "','"
SQL2 + GetDatabaseString(#DB, 6) + "')"
DatabaseUpdate(#CopyDB, SQL2)
EndIf
c + 1
v = 100 / CountGadgetItems(#gad_ListView) * c
StatusBarProgress(#Statusbar, 1, Int(v), #PB_StatusBar_Raised) ;Wie kann ich das asynchron machen ?
Wend
FinishDatabaseQuery(#DB)
Else
Debug DatabaseError()
EndIf
CloseDatabase(#CopyDB)
StatusBarText(#Statusbar, 2, "Der Export war erfolgreich.")
MessageRequester("Programm", "Die Datenbank wurde erfolgreich exportiert!")
ListFuellen()
Else
Debug DatabaseError()
MessageRequester("Fehler", "Die Datenbank konnte nicht exportiert werden.")
End
EndIf
Else
Debug fili$ + " konnte nicht erstellt werden."
MessageRequester("Schreib-Fehler", "Die Datenbank konnte nicht erstellt werden.")
EndIf
Betriebssysteme: div. Windows, Linux, Unix - Systeme
no Keyboard, press any key
no mouse, you need a cat
no Keyboard, press any key
no mouse, you need a cat
Re: Datenbank Threads
P.S. Editiert
Erst mal die Aufgaben in Prozeduren zusammenfassen und diese dann aus einem Thread ausführen...
Siehe mein Beispiel...
Sollte so weit funktionieren.
Habe mal ein Beispiel von mir auf Thread umgebaut. Mit den Menu/Ablage/Start wird der Thread 'MyThread' gestartet...
Ist nur ein Beispiel für die Funktion 'ShowDatabaseRows(Gadget, DBase, Hide = #False)'
und jetzt als 'DoShowDatabaseRows(Gadget, DBase, Hide = #False) aus einem Thread heraus.
Läuft unter Linux und Mac
Erst mal die Aufgaben in Prozeduren zusammenfassen und diese dann aus einem Thread ausführen...
Siehe mein Beispiel...
Sollte so weit funktionieren.
Habe mal ein Beispiel von mir auf Thread umgebaut. Mit den Menu/Ablage/Start wird der Thread 'MyThread' gestartet...
Ist nur ein Beispiel für die Funktion 'ShowDatabaseRows(Gadget, DBase, Hide = #False)'
und jetzt als 'DoShowDatabaseRows(Gadget, DBase, Hide = #False) aus einem Thread heraus.
Läuft unter Linux und Mac
Code: Alles auswählen
;-TOP
; ***************************************************************************************
; ShowDatabaseItems by mk-soft from 12.02.2017
; DoShowDatabaseItems for threads from 04.07.2015
IncludeFile "Modul_ThreadToGUI.pb"
UseModule ThreadToGUI
Procedure GetTextWidth(Text.s, FontID.i = 0)
Static image
Protected result
If Not image
image = CreateImage(#PB_Any, 1, 1)
EndIf
If image And StartDrawing(ImageOutput(image))
If FontID
DrawingFont(FontID)
EndIf
result = TextWidth(Text)
StopDrawing()
EndIf
ProcedureReturn result
EndProcedure
; ---------------------------------------------------------------------------------------
Procedure ClearGadgetColumns(Gadget)
CompilerIf #PB_Compiler_Version <= 551
ClearGadgetItems(Gadget)
While GetGadgetItemText(Gadget, -1, 0)
RemoveGadgetColumn(Gadget, 0)
Wend
CompilerElse
RemoveGadgetColumn(Gadget, #PB_All)
CompilerEndIf
EndProcedure
; ---------------------------------------------------------------------------------------
Procedure CountGadgetColumns(Gadget)
Protected result
CompilerIf #PB_Compiler_Version <= 551
While GetGadgetItemText(Gadget, -1, result)
result + 1
Wend
CompilerElse
result = GetGadgetAttribute(Gadget, #PB_ListIcon_ColumnCount)
CompilerEndIf
ProcedureReturn result
EndProcedure
; ---------------------------------------------------------------------------------------
; ThreadToGUI: Only inside Threads
Procedure DoShowDatabaseRows(Gadget, DBase, Hide = #False)
Protected result.i, columns.i, index.i, size.i, text.s
Repeat
If GadgetType(Gadget) <> #PB_GadgetType_ListIcon
Break
EndIf
If Not IsDatabase(DBase)
Break
EndIf
DoHideGadget(Gadget, Hide)
DoClearGadgetColumns(Gadget)
columns = DatabaseColumns(DBase)
For index = 0 To columns - 1
text = DatabaseColumnName(DBase, index)
size = GetTextWidth(text) + 12
DoAddGadgetColumn(Gadget, index, text, size)
Next
While NextDatabaseRow(DBase)
text = ""
For index = 0 To columns - 1
text + GetDatabaseString(DBase, index) + #LF$
Next
DoAddGadgetItem(Gadget, -1, text)
Wend
FinishDatabaseQuery(DBase)
DoHideGadget(Gadget, #False)
DoWait() ; Wait on Mainscope for do all Events
result = CountGadgetItems(Gadget)
Until #True
ProcedureReturn result
EndProcedure
; ---------------------------------------------------------------------------------------
Procedure ShowDatabaseRows(Gadget, DBase, Hide = #False)
Protected result.i, columns.i, index.i, size.i, text.s
Repeat
If GadgetType(Gadget) <> #PB_GadgetType_ListIcon
Break
EndIf
If Not IsDatabase(DBase)
Break
EndIf
HideGadget(Gadget, Hide)
ClearGadgetColumns(Gadget)
columns = DatabaseColumns(DBase)
For index = 0 To columns - 1
text = DatabaseColumnName(DBase, index)
size = GetTextWidth(text) + 12
AddGadgetColumn(Gadget, index, text, size)
Next
While NextDatabaseRow(DBase)
text = ""
For index = 0 To columns - 1
text + GetDatabaseString(DBase, index) + #LF$
Next
AddGadgetItem(Gadget, -1, text)
Wend
FinishDatabaseQuery(DBase)
HideGadget(Gadget, #False)
result = CountGadgetItems(Gadget)
Until #True
ProcedureReturn result
EndProcedure
; ***************************************************************************************
CompilerIf #PB_Compiler_IsMainFile
; Constant
Enumeration ;Window
#Main
EndEnumeration
Enumeration ; Menu
#Menu
EndEnumeration
Enumeration ; MenuItems
#MenuExitApplication
#MenuStart
EndEnumeration
Enumeration ; Gadgets
#List
#Edit
EndEnumeration
Enumeration ; Statusbar
#Status
EndEnumeration
; Global Variable
Global ExitApplication
; Functions
UseSQLiteDatabase()
Procedure CheckDatabaseUpdate(Database, Query$)
Result = DatabaseUpdate(Database, Query$)
If Result = 0
Debug DatabaseError()
EndIf
ProcedureReturn Result
EndProcedure
Procedure CreateDummyDatabase(DBase)
If OpenDatabase(DBase, ":memory:", "", "")
CheckDatabaseUpdate(DBase, "CREATE TABLE food (recid INTEGER PRIMARY KEY ASC, name CHAR(50), weight FLOAT)")
CheckDatabaseUpdate(DBase, "INSERT INTO food (name, weight) VALUES ('apple', '10.005')")
CheckDatabaseUpdate(DBase, "INSERT INTO food (name, weight) VALUES ('pear', '5.9')")
CheckDatabaseUpdate(DBase, "INSERT INTO food (name, weight) VALUES ('banana', '20.35')")
Else
Debug "Can't open database !"
EndIf
EndProcedure
Procedure UpdateWindow()
Protected x, y, dx, dy, menu, status
menu = MenuHeight()
If IsStatusBar(#Status)
status = StatusBarHeight(#Status)
Else
status = 0
EndIf
x = 0
y = 0
dx = WindowWidth(#Main)
dy = WindowHeight(#Main) - menu - status
ResizeGadget(#List, x, y, dx, dy)
EndProcedure
Procedure MyThread(id)
;-Test database
CreateDummyDatabase(0)
If DatabaseQuery(0, "SELECT * FROM food WHERE weight > 0")
count = DoShowDatabaseRows(#List, 0, #True)
DoStatusBarText(#Status, 0, "Items: " + count)
EndIf
EndProcedure
; Main
Procedure Main()
Protected event, style, dx, dy
style = #PB_Window_SystemMenu | #PB_Window_MinimizeGadget | #PB_Window_MaximizeGadget | #PB_Window_SizeGadget
dx = 800
dy = 600
If OpenWindow(#Main, #PB_Ignore, #PB_Ignore, dx, dy, "Main", style)
; Menu
CreateMenu(#Menu, WindowID(#Main))
MenuTitle("Ablage")
MenuItem(#MenuStart, "&Start")
MenuBar()
MenuItem(#MenuExitApplication, "Be&enden")
; Enable Fullscreen
CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
Protected NewCollectionBehaviour
NewCollectionBehaviour = CocoaMessage(0, WindowID(#Main), "collectionBehavior") | $80
CocoaMessage(0, WindowID(#Main), "setCollectionBehavior:", NewCollectionBehaviour)
; Mac default menu
If Not IsMenu(#Menu)
CreateMenu(#Menu, WindowID(#Main))
EndIf
MenuItem(#PB_Menu_About, "")
MenuItem(#PB_Menu_Preferences, "")
CompilerEndIf
; Gadgets
ListIconGadget(#List, 0, 0, dx, dy, "recid", 100)
; Statusbar
CreateStatusBar(#Status, WindowID(#Main))
AddStatusBarField(#PB_Ignore)
UpdateWindow()
BindEventGUI()
; Main Loop
Repeat
event = WaitWindowEvent()
Select event
Case #PB_Event_Menu
Select EventMenu()
CompilerIf #PB_Compiler_OS = #PB_OS_MacOS
Case #PB_Menu_About
Case #PB_Menu_Preferences
Case #PB_Menu_Quit
ExitApplication = #True
CompilerEndIf
Case #MenuExitApplication
ExitApplication = #True
Case #MenuStart
CreateThread(@MyThread(), 0)
EndSelect
Case #PB_Event_Gadget
Select EventGadget()
Case #List
EndSelect
Case #PB_Event_SizeWindow
Select EventWindow()
Case #Main
UpdateWindow()
EndSelect
Case #PB_Event_CloseWindow
Select EventWindow()
Case #Main
ExitApplication = #True
EndSelect
EndSelect
Until ExitApplication
EndIf
EndProcedure : Main()
End
CompilerEndIf
Zuletzt geändert von mk-soft am 04.07.2017 21:10, insgesamt 1-mal geändert.
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Re: Datenbank Threads
P.S.2
MessageRequester, etc dürfen nicht aus einem Thread aufgerufen werden. Verwende 'DoMessageRequester(...)
MessageRequester, etc dürfen nicht aus einem Thread aufgerufen werden. Verwende 'DoMessageRequester(...)
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Re: Datenbank Threads
Danke für den Code.
Ich bekomme es trotzdem noch nicht hin.
Bei mir friert das ganze Fenster immer ein.
Aber ich bleibe dran.
Ich bin mir sicher das irgend wann der Groschen fällt.
Also erst einmal danke!
Ich werde mal weiter probieren.
Ich bekomme es trotzdem noch nicht hin.
Bei mir friert das ganze Fenster immer ein.
Aber ich bleibe dran.
Ich bin mir sicher das irgend wann der Groschen fällt.
Also erst einmal danke!
Ich werde mal weiter probieren.
Betriebssysteme: div. Windows, Linux, Unix - Systeme
no Keyboard, press any key
no mouse, you need a cat
no Keyboard, press any key
no mouse, you need a cat
Re: Datenbank Threads
Die Event-Schleife muss im Mainscope natürlich immer laufen... (Nicht im Thread)
Sonst geht gar nichts...
Code: Alles auswählen
Repeat
event = WaitWindowEvent()
Select event
Case #PB_Event_CloseWindow
Break
EndSelect
ForEver
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Re: Datenbank Threads
Hi mk-soft,
meine Rückmeldung:
Es funktioniert!!!
Ich hatte nur ein paar kleine Fehler.
Habe meinen Code dabei aber noch etwas optimiert und überarbeitet.
Ich verwende aber nicht dein ThreadToGUI - Modul, weil es:
1. Zu "Oversize" wäre
und
2. Bei mir ohne Anpassung deines Codes einen Zeiger-Error liefert und das Programm einfach abschmiert.
Dieser Fehler würde bei mir an einer Stelle mit PostEvent(...) auftreten.
Ich verwende bei einer Listview einen Callback für die "Kopf-Einträge" (mit Pfeil zum sortieren).
Debugger-Ausgabe:
Mein eigentlich größter Fehler war:
Ich wollte die GUI in Threads stecken, aber ich muss die Datenbank-Dinge in Threads stecken.
Naja egal...
das Thema ist erstmal erledigt.
meine Rückmeldung:
Es funktioniert!!!
Ich hatte nur ein paar kleine Fehler.
Habe meinen Code dabei aber noch etwas optimiert und überarbeitet.
Ich verwende aber nicht dein ThreadToGUI - Modul, weil es:
1. Zu "Oversize" wäre
und
2. Bei mir ohne Anpassung deines Codes einen Zeiger-Error liefert und das Programm einfach abschmiert.
Dieser Fehler würde bei mir an einer Stelle mit PostEvent(...) auftreten.
Ich verwende bei einer Listview einen Callback für die "Kopf-Einträge" (mit Pfeil zum sortieren).
Code: Alles auswählen
ProcedureC LinksklickAufKopfzelle_Rueckruf(ColumnObject.I, Column.I)
SortColumn = Column
If SortIsAscending
PostEvent(#Event_ListIcon_SortDescending)
Else
PostEvent(#Event_ListIcon_SortAscending) ;Hierbei kommt sonst der Zeiger-Error bei Verwendung von ThreadToGUI !!!
EndIf
SortIsAscending ! 1
EndProcedure
Code: Alles auswählen
Procedure PostEventCB()
Protected *data.udtParam
*data = EventData()
With *data
Select \Command ;Hierbei kommt dann bei mir ein Zeigerfehler!!!
Case #WaitOnSignal
SignalSemaphore(\Param1)
Ich wollte die GUI in Threads stecken, aber ich muss die Datenbank-Dinge in Threads stecken.
Naja egal...
das Thema ist erstmal erledigt.
Betriebssysteme: div. Windows, Linux, Unix - Systeme
no Keyboard, press any key
no mouse, you need a cat
no Keyboard, press any key
no mouse, you need a cat
Re: Datenbank Threads
Hast bestimmt die gleiche Event-Nummer wie die vom ThreadToGUI genommen. (#PB_Event_FirstCustomValue)
Bei BindEventGUI(...) kann man die zu verwendete Event-Nummer festlegen. Eine Doppelverwendung für natürlich zu einem Fehler.
Bei BindEventGUI(...) kann man die zu verwendete Event-Nummer festlegen. Eine Doppelverwendung für natürlich zu einem Fehler.
Code: Alles auswählen
IncludeFile "Modul_ThreadToGUI.pb"
Enumeration #PB_Event_FirstCustomValue
#My_Event_ThreadToGUI
#Event_ListIcon_SortDescending
#Event_ListIcon_SortAscending
EndEnumeration
...
ThreadToGUI::BindEventGUI(#My_Event_ThreadToGUI)
Alles ist möglich, fragt sich nur wie...
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive
Projekte ThreadToGUI / EventDesigner V3 / OOP-BaseClass-Modul
Downloads auf MyWebspace / OneDrive