Developing Add-Ons for CC3+ – Part 3: Communicating with the User

This is the third article in my series about XP development. To understand this article properly, you should be familiar with the contents of the previous articles.

When writing commands for CC3+, we frequently need to communicate with the user. This is often in the form of requesting some data from the user, such as where to place a node or which color to use, or we want to provide information to the user, such as instructions on the command line or the information the user requested. In this article, I’ll talk about the Text Formatting & Output Service (FormSt) used to prepare data to display to the user, and the data request format (ReqData). We have been touching both of these briefly in the two prior articles, but it is time to discuss these a bit more in depth.

Text Formatting & Output Service (FormSt)

FormSt is responsible for preparing text for display to the user, and for actually displaying it. The preparation includes taking text strings and variables and building a FormSt data packet from them while the output service displays the output where you need it, such as on a command line or a dialog box or a variable in memory.

Usually, the way we build a FormSt packet is through the use of the FORMST macro. Below is an example from a standard XP about box:

FORMST(MyAPkt, "CC3+ XP\n\n"
	"About box for my CC3+ XP"
)

The first parameter in this box is the variable that will hold the data packet. The rest is simply the text strings to display (the example uses two, but you can use more or less).

We can now display the text in a dialog box by calling FormSt():

FormSt(&MyAPkt, RSC(FD_MsgBox));

This function simply takes in a reference to the FORMST packet, and a destination, and displays the message. In the example above we use a simple message box, but you can use other destinations too, these are found in the _FORMST.H file from the toolkit. Note that not all destinations can be used in all circumstances, and that not all destinations works with line breaks (like we have in the example above, the \n character). For example, you can display data in the status bar by using the FD_Status destination:

FormSt(&MyAPkt, RSC(FD_Status));

The destinations that make sense for a straight up call like this are:

FD_MsgBox	3	// Message Box (generic, no icons)
FD_InfoBox	4	// Info icon message box
FD_WarnBox	5	// Warning (! icon) message box
FD_ErrorBox	6	// Critical error message box
FD_QuestBox	7	// Question (yes/no) message box
FD_CancelBox	8	// Yes/No/Cancel message box
FD_RetryBox	9	// Retry/Cancel message box
FD_Status	11	// Status bar

Note that you cannot easily set the titles of these dialog boxes. There are other ways of making dialogs if you need this, anything you can do with the standard Windows SDK goes, but those aren’t macro safe.

Advanced FormSt data packets

The real use for FormSt is apparent when we look at the more advanced form of the construct. Let us start with a simple example

int n1 = 6;
int n2 = 23;
int sum = n1 + n2;
int diff = n1 - n2;
	
FORMSTPKT(MyAPkt, "!02 + !01 = !03\n!01  - !02 = !04", 4)
	ITEMFMT(n1, FT_Dec4, FJ_Right, 3, 0)
	ITEMFMT(n2, FT_Dec4, FJ_Right, 3, 0)
	ITEMFMT(sum, FT_Dec4, FJ_Right, 3, 0)
	ITEMFMT(diff, FT_Dec4, FJ_Right, 3, 0)
FORMSTEND
	
FormSt(&MyAPkt, RSC(FD_MsgBox));

The interesting thing here is our ability to insert variables into the text. If you look at the first line, you’ll see a text string with multiple numbers prefixed by a exclamation mark. These are placeholders that will be replaced by the data on the following lines. The last parameter, 4 in this case, is the number of these variables, and must match the number of ITEMFMT lines below. Note that all of FORMSTPKT, ITEMFMT and FORMSTEND are macros, not functions.

