Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

.NET

Networking With Windows 3.0


MAR91: NETWORKING WITH WINDOWS 3.0

NETWORKING WITH WINDOWS 3.0

Give your NetWare utilities a face lift

This article contains the following executables: MEGAPHON.ARC

Mike Klein

Mike is a software engineer with Insight Development Corporation and specializes in Microsoft Windows, HP NewWave, and Novell NetWare. He is also the author of several books and numerous magazine articles, and can be reached at 500 Cole St., San Francisco, CA 94117, via CompuServe at 73750,2152, and on Telepath as MikeKlein.


Every programmer is familiar with the inversely proportional graph involving software, which shows that with an increase in user-friendliness and ease-of-use comes a decrease in ease of development for the programmer. And I have to admit that this has somewhat biased my opinion about programming for a GUI environment such as Microsoft Windows. However, the old saying about the bark being worse than the bite seems to apply to Windows 3.0. In fact, I found that by "snitching" a couple of predefined program templates (included in the Windows 3.0 SDK), development can be fairly easy. In no time at all I was finishing up my first Windows program, a NetWare messaging and user information utility I call "Megaphone."

I started with the idea that Megaphone would be a simple message-sending utility for allowing a user to type in a 50-character message and broadcast it to another user or users on the network. However, I got a small case of "feature-itis" during development, and Megaphone now sports some additional functionality. For instance, by double-clicking on a user's name, network statistics such as login name, full name, login date and time, and network and node address can be displayed.

Megaphone was written using Microsoft C 6.0, the Microsoft Windows 3.0 Software Development Kit, Solution Systems' Brief programming editor, Microsoft's Windows Write, and Novell's NetWare C Interface. Megaphone requires a version of Windows 3.0 that has been installed for NetWare and a NetWare network to run on. Although Megaphone has only been tested on NetWare 2.1x, it should run on ELS NetWare and NetWare 386.

A New Way of Thinking (or Programming)

The biggest hurdle for new Windows programmers is the fact that you don't have entire control over your program -- Windows does. No longer does your program call the shots. Instead of polling the mouse to find out when it has been clicked on a certain window, Windows informs a particular window's procedure that a mouse has been clicked in its area. Despite the new way of thinking and the supposed difficulty of mastering the 1000+ function calls, Windows is still an easy-to-use and robust development environment.

Under Windows, every created window has a number of properties associated with it, one of which is a far pointer to a procedure that handles events for the window. This procedure handles all or most message processing for the window. In addition, almost every item or object in a window or dialog box is capable of receiving and/or sending messages.

Megaphone has a main window with a system menu, caption, and a minimize button. In Megaphone's client area (see Figure 1) there are a variety of controls, including a list box, a drop-down combo box, an edit control, and three buttons: Send, Settings, and Exit. Each of these controls is capable of sending and receiving a message to other windows and to other controls (which are essentially windows), or of passing messages on to Windows for further processing.

Messages can be sent in one of two ways, either directly to a window's handling procedure (sending a message), or put into the window's application queue and serviced later (posting a message). Messages sent to a window's handling procedure can be sent in one of two ways, either directly to the window (such as an edit control, button, or list box) using its handle, or to a control ID in a dialog box. A control ID is an integer identifier representing a unique value for a control in a dialog box. When a dialog box is first defined or created, the only handles available for manipulation of the dialog and its controls are a window handle to the main dialog box and ID numbers for all of the controls. It's a good idea to set up global window handles to any frequently accessed controls to make sending messages less complicated. Also, certain API functions only work with window handles.

When an application sends messages using PostMessage(), control immediately returns to the calling program. When an application sends a message to another window using SendMessage(), control is not returned until the target window has processed the message sent to it. Note that messages meant for dialog box controls should be sent, not posted. (See Example 1.)

Example 1: Three Windows functions for sending messages

  BOOL  PostMessage (hWnd, wMsg, wParam, lParam);
  DWORD SendMessage (hWnd, wMsg, wParam, lParam);
  DWORD SendDlgItemMessage (hDlg, nIDDlgItem, wMsg, wParam, lParam);

Prior to being dispatched to a window's handling procedure, messages are acquired and dispatched in your application's WinMain(), which is somewhat equivalent to C's main(). WinMain() is where a program does most of its initialization, including registering window classes and displaying windows, and goes into the main message dispatching loop. Before being dispatched to a window handling procedure, messages are stored in a MSG structure, the format of which is shown in Figure 2.

Figure 2: The MSG structure used to store messages

  typedef struct
  {
       HWND   hwnd;     /* Window recvg the message    */
       WORD   message;  /* The actual message code     */
       WORD   wParam;   /* Data dependent upon message */
       LONG   lParam;   /* Addn'l data for message     */
       DWORD  time;     /* Time message received       */
       POINT  pt;       /* Mouse pos at time of msg    */
  }
  MSG;

The key to initial success when programming for Windows is knowing when and how to act on certain messages. Window procedures need to either act on the message (maybe doing nothing with it) or pass it on to Windows (using the DefWindowProc or DefDlgProc functions) for further processing. Sometimes you need to act on the message and still pass it on to Windows. One of my first problems was determining which messages I needed to process and which I could ignore. It's also important to determine whether the message you're acting on is posted after an event has occurred or before. In the case of the Windows message WM_DESTROY, you need to question whether this message is being posted to a window because it has already been destroyed or because Windows wants to destroy it. You'll find that the section of the Windows reference manual that details different Windows messages will be the most dogeared.

All window procedures must be declared FAR PASCAL and EXPORTed in your application's .DEF file. The FAR designation is necessary since Windows' code segment will probably differ from your own application's code segment. The Pascal calling convention, where parameters are pushed onto the stack in the order that they appear, is used because it cuts down on code size. The EXPORT command advertises your function as "callable" to Windows.

The Application

Megaphone is a fairly straightforward Windows program. Listing One is the header file for Megaphone and includes the #defines identifying the controls in the different dialog boxes. Listing Two the C source file for the Megaphone program, could have been broken into several components. However, I didn't want to add to the program's complexity. If you plan on extending this program to other networks, your first step might be to separate the network-specific functions into a separate file.

Listing Three contains the dialog box templates, icon definitions, and other "resources." Listing Four is the .DEF file that contains all the nitty-gritty information including the program name, stack and heap sizes, and window procedures to be imported and exported. Listing Five is a standard Windows make file.

My first problem involved Megaphone's main window, since I wanted to use a dialog box instead of a plain window containing different controls. The problem with the latter approach is that I wanted to retain certain characteristics of a dialog box, such as Tab key processing. However, I also wanted the main dialog box to be treated just like a regular window so that an icon shows at the bottom of the screen when the dialog box is minimized. The answer, a la Charles Petzold's Hexcalc program, was to define the dialog box as a window class, registering and using it in a call to CreateDialog to create the main window. I now had a main window with all the features of a dialog box. You'll find that most utility-style programs use a similar format.

The next problem involved message processing for dialog boxes. Because Megaphone uses almost nothing but modeless dialog boxes, none of the Tab or Alt key controls worked for the different dialog boxes. Normally, the function IsDialogMessage() is used inside a main message-processing loop to check for messages belonging to a dialog box and processes them accordingly, skipping the Windows message-posting procedure. The problem is that IsDialogMessage requires a window handle; I didn't want to have to keep track of up to 50 modeless dialog boxes on my screen, so I created a global window handle that points to the currently active window. Each window-handling procedure traps the WM_ACTIVATE message and accordingly sets the global window handle. This way IsDialogMessage() needs only to be called with the global window handle.

Note that DestroyWindow, not EndDialog, is called when any of the modeless dialog boxes are to be destroyed. In many cases, modal and modeless dialog boxes are dealt with very differently. The different handshaking between window types and the confusion surrounding dialog boxes as a window class took a little getting used to. Many of the dialog box-oriented message descriptions in the SDK are somewhat terse and often confusing. To add to the problem, several "inviting" and undocumented dialog box function calls are made in the source Microsoft provides with the SDK. I couldn't find certain function calls and window messages defined anywhere in the SDK manuals. The SDK proved to be an excellent reference, except in the areas of subclassing, window filters and hooks, owner draw controls, and in describing the different window messages.

