3D PLM Enterprise Architecture

User Interface - Frame

The Command Headers

Objects which stand for your dialog commands
Technical Article

Abstract

Command headers stand for commands in workshops and workbenches, and are loaded instead of the commands when the workshop or workbench itself is loaded. A given command is actually loaded when the end user clicks on its representation to execute it. This article explains how and where to create command headers, and how to use them. 

Reading the "Application Frame Overview" article [1] can be useful to take full benefit from this technical article. You will find a first approach of the command header concept, and how command header are involved in the frame architecture. 


Introduction

Each command you want to make available in your workshop or workbench must have a command header. The command header plays the role of your business card, which holds information such as your name, your company, your function within the company, the address where your company is located, your phone and fax numbers, and your e-mail id. This card is very useful to contact you when you are out the office, and requires a very small space. Like a business card, the command header holds the necessary information to load the command, such as the name of the shared library in which the command's executable code is located, the name of the command class, and the data to pass to the command code when this command becomes the current one. 

The command header has resources for each command to display, such as the command name shown to the end user, its ToolTip displayed in a balloon, its help message, and its icon. This enables the workshop or workbench to be displayed, that is loaded in memory, without any of its commands being itself loaded, except the default one, spares memory space, and improves performance. The end user can see the icons in the toolbars, the items in the menu bar, can ask for help on a given command, without loading any of its commands. It's only when the user clicks on the menu item or on the icon that the command code is actually loaded. The "Creating Resources for Command Headers" article [2] explains in details what are the command header resources and how to assign them to command headers.

Before taking up the sections detailing how to create a command header class, it is important to understand how the command header instances are managed by the frame. This is the main goal of the "Command Header's Management" section. 

A command header instance is always an instance of a class deriving from the CATCommandHeader class. In most cases, you can create this class in a standard way using a macro. However, you can also explicitly create your class in order to manage the command availability, or to create a command header whose representation is customized. This is the topic of the Creating Customized Command Headers section.

Sometimes you would like to reuse commands in your workbench, but either you have no information about them, or you do not want to create a new command header instance to avoid losing resources. The Re-Using Existing Command Headers section first explains how to retrieve the command header instance associated with a command, and then draws your attention on how to reuse correctly an header instance.  

At last, from R12 onwards, you can create command header instances available whatever the type of the opened document, or even if there is no opened document. There are further named general headers. You create them in implementations of the CATIAfrGeneralWksAddin interface. Each implementation of this interface is an Add-in of the CATAfrGeneralWks workshop, also named General workshop [2]. The General Command Headers section explains the specificities of these general headers, and how to create the command that they launch.

[Top]

Command Headers Management

The goal of this paragraph is to explain the life cycle of a command header instance: who creates an instance and who deletes it. These explanations are illustrated by a schema Fig.1 showing that all command header instances associated with a workshop are grouped together in a list. The Workbenches Loading Order paragraph explains how this list is filled up. In an interactive session, this list is displayed in the Command tab page of the Tools/Customize command. The Customize command paragraph gives you some information about the Command tab page and its contents.

Life Cycle

A V5 document is controlled by an editor [3]. Each editor, a CATFrmEditor class instance, keeps a list of command header instances. To be exact, the editor keeps a list of command header instances for each workshop [1] it can manage. This is illustrated by the picture hereunder.

Fig.1 Command Header Instances Lists

Each list is filled at the CATCommandHeader class instantiation. The new instance is inserted in the current list of the current editor. The CATFrmEditor class manages the destruction of the CATCommandHeader instances. When a document is closed, all its lists are deleted, and their content is also deleted.

To avoid filling up uselessly the list of CATCommandHeader instances, it is recommended to make the command header instantiation only in the following methods because these methods are called once for each editor instance:

(*) There is an exception for Add-ins of the CATAfrGeneralWks workshop. In this specific case, the CreateCommands method is called once during the life time of the session. Refer to the General Command Headers section which goes deeper for this specific case. 

For commands implementing CATIAfrCmdPaletteOptions [6] or workbenches implementing CATIAfrPaletteOptions [7], the code creating command header instances should first verify that the header instance does not already exist into the list of the current editor. A call to the CATAfrGetCommandHeader global function, which retrieves a command header instance from its identifier, enables you check this. Keep in mind that these two interfaces enables you to set command header instances into the "Tools Palette" toolbar. But even if these headers are not created in a workbench or an add-in implementation, once created, they are kept, like the others, in the current list of the current editor. 

