Visual C++ DLL to create a User-Tool and a User-Interface


The function to create text entities containing single unit fractions text will be called SUFText (for Single Unit Fraction Text).

You'll use the same DLL created in Tutorial 1, DLL to Show, Hide, or Toggle Point Display. Most of the work related to SUFText creating a user-tool to work with Visual CADD is done by a dialog box. There are two reasons for this: (1) a dialog box is a window which can be used to process Visual CADD messages and (2) a dialog box will be used for the input of the fraction.

Therefore, you will start by setting up the necessary dialog box. Use the resource editor in Visual C++ to create the dialog with four controls: (1) an Edit Box for the numerator, (2) an Edit Box for the denominator, (3) a Button for &OK, and (4) a Button for &Cancel.

The caption of the dialog box will be "Single Unit Fraction."

When the edit boxes are created, they will have resource IDs called IDC_EDIT1 and IDC_EDIT2. To more easily identify these two edit boxes, change the resource IDs to IDC_NUMERATOR and IDC_DENOMINATOR using the General tab of the Properties dialog Properties in the menu.

The &OK button can be set as the default button by checking the Default Button property under the Style tab.

Once the dialog box has been physically created, you should open the ClassWizard (under the View menu) and create a new class. This dialog class will be called CSUFTextDlg and will be created from the MFC base class CDialog. If you've used the resource editor to previously create the dialog box, the Resource Dialog ID should already be filled in.

Create a member variable for the Edit Box for the Numerator called m_Numerator using the Add Variable button in ClassWizard. Do the same for the second Edit Box and call it m_Denominator. These should be integers and may optionally perform validation. For example, it is typical that the denominator is positive, which can be validated by setting a minimum value of 1 (one) in the Member Variables tab of the Class Wizard.

All of these names can be changed, but remember that you would have to change the affected code as well.

For help with creating and editing the dialog box and its controls, refer to your Visual C++ documentation.

If you used the MFC ClassWizard to create the CSUFTextDlg class, then the SUFTEXTDLG.CPP and SUFTEXTDLG.H are automatically created. We will begin by adding code to the SUFTEXTDLG.CPP

The example will use Visual CADD API routines from both VCMAIN32.DLL and VCTOOL32.DLL. Hence, we need to #include VCTYPE32.H, VCMAIN32.H, and VCTOOL32.H. The VCTYPE32.H is required for the definition of a Point2D, which is used in the other headers, and a few constants.

The #include statements at the beginning of SUFTEXTDLG.CPP should look like:


  //	standard system includes
  #include "STDAFX.H"

  //	VCadd includes
  #include "..\VCINCLUDE\VCTYPE32.H"
  #include "..\VCINCLUDE\VCMAIN32.H"
  #include "..\VCINCLUDE\VCTOOL32.H"

  //	project includes
  #include "MFCVCADD.H"
  #include "SUFTEXTDLG.H"

The user-tool will work by prompting the user for a placement point for the text. This point, of the type Point2D, will be declared in the VCVCADD.CPP file, so must be declared as external here. In addition, we will use our standard Visual CADD error codes with file scope, as follows:


  //	globals
  extern Point2D dpP0;	//	defined in VCVCADD.CPP for point placements

  //	locals
  static short iErr;		//	Visual CADD error code
  static short* pErr = &iErr;	//	ptr to Visual CADD error code

The ClassWizard generates a default constructor for the CSUFTextDlg class. In the constructor, you may optionally set default values for the fraction, for example ½. This is done by editing the m_Denominator and m_Numerator lines.


  CSUFTextDlg::CSUFTextDlg(CWnd* pParent /*=NULL*/)
  	: CDialog(CSUFTextDlg::IDD, pParent)
  {
  	//{{AFX_DATA_INIT(CSUFTextDlg)
  	m_Numerator = 1;
  	m_Denominator = 2;
  	//}}AFX_DATA_INIT
  }

The ClassWizard also creates the DoDataExchange and message map. The user-tool will need CSUFTextDlg to handle four Window's messages. You should use the ClassWizard to add message maps for WM_INITDIALOG, WM_DESTROY, WM_CHAR, and WM_LBUTTONDOWN.

