Raising custom events

Just starting out? Need help? Post your questions and find answers here.
Foz
Addict
Addict
Posts: 1359
Joined: Tue Nov 13, 2007 12:42 pm
Location: Manchester, UK

Raising custom events

Post by Foz »

What's the best way of raising events for a multithreaded program?

I have a gui that that is calling a back end that just slogs away until it's done.

I have coded the back end so it is entirely separate from a gui, but I would like to now get some responses to the gui - namely some progress bar updating and when it has finished (so I can re-enable the controls).

However, I don't know what the best way of slotting in such a method.

The easiest method would be to hard code the gui updates in the

I'm thinking of using a Linked List which I can then add to for the messages of what's happened to the end, but I can keep polling the first item and then remove it once read - which is similar to how the existing gui events are generated.

Do you guys have any other ideas?

This was what I had in mind as a test, but the progress bar just doesn't work, but the reenabling of the button does:

Code: Select all

; CUSTOM EVENTS

Global NewList CustomOwnEvents.b()
Global NewList CustomOwnEventValues.l()
Global CustomOwnEvents_Mutex

Procedure RaiseEvent(EventType.b, EventValue.l)
  LockMutex(CustomOwnEvents_Mutex)

  LastElement(CustomOwnEvents())
  AddElement(CustomOwnEvents())
  CustomOwnEvents() = EventType
  FirstElement(CustomOwnEvents())

  LastElement(CustomOwnEventValues())
  AddElement(CustomOwnEventValues())
  CustomOwnEventValues() = EventValue
  FirstElement(CustomOwnEventValues())

  UnlockMutex(CustomOwnEvents_Mutex)
EndProcedure

Procedure.b GetEvents()
  If CountList(CustomOwnEvents()) > 0
    Protected e.b = CustomOwnEvents()

    DeleteElement(CustomOwnEvents())
    FirstElement(CustomOwnEvents())
  EndIf
  
  ProcedureReturn e
EndProcedure

Procedure.l GetEventValues()
  If CountList(CustomOwnEventValues()) > 0
    Protected e.l = CustomOwnEventValues()

    DeleteElement(CustomOwnEventValues())
    FirstElement(CustomOwnEventValues())
  EndIf
  
  ProcedureReturn e
EndProcedure

; BACKEND WORK

Procedure Test(stepper.l)
  Protected count.l = 0

  RaiseEvent(1, 100)  
  
  Repeat
    count + stepper

    RaiseEvent(2, count)
    
    Delay(10)
  Until count = 100
  
  RaiseEvent(3, 1)
EndProcedure


; GUI WORK

Enumeration
  #Window_0
EndEnumeration

Enumeration
  #ProgressBar_0
  #Button_1
EndEnumeration