Workbench Loading Order

When an UI-active object is first UI-activated, the new list of command header instances is filled up in this order:

  1. The general command header instances. Those of DS ( New, Open), and those created in the CreateCommands of each CATIAfrGeneralWksAddin implementation. 
  2. The command header instances of the workshop implementation (DS code)
  3. All the command header instances created in Add-ins of the current workshop (mainly CAA code)
  4. The command header instances of the current workbench ( DS code or CAA code)
  5. All the command header instances created in Add-ins of the current workbench (mainly CAA code)

This means that once the document is opened, only a part of the command header instances are inside the list, since only one workbench has been loaded. The other workbenches and their associated add-ins are loaded only when a transition is invoked (Start menu for example).

The loading of all workbenches can be forced for the following reasons:

The Customize Command

The Customize command enables you to customize the organization of your commands in the frame. The Customize Dialog box contains the Commands tab page, see Fig.2, which displays the available commands for the end user.

Fig.2 Customize Command

The command header instances displayed in the Commands tab page are all the command header instances associated with the current UI-active object since launching the Customize command invokes the re-loading of all workbenches. However there are two exceptions:

  1. Command header instances that a CATIAfrCmdPaletteOptions or CATIAfrPaletteOptions could have been created if they have been called.

  2. Invisible command header instances

  3. Sometimes, it can be useful to hide headers to the end user. These can be internal headers, such as set and unset headers of a check header [17], or headers that you do not want the end user to drag and drop onto a toolbar [6]. 

    However, a hidden header in the Command tab page is not "dead". If the end user knows its name, it can always use the power input to launch it, or you can use the CATAfrStartCommand global function to do the same thing.

    You can hide, or show again a header using the SetVisibility method of the CATCommandHeader class. 

[Top]

Creating Standard Command Headers

To create a standard command header class, you can use the MacDeclareHeader macro. It creates for you a class which derives from CATCommandHeader which is the base class for command headers and should never be directly instantiated.

Let's assume you want to create a command header class named CAAAfrCommandHeader. You simply need to create a file, say CAAAfrCommandHeader.h, with the following code:

#include "CATCommandHeader.h"

MacDeclareHeader(CAAAfrCommandHeader);

This macro creates the CAAAfrCommandHeader class declaration and implementation. To instantiate this command header for a command, you should simply use the following constructor created by the macro.

#include "CAAAfrCommandHeader.h"

new CAAAfrCommandHeader("CAAAfrMyCommandHdr",
                        "CAAAfrCommandLibName",
                        "CAAAfrCommandClass",
                        void * ipParameter);

where:

Different commands can share the same command header class to create their command headers. Refer to the use case that creates a workbench [4]. The following example shows how to create command header instances with and without an argument to pass to the command:

