GUI and Interaction Messages Manual
Communication between different parts of the Cinema 4D GUI happens through multiple messages. These messages are sent to GeDialog and GeUserArea elements:
Messages are represented by BaseContainer objects that store the parameters of the message. The ID of the message is typically the ID of the BaseContainer ( BaseContainer::GetId() ). See BaseContainer Manual .
For details on GeDialog and GeUserArea see GeDialog 手册 and GeUserArea Manual .
// This example catches a message to set the cursor when the mouse is over the user area.All message are first sent to the object's "Message" function ( GeDialog::Message() , GeUserArea::Message() ). If the messages are not handled in the implementation of the "Message" function they will be also handled in the base-class. Certain messages are re-directed to dedicated functions.
Several messages sent to a GeDialog will invoke these member functions:
Several messages sent to a GeUserArea will invoke these member functions:
Messages can be sent to receive information from the target element. In some cases the message must explicitly state that it requests a result.
// prepare message BaseContainer message = BaseContainer ( BFM_EDITFIELD_GETCURSORPOS ); message. SetBool ( BFM_REQUIRESRESULT , true );
// send message const GeData res = this->SendMessage(3000, message);
// get position const Int32 pos = res. GetInt32 (); ApplicationOutput ( "Cursor Pos.: " + String::IntToString (pos));
When the mouse cursor is moved over a GeDialog or GeUserArea a message is sent to these elements. The reaction to this message allows to define the cursor and some help text.
The parameters of the message are:
Compare also ToolData::GetCursorInfo() , SceneHookData::GetCursorInfo() ,等。
The message BFM_CURSORINFO_REMOVE can be sent by a custom callback function to inform a user area that the cursor has left the area:
// This example user area registers a callback function with RemoveLastCursorInfo(). // This function is called when the cursor leaves the user area. It is used to send the // message BFM_CURSORINFO_REMOVE back to the user area to inform it about the event.// declarations static void RemoveCursorInfo(); class RemoveCursorUserArea; static RemoveCursorUserArea* g_userArea = nullptr ; // static variable storing pointer to a user area
// user area will display a different color depending if the cursor is over the area or not class RemoveCursorUserArea : public GeUserArea { public : RemoveCursorUserArea() { if (g_userArea == this ) g_userArea = nullptr ; }; ~RemoveCursorUserArea() { }; Int32 消息 ( const BaseContainer & msg, BaseContainer & result) { // messages are identified by the BaseContainer ID switch (msg. GetId ()) { case BFM_GETCURSORINFO : { // register RemoveCursorInfo() callback g_userArea = this ; RemoveLastCursorInfo (RemoveCursorInfo); _cursorInside = true ; Redraw (); return true ; } case BFM_CURSORINFO_REMOVE : { // message "BFM_CURSORINFO_REMOVE" sent by RemoveCursorInfo() _cursorInside = false ; Redraw (); break ; } } return GeUserArea::Message (msg, result); } void DrawMsg ( Int32 x1, Int32 y1, Int32 x2, Int32 y2, const BaseContainer & msg) { OffScreenOn (); SetClippingRegion (x1, y1, x2, y2);
// gadget color is controlled by the cursor position
if (_cursorInside) DrawSetPen ( 向量 (1.0, 0.0, 0.0)); else DrawSetPen ( 向量 (0.0, 1.0, 0.0)); DrawRectangle (x1, y1, x2, y2); } private : Bool _cursorInside = false ; // set to true if the cursor is over the user area };// function will be called by the user area when the cursor left its area static void RemoveCursorInfo() { if (g_userArea == nullptr ) return ;
// send message to the user area BaseContainer bc; g_userArea->Message( BaseContainer ( BFM_CURSORINFO_REMOVE ), bc); }
A GeDialog or GeUserArea is informed when it receives or looses the focus:
A GeDialog is informed when it is about to be closed. One has to return false if it is safe to close the dialog.
A GeDialog is informed when any kind of interaction with some hosted gadget starts or ends:
When a gadget or custom GUI element is changed by the user it sends the message BFM_ACTION to the parent dialog.
The parameters of the message are:
// This example sends a message from a GeDialog (iCustomGui) // to the parent GeDialog to inform about change of stored value.
// construct the message BaseContainer message( BFM_ACTION ); message. SetInt32 ( BFM_ACTION_ID , GetId()); message. SetData ( BFM_ACTION_VALUE , _value);
// send the message SendParentMessage(message);
// This example catches an interaction with an edit number field in GeDialog::Command(). // If the user clicked with the right mouse button on the number field's arrows, // the value BFM_ACTION_RESET is set.
if ( id == ID_EDIT_NUMBER) { if (msg. GetBool ( BFM_ACTION_RESET )) { // right click on the number field's arrows // set the value to a pre-defined default value const Float defaultValue = 0.5; SetPercent(ID_EDIT_NUMBER, defaultValue, 0.0, 100.0, 1.0); } else { // just pass through the given value const Float value = msg. GetFloat ( BFM_ACTION_VALUE ); SetPercent(ID_EDIT_NUMBER, value, 0.0, 100.0, 1.0); } }Both GeDialog and GeUserArea are informed on keyboard and mouse user interaction events.
The parameters of the message are:
// check if this is a mouse event triggered by the right mouse button const Bool deviceIsMouse = device == BFM_INPUT_MOUSE ; const Bool channelIsRight = channel == BFM_INPUT_MOUSERIGHT ; if (deviceIsMouse && channelIsRight) { ApplicationOutput ( "Right mouse button pressed." ); const Int32 qualifier = msg. GetInt32 ( BFM_INPUT_QUALIFIER ); if (qualifier & QCTRL ) ApplicationOutput ( "Right mouse button with CTRL pressed." ); return true ; }
The input from keyboard interactions is either a character or some special key:
// check if the String contains any content if (key. IsPopulated ()) ApplicationOutput ( "Key: " + key); return true ; }
Further details on the event are stored in these parameters:
// get device and channel const Int32 device = msg. GetInt32 ( BFM_INPUT_DEVICE ); const Int32 channel = msg. GetInt32 ( BFM_INPUT_CHANNEL );
// check if this is a mouse event triggered by the left mouse button const Bool deviceIsMouse = device == BFM_INPUT_MOUSE ; const Bool channelIsLeft = channel == BFM_INPUT_MOUSELEFT ; if (deviceIsMouse && channelIsLeft) { // check if double click if (msg. GetBool ( BFM_INPUT_DOUBLECLICK )) { // get the cursor position Int32 mx = msg. GetInt32 ( BFM_INPUT_X ); Int32 my = msg. GetInt32 ( BFM_INPUT_Y ); Global2Local(&mx, &my); ApplicationOutput ( "Double click at " + String::IntToString (mx) + " - " + String::IntToString (my)); return true ; } }
Both GeDialog and GeUserArea are informed on drag and drop operations onto their area:
The parameters of the message are:
Different types of elements can be dragged:
另请参阅 Drag and Drop and Drag and Drop .
// This example receives a drag & drop message in GeUserArea::Message(). // If the dragged element is a command (from the "Customize Commands" dialog), // the command is executed. case BFM_DRAGRECEIVE : { // check drag area if (!CheckDropArea(msg, true , true )) break ; Int32 type; void * data = nullptr ;// get the drag data if (!GetDragObject(msg, &type, &data)) return false ; if (type == DRAGTYPE_COMMAND ) { // check if the drag has finished if (msg. GetInt32 ( BFM_DRAG_FINISHED )) { // end of drag // get command ID and execute the command const Int32 ID = msg. GetInt32 ( BFM_DRAG_DATA_NEW ); StopAllThreads (); CallCommand (ID); return true ; } else { // while in drag show cursor with question mark return SetDragDestination( MOUSE_QUESTION ); } } return false ; }
A custom GUI element can be used to display a parameter of a NodeData based plugin the in the Attribute Manager. It is possible to send a message from the custom GUI to that NodeData based plugin. This can be used to inform the plugin on what internal data specificity was changed.
另请参阅 Attribute Manager Interaction .
// This example shows some part of a custom GUI that has a color picker that defines // the color of some of it's elements. When this color is changed a message is sent // to the host NodeData based plugin (assuming that the custom GUI is used by the // Attribute Manager to display a parameter).// Update GUI this->InitValues();
// get the current color 向量 color; Float brightness; GetColorField(COLOR_ID, color, brightness);
// store message data BaseContainer messageContent; messageContent. SetVector (MSG_DESCRIPTION_COLORSTRING_COLOR, color);
// prepare message BaseContainer message( MSG_DESCRIPTION_CUSTOMGUI_NOTIFICATION ); message. SetInt32 ( BFM_ACTION_ID , GetId()); message. SetInt32 ( BFM_ACTION_VALUE , MSG_DESCRIPTION_COLORSTRING_COLORCHANGE); message. SetContainer ( MSG_DESCRIPTION_CUSTOMGUI_NOTIFICATION_CONTENT , messageContent); message. SetInt32 ( MSG_DESCRIPTION_CUSTOMGUI_NOTIFICATION_ID , ID_CUSTOMGUI_COLORSTRING);
// send message SendParentMessage(message);
A GeUserArea can inform the parent dialog when it changed its size:
A GeDialog can be part of a layout and is created when that layout is loaded. To restore a given state of that GeDialog , internal data of that dialog must be stored with the layout. This internal data is received and reset using these messages:
A GeDialog defines its layout using groups with multiple columns/rows. The weights of a group define the relative scale of these columns/rows. When the user changes the scale a message is sent to the GeDialog :
// save the weights case BFM_WEIGHTS_CHANGED : { GroupWeightsSave(WEIGHT_GROUP, _weights); break ; } // store the weights case BFM_LAYOUT_GETDATA : { result = BaseContainer (0); result. SetContainer (10000, _weights); return true ; } // restore the weights case BFM_LAYOUT_SETDATA : { const BaseContainer * bc = msg. GetContainerInstance ( BFM_LAYOUT_SETDATA ); if (bc) { // access the sub-container with the ID 10000 const BaseContainer * const weightsContainer = bc-> GetContainerInstance (10000); if (weightsContainer) _weights = *weightsContainer; GroupWeightsLoad(WEIGHT_GROUP, _weights); return true ; } break ; }
A GeUserArea can support a dynamic fading effect. After GeUserArea::ActivateFading() was called, Cinema 4D sends the BFM_FADE message to the user area for the duration of the fading operation.
// redraw using the updated COLOR_BG Redraw (); return true ; } } return GeUserArea::Message (msg, result); } void DrawMsg ( Int32 x1, Int32 y1, Int32 x2, Int32 y2, const BaseContainer & msg) { OffScreenOn (); SetClippingRegion (x1, y1, x2, y2); // draw background with COLOR_BG DrawSetPen ( COLOR_BG ); DrawRectangle (x1, y1, x2, y2); // draw text DrawSetTextCol ( COLOR_CONSOLE_TEXT , COLOR_TRANS ); DrawSetFont ( FONT_MONOSPACED ); DrawText ( "User Area" _s, 0, 0, DRAWTEXT_HALIGN_LEFT ); } };
The message BFM_DRAW is sent to a GeUserArea to draw into the Cinema 4D GUI. The message will call GeUserArea::DrawMsg() .
The clipping dimensions are stored in these parameters:
The draw reason is also stored:
// check if DrawMsg() was called because the focus changed const Bool reasonGotFocus = reason == BFM_GOTFOCUS ; const Bool reasonLostFocurs = reason == BFM_LOSTFOCUS ; if (reasonGotFocus || reasonLostFocurs) return ;
Dialogs and user areas are informed when they become visible:
Some messages can only be sent to specific elements or are only sent by specific elements.
Text edit field messages:
// set the text of the edit field. const String text { "Hello World" }; SetString(ID_TEXTEDIT, text);
// sets the cursor to the end of the new text BaseContainer bc { BFM_EDITFIELD_SETCURSORPOS }; bc. SetInt32 (1, ( Int32 )text.GetLength()); SendMessage(ID_TEXTEDIT, bc);
// set focus Activate(ID_TEXTEDIT);
Multi-line edit fields messages:
Messages to access the internally stored undo stack of a multi-line edit field:
It is also possible to send messages from the IDM list to the multi-line edit field.
// This example GeDialog includes a multi-line edit field and two buttons. // The two buttons can be used to send "Undo" and "Redo" commands to the // multi-line field. The buttons are enabled or disabled depending on the // undo stack of the field. Bool CreateLayout() { SetTitle( "Simple Script Manager" _s); const Int32 style = DR_MULTILINE_STATUSBAR | DR_MULTILINE_HIGHLIGHTLINE | DR_MULTILINE_WORDWRAP | DR_MULTILINE_MONOSPACED | DR_MULTILINE_PYTHON | DR_MULTILINE_SYNTAXCOLOR ;// add a multi line text field AddMultiLineEditText(ID_MULTILINE_EDIT, BFH_SCALEFIT , 0, 200, style);
// set some python code SetString(ID_MULTILINE_EDIT, "import c4d\n\nprint(\"hello world\")\n" _s);
// add two buttons for "Undo" and "Redo" AddButton(ID_UNDO_BUTTON, BFH_SCALEFIT , 0, 10, "Undo" _s); AddButton(ID_REDO_BUTTON, BFH_SCALEFIT , 0, 10, "Redo" _s);
// use a timer to check the undo stack of the text field this->SetTimer(250); return true ; } Bool Command( Int32 id , const BaseContainer & msg) { if ( id == ID_UNDO_BUTTON) { // when "Undo" button was pressed, send IDM_UNDO // to the multi line text field SendMessage(ID_MULTILINE_EDIT, BaseContainer ( IDM_UNDO )); CheckUndoState(); } if ( id == ID_REDO_BUTTON) { // when "Redo" button was pressed, send IDM_REDO // to the multi line text field SendMessage(ID_MULTILINE_EDIT, BaseContainer ( IDM_REDO )); CheckUndoState(); } return GeDialog::Command ( id , msg); } void Timer( const BaseContainer & msg) { CheckUndoState(); }
// ---------------------------------------------------------------------------------------- // Checks the current undo stack of the multi-line edit text. // Depending on the stack the "Undo" and "Redo" buttons are enabled. // ---------------------------------------------------------------------------------------- void CheckUndoState() { // send message to get data BaseContainer getUndoLevelMsg = BaseContainer ( BFM_EDITFIELD_UNDOSTAT_UNDOLEVEL ); getUndoLevelMsg. SetBool ( BFM_REQUIRESRESULT , true ); const GeData res = SendMessage(ID_MULTILINE_EDIT, getUndoLevelMsg);
// check if the message result is a BaseContainer if (res. GetType () == DA_CONTAINER ) { BaseContainer * stat = res. GetContainer (); if (stat) { // get undo stack size and current level const Int32 undoStackCount = stat-> GetInt32 ( BFM_EDITFIELD_UNDOSTAT_COUNT ); const Int32 currentLevel = stat-> GetInt32 ( BFM_EDITFIELD_UNDOSTAT_UNDOLEVEL );
// enable and disable buttons accordingly if (currentLevel > 0) Enable(ID_UNDO_BUTTON, true ); else Enable(ID_UNDO_BUTTON, false );
// check if the current level is not the latest undo-level if (currentLevel < (undoStackCount - 1)) Enable(ID_REDO_BUTTON, true ); else Enable(ID_REDO_BUTTON, false ); } } }
The message BFM_SETSTATUSBAR can be sent to edit the status bar of a scroll group or a progress bar custom GUI element:
The parameters of this message are:
// This example sends a message to a scroll group to set the text of its status bar. BaseContainer bc( BFM_SETSTATUSBAR ); bc. SetData ( BFM_STATUSBAR_PROGRESSON , false ); bc. SetData ( BFM_STATUSBAR_TXT , "Some Text" ); bc. SetData ( BFM_STATUSBAR_HELP , "More Text" ); SendMessage(ID_SCROLLGROUP, bc);
A message is sent from a popup button:
// This example catches a message in GeDialog::Message() after the pop up button was pressed // (but before the pop up menu is created).
case BFM_POPUPNOTIFY : { const Int32 ID = msg. GetInt32 ( BFM_ACTION_ID ); if (ID == ID_POPUP) { ApplicationOutput ( "The pop up button was pressed" _s); } break ; }// This example catches the result in GeDialog::Command() after // something was selected in the pop up button's pop up menu.
if ( id == ID_POPUP) { const Int32 selectedItem = msg. GetInt32 ( BFM_ACTION_VALUE ); ApplicationOutput ( "The item " + String::IntToString (selectedItem) + " was selected" ); }A scroll group informs the parent dialog when it was scrolled:
The base class for custom GUI elements (used to display NodeData parameters in the Attribute Manager) is iCustomGui which is based on GeDialog . Such custom GUI elements receive these messages when the used "User Interface" is changed in the Attribute Manager. The custom GUI can then store the ID of the currently active gadget:
Both GeDialog and GeUserArea receive core messages. These messages call GeDialog::CoreMessage() or GeUserArea::CoreMessage() .
See the Core Messages Manual .
// This example catches EVMSG_ASYNCEDITORMOVE in a GeDialog. case EVMSG_ASYNCEDITORMOVE : { // check if this core message is new if (CheckCoreMessage(bc)) { const Int movement = ( Int )bc. GetVoid ( BFM_CORE_PAR1 ); switch (movement) { case MOVE_START : { ApplicationOutput ( "Start Movement" ); break ; } case MOVE_CONTINUE : { ApplicationOutput ( "Continue Movement" ); break ; } case MOVE_END : { ApplicationOutput ( "End Movement" ); break ; } } } break ; }