Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Mike Lewis on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

How to respond more the one time to one event? 1

Status
Not open for further replies.

Jan Flikweert

Programmer
Mar 20, 2022
85
NL
Hi all,

How to respond more then one time on one event?

I made a button class with BINDEVENT in the init method.

I use 154 buttons based on this class. Only one button responds on this bindevent.


Kind regards,

Jan Flikweert
 
What's your code?
When the buttons should respond to the event they are the delegates.
So what is the event source? I guess some midi event?

In short: It works that one event has multiple delegates. Here's a demo

Let's do a virtual keyboard where one key is capslock and all buttons bind to its click to respond to the capslock state change each for itself.
Each button displays its caption in the current lower or upper case, without making that a for loop in the capslock button so it sets all other button captions, every button cares for itself by binding to the capslock click event.

Code:
Local lnLeft, lnTop, lnOffset
lnOffset = 10
lnLeft = 0
lnTop = 0

_Screen.Addobject('keycapslock','capslockkey')
With _Screen.keyCapslock
       .Top = lnOffset+ lnTop
       .Left = lnOffset+ lnLeft
       .Width = 80
       lnLeft = lnLeft + 90
       .Visible = .T.
EndWith

For lnChar = Asc('a') to Asc('z')
    _screen.AddObject('key'+Chr(lnChar),'key')
    With Evaluate('_screen.key'+Chr(lnChar))
       .Top = lnOffset+lnTop
       .Left = lnOffset+lnLeft
       .Visible = .t.
       lnLeft = lnLeft+40
       If lnLeft>360
          lnLeft=0
          lnTop = lnTop + 40
       Endif
    EndWith    
EndFor 

_screen.AddObject('edtText','Editbox')
With _screen.edtText
   .Top = lnOffset+lnTop+40
   .Left = lnOffset
   .Width = 440
   .Height = 300
   .Visible = .T.
EndWith

Define Class key as CommandButton 
   Width=30
   Height=30

   Procedure Init(tcCaption)
      This.Caption = Substr(This.Name,4)
      BindEvent(_screen.KeyCapslock,"Click",This,"DisplayCapsState",1)
      This.DisplayCapsState()
   EndProc 
   
   Procedure Click()
      _screen.edtText.Value = _screen.edtText.Value + This.Caption  
   EndProc 

   Procedure DisplayCapsState()
      This.Caption = Iif(CapsLock(),Upper(This.Caption),Lower(This.Caption))
   EndProc 
EndDefine

Define Class capslockkey as key
   Procedure Click()
      =Capslock(Not Capslock())
   EndProc
EndDefine

The major feature demonstrated here is that clicking on the CAPSLOCK button does switch all other buttons captions to upper or lower case.
And that's using 26 bindings, 1 of each of the A-Z buttons to the same event source, the _screen.KeyCapslock click event.

Chriss
 
Jan, as Chris has demonstrated, there is no reason not to have more than one delegate for a given source.

If you could post your code, we can try to see what's going wrong. (Just post the minimum code to reproduce the problem; remove any kind not directly related to it.)

Mike


__________________________________
Mike Lewis (Edinburgh, Scotland)

Visual FoxPro articles, tips and downloads
 
Hi Chriss,

Thanks for your reply. Your guess is right: I am working with MIDI in this case to add Linuxsampler. The pro to Garritan Aria player is that it is more stable. The con is it is Open Source, so in case the program needs changes you depend on someone willing to solve the problems.

Your example is correct, and I added ,1 with bindevent.

The difference with your example is I want to let bindevents respond on all buttons at one time.

I suppose that is something which can not be done.

After every Bindevent it returns 1. It does not add a binding. I suppose I can not add a binding?

Kind regards,

Jan Flikweert

 
Chriss,

Here the relevant part of my code.

Code:
ptrCallback_cme=Thisform.HWnd
******Connecting to midi input and defining window callback*****
nResult = midiInOpen(@hDevice, nCmeIn-1, ptrCallback_cme, 0, CALLBACK_WINDOW+MIDI_IO_STATUS)
arrInHandles(1,1)=nCmeIn
arrInHandles(2,1)=hDevice
nResult2 = midiInStart(arrInHandles(2,1))


