Site hosted by Angelfire.com: Build your free website today!

Home
DelphiZeus
6. Brushes and Pens
Basic DC Drawing Functions

Home



The Windows Graphical User Interface presents oportunities to inhance the visual appearance of your Program by drawing on it's Device Context. This requires that you know how create and use Brushes and Pens. In this BrushPen application you will create several different types of pens and brushes, draw with them, and see how to change the way they are created and painted on a DC. There will also be demonstrations of many of the Painting methods which use Pens and Brushes.Using Point arrays in functions like Polygon( ), filled and outlined polygons are drawn. With the function FrameRect( ), a brush is used like a pen. Bitmaps are loaded from file and created to be painted on with pens and brushes. For visual display a Splash Screen is created using a "Popup" window, and using a system Timer, then "HELLO" is drawn on it. All windows backgrounds are filled (painted) by the brush assigned to them, when this background needs to be painted a WM_ERASEBKGND message is sent to the window.
Many New things presented in this BrushPens Program
There is alot of the basic graphical drawing operations presented in this one program. Too much to absorb at one time, you should read through this web page and then use one section like Pens, a Graphical Object, to practice writting code. By now you should be able to create your own program with a button or two, you can then copy some of the pen creation code from the Brush Pens code below. You would assign a Procedure to one of your button clicks like procedure PenDraw; and then copy some Pen Drawing functions like MoveToEx( ) and LineTo( ) in that procedure and see what parameters you need to change in the pen creation and the SetROP2( ) to get various drawing effects. Then add another procedure like procedure DrawPolyGon;   to another button click and copy the code for a point array and Polygon( ) function into that procedure (from procedure DrawIt;). Then change the point array (number of points, position of points) to draw the polygon you want, so that you will begin to understand how it works. Then add a WM_PAINT message and copy some code into it, and add the brush creation code, changing the parameres to find out what difference it will make.

Using the API drawing methods will depend on you changing and experimenting, until
you have working knowledge of the many options availible.



API COLOR, COLORREF, TColorRef
To use pens brushes and many HDC drawing operations, you will need to be familar with the API Color defintion, called COLORREF (TColorRef in Pascal), which is just a 4-byte Cardinal variable. However for Color reference, this Cardinal value is used as three separate Byte values in the Cardinal memory block, for the Red, Green and Blue colors. The low-order byte of the Cardinal memory block, contains a value for the relative intensity of red; the second byte contains a value for green; and the third byte contains a value for blue. The high-order byte should be zero, for all operations in this lesson. This is because your computers monitor's pixel color is defined as a combination of 3 colors, Red, Green and Blue. The Windows OS, defines a color as a combination of three primary colors Red, Green, and Blue. Windows defines a color by giving it a color value (sometimes called an RGB triplet), which consists of three Byte (8-bit,  0 to 255) values specifying the intensities of its color components. Black has the minimum intensity for red, green, and blue, so the color value for black is (Red=0, Green=0, Blue=0). White has the maximum intensity for red, green, and blue, so its color value is (Red=255, Green=255, Blue=255). . . To express a color value you could use a decimal number like 65280 (Green) or 16711680 (Blue), but this is confusing, to try and set colors. Colors are expressed in a Hexadecimal notation using the Pascal "$" for a Hex value, the $0000FF would be a Red, a $00FF00 would be a Green. and $FF0000 would be a Blue, notice that the "Byte" position for each separate color is Two number positions in the Hex number representation. So to set a color to a "Yellow" (Red and Green) value you would use $00FFFF, the first "00" after the $, will set the "Blue" byte, the next "FF" will set the "Green" byte, and the last "FF" will set the "Red" byte.
There is an API function called "RGB( )"
function RGB(r, g, b: Byte): COLORREF;
which will return a COLORREF (Cardinal) value, when you supply the three separate Red, Green and Blue color byte values.
There are three API functions to get the separate Red, Green and Blue values from a COLORREF-Cardinal variable
function GetRValue(rgb: Cardinal): Byte;
function GetGValue(rgb: Cardianl): Byte;
function GetBValue(rgb: Cardinal): Byte;
In my code references, I will use the variable "Type" Cardinal, instead of using the variable "Type" COLORREF or TColorRef, which is the same as a Cardinal "Type". I find the TColorRef desigation to be misleading, although it is meant to tell you that this is a "Color" variable, but it is NOT a separate "Type", it's just a Cardinal "Type". If you want some variable type in your code to help show you that it is a color reference, then change my "Cardinal" type to the "TColorRef" type.

  NOTE - In the newer windows systems - Windows 2000 and newer - There are graphical functions that use the High-Order byte in a 4 byte COLORREF (Cardinal), sometimes called the Alpha byte. I will not use this "Alpha Byte" here or refer to it in any way.


Pens, a Graphical Object
Like Fonts, Pens are graphic objects, so you need to Create and Delete pens you use for drawing on a Device Context. A Pen is a graphics tool that a program uses to draw lines and curves. Delphi VCL uses the TPen to wrap this graphic object. There are two types of pen: cosmetic and geometric. A cosmetic pen is used for quickly drawing lines one pixel wide. A geometric pen is used when you need lines that are wider than a single pixel, lines with unique end and join styles, or scalable lines. A pen can be created so it will draw a non-solid line with dots or dashes. There are 3 Stock Object pens, BLACK_PEN, WHITE_PEN, and NULL_PEN, a Device Context default pen is the Stock Object Black Pen.
To create a pen you can use CreatePen( ) function -
function CreatePen(
         Style: Integer;  // Pen Style
         Width: Integer; // Pen Width
         Color: Cardinal // Pen Color
         ): HPEN; // Handle of new Pen
or use CreatePenIndirect( ) with the TLogPen record.
function CreatePenIndirect(const LogPen: TLogPen): HPEN;

  PLogPen = ^TLogPen;
  tagLOGPEN = packed record
    lopnStyle: Cardinal;
    lopnWidth: TPoint;
    lopnColor: Cardinal;
  end;
  TLogPen = tagLOGPEN;
A geometric pen creation function is the ExtCreatePen( )
function ExtCreatePen(
         PenStyle,  // Pen Style
         Width: Cardinal;  // Pen Width
         const Brush: TLogBrush; // a record for brush attributes
         StyleCount: Cardinal; // length of custom Style array
         Style: Pointer // optional array of custom style bits
         ): HPEN;  // Handle of new Pen


Pen Creation Paramters
Here is some information for the Pen Creation parameters, these can be applied to the pen creation functions above, but mostly deal with the CreatePen( ) function

Style parameter
There are eight predefined pen styles. The Dash and Dot styles are only availible on cosmetic pens that are One pixel wide.
  1. PS_SOLID     - makes a solid line
  2. PS_DASH     - makes a line with dashes
  3. PS_DOT       - makes a line with dots
  4. PS_DASHDOT   - makes a line with dashes and dots
  5. PS_DASHDOTDOT   - line with a dash followed bt 2 dots
  6. PS_NULL      - no line is drawn
  7. PS_INSIDEFRAME   - forces line draw inside shape
  8. PS_USERSTYLE   - drawing defined by user.   This style can only be used in the ExtCreatePen( ) function
Width parameter
You set the pen width here, a pen width of One will create a cosmetic pen, any width greater than one will make a geometric pen.
Color parameter
Use a Cardinal (TColorRef) here, which is expressed in Hexadecimal Blue, Green, Red values. Blue would be $FF0000, Green would be $00FF00, and Red would be $0000FF.

Background Colors
If you create and draw with a Dash or Dot pen, you would expext the line (dash or dot) color to be the pen color, and it is, but what is the color used between the dots and dashes? ? Like drawing with Fonts, the "Background" color for the Device Context is used. If you use

OldColor := SetBkColor(TempDC, $0000FF);

then the spaces between the dots and dashes will be Red. You can also set the background Mode to Transparent, if you don't want the spaces to be filled.

SetBkMode(TempDC, TRANSPARENT);

The HDC Background color is also used to fill the background of patterned brushes. It may take some adjustment to keep in mind that the "Font" drawing properties covered in the previous lesson are really DC drawing properties, and ALSO used for the Pen and Brush drawing.


Brushes
Like Fonts, Brushes are graphic objects, so you need to Create and Delete brushes you use for drawing on a Device Context. A Brush is a graphics tool that you use to paint (fill) the interior of shapes or areas. You might use brushes to paint shapes, to fill the interior of a rectangle or ellipise, or paint the sections of pie charts and the bars in bar graphs. There are two types of brushes: logical and physical. A logical brush is a description (parameter values) of a bitmap that an application would use to paint shapes. A physical brush is the actual 8x8 bitmap that a device driver creates based on an application's logical-brush parameters. Much like logical fonts and physical fonts. There are 4 functions used to create a brush.
CreateSolidBrush($FF0000)
is used for Solid brushes,
CreateHatchBrush(HS_HORIZONTAL, $FF00FF)
is used for Hatch Brushes. There are six hatch styles you can use, listed below
  1. HS_HORIZONTAL   - makes a horizontal hatch
  2. HS_VERTICAL     - makes a vertical hatch
  3. HS_FDIAGONAL   - makes a forward diagonal hatch
  4. HS_BDIAGONAL   - makes a backward diagonal hatch
  5. HS_CROSS       - makes a cross hatch
  6. HS_DIAGCROSS  - makes a diagonal cross hatch
