|
|
1.1.1.2 ! root 1: ! 2: /******************************************************************************\ ! 3: * This is a part of the Microsoft Source Code Samples. ! 4: * Copyright (C) 1993 Microsoft Corporation. ! 5: * All rights reserved. ! 6: * This source code is only intended as a supplement to ! 7: * Microsoft Development Tools and/or WinHelp documentation. ! 8: * See these sources for detailed information regarding the ! 9: * Microsoft samples programs. ! 10: \******************************************************************************/ ! 11: 1.1 root 12: /*****************************************************************************\ 13: * 14: * DDEMO.C 15: * 16: * This file implements a simple DDEML sample application that demonstrates 17: * some of the ways the DDEML APIs can be used. 18: * 19: * Each instance of this application becomes both a DDE client and a DDE 20: * server with any other instances of this application that are found. 21: * 22: * Since it assumes it is talking to itself, this program takes some liberties 23: * to simplify things. For instance, this application does not support the 24: * standard SysTopic topic and does not use any standard formats. 25: * 26: * The basic concepts this application will show you are: 27: * 28: * How to use lists of conversations properly 29: * How to handle links 30: * How to handle simple asynchronous transactions 31: * How to use your own custom formats 32: * 33: \*****************************************************************************/ 34: #include <windows.h> 35: #include <ddeml.h> 36: #include <stdlib.h> 37: #include <string.h> 38: 39: HDDEDATA CALLBACK DdeCallback(WORD wType, WORD wFmt, HCONV hConv, HSZ hszTopic, 40: HSZ hszItem, HDDEDATA hData, DWORD lData1, DWORD lData2); 41: VOID PaintDemo(HWND hwnd); 42: LONG APIENTRY MainWndProc(HWND hwnd, UINT message, WPARAM wParam, 43: LONG lParam); 44: 45: /* 46: * Define this value to limit how fast data changes. If we just let data 47: * change as fast a possible, we might bog down the system with DDE 48: * messages. 49: */ 50: #define BASE_TIMEOUT 100 51: 52: BOOL fActive; // indicates data is changing 53: DWORD idInst = 0; // our DDEML instance object 54: HANDLE hInst; // our instance/module handle 55: HCONVLIST hConvList = 0; // the list of all convs we have open 56: HSZ hszAppName = 0; // the generic hsz for everything 57: HWND hwndMain; // our main window handle 58: TCHAR szT[20]; // static buffer for painting 59: TCHAR szTitle[] = "DDEmo"; // the generic string for everything 60: UINT OurFormat; // our custom registered format 61: int InCount = 0; // static buffer to hold incomming data 62: int cConvs = 0; // number of active conversations 63: int count = 0; // our data 64: int cyText, cxText, cyTitle; // sizes for painting 65: 66: int WINAPI WinMain( 1.1.1.2 ! root 67: HINSTANCE hInstance, ! 68: HINSTANCE hPrevInstance, 1.1 root 69: LPSTR lpCmdLine, 70: INT nCmdShow) 71: { 72: MSG msg; 73: WNDCLASS wc; 74: TEXTMETRIC metrics; 75: HDC hdc; 76: 77: wc.style = 0; 78: wc.lpfnWndProc = MainWndProc; 79: wc.cbClsExtra = 0; 80: wc.cbWndExtra = 0; 81: wc.hInstance = hInstance; 82: wc.hIcon = NULL; 83: wc.hCursor = LoadCursor(NULL, IDC_ARROW); 84: wc.hbrBackground = NULL; 85: wc.lpszMenuName = NULL; 86: wc.lpszClassName = szTitle; 87: 88: if (!RegisterClass(&wc)) 89: return(FALSE); 90: 91: /* 92: * Here we tell DDEML what we will be doing. 93: * 94: * 1) We let it know our callback proc address - MakeProcInstance 95: * is called just to be more portable. 96: * 2) Filter-inits - don't accept any WM_DDE_INITIATE messages for 97: * anything but our registered service name. 98: * 3) Don't bother to notify us of confirmed connections 99: * 4) Don't allow connections with ourselves. 100: * 5) Don't bother us with XTYP_POKE transactions. 101: */ 102: if (DdeInitialize(&idInst, 103: (PFNCALLBACK)MakeProcInstance((FARPROC)DdeCallback, hInstance), 104: APPCMD_FILTERINITS | 105: CBF_SKIP_CONNECT_CONFIRMS | 106: CBF_FAIL_SELFCONNECTIONS | 107: CBF_FAIL_POKES, 108: 0)) 109: return(FALSE); 110: 111: hInst = hInstance; 112: hwndMain = CreateWindow( 113: szTitle, 114: szTitle, 115: WS_CAPTION | WS_BORDER | WS_SYSMENU, 116: CW_USEDEFAULT, 117: CW_USEDEFAULT, 118: 0, 119: 0, 120: NULL, 121: NULL, 122: hInstance, 123: NULL 124: ); 125: 126: if (!hwndMain) { 127: DdeUninitialize(idInst); 128: return(FALSE); 129: } 130: 131: hdc = GetDC(hwndMain); 132: GetTextMetrics(hdc, &metrics); 133: cyText = metrics.tmHeight + metrics.tmExternalLeading; 134: cxText = metrics.tmMaxCharWidth * 8; 135: cyTitle = GetSystemMetrics(SM_CYCAPTION); 136: ReleaseDC(hwndMain, hdc); 137: 138: SetWindowPos(hwndMain, 0, 0, 0, cxText, cyText + cyTitle, 139: SWP_NOMOVE | SWP_NOZORDER | SWP_NOREDRAW | SWP_NOACTIVATE); 140: ShowWindow(hwndMain, nCmdShow); 141: UpdateWindow(hwndMain); 142: 143: /* 144: * Initialize all our string handles for lookups later 145: */ 146: hszAppName = DdeCreateStringHandle(idInst, szTitle, 0); 147: /* 148: * Register our formats 149: */ 150: OurFormat = RegisterClipboardFormat(szTitle); 151: /* 152: * Connect to any other instances of ourselves that may already be 153: * running. 154: */ 155: hConvList = DdeConnectList(idInst, hszAppName, hszAppName, hConvList, NULL); 156: /* 157: * Register our service - 158: * This will cause DDEML to notify DDEML clients about the existance 159: * of a new DDE service. 160: */ 161: DdeNameService(idInst, hszAppName, 0, DNS_REGISTER); 162: 163: while (GetMessage(&msg, 0, 0, 0)) { 164: TranslateMessage(&msg); 165: DispatchMessage(&msg); 166: } 167: 168: DestroyWindow(hwndMain); 169: UnregisterClass(szTitle, hInstance); 170: return(FALSE); 171: } 172: 173: 174: /* 175: * BroadcastTransaction 176: * 177: * Does the specified transaction on all conversations in hConvList 178: */ 179: VOID BroadcastTransaction( 180: PBYTE pSrc, 181: DWORD cbData, 182: UINT fmt, 183: UINT xtyp) 184: { 185: HCONV hConv; 186: DWORD dwResult; 187: int cConvsOrg; 188: 189: cConvsOrg = cConvs; 190: cConvs = 0; 191: if (hConvList) { 192: /* 193: * Enumerate all the conversations within this list - note that 194: * DDEML will only return active conversations. Inactive conversations 195: * are automatically removed. 196: */ 1.1.1.2 ! root 197: hConv = DdeQueryNextServer(hConvList, 0); 1.1 root 198: while (hConv) { 199: /* 200: * Count the active conversations while we're at it. 201: */ 202: cConvs++; 203: /* 204: * Spawn an asynchronous transaction - this was chosen because 205: * we have not particular action if an error ocurrs so we just 206: * don't care too much about the results - this technique will 207: * NOT do for XTYP_REQUEST transactions though. 208: */ 209: if (DdeClientTransaction(pSrc, cbData, hConv, hszAppName, fmt, 210: xtyp, TIMEOUT_ASYNC, &dwResult)) { 211: /* 212: * We immediately abandon the transaction so we don't get 213: * a bothersome XTYP_XACT_COMPLETE callback which we don't 214: * care about. 215: */ 216: DdeAbandonTransaction(idInst, hConv, dwResult); 217: } 218: 219: hConv = DdeQueryNextServer(hConvList, hConv); 220: } 221: } 222: if (cConvs != cConvsOrg) { 223: /* 224: * Oh, the number of active conversations has changed. Time to 225: * repaint! 226: */ 227: InvalidateRect(hwndMain, NULL, TRUE); 228: } 229: } 230: 231: 232: /* 233: * MyProcessKey 234: * 235: * We demonstrate the robustness of NT here by forcing a GP anytime the 236: * 'B' key is pressed while this window has the focus. NT should properly 237: * fake termination to all other apps connected to us. 238: */ 239: VOID MyProcessKey( 240: TCHAR tchCode, 241: LONG lKeyData) 242: { 243: switch (tchCode) { 244: case TEXT('B'): 245: case TEXT('b'): 246: *((PBYTE)(-1)) = 0; // Cause GP fault! 247: break; 248: } 249: } 250: 251: 252: 253: LONG APIENTRY MainWndProc( 254: HWND hwnd, 255: UINT message, 256: WPARAM wParam, 257: LONG lParam) 258: { 259: RECT rc; 260: 261: switch (message) { 262: case WM_CREATE: 263: /* 264: * initially we are inactive - this reduces some of the message 265: * traffic while we are initializing - but we could start active fine. 266: */ 267: fActive = FALSE; 268: break; 269: 270: case WM_RBUTTONDOWN: 271: if (GetKeyState(VK_CONTROL) & 0x8000) { 272: /* 273: * A CTRL R_BUTTON click will cause ALL instances of this app 274: * to become inactive. 275: */ 276: BroadcastTransaction("PAUSE", 6, 0, XTYP_EXECUTE); 277: MessageBeep(0); 278: } 279: /* 280: * A R_BUTTON click makes us inactive. Repaint to show state change. 281: * We do a synchronous update in case there is too much DDE message 282: * activity to allow the WM_PAINT messages through. Remember DDE 283: * messages have priority over others! 284: */ 285: KillTimer(hwndMain, 1); 286: fActive = FALSE; 287: InvalidateRect(hwnd, NULL, TRUE); 288: UpdateWindow(hwnd); 289: break; 290: 291: case WM_LBUTTONDOWN: 292: if (GetKeyState(VK_CONTROL) & 0x8000) { 293: /* 294: * A CTRL L_BUTTON click will cause ALL instances of this app 295: * to become active. 296: */ 297: BroadcastTransaction("RESUME", 7, 0, XTYP_EXECUTE); 298: MessageBeep(0); 299: } 300: /* 301: * An L_BUTTON click makes us active. Repaint to show state change. 302: */ 303: SetTimer(hwndMain, 1, BASE_TIMEOUT + (rand() & 0xff), NULL); 304: fActive = TRUE; 305: InvalidateRect(hwnd, NULL, TRUE); 306: UpdateWindow(hwnd); 307: break; 308: 309: case WM_CHAR: 310: MyProcessKey((TCHAR)wParam, lParam); 311: break; 312: 313: case WM_TIMER: 314: /* 315: * We use timers for simplicity. On Win3.1 we could run out of 316: * timers easily but we don't have this worry on NT. 317: * 318: * Each tick, we increment our data and call DdePostAdvise() to 319: * update any links there may be on this data. DDEML makes link 320: * updates on specific items quite easy. 321: */ 322: count++; 323: DdePostAdvise(idInst, hszAppName, hszAppName); 324: /* 325: * Invalidate the part of ourselves that shows our data and 326: * synchronously update it in case DDE message activity is blocking 327: * paints. 328: */ 329: SetRect(&rc, 0, 0, cxText, cyText); 330: InvalidateRect(hwndMain, &rc, TRUE); 331: UpdateWindow(hwndMain); 332: break; 333: 334: case WM_PAINT: 335: PaintDemo(hwnd); 336: break; 337: 338: case WM_CLOSE: 339: KillTimer(hwnd, 1); 340: /* 341: * We do DDE cleanup here. It is best to do DDE cleanup while 342: * still in the message loop to allow DDEML to recieve messages 343: * while shutting down. 344: */ 345: DdeDisconnectList(hConvList); 346: DdeNameService(idInst, 0, 0, DNS_UNREGISTER); 347: DdeFreeStringHandle(idInst, hszAppName); 348: DdeUninitialize(idInst); 349: PostQuitMessage(0); 350: break; 351: 352: default: 353: return (DefWindowProc(hwnd, message, wParam, lParam)); 354: } 355: return(0); 356: } 357: 358: 359: VOID PaintDemo( 360: HWND hwnd) 361: { 362: PAINTSTRUCT ps; 363: RECT rc; 364: HCONV hConv; 365: CONVINFO ci; 366: int cConvsOrg = cConvs; 367: 368: BeginPaint(hwnd, &ps); 369: /* 370: * Draw our data on top - Black for active, Grey for inactive. 371: */ 372: SetRect(&rc, 0, 0, cxText, cyText); 373: SetBkMode(ps.hdc, TRANSPARENT); 374: SetTextColor(ps.hdc, 0x00FFFFFF); // white text 375: FillRect(ps.hdc, &rc, GetStockObject(fActive ? BLACK_BRUSH : GRAY_BRUSH)); 376: DrawText(ps.hdc, itoa(count, szT, 10), -1, &rc, DT_CENTER | DT_VCENTER); 377: 378: /* 379: * Now draw the most recently recieved data from each server we are 380: * connected to. 381: */ 382: if (hConvList) { 383: OffsetRect(&rc, 0, cyText); 384: SetTextColor(ps.hdc, 0); // draw black text 385: cConvs = 0; 1.1.1.2 ! root 386: hConv = DdeQueryNextServer(hConvList, 0); 1.1 root 387: while (hConv) { 388: cConvs++; 389: /* 390: * count how many conversations are active while we're at it. 391: */ 392: ci.cb = sizeof(CONVINFO); 393: DdeQueryConvInfo(hConv, QID_SYNC, &ci); 394: FillRect(ps.hdc, &rc, GetStockObject(WHITE_BRUSH)); // white bkgnd 395: DrawText(ps.hdc, itoa(ci.hUser, szT, 10), -1, &rc, 396: DT_CENTER | DT_VCENTER); 397: OffsetRect(&rc, 0, cyText); 398: hConv = DdeQueryNextServer(hConvList, hConv); 399: } 400: } 401: EndPaint(hwnd, &ps); 402: if (cConvsOrg != cConvs) { 403: /* 404: * The number of active conversations changed! Resize to fit. 405: */ 406: SetWindowPos(hwndMain, 0, 0, 0, cxText, 407: (cyText * (cConvs + 1)) + cyTitle, 408: SWP_NOMOVE | SWP_NOZORDER | SWP_NOACTIVATE); 409: } 410: } 411: 412: 413: 414: /* 415: * This is the main DDEML callback proc. It handles all interaction with 416: * DDEML that is DDEML originated. 417: */ 418: HDDEDATA CALLBACK DdeCallback( 419: WORD wType, 420: WORD wFmt, 421: HCONV hConv, 422: HSZ hszTopic, 423: HSZ hszItem, 424: HDDEDATA hData, 425: DWORD lData1, 426: DWORD lData2) 427: { 428: LPTSTR pszExec; 429: 430: switch (wType) { 431: case XTYP_CONNECT: 432: /* 433: * Only allow connections to us. We can always return TRUE because 434: * the CBF_FILTERINITS bit given to DdeInitialize() told DDEML to 435: * never bother us with connections to any service names other than 436: * what we have registered. 437: * 438: * Note that we do not handle the XTYP_WILD_CONNECT transaction. 439: * This means that no wild-card initiates to us will work. 440: */ 441: return(TRUE); 442: 443: case XTYP_ADVREQ: 444: case XTYP_REQUEST: 445: /* 446: * These two transactions are the only ones that require us to 447: * render our data. By using a custom format, we don't have to 448: * convert our count to text form to support CF_TEXT. 449: */ 450: return(DdeCreateDataHandle(idInst, (PBYTE)&count, sizeof(count), 0, 451: hszAppName, OurFormat, 0)); 452: 453: case XTYP_ADVSTART: 454: /* 455: * Only allow links to our Item in our format. 456: */ 1.1.1.2 ! root 457: return((UINT)wFmt == OurFormat && hszItem == hszAppName); 1.1 root 458: 459: case XTYP_ADVDATA: 460: /* 461: * Data is comming in. We don't bother with XTYP_POKE transactions, 462: * but if we did, they would go here. Since we only allow links 463: * on our item and our format, we need not check these here. 464: */ 465: if (DdeGetData(hData, (PBYTE)&InCount, sizeof(InCount), 0)) { 466: DdeSetUserHandle(hConv, QID_SYNC, InCount); 467: } 468: /* 469: * update ourselves to reflect the new incomming data. 470: */ 471: InvalidateRect(hwndMain, NULL, TRUE); 472: /* 473: * This transaction requires a flag return value. We could also 474: * stick other status bits here if needed but its not recommended. 475: */ 476: return(DDE_FACK); 477: 478: case XTYP_EXECUTE: 479: /* 480: * Another instance wants us to do something. DdeAccessData() 481: * makes parsing of execute strings easy. Also note, that DDEML 482: * will automatically give us the string in the right form 483: * (UNICODE vs ASCII) depending on which form of DdeInitialize() 484: * we called. 485: */ 486: pszExec = DdeAccessData(hData, NULL); 487: if (pszExec) { 488: if (fActive && !stricmp("PAUSE", pszExec)) { 489: KillTimer(hwndMain, 1); 490: fActive = FALSE; 491: InvalidateRect(hwndMain, NULL, TRUE); 492: UpdateWindow(hwndMain); 493: } else if (!fActive && !stricmp("RESUME", pszExec)) { 494: SetTimer(hwndMain, 1, BASE_TIMEOUT + (rand() & 0xff), NULL); 495: fActive = TRUE; 496: InvalidateRect(hwndMain, NULL, TRUE); 497: UpdateWindow(hwndMain); 498: } 499: /* 500: * The beep gives good feedback on how fast the execute was. 501: */ 502: MessageBeep(0); 503: } 504: break; 505: 506: case XTYP_DISCONNECT: 507: /* 508: * Somebody went away, repaint so we update our cConvs count. 509: */ 510: InvalidateRect(hwndMain, NULL, TRUE); 511: break; 512: 513: case XTYP_REGISTER: 514: /* 515: * Since a new server just arrived, lets make sure our links are 516: * up to date. Note that only one link on a 517: * conversation/topic/item/format set will work anyway so we don't 518: * worry about duplicate links. 519: * 520: * Note also that we are using hszItem - which is the InstanceSpecific 521: * name of the server that is registering. This greatly reduces the 522: * number of messages that go flying around. 523: */ 524: hConvList = DdeConnectList(idInst, hszItem, hszAppName, hConvList, NULL); 525: BroadcastTransaction(NULL, 0, OurFormat, XTYP_ADVSTART); 526: SetWindowPos(hwndMain, 0, 0, 0, cxText, 527: (cyText * (cConvs + 1)) + cyTitle, SWP_NOMOVE | SWP_NOZORDER); 528: UpdateWindow(hwndMain); 529: return(TRUE); 530: } 531: return(0); 532: }
This archive runs on limited infrastructure. Preserving old code on modern bandwidth. Automated agents are requested to crawl responsibly.