Implementing Custom Annotations
(Document and Medical Imaging toolkits)
Functionality added as of Version 14 makes it possible for you to create custom annotations that look and behave in almost any way imaginable. Custom annotations can be made to respond to almost any key sequence: the annotation DLL sends the WM_LTANNEVENT messages LTANNEVENT_LBUTTONDOWN, LTANNEVENT-MOUSEMOVE, and the LTANNEVENT_LBUTTONUP; and the WM_LTANNEVENT IParam points to an ANNMOUSEPOS structure that contains detailed information about the mouse’s position and status.
The following functions are used when creating custom annotations:
In addition, several messages have been added to the WM_LTANNEVENT event in order to implement the functionality for creating custom annotations.
There are 5 steps involved in creating a custom annotation:
1. |
Break the custom annotation into components. |
2. |
Decide on the location and behavior of the annotation object handles. |
3. |
Define the functions for creating the annotation. |
4. |
Define the functions for the user handles. |
5. |
Define any Miscellaneous functions that may be necessary. |
For more information refer to the WM_LTANNEVENT event and ANNMOUSEPOS structure documentation.
In the LEAD toolkit, there is no L_AnnDefine capability for moving and resizing a line simultaneously. The following example creates a custom object that demonstrates this capability. More specifically, this custom object creates a rectangle that has a line in the middle of it, and that line continues to stay in the middle whenever you resize and/or rotate the rectangle as shown in the figure below. For complete sample code, refer to Example1 in your C:\Program Files\LEAD Technologies, Inc\LEADTOOLS\Examples\DLL\ANNOTATE directory.
This object being created has 4 nodes. The line bisecting the rectangle has a yellow node at the top end and a gray node at the bottom end. The two sides have a node in the middle: a black node on the left and a red node on the right.
When the object is selected, you can click and drag the object to another location by clicking on a point that is not a node.
If you click on one of the two side nodes, you can adjust the width of the rectangle, and as the rectangle is redrawn, the line in the middle adjusts so it stays in the middle.
If you click on the top or bottom node, the opposite node becomes an anchor point and you can simultaneously adjust the height and rotate the rectangle as much as 90 degrees about the anchor point in either the clockwise or counterclockwise direction. The line in the middle continuously adjusts so it remains parallel to the two sides, anchored to the top and bottom, and bisects the rectangle.
To create this object, perform the following steps:
1. |
Break the custom annotation into components. At this point, it is easiest to make a copy of example1.h and example1.c, and replace each occurrence of "example1" with the name of your custom annotation object. |
|
(The custom annotation object Example1 consists of a rectangle and a square. We will create the custom object by grouping together a line and a rectangle.) |
2. |
Decide on the location and behavior of the annotation object handles (Example1 has four handles—one on each side, one on the top, and one on the bottom). |
Create defines for the handles, assigning each a unique ID.
(The defines for Example1 are:
#define HANDLE_ID_HORIZONTAL_TOP 100
#define HANDLE_ID_HORIZONTAL_BOTTOM 101
#define HANDLE_ID_VERTICAL_LEFT 102
#define HANDLE_ID_VERTICAL_RIGHT 103
)
Decide on their behavior.
(The behavior for the handles for Example1 are:
HANDLE_ID_VERTICAL_LEFT --horizontal resize
HANDLE_ID_VERTICAL_RIGHT --horizontal resize
HANDLE_ID_HORIZONTAL_TOP -- rotate around ANDLE_ID_HORIZONTAL_BOTTOM
HANDLE_ID_HORIZONTAL_BOTTOM -- vertical resize
)
3. |
Define the functions for creating the annotation. Create functions are called (via the WM_LBUTTONDOWN message) when creating the custom annotation using automation and the annotation menu. |
Using L_AnnDefine functions, create the object(s) that will be displayed when drawing your annotation. An outline of the object(s) will be drawn when moving the mouse during object creation.
Create each of the component annotation objects using L_AnnCreateItem(). It is useful to create a structure (EXAMPLE1) and add this to the LPCHILDDATA structure. The EXAMPLE1 structure contains fields for everything required to create the custom annotation.
Then call L_AnnDefine(ANNDEFINE_BEGINSET) to begin definition of the object.
(For illustration of this in Example1, refer to the code beginning with:
L_VOID Example1_LButtonDown(HWND hWnd, LPCHILDDATA pData);
)
Call L_AnnDefine(ANNDEFINE_APPEND) for each of the component objects.
(For illustration of this in Example1, refer to the code beginning with:
L_VOID Example1_MouseMove(LPCHILDDATA pData);
The Example1 object has a vertical line that is moved and resized as the rectangle grows. There is no L_AnnDefine state to do this, so it is necessary to process the LTANNEVENT_HIGHLIGHT message. This message allows you to customize the hilighting behavior).
Finish defining all of the component objects (In Example1, the components are a line and a rectangle). For each of the components, set a tag that identifies component as part of a single custom object, and also identifies the part of the custom object. This is accomplished by calling AnnSetID().
Hide the default handles for the component objects using HideDefaultHandles().
Select the component objects using L_AnnSetSelected, give them their default automation values using L_AnnSetAutoDefaults, and finally group them together using L_AnnGroup. Grouping the component objects places them all in a new annotation container, which is part of the root annotation container.
Finally, add user handles to the custom object.
here are several ways to add user handles. The handles can all be added to one of the component objects, or the handles can be added to one or more of the component objects. The best choice depends on the particular custom object. (Look at the function Example1_AddUserHandles to get an idea how to add user handles.)
(For illustration of this in Example1, refer to the code beginning with:
L_VOID Example1_LButtonUp(LPCHILDDATA pData, L_UINT uTool);
)
In some cases, it may be necessary to destroy and recreate an object. ( Vertical lines normally exist as independent objects and do not maintain their orientation and length with reference to other objects. These behaviors are required for the vertical line in Example1 and so the vertical line behavior must be customized. To create the illusion that the vertical line is moving you destroy the "dummy" line and recreate it where you want it—in the center of the rectangle.)
4. |
Define functions for the user handles. ** Update Handle functions are called (via the LTANNEVENT_LBUTTONDOWN message) when the mouse drags a user handle. |
In Example1 there are three functions, and these completely define the behavior of each user handle. Each of these functions does the following:
1. |
Calls AnnGetNeighborObjects--The user handle can be on any of the component annotation objects. AnnGetNeighborObjects gets an array of ALL the corresponding objects that make up the custom annotation. |
|
2. |
Calls AnnSortNeighborObjects--This sorts the component objects according to the component number. Recall that when you create the component objects, AnnSetID(HANNOBJECT hObject, L_INT nMainID, L_INT nPartID) was called for each component. |
|
|
The first component has a nPartID of 0, the next has nPartID of 1, and so on. After sorting |
|
|
the objects, pData->AnnObjectNeighbors[n] now contains the nth component of the custom object. |
|
3. |
Does a switch statement on (pData->nUserHandleID) to determine which user handle is being dragged, and uses the corresponding L_AnnDefine and L_AnnDefine2 statements to accomplish the desired behavior. |
|
4. |
Restricts the mouse cursor. This can easily be done in two ways: |
|
|
i) |
Calling L_AnnRestrictCursor(pData->hContainer, &rcClip, NULL, NULL, FALSE); (See the function BoxSide custom object for an example.) |
|
Or |
|
|
ii) |
Setting pMousePos->pt (in the pANNMOUSEPOS structure) to the adjusted point. Set pMousePos->fUpdatePos = TRUE. The function L_AnnAdjustPoint can be helpful, especially when working with rotated objects. For details, see the documentation for WM_LTANNEVENT. |
(For illustration of the first needed function in Example1, refer to the code beginning with:
L_VOID Example1_Handle_LButtonDown(LPCHILDDATA pData, pANNMOUSEPOS pMousePos);
If dragging handles HANDLE_ID_VERTICAL_LEFT or HANDLE_ID_VERTICAL_RIGHT, we want to resize the rectangle (L_AnnDefine(ANNDEFINE_BEGINRESIZE) and move the line (L_AnnDefine(ANNDEFINE_MOVE).
If dragging handle HANDLE_ID_HORIZONTAL_BOTTOM, we want to again resize the rectangle (L_AnnDefine(ANNDEFINE_BEGINRESIZE)), but with the line instead of moving it, we now want to resize it. (L_AnnDefine(ANNDEFINE_BEGINRESIZE)).
If dragging handle HANDLE_ID_HORIZONTAL_TOP, we want to rotate the rectangle around HANDLE_ID_HORIZONTAL_BOTTOM. This is accomplished by setting HANDLE_ID_HORIZONTAL_BOTTOM to be the anchor point, and doing a L_AnnDefine(ANNDEFINE_BEGINROTATE). This is done for both the line and the rectangle.
)
(For illustration of the second needed function in Example1, refer to the code beginning with:
L_VOID Example1_Handle_MouseMove(LPCHILDDATA pData, pANNMOUSEPOS pMousePos);
In this function simply update the component objects. Call L_AnnDefine(ANNDEFINE_APPEND) for each object. If you have decided to restrict the mouse cursor using L_AnnAdjustPoint(), you must call in this function.
)
(For illustration of the third needed function in Example1, refer to the code beginning with:
L_VOID Example1_Handle_LButtonUp(LPCHILDDATA pData, pANNMOUSEPOS pMousePos);
In this function, call L_AnnDefine(ANNDEFINE_END) for each component object.
Also, update the location of the user handles. The user handles move with the custom object when the entire object is moved. However, when you modify components of the entire object (by using L_AnnDefine) the handles do not move automatically. In this case, it is necessary to move the handles. The easiest way to do this is to call the utility function MoveHandle().
)
5. |
Define any Miscellaneous functions that may be necessary. The example custom annotation object needs two miscellaneous functions: one that creates and sets the location of all the user handles, and another that is called whenever any component object is about to be highlighted. |
(For illustration of creating user handles in Example1, refer to the code beginning with:
L_VOID Example1_AddUserHandles(HANNOBJECT hObject, pANNPOINT pAnnPoints);
This function creates the user handles for the Example1 custom object. It is also used in the Example1_Handle_LButtonUp to set the location of all the user handles. Another way to accomplish this is to use the MoveHandle() function instead in the Example1_Handle_LButtonUp function.
)
(For illustration of creating custom highlighting in Example1, refer to the code beginning with:
L_VOID Example1_Hilight(LPCHILDDATA pData, pANNHILIGHT pAnnHilight);
This function is called whenever any component object is about to be hilighted. Objects are hilighted in response to the L_AnnDefine and L_AnnDefine2 functions. The calls to hilight an object come in pairs--the first call removes the previous hilight, and the next call draws the new hilight. You can tell which type of hilight this is by checking the pAnnHilight->bRemoveHilight flag. If TRUE, the hilight is the first of the pair--it is removing a previous hilight.
This function is useful for creating custom hilighting. In the LEAD toolkit, there is no L_AnnDefine for moving and resizing a line simultaneously. By using this function, (which is called as a result of processing the WM_LTANNEVENT when lParam is LTANNEVENT_HIGHLIGHT), the behavior of the line hilight is customized by changing the pAnnHilight->ppts array.
)
See Also
Automated User Interface for Annotations
Implementing an Automated Annotation Program