Notifications
Clear all

[Closed] dotNet.addEventHandler

Trying to update my script to work in max 2019.
It was working fine in Max2016 where I added an event within a struct to a button within a group defined in the struct…but 2019 now has this documention:

“NOTE:You can only add functions that are registered in the global scope as event handlers. If you need to register a function that is hidden from the global scop (for example, contained in a struct), you need to define a global function to point to the hidden function.”
which is new in 2018
so I create a member function in a struct,
the struct is part of a rollout
it creates a button which attaches an event handler which executes the member function…
this is what I am kind of doing.

MasterValue = rollout ThisRollout "" width:200
(
   local MyForm
   struct MyStruct =
   (
      PUBLIC
      sName,
      function dunny = (print "nothing"),
      function CreateGui 
      (
         MyButton = dotnetObject "button"
         MyForm.controls.add MyButton
         global gdunny = this.dunny -- added this line to try and make events in the struct global
         dotnet.addEventHander MyButton "click" gdunny
      ),
      on create do
      (
          CreateGui()
      )
   )
   function CreateWinform =
   (
      MyForm = dotNetObject "MaxCustomControls.MaxForm"
      MyForm.showmodeless()
   )
   button Maxbutton ""
   holderforstruct = #()
   on Maxbutton pressed do 
   (
      MyForm = CreateWinform()
      holderforstruct  = MyStruct sName:"YANstruct"
   )
)

The code doesn’t actually work, the only additional lines I have put in to is the line “global gdunny = this.dunny” to essentially make a global variable and assign it to a function. I can execute the function from the listener, but the dotnet button refuses to do much at all.

12 Replies

in 2019,The event must be a global function
But there are some syntax error in your code

Quite a few ways around that while keeping the function inside the struct, for example:

masterValue = rollout thisRollout "" width:200
(
    struct myStruct
    (
        name,
        form,
        fn dunny = print name,
        fn createGui =
        (
            ::myButton = dotNetObject "Button"
            myButton.FlatStyle = myButton.FlatStyle.Flat
            myButton.Text = name

            form.Controls.Add myButton
        ),
        on create do createGui()
    )
    fn createWinform =
    (
        myForm = dotNetObject "MaxCustomControls.MaxForm"
        myForm.showModeless()
        return myForm
    )

    button maxButton "Show Form"
    
    on maxButton pressed do
    (
        myForm = createWinform()
        ::holderForStruct = myStruct name:"YANstruct" form:myForm
        dotNet.addEventHandler myButton "Click" (fn _ = ::holderForStruct.dunny())
    )
)
createDialog masterValue

Thanks for responding, and great to hear from you after so many years.
Thanks also for fixing my mockup of code such that it actually works.
my code that i wrote that mock template from generates multiple group objects in a maxform and cascades them down the page as more maxobjects are added to the scene. It also deletes each instance of the window and the object if the window is closed or the individual object interface is closed. So I use multiple instances of the struct, and let the user remove them and add them back in etc. So each event is tied to a particular window which is a struct.
So as I don’t know how many windows/objects/events will need to be present, and they need to know which window to update with the particular maxform button press, I generate a unique handler for each window/struct/object.
I understood your solution until this line:

dotNet.addEventHandler myButton "Click" (fn _ = ::holderForStruct.dunny())

i’ve never seen the syntax:

 fn _ = ::holderForStruct.dunny()

I know the “::” makes the variable global, but I don’t know how “fn _ =” works?
your code makes the button global, and the struct global, and the event handler global. I can’t seem to figure out how to adapt this to a situation where there are multiple unique instances of structs spawned and each has to have a global event – wanted to avoid creating lots of global stuff which why I used a struct in the first place.
It was all working so well in max2016, max2017. I never even installed 2018, but jumped straight to 2019. I will try to modify your code more to adapt it. I’ll post up here if I succeed.

Instead of a holder of one variable, hold an array of variables in that case, something along the lines of:

masterValue = rollout thisRollout "" width:200
(
   global buttonHolders = #()
   struct myStruct
   (
      index,
      form,
      button,
      fn dunny = print index,
      fn createGui =
      (
         button = dotNetObject "Button"
         button.FlatStyle = button.FlatStyle.Flat
         button.Bounds = dotNetObject "System.Drawing.Rectangle" (30 * mod index 10) 10 25 25
         button.Text = index as string
         form.Controls.Add button
      ),
      on create do createGui()
   )
   fn createWinform =
   (
      local myForm = dotNetObject "MaxCustomControls.MaxForm"
      myForm.showModeless()
      return myForm
   )
   button maxButton "Show Form"
   on maxButton pressed do
   (
      local myForm = createWinform()
      
      for i = 1 to 5 do
      (
         local holder = myStruct index:i form:myForm
         append buttonHolders holder
         dotNet.addEventHandler holder.button "Click" (execute ("fn _ = buttonHolders[" + i as string + "].dunny()"))
      )
   )
)
createDialog masterValue

The underscore is just a name of a function wrapping the global function call (the event handler itself is not global btw.)

Thanks for that solution, it does work :), and I can adapt it to my needs.
It feels like a little bit of a hack – everywhere I use the execute function feels like a bit of a hack .

