CS-360 Fall, 2000 Class 3 R. Eckert WINDOWS RESOURCES, TEXT AND GRAPHICS (DRAWING ON A WINDOW) THE RESOURCE SCRIPT (.RC FILE)-- The WINAPP2 example program produces a menu bar with "Rectangle", "Circle", "Clear Screen", and "Quit" items and has a program icon that appears on the task bar when the application is minimized. Recall that resources are static data that the linker combines with our compiled application. Resources are defined in a script (.rc) file that is compiled by a resource compiler into a .res file. The WINAPP2.RC resource script file must have a header file included (which gives values to named constants that identify the menu items) and then the description of the menu and the icon. The most straightforward way of setting up the resource script file is to use a text editor: /* WINAPP2.RC resource file */ #include "resource.h" MYICON ICON myicon.ico MYMENU MENU BEGIN MENUITEM "&Circle", IDM_CIRCLE MENUITEM "&Rectangle", IDM_RECTANGLE MENUITEM "Clear &Screen", IDM_CLEAR MENUITEM "&Quit", IDM_QUIT END In this resource script there is an icon identified by "MYICON". The keyword ICON specifies that the following file name (here myicon.ico) is that of the file that describes the actual icon. In addition there is a menu identified by "MYMENU" which has the menu items: "Circle", "Rectangle", "Clear Screen", and "Quit". Notice that each menu item is identified by constant name: IDM_CIRCLE, IDM_RECTANGLE, IDM_CLEAR, and IDM_QUIT. These identifiers have been defined in the resource.h header file whose contents are shown here: #define IDM_CIRCLE 40006 #define IDM_RECTANGLE 40007 #define IDM_CLEAR 40008 #define IDM_QUIT 40009 In the .CPP source program file, the menu items are referred to by these constant names. But they need to have ID numbers so the program can determine which item was selected. The use of constant names makes the program more readable and less prone to using the wrong ID value. The values and names can be anything. The resource.h header file must also be included in the .CPP source program file so that the same ID values are known both to the C++ compiler and to the resource compiler. In the resource script's "MYMENU", the keyword MENUITEM specifies that the following text in quotes will appear on the window's main menu bar. -The '&' means that the following character, when used in conjunction with ALT from keyboard and 'cooked', will generate a message indicating that the item has been selected. The character following the '&' will be underlined on the window's menu bar. The resource script file can also be prepared visually using Microsoft's Developer Studio resource editors. If done in this way the various id numbers will be assigned automatically. This is especially useful when many resources (in addition to a menu and an icon) are to be incorporated into the application. We'll see how to do this later. In the WINAPP2.CPP program, the lpszMenuName field of the wndclass structure is set to "MYMENU"--the menu described in the resource file-- when the window class is registered. This menu will appear when any window based on this window class is created. As we'll see, the WndProc() must made to respond to user interaction with menu items. The key idea is the following: -Whenever a menu item is selected with the mouse or its keyboard alternative, Windows sends the application a WM_COMMAND message; -The low-order word of the wParam contains the ID number of the menu item selected. This can be extracted by using the macro LOWORD(). -We can do a switch/case on LOWORD(wParam) to perform the correct action. The hIcon member of the wndclass is to be set to the icon identified by the name "MYICON"--the icon described in the resource file--when the window class is registered. To do that, we make use of the Win32 function LoadIcon(hInstance,"MYICON"). The first parameter, the instance handle, identifies the instance of the application containing the resource information (the present one), and the second parameter identifies which resource it is. Before examining the details of the WINAPP2.CPP program's WndProc(), we need to see how graphics and text output work in Windows. TEXT AND GRAPHICS OUTPUT (Displaying something in a window) Under DOS--character modes have fixed sizes and fonts. -This is fast, but has limited display possibilities. DOS systems have video boards that support several graphics modes, but: -Drawing commands may depend on graphics mode. -Each program that does graphics has to have different logic for each graphics mode which means lots of duplicate code. -There can be program portability problems. Windows--always runs in a graphics mode. -It is slower, but more flexible. -Everything (including text) is drawn one pixel at a time, so: -Any size or shape is possible. THE DEVICE CONTEXT AND DEVICE INDEPENDENCE-- An important design goal of Windows was "Device Independence." This means that: -The same program should work using different hardware without modification. -Windows takes care of hardware interface. -The programmer can concentrate on the program. How is device independence achieved-- -Windows programs don't send data directly to hardware devices (screen, printer). -They use the "Graphics Device Interface" (GDI) as an intermediary. -They draw on an abstract surface called a "device context" (DC). The following figure illustrates the relationship between the hardware device, the Graphics Device Interface, and the Device Context.The Device Context-- -Is associated with a physical device: (display screen, printer, bitmap, metafile). -Abstracts the physical device it represents. -Commands to draw on a DC are the same regardless of Hardware (video card, printer). -The GDI translates these to hardware commands to output to the physical device. -The DC is accessed with a "handle to a DC". -It must be "gotten" from Windows using GetDC() or BeginPaint(). -It is specific to a given window so the application can only draw in its client area. -The DC specifies attribute settings for drawing (e.g., colors). -Attribute settings specify HOW drawing primitives will look e.g., background color, text color, etc. -An advantage: when drawing, we don't have to send a whole bunch of parameters. -A DC contains drawing objects (pen, brush, bitmap, font, etc.) These also determine how primitives look (pen--> line-drawing color). -Common DCs come from a Windows cache of limited size! A program should release a DC when it's done using it, or disastrous things could happen! -DCs are released with ReleaseDC() or EndPaint(). The GDI: Graphics Device Interface-- -The part of Windows that converts drawing function calls to hardware commands. -It is located in the file GDI.EXE (a DLL). -It has many graphics functions: -Functions that draw Graphics/Text primitives. -Functions to change the values of attributes (settings) in a DC. -It has functions that create drawing "objects" (pens, brushes, fonts, etc.). -It is shared by all running applications, so a GDI error by one program can crash Windows. -The GDI may make use of a device driver program (.DRV) for certain devices. -.DRV programs assist the GDI in converting graphics commands to hardware commands. -These must be provided by the manufacturer of new devices (graphics cards, etc.). SOME GDI ATTRIBUTE SETTINGS AND OBJECTS-- ATTRIBUTE DEFAULT FUNCTION ------------------------------------------------------------------------------- Background color white SetBkColor() color of empty spaces Background mode OPAQUE SetBkMode() or TRANSPARENT Brush Origin (0,0) SetBrushOrigin() pattern origin Clipping Region whole surf. SelectClipRgn() where output displayed Current Position (0,0) MoveToEx() Start coordinate of line Drawing Mode R2COPYPEN SetROP2() How to combine with background Mapping Mode MM_TEXT SetMapMode() units/scaling in DC Polygon Fill Mode ALTERNATE SetPolyFillMode() how polygons filled Text Char Spacing 0 SetTextCharacterExtra() Text Color Black SetTextColor() color of text OBJECT DEFAULT FUNCTION ------------------------------------------------------------------------ Bitmap none SelectObject() image object Brush WHITE_BRUSH SelectObject() area fill object Font SYSTEM_FONT SelectObject() text font object Pen BLACK_PEN SelectObject() line-drawing object Color Palette DEFAULT_PALETTE SelectPalette() color palette Graphics objects may be thought of as being similar to an artist's tools. They must be created and brought into ("selected into") a device context before they can be used. In this analogy the device context should be thought of as the artist's portfolio in which he carries his canvasas and other tools. The following figure illustrates the process:
Drawing basics (graphics and text): Steps your program must perform-- 1. Obtain a DC. 2. Obtain (Create) and select GDI objects, if required (e.g., a pen). 3. Set drawing attributes, if necessary. 4. Call drawing functions. 5. Deselect and Delete any GDI objects that were created. 6. Release the DC -- in same state in which it was obtained. hDC = GetDC(hWnd); -- -Retrieves a handle to ad device context for the client area of the specified window. -The device context is identified by the value returned in hDC. -This will be used for all subsequent drawing on that DC. -GetDC() allows the application to draw on the DC at any time. Another function that gets a DC: BeginPaint(hWnd, &ps); -- -This is only used in response to a WM_PAINT message-- which is issued when some area of the window has been exposed and thus needs to be repainted. -It returns a handle to a device context for the client area of the specified window. -And a pointer to a paint structure (PAINTSTRUCT). -This contains information about the area of the window that needs to be repainted: typedef struct tagPAINTSTRUCT { HDC hdc; /* device context handle */ BOOL fErase; /* should background be redrawn? TRUE/FALSE */ RECT rcPaint; /* rectangular area to update */ BOOL fRestore; /* reserved for internal use by Windows */ BOOL fIncUpdate; /* reserved */ BYTE rgbReserved[16]; /* reserved */ } PAINTSTRUCT; ReleaseDC (hWnd, hDC); -- -Each DC takes about 1000 bytes of memory; so we can only have a limited number of common DCs in memory at a time. -You must release the DC as soon as you've done your drawing. -If not, bad things can happen AFTER the application has been running a while. -It is used in conjunction with GetDC(). -EndPaint() does the equivalent for a DC obtained with BeginPaint(). WINDOWS RGB COLOR MODEL-- Windows uses four-byte numbers to represent colors. The simplest scheme is direct color in which a color is specified by giving a COLORREF value: typedef DWORD COLORREF; ------------------------------------------------------------ | 0 | Blue (0-255) | Green (0-255) | Red (0-255) | ------------------------------------------------------------ If the most Significant Byte is zero: It means that direct RGB color is being used. The other bytes specify Red, Green, and Blue intensities. Dithering is done for colors that don't match the system colors. If the most Significant Byte is not zero: The other bytes specify an index into a color lookup table (palette). The RGB values are found at that entry in the lookup table. This is indirect color. The macro RGB() allows us to specify Red, Green Blue intensities. It generates a COLORREF value (which can be used in color-setting functions) e.g.-- COLORREF cr; cr = RGB (0,0,255); /* blue */ The definition of the macro RGB(): #define RBG(r,g,b) ((DWORD)(((BYTE)(r) | ((WORD)(g) << 8)) | (((DWORD)(BYTE(b)) << 16))) Example use in a program-- -SetTextColor(hDC,RGB(255,0,0)) // changes the text-drawing color to red. -SetBkColor(hDC,RGB(0,0,255)) changes the color of the background around each character to blue. GDI Objects--pens, brushes, bitmaps, fonts -Used to "draw" or "paint" on a device context. -Created with functions like: CreatePen (PS_STYLE, nWidth, cColRef); CreateSolidBrush (cColRef); -There are lots more (see on-line help). -To be used they must be "selected into" the DC with SelectObject(): HGDIOBJ SelectObject(HDC, HGDIOBJ); HGDIOBJ--handle to a GDI object. Parameter--a handle to the new object being selected into the DC. Value returned--a handle to the object being displaced from the DC. When selected, the data associated with the object is made available to the DC for immediate use with graphics drawing functions: Some GDI drawing functions-- [In each of the following (x1,y1), (x2,y2) are the coordinates of diagonally opposite corners of the bounding rectangle] Arc (hDC,x1,y1,x2,y2,xArcStart,yArcStart,xArcEnd,yArcEnd); [draws a portion of an ellipse with the current pen] Chord (hDC,x1,y1,x2,y2,xArcStart,yArcStart,xArcEnd,yArcEnd); [paints a filled portion of an ellipse bounded by ellipse border & line using the current brush to fill the area, current pen for border] Ellipse (hDc,x1,y1,x2,y2); [paints an ellipse with the current brush] MovetoEx (hDC,x1,y1,lpPoint); [establish position of start of line] LineTo (hDC,x1,y1); [draws a line from start point to the specified point with current pen] Pie (hDC,x1,y1,x2,y2,xArcStart,yArcStart,xArcEnd,yArcEnd); [paints a filled area of ellipse bounded by ellipse arc and a wedge using the current brush for area, current pen for border] Polygon (hDC,points_array,nCount); [paints a closed polygon using the current brush and pen] Polyline (hDC,points_array,nCount); [draws a series of one or more connected lines with the current pen] Rectangle (hDC,x1,y1,x2,y2); [paints a rectangle with the current brush and pen] SetPixel (hDC,x1,y1,colref); [Turns the pixel (point) at x1,y1 to the color specified by colref] There are many more drawing primitives (see the on-line help). When an object is selected, Windows allocates space for the object in the program's data segment; this spaced is limited, so a program should delete the object when it's done drawing with it--use DeleteObject(). If you don't, they will use up memory after the program that created them has terminated! If there are too many undeleted objects, Windows could crash! But to delete an object-- -first, the object has to be selected out of the DC. -(trying to delete an object still selected into a DC could crash the program). To do this, select the original object back into the DC-- -This displaces former object. -Then the former object can be deleted. A typical sequence with objects: HPEN hOldPen, hNewPen; HDC hDC; hDC = GetDC(hWnd); /* Get a device context to draw on */ hNewPen = CreatePen(PS_SOLID, 3, RGB(0,0,99)); /* Create pen to draw with */ hOldPen = (HPEN)SelectObject(hDC, hNewPen); /* Select new pen into DC ...*/ /* & save handle to old one */ /* DO SOME DRAWING STUFF WITH THE PEN HERE */ SelectObject(hDC,hOldPen); /* Displace New Pen from DC */ /* Now we can delete it */ DeleteObject(hNewPen); /* Delete the new pen */ ReleaseDC(hWnd,hDC); /* Get rid of device context */ Stock objects--predefined in Windows; obtain one with GetStockObject(); GetStockObject() retrieves a handle to a predefined stock pen/brush/font-- -Stock objects are maintained by Windows. -They should not be deleted! Example-- SelectObject (hDc, GetStockObject(BLACK_PEN)); Stock Object Type Choices ----------------------------------------------------- Pen BLACK_PEN, WHITE_PEN, NULL_PEN Brush DKGRAY_BRUSH, GRAY_BRUSH, BLACK_BRUSH, LTGRAY_BRUSH, NULL_BRUSH, WHITE_BRUSH Font ANSI_FIXED_FONT, ANSI_VAR_FONT, DEVICE_DEFAULT_FONT, OEM_FIXED_FONT, SYSTEM_FIXED_FONT, SYSTEM_FONT ANSI_FIXED_FONT--useful for tables with characters that need to line up. ANSI_VAR_FONT--smallest of the stock fonts (good for limited space). An object can be displaced out of the DC for deletion by selecting a stock object into the DC; i.e. replace the last 3 lines of code above with: SelectObect(hDC, GetStockObject(BLACK_PEN)); DeleteObject(hNewPen); ReleaseDC(hWnd, hDC); THE WINAPP2.CPP APPLICATION MESSAGE PROCESSING: Details of WndProc()-- Our WINAPP2.CPP program code overrides the default window procedure action for WM_COMMAND, WM_LBUTTONDOWN, WM_RBUTTONDOWN, WM_CHAR, and WM_DESTROY messages. WM_COMMAND (menu item clicked)-- LOWORD(wParam)==IDM_CIRCLE (User clicked on "Circle" menu item) ==> Get a device context, create a blue pen for the outline and a crosshatched magenta brush for the interior of the circle, select these objects into the device context, and call the Ellipse() function to draw the circle. After that, displace the pen and brush from the device context, delete them, and release the device context. LOWORD(wParam)==IDM_RECTANGLE (user clicked on "Rectangle" menu item) ==> Get a CD, create a red pen and a solid cyan brush, select them into the DC, call the Rectangle() function, select the objects out of the DC, delete them, and release the DC. LOWORD(wParam)==IDM_CLEAR (User clicked on "Clear Screen" menu item) ==> call InvalidateRect() which causes Windows to send a WM_PAINT message. This message means that the client area needs to be repainted. (It is also generated any time the window is exposed.) The Default Window Procedure responds to this message by repainting the client area with the class background brush, effectively erasing the window's client area. LOWORD(wParam)==IDM_QUIT (User clicked on "Quit") ==> program calls DestroyWindow(), which causes Windows to destroy the window. WM_LBUTTONDOWN (Left mouse button pressed) ==> -Get x,y coordinates of cursor from lParam with LOWORD() & HIWORD() macros; -Get a device context to draw on; -Output the letter "L" at (x,y) on the device context with TextOut(); -Release the device context. WM_RBUTTONDOWN (Right mouse button pressed) ==> -Same as above, but outputs the letter "R". WM_CHAR (A key or key combination was pressed that corresponds to an ANSI character) ==> -Get a device context to write on; -copy the character from the wParam into the string cBuf; -Output it to the upper left hand corner of the window's client area with TextOut(); -Release the device context. WM_DESTROY (User double clicked on System menu, close button, or hit Alt-F4) ==> -Post a WM_QUIT message to the application's queue -This will cause the program to exit the event loop and return to Windows as described in earlier notes. USING MICROSOFT DEVELOPER STUDIO TO CREATE A VISUAL C++ APPLICATION-- The following is a recipe for preparing the WINAPP2 application visually-- 1. Get into Developer Studio, open a New Workspace, and create a Win32 application. ("File | New | Projects tab | Win32 Application") 2. Open a new C++ file and type in the program or copy and paste it from clipboard: ("File | New | Files tab | C++ Source"); make sure "Add to Project" is checked and enter a name (e.g., winapp2). Type or paste in the resulting Edit window. Be sure to have the statement: #include "resource.h" which will include the resource constants that are to be defined by Developer Studio. (This replaces the statement #include "winapp2.h" in the manual version.) 3. Create the .rc file using: "File | New | Files tab | Resource Script" Give it the name winapp2.rc; make sure "Add to Project" is checked. 4. From the main menu select "Insert | Resource | Icon | New" This will bring up the icon editor. Draw the icon you want. When done, hit enter and, in the Icon Properties Dialog Box, type the ID: "MYICON" (MUST BE IN QUOTES) and the .ico file name you want to give it. 5. Select "Insert | Resource | Menu | New" This will bring up the menu editor. Double click in the dotted rectangle on the gray menu bar. In the resulting "Menu Item Properties" box, remove the Pop-up check mark and enter the ID: IDM_CIRCLE and the Caption &Circle. Do the same for the &Rectangle, Clear &Screen, and &Quit menu items. The IDs should be: IDM_RECTANGLE, IDM_CLEAR, and IDM_QUIT. 6. After minimizing the Menu Editor box, you will see that the menu name is IDR_MENU1. Right click on that and click on "Properties" in the resulting dialog box. This will bring up a "Menu Properties" box". Change the ID: to "MYMENU" (Again it must be in quotes.) 7. Build the project. If you haven't made any mistakes, your application should be ready to go. You might want to take a look at what Developer Studio has done for you. Examine the resource.h file and the winapp2.rc file. (Be sure to open the latter as a text file ("File | Open" and in the resulting dialog box, choose winapp2.rc, change the Open As: "Auto" to "Text", and click "Open"). Don't be alarmed at all the extra junk that Developer Studio has put in this file. As you read this, the process of creating an application visually may seem to be very complex. But once you get used to the visual way of doing things, you will find that it becomes very easy and intuitive! MANUALLY SETTING UP ICON AND MENU RESOURCES FOR YOUR APP: A SOMETIMES QUICK AND DIRTY ALTERNATIVE-- -Use Developer Studio's Icon Editor to create and save the icon (.ico) file. -Use any text editor to create and save the .h file with the constant definitions; e.g., enter lines like #define IDM_CIRCLE 40006. -Use a text editor to create and save the .rc file with ICON and MENU definitions. -Use a text editor to create the .cpp (or .c) source file. Both these files should #include the .h file. -Open a New Workspace and create a Win32 Application. -Copy the first four files into the resulting Workspace directory. -Add the .rc and .cpp (or .c) files to the project. ("Project | Add to Project | Files"). -Build the project. This is a sometimes fast way of doing things, but only useful for applications that have very few resources. For most projects you'll want to develop your resources visually using the various resource editors. Then Developer Studio will automatically incorporate them into your project.