The background of a hatch Brush is filled with the HDC background color, which can be changed with SetBkColor( ). This is easy to forget when you first start using API brushes. If the HDC background mode is TRANSPARENT then no color will fill the Hatch background.

To use a bitmap for a brush call this function.
CreatePatternBrush(hBitmap1)
is used for bitmap pattern Brushes. If you use
CreateBrushIndirect(BrushLog1);
you can create all 3 types of Brushes (see API Help "Brushes"). A pattern brush uses an 8x8 pixel bitmap that you assign to the brush, the bitmap can be larger than 8x8 but not smaller. Since bitmaps are used to make Patterned Brushes, It's time for begining bitmap info.

Bitmaps for Brushes
Bitmaps begin to get complex because of the different number of bits per pixel (1bit, 4bit, 8bit, 16bit, 24bit, 32bit) that a bitmap can use and their palettes and color planes. Also there are Device Independent Bitmaps (DIB) and Device Dependent Bitmaps (DDB). I will not try to explain these here in the BrushPens program, I will try give enough info to use bitmaps in pattern brushes and use the pixel transfer function, BitBlt( ) to draw the bitmap on a hDC. The first Patterned brush created in BrushPens is Brush4, it uses a bitmap loaded from this program's resource using
Bitmap2 := LoadBitmap(hInstance,'BRUSH');
Bitmap2 is the Handle of the bitmap returned from the function, this Handle is NOT a hDC (device context), and has no hDC Properties. You can not draw on it, or use hDC pixel transfer functions on this bitmap until you assign a memory hDC to it, more on using bitmaps later. . . You can use this bitmap handle to make a pattern brush. With BrushLog1, you set the BrushLog1.lbStyle to BS_PATTERN and the BrushLog1.lbHatch to this bitmap Handle, then call CreateBrushIndirect(BrushLog1).
BrushLog1.lbStyle := BS_PATTERN;
BrushLog1.lbHatch := Bitmap2;
Brush4 := CreateBrushIndirect(BrushLog1);
DeleteObject(Bitmap2);
Brush4 does not use or reference Bitmap2 after the brush is created, since the Bitmap2 handle is not needed any more, this object is deleted. This is the most common way to create pattern brushes, with a resource bitmap. With Brush5 we create a bitmap then set it's pixels and assign it as a pattern bitmap.

Bitmap Pixel by Pixel
Look at "function MakeBrush5: THandle;". If you are creating a bitmap that will only be used in your program then a Device Dependent Bitmap is the easiest method. CreateCompatibleBitmap( ) sets up a device dependent bitmap to match (bits per pixel, color palette) the hDC you give as a parameter. Since MakeBrush5 is called before we create our hMainForm window, there is no window we can get a hDC for yet. If you use
TempDC := GetDC(0);
it returns the hDC of the virtual Desktop (the main or base hDC, the Screen). We then use TempDC for our compatable hDC calls.
TempDC := GetDC(0);
Bitmap2 := CreateCompatibleBitmap(TempDC,8,8);
BmpDC := CreateCompatibleDC(TempDC);
ReleaseDC(GetDesktopWindow,TempDC);
SelectObject(BmpDC,Bitmap2);
CreateCompatibleBitmap returns a handle for a 8x8 bitmap with the same color properties (bits per pixel, and palette) as the desktop. Since we only use 2 colors (black and white), we are not concerned about palettes or bits per pixel. CreateCompatibleDC(TempDC); returns a "Memory" device context. A memory device context has a "Display Surface" that exists only in memory (the bitmap array of pixels). This newly created "BmpDC" has only one white pixel and is not useable like this. When SelectObject(BmpDC,Bitmap2); is called, the BmpDC will have the same width, height, and color pixel organization as Bitmap2. This selection process is a reverse of the Font, Pen and Brush object selection we have seen before. Instead of the Bitmap being Selected into the hDC, , , the hDC is Assigned to the Bitmap. Now drawing on BmpDC will change the pixels of Bitmap2. Now we can use BmpDC in
Dot := 0;
for i:= 0 to 7 do
  begin
    for k:= 0 to 7 do
    if ((k+Dot) mod 2) <> 0 then
    SetPixelV(BmpDC,i,k,$00000000)
    else
    SetPixelV(BmpDC,i,k,$00FFFFFF);
    Inc(Dot);
  end;
SetPixelV( ) will set the color of a single pixel in BmpDC. Using "if ((k+Dot) mod 2) <> 0" will make alternating pixels black and white. This black then white then black pixel bitmap is set into the pattern Brush5. This brush will be used for special brush effects.

Look at "function StripeBrush(Clr1, Clr2: DWORD; Horz: Boolean): THandle;", It creates a bitmap like in MakeBrush5 but has a different for loop to make stripes.
for i:= 0 to 7 do
  begin
    for k := 0 to 7 do
    case i of
    1,2,5,6: if Horz then
             SetPixelV(BmpDC, i, k, Clr1) else
             SetPixelV(BmpDC, k, i, Clr1);
    else
    if Horz then
      SetPixelV(BmpDC, i, k, Clr2) else
      SetPixelV(BmpDC, k, i, Clr2);
    end;
  end;
The for loop will make a vertical or horizontal stripe bitmap with the 2 colors in Clr1 and Clr2. This for loop goes through all 64 pixels in the 8x8 bitmap, so you can create a custom pattern brush at runtime.

Using Pens and Brushes
We will need a display surface to see what is drawn, TempDC := GetDC(hMainForm); will get the DC of our MainForm. If one of the basic drawing functions like Rectangle( ) or Ellipse( ) is used then the Current Brush and Pen are used to draw it. The default brush is white and the default pen is black.
OldBrush := SelectObject(TempDC, Brush1);
OldPen := SelectObject(TempDC, Pen1);
will change the current brush or pen.

  - Drawing with Pens
Look at   procedure PenDraw;   in the BrushPens Program below. In this PenDraw procedure several lines are drawn with 3 diferent Pens, and SetROP2( ) is used to change how the pens are blended with the background color, see the "PatBlt dwRop codes" chart below for the Boolean operations of the fnDrawMode paramater. SetROP2 also changes how the brush is blended in Pen and Brush fuctions like Rectangle( ) and Polygon( ). If you call
SetROP2(TempDC, R2_NOT);
and draw a line with any color pen, the line is drawn by inverting destination pixel's color, and if you draw it a second time the line color is inverted back to the original color. Look at the PatBlt dwRop codes chart below. I can not try and explain the boolean operations and the result you will see on screen except for R2_BLACK (black pixel), R2_WHITE (white pixel), R2_NOP (no change), and R2_COPYPEN (pen color). The other operations depend on the color of the destination hDC, the color of the Pen or both, and a boolean operation. If you have some understanding or experience with RGB colors, you should experiment with the different raster blending operations to see if they might be useful to you.
There are several multi-point drawing functions like PolyLine( ), PolyPolyline( ), or Polygon( ), To use these you will have to set up a Point Array and give the position (point) for each pen line drawn. (see the code in "procedure DrawIt;" and "procedure PaintIt;" below).

  - Drawing with Brushes
FillRect( ) can be used to draw a Rectangle that is filled with a brush you give as a parameter. You can use System Color brushes like this -
FillRect(TempDC, Rect1, COLOR_ACTIVECAPTION+1);
The PatBlt( ) function fills a Rect with the current brush, but unlike Rectangle( ) it does Not use a Pen to outline the rectangle, and it has a raster operation code, which can be useful to get background color mixing or inversion. PatBlt(TempDC, 50, 70, 66, 28, PATCOPY); will copy the current brush onto the TempDC as a rectangle. Notice that the dwRop parameter is PATCOPY. Only 5 dwRop codes are listed in the Win32 API Help. But there are others.
PatBlt dwRop codes, D is Destination, P is Pattern
PatBlt CODE

$000042
$0500A9
$0A0329
$0F0001
$500325
$550009
$5A0049
$5F00E9
$A000C9
$A50065
$AA0029
$AF0229
$F00021
$F50225
$FA0089
$FF0062
BOOLEAN

False
not (P or D)
not P and D
not P
P and not D
not D
P xor D
not (P and D)
P and D
not (P xor D)
D
not P or D
P
P and not D
P or D
True
NAME

BLACKNESS




DSTINVERT
PATINVERT





PATCOPY


WHITENESS
SetROP2 equivalent