******Click event from VCX class stops which is used by 154 stops**********
******More then one stop will be used simultaniously****************
IF this.cme_mpl="CME" Then
	?Bindevent(ptrCallback_cme, MIM_DATA,This,'hndlvnt_cme',1)
Else
	?Bindevent(ptrCallback_mpl, MIM_DATA,This,'hndlvnt_mpl',1)
Endif

Kind regards,


Jan Flikweert
 
The difference with your example is I want to let bindevents respond on all buttons at one time.
How's that different. All buttons react instantly. Of course one after another, but from a human's perspective all captions change at the same time, don't they?

The,1 is just enforcing the event (the click) before the delegate code runs. And in my case that is necessary as I want the capslock switching before each button reacts with the new CAPSLOCK() aleady set. In other situations it can be necessary to wait with the actual event unless the delegate or all delegates are done. It has no influence on there being multiple delegates possible.


I think I see what you mean with only seeing ones from that. BINDEVENT() returns the number of bindings for the object's event. Well, you bind to two things, two different HWNDS. I also think even when you bind to the same HWND, that's not a VFP object. It's the combination of object and event that has to be the same to increment the binds counter, if you look at it like that.

Where is this code? It's not all in the init of each button, is it? You only init midi once, don't you? Not in each button?

Chriss
 
Chriss,

Indeed I init midi once loading the form. I suppose you mean that MIDI should be init for each button, actually that is 154 times?

Indeed I activate the stop button one at a time. But be aware the callback should fire on for each stop but keeps running while pressing a midi key.

I am going to try that.

Kind regards,

Jan Flikweert
 
Indeed I init midi once loading the form. I suppose you mean that MIDI should be init for each button, actually that is 154 times?
No, your code is one block, which suggests each button does a midi init and a bindevents.

If you have 154 midi devices, then yes, but I don't think. I don't know your real world scenario. But I think usually of one midi device being enough, so I said that because I thought it#s wrong.

So your code is split into multiple parts? Then whare is what part of the code? I don't get it.



Chriss
 
By the way, if you bind to same message for a hwnd, it's true you just override the previous binding.

But you can cascade this with the help of one Windows eventhandler: Bind that one Windows message delegate to the window message and bind the buttons to that delegates' handleevent method, like this:
Code:
#Define WM_SIZE 0x0005
#Define GWL_WNDPROC  -4

oHwndDelegate = CreateObject("WindowsMessageDelegate")
oDelegate1 = CreateObject("ObjectDelegate")
oDelegate2 = CreateObject("ObjectDelegate")

? BindEvent(_screen.HWnd, WM_SIZE,oHwndDelegate ,'Handleevent')
On Key Label F5 Clear Events
Read Events


Define Class WindowsMessageDelegate as custom
   Procedure Handleevent()
       Lparameters hWnd, Msg, wParam, lParam
       
       ? 'handleevent', hWnd, Msg, wParam, lParam
       
       * The following is just standard code so windows events/messages are processed normally,
       * ie in short: ...so your app works. So better not forget to do this, ever, when handling events
       LOCAL lnOldProc, lnResult
  
       DECLARE INTEGER GetWindowLong in Win32API;
          INTEGER hWnd, INTEGER nIndex
       lnOldProc = GetWindowLong(hWnd, GWL_WNDPROC)

       DECLARE INTEGER CallWindowProc in Win32API;
          INTEGER lpPrevWndFunc, INTEGER hWnd, INTEGER Msg, INTEGER wParam, INTEGER lParam
       lnResult  = CallWindowProc(lnOldProc, hWnd, Msg, wParam, lParam)
       
       RETURN lnResult
    EndProc 
EndDefine

Define Class ObjectDelegate as Custom
   Width=30
   Height=30

   Procedure Init(tcCaption)
      ? BindEvent(oHwndDelegate,"Handleevent",This,"AlsoHandleevent",1)
   EndProc 
   
   Procedure AlsoHandleevent()
       Lparameters t1, t2, t3, t4
       ? 'alsohandleevent', t1, t2, t3, t4
   EndProc 
EndDefine

You get the same parameter value. Notice you only need the typical window message event handling code returning the CallWindowProc return value once in the WindowsMessageDelegate and not in all the other delegates (your buttons).