When Windows creates a dialog box or any other sort of window, several window parameters have to be set, including class, style, positioning, size, and the window's parent (if one exists). Megaphone's User and Message windows were both created with no parent window, making them somewhat independent. When the Megaphone window is minimized, the user information and message windows stay up on the screen. Although they don't have a parent, they are still technically child windows. When Megaphone's main window is closed, all windows (whether children or not) belonging to that program instance are removed from the screen.

NetWare Programming

I found that adding NetWare-API functionality to Megaphone was completely painless. Future access to the NetWare API will be made possible by a DLL shared amongst all Windows applications. Currently, however, a library must be statically linked in at compile time. If you're using assembly and doing direct INT 21 calls, then you need to IMPORT NetWare.NetWareRequest into your application's .DEF file. This is necessary because Windows needs to trap any calls you make directly to DOS. Since Megaphone uses the C interface, this step isn't required -- the conversion is automatically done. Novell provides two APIs for NetWare: the NetWare C Interface-DOS (preferred); and the NetWare DOS Interface (for assembly language programmers).

The problem with NetWare's SEND utility (which Megaphone mimics somewhat) is that when a message is sent to a character-mode DOS or Windows-based workstation, all background processing halts. In addition, only one message may be received by a workstation at a time. One of the first things that Megaphone does is instruct the file server to hold messages destined for the workstation. (Normally, the file server sends messages to a workstation as soon as it gets them.) Next, Megaphone sets up a Windows timer event when a WM_TIMER message is received and polls the server every five seconds for messages. This could end up generating a certain amount of traffic on your network, so you may want to alter the polling delay somewhat. Because modeless dialog boxes are used for the message (instead of system-modal dialog boxes, which require user input and freeze the background windows), multiple messages may exist on the computer's screen without any user interaction.