R2_BLACK
R2_NOTMERGEPEN
R2_MASKNOTPEN
R2_NOTCOPYPEN
R2_MASKPENNOT
R2_NOT
R2_XORPEN
R2_NOTMASKPEN
R2_MASKPEN
R2_NOTXORPEN
R2_NOP
R2_MERGENOTPEN
R2_COPYPEN
R2_MERGEPENNOT
R2_MERGEPEN
R2_WHITE

Like the SetROP2( ) function, PatBlt( ) can set a a mixing mode for combining the destination hDC color with the brush color. In Brush5 there are alternating black and white pixels. And this brush is used in some PatBlt operations for a "darken" effect and a "lighten" effect. See "procedure PaintIt;" and the WM_ERASEBKGND message in MessageProc for some examples.

                       


Using Windows System Timer
A system Timer is a Windows routine that repeatedly measures a specified interval, in milliseconds. Every time this interval elapses, the system sends a WM_TIMER message to the window associated with the timer. In the Delphi VCL it has a TTimer wrapper. You can start a timer by calling the SetTimer( ) function, with the timer interval (uElapse parameter) as an Integer value ranging from 1 to 4,294,967,295 milliseconds (about 50 days). This timer uses the timer logic in the PC's hardware and ROM Bios, which initializes a timer chip to generate a hardware interupt called the "Clock Tick" or "Timer Tick". This "Clock Tick" interupt is generated every 54.925 milliseconds or about 18.2 times a second. The Windows System Timer uses this interupt for it's intervals, so SetTimer( ) has the same resolution of 54.925 milliseconds, That's right, about 55 milliseconds, so no matter what you set the milliseconds to in the uElapse parameter, it will be "Rounded" down to the nearest 54.925 millisecond interval, which means that the SetTime( ) timing will NOT change untill you change the uElapse parameter to the next value divisible by 54.935. Also the WM_TIMER message is given a low priority and most other messages will be processed before the WM_TIMER message. So if you use
SetTimer(hForm1,1,200, nil);
you will NOT get a timer interval of 200 milliseconds, it will be about 164.775 milliseconds. But even this interval will be inconsistant and change with the CPU usage and the number of messages in your threads message queue and the time it takes to process those messages. Even moving the mouse will change when the WM_TIMER message is processed since the Timer message is usually placed at the bottom of of the message queue. This interval value is only approximate and can be very inaccurate.
Using SetTimer( ) is a convient way to get timed events if you want them for intervals of more than 200 milliseconds. For intervals below 200 milliseconds, the interval accuracy of the System Timer is so poor that is not a good choise. With a System Timer you can Not get an interval below 55 miliseconds. (The more recent windows systems like XP have updated the system timer so it does not use the old "Clock Tick", so it is more accurate and can do intervals below 55 milliseconds). I beleive the intention of the original system timer was for "People" time (seconds, minutes, hours, days) and not "Computer" time (milliseconds, microseconds).
You create a timer by using the SetTimer( ) function, more than one timer may be created, each with a different ID number. If you place a window handle in the call to SetTimer( ), then the timer sends WM_TIMER messages to that window when the timer interval has elapsed. To know which timer sent the message, the wParam parameter of a WM_TIMER message contains the ID number of the timer. A timer starts timing the interval as soon as it is created. A program can change a timer's interval value by using SetTimer( ) and can destroy a timer by using the KillTimer( ) function. Programs should destroy timers with KillTimer( ) as soon as they are not necessary. This system Timer was created to use the message queue to give it a "Safety" factor, so you never have to worry about your program's processing being Interupted by a Timer event, lengthy processing will continue until it is finished and then the messages from the message queue will be processed, just like mouse or keyboard messages. Also, the system allows only ONE WM_TIMER message per Timer ID, in the queue, so no matter how many timer intervals are used in your lengthy processing, there's only one timer message.

Here are the parameters for SetTimer( )
function SetTimer(
    hWnd: THandle,         // handle of window for timer messages
    nIDEvent: Cardinal,    // timer ID number
    uElapse: Cardinal,     // interval value
    lpTimerFunc: Pointer   // address of timer procedure
   ): Cardinal; // success return is Timer ID, fail is Zero
In this Brush Pen program the main window hMainForm is set as the hWnd in SetTimer( ) and timer messages are processed in the WM_TIMER message of MessageProc. See the code in WM_CREATE and WM_TIMER messages in MessageProc for examples. Sometimes if you have alot of timers and timer events it is easier to use a separate TimerProc( ) function away from your MessageProc. In the lpTimerFunc parameter of SetTimer( ) you can give the address of another function, and the WM_TIMER message will NOT be sent to the hWnd MessageProc. But this will not be demonstrated in the BrushPens Program. For information about this and the more advanced Mutimedia and Thread timers see the DelphiZeus page -
11. Using MuliMedia and Thread Timers


Owner Drawn Controls
Several types of controls can use a OWNERDRAW style in their CreateWindow fuction and be custom drawn. Static, Button, ListBox, ComboBox, Menu, Tab, and ListView controls can have an OWNERDRAW style. In the code of BrushPens we will create an "easy Owner Drawn" Static control with the handle   hOwnerDrawS -
hOwnerDrawS := CreateWindow('Static', '', 
     WS_VISIBLE or WS_CHILD or SS_OWNERDRAW, 
     14, 90, 100, 30, hMainForm,0, hInstance,nil);
This has the SS_OWNERDRAW style. This style tells the system not to do normal painting for this control, that you will do ALL of the drawing for this one. When the SS_OWNERDRAW style flag bit is set, the system will not paint this static control, instead the WM_DRAWITEM message is sent to the parent window to tell you when to paint this control, with information about how to paint it is in the LParam. Since Static controls do not get keyboard or mouse input, they are easy to paint, in the next lession we will do an Owner Draw button which will you have to paint according to the "State" (pushed, enabled) it is in. The LParam of the WM_DRAWITEM message is a Pointer to a   DRAWITEMSTRUCT, which is a record with these fields -
  PDrawItemStruct = ^TDrawItemStruct;
  tagDRAWITEMSTRUCT = packed record
    CtlType: Cardinal;
    CtlID: Cardinal;
    itemID: Cardinal;
    itemAction: Cardinal;
    itemState: Cardinal;
    hwndItem: HWND;
    hDC: HDC;
    rcItem: TRect;
    itemData: Cardinal;
  end;
  TDrawItemStruct = tagDRAWITEMSTRUCT;
  DRAWITEMSTRUCT = tagDRAWITEMSTRUCT;
Read the the Win32 API help for index   "DRAWITEMSTRUCT". Look at the code in MessageProc below, a variable DrawItem: PDrawItemStruct; is declared as a pointer, since the LParam will be a pointer to a TDrawItemStruct. Only three fields are used, "DrawItem.hwndItem", "DrawItem.rcItem", and "DrawItem.hDC" because we do not need to check on the state of this control in order to paint it. First we get the
DrawItem := Pointer(LParam);
then a brush and font are selected into the hDC, then a rectangle, a RoundRect and some text are drawn, the Result is set to 1 and we Exit so no system painting takes place. You should restore any graphic objects you select into this hDC. Since static controls do not get any user input (mouse clicks or keyboard), it is better to Not use a Static control at all and just do all of the drawing needed for this static control's visual look in the Main Form's WM_PAINT message (This saves on system resources and messaging also). I did it here only to introduce you to Owner Drawn controls, in the next lesson you will create an owner drawn button, which does get user input.


the Splash Screen, a Popup Window
In order to use the SetTimer( ) function, a Splash Screen is shown before the Main Form is shown. A System Timer WM_TIMER message causes letters to be painted on the Splash Screen one at a time. In the Fonts Program, only child windows (buttons) for the main Form were created, and child windows are in the client area of the parent window. What if you need a window to be outside of the client area of the main Form, like a Splash screen? In this Program we make a Popup Window to use as a splash screen, in the BrushPen program below, look at the function -
hSplash := CreateWindow(wClass.lpszClassName,
             '',WS_POPUP or WS_VISIBLE, 
             (GetSystemMetrics(SM_CXSCREEN) div 2)-270,
             (GetSystemMetrics(SM_CYSCREEN) div 2)-212, 
             300, 230,hMainForm, 0, hInstance, nil);
The WS_THICKFRAME, WS_CAPTION, and WS_SYSMENU are not used for this popup window, so it will not have a Caption or borders, just what we need for a Splash screen. The hMainForm is set as the Parent for this window, but since the WS_POPUP style is used it will NOT be places on the Client area of hMainForm. Notice that the window's Class parameter is wClass.lpszClassName, the same class used for our main Form window. This means that the messages for this popup window will be sent to the same MessageProc used by the main Form. If the same MessageProc is for both windows then how do we know which messages are for which window? The target window's handle is in the hWnd parameter of the message, we have ignored this parameter until now. If you look at the WM_ERASEBKGND and WM_PAINT messages you will see a
if hWnd = hMainForm then begin
to test the hWnd for the window's handle and determine the window the message is for.


