Home |
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 |
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 Penor 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.
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
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.
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. | ||||
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 ZeroIn 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. |
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 itBRUSH 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