We will start by adding code to OnInitDialog.

Whenever a dialog loads, Windows automatically generates a WM_INITDIALOG message before the dialog is displayed. The function OnInitDialog will process this message. Because the WM_INITDIALOG message occurs when the dialog is loaded, the OnInitDialog function is a good place to perform any initialization required by the dialog (hence, its name Init-Dialog).

In this example, the dialog will be used to create a user-tool and to process the Visual CADD messages. Processing Visual CADD messages requires that the program tell Visual CADD to share Visual CADD messages with it. The Visual CADD API routine VCSetAlertApp does this and requires two parameters besides the error code.

First, Visual CADD must know to which window to send messages. VCSetAlertApp tells Visual CADD the window by supplying a handle to the dialog's window, which is the m_hWnd member of the dialog. Note that this handle is a window handle, defined by Windows, and is different from both a placement handle and an entity handle in Visual CADD or the Visual CADD API.

Second, Visual CADD must know which messages your program wishes to share. There are many, but this example only needs two. One is a mouse-down message, which corresponds to a left-button click when placing an entity on a Visual CADD drawing. Visual CADD users know that points may be placed in a number of ways, including mouse-clicks, keyboard entry, tracking, and snaps. Whenever Visual CADD is placing a point, it treats all of these alternatives as the equivalent of a mouse-click. For example, if a point is being placed using tracking, the Pen Up (PU) which finally places the point is treated as though the user clicked the mouse at that point. Thus, the program needs only share the mouse-down message to share all point placement.

The second message needed by most user-tools is an abort message. When the Visual CADD user is placing a point, the ESCAPE key will abort the placement. Because this example will prompt the user to place a point, it should also share the abort message so it knows if the user aborted the user-tool.

The VCTYPE32.H header defines a number of codes for specifying messages. When your program needs several messages, their codes should be connected with the bitwise-or operator |, which causes a 'bitwise or' to be performed on the numeric codes.

The two codes needed are ALERT_APP_UTOOL_MOUSEDOWN | ALERT_APP_UTOOL_ABORT, for mouse-down and abort.

In the default OnInitDialog function started by the ClassWizard, you can add the VCSetAlertApp function:

  BOOL CSUFTextDlg::OnInitDialog()
  {
  	CDialog::OnInitDialog();

  	//	capture mousedown and abort messages to this dialog
  	VCSetAlertApp(pErr, (LONG)m_hWnd,
  		ALERT_APP_UTOOL_MOUSEDOWN |
  		ALERT_APP_UTOOL_ABORT);

As explained in Example of Parsing the Database and Modifying Entities, tools should support undo by using VCBeginOperation. So, add the following:

  	//	begin for undo
	VCBeginOperation(pErr);

Visual CADD is told that a user-tool is being created by using VCSetUserTool, which takes three parameters. These are the number of steps, the native command defined by the user-tool, and the first user prompt. This example only requires one point placement, so there will be only one step. The native command to be defined in the CMDEXT.DEF will be SUFText and the prompt will be "Pick Starting Point" (the same as Text Editor (TE)).

So, add the following:

  	//	user tool prompt
  	VCSetUserTool(1, "SUFText", "Pick Starting Point");

The first step is for the user-tool to prompt the user for the point placement. So, the dialog should be minimized to remove it from the screen. ShowWindow sets the show state of a window, and the constant SW_MINIMIZE will minimize the window. We will use the ShowWindow member function of the CWnd class, but we could also use the Win32 version which requires the window handle of the dialog as an additional parameter.

Then, we will use the VCGethWndFrame routine to get the window handle to Visual CADD. The Visual CADD window handle is used to set the input focus to Visual CADD, so it is ready to accept the point placement from the user. The Visual CADD window frame is not an object of a MFC class, so the only SetFocus available for use is the Win32 version (specified by the scope resolution operator ::) which takes the window handle as a parameter. And, that is all that is needed for initialization:

  	//	minimize the dialog and activate Visual CADD
  	ShowWindow(SW_MINIMIZE);
  	::SetFocus((HWND)VCGethWndFrame());

  	return TRUE;
  }

