[Closed] DotNet Button loses EventHandler…?
So I’ve been using the FlowLayoutPanel control in my current script, which I have multiple times before, and I haven’t run into this issue…
My code is setup more or less the same, with event handler functions above where I create dotnetobject buttons, then I add the event handlers (Up, Enter,Down,Leave) to the buttons. I then add the buttons to an array and finally add them to the FlowPanel when I’m done.
The buttons then call functions when they are pressed. With my other scripts, the buttons never called a function , or worked on Left Click.
But what happens is, the buttons events work for a bit, they call my functions fine, then all of a sudden only 1 of the buttons now works. All of its event handlers still work for that one. But the other button(s), their Mouse Enter/Tooltip still shows… but nothing else works… But then if I clear the flowpanel and reload the buttons, they work again??
Any Ideas?
Thanks!
Look up “DotNet Objects and Classes Life Time Control” in the Maxscript help:
In versions prior to 3ds Max 2010, performing MAXScript garbage collection could cause DotNet event handlers to be deleted too early.
With this method available in 3ds Max 2010 and higher, this can be avoided by setting the life time control of the corresponding objects to be handled by DotNet instead of MAXScript.
dotNet.setLifetimeControl {<dotNetObject> | <dotNetClass>} {#mxs | #dotnet}
Controls whether the MAXScript value wrapping the DotNet object or DotNet class should be garbage collected when there are no MAXScript references to the value (#mxs), or whether the DotNet object also has to have been destroyed before the MAXScript value can be garbage collected (#dotnet).
By default, a dotNetObject value holds a strong reference to the DotNet object, and the DotNet object is deleted only when the dotNetObject value is deleted. This is equivalent to specifying #mxs as second argument in the above method.
By specifying #dotnet as second argument, we tell MAXScript that the dotNetObject cannot be collected until the DotNet object is deleted. The dotNetObject value holds a weak reference to the DotNet object and the DotNet object is deleted when no other DotNet objects hold a reference to it and a DotNet garbage collection occurs.
-Eric
Cool, thanks for that tip I would like to try and not use max2010 methods though, as It would be nice to keep this backwards compatible to max 2008 like my tools are now.
So, I took this method from James Haywood I believe, and it seems to work … so far, for some of my buttons.
fn startFocus arg =
(
enableAccelerators =
false
print enableAccelerators
)
fn stopFocus
arg =
(
enableAccelerators = true
print
enableAccelerators
)
dotNet.addEventHandler btndragdrop "GotFocus"
startFocus
dotNet.addEventHandler btndragdrop "LostFocus"
stopFocus
dotNet.setLifetimeControl btndragdrop #dotNet
But, if I add those to buttons in another FlowLayoutPanel that gets created on another button click from the 1st Rollout, I get errors where it cant add a dotnetobject button because its not a control, or the buttons still lose their handlers.
Right now though, with only using those focus events for the main rollout, I can open the 2nd rollout and interact with the buttons, but as soon as I use a rightclick option, which updates both FlowPanels, the 2nd ones buttons lose their handles again, until I reopen the rollout again. If that makes sense…
I’ll have to watch the event handlers yet in the 1st rollout, but I am still curious if there is another way.
But I was talking to someone here at work about what might be a good way to add/remove buttons to my toolbar. Which is what this post is pertaining to.
So I think instead of having a second rollout with the FlowLayout, I will just do something like the Exclude list for Lights, with the double pane window, where u can see what’s added and what you can add/remove with the double arrows.
Hi everybody, this is my first post here.
Well, dotNet.setLifetimeControl is nice improvement in Max 2010 but not benefit my work too cause am on Max 2009, so am forced to use different approach. It’s maybe not perfect at some points (but we have not a big choice at this case) and probably its a popular enough from the ActiveX times, but worth to share it, I hope. So, what I do is to define Max native rollout (instead of dotNet form) and add the handlers directly to it (not via dotNet.addEventHandler).
rollout example ""
(
dotNetControl tb "System.Windows.Forms.Textbox" \
pos:[0,0] height:18
on tb KeyDown eArg do (...)
on tb MouseLeave eArg do (...)
...
)
So my double list idea for adding/removing works fine.
But I keep getting this error when I try to populate the FlowLayoutPanel with the buttons if
they use the
dotNet.setLifetimeControl btndragdrop #dotNet
-- Unable to convert: dotNetObject:System.Windows.Forms.Button to type: System.Windows.Forms.Control
Do I need to delete the buttons instead of clearing the LayoutPanel, so it doesnt try and look for the buttons or something?
Ok, so so far it seems as I fixed it… but I will be doing more testing.
I am using this method right now.
http://forums.cgsociety.org/showpost.php?p=6656303&postcount=2
This may or may not help, but here is an exchange I had with Larry Minton regarding the SetLifetimeControl function…
LarryMinton wrote:
Just a FWIW, since you are adding scripted event handlers to the .net controls, and placing those controls in a form whose lifetime is longer than the mxs values wrapping the controls, you should add:
– Add the controls to the main form
hForm.Controls.Add(txtTextBox)
hForm.Controls.Add(btnOpenFile)
hForm.Controls.Add(btnPrint)– set lifetime control of buttons to be controlled by .net
dotnet.setLifetimeControl btnOpenFile #dotnet
dotnet.setLifetimeControl btnPrint #dotnetOtherwise, after you run the script, if you do a gc() the buttons no longer do anything. This is because there are no references to the mxs values for the buttons and they get garbage collected. The event handlers are stored in the mxs values for the buttons, so after the mxs value is deleted the event handlers no longer work.
Larry
JHaywood wrote:
Any reason this isnt the default behavior when creating event handlers?
LarryMinton wrote:
Because this is only applicable to .net objects that are placed in other .net objects. An example of when you have an event handler and you dont want the objects lifetime control to change is a timer object. You want the timer object to be deleted when the mxs value wrapping the object is deleted. If you set the timer objects lifetime control to #dotnet, then at some random point in the future the timer object will be deleted by the .net garbage collector since no .net object holds a reference to the timer object.
Larry
JHaywood wrote:
Is there any downside to setting the lifetime control to #dotNet on every control with an event handler, regardless of whether its in another .net object or not?
LarryMinton wrote:
Yes, just what I said in my message. If you dont place the control in another .net object, then nothing holds a strong reference to the control, and the .net garbage collector will at some point delete the control.
Larry
Hmmm… interesting. Thanks for the post James.
Yeah, I was/am placing DotnetButtons in a Dotnet FlowLayoutPanel, so that makes sense. But after using that setLifetime and adding the event handlers to each button AFTER they are added to the panel and are displayed, that fixed my issues.
But it looks like my new toolbar, which is what I’m working on, will only work in 2010 and higher. But I think with the new features in 2012, people might jump to that anyways.
Yeah, I ran into that same problem too. Setting the lifetimeControl after the control is created is definitely the way to go.