WM_CTLCOLORSTATIC, Static control color message
Just before a static control is painted, a WM_CTLCOLORSTATIC message is sent to the parent window to allow you to set the text color and background color for that static control. The lParam has the Handle of the static control to be painted, so you must test this value to determine which static control is going to be affected by this message. The wParam is the HDC for that control, so you use the SetTextColor( ) function to change the color of the Text in this control, like this -
SetTextColor(wParam,$00FFFF);
You can also set the Text background color with - SetBkColor(wParam,$00FF0000);. To set the brush used to paint the static control's background, you send the brush's Handle back as the message Result, like this - Result := Brush1;. It is important NOT to call DefWindowProc( ) after you process this message, if you do call DefWindowProc( ), it will paint the default "Standard" colors as if you never set any of the color properies for this control. So you should Exit. See the code in the BrushPens program below.
Other types of controls have there own Pre-Paint messages, Edits have the WM_CTLCOLOREDIT, and Scroll Bars have the WM_CTLCOLORSCROLLBAR,


WM_ERASEBKGND, the Background painting message
Each window has a brush assigned to it to paint it's background. You can read the Win32 API Help under index "Window Background" for information about this. The background brush is set when you register your windows Class for your main window, the TWndClass record has a hbrBackground member, where you assign the handle of the brush you want the system to use to paint the background of your window. All controls have a brush assigned to them for background painting, a Button will have a different brush than an Edit. It is also posible to assign a "Null" brush (No brush) for the background brush, which will mean that the system does not do any background painting, this is not used very much unless you do all of the background painting yourself. The background painting is important for what it covers up "Erases", a window's pixels are not changed unless there is a drawing operation called to paint the pixels. If you assign a Null brush for your mMain window (wClass.hbrBackground := 0;) then nothing is erased on your window's background. If another window covers your main form and then your main form is activated and given the focus, the background will still have the pixel colors of the window that covered it. Try this by setting the the hbrBackground := 0; to see what happens.

In the BrushPens program code I draw a border around the main window with 2 brushes in the WM_ERASEBKGND message using the PatBlt( ) function. There are several more rectangles filled with various brushes using FillRect( ) and PatBlt( ) functions. It is Important to notice that DefWindowProc( ) is called BEFORE the other drawing operations, this will paint the window with the Class background brush. If called after your drawing it will erase what was drawn. With the Popup Splash Screen I just fill the Splash window background with Brush4. Notice that this function will Exit at the end of the WM_ERASEBKGND message, so DefWindowProc( ) will NOT be called and erase all the drawing.




BrushPens program


This BrushPens program shows how to use pens and brushes in some of the hDC drawing functions. There are some basic bitmap functions to show you some easy bitmap uses, but bitmaps will not be covered here. To create a Patterned Brush you need a bitmap. I use a Resourse bitmap in {$R BrushBmp.RES}. You need a 8x8 pixel bitmap in standard windows 16 colors (4 bit color depth) which you should name "brush.bmp". The .RC file will have this line in it
BRUSH BITMAP "brush.bmp"
and be named   "BrushBmp.RC"   Then compile this .rc file with brcc32.exe.

This also has an Owner Draw Static control to show that you must handle painting it, see WM_DRAWITEM: in fuction MessageProc. There is a new style of window created called WS_POPUP (see hSplash := CreateWindow below) which is used as a Splash screen. The WS_POPUP style is used for windows that are not child windows, and exist outside the main Form window as a separate window. To have a splash screen requires the use of a windows system Timer (see WM_CREATE: and WM_TIMER: in MessageProc). The background painting of our Forms is handled in the WM_ERASEBKGND: of MessageProc. If you do not call Result := DefWindowProc(hWnd,Msg,wParam,lParam); in the WM_ERASEBKGND: message, then the background is not painted with wClass.hbrBackground brush.

There's alot of code presented here, you may want to create your own test project and then add just two or three Buttons, and then create your own procedures for drawing with pens and brushes, using the code examples below to get examples for using functions like Polygon( ) or SetROP2( ), , Look in the procedure DrawIt; and get some code to experiment with to learn how these work. You might try the PatBlt( ) or the DrawIcon( ) or the DrawFocusRect( ) functions, , or whatever you want to try and learn about how to do that.

see comments in code for more info

program BrushPens;
{this program has examples for using Brushes,
Pens and bitmaps. More Drawing functions are shown}

uses
  Windows, Messages, SmallUtils;

{$R *.RES}
{$R BrushBmp.RES}

var
wClass: TWndClass;
hMainForm, hSplash, hExitBut, hDrawBut, hPaintBut, hOwnerDrawS,
hLabel1, hIcon1, hMakeBmpBut, hPenDrawBut: Integer;
mainMsg: TMSG;
Font1, Font2, Font3, Font4: Integer;
Bitmap1, Bitmap2: Integer;
Brush1, Brush2, Brush3, Brush4, Brush5, Brush6, Brush7: hBrush;
Pen1, Pen2: hPen;
{for Fonts Brushes and Pens, I do not use an h 
like hPen1, since I know all fonts, pens and brushes 
are Device Object Handles, not controls
You will deside which Handle variable name to use.
 hBrush, hPen hFont, ect, are all Cardinal types, but you can use
 them as Integer types, as the Bitmaps above}
FontLog1: TLogFont;
PenPoint: TPoint;
PenLog1: TLogPen;
BrushLog1: TLogBrush;
TempDC: HDC;
FormRect: TRect;
Size1: TSize;
DrawTimes: Integer;

const
Hello = 'HELLO';

procedure ShutDown;
begin
DeleteObject(Font1);
DeleteObject(Font2);
DeleteObject(Font3);
DeleteObject(Brush1);
DeleteObject(Brush2);
DeleteObject(Brush3);
DeleteObject(Brush4);
DeleteObject(Brush5);
DeleteObject(Brush6);
DeleteObject(Brush7);
DeleteObject(Pen1);
DeleteObject(Pen2);
DeleteObject(Bitmap1);
DeleteObject(Bitmap2);
{Be sure to DeleteObject for ALL
graphics objects you create}

PostQuitMessage(0);
end;

procedure DrawIt;
var
PntArray: Array[0..11] of TPoint;
DWordArray: Array[0..2] of Cardinal;
TempBrush: THandle;
begin
{DrawIt draws a framed Rectangle with a title'
and two 3D Cubes with Polygon using SetROP2 to
blend Brush5 with the background to lighten and darken}
TempDC := GetDC(hMainForm);
SelectObject(TempDC, Brush6);
{Brush6 is a stripe brush}
SelectObject(TempDC, Pen2);
Rectangle(TempDC,200,58,478,230);
{large Framed Rectangle}

SelectObject(TempDC, Brush1);
SelectObject(TempDC, Pen1);
Rectangle(TempDC,210,64,468,95);
{title rect}

{the next series of MoveToEx and LineTo
draws a "Hi"}
SelectObject(TempDC, Pen2);
MoveToEx(TempDC,220,68,nil);
LineTo(TempDC,220,91);
MoveToEx(TempDC,220,79,nil);
LineTo(TempDC,232,79);
LineTo(TempDC,232,68);
LineTo(TempDC,232,91);
MoveToEx(TempDC,239,91,nil);
LineTo(TempDC,239,80);
MoveToEx(TempDC,239,72,nil);
LineTo(TempDC,239,71);

SelectObject(TempDC, Font3);
SetTextColor(TempDC, $000000FF);
SetBkColor(TempDC, $00FF0000);
TextOut(TempDC,280,66,'DrawIt  here', 12);

SelectObject(TempDC, GetStockObject(BLACK_PEN));
TempBrush := CreateSolidBrush($000033FF);
{change the color of this TempBrush to see that the
Highlighted and darkened sides of the cube will also
change color}
SelectObject(TempDC,TempBrush);

{the next Polygon and PolyPolyline will draw 2
cubes}

{to use the multi Point drawing fuctions like
PolyLine, Polygon, PolyPolygon ect. you will need to
set where the lines are drawn in a PointArray}
PntArray[0].x := 278;
PntArray[0].y := 112;
  PntArray[1].x := 220;
  PntArray[1].y := 125;
PntArray[2].x := 220;
PntArray[2].y := 200;
  PntArray[3].x := 280;
  PntArray[3].y := 218;
PntArray[4].x := 336;
PntArray[4].y := 198;
  PntArray[5].x := 336;
  PntArray[5].y := 125;
Polygon(TempDC,PntArray, 6);
{Polygon( ) requires you to tell it how many points
to read from the Array, since 6 is used here.. PntArray
has more than 6 points, but only 6 will be read because 
the parameter is set to 6}

{PntArray[6].x := 220;
PntArray[6].y := 125;
Polygon(TempDC,PntArray[1], 6);}
{you can use PntArray[1] instead of PntArray to get
Polygon to start with the second Point}