The only real problems when programming for NetWare surface in multiple-server environments. NetWare defines three types of server attachments: Primary, the server that a user runs their network login scripts from; preferred, the server that all requests go to; and default, the server that your current drive letter points to (assuming it's a network drive).

Normally, if a preferred server connection exists, all requests go to the preferred server. If a preferred server doesn't exist, then the user's current drive is checked to see if it's a network or local device. If it points to a network volume, then that particular server is used. If the user's current drive points to a local drive, requests go to the primary server. If a primary server doesn't exist, requests go to the first server in the server name table. All of NetWare's API calls either automatically assume the preferred server, get passed a handle to a specific server, or include a parameter involving a network drive letter.

One other important thing to note is the difference between connection numbers and connection IDs. A file server has a certain number of user connections it can support (100 for NetWare/286, 250 for NetWare/386), each represented by a 16-bit connection number. Each workstation, on the other hand, can have up to eight file server attachments, each represented by a 16-bit connection ID. Every file server pointed to by a workstation's connection ID has a matching connection number that points to the workstation. It's important from the start to be clear on which value a function call is asking and which it is returning.

Handling of multiple-server connections in Megaphone can be improved. Because a user could inadvertently change the status of any of their connections, some periodic checking for changes should be done. With a little extra programming to occasionally monitor connection status, the user list box and server combo box could be changed to dynamically alter their contents in response to changes in the network.

Using Megaphone

Megaphone allows users to send messages to each other and retrieve simple connection information on other users. To retrieve connection information (see Figure 3) on a user, merely double click on their name. You can do this as many times as you want, displaying multiple information windows. To send a message, just click on a single user or a group of users, type a message into the message box, and click on the Send button -- it's that easy. To select or deselect all of the users in the list box, just click the left mouse button anywhere in Megaphone's white client area around the satellite dish icon. To automatically refresh network connection information and update the contents of all the list boxes, simply choose Refresh from the menu or click the left mouse button on the satellite dish icon.

When a message from another workstation is received by Megaphone, it is displayed on the screen in a modeless dialog box (see Figure 4). Incoming messages are displayed in a window consisting of a caption showing who sent the message and the date and time the message was received; an edit control for the incoming message and possibly the outgoing response; a Reply button (sometimes disabled) for responding to messages; a Cancel button to remove the message; and lastly, a Save button, which minimizes the message and saves it as a special Message class icon at the bottom of the screen.

The reply feature is possible due to some special formatting when the message is first sent. Using NetWare's built-in messaging utilities, replies to messages are not possible. Megaphone makes it a lot easier to respond to a coworker's request by formatting the outgoing message in the format username[connection#]message. Incoming messages, including NetWare's (which are formatted differently), are then parsed according to this format. If an incoming message does not follow this format, the reply button is disabled.

If you don't want to be disturbed, press the Settings button and change some of your defaults (see Figure 5). If you're feeling unsociable and don't want to receive messages, simply remove the Accept box's check mark. Also included in the Settings menu under Incoming Messages is the Iconize check box, which determines whether incoming messages are displayed as a window or iconized. The Settings menu also includes a set of radio buttons determining whether retrieved users have to be attached to a server or simply defined in its bindery. A limited amount of information can be retrieved on users even when they aren't logged in to a particular server.

Improvements

One enhancement that could be added to Megaphone is checking for double clicks in the file server combo box (perhaps changing it to a list box), where a double-click on a file server would bring up key statistics on it, such as network I/O and traffic information. Key to implementing this feature would be issuing NetWare API calls such as GetLANDriverConfigInfo(), which is used to retrieve LAN driver information. Adding a user group box would also be nice, where single-clicking on a group would automatically highlight every member in the list box that's in the group, and double-clicking on the group would bring up miscellaneous information about the group. This option could be easily implemented using NetWare's ReadPropertyValue call.

Keyboard support could be improved to trap the Delete key while in the user list box, deleting the user's connection when pressed (requires calling NetWare's ClearConnectionNumber). A real-time chatting facility using NetWare's SPX/IPX network transport protocol would also be useful.

Hopefully I've been able to give you enough information and enough code to get you started on Windows-based network utilities. The Windows API is seemingly endless and quite robust -- you'll always be surprised by that one function call you "happen to stumble across." Likewise, I continue to find new uses for the different calls in the NetWare API. Combining the two can make for some really powerful network applications.


_NETWORKING WITH WINDOWS 3_
by Mike Klein


[LISTING ONE]
<a name="00af_000c">

/* MEGAPHON.H */

#define MAX_CONNECTIONS 100
#define MAX_MESSAGE_LEN  56

#define IDT_MESSAGETIMER 100

#define IDC_USERLISTBOXTITLE    100
#define IDC_USERLISTBOX         101
#define IDC_SERVERCOMBOBOXTITLE 102
#define IDC_SERVERCOMBOBOX      103
#define IDC_MESSAGEEDITBOX      104
#define IDC_SENDBUTTON          105
#define IDC_SETTINGSBUTTON      106
#define IDC_EXITBUTTON          107

#define IDC_ACCEPTMESSAGES    100
#define IDC_ICONIZEMESSAGES   101
#define IDC_ONLYATTACHEDUSERS 102
#define IDC_ALLUSERSINBINDERY 103

#define IDC_USERNAME  100
#define IDC_STATION   101
#define IDC_NODE      102
#define IDC_FULLNAME  103
#define IDC_LOGINTIME 104
#define IDC_NETWORK   105

#define IDC_REPLYEDITBOX 100
#define IDC_REPLYBUTTON  101
#define IDC_SAVEBUTTON   102

#define IDM_EXIT        200
#define IDM_ABOUT       201
#define IDM_REFRESH     220


int  PASCAL WinMain(HANDLE, HANDLE, LPSTR, int);

LONG FAR PASCAL MainWndProc(HWND, unsigned, WORD, LONG);
LONG FAR PASCAL UserInfo(HWND, unsigned, WORD, LONG);
LONG FAR PASCAL MessageHandler(HWND, unsigned, WORD, LONG);
BOOL FAR PASCAL About(HWND, unsigned, WORD, LONG);
BOOL FAR PASCAL Settings(HWND, unsigned, WORD, LONG);

BOOL PASCAL InitNetStuff(VOID);
VOID PASCAL EnableOrDisableSendButton(VOID);
VOID PASCAL SendNetWareMessageToUsers(VOID);
VOID PASCAL ShowUserInformation(VOID);





<a name="00af_000d">
<a name="00af_000e">
[LISTING TWO]
<a name="00af_000e">

/**************************************************************************

    PROGRAM: Megaphone
    AUTHOR : Mike Klein
    VERSION: 1.0
    FILE   : megaphon.exe
    CREATED: 10-25-90

    REQUIREMENTS: Windows 3.x and a Novell NetWare 2.1x or compatible network

    PURPOSE     : Messaging and simple information system for Novell NetWare.
                  Allows quick replies to messages from coworkers and
                  miscellaneous login information about them.

**************************************************************************/


#define NOCOMM


#include <windows.h>
#include <direct.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>


#include <nwbindry.h>
#include <nwconn.h>
#include <nwdir.h>
#include <nwmsg.h>
#include <nwwrkenv.h>

#include "megaphon.h"


HANDLE hInstMegaphone;          /* Original instance of Megaphone           */

/* These are handles to commonly accessed controls in different dialogs     */

HWND hWndCurrent;        /* handle to currently active window on screen     */
HWND hDlgMegaphone;      /* handle to Megaphone dialog window               */
HWND hWndUserListBox;
HWND hWndServerComboBox;
HWND hWndMessageEditBox;
HWND hWndSendButton;

/* Set up some additional global variables to commonly used NetWare stuff   */

WORD DefaultConnectionID;
BYTE ServerName[48];

WORD UserConnectionNum;
BYTE UserName[48];
BYTE UserLoginTime[7];

WORD SelUserConnectionNum;
BYTE SelUserNetworkAddr[4];
BYTE SelUserNodeAddr[6];
BYTE SelUserName[48];
BYTE SelUserFullName[48];
BYTE SelUserLoginTime[7];

BOOL AcceptMessages = TRUE;
BOOL IconizeMessages = FALSE;
BOOL AllUsers = FALSE;

BYTE Text[100];


/**************************************************************************

    FUNCTION: WinMain

    PURPOSE : Calls initialization function, processes message loop

**************************************************************************/

int PASCAL WinMain(HANDLE hInstance, HANDLE hPrevInstance, LPSTR lpCmdLine,
   int nCmdShow)
{
   WNDCLASS wc;
   MSG      msg;

   if(!hPrevInstance)                    /* Other instances of app running? */
   {
      hInstMegaphone = hInstance;        /* Remember original instance      */

      /* Fill in window class structure with parameters that describe the   */
      /* main window.                                                       */

      wc.style         = CS_DBLCLKS;     /* Process double click msgs       */
      wc.lpfnWndProc   = MainWndProc;    /* Function to retrieve msgs for   */
                                         /* windows of this class.          */
      wc.cbClsExtra    = 0;              /* No per-class extra data.        */
      wc.cbWndExtra    = DLGWINDOWEXTRA; /* Set becuase we used the CLASS   */
                                         /* statement in dialog box         */
      wc.hInstance     = hInstance;      /* Application that owns the class.*/
      wc.hIcon         = LoadIcon(hInstance, "Megaphone");
      wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
      wc.hbrBackground = GetStockObject(WHITE_BRUSH);
      wc.lpszMenuName  = "Megaphone";
      wc.lpszClassName = "Megaphone";

      if(!RegisterClass(&wc))
         return(FALSE);

      /* Fill in window class structure with parameters that describe the   */
      /* message window.                                                    */

      wc.style         = NULL;           /* Process double click msgs       */
      wc.lpfnWndProc   = MessageHandler;
      wc.cbClsExtra    = 0;              /* No per-class extra data.        */
      wc.cbWndExtra    = DLGWINDOWEXTRA; /* Set becuase we used the CLASS   */
                                         /* statement in dialog box         */
      wc.hInstance     = hInstance;      /* Application that owns the class.*/
      wc.hIcon         = LoadIcon(hInstance, "Message");
      wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
      wc.hbrBackground = GetStockObject(WHITE_BRUSH);
      wc.lpszMenuName  = NULL;
      wc.lpszClassName = "Message";

      if(!RegisterClass(&wc))
         return(FALSE);

      /* Fill in window class structure with parameters that describe the   */
      /* userinfo window.                                                   */

      wc.style         = NULL;           /* Process double click msgs       */
      wc.lpfnWndProc   = UserInfo;
      wc.cbClsExtra    = 0;              /* No per-class extra data.        */
      wc.cbWndExtra    = DLGWINDOWEXTRA; /* Set becuase we used the CLASS   */
                                         /* statement in dialog box         */
      wc.hInstance     = hInstance;      /* Application that owns the class.*/
      wc.hIcon         = LoadIcon(hInstance, "User");
      wc.hCursor       = LoadCursor(NULL, IDC_ARROW);
      wc.hbrBackground = GetStockObject(WHITE_BRUSH);
      wc.lpszMenuName  = NULL;
      wc.lpszClassName = "User";

      if(!RegisterClass(&wc))
         return(FALSE);

      /* Create the main dialog window Megaphone, get window handles to     */
      /* several of the controls, send the message edit box a message to    */
      /* limit itself to MAX_MESSAGE_LEN characters, and set the system     */
      /* font (mono-spaced) for server combo box and user list box.         */

      hDlgMegaphone = CreateDialog(hInstance, "Megaphone", NULL, 0L);

      hWndUserListBox     = GetDlgItem(hDlgMegaphone, IDC_USERLISTBOX);
      hWndServerComboBox  = GetDlgItem(hDlgMegaphone, IDC_SERVERCOMBOBOX);
      hWndMessageEditBox  = GetDlgItem(hDlgMegaphone, IDC_MESSAGEEDITBOX);
      hWndSendButton      = GetDlgItem(hDlgMegaphone, IDC_SENDBUTTON);

      SendMessage(hWndMessageEditBox, EM_LIMITTEXT, MAX_MESSAGE_LEN - 1, 0L);
      SendMessage(hWndUserListBox, WM_SETFONT,
         GetStockObject(SYSTEM_FIXED_FONT), FALSE);
      SendMessage(hWndServerComboBox, WM_SETFONT,
         GetStockObject(SYSTEM_FIXED_FONT), FALSE);

      /* Finally, show the Megaphone dialog box                             */

      ShowWindow(hDlgMegaphone, nCmdShow);
      UpdateWindow(hDlgMegaphone);

      /* Initialize the network stuff, and fill in list boxes               */

      InitNetStuff();
   }
   else
   {
      /* If there was another instance of Megaphone running, then switch to */
      /* it by finding any window of class = "Megaphone". Then, if it's an  */
      /* icon, open the window, otherwise just make it active.              */

      hDlgMegaphone = FindWindow("Megaphone", NULL);
      if(IsIconic(hDlgMegaphone))
         ShowWindow(hDlgMegaphone, SW_SHOWNORMAL);
      SetActiveWindow(hDlgMegaphone);

      return(FALSE);
   }

   /* Acquire and dispatch messages until a WM_QUIT message is received.    */
   /* The window handle hWndCurrent points to the currently active window,  */
   /* and is used to identify and process keystrokes going to any modeless  */
   /* dialog box.                                                           */

   while(GetMessage(&msg, NULL, NULL, NULL))
      if(hWndCurrent == NULL || !IsDialogMessage(hWndCurrent, &msg))
      {
         TranslateMessage(&msg);
         DispatchMessage(&msg);
      }
}


/**************************************************************************

    FUNCTION: MainWndProc

    PURPOSE :  Processes messages for Megaphone dialog box

**************************************************************************/

long FAR PASCAL MainWndProc(HWND hWnd, unsigned wMsg, WORD wParam, LONG lParam)
{
   FARPROC lpProc;            /* Far procedure ptr to be used for About box */

   HWND    hWndTemp;
   HWND    hDlgMessage;

   BYTE *ptr;
   int  Index = 100;

   BYTE MessageCaption[100];
   BYTE MessageDate[9];
   BYTE MessageTime[9];

   switch(wMsg)
   {
      case WM_COMMAND              :

         switch(wParam)
         {
            /* When wParam == 1, return was pressed in either the list box, */
            /* combo box, check boxes, or edit control.                     */

            case 1                  :

               /* Find out the current control (window) and process ENTER   */
               /* key accordingly.                                          */

               switch(GetDlgCtrlID(GetFocus()))
               {
                  case IDC_USERLISTBOX    :

                     ShowUserInformation();
                     break;

                  case IDC_SERVERCOMBOBOX :

                     SendMessage(hWndServerComboBox, WM_KILLFOCUS, NULL, 0L);
                     SendMessage(hWndServerComboBox, WM_SETFOCUS, NULL, 0L);
                     break;

                  case IDC_MESSAGEEDITBOX :

                     if(IsWindowEnabled(hWndSendButton))
                     {
                        SendMessage(hWndSendButton, WM_LBUTTONDOWN, 0, 0L);
                        SendMessage(hWndSendButton, WM_LBUTTONUP, 0, 0L);
                        SendMessage(hDlgMegaphone, WM_NEXTDLGCTL,
                           hWndMessageEditBox, TRUE);
                     }
                     else
                     {
                        MessageBeep(0);
                        MessageBox(hDlgMegaphone, "You need a message and a user(s)",
                           "ERROR", MB_ICONEXCLAMATION | MB_OK);
                     }
                     break;

                  default                 :

                     break;
               }
               break;

            case IDC_USERLISTBOX         :

               if(HIWORD(lParam) == LBN_DBLCLK)
               {
                  /* Restore the list box item's selection state. If it     */
                  /* isn't flagged, then flag it, and vice versa.           */

                  Index = (int) SendMessage(hWndUserListBox, LB_GETCURSEL, 0, 0L);
                  if(SendMessage(hWndUserListBox, LB_GETSEL, Index, 0L))
                     SendMessage(hWndUserListBox, LB_SETSEL, FALSE, Index);
                  else
                     SendMessage(hWndUserListBox, LB_SETSEL, TRUE, Index);

                  ShowUserInformation();
               }
               else
                  EnableOrDisableSendButton();

               break;

            case IDC_SERVERCOMBOBOX      :

               if(HIWORD(lParam) == CBN_SELCHANGE)
               {
                  if((Index = (int) SendMessage(hWndServerComboBox, CB_GETCURSEL,
                     0, 0L)) == CB_ERR)
                     break;

                  SendMessage(hWndServerComboBox, CB_GETLBTEXT, Index,
                     (LONG) (LPSTR) ServerName);

                  if(!GetConnectionID(ServerName, &DefaultConnectionID))
                  {
                     SetPreferredConnectionID(DefaultConnectionID);
                     InitNetStuff();
                  }
               }
               break;

            case IDC_MESSAGEEDITBOX      :

               EnableOrDisableSendButton();
               break;

            case IDC_SENDBUTTON          :

               if(HIWORD(lParam) == BN_CLICKED)
                  SendNetWareMessageToUsers();
               break;

            case IDM_EXIT                :
            case IDC_EXITBUTTON          :

               SendMessage(hDlgMegaphone, WM_CLOSE, 0, 0L);
               break;

            case IDM_ABOUT               :

               lpProc = MakeProcInstance(About, hInstMegaphone);
               DialogBox(hInstMegaphone, "About", hWnd, lpProc);
               FreeProcInstance(lpProc);
               break;

            case IDM_REFRESH             :

               InitNetStuff();
               break;

            case IDC_SETTINGSBUTTON      :

               lpProc = MakeProcInstance(Settings, hInstMegaphone);
               DialogBox(hInstMegaphone, "Settings", hWnd, lpProc);
               FreeProcInstance(lpProc);
               break;

            default                      :

               break;
         }

         break;

      case WM_TIMER                :

         /* This is the Windows timer for retrieving messages that goes off */
         /* every five seconds.                                             */

         GetBroadcastMessage(Text);

         if(*Text)
         {
            /* Create the message reply dialog box and limit the edit box   */
            /* to NetWare's limit of 56 or so characters.                   */

            hDlgMessage = CreateDialog(hInstMegaphone, "Message",
               hDlgMegaphone, 0L);

            SendDlgItemMessage(hDlgMessage, IDC_REPLYEDITBOX, EM_LIMITTEXT,
               MAX_MESSAGE_LEN - 1, 0L);

            /* Parse the incoming string of 'username[station#]message'     */

            if((ptr = strchr(Text, '[')) == NULL)
            {
               /* If the incoming message isn't formatted by NetWare's      */
               /* SEND command, SESSION program, or Megaphone, then we      */
               /* can't use the REPLY button, so disable it.                */

               SelUserName[0] = '\0';
               SelUserConnectionNum = 0;
               EnableWindow(GetDlgItem(hDlgMessage, IDC_REPLYBUTTON), FALSE);
            }
            else
            {
               /* Pull up the user name and connection#, and message, which */
               /* is right after the ']'.                                   */

               strncpy(SelUserName, Text, ptr - Text);
               SelUserName[ptr - Text] = '\0';
               SelUserConnectionNum = atoi(ptr + 1);
               if((ptr = strchr(Text, ']')) != NULL)
                  lstrcpy((LPSTR) Text, (LPSTR) (ptr + 1));

               /* Check again to see if we pulled up a valid Conn#. If we   */
               /* didn't, then disable the REPLY button.                    */

               if(SelUserConnectionNum < 1 || SelUserConnectionNum > 255)
                  EnableWindow(GetDlgItem(hDlgMessage, IDC_REPLYBUTTON), FALSE);
            }

            /* Put the retrieved message in the dialog's edit box           */

            SetDlgItemText(hDlgMessage, IDC_REPLYEDITBOX, Text);

            /* Record the date and time that the message came in at and     */
            /* make it reflected in the message caption.                    */

            _strdate(MessageDate);
            _strtime(MessageTime);
            wsprintf(MessageCaption, "%s %s %s", (LPSTR) SelUserName,
               (LPSTR) MessageDate, (LPSTR) MessageTime);
            SetWindowText(hDlgMessage, MessageCaption);

            /* Finally, show (or minimize) the completed message dialog box */

            if(IconizeMessages)
               ShowWindow(hDlgMessage, SW_SHOWMINNOACTIVE);
            else
               ShowWindow(hDlgMessage, SW_SHOWNORMAL);

            MessageBeep(0);
         }

         return(0L);

      case WM_CLOSE                :

         /* Check before closing the main window if there are any           */
         /* outstanding messages that haven't been closed.                  */

         if(hWndTemp = FindWindow((LPSTR) "Message", NULL))
         {
            if(MessageBox(hDlgMegaphone,
               "Quit without disposing of/reading messages?",
               "Messages Outstanding", MB_YESNO | MB_APPLMODAL |
               MB_ICONEXCLAMATION | MB_DEFBUTTON2) == IDYES)
            {
               DestroyWindow(hDlgMegaphone);
            }
            else
            {
               ShowWindow(hWndTemp, SW_SHOWNORMAL);
               SetActiveWindow(hWndTemp);
            }
         }
         else
            DestroyWindow(hDlgMegaphone);

         return(0L);

      case WM_SETFOCUS             :

         if(IsWindowEnabled(hWndMessageEditBox))
            SendMessage(hDlgMegaphone, WM_NEXTDLGCTL, hWndMessageEditBox, TRUE);
         else
            SendMessage(hDlgMegaphone, WM_NEXTDLGCTL,
               GetDlgItem(hDlgMegaphone, IDC_EXITBUTTON), TRUE);

         return(0L);

      case WM_ACTIVATE             :

         hWndCurrent = (wParam == NULL) ? NULL : hWnd;
         break;

      case WM_DESTROY              :

         PostQuitMessage(0);
         return(0L);

      default                      :

         break;

   }
   return(DefDlgProc(hWnd, wMsg, wParam, lParam));
}


/***************************************************************************

    FUNCTION: SendNetWareMessageToUsers

    PURPOSE : Do I really need to explain this one?

***************************************************************************/

VOID PASCAL SendNetWareMessageToUsers(VOID)
{
   BYTE Message[MAX_MESSAGE_LEN];
   WORD ConnectionsToSend[MAX_CONNECTIONS];
   BYTE ResultList[MAX_CONNECTIONS];
   int  NumUsers;
   int  i, j;

   /* Get text inside message edit box and format message so it includes    */
   /* the username, connection#, and message. The first two fields are      */
   /* needed for replying back since there's nothing in NetWare's messaging */
   /* facility to tell you who sent the message.                            */

   GetDlgItemText(hDlgMegaphone, IDC_MESSAGEEDITBOX, (LPSTR) Text, MAX_MESSAGE_LEN);

   wsprintf(Message, "%s[%d]%s", (LPSTR) UserName, UserConnectionNum,
      (LPSTR) Text);

   /* Get total number of users in list box and check to see if they've     */
   /* been selected or not. If they have, get their Connection# and put it  */
   /* in the ConnectionsToSend array.                                       */

   NumUsers = (int) SendMessage(hWndUserListBox, LB_GETCOUNT, 0, 0L);

   for(i = j = 0; i < NumUsers; i++)
      if(SendMessage(hWndUserListBox, LB_GETSEL, i, 0L))
      {
         SendMessage(hWndUserListBox, LB_GETTEXT, i, (LONG) (LPSTR) Text);
         ConnectionsToSend[j++] = atoi(&Text[18]);
      }

   /* Send the message to users in the array.                               */

   SendBroadcastMessage(Message, ConnectionsToSend, ResultList, j);

   /* Scan through the ResultList array checking for messages that had      */
   /* problems. Selecting OK will continue to check the status of the other */
   /* messages, where selecting CANCEL from the message box will abort the  */
   /* send status checking altogether.                                      */

   for(i = 0; i < j; i++)
      switch(ResultList[i])
      {
         case 0xfc :

            wsprintf(Text, "Message to Connection %d", ConnectionsToSend[i]);
            if(MessageBox(hDlgMegaphone,
               "Message not sent - User already has message pending",
               Text, MB_OKCANCEL | MB_ICONEXCLAMATION) == IDCANCEL)
            {
               i = j;
            }
            break;

         case 0xfd :

            wsprintf(Text, "Message to Connection %d", ConnectionsToSend[i]);
            if(MessageBox(hDlgMegaphone,
               "Message not sent - Invalid connection number",

               Text, MB_OKCANCEL | MB_ICONEXCLAMATION) == IDCANCEL)
            {
               i = j;
            }
            break;

         case 0xff :

            wsprintf(Text, "Message to Connection %d", ConnectionsToSend[i]);
            if(MessageBox(hDlgMegaphone,
               "Message not sent - User has blocking turned on",
               Text, MB_OKCANCEL | MB_ICONEXCLAMATION) == IDCANCEL)
            {
               i = j;
            }
            break;

         default   :

            break;
      }
}


/**************************************************************************

    FUNCTION: EnableOrDisableSendButton

    PURPOSE : Based on a message being in the edit box and at least one
              selected user, the send button is enabled or disabled

**************************************************************************/

VOID PASCAL EnableOrDisableSendButton(VOID)
{
   /* Check to see if at least one user is selected and at least a one      */
   /* character message in the edit box. If there is, then enable the SEND  */
   /* button and thicken it to make it the default response when ENTER is   */
   /* pressed.                                                              */

   if(SendMessage(hWndUserListBox, LB_GETSELCOUNT, 0, 0L) &&
      SendMessage(hWndMessageEditBox, EM_LINELENGTH, -1, 0L))
   {
      EnableWindow(hWndSendButton, TRUE);
   }
   else
   {
      EnableWindow(hWndSendButton, FALSE);
   }
}


/***************************************************************************

    FUNCTION: InitNetStuff

    PURPOSE : Initialize network connections and fill in combo and list boxes

***************************************************************************/

BOOL PASCAL InitNetStuff(VOID)
{
   HCURSOR hOldCursor;

   WORD NumberOfConnections;
   WORD NumberOfServers;

   int  Index;
   BYTE DirHandle;

   BYTE TempServerName[48];
   WORD ObjectType;
   WORD ConnID;
   WORD ConnectionList[MAX_CONNECTIONS];
   BYTE SearchObjectName[48] = "*";
   BYTE ObjectName[48];
   long ObjectID;
   BYTE ObjectHasProperties;
   BYTE ObjectFlag;
   BYTE ObjectSecurity;

   /* Check to see if a connection has been made to any server              */

   if(UserConnectionNum = GetConnectionNumber())
      if(!GetConnectionInformation(UserConnectionNum, UserName, &ObjectType,
         &ObjectID, UserLoginTime))
         if(*UserName)
         {
            /* If we have a preferred connection ID, then were supposed to  */
            /* use it for all of our requests. If we don't, then check to   */
            /* see if we're sitting on a local drive (bit 0 or 1 not set).  */
            /* If we are, then set the default connection ID to that of the */
            /* primary server. If we're sitting on a network drive, then    */
            /* requests go to the associated server.                        */

            if(GetPreferredConnectionID())
               DefaultConnectionID = GetPreferredConnectionID();
            else
            {
               if(!(GetDriveInformation((BYTE) (_getdrive() - 1), &ConnID,
                  &DirHandle) & 3))
               {
                  DefaultConnectionID = GetPrimaryConnectionID();
               }
               SetPreferredConnectionID(DefaultConnectionID);
            }

            /* Set NetWare's message mode so that Megaphone can poll for       */
            /* messages instead of automatically having them sent to the       */
            /* station.                                                        */

            EnableBroadcasts();
            SetBroadcastMode(3);

            /* Set up a Windows timer so that every 5 seconds, the server is   */
            /* polled for waiting messages.                                    */

            SetTimer(hDlgMegaphone, IDT_MESSAGETIMER, 5000, NULL);

            EnableWindow(GetDlgItem(hDlgMegaphone, IDC_SETTINGSBUTTON), TRUE);
         }

   if(!UserConnectionNum)
   {
      EnableWindow(GetDlgItem(hDlgMegaphone, IDC_SETTINGSBUTTON), FALSE);
      MessageBox(hDlgMegaphone, "Must be logged into a NetWare server",
         "ERROR - NO USERS", MB_ICONSTOP | MB_OK);
      return(FALSE);
   }

   /* Now that we've established a network connection, let's fill in the    */
   /* drop-down combo box with file servers and the list box with users of  */
   /* whatever the node's preferred server is.                              */

   /* Turn off re-drawing of the list box so it doesn't flicker, reset the  */
   /* contents of both boxes, capture and intercept all mouse activity, and */
   /* turn the cursor into an hourglass.                                    */

   SendMessage(hWndUserListBox, WM_SETREDRAW, FALSE, 0L);
   SendMessage(hWndUserListBox, LB_RESETCONTENT, 0, 0L);
   SendMessage(hWndServerComboBox, CB_RESETCONTENT, 0, 0L);
   SetCapture(hDlgMegaphone);
   hOldCursor = SetCursor(LoadCursor(NULL, IDC_WAIT));

   /* Scan through the possible ConnectionID#'s (1-8) and see what file     */
   /* servers are attached, if any are, and put them in the combo box.      */

   for(ConnID = 1; ConnID < 9; ++ConnID)
   {
      GetFileServerName(ConnID, TempServerName);
      if(*TempServerName)
         SendMessage(hWndServerComboBox, CB_ADDSTRING, NULL,
            (LONG) (LPSTR) TempServerName);
   }

   /* Get default server                                                    */

   GetFileServerName(DefaultConnectionID, ServerName);

   /* Search the NetWare bindery for active user connections, putting       */
   /* them into the list box                                                */

   ObjectID = -1;
   while(!ScanBinderyObject(SearchObjectName, OT_USER, &ObjectID, ObjectName,
      &ObjectType, &ObjectHasProperties, &ObjectFlag, &ObjectSecurity))
   {
      GetObjectConnectionNumbers(ObjectName, OT_USER, &NumberOfConnections,
         ConnectionList, MAX_CONNECTIONS);

      /* If there are multiple connections for a single user then we        */
      /* have to make sure and get all of them.                             */

      if(!NumberOfConnections)
      {
         if(AllUsers)
         {
            wsprintf(Text, "[%s]", (LPSTR) ObjectName);
            SendMessage(hWndUserListBox, LB_ADDSTRING, NULL, (LONG) (LPSTR) Text);
         }
      }
      else
         for(Index = 0; Index < (int) NumberOfConnections; ++Index)
         {
            if(UserConnectionNum == ConnectionList[Index])
               wsprintf(Text, "%-16.16s *%3d", (LPSTR) ObjectName, ConnectionList[Index]);
            else
               wsprintf(Text, "%-17.17s %3d", (LPSTR) ObjectName, ConnectionList[Index]);
            SendMessage(hWndUserListBox, LB_ADDSTRING, NULL, (LONG) (LPSTR) Text);
         }
   }

   /* Turn re-drawing for the list box back on and make the first item in   */
   /* the server combo box and user list box the default.                   */

   InvalidateRect(hWndUserListBox, NULL, TRUE);
   SendMessage(hWndUserListBox, LB_SETSEL, 0, 0L);
   SendMessage(hWndUserListBox, WM_SETREDRAW, TRUE, 0L);

   /* Select the default server in the server combo box                     */

   SendMessage(hWndServerComboBox, CB_SELECTSTRING, -1, (LONG) (LPSTR) ServerName);

   /* Add the # of servers and users to the caption on the server and list  */
   /* boxes.                                                                */

   NumberOfConnections = (int) SendMessage(hWndUserListBox,   LB_GETCOUNT, 0, 0L);
   wsprintf(Text, "%d &Users on %s", NumberOfConnections, (LPSTR) ServerName);
   SetDlgItemText(hDlgMegaphone, IDC_USERLISTBOXTITLE, (LPSTR) Text);

   NumberOfServers = (int) SendMessage(hWndServerComboBox, CB_GETCOUNT, 0, 0L);
   wsprintf(Text, "%d Ser&vers", NumberOfServers);
   SetDlgItemText(hDlgMegaphone, IDC_SERVERCOMBOBOXTITLE, (LPSTR) Text);

   /* Restore mouse activity, set the cursor back to normal, and initially  */
   /* disable the Send button.                                              */

   ReleaseCapture();
   SetCursor(hOldCursor);
   EnableOrDisableSendButton();

   return(TRUE);
}


/***************************************************************************

    FUNCTION: About

    PURPOSE : Processes messages for About box

***************************************************************************/

BOOL FAR PASCAL About(HWND hWnd, unsigned wMsg, WORD wParam, LONG lParam)
{
   switch(wMsg)
   {
      case WM_INITDIALOG :

         return(TRUE);

      case WM_COMMAND    :

         if(wParam == IDOK || wParam == IDCANCEL)
         {
            EndDialog(hWnd, TRUE);
            return(TRUE);
         }
         break;

      default            :

         break;
   }
   return(FALSE);
}


/***************************************************************************

    FUNCTION: Settings

    PURPOSE : Processes messages for Settings window

***************************************************************************/

BOOL FAR PASCAL Settings(HWND hWnd, unsigned wMsg, WORD wParam, LONG lParam)
{
   switch(wMsg)
   {
      case WM_INITDIALOG :

         CheckDlgButton(hWnd, IDC_ACCEPTMESSAGES, AcceptMessages);
         CheckDlgButton(hWnd, IDC_ICONIZEMESSAGES, IconizeMessages);

         if(AllUsers)
            CheckRadioButton(hWnd, IDC_ALLUSERSINBINDERY,
               IDC_ALLUSERSINBINDERY, IDC_ALLUSERSINBINDERY);
         else
            CheckRadioButton(hWnd, IDC_ONLYATTACHEDUSERS,
               IDC_ONLYATTACHEDUSERS, IDC_ONLYATTACHEDUSERS);

         break;

      case WM_COMMAND    :

         switch(wParam)
         {
             case IDC_ACCEPTMESSAGES    :

               if(IsDlgButtonChecked(hWnd, IDC_ACCEPTMESSAGES))
               {
                  EnableBroadcasts();
                  SetTimer(hDlgMegaphone, IDT_MESSAGETIMER, 5000, NULL);
                  AcceptMessages = TRUE;
               }
               else
               {
                  DisableBroadcasts();
                  KillTimer(hDlgMegaphone, IDT_MESSAGETIMER);
                  AcceptMessages = FALSE;
               }

               break;

            case IDC_ICONIZEMESSAGES    :

               if(IsDlgButtonChecked(hWnd, IDC_ICONIZEMESSAGES))
                  IconizeMessages = TRUE;
               else
                  IconizeMessages = FALSE;

               break;

            case IDC_ONLYATTACHEDUSERS :

               AllUsers = FALSE;
               InitNetStuff();
               break;

            case IDC_ALLUSERSINBINDERY :

               AllUsers = TRUE;
               InitNetStuff();
               break;

            case IDOK                  :
            case IDCANCEL              :

               EndDialog(hWnd, TRUE);
               return(TRUE);

            default            :

               break;
         }
   }
   return(FALSE);
}


/***************************************************************************

    FUNCTION: MessageHandler

    PURPOSE : Processes messages for user message dialog box/window

***************************************************************************/

long FAR PASCAL MessageHandler(HWND hWnd, unsigned wMsg, WORD wParam, LONG lParam)
{
   BYTE Message[MAX_MESSAGE_LEN];
   WORD ConnectionsToSend[1];
   BYTE ResultList[1];

   switch(wMsg)
   {
      case WM_COMMAND  :

         switch(wParam)
         {
            case 1               :

               /* A '1' is generated when ENTER is pressed while in a       */
               /* control in a dialog box and there's no default button     */
               /* defined. This is to trap the ENTER key when in the reply  */
               /* edit box. We'll simulate the pressing of the REPLY button */

               if(IsWindowEnabled(GetDlgItem(hWnd, IDC_REPLYBUTTON)))
               {
                  SendMessage(GetDlgItem(hWnd, IDC_REPLYBUTTON),
                     WM_LBUTTONDOWN, 0, 0L);
                  SendMessage(GetDlgItem(hWnd, IDC_REPLYBUTTON),
                     WM_LBUTTONUP, 0, 0L);
               }
               else
               {
                  MessageBeep(0);
                  MessageBox(hWnd, "You cannot reply to this message",
                     "ERROR", MB_ICONEXCLAMATION | MB_OK);
               }
               break;

            case IDC_SAVEBUTTON  :

               /* "Save" the message by iconizing it                        */

               CloseWindow(hWnd);
               break;

            case IDC_REPLYBUTTON :

               /* Get text from edit box located in message dialog box      */

               GetDlgItemText(hWnd, IDC_REPLYEDITBOX, (LPSTR) Text,
                  MAX_MESSAGE_LEN);

               /* Set up my connection# to send to, format the message, and */
               /* send it to the user.                                      */

               ConnectionsToSend[0] = SelUserConnectionNum;
               wsprintf(Message, "%s[%d]%s", (LPSTR) SelUserName,
                  SelUserConnectionNum, (LPSTR) Text);

               SendBroadcastMessage(Message, ConnectionsToSend, ResultList, 1);

               /* Possible results of sending a message                     */

               switch(ResultList[0])
               {
                  case 0xfc :

                     wsprintf(Text, "Message to Connection %d",
                        ConnectionsToSend[0]);
                     MessageBox(hDlgMegaphone,
                        "Message not sent - User already has message pending",
                        Text, MB_OK | MB_ICONEXCLAMATION);
                     break;

                  case 0xfd :

                     wsprintf(Text, "Message to Connection %d",
                        ConnectionsToSend[0]);
                     MessageBox(hDlgMegaphone,
                        "Message not sent - Invalid connection number",
                        Text, MB_OK | MB_ICONEXCLAMATION);

                     break;

                  case 0xff :

                     wsprintf(Text, "Message to Connection %d",
                        ConnectionsToSend[0]);
                     MessageBox(hDlgMegaphone,
                        "Message not sent - User has blocking turned on",
                        Text, MB_OK | MB_ICONEXCLAMATION);

                     break;

                  default   :

                     break;
               }

               /* Get rid of the message reply dialog box/window            */

               DestroyWindow(hWnd);
               return(0L);

            case IDCANCEL        :

               DestroyWindow(hWnd);
               return(0L);

            default              :

               break;
         }
         break;

      case WM_CLOSE         :

         DestroyWindow(hWnd);
         return(0L);

      case WM_SETFOCUS      :

         /* Set the focus to the edit control.                              */

         SendMessage(hWnd, WM_NEXTDLGCTL, GetDlgItem(hWnd, IDC_REPLYEDITBOX),
            TRUE);

         return(0L);

      case WM_ACTIVATE      :

         hWndCurrent = (wParam == NULL) ? NULL : hWnd;
         break;

      default          :

         break;
   }
   return(DefDlgProc(hWnd, wMsg, wParam, lParam));
}


/***************************************************************************

    FUNCTION: ShowUserInformation

    PURPOSE : Shows user information on current or double-clicked entry

***************************************************************************/

VOID PASCAL ShowUserInformation(VOID)
{
   int Index;
   BYTE *ptr;

   HWND hDlgUserInfo;

   WORD ObjectType;
   long ObjectID;
   WORD SocketNum;
   BYTE PropertyValue[128];
   BYTE MoreSegments;
   BYTE PropertyFlags;

   /* Get an index to the user name underneath the cursor,   */
   /* and then get the user's connection number so we can    */
   /* retrieve NetWare information on him/her.               */

   Index = (int) SendMessage(hWndUserListBox, LB_GETCURSEL, 0, 0L);
   SendMessage(hWndUserListBox, LB_GETTEXT, Index, (LONG) (LPSTR) Text);

   /* If entry in list box doesn't have a connection #, then we need to get */
   /* the login name by parsing the list box string, we can't use a call to */
   /* GetConnectionInformation().                                           */

   memset(SelUserLoginTime, '\0', 7);
   memset(SelUserNetworkAddr, '\0', 4);
   memset(SelUserNodeAddr, '\0', 6);
   SelUserFullName[0] = '\0';
   SelUserConnectionNum = 0;

   if(Text[0] == '[')
   {
      ptr = strchr(Text, ']');
      strncpy(SelUserName, Text + 1, ptr - Text - 1);
      SelUserName[ptr - Text - 1] = '\0';
   }
   else
   {
      ptr = strchr(Text, ' ');
      strncpy(SelUserName, Text, ptr - Text);
      SelUserConnectionNum = atoi(&Text[18]);
      SelUserName[ptr - Text] = '\0';

      /* We can get connection info only for users that are logged in to a  */
      /* server. So, get the user name and login time, network and node     */
      /* address, and full name for specified connection#.                  */

      GetConnectionInformation(SelUserConnectionNum, SelUserName, &ObjectType,
         &ObjectID, SelUserLoginTime);

      GetInternetAddress(SelUserConnectionNum, SelUserNetworkAddr,
         SelUserNodeAddr, &SocketNum);
   }

   if(!ReadPropertyValue(SelUserName, OT_USER, "IDENTIFICATION", 1,
      PropertyValue, &MoreSegments, &PropertyFlags))
   {
      wsprintf(SelUserFullName, "%s", (LPSTR) PropertyValue);
   }

   /* Create userinfo dialog box and change caption to the   */
   /* user's login name.                                     */

   hDlgUserInfo = CreateDialog(hInstMegaphone, "UserInfo", hDlgMegaphone, 0L);
   SetWindowText(hDlgUserInfo, SelUserName);

   /* Initialize the user info dialog box by changing */
   /* the values of different static text fields to   */
   /* reflect acquired user information               */

   wsprintf(Text, ": %s", (LPSTR) SelUserName);
   SetDlgItemText(hDlgUserInfo, IDC_USERNAME, (LPSTR) Text);

   wsprintf(Text, ": %d", SelUserConnectionNum);
   SetDlgItemText(hDlgUserInfo, IDC_STATION, (LPSTR) Text);

   wsprintf(Text, ": %s", (LPSTR) SelUserFullName);
   SetDlgItemText(hDlgUserInfo, IDC_FULLNAME, (LPSTR) Text);

   wsprintf(Text, ": %02d/%02d/%02d %02d:%02d:%02d",
      SelUserLoginTime[1], SelUserLoginTime[2], SelUserLoginTime[0],
      SelUserLoginTime[3], SelUserLoginTime[4], SelUserLoginTime[5]);
   SetDlgItemText(hDlgUserInfo, IDC_LOGINTIME, (LPSTR) Text);

   wsprintf(Text, ": %02X%02X%02X%02X",
      SelUserNetworkAddr[0], SelUserNetworkAddr[1],
      SelUserNetworkAddr[2], SelUserNetworkAddr[3]);
   SetDlgItemText(hDlgUserInfo, IDC_NETWORK, (LPSTR) Text);

   wsprintf(Text, ": %02X%02X%02X%02X%02X%02X",
      SelUserNodeAddr[0], SelUserNodeAddr[1], SelUserNodeAddr[2],
      SelUserNodeAddr[3], SelUserNodeAddr[4], SelUserNodeAddr[5]);
   SetDlgItemText(hDlgUserInfo, IDC_NODE, (LPSTR) Text);

   ShowWindow(hDlgUserInfo, SW_SHOWNORMAL);
}


/***************************************************************************

    FUNCTION: UserInfo

    PURPOSE : Processes messages for user info box

***************************************************************************/

long FAR PASCAL UserInfo(HWND hWnd, unsigned wMsg, WORD wParam, LONG lParam)
{
   switch(wMsg)
   {
      case WM_COMMAND :

         if(wParam == IDOK || wParam == IDCANCEL)
         {
            DestroyWindow(hWnd);
            return(0L);
         }
         break;

      case WM_CLOSE   :

         DestroyWindow(hWnd);
         return(0L);

      case WM_ACTIVATE      :

         hWndCurrent = (wParam == NULL) ? NULL : hWnd;
         break;

      default         :

         break;
   }
   return(DefDlgProc(hWnd, wMsg, wParam, lParam));
}





<a name="00af_000f">
<a name="00af_0010">
[LISTING THREE]
<a name="00af_0010">

#include "windows.h"
#include "megaphon.h"


Megaphone ICON MEGAPHON.ICO
Message   ICON MESSAGE.ICO
User      ICON USER.ICO


Megaphone MENU
BEGIN
   POPUP "&File"
   BEGIN
      MENUITEM "&Exit",      IDM_EXIT
      MENUITEM SEPARATOR
      MENUITEM "&About ...", IDM_ABOUT
   END
   MENUITEM "&Refresh!", IDM_REFRESH
END


Megaphone DIALOG 65, 75, 165, 139
CLASS     "Megaphone"
CAPTION   "Megaphone - NetWare Intercom"
STYLE     WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX
BEGIN
   CONTROL "Megaphone", -1, "static",
      SS_ICON | WS_CHILD, 24, 6, 0, 0
   CONTROL "&Users", IDC_USERLISTBOXTITLE, "static",
      SS_LEFTNOWORDWRAP | WS_CHILD, 65, 2, 95, 9
   CONTROL "", IDC_USERLISTBOX,    "listbox",
      LBS_STANDARD | LBS_EXTENDEDSEL | WS_VSCROLL | WS_TABSTOP |
      WS_CHILD,
      65, 12, 95, 71
   CONTROL "Ser&vers", IDC_SERVERCOMBOBOXTITLE, "static",
      SS_LEFTNOWORDWRAP | WS_CHILD,   5, 23, 55, 9
   CONTROL "", IDC_SERVERCOMBOBOX, "combobox",
      CBS_HASSTRINGS | CBS_SORT | CBS_DROPDOWNLIST |
      WS_VSCROLL | WS_TABSTOP | WS_CHILD,
      5, 33, 55, 63
   CONTROL "&Message", -1, "static", SS_LEFT | WS_CHILD,
      5, 80, 55, 9
   CONTROL "", IDC_MESSAGEEDITBOX, "edit",
      ES_LEFT | ES_AUTOHSCROLL | ES_UPPERCASE | WS_BORDER | WS_TABSTOP | WS_CHILD,
      5, 90, 155, 12
   CONTROL "&Send", IDC_SENDBUTTON, "button",
      BS_PUSHBUTTON | WS_TABSTOP | WS_CHILD,
      5, 110, 45, 15
   CONTROL "Se&ttings", IDC_SETTINGSBUTTON, "button",
      BS_PUSHBUTTON | WS_TABSTOP | WS_CHILD,
      60, 110, 45, 15
   CONTROL "&Exit", IDC_EXITBUTTON, "button",
      BS_PUSHBUTTON | WS_TABSTOP | WS_CHILD,
      115, 110, 45, 15
END


Message DIALOG 100, 100, 170, 55
CAPTION "Message"
CLASS   "Message"
STYLE   WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX
BEGIN
   CONTROL "",        IDC_REPLYEDITBOX, "edit",
      ES_LEFT | ES_AUTOHSCROLL | ES_UPPERCASE | WS_BORDER |
      WS_TABSTOP | WS_CHILD, 5, 10, 155, 12
   CONTROL "&Reply",  IDC_REPLYBUTTON,  "button",
      BS_PUSHBUTTON | WS_TABSTOP | WS_CHILD, 5, 30, 45, 15
   CONTROL "&Cancel", IDCANCEL,         "button",
      BS_PUSHBUTTON | WS_TABSTOP | WS_CHILD, 60, 30, 45, 15
   CONTROL "&Save",   IDC_SAVEBUTTON,    "button",
      BS_PUSHBUTTON | WS_TABSTOP | WS_CHILD, 115, 30, 45, 15
END


UserInfo DIALOG 68, 54, 145, 79
CAPTION  "User Information"
CLASS    "User"
STYLE    WS_POPUPWINDOW | WS_CAPTION | WS_MINIMIZEBOX
BEGIN
   CONTROL "User",       -1,           "static",
      SS_LEFTNOWORDWRAP | WS_CHILD, 42, 3, 18, 9
   CONTROL ":",          IDC_USERNAME,  "static",
      SS_LEFTNOWORDWRAP | WS_CHILD, 60, 3, 82, 9
   CONTROL "Stn",        -1,           "static",
      SS_LEFTNOWORDWRAP | WS_CHILD, 42, 12, 18, 9
   CONTROL ":",          IDC_STATION,   "static",
      SS_LEFTNOWORDWRAP | WS_CHILD, 60, 12, 77, 9
   CONTROL "Node",       -1,           "static",
      SS_LEFTNOWORDWRAP | WS_CHILD, 42, 21, 18, 9
   CONTROL ":",          IDC_NODE,      "static",
      SS_LEFTNOWORDWRAP | WS_CHILD, 60, 21, 77, 9
   CONTROL "Full Name",  -1,           "static",
      SS_LEFTNOWORDWRAP | WS_CHILD, 3, 30, 39, 9
   CONTROL ":",          IDC_FULLNAME,  "static",
      SS_LEFTNOWORDWRAP | WS_CHILD, 42, 30, 95, 9
   CONTROL "Login Time", -1,           "static",
      SS_LEFTNOWORDWRAP | WS_CHILD, 3, 39, 39, 9
   CONTROL ":",          IDC_LOGINTIME, "static",
      SS_LEFTNOWORDWRAP | WS_CHILD, 42, 39, 95, 9
   CONTROL "Network",    -1,           "static",
      SS_LEFTNOWORDWRAP | WS_CHILD, 3, 48, 39, 9
   CONTROL ":",          IDC_NETWORK,   "static",
      SS_LEFTNOWORDWRAP | WS_CHILD, 42, 48, 95, 9
   CONTROL "User",       -1,           "static",
      SS_ICON | WS_CHILD,           11, 6, 15, 15
   DEFPUSHBUTTON "OK", IDOK, 39, 60, 45, 15
END


Settings DIALOG 11, 21, 175, 65
CAPTION  "Settings"
STYLE    WS_POPUPWINDOW | WS_CAPTION
BEGIN
   CONTROL "&Incoming Messages", -1, "button",
      BS_GROUPBOX | WS_CHILD, 5, 5, 75, 35
   CONTROL "Accept", IDC_ACCEPTMESSAGES, "button",
      BS_AUTOCHECKBOX | WS_TABSTOP | WS_CHILD, 10, 15, 60, 10
   CONTROL "Iconize", IDC_ICONIZEMESSAGES, "button",
      BS_AUTOCHECKBOX | WS_TABSTOP | WS_CHILD, 10, 25, 60, 10
   CONTROL "&Users to scan", -1, "button",
      BS_GROUPBOX | WS_CHILD, 85, 5, 85, 35
   CONTROL "Attached users only", IDC_ONLYATTACHEDUSERS, "button",
      BS_AUTORADIOBUTTON | WS_CHILD, 90, 15, 75, 10
   CONTROL "Include unattached", IDC_ALLUSERSINBINDERY, "button",
      BS_AUTORADIOBUTTON | WS_CHILD, 90, 25, 75, 10
   CONTROL "&OK", IDOK, "button",
      BS_DEFPUSHBUTTON | WS_TABSTOP | WS_CHILD, 55, 45, 55, 15
END


About   DIALOG 22, 17, 110, 80
CAPTION "About"
STYLE   WS_POPUPWINDOW | WS_CAPTION
BEGIN
   CTEXT "Megaphone - NetWare Intercom" -1,  0,  5, 110,  8
   CTEXT "Version 1.0"                  -1,  0, 14, 110,  8
   CTEXT "by Mike Klein"                -1,  0, 22, 110,  8
   ICON  "User"                         -1, 13, 35,   0,  0
   ICON  "Megaphone"                    -1, 48, 35,   0,  0
   ICON  "Message"                      -1, 82, 35,   0,  0
   DEFPUSHBUTTON "OK"                 IDOK, 33, 60,  45, 15
END






<a name="00af_0011">
<a name="00af_0012">
[LISTING FOUR]
<a name="00af_0012">

; module-definition file for Megaphone -- used by LINK.EXE

NAME         Megaphone     ; application's module name
DESCRIPTION  'Megaphone - NetWare Intercom'
EXETYPE      WINDOWS      ; required for all Windows applications
STUB         'WINSTUB.EXE' ; Generates error message if application
            ; is run without Windows

;CODE can be moved in memory and discarded/reloaded
CODE  PRELOAD MOVEABLE DISCARDABLE

;DATA must be MULTIPLE if program can be invoked more than once
DATA  PRELOAD MOVEABLE MULTIPLE

HEAPSIZE     1024
STACKSIZE    5120      ; recommended minimum for Windows applications

; All functions that will be called by any Windows routine
; MUST be exported.

EXPORTS
   MainWndProc    @1
   About          @2
   UserInfo       @3
   MessageHandler @4
   Settings       @5






<a name="00af_0013">
<a name="00af_0014">
[LISTING FIVE]
<a name="00af_0014">

# Standard Windows make file.  The utility MAKE.EXE compares the
# creation date of the file to the left of the colon with the file(s)
# to the right of the colon.  If the file(s) on the right are newer
# then the file on the left, Make will execute all of the command lines
# following this line that are indented by at least one tab or space.
# Any valid MS-DOS command line may be used.

# This line allows NMAKE to work as well

all: megaphon.exe

# Update the resource if necessary

megaphon.res: megaphon.rc megaphon.h megaphon.ico
    rc -r megaphon.rc

# Update the object file if necessary

megaphon.obj: megaphon.c megaphon.h
    cl -W4 -c -AS -Gsw -Oad -Zp megaphon.c

# Update the executable file if necessary, and if so, add the resource back in.

megaphon.exe: megaphon.obj megaphon.def
    link /NOD megaphon,,, libw slibcew snit, megaphon.def
    rc megaphon.res

# If the .res file is new and the .exe file is not, update the resource.
# Note that the .rc file can be updated without having to either
# compile or link the file.

megaphon.exe: megaphon.res
    rc megaphon.res


Copyright © 1991, Dr. Dobb's Journal


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.