VGUI panels communicate via a message system to signal state changes or events to parents or children (or any other panel). Messages are not sent directly (e.g. by calling a panel listener function), rather they are handed over to VGUI which delivers them to the target panel. Thus beside the message content, a sender and a receiver panel must be specified as VPANEL handles. VGUI sends event messages to inform panels about changes or events (mouse moved, input focus changed etc).
The message name and content is specified using a KeyValues object (\public\KeyValues.h). The KeyValues class is a very generic and flexible structure to store data records containing strings, integer or float numbers. A KeyValues object has a name and a set of data entries. Each entry has a unique key name and corresponding value. KeyValues also allow creating hierarchical structures using sub keys, though most VGUI messages are flat data records. KeyValues don't have data/type definitions or similar, so you can add or remove entries of any type as you like. Thus sender and receiver must both know the internal structure (e.g. key names, types and their meaning) of a KeyValues message object to communicate successfully. Here some sample code how to use KeyValues in general:
// create a new KeyValues Object named "MyName"
KeyValues *data = new KeyValues("MyName");
// add data entries
data->SetInt( "aInteger", 42 );
data->SetString( "aString", "Marvin" );
// read data entries
int x = data->GetInt("aInteger");
Con_Printf( data->GetString("aString") );
// destroy KeyValues object again, free data records
data->deleteThis();
To send a message you can call the Panel member function PostMessage(…) or directly ivgui()-> PostMessage(…). The name of the KeyValues object is also the message name used for later dispatching. VGUI will call the target panel's OnMessage(…) function, which will dispatch the message to a previous defined message handler. A panel can register new message handlers with one of the MESSAGE_FUNC_* macros, which adds a handler function to the message map. Never overwrite OnMessage(…) to handle new messages, always use a macro.
First declare a message handler for the receiving panel:
class MyParentPanel : public vgui::Panel
{
...
private:
MESSAGE_FUNC_PARAMS( OnMyMessage, "MyMessage", data );
}
void MyParentPanel::OnMyMessage (KeyValues *data)
{
const char *text = data->GetString("text");
}
The sending panel creates a KeyValues object, adds message parameters and sends the message (to its parent in this case). The KeyValues object is destroyed by VGUI after it has been processed.
void MyChildPanel::SomethingHappend()
{
if ( GetVParent() )
{
KeyValues *msg = new KeyValues("MyMessage");
msg->SetString("text", "Something happend");
PostMessage(GetVParent(), msg);
}
}
Using PostMessage() the sending panel must address a single, specific target, which means that all other panels interested in a state change must be known and addressed individually. To avoid hard coding these dependencies, panels have a public event system called action signals. A panel fires generic events with PostActionSignal(KeyValues *message) and interested panels can register as listeners for these signals with AddActionSignalTarget(Panel *target). These action signals are widely used by VGUI controls, for example messages like "TextChanged" fired by class TextEntry or "ItemSelected" used by class ListPanel. All action signal messages contain a pointer entry "panel" that points to the sending panel.
The example from above using action signals would need the parent panel to register as a listener, preferably in the constructor after the child panel has been created. The child panel just uses PostActionSignal() instead of PostMessage():
MyParentPanel::MyParentPanel()
{
...
m_pChildPanel->AddActionSignalTarget( this );
}
void MyParentPanel::OnMyMessage (KeyValues *data)
{
const char *text = data->GetString("text");
Panel *pSender = (Panel *) kv->GetPtr("panel", NULL);
}
void MyChildPanel::SomethingHappend()
{
KeyValues *msg = new KeyValues("MyMessage");
msg->SetString("text", "Something happend");
PostActionSignal ( msg );
}
A commonly used action signal is the "Command" message, since no message handler needs to be installed. Panels just need to derive the virtual Panel function OnCommand(const char *command) and check for the correct command string. The "Command" message is used by all Button classes and is fired whenever the button is pressed. Here the example from above using the Command message:
class MyParentPanel : public vgui::Panel
{
...
protected:
virtual void OnCommand(const char *command);
}
MyParentPanel::MyParentPanel()
{
...
m_pChildPanel->AddActionSignalTarget( this );
}
void MyParentPanel::OnCommand(const char *command)
{
if (!stricmp(command, "SomethingHappend "))
{
DoSomething ();
}
else
{
BaseClass::OnCommand(command);
}
}
void MyChildPanel::SomethingHappend()
{
KeyValues *msg = new KeyValues("Command");
msg->SetString("command", "SomethingHappend");
PostActionSignal ( msg );
}