Writing a Direct Manipulation Spy
Almost everyone has at least heard of or even used the development tool PM Spy. The equivalent of the Windows application Spy, it captures all of the messages sent to a window or queue and displays them in a nicely formatted listbox. Some time ago, while adding direct manipulation support to a rather complex application, I realized how difficult it is to actually do this; also, there were more than just a few questions on how to integrate with the Workplace Shell via "drag-n-drop".
"Wouldn't it be nice," I thought, "if there were a tool to help people with rendering mechanisms, etc.?" I concluded that it would be nice, and I finally got the gumption to write one.
Drop Me! is a tool that saves all of the information about the items being dragged over its window. When it receives notification that the drag operation has left the window, it displays the information in a container. All of the fields in the DRAGITEM structure are saved.
When you think about it, this utility is a trivial one. However, this article is being written to discuss the trials and tribulation of its authoring.
Right to the Heart of the Matter
The application is rather straight forward; it is a PM application which creates a "standard window" and within that a container window is created as a child of the client window. The container is initialized in detail view.
Let us consider the events that occur when direct manipulation occurs, from the target's perspective:
- The user initiates the drag operation
- The drag moves over the window
- The user either terminates the drag operation by
- ...releasing the mouse button to perform the operation
- ...pressing F1 for direct manipulation help
- ...pressing ESC to cancel the operation
- ...or the drag leaves the window
From a message standpoint, the application receives a DM_DRAGOVER, DM_DROP, DM_DRAGHELP, or DM_DRAGLEAVE message for steps 2, 3a, 3b, and 4, respectively. Since the client is covered by the container, however, we must instead rely on it to notify us of these events.
Fortunately, there are many notifications for direct manipulation provided by the container class. We are interested specifically in the CN_DRAGOVER and CN_DRAGLEAVE notifications; because we will not be accepting any drop operations, we do not care about the CN_DROP notification.
"Doc, this headache is killing me"
Originally, I had implemented the utility in the following manner: when the CN_DRAGOVER message was received, convert each DRAGITEM field to a string and store the resulting structures in a linked list (assuming the list was not already initialized); when the CN_DRAGLEAVE message was received, if the list was not empty, empty the container, add the contents of the linked list to the container, and empty the linked list to prime ourselves for the next drag operation.
This was much too complex. First, it requires that the Common/2 package (containing the linked list routines) be installed on the machine where it is being compiled. Second, why can we not simply insert the records into the container when we receive the CN_DRAGOVER message?
Hmm, good question.
My intent was not to "scare" the user by having this information pop up immediately when the mouse moved over the container. However, moving the CN_DRAGLEAVE processing to the end of the CN_DRAGOVER processing as a test revealed that the records, while added to the container, do not get displayed because the direct manipulation operation locks the screen until it is completed. Back to the drawing board.
The result is what is included as dropme.zip. The source no longer requires Common/2, which results in an executable size that has been halved. Also, the code is less complex, which means it is easier to understand.
For the Container Virgins
If you have never done any programming with the container control, I highly recommend you read the "Programming the Container Control" series (3 parts) in issues 3, 4, and [[Programming the Container Control - Part 3|5] of EDM/2. I will briefly describe the specifics of the interactions with the container here, however.
The container is created using WinCreateWindow() in the WM_CREATE processing and is resized in the WM_SIZE processing to insure that it completely covered the client. The initialization, which also occurs in WM_CREATE, involves allocating the FIELDINFO structures (via a CM_ALLOCDETAILFIELDINFO message), initializing them, and finally inserting them (via a CM_INSERTDETAILFIELDINFO message). Before exiting, the container is set to "detail view" mode (via a CM_SETCNRINFO message) specifying that the column headings should also be displayed.
When the container notifies us that a drag operation is occurring over our window (by sending us a WM_CONTROL message specifying CN_DRAGOVER as the notification code), we allocate enough records for each item being dragged (via a CM_ALLOCRECORD message) and then loop through the items; DrgQueryDragitem() is called for each and the contents of the DRAGITEM structure are converted to strings to be displayed in the container. Finally, the current contents are emptied (via a CM_REMOVERECORD message) and the new records are inserted (via a CM_INSERTRECORD message).
Now that we have our nifty utility, it would be nice if it had the ability to...
- ...copy one or more records to the clipboard.
- ...save the contents of the container to a file.
The first item should not be difficult and, using the article "Threads in PM Applications" from last month's issue of EDM/2, the second item should not require too much time to implement either. These exercises are left to the reader.
Here is the "Reader's Digest Condensed" version of a humorous incident that I had while writing this application. I would like to thank my good friend Peter Haggar (the original container developer) for taking time out of his busy schedule to give me a much-needed hand in debugging.
- "Hi Peter. This is Larry Salomon."
- "Hey Larry. How are you?"
- "Can't complain much. Yourself?"
- "Not too bad. What can I do for you?"
- "Well, I have the problem. It seems that when I try to insert some records into the container during a CN_DRAGLEAVE message, my application traps. I have checked everything in the insertion procedure and it looks fine. Is this some sort of critical section that I need to be aware of?"
[Tap tap tap] (He brings up the source code)
- "Well, it seems that we do check to see if a drag operation is in progress and call the appropriate function to get the HPS since the screen is locked."
[The rest of the "debugging over the phone" is deleted]
[The next morning]
- "Hi Peter; it's me again."
- "As I was driving home last night, I thought that because I'm not using the pszIcon field that the container might still be trying to access it and that is causing the trap. However, that is not the case. Here is everything that my application does. First, it allocates the FIELDINFO structures and inserts them into the container. I'm using CFA_STRING because I know the DRAGITEM will probably not be valid anymore. And then I..."
- "Are your field offsets pointing to a pointer to the string?"
- "Oh, shit..."
- "Yeah, we should've named the constant CFA_PSZ; it confuses everyone. By the way, isn't this [pitfall] in your book?"
- "Oh, shit. How embarrassing..."
See you next time.