Notifications
Clear all

[Closed] Controller Callback

I will now try to explain in simple language how the system for sending messages in 3DS MAX is arranged using the example of a plugin.
The plugin creates an object that can send messages to all other objects that depend on it. It should be provided by the plugin developer.
One of these objects is ParamBlock.
ParamBlock already decides for itself how to pass this message and whom. Such objects including are elements UI (controls).
Thus, to receive messages from an object, you have to be either an object dependent on it, or to become the one to whom the ParamBlock sends messages.

Am I explaining clearly yet? …

 MZ1

I guess I got it, Are you taking about references that will create connections between objects? So we can get them by using “refs.dependents” or “refs.dependsOn” functions?

How about something like this. Far from perfection but with a little effort it could work for an in-house solution, and I guess it could all be wrapped inside a .Net derived control.

Note: Minimal implementation, so bugs are expected as well as unhandled behaviors.

EDIT: Removed all Change Handlers to handle all events within the Redrawing Callback.

(
	try destroydialog ::RO_SPINNER catch()
	
	rollout RO_SPINNER "" width:110 height:56
	(
		dotnetcontrol dn_spinner "System.Windows.Forms.NumericUpDown" pos:[16,16] width:80
		
		local color_default  = (dotnetclass "System.Drawing.Color").white
		local color_animated = (dotnetclass "System.Drawing.Color").Red
		local color_key      = (dotnetclass "System.Drawing.Color").Green
		local color_black    = (dotnetclass "System.Drawing.Color").Black
		local color_white    = (dotnetclass "System.Drawing.Color").White
		
		local ctrl        = bezier_float()
		local mouseDown   = false
		local valueChaged = false
		
		fn Hold mAction mString: =
		(
			case mAction of
			(
				 #start: if not thehold.holding() do thehold.begin()
				#accept: if thehold.holding() do thehold.accept mString
				#cancel: if thehold.holding() do thehold.cancel()
			)
		)
		
		fn GetSpinnerValue =
		(
			val = getproperty dn_spinner #value asdotnetobject:true
			return val.ToSingle val
		)
		
		fn UpdateControl =
		(
			dn_spinner.Value = ctrl.value
			
			back = color_default
			fore = color_black
			
			if numkeys ctrl > 0 then
			(
				back = color_animated
				fore = color_white
				
				for j = 1 to numkeys ctrl do
				(
					if getkeytime ctrl j == currenttime do
					(
						back = color_key
						fore = color_white
					)
				)
			)
			
			dn_spinner.BackColor = back
			dn_spinner.ForeColor = fore
			
			dn_spinner.Update()
		)
		
		fn AssignController mController =
		(
			if iskindof mController bezier_float then
			(
				ctrl = mController
			)else(
				ctrl = bezier_float()
				$.height.controller = ctrl
			)
			
			dn_spinner.Value = ctrl.value
		)
		
		on RO_SPINNER open do
		(
			dn_spinner.Minimum       = -1E9
			dn_spinner.Maximum       = 1E9
			dn_spinner.DecimalPlaces = 4
			dn_spinner.Increment     = 0.1
			
			dn_spinner.Value = ctrl.value
			
			unregisterRedrawViewsCallback RO_SPINNER.UpdateControl
			registerRedrawViewsCallback RO_SPINNER.UpdateControl
		)
		
		on RO_SPINNER close do
		(
			unregisterRedrawViewsCallback RO_SPINNER.UpdateControl
		)
		
		on dn_spinner MouseDown sender args do
		(
			Hold #start
			mouseDown = true
			valueChaged = false
		)
		
		on dn_spinner MouseUp sender args do
		(
			ctrl.value = GetSpinnerValue()
			
			Hold #accept mString:"Parameter Changed"
			if valueChaged == true do setfocus RO_SPINNER
			mouseDown = false
		)
		
		on dn_spinner KeyUp sender args do
		(
			if args.KeyCode == args.KeyCode.Enter do
			(
				Hold #start
				
				ctrl.value = GetSpinnerValue()
				
				dn_spinner.Select 0 dn_spinner.Text.Count
				
				Hold #accept mString:"Parameter Changed"
			)
		)
		
		on dn_spinner ValueChanged sender args do
		(
			if mouseDown == true do
			(
				ctrl.value = GetSpinnerValue()
				valueChaged = true
			)
		)
		
	)

	createdialog RO_SPINNER
	
	
	/* TEST SCENE ------------------------------------ */
	
	with undo off delete objects
	
	node = box isselected:on
		
	with animate on
	(
		at time 25 node.height  = 75
		at time 75 node.height  = -50
		at time 100 node.height = 25
	)
	
	RO_SPINNER.AssignController node.height.controller
	
	/* ---------------------------------------------- */
	
)
 MZ1

Cool, My controls are WPF, But I don’t want to complicate everything with WPF stuffs and my focus is on update mechanism. So It seems your offered method “registerRedrawViewsCallback” works perfect.

I am not aware of a build-in WPF numericUpDown control or similar, but if you are using a third party control or creating your own, it should be pretty similar to the example. Probably Methods, Properties and Events will be different but it needs just the basic ones.

Other thing that would be nice is to add the Mouse drag behavior.

The only issue I find with the Redraw Callback is that it updates too many times in shaded views due to the progressive refinement of Nitrous driver. Maybe you can avoid it by checking the previous and current values and just update if they are different if you see the performance degrades too much.

If you are customizing your control, you could add an Update method to it and just call the method from the callback, or you could add the callback directly to the control in .Net so each control would have its own callback.

1 Reply
 MZ1
(@mz1)
Joined: 10 months ago

Posts: 0

That’s is also another point, One register for all or register a callback for each control?

I would first try to have the Redraw Callback within the control, one Update() method and call it from the Callback. And create/destroy the Callback when controller is assigned/removed from the control.

That way you can update the controls individually, and the control would be ready to be added to any form without having to code a global Callback in each form.

If it doesn’t work as you would like you could still call the Update method from a Callback in the Form.

Page 3 / 3