SelectObject(TempDC,Brush5);
{the pixels in Brush5 alternate from black to white}
SetROP2(TempDC,R2_MERGEPEN);
{SetROP2 changes the way a fill brush and pen are blended
with the background, this will show only the white pixels
in Brush5 for a lighten effect}
PntArray[2].x := 280;
PntArray[2].y := 137;
  PntArray[3].x := 336;
  PntArray[3].y := 125;
Polygon(TempDC,PntArray, 4);
{Draws the Top of the cube}

PntArray[0].x := 280;
PntArray[0].y := 138;
  PntArray[1].x := 280;
  PntArray[1].y := 218;
PntArray[2].x := 336;
PntArray[2].y := 199;
SetROP2(TempDC,R2_MASKPEN);
{R2_MASKPEN will show only the black pixels in
Brush5 for a darken effect}
Polygon(TempDC,PntArray, 4);
{Draws the side of the cube}
{PatBlt(wParam, 159, Rect1.Top + 220, 40, Rect1.Bottom-40,PATCOPY);}
{SetBkColor(TempDC, $0066FFFF);
SetTextColor(TempDC, $00000000);}
{PatBlt(TempDC, 300, 60, 100, 130,$AF0229);}
{PatBlt(TempDC, 300, 60, 100, 130,$A000C9);}

{PolyPolyLine will have 3 separate draws,
First Draw in PolyPolyLine}
PntArray[0].x := 407;
PntArray[0].y := 112;
  PntArray[1].x := 347;
  PntArray[1].y := 125;
PntArray[2].x := 407;
PntArray[2].y := 137;
  PntArray[3].x := 463;
  PntArray[3].y := 125;
PntArray[4].x := 405;
PntArray[4].y := 112;

{Second Draw in PolyPolyLine}
  PntArray[5].x := 347;
  PntArray[5].y := 125;
PntArray[6].x := 347;
PntArray[6].y := 200;
  PntArray[7].x := 407;
  PntArray[7].y := 218;
PntArray[8].x := 407;
PntArray[8].y := 137;

{Third Draw in PolyPolyLine}
PntArray[9].x := 407;
PntArray[9].y := 218;
  PntArray[10].x := 463;
  PntArray[10].y := 200;
PntArray[11].x := 463;
PntArray[11].y := 125;

{the DWordArray has the number of points
used in each draw}
DWordArray[0] := 5;
DWordArray[1] := 4;
DWordArray[2] := 3;

SetROP2(TempDC,R2_COPYPEN);
{Reset background blend}
PolyPolyline(TempDC,PntArray,DWordArray,3);
SetROP2(TempDC,R2_XORPEN);
Polygon(TempDC,PntArray[5], 4);
{by using PntArray[5] you don't have to re-enter
the points for the side of the cube}
DeleteObject(TempBrush);
ReleaseDC(hMainForm,TempDC);
end;

procedure PaintIt;
var
PaintRect: TRect;
PntAry: Array[0..7] of TPoint;
TempBrush: THandle;
begin
SetRect(PaintRect,300,62,476,202);
TempDC := GetDC(hMainForm);
FillRect(TempDC, PaintRect, COLOR_ACTIVECAPTION+1);
{you can use a system color value +1 instead of a brush
for FillRect}