void CAAAfrGeometryWks::CreateCommands()
{
  ...
  // Case without argument 
  new CAAAfrGeometryWksHeader("CAAAfrPointHdr",
                              "CAADegGeoCommands", 
                              "CAADegCreatePointCmd",    
                              (void *) NULL);
  ...
  // Cases with argument 
  new CAAAfrGeometryWksHeader("CAAAfrNormalXHdr",
                              "CAAAfrGeoCommands",
                              "CAAAfrChangeViewNormalCmd",
                              (void *)CATINT32ToPtr(1));

  new CAAAfrGeometryWksHeader("CAAAfrNormalYHdr",
                              "CAAAfrGeoCommands",
                              "CAAAfrChangeViewNormalCmd",
                              (void *)CATINT32ToPtr(2));

  new CAAAfrGeometryWksHeader("CAAAfrNormalZHdr",
                              "CAAAfrGeoCommands",
                              "CAAAfrChangeViewNormalCmd",
                              (void *)CATINT32ToPtr(3));

(*) CATINT32ToPtr is to be 64 compliant. 

[Top]

Creating Customized Command Headers

There are two reasons to create a customized command header:

  1. When you want your command availability to depend on the context that is on what exists or what happens in the document. 
  2. For example, let's assume a command applies to a given object in a document. If one or several instances of this object exist in the document, the command can be used and should be available. On the opposite, if no instance of this object exists, the command cannot execute, and it should be set unavailable. Its representation in menus and toolbars should be grayed out, and nothing should happen when the end user clicks on it. A full example describes such a customized command header [8].

  3. When you want to customize the representation of a command header. 
  4. Here are three examples:

    Fig.3 Examples of Customized Representations
    It is a combo header. In place of the push button, the command header instance is represented by a combo. A full example describes its creation [9]. 
    It is a "Most Recent Used" header. In place of a push item, the command header instance is represented by a dynamic list of push items (Item 1 and Item 2). When end users select one of them, a command is launched to display the selected item. Refer to the referenced use case for a complete description of such a command header. [10
    Two editors in a toolbar. Refer to the referenced use case for more details [19].

Customized Command Header Class Structure

A customized command header class cannot be created using macros. The example below is a minimum customized command header class. 

#include <CATCommandHeader.h>

class MyCustomizedCommandHeader : public HdrBaseClass
{
  CATDeclareClass;
  CATDeclareHeaderResources;

  public :
    MyCustomizedCommandHeader (const CATString & iHeaderName);
    virtual MyCustomizedCommandHeader ();
    CATCommandHeader * Clone();
      
  private :
    MyCustomizedCommandHeader (CATCommandHeader *ipHeaderToCopy);

    MyCustomizedCommandHeader ();
    MyCustomizedCommandHeader (const MyCustomizedCommandHeader &iObjectToCopy);
    ... 
};   

The customized command header class should derive from a CATCommandHeader class, so HdrBaseClass can be:

It should include the CATDeclareClass and CATDeclareHeaderResources macros. CATDeclareClass declares that the MyCustomizedCommandHeader class belongs to a CAA component, and CATDeclareHeaderResources inserts the methods to manage the command header resources. [2]

This class should also include in its public part:

This class should also include in its private part:

Here is an example of implementation of the MyCustomizedCommandHeader class. 

#include "MyCustomizedCommandHeader .h"
...
CATImplementClass(MyCustomizedCommandHeader ,
                  Implementation,
                  HdrBaseClass,
                  CATNull);

CATImplementHeaderResources(MyCustomizedCommandHeader ,   
                            HdrBaseClass,             
                            MyCustomizedCommandHeader );  
 
...

The CATImplementClass macro states that the MyCustomizedCommandHeader class OM-derives [11] from HdrBaseClass, the base class, and is a component main class thanks to the Implementation keyword.

The CATImplementHeaderResources macro is used in conjunction with the CATDeclareHeaderResources macro in the header file. It defines the command header resource file name [2]. The base class name set as second argument helps you use resource concatenation [12]. The third argument could be set to the name of another class associated with resource files using its class name, or to the name, without suffix, of an already existing resource file pair. Here it states that its associated resource file names use the class name: MyCustomizedCommandHeader.CATNls and MyCustomizedCommandHeader.CATRsc, respectively. 

The constructor must call one of the constructors of the base class, and the destructor is empty.

...
MyCustomizedCommandHeader ::MyCustomizedCommandHeader (const CATString &iHeaderName)
                             : HdrBaseClass(...)
{...}
				   
MyCustomizedCommandHeader ::MyCustomizedCommandHeader ()                     
{}
...

The Clone method calls the constructor class with this as an argument. The constructor class with a CATCommandHeader pointer is called. This constructor calls the same constructor of the base class.

...
CATCommandHeader * MyCustomizedCommandHeader ::Clone() 
{
  return new MyCustomizedCommandHeader (this);
}

MyCustomizedCommandHeader ::MyCustomizedCommandHeader (CATCommandHeader * ipHeaderToCopy)
                             : HdrBaseClass(ipHeaderToCopy)
{}
...

Managing Command Availability

One good reason to create explicitly a command header is the need to manage the availability of the command represented by the header. [8].

...
MyCustomizedCommandHeader ::MyCustomizedCommandHeader (const CATString &iHeaderName)
                             : HdrBaseClass(...)  
{ 

    if ( condition )
      BecomeAvailable();
    else
      BecomeUnavailable();
      
    ::AddCallback(this,    
                 publisher,   
                "NotifClassNameForAvailable",   
		(CATSubscriberMethod)&MyCustomizedCommandHeader ::AvailableCB, 
                NULL);  
                
    ::AddCallback(this,    
                 publisher,   
                "NotifClassNameForUnavailable",   
		(CATSubscriberMethod)&MyCustomizedCommandHeader ::UnavailableCB, 
                NULL);  
                  		   
}

The constructor should initialize the command availability by appropriately calling the BecomeAvailable or BecomeUnavailable method. If several commands share the same command header class, each command should be examined individually.

In addition, the constructor , and/or methods called by it, can set a callback on objects and events that can change the command availability. Methods called by these callbacks should of course be declared. 

...
void MyCustomizedCommandHeader ::UnavailableCB(CATCallbackEvent iPublishedEvent ,
		                          void   * iPublishingObject , 
		                          CATNotification   * iNotif,
				          CATSubscriberData   iUsefulData,
					  CATCallback         iCallbackId)
{
  BecomeUnavailable();
} 
void MyCustomizedCommandHeader ::AvailableCB(CATCallbackEvent iPublishedEvent ,
		                          void   * iPublishingObject , 
		                          CATNotification   * iNotif,
				          CATSubscriberData   iUsefulData,
					  CATCallback         iCallbackId)
{
  BecomeAvailable();
} 
...

Creating a Customized Representation 

Before detailing how to create such a command header, a global explanation is useful. Below is a diagram introducing the main objects.

You retrieve the MVC model:

Each command header instance can be represented one or several times. Each representation is the association of the command header instance with a starter. You do it in a workbench or in an Add-in, but the end user can interactively do it, by dragging and dropping a command (header) onto a toolbar.  

The customized command header is a component [17]. Here is its UML diagram: 

MyHeader, the component representing the customized command header Object Modeler [11] and C++ derives from the CATAfrDialogCommandHeader component. 

This component must implement the CATIAfrCommandHeaderRep interface. This interface has three methods: CreateToolbarRep, CreateMenuRep, and CreateCtxMenuRep. Each method corresponds to the container where the starter associated with the command header instance is included. The invoked method instantiates a class which creates the graphic representation. Below is the UML diagram of the class creating the graphic representation:

MyRep is a class which derives from the CATAfrCommandHeaderRep class. The main roles of this class are:

Managing Contextual Help

The “More Info …” shortcut from the LongHelp message is available only if the Dialog (CATDlgxxx) object SetLongHelpId method was called. On Standard command headers SetLongHelpId is automatically called on the button using the LongHelpId resource of the command header. For customized command headers the command header does not know the Dialog objects so no SetLongHelpId is called. SetLongHelpId has to be called explicitely on the Dialog objects created to represent the customized command header.

...
void MyCustomizedCommandHeader ::Build()
{
  ... pDialogItem->SetLongHelpId(pCustomizedCommandHeader->GetContextualHelp());
  ...
} 

[Top]

Re-Using Existing Command Headers

Until now, this article has explained how to create a command header instance to associate it with your own command. But sometimes you would like to re-use an existing command (CATCommand ) without recreating a new command header instance:

So you should be able to retrieve the identifier of a command header thanks to its NLS name. It is the one displayed in the Commands page of the Customize Command. The Workshop Exposition Command resolves this problem. 

However, once you have this identifier, before associating it with a starter, you should be aware of the workbench loading management. The rules to respect are detailed in the Reusability Rules paragraph.    

The Workshop Exposition Command 

The frame application provides the "Workshop Exposition" command to give the command header identifiers. You launch it as explained in the following scenario:

Launch CATIA, then, when the application is ready, 

In the picture below, the current workshop is the Part workshop, and the current workbench is Part Design

Fig.5 The Workshop Exposition Command

This Dialog command contains: 

The generated files contain two types of information, one being the list of the command header identifiers.  Here is an extract of the CATAfrGeneralWks.txt file:

where:

Title  The Nls title of the command header instance
Id The identifier of the command header instance
DLL The library exporting the CATCommand 
Cmd The CATCommand to execute 
Arg The possible argument of the CATCommand

Reusability Rules

Before associating any identifier with a starter, you must know that the workbenches (and their add-ins) are loaded when necessary. See the "Workbenches Loading Order" section. It means that, at a given time, all the command header identifiers defined in the workshop and its add-ins exist, but they are not necessary those of a workbench and its add-ins. The consequences are the followings:

It gives the following advices:

General Command Headers

The CATIAfrGeneralWksAddin interface enables you to define an add-in of the General (CATAfrGeneralWks) workshop [14], in other words to define command headers and starters always available whatever the open document, or even if no document is open. 

The CreateCommands and CreateToolbars methods, the two methods of any Add-in interface, are called once during the session, when it starts. The command header instances created in the CreateCommands method are kept in a list (named GeneralHdrList on the Fig.6 ). This list is written in each list associated with an editor, and the headers are duplicated thanks to the Clone method.

Fig.6

When no document is open, a command is launched from a command header instance coming from the GeneralHdrList list. But if a document is open, the same command is launched from the command header instance coming from the list associated with the document editor. With this architecture, you can understand that even if the end user has the feeling that the icon representing the command is associated with the same object, in fact, swapping between documents or closing all of them, invokes different command header instances. What are the consequences:

If the header is started when no document is open, it comes from the GeneralHdrList, the shared or exclusive command will be deleted as soon as a document is opened. 

If the header is started when an editor is active, it comes from a list associated with this editor, the shared or exclusive command will be also deleted as soon as another editor is activated. 

Consequently, you do not encounter any problem,  the starter of the command header is always normal (not highlighted) in the list associated with the "leaving" editor because the command is always deleted before a swap. So after a swap, you retrieves always the state "normal" in any another list. You can re-launch the same command with another command header instance. 

In this situation, you also have the Dialog command with the CATDlgWndModal behavior. You have an example with the Add Item in MRU command of the CAAAfrMRUHeader use case [10]. The command is not described, but you will retrieve in this article the location of the code. 

In most cases, this command is a CATDlgDialog class. An example is the Search command, or the Workshop Exposition command. 

It is recommended to create a command header which launches a simple CATCommand class. This class has no method. In the constructor class and in this order:

  1. Create the Dialog box  
  2. Take care of the Dialog parent - Refer to the technical article about the frame layout [3], and compare the difference between GetMainWindow and GetApplicationDocument, the two methods of CATApplicationFrame class. 

    The Dialog box must manage its life cycle. When the end user clicks the Close button, or the Close/Cancel buttons if they exist, the Dialog box is deleted (by a RequestDelayedDestruction on itself)

  3. Call RequestDelayedDestruction on itself 
  4. The "button" of the command header instance becomes "normal" (not highlighted anymore). The end user can re-launch the command, the Dialog box will appear twice, if the previous one has not been closed.

This command class remaining undefined (do not use the RequestStatusChange method ), the current shared or exclusive command is not deleted. 

[Top]


In Short

A command header stands for a command and avoids loading the command when the end user does not require it. A command header is an instance of a command header class. This class can be used for several commands, and can be created either using a macro or explicitly if the command header should manage availability information or customize its representation.

It is possible to re-use command header identifiers, but there are two rules to respect:

  1. In a workbench or in an add-in (workshop/workbench) avoid using an identifier coming from another workbench or workbench's Add-in.
  2. In a contextual menu do not use an identifier coming from a workbench or a workbench Add-in, but only coming from the workshop or an workshop add-in.

The "Workshop Exposition" command enables you to retrieve the command header identifiers.

[Top]


References

[1] Application Frame Overview
[2] Creating Resources for Command Headers
[3] Understanding the Application Frame Layout
[4] Creating a Workbench
[5] Creating an Add-in
[6] Creating a Command with Options in the "Tools Palette" Toolbar
[7] Using the "Tools Palette" Toolbar for a Workbench
[8] Creating Customized Command Headers
[9] Creating a Combo Command Header
10] Creating a Most Recent Used Command Header
[11] Object Modeler Inheritances
[12] Assigning Resources to a Dialog Box
[13] Inserting Commands in Contextual Menus
[14] Making Your Document Independent Command Available in All Workbenches
[15] The CAA Command Model
[16] Creating Check Button
[17] Creating a Component
[18] The Callback Mechanism
[19] Creating Editors in Toolbar
[Top]

History

Version: 1 [Jan 2000] Document created
Version: 2 [Feb 2003] Command header re-usage explanations
Version: 3 [Sep 2003] Palette and General workshop add-in Integration  
Version: 4 [Jan 2004] Customized command header integration + a complete review 
[Top]

Copyright © 2000, Dassault Systèmes. All rights reserved.