Procedure Open_Window_0()
  If OpenWindow(#Window_0, 216, 0, 600, 300, "New window ( 0 )",  #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_TitleBar )
    If CreateGadgetList(WindowID(#Window_0))
      ProgressBarGadget(#ProgressBar_0, 100, 150, 400, 20, 0, 10)
      ButtonGadget(#Button_1, 230, 200, 140, 50, "")
      
    EndIf
  EndIf
EndProcedure

CustomOwnEvents_Mutex = CreateMutex()

Open_Window_0()

Define CustomEvents.b
Define CustomEventValues.l

Repeat
  Event = WaitWindowEvent()
  WindowID = EventWindow()
  GadgetID = EventGadget()
  EventType = EventType()

  LockMutex(CustomOwnEvents_Mutex)
  CustomEvents = GetEvents()
  CustomEventTypes = GetEventValues()
  UnlockMutex(CustomOwnEvents_Mutex)
  
  If Event = #PB_Event_Gadget
    If GadgetID = #Button_1
      DisableGadget(#Button_1, #True)
      CreateThread(@Test(), 1)
    EndIf
  EndIf
  
  Select CustomEvents
  Case 1
    SetGadgetAttribute(#ProgressBar_0, #PB_ProgressBar_Maximum, CustomEventValues)
  Case 2
    SetGadgetState(#ProgressBar_0, CustomEventValues)
  Case 3
      DisableGadget(#Button_1, #False)
  EndSelect
Until Event = #PB_Event_CloseWindow
User avatar
netmaestro
PureBasic Bullfrog
PureBasic Bullfrog
Posts: 8453
Joined: Wed Jul 06, 2005 5:42 am
Location: Fort Nelson, BC, Canada

Post by netmaestro »

I'd just use normal messaging:

Code: Select all

#Custom_Event_1 = #WM_APP+1
#Custom_Event_2 = #WM_APP+2
#Custom_Event_3 = #WM_APP+3

Procedure Thread(void)
  Repeat
    PostMessage_(WindowID(0), #Custom_Event_1, 10, 10)
    Delay(1000)
    PostMessage_(WindowID(0), #Custom_Event_2, 20, 20)
    Delay(1000)
    PostMessage_(WindowID(0), #Custom_Event_3, 30, 30) 
    Delay(1000)
  ForEver
EndProcedure

OpenWindow(0,0,0,320,240,"",#PB_Window_SystemMenu|#PB_Window_ScreenCentered)
tid = CreateThread(@Thread(),0)

quit=0
Repeat
  EventID=WaitWindowEvent()
  Select EventID
    Case #PB_Event_CloseWindow
      quit=1
    Case #Custom_Event_1
      Debug "Custom Event 1"
      Debug EventwParam()
      Debug EventlParam()
    Case #Custom_Event_2
      Debug "Custom Event 2"
      Debug EventwParam()
      Debug EventlParam()
    Case #Custom_Event_3
      Debug "Custom Event 3"
      Debug EventwParam()
      Debug EventlParam()
  EndSelect
Until quit

KillThread(tid)
End
This also makes available wParam and lParam to you for sending further information with the message, which is often useful.
BERESHEIT
Foz
Addict
Addict
Posts: 1359
Joined: Tue Nov 13, 2007 12:42 pm
Location: Manchester, UK

Post by Foz »

That's pretty nifty, it's a pity that it's Window's only though...

Does anyone have suggestions for a cross platform solution ?

If not, I'll use this or persevere with my previous idea.
Foz
Addict
Addict
Posts: 1359
Joined: Tue Nov 13, 2007 12:42 pm
Location: Manchester, UK

Post by Foz »

Hunting through my code revealed a couple of nasty mistakes. It works now (code below).

However, is it the best way of doing it cross platform ?

Code: Select all

;{ CUSTOM EVENTS

Global NewList CustomOwnEvents.b()
Global NewList CustomOwnEventValues.l()
Global CustomOwnEvents_Mutex

Procedure RaiseEvent(EventType.b, EventValue.l)
  LockMutex(CustomOwnEvents_Mutex)

  LastElement(CustomOwnEvents())
  AddElement(CustomOwnEvents())
  CustomOwnEvents() = EventType
  FirstElement(CustomOwnEvents())

  LastElement(CustomOwnEventValues())
  AddElement(CustomOwnEventValues())
  CustomOwnEventValues() = EventValue
  FirstElement(CustomOwnEventValues())

  UnlockMutex(CustomOwnEvents_Mutex)
EndProcedure

Procedure.b GetEvents()
  If CountList(CustomOwnEvents()) > 0
    Protected e.b = CustomOwnEvents()

    DeleteElement(CustomOwnEvents())
    FirstElement(CustomOwnEvents())
  EndIf
 
  ProcedureReturn e
EndProcedure

Procedure.l GetEventValues()
  If CountList(CustomOwnEventValues()) > 0
    Protected e.l = CustomOwnEventValues()

    DeleteElement(CustomOwnEventValues())
    FirstElement(CustomOwnEventValues())
  EndIf
 
  ProcedureReturn e
EndProcedure
;}

;{ BACKEND WORK

Procedure Test(stepper.l)
  Protected count.l = 0

  RaiseEvent(1, 100) 
 
  Repeat
    count + stepper

    RaiseEvent(2, count)
   
    Delay(10)
  Until count = 100
 
  RaiseEvent(3, 1)
EndProcedure
;}

;{ GUI WORK
Enumeration
  #Window_0
EndEnumeration

Enumeration
  #ProgressBar_0
  #Button_1
EndEnumeration


Procedure Open_Window_0()
  If OpenWindow(#Window_0, 216, 0, 600, 300, "New window ( 0 )",  #PB_Window_SystemMenu | #PB_Window_SizeGadget | #PB_Window_TitleBar )
    If CreateGadgetList(WindowID(#Window_0))
      ProgressBarGadget(#ProgressBar_0, 100, 150, 400, 20, 0, 10)
      ButtonGadget(#Button_1, 230, 200, 140, 50, "")
     
    EndIf
  EndIf
EndProcedure

CustomOwnEvents_Mutex = CreateMutex()

Open_Window_0()

Define CustomEvents.b
Define CustomEventValues.l

Repeat
  Event = WindowEvent()
  WindowID = EventWindow()
  GadgetID = EventGadget()
  EventType = EventType()

  If Event = #PB_Event_Gadget
    If GadgetID = #Button_1
      DisableGadget(#Button_1, #True)
      CreateThread(@Test(), 1)
    EndIf
  EndIf
 
  LockMutex(CustomOwnEvents_Mutex)
  CustomEvents = GetEvents()
  CustomEventValues = GetEventValues()
  UnlockMutex(CustomOwnEvents_Mutex)
 
  Select CustomEvents
  Case 1
    SetGadgetAttribute(#ProgressBar_0, #PB_ProgressBar_Maximum, CustomEventValues)
  Case 2
    SetGadgetState(#ProgressBar_0, CustomEventValues)
  Case 3
      DisableGadget(#Button_1, #False)
  EndSelect

Until Event = #PB_Event_CloseWindow
;}
Psych
Enthusiast
Enthusiast
Posts: 239
Joined: Thu Dec 18, 2008 3:35 pm
Location: Wales, UK

Post by Psych »

I do like that, although I would put the value pairs in a structure and have just the one list, I am always wary of maintaining sync between 2 lists.
I'm new to this language, having done all my stuff with vb.net, is there a way to have a generic object in purebasic (a variable that will accept anything, a variant)? You could then have 1 structure containing an event ID, and a variant containg parameter information. Maybe a parramarray() of sorts? Of course, you don't need this for your example, I am just thinking of a way of taking this forward to make 1 handler for every type of event.
Code it once then apply it to anything you want in the future.
I'll certainly be using this method for handling events, thanks.
AND51
Addict
Addict
Posts: 1040
Joined: Sun Oct 15, 2006 8:56 pm
Location: Germany
Contact:

Post by AND51 »

@ netmaestro
Can oyu tell me more about #WM_APP? How large is the range, can you add +100 or +1000 if you want?
PB 4.30

Code: Select all

onErrorGoto(?Fred)
User avatar
Fluid Byte
Addict
Addict
Posts: 2336
Joined: Fri Jul 21, 2006 4:41 am
Location: Berlin, Germany

Post by Fluid Byte »

PSDK (R2) wrote:Image
Windows 10 Pro, 64-Bit / Whose Hoff is it anyway?
AND51
Addict
Addict
Posts: 1040
Joined: Sun Oct 15, 2006 8:56 pm
Location: Germany
Contact:

Post by AND51 »

Ah, cool!
But what means "private window classes"? Is this my own created windows?
Otherweise, I can't see a difference between #WM_APP and #WM_USER at the moment...
PB 4.30

Code: Select all

onErrorGoto(?Fred)
User avatar
Fluid Byte
Addict
Addict
Posts: 2336
Joined: Fri Jul 21, 2006 4:41 am
Location: Berlin, Germany

Post by Fluid Byte »

AND51 wrote:But what means "private window classes"? Is this my own created windows?
Indeed. If you create a custom window class via API or want expand PB's message range you can use #WM_USER. But these only work within your own private window callback. They don't work outside your application because some other common controls may use them. So if you want a global message that even works outside your program you should use #WM_APP to avoid conflicts.
PSDK (R2) wrote:Message numbers in the first range (0 through WM_USER–1) are defined by the system. Values in this range that are not explicitly defined are reserved by the system.

Message numbers in the second range (WM_USER through 0x7FFF) can be defined and used by an application to send messages within a private window class. These values cannot be used to define messages that are meaningful throughout an application, because some predefined window classes already define values in this range. For example, predefined control classes such as BUTTON, EDIT, LISTBOX, and COMBOBOX may use these values. Messages in this range should not be sent to other applications unless the applications have been designed to exchange messages and to attach the same meaning to the message numbers.

Message numbers in the third range (0x8000 through 0xBFFF) are available for application to use as private messages. Message in this range do not conflict with system messages.
Windows 10 Pro, 64-Bit / Whose Hoff is it anyway?
User avatar
Kaeru Gaman
Addict
Addict
Posts: 4826
Joined: Sun Mar 19, 2006 1:57 pm
Location: Germany

Post by Kaeru Gaman »

ermn... so the Range WM_APP thru $BFFF is systemwide but for all apps?

means, if another app with own WMs is running, the conflict is programmed...?
oh... and have a nice day.
User avatar
idle
Always Here
Always Here
Posts: 6240
Joined: Fri Sep 21, 2007 5:52 am
Location: New Zealand

Post by idle »

if you need to have a unique message you can always use RegisterWindowMessage

I don't know of a cross platform way to handle events but this looks interesting

http://www.codeproject.com/KB/cpp/Cross ... _Loop.aspx
Post Reply