DDE in PageMaker

The Windows version of PageMaker supports Microsoft's Dynamic Data Exchange protocol. Using the PageMaker scripting language, you can control PageMaker from another Windows application.

A major advantage of interapplication communication (IAC) is that it lets you integrate the functionality of multiple applications. For example, you could write an application that automates the production of a catalog. It could open a database application, extract any new product descriptions from the catalog database, and then open PageMaker, locate the previous descriptions in the catalog, and replace both the descriptions and the images.

About IAC using DDE

In Windows, an application initiates contact with PageMaker using DDE messages. In DDE parlance, PageMaker is the server and the application is the client. The DDE messages EXECUTE, REQUEST, and DATA establish contact between the applications and transmit the commands, queries, and responses. The application sends commands and parameters in EXECUTE messages. REQUEST messages carry queries, and PageMaker replies to a REQUEST using a DATA message.

You can communicate with PageMaker by sending DDE messages directly to PageMaker from any application that supports DDE commands. You can also create an application that calls the routines contained in the Windows 95 DDE Manager Library (DDEML). The examples in this chapter illustrate both methods of using DDE to communicate with PageMaker.

Using DDE messages

PageMaker recognizes four DDE messages:

 DDE Message Description
 WM_DDE_INITIATE Use this message to begin a conversation. PageMaker registers itself as "PageMaker." It responds to WM_DDE_INITIATE messages for "PageMaker" that use any topic name (including NULL).
 WM_DDE_EXECUTE Use this message to send PageMaker scripting commands. Results are processed as text strings.
 WM_DDE_REQUEST Use this message to send PageMaker scripting queries. Queries must use the CF_TEXT format. (For more information, see SDK Guide for Microsoft Windows and Windows NT.) Results are processed as text strings.
 WM_DDE_TERMINATE Use this message to end a conversation.

PageMaker sends two DDE messages in response to commands or queries:

 DDE Message Description
 WM_DDE_DATA PageMaker uses this message to transmit the result of the query to the application that issued the WM_DDE_REQUEST.
 WM_DDE_ACK PageMaker uses this message to acknowledge the receipt of a command.

Sending commands and queries to PageMaker

As described above, you use WM_DDE_EXECUTE to send commands and WM_DDE_REQUEST to send queries.

You should also follow these general guidelines when sending commands and queries to PageMaker:

Separate multiple commands or queries within the message by new line characters or by semicolons (;) For example:

new; close
select(5,6)

Send no more than one query per message. There can be only one text reply from a DDE query. If you send more than one query per event, only the result of the last query is returned in the reply.

Receiving replies from PageMaker

PageMaker uses WM_DDE_DATA to transmit the result of the query to the application that issued the WM_DDE_REQUEST. A query reply is a null-terminated character string. Items in a reply are separated by commas.

Example: Calling routines in DDE Manager Library

This example illustrates how to establish a callback routine, initiate a conversation, send a command and a query, and end a conversation with PageMaker by calling routines in the Windows 95 DDE Manager Library (DDEML).

The callback routine for DDEML

To use DDEML, you must first write a callback function. MyDdeCallback is registered on DdeInitialize and called during various DDEML functions. Remember to export this function in the .DEF file of your application.

static PFNCALLBACK lpCallback; // Procedure instance for callback function.

HDDEDATA CALLBACK MyDdeCallback(UINT type, UINT fmt, HCONV hconv, HSZ hsz1, HSZ hsz2, HDDEDATA hData, DWORD dwData1, DWORD dwData2)
{
switch (type) {
default:
// No need to handle any callback types.
return 0;
}
}

Initiating a DDE conversation

StartPMConv initiates a DDE conversation with PageMaker and returns the handle that you use to send commands or queries to PageMaker. Once you initiate the conversation, you can send multiple commands and queries to PageMaker.

HCONV StartPMConv(void)
{
DWORD dwInst = 0; // Instance identifier for DDEML.
HSZ hszService; // String handle for PageMaker service name.
HCONV hconv; // Handle to PageMaker DDE conversation.
lpCallback = // Make procedure instance
(PFNCALLBACK)MakeProcInstance( // for the callback function.
(FARPROC)MyDdeCallback, hInst);
DdeInitialize(&dwInst, // Initialize the DDEML.
lpCallback, APPCLASS_STANDARD | APPCMD_CLIENTONLY, 0);
hszService = // Make a string handle for the
DdeCreateStringHandle(dwInst, // service name.
"PageMaker", CP_WINANSI);
hconv = DdeConnect(dwInst, // Establish a DDE conversation.
hszService, 0, 0);
DdeFreeStringHandle(dwInst, // Free the memory associated with
hszService); // the service name.
return hconv;
}

Sending a command to PageMaker

The following routine sends a null terminated string command to PageMaker.

void SendPMCommand(HCONV hconv, LPSTR lpCmd)
{
DWORD dwResult; // DDE conversation status.
// Send the command string to PageMaker.
DdeClientTransaction(lpCmd, strlen(lpCmd) + 1, hconv, 0, CF_TEXT, XTYP_EXECUTE, 1000, &dwResult);
}

Sending a query to PageMaker

The following routine sends a query string to PageMaker and displays the results of the query in a Windows message box.