{the next operations draw a Pacman figure}
SelectObject(TempDC, Brush2);
SelectObject(TempDC, Pen2);
SetBkColor(TempDC, $0000EEEE);
Pie(TempDC,320,80,400,160,400,80,400,160);
{Pie will draw a Packman like figure}
SelectObject(TempDC, Brush1);
SelectObject(TempDC, Pen1);
Ellipse(TempDC,350,90,360,100);
{Ellipse draws Packman's eye}

Chord(TempDC,410,90,450,150,450,122,410,122);
PntAry[0].x := 410;
PntAry[0].y := 122;
PntAry[1].x := 450;
PntAry[1].y := 122;
PntAry[2].x := 443;
PntAry[2].y := 152;
PntAry[3].x := 435;
PntAry[3].y := 125;
PntAry[4].x := 427;
PntAry[4].y := 162;
PntAry[5].x := 419;
PntAry[5].y := 125;
PntAry[6].x := 414;
PntAry[6].y := 150;
PntAry[7].x := 410;
PntAry[7].y := 122;
Polygon(TempDC,PntAry,8);
SelectObject(TempDC, GetSysColorBrush(COLOR_MENUTEXT));
{GetSysColorBrush will return the sys brush of the sys color}
Ellipse(TempDC,416,96,426,106);
Ellipse(TempDC,430,96,440,106);
TempBrush := CreateSolidBrush($0000FF00);
SelectObject(TempDC, TempBrush);
PatBlt(TempDC, PaintRect.Left, PaintRect.Bottom - 22, 
       PaintRect.Right-PaintRect.Left, 22,PATCOPY);
{PatBlt fills a Rect with the current brush, unlike Rectangle( ) it 
does Not use a Pen, and it has a raster operation code, which can be useful}
SelectObject(TempDC, Brush5);
PatBlt(TempDC, PaintRect.Left, PaintRect.Bottom - 18,
       PaintRect.Right-PaintRect.Left, 8,PATINVERT);
{by using PATINVERT as the raster operation, 
you can do this PatBlt again and restore the pixels}

PatBlt(TempDC, PaintRect.Left, PaintRect.Bottom - 10,
       PaintRect.Right-PaintRect.Left, 10,$A000C9);
{by using $A000C9 as the raster operation, 
only the Black pixels of Brush5 are shown}
SetRect(PaintRect,310,72,466,170);
FrameRect(TempDC,PaintRect,Brush5);
{FrameRect uses a brush instaed of a pen to draw a Rectangle frame
which can give you more effects using Hatched or Pattern brushes}
InflateRect(PaintRect,3,3);
{OffsetRect(PaintRect,3,3);}
FrameRect(TempDC,PaintRect,Brush5);
InflateRect(PaintRect,3,3);
FrameRect(TempDC,PaintRect,Brush5);
ReleaseDC(hMainForm,TempDC);
DeleteObject(TempBrush);
end;

procedure MakeBmp;
  var
  BmpDC: HDC;
  Rect1: TRect;
  New: Boolean;
  ArPoints: array[0..7] of TPoint;
begin
{MakeBmp creates a bitmap and draws on it, then
paints it on the MainForm's DC}
New := False;
TempDC := GetDC(hMainForm);
BmpDC := CreateCompatibleDC(TempDC);
{DC's created with CreateCompatibleDC are memory
device contexts and have no pixels, an empty DC}
if Bitmap2 = 0 then
  begin
  {test to see if Bitmap2 exists}
  Bitmap2 := CreateCompatibleBitmap(TempDC,160,120);
  {bitmaps created with CreateCompatibleBitmap are
  device dependent, but since we are not saving it to disk,
   it will be OK. Creating a Device Independent Bitmap takes
   alot more code}
  New := True;
  end;
SelectObject(BmpDC,Bitmap2);
{when you select a bitmap into a memory DC, the memory DC
represents the pixels in the bitmap}

if New then
  begin
  {if the bitmap has just been created, draw on it}
  SetRect(Rect1,0,0,160,120);
  FillRect(BmpDC,Rect1,GetStockObject(LTGRAY_BRUSH));
  SelectObject(BmpDC,Brush2);
  SetBkColor(BmpDC, $0066EE00);
  Ellipse(BmpDC,2,2,156,116);
  SelectObject(BmpDC,Brush1);
  Rectangle(BmpDC,16,34,132,76);
  SelectObject(BmpDC,Font3);
  SetTextColor(BmpDC,$000000FF);
  SetBkColor(BmpDC,$0000FF00);
  TextOut(BmpDC,30,40,' Bitmap2 ',9);

  {these points draw an Arrow with Polygon( )}
  ArPoints[0].x := 2;
  ArPoints[0].y := 8;
    ArPoints[1].x := 14;
    ArPoints[1].y := 2;
  ArPoints[2].x := 14;
  ArPoints[2].y := 6;
    ArPoints[3].x := 20;
    ArPoints[3].y := 6;
  ArPoints[4].x := 20;
  ArPoints[4].y := 10;
    ArPoints[5].x := 14;
    ArPoints[5].y := 10;
  ArPoints[6].x := 14;
  ArPoints[6].y := 14;
    ArPoints[7].x := 2;
    ArPoints[7].y := 8;
  Polygon(BmpDC, ArPoints, 8);

  BitBlt(TempDC, 256, 108, 160, 120, BmpDC, 0, 0, SRCCOPY);
  {BitBlt paints the bitmap onto the Form}
  SetWindowText(hMakeBmpBut,'UpsideDown');
  end else
  if GetWindowStr(hMakeBmpBut) = 'UpsideDown' then
  begin
  StretchBlt(TempDC, 256, 228, 160, -120, BmpDC, 0, 0, 160, 120, SRCCOPY);
  {StretchBlt is used to draw a bitmap with different dimentions,
  to streach or srink it.
  by changing the destination top and bottom positions, the bitmap
  is drawn upside down, the source Rect can also be changed}
  SetWindowText(hMakeBmpBut,'Reverse');
  end else
  begin
  (TempDC, 256, 108, 160, 120, BmpDC, 160, 0, -160, 120, SRCCOPY);
  StretchBlt{by changing the source left and right positions, the bitmap
  is drawn reversed}
  SetWindowText(hMakeBmpBut,'UpsideDown');
  end;

DeleteDC(BmpDC);
ReleaseDC(hMainForm,TempDC);
end;

procedure PenDraw;
var
TempPen: THandle;
begin
{this shows how SetROP2 changes pen drawing display}
TempDC := GetDC(hMainForm);

SelectObject(TempDC, Pen1);
MoveToEx(TempDC,200,60,nil);
LineTo(TempDC,260,60);
SetROP2(TempDC,R2_MERGEPEN);
{each pen LineTo( ) is given a different ROP2 code}
MoveToEx(TempDC,200,70,nil);
LineTo(TempDC,260,70);
SetROP2(TempDC,R2_NOTMERGEPEN);
MoveToEx(TempDC,200,80,nil);
LineTo(TempDC,260,80);
SetROP2(TempDC,R2_MERGENOTPEN);
MoveToEx(TempDC,200,90,nil);
LineTo(TempDC,260,90);
SetROP2(TempDC,R2_NOTCOPYPEN);
MoveToEx(TempDC,200,100,nil);
LineTo(TempDC,260,100);
SetROP2(TempDC,R2_XORPEN);
MoveToEx(TempDC,200,110,nil);
LineTo(TempDC,260,110);

TempPen := CreatePen(PS_DOT, 1, $00000099);
SelectObject(TempDC, TempPen);
SetROP2(TempDC,R2_COPYPEN);
MoveToEx(TempDC,200,120,nil);
LineTo(TempDC,260,120);
SetBkColor(TempDC, $00FFAA99);
{for Dot and Dash pens SetBkColor changes color of
the background of the pen}
MoveToEx(TempDC,200,130,nil);
LineTo(TempDC,260,130);
SetROP2(TempDC,R2_MERGEPEN);
MoveToEx(TempDC,200,140,nil);
LineTo(TempDC,260,140);
SetROP2(TempDC,R2_XORPEN);
MoveToEx(TempDC,200,150,nil);
LineTo(TempDC,260,150);
SetROP2(TempDC,R2_NOTXORPEN);
MoveToEx(TempDC,200,160,nil);
LineTo(TempDC,260,160);
DeleteObject(TempPen);


SelectObject(TempDC, Pen2);
SetROP2(TempDC,R2_COPYPEN);
MoveToEx(TempDC,200,170,nil);
LineTo(TempDC,260,170);
SetROP2(TempDC,R2_MERGEPEN);
MoveToEx(TempDC,200,180,nil);
LineTo(TempDC,260,180);
SetROP2(TempDC,R2_MERGEPENNOT);
MoveToEx(TempDC,200,190,nil);
LineTo(TempDC,260,190);
SetROP2(TempDC,R2_MASKNOTPEN);
MoveToEx(TempDC,200,200,nil);
LineTo(TempDC,260,200);
SetROP2(TempDC,R2_NOTMASKPEN);
MoveToEx(TempDC,200,210,nil);
LineTo(TempDC,260,210);
ReleaseDC(hMainForm,TempDC);

end;

function MakeBrush5: THandle;
  var
  BmpDC: hDC;
  i, k, Dot: Integer;
begin
{this creates a brush with alternating blcak and white pixels}
TempDC := GetDC(0);
Bitmap2 := CreateCompatibleBitmap(TempDC,8,8);
BmpDC := CreateCompatibleDC(TempDC);
ReleaseDC(GetDesktopWindow,TempDC);
SelectObject(BmpDC,Bitmap2);

Dot := 0;
for i:= 0 to 7 do
  begin
    for k:= 0 to 7 do
    if ((k+Dot) mod 2) <> 0 then
    SetPixelV(BmpDC,i,k,$00000000)
    else
    SetPixelV(BmpDC,i,k,$00FFFFFF);
    Inc(Dot);
  end;
BrushLog1.lbStyle := BS_PATTERN;
BrushLog1.lbHatch := Bitmap2;
Result := CreateBrushIndirect(BrushLog1);
DeleteDC(BmpDC);
DeleteObject(Bitmap2);
end;

function StripeBrush(Clr1, Clr2: DWORD; Horz: Boolean): THandle;
var
BmpDC: hDC;
i, k: Integer;
begin
{this creates a brush with horizontal or vertical stripes}
TempDC := GetDC(0);
Bitmap2 := CreateCompatibleBitmap(TempDC,8,8);
BmpDC := CreateCompatibleDC(TempDC);
ReleaseDC(GetDesktopWindow,TempDC);
SelectObject(BmpDC,Bitmap2);

for i:= 0 to 7 do
  begin
    for k:= 0 to 7 do
    case i of
    1,2,5,6: if Horz then
             SetPixelV(BmpDC,i,k,Clr1) else
             SetPixelV(BmpDC,k,i,Clr1);
    else
    if Horz then
      SetPixelV(BmpDC,i,k,Clr2) else
      SetPixelV(BmpDC,k,i,Clr2);
    end;
  end;

BrushLog1.lbStyle := BS_PATTERN;
BrushLog1.lbHatch := Bitmap2;
Result := CreateBrushIndirect(BrushLog1);
DeleteDC(BmpDC);
DeleteObject(Bitmap2);
end;

function MessageProc(hWnd, Msg, WParam, LParam: Integer): Integer; stdcall;
  var
  BmpDC: HDC;
  PaintS: TPaintStruct;
  Rect1: TRect;
  PDrawItem: PDrawItemStruct;
  oBottom: Integer;
OrigBrush, OrigFont: THandle;
begin
{this MessagePro gets messages for for 2 windows, hMainForm and hSplash.
It may not be a good idea to deal with 2 windows messages in a single Proc,
because of the confusion it may cause for you. I did it here to show that
you distinguish the windows messages by the hWnd, see WM_PAINT}
case Msg of
WM_CREATE: begin
             SetTimer(hMainForm,1,4500,nil);
             ShowWindow(hMainForm, SW_HIDE);
             ShowWindow(hSplash,SW_SHOW);
             SetTimer(hMainForm,2,330,nil);
           end;
WM_TIMER: begin
          if WParam = 1 then
            begin
            KillTimer(hMainForm,1);
            KillTimer(hMainForm,2);
            ShowWindow(hMainForm, SW_SHOWNORMAL);
            ReleaseDC(hSplash,TempDC);
            ShowWindow(hSplash,SW_HIDE);
            DestroyWindow(hSplash);
            end else
            begin
            if TempDC = 0 then
              begin
              TempDC := GetDC(hSplash);
{TempDC is used because it is outside of this function and
will last through several calls of this function}
              SelectObject(TempDC, Font4);
              SetTextColor(TempDC, $000000FF);
              SetBkColor(TempDC,$00FFFF00);
              SetTextAlign(TempDC, TA_UPDATECP);
              SetTextCharacterExtra(TempDC, 8);
              MoveToEx(TempDC,30,70, nil);
              end;
            if DrawTimes = 0 then
              begin
              SelectObject(TempDC,Brush4);
              PatBlt(TempDC, 0, 0, 300, 250,PATCOPY);
              end else
            if DrawTimes < 6 then
            TextOut(TempDC,0,0,@Hello[DrawTimes],1)
            else
              PatBlt(TempDC, 0, 0, 300, 250,DSTINVERT);

            Inc(DrawTimes);
            if DrawTimes > 7 then
            KillTimer(hMainForm,2);
            end;
          end;
WM_ERASEBKGND:
  begin
{the WM_ERASEBKGND is the message for painting the window's
background, I mostly use brush functions here and other types of drawing in WM_PAINT}
  Result := 1;
  {if you are not compleatly filling the background then
  call DefWindowProc( ) first or it will overdraw anything
  you draw with the Class brush}
  if hWnd = hMainForm then
    begin
    Result := DefWindowProc(hWnd,Msg,wParam,lParam);
    GetClientRect(hWnd, Rect1);
    oBottom := Rect1.Bottom;
    Rect1.Bottom := 36;
    FillRect(wParam,Rect1,Brush2);
    {Rect1.Top := 300;}
    Rect1.Bottom := oBottom;
    SelectObject(wParam,Brush4);
    PatBlt(wParam, 136, Rect1.Bottom - 46, 38, 40,PATCOPY);
    SelectObject(wParam,Brush6);
    {164,48,480,263}
    PatBlt(wParam, 156, 42, 28, 18,PATCOPY);
    {the next 3 PatBlt draws the borders}
    PatBlt(wParam, Rect1.Left, Rect1.Top, 8, Rect1.Bottom,PATCOPY);
    PatBlt(wParam, Rect1.Right-8, Rect1.Top, 8, Rect1.Bottom,PATCOPY);
    SelectObject(wParam,Brush7);
    PatBlt(wParam, Rect1.Left+4, Rect1.Bottom-8, Rect1.Right-8, 8,PATCOPY);
    SelectObject(wParam,Brush5);
    PatBlt(wParam, 159, Rect1.Top + 220, 40, 60,PATCOPY);
    PatBlt(wParam, 154, Rect1.Bottom - 46, 170, 20,$AF0229);
    PatBlt(wParam, 154, Rect1.Bottom - 26, 170,20,$A000C9);
    {FillRect(wParam,Rect1,Brush5);}
    end else
    begin
    SelectObject(wParam,Brush4);
    PatBlt(wParam, 0, 0, 300, 250,PATCOPY);
    {this is the background paint for the Splash Screen}
    end;
  Exit;
  {be sure to Exit so DefWindowProc( ) woun't be called at the end}
  end;
WM_PAINT: begin
          if hWnd = hMainForm then
          begin
          BeginPaint(hWnd, PaintS);
          {use WM_PAINT to draw on your Form, the BeginPaint tells
          you the WM_PAINT hDC to use to paint on}
          SelectObject(PaintS.hDC,Brush1);
          SelectObject(PaintS.hDC,Pen1);
          GetWindowRect(hLabel1, Rect1);
          {get the window Rect to see if PaintS.rcPaint will
          need to paint it}
          ScreenToClient(hMainForm,Rect1.TopLeft);
          ScreenToClient(hMainForm,Rect1.BottomRight);

          {the PaintS record has a rcPaint Rect, which has the Area that will be painted
           anything outside this Rect will NOT be dwawn, even if it is in thses Paint commands}
          if PaintS.rcPaint.Top < Rect1.Bottom then
            begin
            {you can test the rcPaint to see if you need to paint, this is suppose 
             to increase the efficientcy of painting, but with modern video display 
             it may not make much difference}
            Ellipse(PaintS.hDC,Rect1.Left-((Size1.cy) div 2),
                    Rect1.Top,Rect1.Left+((Size1.cy+2) div 2),
                    Rect1.Bottom{Size1.cy+Rect1.Top});
            Ellipse(PaintS.hDC, Rect1.Left+Size1.cx+(Size1.cx div 10)-((Size1.cy+4) div 2),
                   Rect1.Top, Rect1.Left+Size1.cx+(Size1.cx div 10)+((Size1.cy) div 2),
                   Rect1.Bottom{Size1.cy+6});
            {these 2 Ellipse draw hLable1 oval ends based on the Font3 size}
            end;
          
          DrawIcon(PaintS.hDC,10,40,LoadImage(hInstance, 'MainIcon', IMAGE_ICON, 
                   0, 0, LR_DEFAULTSIZE));
          {DrawIcon(DC,2,40,LoadIcon(hInstance, 'MainIcon'));}
          {LoadIcon( ) can also be used and is simpler, but has less options,
          you do NOT have to free or DeleteObject or DetroyIcon for Icons except
          if you use CreateIconIndirect( )}
          SetRect(Rect1,164,48,480,263);
          DrawFocusRect(PaintS.hDC,Rect1);
          InflateRect(Rect1,2,2);
          DrawFocusRect(PaintS.hDC,Rect1);
          SelectObject(PaintS.hDC,Font2);
          SetBkColor(PaintS.hDC,GetSysColor(COLOR_BTNFACE));
          TextOut(PaintS.hDC,268,38,' Drawing Area ',14);
          SelectObject(PaintS.hDC,Brush4);
          GetClientRect(hWnd, Rect1);
          PatBlt(PaintS.hDC, 187, Rect1.Bottom-54, 70, 50,PATCOPY);
          PatBlt(PaintS.hDC, 240, Rect1.Bottom-54, 70, 50,PATINVERT);
          {test Bitmap1 to see if it was loaded from file}
          if Bitmap1 <> 0 then
            begin
            BmpDC := CreateCompatibleDC(PaintS.hDC);
            {to draw with a bitmap you need a memory DC for it
             CreateCompatibleDC will get a DC with the bits per pixel of DC}
            SelectObject(BmpDC,Bitmap1);
            {when you select the Bitmap into BmpDC, it Becomes the DC for that
            bitmap}
            BitBlt(PaintS.hDC, 56, 158, 64, 80, BmpDC, 0, 0, SRCCOPY);
            DeleteDC(BmpDC);
            {be sure to release the BmpDC}
            end;
          EndPaint(hWnd,PaintS);
          Result := 0;
          Exit;
          end;
          end;
WM_DRAWITEM: begin
{WM_DRAWITEM is sent to the parent window when an 
             Owner Drawn child control needs painting}
             PDrawItem := Pointer(LParam);
             {TypeCasting the PDrawItem to Pointer(LParam) is allowed even though 
              the "type" of the PDrawItem is another pointer type}

             {PDrawItem Record will contain info needed to paint the control
              test the hwndItem to see if it is the control}
             if PDrawItem.hwndItem = hOwnerDrawS then
               begin
               OrigBrush := SelectObject(PDrawItem.hDC,Brush2);
               OrigFont := SelectObject(PDrawItem.hDC,Font2);
               FillRect(PDrawItem.hDC, PDrawItem.rcItem, GetStockObject(BLACK_BRUSH));
               RoundRect(PDrawItem.hDC, PDrawItem.rcItem.Left+3, PDrawItem.rcItem.Top+3,
                 PDrawItem.rcItem.Right-3, PDrawItem.rcItem.Bottom-3,10,10);
               {Draw two rectangles and the text}
               SetBkMode(PDrawItem.hDC,TRANSPARENT);
               DrawText(PDrawItem.hDC,'Owner Draw',-1, PDrawItem.rcItem, 
                        DT_CENTER or DT_VCENTER or DT_SINGLELINE);
               SelectObject(PDrawItem.hDC,OrigBrush);
               SelectObject(PDrawItem.hDC,OrigFont);
               {restore the font and brush}
               Result := 1;
           {Set the Result to One and Exit, so the 
            DefWindowProc will NOT be called}
               Exit;
               end;
             end;
WM_COMMAND: if lParam = hExitBut then PostMessage(hMainForm,WM_CLOSE,0,0)
            else if lParam = hMakeBmpBut then MakeBmp
            else if lParam = hPaintBut then PaintIt
            else if lParam = hDrawBut then DrawIt
            else if lParam = hPenDrawBut then PenDraw;
WM_DESTROY: if hWnd = hMainForm then ShutDown;
WM_CTLCOLORSTATIC: if (LParam = abs(hLabel1)) then
{WM_CTLCOLORSTATIC is the pre Static Paint message to get
colors to paint hLabel1}
                     begin
                     SetTextColor(wParam,$0000FFFF);
                     SetBkColor(wParam,$00FF0000);
                     {SetBkColor is only for the text drawing}
                     Result := Brush1;
                     {Result is the Brush Handle used to paint any
                     background not covered by text}
                     Exit;
                     {IMPORTANT
                       You MUST Exit so the DefWindowProc is NOT called
                       try it without Exit and the Static will NOT change colors}
                     end;
end;
Result := DefWindowProc(hWnd,Msg,wParam,lParam);
end;

begin   //   Main Begin / / / / / / / / / / / / / / / / / / / / /
{since Brushes, Pens and Fonts are Window's System Objects
I usually create them first. You must Delete these Objects
before your Program ends, see ShutDown procedure above}

Brush1 := CreateSolidBrush($00FF0000);
Brush2 := CreateHatchBrush(HS_DIAGCROSS, $00FF00FF);
BrushLog1.lbStyle := BS_HATCHED;
BrushLog1.lbColor := $00FFFF00;
BrushLog1.lbHatch := HS_HORIZONTAL;
Brush3 := CreateBrushIndirect(BrushLog1);
{GetSysColorBrush(nIndex	// system color index
   );}
Bitmap2:= LoadBitmap(hInstance,'BRUSH');
BrushLog1.lbStyle := BS_PATTERN;
BrushLog1.lbHatch := Bitmap2;
Brush4 := CreateBrushIndirect(BrushLog1);
DeleteObject(Bitmap2);

Brush5 := MakeBrush5;
Brush6 := StripeBrush(GetSysColor(COLOR_ACTIVECAPTION), GetSysColor(COLOR_CAPTIONTEXT), True);
Brush7 := StripeBrush(GetSysColor(COLOR_CAPTIONTEXT), GetSysColor(COLOR_ACTIVECAPTION), False);

Pen1 := CreatePen(PS_SOLID, 1, $00FF0000);
Penlog1.lopnStyle := PS_SOLID;
PenPoint.x := 3;
PenLog1.lopnWidth := PenPoint;
PenLog1.lopnColor := $0000CC00;
Pen2 := CreatePenIndirect(PenLog1);

{to fit on this Form the "small.bmp" should be less than 200x200 and to avoid
palette problems use a 4bit image with the standard windows palette.
You can experiment if you are not familar with display device palettes.
Try a photo (over 2000 diferent colors) 24bit bitmap and set your computer to
display 256 colors and see how the bitmap colors are remapped}
Bitmap1 := LoadImage(0,'C:\Stuff\small.bmp', IMAGE_BITMAP,0,0, 
                    LR_LOADFROMFILE or LR_CREATEDIBSECTION);
{LoadImage is an easy way to load a Bitmap, Icon or Cursor from a file,
It can also load an Image from a resource. There's alot to consider when using a Bitmap
which is not covered here, Device dependence, color palette, amount of color info
per pixel (1bit, 4bit, 8bit, 16bit, 24bit, 32bit). These things will not be covered
here. See API Help for LoadImage and look up LR_CREATEDIBSECTION}

Bitmap2 := 0;
{Bitmap2 may not be created so initialize it to 0}

ZeroMemory(@FontLog1, SizeOf(FontLog1));
{Most of the DEFAULT parameter values in the FontLog1 are Zero,
 so you can save some typing by ZeroMemory of the FontLog1}
with FontLog1 do
  begin
  lfHeight := -12;
  // none of the Default values need to be set if ZeroMemory
  lfPitchAndFamily := VARIABLE_PITCH or FF_SWISS;
  lfFaceName := 'MS Sans Serif';
  end;
Font1 := CreateFontIndirect(FontLog1);

with FontLog1 do
  begin
  lfHeight := -14;
  lfWeight := FW_BOLD;
  lfOutPrecision := OUT_TT_PRECIS;
  lfQuality := ANTIALIASED_QUALITY;
  lfPitchAndFamily := VARIABLE_PITCH or FF_ROMAN;
  lfFaceName := 'Times New Roman';
  end;
Font2 := CreateFontIndirect(FontLog1);

with FontLog1 do
  begin
  lfHeight := -20;
  lfWeight := FW_NORMAL;
  lfPitchAndFamily := VARIABLE_PITCH or FF_DECORATIVE;
  lfFaceName := 'Comic Sans MS';
  end;
Font3 := CreateFontIndirect(FontLog1);

with FontLog1 do
  begin
  lfHeight := -60;
  lfWeight := FW_BOLD;
  lfFaceName := 'Comic Sans MS';
  end;
Font4 := CreateFontIndirect(FontLog1);

wClass.hInstance := hInstance;
with wClass do
  begin
    Style := CS_VREDRAW;
{the CS_VREDRAW tells it to redraw the whole window on
verical resize, but may cause filckering. Resize Form in
the vertical and horizontal direction to see the difference}
    hIcon :=         LoadIcon(hInstance,'MAINICON');
    lpfnWndProc :=   @MessageProc;
    hbrBackground := COLOR_BTNFACE+1;
    {hbrBackground := 0;}
{try setting the background brush to 0, to see what happends}
    lpszClassName := 'Form Class';
    hCursor :=       LoadCursor(0,IDC_ARROW);
  end;

RegisterClass(wClass);

hMainForm := CreateWindow(
    wClass.lpszClassName,   // pointer to registered class name
    'Brush, Pen and Static Demo',   // pointer to window name (title bar Caption here)
    WS_OVERLAPPEDWINDOW,   // window style
{WS_OVERLAPPEDWINDOW is the default standard main window with a
Title bar and system menu and sizing border}
    (GetSystemMetrics(SM_CXSCREEN) div 2)-276,   // horizontal position of window
    (GetSystemMetrics(SM_CYSCREEN) div 2)-222,   // vertical position of window
    500,   // window width
    350,   // window height
    0,   // handle to parent or owner window
{this is the MAIN window, so it will be the parent}
    0,   // handle to menu or child-window identifier
    hInstance,   // handle to application instance
    nil   // pointer to window-creation data
   );
{the WS_VISIBLE style was NOT set in this Main window creation}

TempDC := GetDC(hMainForm);
SelectObject(TempDC,Font3);
{for a Device Context,
HDC, see Win32 API help for "Device Contexts", relates to a Canvas in Delphi)
you need to SelectObjects (fonts, pens, brushes) to use in Drawing on that DC}
GetTextExtentPoint32(TempDC, 'DC Paint and Static Control Demo', 32, Size1);
{GetTextExtentPoint32 gets the size of text for that DC
Size1 is used to get the correct size for Label1, even though this is NOT the
hLabel1 DC, the Text size should be the same for the same font and text Font3}

ReleaseDC(hMainForm,TempDC);
{to call GetDC and then ReleaseDC may seem unnessary,
why not just call GetDC once when the app starts and then
ReleaseDC once when the app closes,
these GetDC are quick functions and releasing it cuts down
on window's system resources use}

GetClientRect(hMainForm, FormRect);

hLabel1 := CreateWindow('Static', 'DC Paint and Static Control Demo',
         WS_VISIBLE or WS_CHILD or SS_CENTER,
         (FormRect.Right div 2)-((Size1.cx+(Size1.cx div 10)) div 2),
         5, Size1.cx+(Size1.cx div 10),Size1.cy+1,hMainForm,0,hInstance,nil);
{hLabel1 is assigned a Font name that will not be found, Font3, so I calculate 
the Label1 size based on the Size1 from GetTextExtentPoint32() so it will be 
the correct size for any font used, change the font name or font size in 
Font3 := CreateFontIndirect(FontLog1); 
and see how it changes Label1}
SendMessage(hLabel1,WM_SETFONT,Font3,0);

hIcon1 := CreateWindow('Static', 'MAINICON',
    WS_VISIBLE or WS_CHILD or SS_ICON,
    10,2,1,1,hMainForm,0,hInstance,nil);

hExitBut := CreateWindow('Button','Exit',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT or BS_TOP or WS_GROUP,
    380,280,64,28,hMainForm,0,hInstance,nil);
SendMessage(hExitBut, WM_SETFONT, GetStockObject(SYSTEM_FIXED_FONT),0);

hOwnerDrawS := CreateWindow('Static', '',
         WS_VISIBLE or WS_CHILD or SS_OWNERDRAW,14,90,100,30,hMainForm,0,hInstance,nil);
         {OwnerDraw has no default painting except to fill the rect with button color
         you will have to do all the painting in message WM_DRAWITEM above}

hPenDrawBut := CreateWindow('Button','Pen Draw',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT,
    20,128,74,24,hMainForm,0,hInstance,nil);
SendMessage(hPenDrawBut, WM_SETFONT, GetStockObject(ANSI_VAR_FONT),0);

hMakeBmpBut := CreateWindow('Button','Make Bmp',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_CENTER or BS_TEXT,
    20,228,90,24,hMainForm,0,hInstance,nil);
SendMessage(hMakeBmpBut,WM_SETFONT,Font2,0);

hPaintBut := CreateWindow('Button','Paint It',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_LEFT or BS_TEXT,
    20,258,80,21,hMainForm,0,hInstance,nil);
SendMessage(hPaintBut,WM_SETFONT,Font2,0);

hDrawBut := CreateWindow('Button','Draw It',
    WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_RIGHT or BS_TEXT,
    20,286,70,21,hMainForm,0,hInstance,nil);
SendMessage(hDrawBut,WM_SETFONT,Font1,0);

hSplash := CreateWindow(wClass.lpszClassName,
             '',WS_POPUP or WS_VISIBLE, (GetSystemMetrics(SM_CXSCREEN) div 2)-270,
             (GetSystemMetrics(SM_CYSCREEN) div 2)-212, 300, 230,hMainForm, 0, hInstance, nil);

TempDC := 0;
DrawTimes := 0;

while GetMessage(mainMsg,0,0,0) do
  begin
  {GetMessage will return True until it gets a WM_OUIT message}
    TranslateMessage(mainMsg);  // Translate any WM_KEYDOWN keyboard Msg to a WM_CHAR message
    DispatchMessage(mainMsg);   // Send Msg to the WndMessageProc
  end;

end.
Because Windows is a graphical interface, there are alot of methods and options for drawing pixels on the screen, so that many different visual styles and effects are possible. With more options, there is much more to learn when you want use these for your own display stylings. This program has presented you with many of the basic drawing methods for pens and brushes. It is important for you to change and experiment with the methods and options for the many drawing operations available, in order to to have some idea of how to get a "Look" and "Visual Style" for your GUI. Or at least be able to do functional graphical arrangement and display highlighting for a simple user GUI. You should create several new projects and see what kinds of "Visual Style" you can create with these basic Font, Pen and Brush methods.

                           

Next
We have made a programs useing fonts, pens and brushes. Next we'll try using button and edit controls.
  Lesson 7, Using Button and Edit controls


       

Lesson -     One Two  Three  Four  Five  Six  Seven  Eight  Nine  Ten  Eleven  Twelve  Thirteen  Fourteen




H O M E