Controller Callback


#5

It depends.

Change handlers are called only for user initiated events, and not for changes resulting from a change in controller values. For example, a change handler on the transform attribute of a node would be called when the user moves the node. If the position of the node is animated, and you play back the animation, the transform attribute change handler is not called


#6

Actually I need both cases for different interactions, user Initiated and all changes.Mostly all.


#7

Perhaps some trick, but you should explain what you are controlling and what should happen when changed.


#8

I just created a WPF spinner and I want to create a property exactly like the built-in Max’s Spinner.controller property.
Updates are something like two-way binding in WPF. Cases are:

  • Controller value change -> Spinner value update.
  • User input value to spinner -> Controller value update.
  • Spinner value change by code not user -> Controller will not change.
  • Controller has keyframe -> spinner border will be red.

#9

in general case you can’t make the true connection of a max controller and a wpf ui controller.
in other case you have to rewrite a lot of system base classes

you can write your own controller which will be able to send messages to a specified ui controller, but again… you can’t do it for any built-in controller

do you see any example in max UI (.net, wpf, qt, etc.) except built-in rollout controllers where a controller connected to a controller (or a subanim)? The answer is NO. Because it’s practically impossible.

the best (and only) you can do is to use all available built-in callbacks, events, and notifications (units change, time change, etc.) to update your “connected” UI control.

another why is connect a controller to a rollout control and use kindof ‘native window’, or ‘window monitor’, or system hooks to monitor messages of this rollout controls,. and update yours


#10

And you have to understand that this ‘fake’ connection will be very slow


#11

Well, It seems there isn’t a general solution for this. What if I limit my request to very specific and managed situation:
We can create or extend our controller and use it for the property.
We need to only support float type.
We have also control over MSX side.
Performance is important but at least I need one working method.


#12

you already have it built-in in recent max versions…

it’s plugin FloatController

it has “getValue” handler. You can use it for updating your UI control


#13

I already used this feature in my rigging a lot and works perfect.
Is it possible to create a plugin like this inside C#?
Another problem for me is I have to support Max 2014-2019.


#14

Yes, you can create a Controller plugin with the NET.SDK, but it’s a pain in the ass.
The names in the UI won’t look fine and possibly you will need different versions for 2014, 2016 and possibly 2017 and up.
I think the user @48design knows how to overwrite the UI with WPF, but he has not time to write an snipet.


#15

Sorry, What you mean by

The names in the UI won’t look fine and possibly you will need different versions for 2014, 2016 and possibly 2017 and up.


#16

The strings for the parameters block are not encoded correctly, so in your UI you will see instead of your parameter name for example “width” you will see a mix of extrange characters. This was solved, if I remember, in version 2017.

For compatibility between versions, there are some methods you MUST override that have changed, for example:

  • in Max 2014:
    public override RefResult NotifyRefChanged(IInterval changeInt, IReferenceTarget hTarget, ref UIntPtr partID, RefMessage message)
  • in Max 2016
    public override RefResult NotifyRefChanged(IInterval changeInt, IReferenceTarget hTarget, ref UIntPtr partID, RefMessage message, bool propagate)
    There’s one more parameter! Surely someone with better knowledge of C# can avoid this problem by asking for the max version and creating an #if , but I don’t.

And there are things that desappear (I don’t remember exactly, but after days to get something working, when I tryed it with 2017 version this cammand didn’t exits).


#17

Got it, Thanks, I guessed Autodesk.max is changed over the time.Note that I don’t want to replace native controls. My UI is completely separate window snapped over the Max UI, So I don’t need to interact with parameterblocks or any other max controls. My pipeline is to create a WPF library include my windows, panels and controls. then I will load it as runtime dll in max. I will add eventhandlers and properties and bindings as much as possible in C#, But sometimes I had to add some eventhandlers in Max. In case the custom controls such as spinner, I prefer to add controller property in C#, because I’m pretty sure is much faster than MSX.


#18

it’s a very controversial judgment.


#19

You right, I shouldn’t say that without measuring…, But I want to say working with available methods inside a class like IControl is faster compare to call a MaxScript function by using C# and “ExecuteMAXScriptScript” to return a value.


#20

Hi!

As I got mentioned here: indeed I didn’t have time, yet, because my little son was born and eats all of my time…
In fact I am not able to overwrite the UI with WPF, I do only open/close a WPF window when opening/closing the parameter dialog.

Best regards


#21

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? …


#22

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?


#23

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
	
	/* ---------------------------------------------- */
	
)

#24

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.