Gadget Functionen und Threads

Anfängerfragen zum Programmieren mit PureBasic.
Benutzeravatar
Konne
Beiträge: 764
Registriert: 30.03.2005 02:20
Kontaktdaten:

Gadget Functionen und Threads

Beitrag von Konne »

Hallo ich schreibe grade ein Program und es wäre wichtig für mich zu wissen welche Gadget Funktionen nicht aus einem anderen Thread, als dem in dem das MainWindow ist, verwendet werden können.

Zb. Habe ich herausgefunden das alle Gadget erstellungsfunktionen nicht tun.

SetGadgetText() tut allerdings.

Code: Alles auswählen

Procedure T(Mutex)
  LockMutex(Mutex)
  
  SetGadgetText(1,"WOW")
EndProcedure

wnd=OpenWindow(200,200,200,200,200,"Test")
CreateGadgetList(wnd)
ButtonGadget(1,0,0,100,200,"Click to Change")

Mutex=CreateMutex()
LockMutex(Mutex)
CreateThread(@T(),Mutex)

Repeat
  Select WaitWindowEvent()
    Case #PB_Event_Gadget
      UnlockMutex(Mutex)
    
    Case #PB_Event_CloseWindow
      End
      
   EndSelect   
ForEver
Habe leider in der Hilfe keinen Hinweis darauf gefunden. Bitte helft mir.
freak
PureBasic Team
Beiträge: 766
Registriert: 29.08.2004 00:20
Wohnort: Stuttgart

Beitrag von freak »

Es gibt ein paar einfache Regeln, die zu beachten sind:

Windows:
Ein Thread der GUI Elemente (Gadget, Window, Toolbar, etc) erstellt muss seinen eigenen Eventloop haben.

Linux (wenn v4 dann mal da ist ;)):
Nur der Hauptthread darf einen Eventloop haben, er sollte auch alle GUI Elemente erstellen.

Mac: Weis ich grad nicht.

Allgemein:
Gadgets auf dem Fenster eines anderen Threads erstellen geht nicht. (gibt ein Durcheinander mit den Events)

Gadgets eines anderen Threads manipulieren geht, eventuell muss der Befehl aber so lange warten bis
der Thread dem das Fenster gehört ein WindowEvent()/WaitWindowEvent() ausgeführt hat. (Ist ein Messaging problem)
Also sollten der Thread der das Fenster/Gadget erzeugt hat einen laufenden Eventloop haben,
dann ist Manipulation der Gadgets von anderen Threads kein Problem.

(Ein Problem wäre sowas: Ich erstelle ein Gadget im Hauptthread und starte einen Thread,
der SetGadgetText oder sowas damit macht. Der Hauptthread wartet aber (z.b. mit WaitThread()
bis der Thread ended, befor er wieder ein WaitWindowEvent() ausführt.
Das gibt ein Deadlock, weil das SetGadgetText den WaitWindowEvent() Aufruf im anderen Thread
braucht, aber das erst ausgeführt wird, wenn das SetGadgetText fertig ist.)

Die Threadsafe Option des Compilers ist auf jeden Fall wichtig dass es keine Probleme gibt.

Also kurz:

Damits auf allen Systemen läuft, ist folgendes System gut:
- Main thread erstellt alle Fenster/Gadgets
- Main thread hat den Eventloop, der kontinuierlich Events verarbeitet.
- Andere threads manipulieren nur Gadgets (SetGadgetText, AddGadgetItem, etc...)

Wenn es nur für Windows ist, geht auch das:
- Thread A erstellt Fenster A + Gadgets und hat einen Eventloop für diese Events
- Thread B erstellt Fenster B + Gadgets und hat Eventloop für diese Events
- Manipulieren der Gadgets geht überall, solange der Eventloop des erstellenden Threads läuft.


Das Verhalten von Gadget/Windows und Events ist leider stark vom Event-System des Betriebssystems (bzw von Gtk auf Linux) abhängig, desshalb lassen sich diese Einschrängungen leider nicht umgehen.

Wenn das alles für Linux implementiert ist und feststeht kommt das hier auch in die Hilfe.
Benutzeravatar
Konne
Beiträge: 764
Registriert: 30.03.2005 02:20
Kontaktdaten:

Beitrag von Konne »

Weiß jemand ob es unter windows die möglichkeit gibt ein child fenster mit seinem eigenen eventloop zu erstellen? Sowas wie ein Containergadget nur halt mit eigenem Eventloop.
Benutzeravatar
AND51
Beiträge: 5220
Registriert: 01.10.2005 13:15

Beitrag von AND51 »

Hallo!

Ich habe dasselbe Problem. Ich möchtw aus einem Thread heraus ButtonImageGadget()s und CheckBoxGadget()s erstellen. Die Rückgabewerte sind <>0, aber sie erscheinen nicht.

Da ich die Gadgets nachträglich zur Laufzeit hinzufügen muss (je nachdem, wie viele Bilder geladen werden), kann ich die Gadgets leider nicht vorher erstellen.


Was ich weiß ist, dass der Thread Eigentümer des Fensters sein muss. Kann ich daher nicht die Gadgets erstellen und dann irgendwie mit SetParent_() oder so dem Hauptthread übergeben?

Die dynamischen Gadgets sollen übrigens in ein ScrollareaGadget() (Besitzer: Hauptthread) gepackt werden. Nachdem der Thread seine Arbeit geleistet hat, wird er beendet, da er nicht mehr gebraucht wird; es lohnt sich also gar nicht, den Thread mit einem eigenem Eventloop auszustatten.
PB 4.30

Code: Alles auswählen

Macro Happy
 ;-)