Each of the ITEMFMT lines represent one variable to be inserted into the text. In addition to declaring the variable to insert, these lines also control the appearance of the content. The five parameters are

  1. Variable name. This need to be an existing variable.
  2. Variable data type. This controls how the variable is used/displayed. For example, FT_Dec4 as used in the example treats it as a standard decimal number, while if you set it to FT_Dist4 instead, it will be treated as a distance, which also means it will take on the formatting of the current distance format in the drawing. Check the example below where I use point and distance types here. You’ll find the full list of supported types in _FORMST.H. Note that this value is loosely dependent on the actual type of the variable, but there isn’t a 1-to-1 ratio here.
  3. This value specifies the justification of the inserted variable. Valid values are FJ_Right, FJ_Left, FJ_Cen and FJ_Var. The first three expects a fixed field size (see below) to justify the content within, while the latter is for variable field size where the content just takes up the place it needs
  4. This is the field size in characters. This is used for the justification options, for example if the field size is 10 and the justification is set to FJ_Cen, the contents of the variable will be centered within the field no matter the actual size of the variable content. Note that this also applies a max size for the field, so if the field size is 4, and the content is 5 characters long, the last character will be stripped off. There is one caveat here though, Windows uses proportional fonts for its dialog boxes by default, which means that trying to justify things based on the number of characters is troublesome. If the justification is set to FJ_Var, this value is ignored.
  5. This controls the number of decimal places if the variable is any type of real number. Set it to 0 to just get the integer value. You can also set it to FDP_User to have CC3+ use whatever is the current distance display format.

Here is a slightly more advanced example:

GPOINT2 p1 = { 7.0f,13.0f };
GPOINT2 p2 = { 24.0f,97.0f };
float dist = Dist2P(p1.x, p1.y, p2.x, p2.y);
		
FORMSTPKT(MyAPkt, "The distance between (!01) and (!02) is !03", 3)
	ITEMFMT(p1, FT_2dC4, FJ_Var, 1, 0)
	ITEMFMT(p2, FT_2dC4, FJ_Var, 1, 0)
	ITEMFMT(dist, FT_Dist4, FJ_Var, 0, FDP_User)
FORMSTEND

FormSt(&MyAPkt, RSC(FD_MsgBox));

This example sets up two points, and then calculates the distance between them. The FormSt packet is then set up to display the two points using an appropriate format for displaying points (FT_2dC4) and the distance between them using a distance display format. The interesting thing here is that this will follow the display format set in the UNITS dialog. Try setting different display formats and drawing units and try it out. Also note that CC3+ does calculate between units, so if you set the drawing units to Feet, and the display format to meters, the numbers in the dialog will reflect that.

Requesting Data

So, we know how to provide information to the user, but we also need a way for the user to provide data back to the user. There are generally several ways to do this in CC3+, such as by typing it in on the command line, clicking in the drawing window, or answering a dialog. Let us examine these options.

Simple Dialogs

For simple questions (like yes/no), we can use one of the predefined dialogs, the call to FormSt return a value depending on the button we clicked on. We can then compare the result to IDOK, IDYES, IDNO, IDRETRY and IDCANCEL to figure out what button the user clicked.

FORMST(MyAPkt, "Do you really want this")

int response = FormSt(&MyAPkt, RSC(FD_QuestBox));

if (response == IDYES) {
	FORMST(MyAPkt2, "Yes, You do")
	FormSt(&MyAPkt2, RSC(FD_InfoBox));
} else {
	FORMST(MyAPkt2, "No, you don't")
	FormSt(&MyAPkt2, RSC(FD_WarnBox));
}

Advanced Requests

For more advanced requests however, CC3+ has a highly configurable request data call. To use this, you need to set up an RDATA struct containing the request parameters, and then call ReqData. ReqData then takes care of getting the data from the user, and then calls the specified method with the result, allowing you to handle it.

The RDATA struct is defined like this in _RDATA.H

typedef struct tagRDATA{
DWORD		len;		// length of structure
char		dtype;		// RD_xxx data type
PCMDPROC	RBProc;		// right button pressed procedure
DWORD		flags;		// RDF_xxx options flags
DWORD		*dadr;		// data store address (0=don't)
DWORD		*prmp;		// prompt FormSt packet
DWORD		CsrProc;	// RDC_xxx cursor type code
PRCVPROC	RcvProc;	// Data Receive Procedure
PVERPROC	XDta;		// extra data (verify data proc)
PCTXTPROC	CtxtProc;	// context procedure [F2]
DWORD		HelpNum;	// Help context id
DWORD		*HelpFile;	// Help file name
DWORD		keysink;	// 0 = prompt line	
} RDATA;

I won’t describe every option in detail here, but I’ll go through the basics to get you started. Let us look at an example that request a string from the user

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
char name[50];