The return TRUE simply returns a success code from the initialization, which was added by the Wizard, along with some comments.

When a dialog is unloaded, a WM_DESTROY message is automatically created. The OnDestroy function processes this message. Because the SUFText is creating a user-tool, the dialog will be unloaded when the user-tool is done. So, the OnDestroy function is a good place to put code for clean-up.

The only thing needed in our dialog is to tell Visual CADD not to send any more messages, because the user-tool is done. This is done with VCClearAlertApp with the same window handle used for the VCSetAlertApp.

Now, we put VCBeginOperation in the OnInitDialog function, so it may seem natural to put the VCEndOperation in the OnDestroy function. However, because we do no know if the form is unloading due to a cancel (or abort) or due to successful completion, we will need to put the VCEndOperation elsewhere.

We can also use VCAppExit for Visual CADD to do its own clean-up. All that is needed is:

  void CSUFTextDlg::OnDestroy()
  {
  	CDialog::OnDestroy();

  	//	clear mousedown and abort messages
  	VCClearAlertApp(pErr, (LONG)m_hWnd);
  	VCAppExit(pErr);
  }

If the user aborts the user-tool while the Visual CADD interface has the input focus, Visual CADD will send an abort message. For example, if one tool is active when the user starts another tool (except snapping and tracking tools), then Visual CADD will abort the first tool. The user can also abort a tool by pressing ESCAPE. In either case, Visual CADD will send a WM_CHAR message with a key code of 27. (Note that the Visual CADD API Help file says the key code is 256. However, much experience indicates that Visual CADD sends a key code of 27.) User-tools should process the WM_CHAR message to check for user aborts. The OnChar function will do so. To be safe, the application should check for key codes 27 and 256.

In addition, your dialog can treat the ESCAPE key as an abort while the dialog box is open. The ASCII code for ESCAPE is 27. If you check for abort codes as described above, your application will already process the ESCAPE key.

Thus, if either one of these codes is encountered, the user-tool should cancel as if the user had clicked the Cancel button on the dialog. This is done by using the CWnd member function PostMessage to post a WM_COMMAND windows message with an IDCANCEL parameter, which is the same message generated if the user presses the Cancel button. The message is posted to the CSUFTextDlg window and an existing message handler built into the CDialog class will handle this message.

If any other character code is encountered, the default handler will process the character.


  void CSUFTextDlg::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
  {
  	//	check for Visual CADD abort codes
  	if ((nChar == 27) || (nChar == 256))
  	{
  		//	on an abort, post a cancel message
  		PostMessage(WM_COMMAND, IDCANCEL, 0L);
  	}
  	else
  	{
  		//	otherwise, default handling of character
  		CDialog::OnChar(nChar, nRepCnt, nFlags);
  	}
  }

After the user has been prompted by Visual CADD to place a point for the text, the dialog will wait for the WM_LBUTTONDOWN message, which indicates that the user has completed the point placement.

When the dialog gets the WM_LBUTTONDOWN message, it must get the location of the point from Visual CADD. The Visual CADD API routine VCGetUserToolLBDown does this, with the point being saved into the Point2D variable dpP0. Note that the CPoint passed to OnLButtonDown by Windows contains the window screen coordinates of the point and is not the same as the Point2D containing the Visual CADD real world coordinates.

  void CSUFTextDlg::OnLButtonDown(UINT nFlags, CPoint point)
  {
  	//	get mousedown point
  	VCGetUserToolLBDown(pErr, &dpP0);

Once the placement location is set, all that remains is for the form to get the fraction from the user. This is done by now restoring the dialog window and giving it the input focus. Recall that the dialog was already loaded when the user-tool was started, but was minimized while the user placed the point.

The CWnd member functions ShowWindow and SetWindowPosition will restore the window and place it at a convenient location on the screen.

  	//	show dialog box
  	ShowWindow(SW_NORMAL);

  	//	move with no size change or z-order
  	SetWindowPos( &wndTop, 128, 192, 1, 1,
  		SWP_NOSIZE | SWP_NOZORDER | SWP_SHOWWINDOW);

When the dialog is active, we want the numerator to be ready to accept input. The CWnd member functions GetDlgItem and SetFocus will get the numerator Edit Box and give it the input focus.

When the user first edits the numerator, we want the default value of "1" to be selected, so that if the user types a new number, it replaces the 1 without having to use the DELETE key. The selection is done by the CEdit member function SetSel. Because GetDlgItem returns a pointer to a CWnd object, it must be type cast to a CEdit pointer to access the SetSel function. Furthermore, the member selection operator, ->, takes precedence over the type cast, so the type cast must be forced with parentheses.

  	//	set focus to Numerator
  	GetDlgItem(IDC_NUMERATOR)->SetFocus();

  	//	select all characters in Numerator
  	((CEdit*)GetDlgItem(IDC_NUMERATOR))->SetSel(0, -1);

  	CDialog::OnLButtonDown(nFlags, point);
  }

