Difficulty opening window in a thread PB5.11

Windows specific forum
RichardL
Enthusiast
Enthusiast
Posts: 532
Joined: Sat Sep 11, 2004 11:54 am
Location: UK

Difficulty opening window in a thread PB5.11

Post by RichardL »

Good morning,

I have a project in which I open a Window with it's associated event handler in a thread.
(The code manages the download and display of a Google Map)
This is code I have used for some time and it runs under 5.11 when compiled directly, including showing the map/s.
When I compile it with the debugger the code runs OK up to the point where the thread attempts to open a window and I get the message:
<<OpenWindow() can only be called from the main thread>>

I cannot imagine this is an overlooked bug going back for a long time because I have compiled the program many times under earlier versions of PB and I must have used the debugger on numerous occassions.
I have only recently made a move from 4.6 to 5.11 and cannot easily go back to check due to issues with PurePDF that I now need to use as an IncludeFile with 5.11

Yes... I'm Thread safe!

Any suggestions would be appreciated.
RichardL
Last edited by RichardL on Sat Jun 08, 2013 8:56 am, edited 1 time in total.
c4s
Addict
Addict
Posts: 1981
Joined: Thu Nov 01, 2007 5:37 pm
Location: Germany

Re: Difficulty opening window in a thread PB5.11

Post by c4s »

There has been a lengthy discussion about the reason behind that change in PB 5.10. You can use this workaround to make PB work as before:

Code: Select all

CompilerIf #PB_Compiler_Version >= 510 And #PB_Compiler_Debugger
Procedure _OpenWindow(Window, x, y, InnerWidth, InnerHeight, Title$, Flags=0, ParentID=0)
	Protected Result = 0

	DisableDebugger
	Result = OpenWindow(Window, x, y, InnerWidth, InnerHeight, Title$, Flags, ParentID)
	EnableDebugger

	ProcedureReturn Result
EndProcedure

Macro OpenWindow(Window, x, y, InnerWidth, InnerHeight, Title, Flags=0, ParentID=0)
	_OpenWindow(Window, x, y, InnerWidth, InnerHeight, Title, Flags, ParentID)
EndMacro

