7. How do I do...

Now this is the stage, where you should be able to stand on your own two feet, however weak they might still be. What you need now, is some guidelines on where to go, or better how to be able to go where you want to go. In this chapter I will give you some clues how to do certain things that are useful for many applications.

You don't have to read this chapter in the given order. Instead you can look up a topic you want further information about and try to implement it into one of the example programs.

  • Common Dialogs
  • Setting control colours and using 3D controls
  • Customizing Dialogue Controls
  • Bitmaps
  • Printing
  • Sound
  • Getting more information
  • Previous chapter


    Common Dialogues

    Common Dialogues are a very important feature introduced with Windows 3.1 in order to standardise and simplify actions required most applications for both the users and the programmer. When Windows 3.0 started to get a really big success it soon became a problem that every application used a different methodology for similar things. The File Open dialogue is probably the best example for that. First it was not to easy to program although you'd need it in almost every program, and second users didn't find it too exciting facing a different interface every time. So Microsoft came up with the so called Common Dialogues which provide a consistent user interface for the most common tasks for every application. You can of course still do it all yourself, but you will find it a lot more convenient and easy to use them. The common dialogues reside in the dynamic-link library COMMDLG.DLL which provides the following functions:

    ChooseColor Opens a dialogue in which the user can create and select a colour. You will find this dialogue coming up e.g. if you choose to customise your desktop colours in the control panel.

    ChooseFont Allows to select a font and its type and size and colour properties. Optionally you get a preview of what a particular text with this font would look like.

    FindText This is a modeless dialogue for searching text in a document. This will of course like all other common dialogues only provide a dialogue box not the actual search in your document.

    ReplaceText The same as above but with the additional option to replace a particular text.

    GetOpenFileName This is the nicest, quickest and most user friendly way to create a dialogue box that allows the user to select a file to open. There are many options and you can even use your own dialogue template, but the functionality of the dialougue like diplaying and changing directories and drives will be completely handled for you.

    GetSaveFileName Ditto but for saving files.

    PrintDlg A dialogue to set up printer properties

    In order to common dialogues you first need to include the header file COMMDLG.H in your code file. You then call one of the above functions with the parameters described in the SDK help file. If you find the description given in there a bit too technical, here is a little example how to display a file open and file save dialogue.

    First define the maximum length of the filename in your main header file:

    
    #define MAXFILENAMELEN 80
    
    

    then define following function in one of your source modules. Change the member Flags as appropriate.

    
    BOOL DlgGetFileName(HWND hWnd,LPSTR lpszTitle,LPSTR lpszFormat,LPSTR lpszFileName,BOOL save)
    
    {  OPENFILENAME   of;
    
       int            result;
    
       // Initialize the OPENFILENAME members   of.lStructSize       = sizeof(OPENFILENAME);
    
       of.Flags             = OFN_HIDEREADONLY | OFN_NOCHANGEDIR | OFN_SHOWHELP;
    
       of.hwndOwner         = hWnd;
    
       of.hInstance         = hInstance;
    
       of.lpstrFile         = lpszFileName;
    
       of.lpstrFilter       = lpszFormat;
    
       of.nMaxFile          = MAXFILENAMELEN;
    
       of.lpstrInitialDir   = NULL;
    
       of.lpstrTitle        = lpszTitle;
    
       of.lpTemplateName    = 0;
    
       of.lpfnHook          = NULL;
    
       // Display the dialog   if (save) result=GetSaveFileName(&of);
    
            else result=GetOpenFileName(&of);
    
       return result;
    
    }
    
    

    In the module containing the window procedure of your main window you should then define a string containing the name and extension of your file format and allocate a buffer for the filename:

    
    static const char szFileFormat[]={ "My file\0*.MYF\0" };
    
    static char szFileName[MAXFILENAMELEN];
    
    

    And finally you need to call the function in your window or dialogue procedure with somewhat like:

    
    case WM_COMMAND:
    
         switch(wParam)
    
          {
    
           case IDM_OPEN:
    
                if (DlgGetFileName(hWnd,"Open a file",szFileFormat,szFileName,FALSE)) 
    
                  { int hfile;
    
                    hfile=_lopen(szFileName,OF_READ);
    
                    ...
    
                    ...
    
                  }
    
                break;
    
           case IDM_SAVE:
    
                if (DlgGetFileName(hWnd,"Save a file",szFileFormat,szFileName,TRUE)) 
    
                  { int hfile;
    
                    hfile=_lcreat(szFileName,0);
    
                    ...
    
                    ...
    
                  }
    
                break;
    
    

    If you need to extend the functionality of your dialogue e.g. in order to offer a preview of the file you can do that by providing your own dialogue template and/ or specifying a hook procedure. Find information about this in the SDK help file.


    Setting control colours and using 3D controls

    When you implement your own dialogues you will find, that they still look somewhat different from all those fancy ones you see in other programs. What is missing is just a nice grey background and a nice 3D look of all the controls. Surely your application doesn't gain any more functionality with 3D of radio buttons, check boxes, edit controls and the lot, but if you want users to accept your program you'd better go for the fancy ones. Windows 95 already automatically provides a 3D look for controls, all that is missing here is a nice grey background color (the default as you can see is white).

    Now if it just the background color that bothers you, then all you have to do is to process the WM_CTLCOLOR message. This message is sent from the control to the parent window (which is normally a dialogue) to allow different colours to be used. Unfortunately there is another difference between Win16 and Win32. Note, that it does not matter on which version of Windows you run the program only which you compile it for. In Win16 you only get the one message mentioned above and the lower word of lParam indicates what type of control sent the message. This can be a button, an edit control, a list box, a combo box, a static control or the dialogue itself. In Win32 there are six different messages, one for each control type.

    This is how you'd do it for Win16:

    
    case WM_CTLCOLOR:
    
         switch(HIWORD(lParam))
    
          {
    
             case CTLCOLOR_DLG:
    
             case CTLCOLOR_STATIC:
    
             case CTLCOLOR_BTN:
    
                  SetBkColor((HDC)wParam,RGB(192,192,192));
    
                  return GetStockObject(LTGRAY_BRUSH);
    
          }
    
         return FALSE; // use the defaults
    
    

    and the equivialent for Win32:

    
    case WM_CTLCOLORDLG:
    
    case WM_CTLCOLORSTATIC:
    
    case WM_CTLCOLORBTN:
    
         SetBkColor((HDC)wParam,RGB(192,192,192));
    
         return GetStockObject(LTGRAY_BRUSH);
    
    

    In both cases you set the background color for text in the display context given in wParam using SetBkColor and return a handle to a brush. In this case I have obtained the handle to one of the predefined brushes from a call to GetStockObject. This function can also be used for pens and fonts as you can see when looking it up in the SDK help file. However if you want another color you have to create a brush using CreateSolidBrush which you best put in the WM_INITDIALOG. The handle you're receiving must be static declared as static and you can then return it on any call to WM_CTLCOLOR. Don't forget to destroy it in the WM_DESTROY using DeleteObject.

    A disadvantage of this approach is, that you have to include it in every dialogue procedure and you won't get the nice 3D effect unless you're using Windows 95. Therefore you might rather go for using Microsoft's 3D control library. This library is called CTL3DV2.DLL and you'll probably have it already in your Windows system directory. If not, then you should have a look on your compiler CD or any other since it is installed by most programs and can be distributed royalty free. In order to use it, you have to add the file CTL3DV2.LIB to your project just like if you'd add another source code module. This will make the DLL load automatically when your program is started.

    All you've got to do now, is to include the CTL3D.H file

    
      #include <clt3d.h>
    
    

    and add the following lines to the WinMain

    
      Ctl3dRegister(hInstance);
    
      Ctl3dAutoSubclass(hInstance);
    
    

    This should be done prior to anything else. You then enter your dialogue box or message loop as usual and afterwards, just before you leave the WinMain you call

    
      Ctl3dUnregister(hInstance);
    
    

    Now all your dialogues will have a grey background, nice looking dialogue boxes and 3D controls. If you are interested in more controls like tree views, multi column list boxes, spin controls etc. you might want to look at the COMMCTRL.DLL. Information about it can be found on the Microsoft Developer Network Library.


    Customizing Dialogue Controls

    In the dialogue example I have shown you how to use standard controls to input and output data. Standard controls like buttons and list boxes are handy because they make programming quick and easy. But how capable and flexible are they?

    First of all, you can use standard controls not just in dialogue boxes but also in your "normal" windows. This for example creates a button inside the window specified in hWnd:

    
    CreateWindow("BUTTON","&Delete",BS_PUSHBUTTON | WS_CHILD | WS_VISIBLE,10,10,50,20,hWnd,IDC_DELETE,hInstance,NULL);
    
    

    Fair enough, but how about displaying other type of data like graphics, images or both mixed with text. And how about if you need a control with a different behaviour?

    There are three techniques which all allow you to extend the functionality of your dialogues and controls:

    1. Using "Owner Draw" controls. Buttons, list boxes and combo boxes have a property called "owner draw" which you can set, if you wish to perform the output yourself. This is helpful, if the nature of the control you need matches one of those controls and you only want a different representation. This allows you e.g. to create buttons with images on, list boxes with different colour list items or combo boxes with items consisting of an icon and a text string.
    2. "Subclassing" a control. This technique that can be used with any window intercepts the window procedure and allows you to filter window messages. This is useful if the extension to an existing control/ window your aiming for is not in the display but rather the type of interaction. So you can e.g. use subclassing to allow drag and drop of elements from and to a list box.
    3. Creating your own control. This is useful if you want to do something completely different like e.g. a dice control. Here you've got to do all yourself and you can define your own style properties, messages and notification messages. To create your own control you have to register a window class first and define the window procedure for this class. Then simply create a window derived from this class. You can also use your own classes in dialogue templates of course.

    Owner draw controls

    Owner draw controls send you a WM_DRAWITEM message. The lParam of this messages contains a pointer to a DRAWITEMSTRUCT structure that contains the display context, the rectangle of the item in the display context and a pointer to the item data. Here is an example for an owner draw list box that displays a list of icons:

    First create a list box inside your dialogue end set its properties of "owner draw" and "has strings". The resource statement of the list box in the .RC file should looks something like:

    
    CONTROL "", IDC_ICONLIST, "LISTBOX", LBS_NOTIFY | LBS_OWNERDRAWFIXED | LBS_HASSTRINGS | WS_CHILD | WS_VISIBLE | WS_BORDER | WS_VSCROLL, 30, 127, 111, 55
    
    

    Icons have a width and height of 32 pixels. In the WM_INITDIALOG message we first set the height of the list box items to 32:

    
    SendDlgItemMessage(hDlg,IDC_ICONLIST,LB_SETITEMHEIGHT,0,MAKELPARAM(32,0));
    
    

    Now you can add the items. Each requires text that we add with LB_ADDSTRING and and icon handle. This handle is set by sending a LB_SETITEMDATA message.

    
    i=SendDlgItemMessage(hDlg,IDC_ICONLIST,LB_ADDSTRING,0,(LPARAM)(LPSTR)"myIcon");
    
    SendDlgItemMessage(hDlg,IDC_ICONLIST,LB_SETITEMDATA,i,(LPARAM)hIcon));
    
    

    Now you have to handle the WM_DRAWITEM message in the dialogue procedure:

    
    case WM_DRAWITEM:
    
         { LPDRAWITEMSTRUCT lpdis=(LPDRAWITEMSTRUCT)lParam;
    
           HBRUSH  hOrgBrush;
    
           int     iBkColor,iTxtColor;
    
           RECT    rcText;
    
           char    szText[50];
    
           if (((int)lpdis->itemID)<0) break;
    
           // Determine the colors
    
           if (lpdis->itemState & ODS_SELECTED) 
    
             { iBkColor=COLOR_HIGHLIGHT;iTxtColor=COLOR_HIGHLIGHTTEXT; }
    
           else
    
             { iBkColor=COLOR_WINDOW;iTxtColor=COLOR_WINDOWTEXT; }
    
           // Erase the background
    
           hOrgBrush=SelectObject(lpdis->hDC,CreateSolidBrush(GetSysColor(iBkColor)));
    
           PatBlt(lpdis->hDC,1,lpdis->rcItem.top,lpdis->rcItem.right-2,lpdis->rcItem.bottom-lpdis->rcItem.top,PATCOPY);
    
           DeleteObject(SelectObject(lpdis->hDC,hOrgBrush));
    
           // Draw the icon
    
           DrawIcon(lpdis->hDC,1,lpdis->rcItem.top,(HICON)lpdis->itemData);
    
           // Draw the text
    
           rcText=lpdis->rcItem;
    
           rcText.left=36;
    
           SendMessage(lpdis->hwndItem,LB_GETTEXT,lpdis->itemID,(LPARAM)(LPSTR)szText);
    
           SetBkMode(lpdis->hDC,TRANSPARENT);
    
           SetTextColor(lpdis->hDC,GetSysColor(iTxtColor));
    
           DrawText(lpdis->hDC,szText,lstrlen(szText),&rcText,DT_SINGLELINE|DT_VCENTER);
    
           // Draw the Focus rectangle
    
           if (lpdis->itemState & ODS_FOCUS)
    
               DrawFocusRect(lpdis->hDC,&lpdis->rcItem);
    
         }
    
         break;
    
    

    Subclassing a control

    To understand what subclassing can do, consider a list box that outputs a list of tasks that can only be processed in a sequential order. You indicate the current task by highlighting the corresponding list item (using LB_SETCURSEL). What you now don't want is that the user can select any other item. To prevent the user from doing this, you can disable the list box, but unfortunately this will also disable the list box's scrollbar and all items are greyed.

    A solution for this is to subclass the window procedure for the list box and filter out all mouse and keyboard messages. All other messages must be processed as normal so we need to pass them on to the original list box procedure.

    
    static FARPROC lpOrgListboxProc;
    
    LRESULT FAR PASCAL ListSubclassProc(HWND hWnd,UINT msg,WPARAM wParam,LPARAM lParam)
    
    {
    
      switch(msg)
    
       {
    
         case WM_LBUTTONDOWN:
    
         case WM_MOUSEMOVE:
    
         case WM_LBUTTONUP:
    
         case WM_CHAR:
    
              // do nothing          break;
    
         default:
    
              // process as normal          return CallWindowProc(lpOrgListboxProc,hWnd,msg,wParam,lParam);
    
       }
    
      return 0L;
    
    }
    
    

    Now you can subclass the control in the WM_INITDIALOG procedure by storing the original window procedure in lpOrgListboxProc and setting the window procedure for the list box control to your own procedure.

    
    lpOrgListboxProc=(FARPROC)GetWindowLong(GetDlgItem(hWnd,IDC_TASKLIST),GWL_WNDPROC);
    
    SetWindowLong(GetDlgItem(hWnd,IDC_TASKLIST),GWL_WNDPROC,ListSubclassProc);
    
    

    Bitmaps

    Bitmaps are images consisting of n times m pixels (picture elements). You can create and modify bitmaps (stored in .BMP files, which is the standard bitmap format for Windows) with Paintbrush, MS Paint or any other pixel orientated drawing program. The easiest way to use a bitmap image in your application is to include it as a resource. The resource file code for a bitmap is:

    
    ID_IMAGE BITMAP "myimage.bmp"
    
    

    where ID_IMAGE it the name or numeric identifier of the bitmap and "myimage.bmp" is the name of the file containing the bitmap.You can then load it in your program with the command LoadBitmap which gives you a handle to the bitmap (type HBITMAP). e.g.

    
    hImage=LoadBitmap(hInstance,MAKEINTRESOURCE(ID_IMAGE));
    
    

    The only problem you have now, is to bring the bitmap on the screen. This requires a little bit of preparation. First you need a memory display context. Let me just briefly explain what that is. A memory display context is basically the same as a window but instead of mapping output to the screen all drawing goes to memory. Where the output area of a "normal" display context is the client area of a window which is constrained by the window size and the colour depth (bits required to display the maximum number of colours) of the current display mode, the output area of a memory display context is constrained by the width, height and colour depth of the bitmap selected into it. Therefore a memory display context needs a bitmap just as a window needs the screen.

    The reason why we need a memory display context is that we cannot select a bitmap directly into a window display context, but we can copy data from one display context into another, provided that they are compatible. The following code creates a memory display context that is compatible with the display context of the screen:

    
    hMemDC=CreateCompatibleDC(NULL);
    
    

    The value of NULL always creates a display context that is compatible with the screen. Optionally you can give it the handle of your window's display context, but it won't make any difference. If you use bitmaps more than once I recommend that you create a memory display context at the beginning in the WinMain and make the handle global. It is always useful to have a memory display context around.

    Next let's see what we can do with that. Suppose your bitmap is 300 times 200 pixels big and you want to display it at position 0,0 of your window then your WM_PAINT message handling could look somewhat like this:

    
    case WM_PAINT:
    
         { PAINTSTRUCT ps;
    
           HBITMAP hOrgBitmap;
    
           HDC hdc;
    
    
    
           hdc=BeginPaint(hWnd,&ps);
    
            hOrgBitmap=SelectObject(hdc,hImage);
    
            BitBlt(hdc,0,0,300,200,hMemDC,0,0,SRCCOPY);
    
            SelectObject(hdc,hOrgBitmap);
    
           EndPaint(hWnd,&ps);
    
         }
    
         break;
    
    

    The function which does the job is BitBlt which stands for Bit Block Transfer. It copies an area from a source display context, which is the memory display context in this case, into a destination display context, which is our window in this case. This works also the other way round of course and you can also copy from one memory context into another or from window context into another. Before you use a BitBlt with a memory display context however, you have to select a bitmap into it, which you do with SelectObject. As with all other GDI objects (pens, brushes, fonts and palettes) you have to remember the original object and restore the display context if you do not need it any more.

    Instead of BitBlt you can also use StretchBlt which stretches or compresses the bitmap from its original size into the destination rectangle.

    There is now only one thing to remember: Cleaning up afterwards. We have allocated two resources, a bitmap and a display context, which you have to delete before terminating the program. Therefore we use

    
    DeleteObject(hImage);
    
    

    to delete the bitmap and

    
    DeleteDC(hMemDC);
    
    

    to delete the memory display context.

    This was just scratching the surface of bitmaps. There is so much to say and know about bitmaps that I could easily write another tutorial of this size just about it. The problems begin, when you start dealing with Device Independent Bitmaps (DIBs) and Color Palettes. But if you need to know more about that, you really need a book.


    Printing

    Have you ever tried to print out a nice graphic (with your own program of course) in a DOS or UNIX program? It not, then I can tell you that it takes ages (and a lot of reading in the printer manual) to get a reasonable result on your own printer and is virtually impossible to get it right for every printer. If you aim for WYSIWYG (What You See Is What You Get) or even just a proper print preview, you just have to forget it.

    Is Windows any better than? Well it is, and not just a bit. Printing anything under Windows is really trivial and you can not just use almost any font, font size and font style you like but also print out all kind of graphics including bitmaps without any difficulties.

    Suppose you've got a function called PaintAppWindow which you normally call in the WM_PAINT branch of your window procedure like:

    
    case WM_PAINT:
    
         { PAINTSTRUCT ps;
    
           HDC hdc;
    
           hdc=BeginPaint(hWnd,&ps);
    
            PaintAppWindow(hdc);
    
           EndPaint(hWnd,&ps);
    
         }
    
         break;
    
    

    No matter what this function does, you can have exactly the same output on your printer just by calling it with a printer display context instead of a screen display context. Hence the only thing you need to do is something like the following function, which reads the name, driver file name and port number of the current printer (the one you've specified as the standard printer in the control panel) and then creates a display context for that printer:

    
    PrintWindow(void)
    
    { char sDevice[160];
    
      char sName[64],sDriver[80],sPort[16];
    
      HDC hPrinterDC;
    
      GetProfileString("WINDOWS", "DEVICE", "", sDevice,sizeof(sDevice));
    
      sscanf(sDevice, "%64[^,],%80[^,],%16[^,]", sName, sDriver, sPort);
    
      hPrinterDC=CreateDC(sDriver,sName,sPort,NULL);
    
    PaintAppWindow(hPrinterDC);
    
      DeleteDC(hPrinterDC);
    
    }
    
    

    Not too bad, is it? OK, may be I have exaggerated a bit. This works, but in reality you'll have to do a bit more to get a WYSIWYG result. For example to get the equivalent font size or a font you're using for the screen you have to do the following:

    
    int GetFontSize(HDC hPrinterDC,int nScreenHeight)
    
    { POINT pt;
    
      pt.x=0;
    
      pt.y=-MulDiv(nScreenHeight,GetDeviceCaps(hPrinterDC,LOGPIXELSY),72);
    
      DPtoLP(hPrinterDC,&pt,1); 
    
      return pt.y;
    
    }
    
    

    Use the return value now to fill the lfHeight property of a LOGFONT structure and create a font for printing using CreateFontIndirect, select it into your display context using SelectObject and off you go.

    To retrieve the properties of the printer display context you can call GetDeviceCaps as in the example above. To determine the width and height of a printer page in pixels for example you can call GetDeviceCaps with the handle of the printer display context and either HORZSIZE or VERTSIZE.

    Finally you may want to provide a printer configuration dialogue with which the user can set up the printer. Just make use of the common dialogues for that and call PrintDlg.


    Sound

    To play sound you can easily make use of the Multimedia Control Interface (MCI). Information about this can be found in the Multimedia Reference help file (WIN31MWH.HLP). The easiest way is to call SndPlaySound to play back a .WAV file (digital sound). This is how you do it:

    
    #include <mmsystem.h>
    
    ....
    
    sndPlaySound("C:\\WINDOWS\\DING.WAV",SND_SYNC);
    
    ....
    
    

    Midi files (extension .MID) are a bit more difficult to play back. Basically what you have to do is to call mciSendCommand to open a MCI device and then send commands to this device to play them back. There is lots more you can do with mciSendCommand like recording and playing back WAV files as well.


    Getting more information

    Windows programming is a never-ending field of learning you'll probably never be able to know everything about it (I would not dare to say I do). Now getting a book is not a bad idea, but to get all the information you could possibly want you'd need more of a library than a single book. Most books I have ever seen therefore cover either the basics of Windows programming or a very special topic. And every few month there are some new Software Development Kits coming out which allow you to do even more with Windows. Take the Video for Windows Development Kit (VFWDK) for example. With around 12 MB of hard disk space it is certainly one of the smaller SDKs but it gives you all the tools, include files, libraries and sample application to create, manipulate and play back AVI files(Audio Video Interleaved, the Microsoft format for Video files).

    Fortunately there is something that covers everything, and it is even small enough to fit in your room: Microsoft System Development Library. This is a CD full of information about everything you ever wanted to know about Windows including OLE, Visual Basic and Visual Basic for Applications Programming, VBX and OCX Development and all features of Windows 95 and Windows NT. It also contains the complete book "Programming Windows 3.1" by Charles Petzold and all articles ever published in the Microsoft System Journal, a monthly developer magazine, which is also very good source of information. And if you're really interested in what's brewing, you can pop in at Dr. GUI's Espresso Stand which is virtually on the CD (but remember he's not a real doctor!).

    To obtain this CD which is updated every three months you've got to subscribe to the Microsoft Developer Network (MSDN). You will then not just get this CD but also all SDK and operating system software you need. To get information about the MSDN write to or e-mail the:

    Microsoft Developer Network
    One Microsoft Way
    Redmond, WA 98052-6399
    USA

    Fax: (206) 936-7329, Attn: Developer Network
    Internet: msdn@microsoft.com


    The Windows Programming Tutorial

    created by:

    Rainer Döbele

    for Bristol University, February 1996