I copied and pasted your code, and for some reason it decided that there was a scope violation so I think this version it is fixed. Not really sure what I did but it seems to work now (for any following this conversation…)

masterValue = rollout thisRollout "" width:200
(
   global buttonHolders = #()
   struct myStruct
   (
      index,
      form,
      button,
      fn dunny = print index,
      fn createGui =
      (
         button = dotNetObject "Button"
         button.FlatStyle = button.FlatStyle.Flat
         button.Bounds = dotNetObject "System.Drawing.Rectangle" (30 * mod index 10) 10 25 25
         button.Text = index as string
         form.Controls.Add button
      ),
      on create do createGui()
   )
   fn createWinform =
   (
      local myForm = dotNetObject "MaxCustomControls.MaxForm"
      myForm.showModeless()
      return myForm
   )
   button maxButton "Show Form"
   on maxButton pressed do
   (
   local myForm = createWinform()
      for i = 1 to 5 do
      (
         holder = myStruct index:i form:myForm
         append buttonHolders holder
       dotNet.addEventHandler holder.button "Click" (execute ("fn _ = buttonHolders[" + i as string + "].dunny()"))
      )
   )
)
createDialog masterValue

I will try and figure out a more elegant solution – but really any global variable makes me feel a little dirty – thank you autodesk. For the moment I am using Max2016 and importing the scene into max2019 to use its features.

I wonder why they made this change? Probably a whole lot of windows stack problems if and when max crashes leaving a lot of events, without parent buttons and no way to remove them… Still I don’t know how this fixes things?

I am still interested in more info on “fn _=” I cannot find any reference to it and what it does and why people use it. Do you have an article, reference ? It seems to be a wild card way of referencing a function. ANyway more documentation of this would be helpful.
Is there any other way of removing a global variable than

globalvars.remove "my_number"

as it seem you have to convert the variable name into a string in order to delete it.

I did finally work out the correct syntax. But I am trying to use the Sender and eventArgs discussed in the addeventhandler documention
Why are both Sender and eventArgs undefined in this version:

masterValue = rollout thisRollout "" width:200
(
   global buttonHolders = #()
   struct myStruct
   (
      index,
      form,
      button,
      fn dunny Sender eventArgs  = (
         print index
         print Sender 
         print eventArgs 
        ),
      fn createGui =
      (
         button = dotNetObject "Button"
         button.FlatStyle = button.FlatStyle.Flat
         button.Bounds = dotNetObject "System.Drawing.Rectangle" (30 * mod index 10) 10 25 25
         button.Text = index as string
         form.Controls.Add button
      ),
      on create do createGui()
   )
   fn createWinform =
   (
      local myForm = dotNetObject "MaxCustomControls.MaxForm"
      myForm.showModeless()
      return myForm
   )
   button maxButton "Show Form"
   on maxButton pressed do
   (
   local myForm = createWinform()
      for i = 1 to 5 do
      (
         holder = myStruct index:i form:myForm
         append buttonHolders holder
       dotNet.addEventHandler holder.button "Click" (execute ("fn _ = buttonHolders[" + i as string + "].dunny Sender eventArgs"))
      )
   )
)
createDialog masterValue

Once again, _ is just a name like any other, no special syntax. fn _ is the same thing as say fn test, it has no arguments so you need to add them if you want to use them.

Btw. 2019.1 Update is out now and it’s fixed there so you don’t have to do it that way anyway.

I saw the documentation for the update, not seeing anything about event handlers?
I thought that when an event triggered it automtically parsed sender and eventargs to the custom max function (if it took args) so that you could figure out which button caused the event and if it was a single, double, middle or right click.
I got it working how passing args as expected:

masterValue = rollout thisRollout "" width:200
(
   global buttonHolders = #()
   struct myStruct
   (
      index,
      form,
      button,
      fn dunny sender eventArgs = (
         print index
         print Sender
         print eventArgs
        ),
      fn createGui =
      (
         button = dotNetObject "Button"
         button.FlatStyle = button.FlatStyle.Flat
         button.Bounds = dotNetObject "System.Drawing.Rectangle" (30 * mod index 10) 10 25 25
         button.Text = index as string
         form.Controls.Add button
      ),
      on create do createGui()
   )
   fn createWinform =
   (
      local myForm = dotNetObject "MaxCustomControls.MaxForm"
      myForm.showModeless()
      return myForm
   )
   button maxButton "Show Form"
   on maxButton pressed do
   (
   local myForm = createWinform()
      for i = 1 to 5 do
      (
         holder = myStruct index:i form:myForm
         append buttonHolders holder
       dotNet.addEventHandler holder.button "MouseClick" (execute ("fn _ a b  = buttonHolders[" + i as string + "].dunny a b"))
      )
   )
)
createDialog masterValue

thanks for all your help. I have a script that checks the MaxVer (an undocumented Global Var) before executing the conditional code.
It seems a hell of a lot more logical to just allow a function which is not global … so I can set the event handler within the struct without utilizing that “execute ‘string’” is always in the global scope.
thanks again. Back to the grindstone.

Page 1 / 2