Procedure _WaitWindowEvent(Timeout=#PB_Default)
	Protected Result = 0

	DisableDebugger
	Result = WaitWindowEvent(Timeout)
	EnableDebugger

	ProcedureReturn Result
EndProcedure

Macro WaitWindowEvent(Timeout=#PB_Default)
	_WaitWindowEvent(Timeout)
EndMacro

Procedure _WindowEvent()
	Protected Result = 0

	DisableDebugger
	Result = WindowEvent()
	EnableDebugger

	ProcedureReturn Result
EndProcedure

Macro WindowEvent()
	_WindowEvent()
EndMacro
CompilerEndIf
...Just put this on top of your code. No other changes are needed. 8)
If any of you native English speakers have any suggestions for the above text, please let me know (via PM). Thanks!
infratec
Always Here
Always Here
Posts: 6873
Joined: Sun Sep 07, 2008 12:45 pm
Location: Germany

Re: Difficulty opening window in a thread PB5.11

Post by infratec »

Hi,

since there is a reason for that warning, I rewritten my programs to use PostEvent().
You can send events to the main loop which opens and close the window for you.

Bernd
Fred
Administrator
Administrator
Posts: 16684
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: Difficulty opening window in a thread PB5.11

Post by Fred »

infratec wrote:Hi,

since there is a reason for that warning, I rewritten my programs to use PostEvent().
You can send events to the main loop which opens and close the window for you.

Bernd
That's the way to go, and will work everywhere, not only on Windows.
User avatar
doctorized
Addict
Addict
Posts: 856
Joined: Fri Mar 27, 2009 9:41 am
Location: Athens, Greece

Re: Difficulty opening window in a thread PB5.11

Post by doctorized »

I have a thread and sometimes it is needed to show a window to get some data from the user.
Which is the best way to open the window? PostEvent() is good only when we have the window open.
In my case the window is not open as is it not sure that we will need it. Is the code from c4s the best for me?
Fred
Administrator
Administrator
Posts: 16684
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: Difficulty opening window in a thread PB5.11

Post by Fred »

Better use PostEvent() with a custom event to actually open your window.
User avatar
doctorized
Addict
Addict
Posts: 856
Joined: Fri Mar 27, 2009 9:41 am
Location: Athens, Greece

Re: Difficulty opening window in a thread PB5.11

Post by doctorized »

Fred wrote:Better use PostEvent() with a custom event to actually open your window.
How can I do it when the window is not present? WaitWindowEvent() cannot be used, so? A little help?
Fred
Administrator
Administrator
Posts: 16684
Joined: Fri May 17, 2002 4:39 pm
Location: France
Contact:

Re: Difficulty opening window in a thread PB5.11

Post by Fred »

You can just have one invisible window somewhere at program start
Tranquil
Addict
Addict
Posts: 950
Joined: Mon Apr 28, 2003 2:22 pm
Location: Europe

Re: Difficulty opening window in a thread PB5.11

Post by Tranquil »

Sorry for bringing up this old thread but I'm actually out of ideas.

Having the same issue.

RichardL:
How did you solve the communication back to the Thread out of the main thread?
Tranquil
auser
Enthusiast
Enthusiast
Posts: 195
Joined: Wed Sep 06, 2006 6:59 am

Re: Difficulty opening window in a thread PB5.11

Post by auser »

I did not found a good solution for that issue as well. PostEvent() does not really help (or it just helps in few cases and only in one direction).

If you need some new thread that is similar to a new process as if you click on "something.exe" in windows-explorer and the only information that have to be shared regarding mainthread and the subthread is some memory at start-time of the thread then PostEvent() is the wrong direction and even does not really help to make the code slimmer.

Example which worked in the past:

Code: Select all

; MainThread
Repeat 
  Select WindowEvent()
    Case #PB_Event_Gadget
      If EventGadget() = SomeListGadget
        *Copy_Data_From_List_Gadget = GetSomeData(SomeListGadget) 
        OpenThread(@MyThread(),*Copy_Data_From_List_Gadget)
      EndIf
  EndSelect
Forever

; Thread that does not care regarding Mainthread anymore
Procedure MyThread(*Copy_Data_From_List_Gadget)
  Init_and_UseData(*Copy_Data_From_List_Gadget)
  FreeData(*Copy_Data_From_List_Gadget)
  
  window = OpenWindow(#PB_Any, ...)
  individual_gadget = SomeGadget(#PB_Any, ...)
  outstanding_gadget = SomeGadget(#PB_Any, ...)
  
  Repeat 
    
    ; Do something 

    Select WindowEvent()
      Case #PB_Event_Gadget
        Select EventGadget()
          Case individual_gadget
            *furtherstuff = OpenFurtherWindow_1(window) ; Lock window of this thread (only) until user chooses something from further window
          Case outstanding_gadget
            DoSomething()
        EndSelect 
      Case #PB_Event_CloseWindow
        StopMe()
    EndSelect
  Forever
EndProcedure
Example after the change regarding Window handling:

Code: Select all

; MainThread - and Thread for threads
Repeat 
window_event = WindowEvent()
If window_event 
  event_window = EventWindow()
  If event_window = MainWindow 
    If window_event = #PB_Event_Gadget
      If EventGadget() = SomeListGadget
        *various.stuff = AllocateMemory(SizeOf(stuff))
        InitialzieStructure(*various,stuff)
        *various\Copy_Data_From_List_Gadget = GetSomeData(SomeListGadget)
        *various\mutex = CreateMutex()
        *various\EventQueuePtr = AllocateMemory(somesize)
        AddElement(DynamicNumberOfWindows())
        DynamicNumberOfWindows()\variousPtr = *various
        DynamicNumberOfWindows()\window = OpenWindow(#PB_Any, ...)
        *various\window = DynamicNumberOfWindows()\window 
        DynamicNumberOfWindows()\unnamed_gadget1 = SomeGadget(#PB_Any, ...)
        DynamicNumberOfWindows()\unnamed_gadget2 = SomeGadget(#PB_Any, ...)
        DynamicNumberOfWindows()\type = DependsOnClick
        ; ... and lots more
        OpenThread(@MyThread(),*various)
      EndIf 
    EndIf
  Else ; Nightmare
    ForEach DynamicNumberOfWindows()
      If DynamicNumberOfWindows()\window = event_window
        Select DynamicNumberOfWindows()\type 
          Case #Window_Type1
            If window_event = #PB_Event_Gadget
              Select EventGadget()
                Case DynamicNumberOfWindows()\unnamed_gadget1
                  ; Blocking now - everything!
                  LockMutex(DynamicNumberOfWindows()\mutex)
                  DynamicNumberOfWindows()\variousPtr\furtherstuffPtr = OpenFurtherWindow_1(DynamicNumberOfWindows()\window)
                  AddToQueue(DynamicNumberOfWindows()\EventQueuePtr,#Look_at_your_pointer_Event)
                  UnlockMutex(DynamicNumberOfWindows()\mutex)
                Case DynamicNumberOfWindows()\unnamed_gadget2
                  LockMutex(DynamicNumberOfWindows()\mutex)
                  AddToQueue(DynamicNumberOfWindows()\EventQueuePtr,#DoSomething_Window_Whatever_Event)
                  UnlockMutex(DynamicNumberOfWindows()\mutex)
              EndSelect
            ElseIf window_event = #PB_Event_CloseWindow
              LockMutex(DynamicNumberOfWindows()\mutex)
              AddToQueue(DynamicNumberOfWindows()\EventQueuePtr,#CloseEvent)
              UnlockMutex(DynamicNumberOfWindows()\mutex)
            ElseIf window_event = #PostClose
              FreeMutex(DynamicNumberOfWindows()\mutex)
              ClearStructure(*various,stuff)
              FreeMemory(*various)
              CloseWindow(DynamicNumberOfWindows()\window)
            EndIf 
          Case #Window_Type2 
            ; Increase spagetti-code with 30 further gadgets of window 2 again...
        EndSelect 
      EndIf		
    Next 
  EndIf 
EndIf
Forever  


; Thread that have to care regarding mainthread unwillingless
Procedure MyThread(*various.stuff)
  Init_and_UseData(various\Copy_Data_From_List_Gadget)
  
  Repeat 
   
   ; Do something 
  
    LockMutex(*various\mutex) 
    event = ReadQueue(*various\EventQueuePtr)
    UnlockMutex(*various\mutex)
    Select event 
      Case 0
        Continue
      Case #Look_at_your_pointer_Event
        LockMutex(*various\mutex) 
        *furtherstuff = CopyStuff(*various\furtherstuffPtr)
        FreeStuff(*various\furtherstuffPtr)
        UnlockMutex(*various\mutex)
      Case #DoSomething_Window_Whatever_Event
        DoSomething()
      Case #CloseEvent
        CleanUp()
        LockMutex(*various\mutex)
        PostEvent(#PostClose,*various\window)
        ProcedureReturn(0)
    EndSelect
  ForEver 
 
EndProcedure

The code is much much longer, much more failure-sensitive and still does not handle everything as in the past. There is no advantage just a big step backwards. So I kept the code and ended with something like this (ugly):

Code: Select all

;{ Bähhh :P
Procedure __OpenWindow(WinID,x,y,InnerWidth,InnerHeight,Title.s,Flags=0,ParentID=0)
  Protected retval.i
  
  DisableDebugger
  retval = OpenWindow(WinID,x,y,InnerWidth,InnerHeight,Title.s,Flags,ParentID)
  EnableDebugger
  
  ProcedureReturn(retval)
EndProcedure
Procedure __WindowEvent()
  Protected retval.i
  
  DisableDebugger
  retval = WindowEvent()
  EnableDebugger
  
  ProcedureReturn(retval)
EndProcedure
Procedure __WaitWindowEvent(Timeout=#PB_Default)
  Protected retval.i
  
  DisableDebugger
  retval = WaitWindowEvent(Timeout)
  EnableDebugger
  
  ProcedureReturn(retval)
EndProcedure
;}
Dude
Addict
Addict
Posts: 1907
Joined: Mon Feb 16, 2015 2:49 pm

Re: Difficulty opening window in a thread PB5.11

Post by Dude »

auser wrote:The code is much much longer, much more failure-sensitive and still does not handle everything as in the past. There is no advantage just a big step backwards.
I agree. This non-window events inside threads is terrible. My app lets the user create any number of custom windows that they desire, and each window must respond to their own events. Each such window is created inside a thread at runtime, whenever the user wants.

But, this thread issue totally stops this type of functionality from being possible. Yes, I know it'll work with the final exe, but it means I can't debug my app because when the window event commands are reached, the compiler quits with an error. Very annoying.

Yes, I know I can wrap all window stuff with DisableDebugger and EnableDebugger, but frankly, that's an unnecessary workaround and totally impractical when there's so many events to wrap; even more so if you're including files that have windows with events inside them (such as custom input boxes).

No, I can't create all the windows invisibly first and then just show them when needed, because as I said, there's X number of them with different styles. You can't guess what type of window the user is going to create, or how many of them. It could be 1 window, or 1000, and each must have their own event loop and act independently of others.

So, I respectfully ask that either: (a) window opening and events be allowed inside threads again on Microsoft Windows, or if that's impossible, then (b) the compiler internally Disable/Enable the debugger for these commands when the OS is Windows and the exe is being debugged from the IDE.

Thank you.
User avatar
bamsagla
User
User
Posts: 62
Joined: Sat Jan 30, 2010 10:10 am
Location: Laufen, Bavaria, Germany

Re: Difficulty opening window in a thread PB5.11

Post by bamsagla »

Hi everyone,

here's an example how i unterstood window threading should working; the following is only a quick example but should be threadsafe and also safe to close any subwindow even in the threaded event loop. Hopefully this is a proper native PureBasic solution and this is helpful for someone.

Bye, Harry.

Code: Select all

EnableExplicit

; Creating structure for subwindow info
Structure wininfo
	num.i
	gd1.i
	gd2.i
	gd3.i
EndStructure

; Setting global and local variables
Global NewMap subwin.wininfo()
Global globalend.i, main.i, thread.i, syncer
Define events.i, evewin.i, evegad.i, evtype.i, evdata.i, newwin.i, title.s, wincnt.i, gd1.i, gd2.i, gd3.i, t.i

; Creating mutex for use of the global map
syncer = CreateMutex()

; Creating own PostEvents
Enumeration #PB_Event_FirstCustomValue
  #EventNewWindow
  #EventCloseWindow
  #EventAddItem
EndEnumeration

; Creating own PostEventTypes
Enumeration #PB_EventType_FirstCustomValue
  #EventItemCount
EndEnumeration

; Thread procedure with stuff unrelated from the main run with events
Procedure.i somestuff(beginn.i)
	Define.i cycles
	While globalend = 0
		Select cycles
			Case 5000 ; after about 5 seconds window opens
				PostEvent (#EventNewWindow)
			Case 10000 ; after about 10 seconds window opens
				PostEvent (#EventNewWindow)
			Case 12000 ; after about 12 seconds post a value to a specific window
				LockMutex(syncer)
				If Not FindMapElement(subwin(), "Test - subwindow 2") = 0
					PostEvent (#EventAddItem, subwin()\num, subwin()\gd2, #EventItemCount, 2)
				EndIf
				UnlockMutex(syncer)
			Case 15000 ; after about 15 seconds first window closes
				LockMutex(syncer)
				If Not FindMapElement(subwin(), "Test - subwindow 1") = 0
					PostEvent (#EventCloseWindow, subwin()\num, 0)
				EndIf
				UnlockMutex(syncer)
			Case 20000 ; after about 20 seconds second window closes
				LockMutex(syncer)
				If Not FindMapElement(subwin(), "Test - subwindow 2") = 0
					PostEvent (#EventCloseWindow, subwin()\num, 0)
				EndIf
				UnlockMutex(syncer)
		EndSelect
		; Silly way for adding timers in thread
		cycles + 1
		Delay(1)
		; After timed loop thread is beeing continued until main window closes
	Wend
EndProcedure

; Main window creation
main = OpenWindow(#PB_Any, 0, 0, 400, 300, "Test - main window", #PB_Window_SystemMenu)

; Thread is starting
thread = CreateThread(@somestuff(), 0)

; Main Event loop for all windows
While globalend = 0
	events = WaitWindowEvent(2000) ; Waits for all events, also for Thread-Events
	evewin = EventWindow()
	Select events
		; Create new window - event from thread
		Case #EventNewWindow
			wincnt + 1
			title = "Test - subwindow " + Str(wincnt)
			newwin = OpenWindow(#PB_Any, 50 * wincnt, 50 * wincnt, 700, 300, title, #PB_Window_SystemMenu, WindowID(main))
			If Not newwin = 0
				gd1 = ListViewGadget(#PB_Any, 10, 10, 200, 100)
				gd2 = ListViewGadget(#PB_Any, 220, 10, 200, 100)
				gd3 = ListViewGadget(#PB_Any, 430, 10, 200, 100)
				LockMutex(syncer)
				AddMapElement(subwin(), title)
				subwin()\num = newwin : subwin()\gd1 = gd1 : subwin()\gd2 = gd2 : subwin()\gd3 = gd3
				UnlockMutex(syncer)
			EndIf
		; Close window - event from thread
		Case #EventCloseWindow
			If IsWindow(evewin) : CloseWindow(evewin) : EndIf
			LockMutex(syncer)
			If subwin()\num = evewin
				DeleteMapElement(subwin())
			Else
				ForEach subwin() : If subwin()\num = evewin : DeleteMapElement(subwin()) : Break : EndIf : Next
			EndIf
			UnlockMutex(syncer)
		; Add gadget items - event from thread
		Case #EventAddItem
			evegad = EventGadget()
			evtype = EventType()
			evdata = EventData()
			Select evtype
				Case #EventItemCount
					If IsGadget(evegad) : For t = 1 To evdata : AddGadgetItem(evegad, -1, "Item " + Str(CountGadgetItems(evegad))) : Next : EndIf
			EndSelect
		; If window has been closed
		Case #PB_Event_CloseWindow
			If IsWindow(evewin) : CloseWindow(evewin) : EndIf
			; If main window has been closed; set ending paramter
			If evewin = main : globalend = 1 : EndIf
			; If sub window has been closed; remove from subwindow map
			LockMutex(syncer)
			ForEach subwin() : If subwin()\num = evewin : DeleteMapElement(subwin()) : Break : EndIf : Next
			UnlockMutex(syncer)
	EndSelect
Wend

; Thread will be ended automatically - we should wait 5 seconds
If IsThread(thread)
	WaitThread(thread, 5000)
	If IsThread(thread) : KillThread(thread) : EndIf
EndIf
FreeMutex(syncer)

End
- Sherlock Holmes - "When you have eliminated the impossible, whatever remains, however improbable, must be the truth."
In my opinion, he must have been a programmer.
Post Reply