EndMacro

Happy End
Benutzeravatar
HeX0R
Beiträge: 3040
Registriert: 10.09.2004 09:59
Computerausstattung: AMD Ryzen 7 5800X
96Gig Ram
NVIDIA GEFORCE RTX 3060TI/8Gig
Win11 64Bit
G19 Tastatur
2x 24" + 1x27" Monitore
Glorious O Wireless Maus
PB 3.x-PB 6.x
Oculus Quest 2 + 3
Kontaktdaten:

Beitrag von HeX0R »

Du könntest an den Hauptthread per SendMessage_() eine Nachricht (#WM_USER z.B.) senden.

Dann z.B. dem lParam oder wParam eine beiden Threads bekannte Struktur mit auf den Weg geben, wo dann Art des Gadgets, und Grössenverhältnisse angegeben sind.

[Edit]
Beispiel:

Code: Alles auswählen

Enumeration
	#ButtonImageGadget
	#CheckBoxGadget
EndEnumeration

Structure _MY_GADGETS_
	KindOF.l
	x.l
	y.l
	w.l
	h.l
	Text.s
	Flags.l
EndStructure

Procedure MyCallBack(WindowID, Message, wParam, lParam)
	Protected Result = #PB_ProcessPureBasicEvents, *B._MY_GADGETS_

	Select Message
		Case #WM_USER
			*B = wParam
			Select *B\KindOF
				Case #ButtonImageGadget
					ButtonImageGadget(#PB_Any, *B\x, *B\y, *B\w, *B\h, *B\Flags)
				Case #CheckBoxGadget
					CheckBoxGadget(#PB_Any, *B\x, *B\y, *B\w, *B\h, *B\Text, *B\Flags)
			EndSelect
	EndSelect

	ProcedureReturn Result
EndProcedure


Procedure Thread(hWnd.l)
	Protected A._MY_GADGETS_

	Delay(2000)
	A\KindOF = #ButtonImageGadget
	A\x      = 10
	A\y      = 10
	A\w      = 50
	A\h      = 50
	A\Flags  = 0
	SendMessage_(hWnd, #WM_USER, @A, #Null)

	Delay(2000)
	A\KindOF = #CheckBoxGadget
	A\x      = 10
	A\y      = 70
	A\w      = 150
	A\h      = 20
	A\Text   = "A stupid checkbox"
	A\Flags  = 0
	SendMessage_(hWnd, #WM_USER, @A, #Null)

EndProcedure

Procedure Main()

	If OpenWindow(0, 200, 200, 400, 400, "Wait 2 Seconds", #PB_Window_SystemMenu) And CreateGadgetList(WindowID(0))
		SetWindowCallback(@MyCallBack())
		CreateThread(@Thread(), WindowID(0))

		Repeat
			Select WaitWindowEvent()
				Case #PB_Event_CloseWindow
					Break
			EndSelect
		ForEver
	EndIf

EndProcedure

Main()
Benutzeravatar
Ligatur
Beiträge: 196
Registriert: 09.07.2006 00:41

Beitrag von Ligatur »

Konne hat geschrieben:Weiß jemand ob es unter windows die möglichkeit gibt ein child fenster mit seinem eigenen eventloop zu erstellen? Sowas wie ein Containergadget nur halt mit eigenem Eventloop.
Möglichkeit 1:
Du benutzt das Containergadget, weist diesen mit

Code: Alles auswählen

*OldWinProc = SetWindowLong_(GadgetId(#GadNr), #GWL_WNDPROC, @NewWinProc())
deine eigene Windowprozedur zu. Am Ende der Prozedur muß stehen:

Code: Alles auswählen

ProcedureReturn CallWindowProc_(*OldWinProc, hwnd, msg, wParam, lParam)
Möglichkeit 2:
Du füllst zuerst eine WNDCLASS - Strukturvariable aus, registrierst die Fensterklasse mit RegisterClass_() und erstellst dir mit CreateWindowEx_() ein Childfenster, das Style - Flag muß dafür auf #WS_CHILD | #WS_VISIBLE gestetzt werden und der Parameter hwndParent auf dein Hauptfenster - Handle.
Benutzeravatar
AND51
Beiträge: 5220
Registriert: 01.10.2005 13:15

Beitrag von AND51 »

Ich habe es jetzt so gelöst:
Der Thread arbeitet so, wie bisher, lediglich das Erstellen der Gadgets übernimmt der Hauptthread. Ich habe einfach eine zusätzliche Abfrage in den Haupt-Eventloop gepackt:

Stellt die Abfrage fest, dass ein globales Flag gesetzt wurde uns sich die Handles der geladenen Images in einer LinkedList befinden, wird bei jedem durchlauf des Eventloops ein Gadget für ein Image erstellt.
Ich lasse nicht alle Gadgets auf einmal erstellen, weil ich denke, dass das den PC überfordert: Ich lade sehr viele BMP Bilder (bei mir 139), die 600 MB RAM beanspruchen und das 2x, denn ich kopiere jedes Bild uns verkleinere es, um das verkleinerte Bild als Vorschau dem ButtonImageGadget zuzuweisen.

Ganz zufrieden bin ich damit nicht, weil ich am liebsten alles in einer Prozedur hätte, aber das ist die beste Alternativlösung, die ich bisher gefunden habe.

@ hexor:
So ähnlich habe ich es auch gemacht. Aber nicht der Eventloop schreibt dem Thread vor, wie die Gadgets auszusehen haben, sondern umgekehrt:
In einer globalen LinkedList werden die Handles der Images einegtragen und die Positionen der künftigen Gadgets berechnet.
Durch Mutex geschützt greift die oben beschriebene Eventloop-Abfrage erst nach Abschluss des Threads ein.

Die Vorschaubilder in den ButtonImageGadgets sollen anklickbar sein; die Auswertung soll der Hauptthread übernehmen und weitere Schritte einleiten. Deshalb bringt es sowieso nichts, den Thread mit der Erstellung der Gadgets zu beauftragen.


Nebenbei: Weiß jemand, wie man ganz schnell zig BMP Bilder laden kann? Je nachdem, wie viele der User gespeichert hat (bei mir sind es 139), jedes 1280x1024, also 3,8 MB pro Bild. Gibt es einen besseren Trick als ExamineDirectory/LoadImage ?
PB 4.30

Code: Alles auswählen

Macro Happy
 ;-)
EndMacro

Happy End
Benutzeravatar
edel
Beiträge: 3667
Registriert: 28.07.2005 12:39
Computerausstattung: GameBoy
Kontaktdaten:

Beitrag von edel »

Ich möchtw aus einem Thread heraus ButtonImageGadget()s und CheckBoxGadget()s erstellen
Was macht das eigentlich fuer einen Sinn alles in Threads zu stecken ?
Kaeru Gaman
Beiträge: 17389
Registriert: 10.11.2004 03:22

Beitrag von Kaeru Gaman »

AND51 hat geschrieben:Nebenbei: Weiß jemand, wie man ganz schnell zig BMP Bilder laden kann? Je nachdem, wie viele der User gespeichert hat (bei mir sind es 139), jedes 1280x1024, also 3,8 MB pro Bild. Gibt es einen besseren Trick als ExamineDirectory/LoadImage ?
da würde ich mal unterstellen, du verfolgst irgendwie den falschen Ansatz.
welchen sinn hat es denn, alle PICs auf einmal in den Speicher zu ziehen?

was würdest du denn machen wollen, wenn der User über 400 Wallpapers hat?
Der Narr denkt er sei ein weiser Mann.
Der Weise weiß, dass er ein Narr ist.
Benutzeravatar
AND51
Beiträge: 5220
Registriert: 01.10.2005 13:15

Beitrag von AND51 »

@ edel
> Was macht das eigentlich fuer einen Sinn alles in Threads zu stecken ?
Das hat den Sinn, dass man noch eine reagierende GUI hat, während langwierige Dateioperationen durchgeführt werden.

Ich schreibe derzeit mein altes Programm neu. Damals habe ich alles ausnahmslos mit in den Eventloop reingepackt und einfach solange das Fenster gesperrt. Der Benutzer konnte also nicht abbrechen und auch keine anderen Teile des Programms aufrufen. Klickte er einmal irgendwo "Löschen" an, konnte er dies nicht aufhalten. Außerdem kann man sich leicht vorstellen, wie eine 300-Zeilen große Dateioperation den Eventloop verunstaltet (Punkt "Überscihtlichkeit").

Heute stecke ich jede Dateioperation in seine eigene Prozedur. Bei Klick auf einem Knopf wird diese Prozedur dann nicht normal, sondern gleich als Thread aufgerufen. Durch Mutexe gesperrt, kann jeweils immer nur ein Thread arbeiten. Dennoch bleibt die GUI reaktionsbereit und bietet auch noch einen "Abbrechen"-Knopf. Und nein, ich arbeite nicht mit KillThread() sondern mit globalen Abbrech-Variablen.

Doch diese neue Arbeitsweise macht es notwendig, dass alle benötigten Ressourcen vom Thread vorbereitet werden, da der Eventloop nur noch für das Reagieren auf Knopfdrücke und ähnliches bereit ist. Woher soll der Eventloop wissen, wie viele Bilder ein Thread geladen hat, um dann für jedes Bild einen ButtonImageGadget zu erstellen?

@ Kaeru:
> welchen sinn hat es denn, alle PICs auf einmal in den Speicher zu ziehen?
Es handelt sich um ein optionales Zusatztool eines Ego-Shooters "Tactical Ops".
Dieses Tool bietet unter anderem die Möglichkeit, sich die Screenshots anzusehen, die das Spiel erstellt. Das Spiel ist jedoch relativ alt, es erstellt nur Screenshots in voller Bildschirmgröße und das auch noch im BMP-Format.
Mein Tool soll alle Bilder laden, sodass sie auf Abruf sofort bereitgestellt werden. Darüber hinaus brauche ich jedes Bild ein 2. Mal, um es zu verkleinern und als Vorschaubild auf einen Button zu legen.
Der Benutzer bekommt die Möglichkeit alle oder nur ausgewählte Bilder anzusehen (Diashow, hier müssen alle Bilder sofort bereitstehen), zu exportieren (als PNG oder HTML-Gallerie) oder zu löschen.

> was würdest du denn machen wollen, wenn der User über 400 Wallpapers hat?
Ich habe in meinen Spiel ca. 139 Screenshots angesammelt. Jedes in der Größe 1280x1024, im BMP Format ist also jedes Bild 3,8 MB groß.
Ich habe glücklicherweise 2 stolze GB Arbeitsspeicher, sodass bei mir alles reibungslos geladen wird.
Andere Endbenutzer haben soetwas jedoch nicht und da mache ich mir Sorgen, wie das Programm bei denen hinterher laufen soll. Daher habe ich es momentan so gelöst, dass bei allen fehlerhaft geladenen Screenshots, das Image, das Vorschaubild und die Gadgets entfernt werden. Dies sollte funktionieren, und zwar auch dann, wenn der Benutzer nur sehr wenig RAM hat. Sollten nur die ersten 30 Bilder geladen werden, dann kriegt er halt eine Fehlermeldung und die restlichen 109 Bilder werden nicht geladen/angezeigt.

> da würde ich mal unterstellen, du verfolgst irgendwie den falschen Ansatz
Dazu kann ich momentan nichts sagen, denn mir fällt keine bessere Lösung ein, als die, die ich schon habe. Das mag vielleicht etwas eingebildet klingen, aber ich finde, dass ich mich (verglichen mit der Vorgängerversion des Programms) um ein Vielfaches verbessert habe. Ich finde das Programm in der neuen Version tausend mal besser, als das die älteren Versionen und bin auch nochstolz darauf!
Trotzdem bin ich auch nur ein Mensch, der sich entwickelt und Erfahrungen sammelt. Bisher erschein mir einfach keine bessere Lösung für all meine Probleme (z. B. dem Problem, aus Threads heraus Gadgets zu erstellen). Dazu nehme ich einfach eine weitere globale LinkedList, berechne die Position der Gadgets vor. Wenn das Mutex wieder freigegeben ist, greift der Eventloop bei jedem Durchlauf ein Element dieser LinkedList ab und erstelt entsprechend der Vorgaben das Gadget. Es ist zwar nicht meine Traumlösung, aber besser als... nichts!
PB 4.30

Code: Alles auswählen

Macro Happy
 ;-)
EndMacro

Happy End
Antworten