void SendPMQuery(HCONV hconv, LPSTR lpQuery)
{
HDDEDATA hReplyData;
HSZ hszQuery;
// Create a string handle for the query
hszQuery = DdeCreateStringHandle(dwInst, lpQuery, CP_WINANSI);

// Send the command line argument to PageMaker as a query.
hReplyData = DdeClientTransaction(0, 0, hconv, hszQuery, CF_TEXT, XTYP_REQUEST, 1000, &dwResult);

DdeFreeStringHandle(dwInst, hszQuery);

if (hReplyData) {
DWORD dwReplySize;
LPSTR lpReply;
lpReply = // Get a pointer to the
DdeAccessData(hReplyData, // reply handle.
&dwReplySize);
MessageBox(0, lpReply, // Show it in a message "PM Query Result", MB_OK); // dialog box.
DdeUnaccessData(hReplyData); // Done with the data.
DdeFreeDataHandle(hReplyData);
} else {
MessageBox(0, "No reply from
PageMaker", "Error", MB_OK);
}
}

Ending the conversation

When the conversation is complete, call EndPMConv to terminate the DDE conversation.

void EndPMConv(HCONV hconv)
{
DdeDisconnect(hconv); // End conversation.
DdeUninitialize(dwInst); // Allow DDEML to clean up.
FreeProcInstance((FARPROC)lpCallback); // Free the procedure
}// instance for the callback function.

Example: Sending DDE messages

You can also communicate with PageMaker by sending DDE messages directly to PageMaker from a stand-alone plug-in or from within another application. This concept is illustrated by the following Visual Basic program.

This Visual Basic program creates a simple utility that threads (joins) the text of two independent text blocks into one story and then replaces the second text block in its original position. The utility form consists of a button, which sends the commands and queries to select, join, and replace the text, and a simple text window, where user instructions are displayed and replies from PageMaker are sent.

To use this threading utility, you should have a publication open in PageMaker with at least two stories on the page.

Declarations

Here are the "(general)" declarations for the utility:

REM Threading utility courtesy David Butler
REM Subroutine to keep utility on top

Declare Sub SetWindowPos Lib "User" (ByVal hWnd As Integer, ByVal hWndInsertAfter As Integer, ByVal X As Integer, ByVal Y As Integer, ByVal cx As Integer, ByVal cy As Integer, ByVal wFlags As Integer)

Const HWND_TOPMOST = -1
Const HWND_NOTOPMOST = -2
Const SWP_NOACTIVATE = &H10
Const SWP_SHOWWINDOW = &H40

Subroutines

Here are the subroutines used by the utility:

Sub Form_Load ()

REM Make window stay on top of PageMaker

SetWindowPos hWnd, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE Or SWP_SHOWWINDOW
REM Prevent utility from timing out if PageMaker is not running
Text1.LinkTimeout = -1
Text1.LinkTopic = "PageMaker|DDE_LINK"
REM Put help message in text window
UpdateStatus
End Sub
Sub RunScriptCommand (PM_Cmd As String)

Text1.LinkMode = 2

REM Send either commands or query based on first 3 characters
REM You can group commands, but must send queries one by one
REM Use Execute for commands, Request for queries

If Left$(LCase$(PM_Cmd), 3) = "get" Then
Text1.LinkItem = PM_Cmd
Text1.LinkRequest
Else
Text1.LinkExecute PM_Cmd
End If

End Sub

Sub UpdateStatus ()

REM Define help text to appear in text window

Msg$ = "To thread two text blocks, select the first text block and send to back. "
Msg$ = Msg$ + "Then, select the second block and click Thread."

Text1.Text = Msg$

End Sub

Text field

The utility has one text field with the MultiLine property set to True. The LinkClose procedure contains the following code:

Sub Text1_LinkClose ()

REM Let PageMaker finish before utility continues
REM This procedure is important for more complex scripts

DoEvents

End Sub

Command button

The utility has one command button with a caption of Thread. The subroutine for the button follows. Be careful to follow the PageMaker syntax correctly (for example, inserting a space between commands and parameters).

The sample code below sends several commands at a time. If it becomes necessary to troubleshoot a problem, you may want to send one command at a time.

Sub Command1_Click ()

REM Define a paragraph (carriage return) character

Cr$ = Chr$(34) + Chr$(13) + Chr$(10) + Chr$(34)

REM Get coordinates of selected text block
REM Use coordinates later to place text back on page

RunScriptCommand ("getobjectloc topleft")

TLCoord$ = Text1.Text

RunScriptCommand ("getobjectloc bottomright")

BRCoord$ = Text1.Text

REM Highlight and cut text in second text block
REM Then select first text block

RunScriptCommand ("textedit;selectall;cut;select 1;")

REM Get bottom corner of first text block

RunScriptCommand ("getobjectloc bottomright")

BCd$ = Text1.Text

REM Get last character of first text block

RunScriptCommand ("textedit;textcursor +textblock;textselect -char;")

REM If last character is not a return, add one

RunScriptCommand ("getstorytext 0 0")

If Asc(Mid$(Text1.Text, 2)) <> 13 Then
Msg$ = "textcursor +textblock;textenter " & Cr$ & ";"
Else
Msg$ = "textcursor +textblock;"
End If

REM Paste text and reposition text blocks

Msg$ = Msg$ + "paste;select 1;resize bottomright " + BCd$ + ";"
Msg$ = Msg$ + "placenext;place " + TLCoord$ + ";"
Msg$ = Msg$ + "resize topleft " + TLCoord$ + ";"
Msg$ = Msg$ + "resize bottomright " + BRCoord$ + ";"

RunScriptCommand (Msg$)

REM Put help message back in text window

UpdateStatus

End Sub


Comments or suggestions? Contact Adobe Developer Support
Copyright © 1997 - 2001 Adobe Systems Incorporated. All rights reserved.
Legal notices and trademark attributions