That completes the code for the dialog. With the CSUFTextDlg dialog restored and having the focus, the user will enter the fraction and click either the OK or Cancel button. With the point placed and the fraction entered, all that remains is to create single unit fraction text and allow the use to edit it.

Now, we are ready to add the SUFText function to VCVCADD.CPP, which will be called by Visual CADD, will call the dialog, and will then create the text entity.

In VCVCADD.CPP, we need to add a couple of #include and declarations statements. Code to be added in this file also uses a Visual CADD API routine from VCTOOL32.DLL, so we need to add VCTOOL32.H in the #include statements. Our functions will also call the dialog, so we need the header for that dialog. So, we add the following:

  //	VCadd includes
  #include "..\VCINCLUDE\VCTOOL32.H"

  //	project includes
  #include "SUFTEXTDLG.H"

So far, our application object theApp does not have a window - up until now we did not need one. However, we are about to use the dialog, which is a window. An easy way to run this dialog is to temporarily make the dialog window the main application window. To do this, we will need to access the application object, theApp, which was declared in MFCVCADD.CPP (at the very end of that file). We do this with an external declaration. We also need the point from the point placement. It is declared here and also used by the dialog, where it was declared external.

  //	globals
  extern CMFCVCaddApp theApp;	  //	App object defined in MFCVCADD.CPP
  Point2D dpP0;		 	//	passed for point placements