void XPCALL ReqString() {
	FORMST(lpszPrompt, "Your Name:\0")

	RDATA NameReq =
	{ sizeof(RDATA), RD_TxWord, NULL, RDF_C | RDF_SPACEOK, (DWORD*)&name,
	(DWORD*)&lpszPrompt,RDC_ARROW, SayHello, NULL, NULL, 0, NULL, 0 };

	ReqData(&NameReq);

}

void XPCALL SayHello(const int Result, int Result2, int Result3) {
	if (Result != X_OK) { CmdEnd(); return; }
	UNREFERENCED_PARAMETER(Result2);
	UNREFERENCED_PARAMETER(Result3);

	FORMSTPKT(MyFPkt, "Hi !01\nHave a Nice Day\0", 1)
		ITEMFMT(name, FT_Stg, FJ_Var, 0, 0)
	FORMSTEND

	FormSt(&MyFPkt, RSC(FD_MsgBox));
	CmdEnd();
}

When you run this code, you should first get a prompt on the command line asking you for your name, and after inputting it and hitting enter, you should get a dialog showing it.

This code uses a lot of the FormSt stuff we talked about earlier, so I won’t explain those parts.

Line 6-8 sets up the RDATA request. The second parameter (dtype) tells CC3+ what kind of data we want, which also determines how CC3+ will ask for it. In this example, it is set to RD_TxWord, which means that we want some text, which CC3+ will ask for on the command line. The default behavior is to get just a single word, resulting in CC3+ considering the data collection finished on the first whitespace character, like space or enter. This can be modified by the fourth parameter (flags), where I have set it to RDF_SPACEOK. Note that the flag RDF_C MUST always be present, but we can combine multiple flags with | or +. The fifth parameter (dadr) is the address of the variable to store the result in, I’ll use the name array I set up on line 1. The sixth parameter (prmp) is the address to the prompt to display to the user, which should be a FormSt packet.
Parameter 7 (CsrProc) tells CC3+ which cursor type it should use during the request, common options are RDC_None, RDC_Arrow, RDC_Pick and RDC_XH (crosshairs). And finally, parameter 8 (RcvProc) is the prodecure to call when the user has finished providing the data.

Line 10 is simply the call to ReqData which makes CC3+ go ask the user for the data using the settings in our request.

Line 15 checks if the request was successful or not. If the Result parameter is anything but X_OK, the user didn’t provide a valid value. In my example, I just end the command if that is the case, but do be aware that the user could have picked the default option (not applicable in our example) instead, resulting in a value of X_DFLT. If the user canceled, it will be X_CAN, and X_BAD will be sent if the user provided invalid data (text when a number was required for example).

More Details

The second parameter of RDATA is the main way of telling what type of data you want. How CC3+ proceeds with the request depends heavily on this value. For example, if you ask for a coordinate (RD_2DC), CC3+ will allow the user to provide it either by typing a coordinate pair on the command line, OR by clicking anywhere in the drawing window. A distance can likewise be typed in on the command line as a value, or the user can click two points on the screen, and the result will be the distance between these. If you ask for a Color (RD_Color), the user can either type in a color number, click on any color on the color bar, or right click to bring up the color dialog and pick it from there. If you want a selection (RD_Pick), CC3+ will let the user pick entities based on the current selection settings. The main point here being that you basically just tell CC3+ what you want, and it will ensure that the normal options for inputting that kind of value is available for the user, you don’t have to manually define these. What you do need to make usre of is that the variable from parameter 5 (dadr) is of the correct type to store the kind of data you requested.

Note that if you want the user to pick entities, there are two options, RD_Pick and RD_Pick1. RD_Pick obeys the users current select method, and to find the entities the user picked, you’ll have to walk the drawing list (see Developing Add-Ons for CC3+ – Part 2: Entity Basics ) and use the DLS_Std|DLS_Sel flags to get just the selected entities. If you use RD_Pick1 on the other hand, the user will always get prompted to select a single entity, and the reference to that will be stored in the variable just as with all other requests.

Check with _RDATA.H to see all the options available. Here you can also find the valid options for the other positions, such as the cursor types and flags.

You can download my example code.

Leave a Reply