This section discusses the approach developers may use to build action tables and make them available to the system. In most cases building an action table is fairly easy. There is more work involved if you choose to implement your own class derived from ActionItem, but in most cases that isn't needed.
An action table is built by making a static table of action descriptions ( ActionDescription ) and passing it to the constructor for ActionTable. For example, here is the code that builds the action table for the maxMenuDemo HowTo sample:
// This packages up our actions into an ActionDescription for our ActionTable #define NumElements(array) (sizeof(array)/sizeof(array[0])) static ActionDescription spActions[] = { ID_MENU1, // ID IDS_MENU1, // menu1 IDS_MENU1, IDS_CATEGORY, ID_MENU2, IDS_MENU2, IDS_MENU2, IDS_CATEGORY }; // Finally, a method that registers and returns our ActionTable. ActionTable* GetActions() { TSTR name = _T("SDKDocsActionTable"); HACCEL hAccel = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDR_ACCELERATOR1)); int numOps = NumElements(spActions); ActionTable* pTab; pTab = new ActionTable(kMyActions, kMyContext, name, hAccel, numOps, spActions, hInstance); GetCOREInterface()->GetActionManager()->RegisterActionContext(kMyContext, name.data()); return pTab; }
The constructor for ActionTable takes the ID of the table, the context id, a name for the table, a windows accelerator table that gives default keyboard assignments for the operations, the number of items, the table of operation descriptions, and the instance of the module where the string resources in the table are stored.
At the same time the table is built, you also need to register the action context ID with the system. This is done with the IActionManager::RegisterActionContext() method.
The next part of implementing an ActionTable is to derive a class from ActionCallback. This is an abstract class. You will need to pass an instance of the derived class to 3ds Max when you activate the ActionTable by calling IActionManager::ActivateActionTable(). Then when the system wants to execute an action, you will get a callback to ActionCallback::ExecuteAction().
For the maxMenuDemo sample, this looks like:
class ExampleActionCB : public ActionCallback { public: ExampleActionCB(){}; BOOL ExecuteAction(int id); }; // This is where the callbacks are executed. Here we simply print a message to the status prompt. BOOL ExampleActionCB::ExecuteAction (int id) { switch (id) { case ID_MENU1: GetCOREInterface()->PushPrompt(_T("Menu 1 executed")); return true; case ID_MENU2: GetCOREInterface()->PushPrompt(_T("Menu 2 executed")); return true; } return false; }
Finally, the system needs to activate and deactivate the table at the appropriate time. When to do this depends on the scope of applicability of the table. If your ActionTable is exported from an editable object or modifier plugin, then you typically want it only to be active when in the context of the command. This is done by activating it in your plugin's Animatable::BeginEditParams() method, and deactivating it in Animatable::EndEditParams(). See the FFD modifier in the SDK samples directory for an example of this approach. In other plugin types, you can perform the ActionTable activation and deactivation elsewhere, for example if you want to toggle your menu while the plugin is still running.
For the maxMenuDemo sample, activation looks like this:
myActionCB = new ExampleActionCB(); GetCOREInterface()->GetActionManager()->ActivateActionTable( myActionCB, kMyActions );
The first parameter is the ID of the table to activate, and the second is an instance of the ActionCallback class that is responsible for executing actions.