The first step will create a dialog object of the CSUFTextDlg class and run it. This is done by declaring an object, called dlg, of the CSUFTextDlg class. A dialog is itself a window, so we make the dialog window the application's main window by assigning it to the CWinApp member variable m_pMainWnd. It can now be run modally with the CDialog member function DoModal.

  //	Prompt user to place and edit Single Unit Fraction Text
  HRESULT WINAPI SUFText(void)
  {
  	ENTITYHANDLE hEnt;
  	char strSUF[128];

  	//	create the dialog object and make it the main
  	CSUFTextDlg dlg;
  	theApp.m_pMainWnd = &dlg;

  	//	run the dialog modal
  	if (dlg.DoModal() == IDOK)
  	{

The ENTITYHANDLE hEnt and char strSUF will be used later, and discussed at that time. The returned value from the DoModal is checked against IDOK to see if the user pressed the OK button.

If the user pressed OK, the next step is to create the text string with the special codes for single unit fractions, much like the Example of Parsing the Database and Modifying Entities. The difference here is that we know the fraction will have m_Numerator in the numerator and m_Denominator in the denominator.

The sprintf function will write formatted data to a string. The format string contains the special codes for single unit fractions and will insert the numerator and denominator values at the locations of each %d.

  		//	format and set SUF
  		sprintf( strSUF,"\x01\x4E%d\x01/\x01\x44%d\x01\x01",
  			dlg.m_Numerator, dlg.m_Denominator);
  		VCSetTextString(pErr, strSUF);

Having set the text string, a new text entity can be added at the placement point. The VCAddTextEntity routine will add the text entity.

Entities may be added to drawings, hatches and fills, or symbols. The first parameter after the error code indicates where the entity is being added. The NONDEFENTITY constant is defined in the VCTYPE32.H header as -1 (negative-one), which means the entity is being added to the drawing.

The placement point is given by the last parameter, which is the Point2D variable dpP0 retrieved by the OnLButtonDown message handler.

  		//	add text entity
  		VCAddTextEntity(pErr, NONDEFENTITY, dpP0);

After adding the text entity, the user-tool will allow the user to edit it using the same tool as Text Editor (TE). To do this, the entity must be selected. Before selecting it, we must deselect all other entities. The VCClearSelection routine does this.

  		//	clear any previous selections
  		VCClearSelection();

To select the added text entity, we will make it current, then select it. To make it current, we must find it in the database. But, because new entities are always added to the end of the database, we know that the new entity is the last entity. Using VCLastEntity, we can get the entity handle of the new text entity. (Refer to the previous discussion of VCLastEntity for errata to the Visual CADD API Help file.)

Using this entity handle, VCSetCurrentEntity will make it the current entity. Finally, the VCSetCurrentSelected will select the current entity.

  		//	select new text entity
  		VCLastEntity(pErr, &hEnt);
  		VCSetCurrentEntity(pErr, hEnt);
  		VCSetCurrentSelected(pErr);

Once selected, VCEdit will start the Edit (ED) tool, which for text entities is the Text Editor (TE) tool.

  		//	let user edit text
  		VCEdit();

When the user is done editing the text with the single unit fraction, the tool has successfully completed. We can now redraw the drawing and end the undo level.

  		//	redraw and end undo
  		VCInvalidateRect();
  		VCEndOperation(pErr);
  	}

That ends the processing of an IDOK return from the dialog. Now, for the alternative, which is IDCANCEL. All we need to do is abort the undo level.

  	else
  	{
  		//	abort undo on cancel
  		VCInvalidateRect();
  		VCAbortOperation(pErr);	}
  	}
	return(NOERROR);
  }

The function declaration also needs to be added to VCVCADD.H:

HRESULT WINAPI SUFText(void);

Lastly, for the function to be available to external calling applications, such as Visual CADD, the function name needs to be exported by adding it to the EXPORTS section MFCVCADD.DEF:

SUFText

To run your DLL, include the following in your CMDEXT.DEF (recommended) or Assign Script (AS), where the first line is omitted for scripts.

	SUFText,SUT, ,Single Unit Fraction Text, Single Unit Fraction Text,
		DllName;C:\VC++4\MFCVCADD\RELEASE\MFCVCADD.DLL;
		DllFunName;SUFText;DllRun;

Note that it is not mandatory that the above be placed in the CMDEXT.DEF. However, when a user-tool starts, the VCSetUserTool specifies a native command (in this case SUFText) which is looked-up in the CMDEXT.DEF. If found, the tool description (Single Unit Fraction Text) is displayed in the Visual CADD status bar when the user-tool runs.

From Visual CADD, running SUFText (SUT) will prompt the user to place text, just as Text Line (TL) or Text Editor (TE). After placement, a dialog box will accept the fraction to be used in the single unit fraction. Then, the user is allowed to edit the text using Text Editor (TE). The user can add text and, if necessary, move or copy additional fractions by using cut-and-paste (CTRL+C, CTRL+X, and CTRL+V).

Programming Index | VB and VC++ Setup | Example 4 Overview | Programming Links
  t mandatory that the above be placed in the CMDEXT.DEF. However, when a user-tool starts, the VCSetUserTool specifies a native command (in this case SUFText) which is looked-up in the CMDEXT.DEF. If found, the tool description (Single Unit Fraction Text) is displayed in the Visual CADD status bar when the user-tool runs.

From Visual CADD, running SUFText (SUT) will prompt the user to place text, just as Text Line (TL) or Text Editor (TE). After placement, a dialog box will accept the fraction to be used in the single unit fraction. Then, the user is allowed to edit the text using Text Editor (TE). The user can add text and, if necessary, move or copy additional fractions by using cut-and-paste (CTRL+C, CTRL+X, and CTRL+V).

Programming Index | VB and VC++ Setup | Example 4 Overview | Programming Links