So there you have a multiplicator, every button can react to the same midi event, if there is one window event handler that gets triggered and they bind to.

Chriss
 
Hi Chriss,

Thank you for your example.

I did some study at it and run it and I understand it.

The trick you do is
Code:
oDelegate1 = CreateObject("ObjectDelegate")
oDelegate2 = CreateObject("ObjectDelegate")
BindEvent(oHwndDelegate,"Handleevent",This,"AlsoHandleevent",1)

Later today I am going to try that.

Kind regards,

Jan Flikweert
 
Yes, tough out of context it looks wrong. The last line is in the init of the ObjectDelegate class, so this is executed for both oDelegate1 and oDelegate2. And "This" in the third parameter means oDelegate1 and oDelegate2, respectively.

Or in short: Multiple bindings work for objects, as my first capslock keyboard example already shows. And to make use of that for Windows messages, you just have to have one object, which in itself is the delegate/eventhandler for the Windows message. You also get the same parameters, so each delegate on the final level gets called as if it was directly triggered by the Windows message, too. So you can react to the values of hwnd, msg, wParam, lParam, too.

(If that wouldn't have worked the main delegate could have stored the parameters into some properties each further delegate can access, but it simplifies things that the delegate method gets the same parameters as the source).

Chriss
 
Just another thought:

I don't know if your handling methods hndlvnt_cme and hndlvnt_mpl each analyze wPParam and lParam values to see if they are actually addressed by that message, you could also put this into the main WindowsMessageDelegate and "forward" the message to the individual button without bindevents but simply by decoding which button is addressed and acting on it or calling a method of it.

In my example all buttons change their captions, so there's no point of handling everything in the capslock button click. But in your case it might be worth shifting this responsibility up from the button class code to the main delegate, even though you only write the class code once anyway. It just could spare a lot of the same parameter analysis to finally not react anyway of all but one button.

If I imagine this to highlight keys on a virtual keyboard or light up lamps in a real keyboard while a midi file plays, I think this will cause a series of events, one for each key, anyway, so each event only needs one key to react and then this could also be handled centrally.

Chriss
 
Hi Chriss,

Your idea to detect the needed stops at the front door is very good. I think I need more "Event binding" and should go back to the design table.
organigram_iew8qf.jpg


Handling Presets/Combinations/Couplers/Dynamics is doubtfull. They should be each independent parents of the manuals. The base should be: Form/containers for and with each object.

Kind regards,

Jan Flikweert
 
Chriss,

Indeed your example is helpfull, it works. Thank you very much. It is a other approach and I need to change things. In early stage I will detect the status number, channel. Espessially the coupler will be done in early stage using events.

I was afraid the latency was not acceptable, but at te moment that is no problem.

I keep you informed. It is on going.

Kind regards,

Jan Flikweert
 
Hi Chriss, Hi all interested,

I have two questions:

1. Is the use of unbindevents usefull, are there objections?
2. When to use the delegate parameter 1 in bindevent?


Here after the parts of codes I used. In basic the good news it works. I can play notes on/off and enable/disable stops.

There is one con. I changings stops it shifts one stop. So enabling stop 1 results in play notes of stop 2.

I cannot prove there is a mistake in my program.

I proved the audio backends work correct by working with different backends and alternative program.

It can be a MIDI issue.

So I continue to investigate this.

Kind regards,

Jan Flikweert

Code:
*****Part of main prograrm file containing two classes*********
Define Class Hndl_MidiIn_cme As Custom
	stts=0
	Procedure Init
		*Bindevent(ptrCallback_cme, MIM_DATA,This,'Handleevent',1)
		Bindevent(ptrCallback_cme, MIM_DATA,This,'Handleevent')
	Endproc

	Procedure Handleevent &&Split to manual detecting status byte.
	Lparameters HWnd, Msg, wParam, DataByte1
	If Bitand(DataByte1,240)<>240 Then
		This.stts=Bitand(DataByte1,255) && Status
		Do Case
		Case This.stts=144
			This.Handle01(HWnd, Msg, wParam, DataByte1)
		Case This.stts=145
			This.Handle02(HWnd, Msg, wParam, DataByte1)
		Case This.stts=147
			This.Handle04(HWnd, Msg, wParam, DataByte1)
		Case This.stts=176
			This.Handle_dyn1(HWnd, Msg, wParam, DataByte1)
		Case This.stts=177
			This.Handle_cresc(HWnd, Msg, wParam, DataByte1)
		Case This.stts=179
			This.Handle_dyn4(HWnd, Msg, wParam, DataByte1)
		Endcase
	Endif
	Endproc
	PROCEDURE Handle01
		Lparameters HWnd, Msg, wParam, DataByte1
	ENDPROC
	PROCEDURE Handle02
		Lparameters HWnd, Msg, wParam, DataByte1
	ENDPROC
	PROCEDURE Handle04
		Lparameters HWnd, Msg, wParam, DataByte1
	ENDPROC
	PROCEDURE Handle_cresc
		Lparameters HWnd, Msg, wParam, DataByte1
	ENDPROC
	PROCEDURE Handle_dyn1
		Lparameters HWnd, Msg, wParam, DataByte1
	ENDPROC
	PROCEDURE Handle_dyn4
		Lparameters HWnd, Msg, wParam, DataByte1
	ENDPROC	
ENDDEFINE

Define Class Hndl_MidiIn_mpl As Custom
	stts=0
	Procedure Init
		*Bindevent(ptrCallback_mpl, MIM_DATA,This,'Handleevent',1)
		Bindevent(ptrCallback_mpl, MIM_DATA,This,'Handleevent')
	Endproc

	Procedure Handleevent
	Lparameters HWnd, Msg, wParam, DataByte1
	If Bitand(DataByte1,240)<>240 Then
		This.stts=Bitand(DataByte1,255) && Status
		If This.stts=144 OR (This.stts=128) Then
			This.Handle01(HWnd, Msg, wParam, DataByte1)
		Endif
	Endif
	Endproc
	PROCEDURE Handle01
		Lparameters HWnd, Msg, wParam, DataByte1
	ENDPROC
Enddefine
*****************
***Load method CallbackWindow_mpl***********
#Define CALLBACK_FUNCTION    0x30000
#Define CALLBACK_WINDOW    0x10000
#Define MIM_DATA 963
#Define MIDI_IO_STATUS  32
ptrCallback_mpl=Thisform.HWnd
nResult_open = midiInOpen(@hDevice, nMpIn-1, ptrCallback_mpl, 0, CALLBACK_WINDOW+MIDI_IO_STATUS)
If nResult_open=0
	arrInHandles(1,2)=nMpIn-1
	arrInHandles(2,2)=hDevice
	nResult_start = midiInStart(arrInHandles(2,2))
Else
	nResult_start = -1
	Messagebox("Failed to load MIDIPLUS61U Keyboard")
	Thisform.einde
Endif
If nResult_start = 0
	PUBLIC oHndl_MidiIn_mpl AS OBJECT
	oHndl_MidiIn_mpl=CREATEOBJECT("Hndl_MidiIn_mpl")
Else
	Messagebox("Failed to start MIDIPLUS61U Keyboard")
	Thisform.einde
Endif
**********
***Load method CallbackWindow_mpl+CME***********
#Define  CALLBACK_FUNCTION    0x30000
#Define  CALLBACK_WINDOW    0x10000
#Define MIDI_IO_STATUS  32
ptrCallback_cme=Thisform.HWnd
nResult_open = midiInOpen(@hDevice, nCmeIn-1, ptrCallback_cme, 0, CALLBACK_WINDOW+MIDI_IO_STATUS)
If nResult_open=0
	arrInHandles(1,1)=nCmeIn-1
	arrInHandles(2,1)=hDevice
	nResult_start = midiInStart(arrInHandles(2,1))
Else
	nResult_start = -1
	Messagebox("Failed to load Domus Vivace organ")
	Thisform.einde
Endif
If nResult_start = 0
	PUBLIC oHndl_MidiIn_cme AS OBJECT
	oHndl_MidiIn_cme=CREATEOBJECT("Hndl_MidiIn_cme")
Else
	Messagebox("Failed to start Domus Vivace organ")
	Thisform.einde
Endif
**********************************************
************Part Init method button class Stop*************************
IF this.Active=1
	If Alltrim(This.cme_mpl)=="CME" Then
		DO CASE
			CASE This.klavier=0
				Bindevent(oHndl_MidiIn_cme,"Handle01",This,'hndlvnt_cme',1)
			CASE This.klavier=1
				Bindevent(oHndl_MidiIn_cme,"Handle02",This,'hndlvnt_cme',1)
			CASE This.klavier=3
				Bindevent(oHndl_MidiIn_cme,"Handle04",This,'hndlvnt_cme',1)
		ENDCASE
	ELSE
		IF This.klavier=0 Then
			UnBindevents(oHndl_MidiIn_mpl,"Handle01",This,'hndlvnt_mpl',1)
		Endif
	ENDIF
ELSE
	If Alltrim(This.cme_mpl)=="CME" Then
		DO CASE
			CASE This.klavier=0
				UnBindevents(oHndl_MidiIn_cme,"Handle01",This,'hndlvnt_cme')
			CASE This.klavier=1
				UnBindevents(oHndl_MidiIn_cme,"Handle02",This,'hndlvnt_cme')
			CASE This.klavier=3
				UnBindevents(oHndl_MidiIn_cme,"Handle04",This,'hndlvnt_cme')
		ENDCASE
	ELSE
		IF This.klavier=0 Then
			UnBindevents(oHndl_MidiIn_mpl,"Handle01",This,'hndlvnt_mpl')
		Endif
	ENDIF
ENDIF
****************************
 
Hello Jan,

glad to see you get forward.

1. Unbindevents will be useful when you don't want to listen to events anymore. So that's usually done when quitting. There's no need for it, when a program quits all bindings to events will be unbound anyway. Just like QUIT closes DBFs, file handles, etc. There's nothing persisted between sessions. It could be a good move to have cleanup code when you stop and restart the same code acting on the same buttons, etc.

It seems to me you can switch buttons from CME to MPL, whatever that means, then it would be good to have a cme_mpl_assign() method that can act depending on the current .cme_mpl property value and the new value send in as parameter to both unbind and bind to the correct midi messages. Or you use one for all.

What you don't need to do and also should really not do is after one message you bound to comes in, use an unbind to pair the bind with an unbind. The bindevent call is making a subscription to all same events, not only the first one. the closing bracket for a windows message is the call of CallWindowPro and returning its value. That is very Windows message specific and closes the processing of this windows message. But why would you unsubscribe when you still want further midi events to get handled? It makes no sense to unbindevent and then bindevent again, your subscription is still valid and doesn't need renewal. If you want to react to other messages, simply make further bindevents, but typically make them right from the start you know the message numbers for midi events. Make a thousand bindevents to all windows messages from 0 to 1000, if you want to receive them, that is possible and doesnt hurt anybody. Of course most of them won't be midi related, so youd only bind to the messages interesting you, but all of them, even those you might never expect but still are midi related. There is no reason to be conservative and sparse with this.

2. I explained the use of nFlags=1 for the capslock example already. To explain it more generally: There never is a branch of event and delegate happening in parallel. Code is always executed one after the other, When an event, in your case a windows message arrives at a hwnd, you could say that's at the dawn of processing it, you'll only get to the dusk of the event later. With the parameter nFlags you make some decisions, mainly which is done first, the original event handling before your own or your own before the original handling, so you decide whether your own code should run at dawn or after dusk of the event. Sometimes you want the event to happen before the delegate code, sometimes not. In my case of course I'd not want to show the button captions with the opposite case, so first make the capslock state switch, then display it as current state, otherwise I'd always display the opposite letter cases. Sometimes you have no choice and the flag has no influence and can't override it, depends on the case.

There's one more aspect you see in the intellisense help text. That's better understandable from the full help text of Bindevents. One thing is that events usually should happen, but developers often call events from code. If some code calls button.click that's executing the code in the click event, but doesn't raise a click event. And you decide whether that should cause the delegate to execute or it should only execute when real clicks happen, which is not your problem with windows events. Windows events are windows events/ messages. I don't think the flag parameter has much for you. But the order of execution of event vs delegate method code can be a difference. If the midi message does something to a state of something you can only see that after the event was handled that causes such a change, not beforehand. Should be clear as mud.

Chriss
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top