Home |
5. Device Context and Using Fonts Basic Drawing Functions |
Home |
The Programs you create in Delphi have many of the standard windows graphical operations (creating fonts, brushes, pens, and device contexts) done for you automaticly. When you set a font on a TEdit, you do not see the code used to create and set the parameters for that font. You do not have to destroy the fonts, brushes or pens that are used for Canvas drawing. Many VCL components have a "Canvas" to use for graphical procedures like LineTo, TextOut, FillRect, Draw and many others. TCanvas is a way for Delphi to wrap the Windows "Device Context" and many of the drawing functions it supports. You may want to read the Win32 API Help for "Device Contexts" (Help Index "Device Contexts"), which begins with "A device context is a structure that defines a set of graphic objects and their associated attributes, and the graphic modes that affect output.". | And click the >_> at the top of the help program to read the sequence of pages. Because there are many types of video cards and monitors and the drivers for them, it would be imposible to write code to cover all the video cards, so windows has a Device Context to translate graphical functions for the video card driver to use. "One of the chief features of the Microsoft Win32 application programming interface (API) is device independence." This Fonts program will demonstrate some essential begining graphical functions using TextOut( ), FillRect( ), Rectangle( ), and Ellipse( ). But this is only a begining, there's a great deal of graphics functions and options not covered here. In this page there will be a short description for some of these functions and objects starting with fonts. But you may want to read the Win32 API Help for fonts also. |
Device Context Types The Device in "Device Context" is a term from early DOS and Windows programming for the video card device or a printer device. A Device Context (DC) means that the windows system will put an API "wrapper" around the device driver for your video card or printer to do graphical operations with that device. The low level code needed to draw a line for the driver of a printer, and the driver of a 1 megabyte video card, and the driver of a 128 megabyte video card, would be very different. The low level driver code may also be very different, even if you just switch your computers display color depth from Full Color to 256 color (if it supports both of these display modes). But by using the API Device Context you can draw a line on on any of these with the LineTo( ) function, without checking for, or setting any device drivers. . . . Since Windows is a "Visual" system, it has extensive Graphics Device Interface (GDI) functions, which will take time to learn. So just take it one step at a time. In Win32 API there are four types of device contexts: display, printer, memory (or compatible), and information. Each type has a specific purpose, described by the following table.
To use a DC in GDI functions, you first have to Get or Create a windows system "Device Context Handle" for it, with a function like GetDC( ) or others. If you are drawing in the WM_PAINT message of the Window Proc then the DC handle is obtained with BeginPaint( ). When these functions return a device context handle, Windows initializes the Device Context with default graphic objects (black pen, white brush), attributes (text align is Top-Left, text color is Black), and modes (BkMode is OPAQUE). Any drawing operations on this DC use these defaults unless one of the GDI functions is used to select a new object, change the properties of the current object, or select a new mode. Device Context's Graphic Objects A Device Context needs methods to get it's pixel content and change it's pixels (paint and draw). The pen, brush, font, bitmap, palette, region, and path associated with a device context are referred to as the device context's Graphic Objects and are used for drawing and painting. When an application gets or creates a device context, Windows automatically stores a set of default objects in it. (There is no default bitmap or path.) A program can get the attributes of the DC's graphic objects by calling the GetCurrentObject( ) and GetObject( ) functions. You can look at your local API Help or web page at MSDN-GetObject. The program can create and change these objects by selecting a new one into the Device Context by calling the SelectObject( ) function. These new selected objects will be used in drawing functions to change the pixels of the DC. A program must always have a method to call DeleteObject( ) for Every Graphic Object that it creates. Failing to delete objects may causes serious performance problems. These Graphic Objects (Fonts, Pens, Brushes, Bitmaps, Palettes, Regions and Paths) are System objects and the Information Records (memory) for these are in the system's memory, not your programs memory, so if these objects are not Deleted then they will still use memory even after your program closes. Use Stock Objects To save on resources, it's a good idea to use GetStockObject( ) to get windows system stock fonts, brushes, or pens when possible. However, there is very little variety avaiable in the stock objects. Look at your local API Help for index "GetStockObject" or the web page MSDN-GetStockObject, to see the list of stock objects. The BLACK_BRUSH, WHITE_BRUSH, BLACK_PEN, WHITE_PEN and ANSI_VAR_FONT are very useful stock objects. hFont := GetStockObject(ANSI_VAR_FONT) will usually get the MS Sans Serif font. Stock Objects do NOT need to be deleted. Drawing on a DC To draw some text on your Main Forms DC you have to get the DC's Handle from the system, then do the text drawing and then Release that DC. Here are three very important Device Context functions - function GetDC( hWnd: HWND // handle of window ): HDC; // Handle of Device Context function TextOut( DC: HDC; // Handle of device context X: Integer; // x-coordinate of starting position Y: Integer; // y-coordinate of starting position Str: PChar; // address of text string Count: Integer // number of characters in string ): BOOL; // for success, the return value is True function ReleaseDC( hWnd: HWND; // handle of window hDC: HDC // handle of device context ): Integer; // for success, the return value is 1You might look at your local API Help for these functions or go to these web pages MSDN-GetDC . . . MSDN-TextOut . . . MSDN-ReleaseDC Here is an example of code to use these - procedure DrawText; var MainDC: HDC; begin MainDC := GetDC(hMainForm); TextOut(MainDC, 20, 30, 'Text on Main Form', 17); ReleaseDC(hMainForm, MainDC); end;This will draw black text on a white background using the system font. Notice that unlike Delphi VCL TCanvas and TFont, the TextOut function does not have parameters for the Font name or size used, the color of the text, or the background color of the text. These text drawing attributes are part of the Logical Font creation and the Device Context (MainDC) and are set to the default values (font=System Font, text color=Black, background color=White, text align=Top-Left, intercharater spacing=0) when you call GetDC( ) to intitialize a Device Context. You will need to assign a font to a new DC to change it from the default system font and assign a font color and other DC charteristics to get the look you want. . To assign a graphic Object, like a hFont, to a Device Context, you would use the API SelectObject( ) function - function SelectObject( DC: HDC; // Handle of Device Context hObj: THandle // Handle of Object ): THandle; // Handle of the Object being replacedYou might look at this in your local API Help or web page at MSDN-SelectObject . . . The hObj parameter is the handle of the graphic object to be "Used" with that hDC, in graphic operations that require that type of object. For the Font, Pen, and Brush objects, the selected object will be used when drawing is done that requires that object. The Bitmap, Palette, Region, and Path objects have a different "Meaning" for being selected in to a hDC. There is more about setting the text drawing properties of a Device Context latter in this lesson at Drawing Text on the HDC Here is some code to show how to change the Font drawing on a DC - procedure DrawText2; var MainDC: HDC; begin MainDC := GetDC(hMainForm); {use DC adjustment fuctions to change the drawing properties of the DC} SelectObject(MainDC,GetStockObject(ANSI_FIXED_FONT)); // the ANSI_FIXED_FONT will now be used for font drawing SetTextColor(MainDC,$0000FF); // Text drawing color is now Red SetBkMode(MainDC,TRANSPARENT); // Text background will Not be drawn TextOut(MainDC, 20, 30, 'Text on Main Form', 17); ReleaseDC(hMainForm, MainDC); end;It is recommended and common practice, when using a Display Device Context (like the MainDC above), to get or create this DC and then release this DC as soon as posible. Since the Video Display is a shared resource, use it only when nessary, getting a DC with GetDC( ) is a very fast operation, so is ReleaseDC( ). IMPORTANT: ALWAYS make sure that you call ReleaseDC( ) for each call to GetDC( ). If you are used to using the Delphi TCanvas, then it may be helpful to remind you that any change in the Device Context drawing properties are NOT persistant and will be lost as soon as you call ReleaseDC( ). So if you change the DC's text color to Red and then Release the DC, the next time you call GetDC( ) for the same window, this DC will now have the default black text color, not red. WM_PAINT message and drawing The WM_PAINT message is sent to your MessageProc through the GetMessage( ) function when ever the system thinks that something needs to be refreshed by redrawing it or some part of the client area (the non-client area gets the WM_NCPAINT message which you usually do not have to use). Whenever a portion of a client area (main window or control) is covered and the revealed a WM_PAINT message will be sent. The exceptions to this is when the Cursor or a cursor draged icon is over the window, the system saves a bitmap of that area and then redraws that Cursor covered area with the saved bitmap without sending a WM_PAINT. Since the entire client area may not need to be repainted the system uses an internal "paint information record" for each window getting the WM_PAINT, which contains the smallest rectangle that needs to be repainted. This area is called the "invalid" or "Update" region. If a WM_PAINT message is sent and this Invalid Region changes before your app can process the WM_PAINT message, windows will update the Invalid Region, but will not send another WM_PAINT message, so there can only be One WM_PAINT message in your message queue. Almost every program will process the WM_PAINT message for it's main form. The API has the method of calling two functions BeginPaint( ) and EndPaint( ) to do drawing in the WM_PAINT message (ONLY in the WM_PAINT message). These two functions do NOT work out side of the WM_PAINT message, and they are a matched pair. When you process the WM_PAINT you can to get the Painting information like DC handle and Invalid region (TRect) from a TPaintStruct by calling the BeginPaint( ) function. This BeginPaint will get the system information for the current WM_PAINT message for that window. function BeginPaint(hWnd: THandle; var lpPaint: TPaintStruct): HDC;You can look this up in your local API Help or web page at MSDN-BeginPaint . Also see Help for EndPaint( ) function or web page at MSDN-EndPaint And for the WM_PAINT message you could use code like this- var paintDC: HDC; PaintS: TPaintStruct; Str1: String; if Msg = WM_PAINT then begin Str1 := 'Hello'; paintDC := BeginPaint(hWnd, PaintS); Rectangle(paintDC,30,26,140,56); SelectObject(paintDC, hFont1); SetTextColor(paintDC,$000000FF); SetBkMode(paintDC,TRANSPARENT); TextOut(paintDC, 40, 40,PChar(' this is '+Str1), 9+Length(Str1)); EndPaint(hWnd,PaintS); // ALWAYS call EndPaint( ) if you call BeginPaint( ) in WM_PAINT end;This will draw a rectangle (with default brush and pen) and then draw text on that rectangle. hFont is the Handle of the font selected, so the text will use that font and the text color will be Red, from SetTextColor( ), and there will be No text background color painted by the TextOut( ) function, because SetBkMode(paintDC,TRANSPARENT); prevents it. Using BeginPaint(hWnd, PaintS); BeginPaint is different than GetDC, it returns a hDC (which is clipped) to draw on, but it also returns a TPaintStruct Record (see PAINTSTRUCT in API Help) which is in windows.pas as - tagPAINTSTRUCT = packed record hdc: HDC; fErase: BOOL; rcPaint: TRect; fRestore: BOOL; fIncUpdate: BOOL; rgbReserved: array[0..31] of Byte; end; TPaintStruct = tagPAINTSTRUCT;See your local API Help about PAINTSTRUCT or web page at MSDN-PAINTSTRUCT The values of fRestore, fIncUpdate, and rgbReserved are reserved for the OS and not used. The HDC is the same value returned by BeginPaint( ). The fErase value will be True if there is no brush (or NULL_BRUSH) assigned to the hbrBackground member of the Window Class Record. The rcPaint TRect can be used to determine where the hDC will be painted. Unlike GetDC( ) the BeginPaint hDC may NOT paint the entire hDC, painting is only allowed on the area of the rectangle, rcPaint. When EndPaint( ) is called the rcRect is set to 0. If you call PostMessage( ) or SendMessge( ) with the WM_PAINT message, NOTHING will happen, no drawing will be done, because the rcPaint will be Zero. To set the rcPaint rectangle to have an invalid area (not be Zero), you will need to use the function InvalidateRect( ). InvalidatRect( ), used to Redraw Client Areas If you read your local API Help for InvalidateRect or web page at MSDN-InvalidatRect, you will see that this is for adding a rectangle to a window's update region and then redrawing the client area of that window. This is a function that is frenquently used when you need to refresh or redraw a window or control. If you set the lpRect to nil, then the entire client area will be redrawn. If you only need a smaller portion redrawn, then you can set a Rectangle to the part of the client area that needs the refreshing, and place it in the lpRect parameter. The bErase parameter is commonly set to True, so the background will also be refreshed, but can be set to False to prevent the WM_ERASEBKGND message from being sent. This is a function you should be familar with, because you will need it to get controls and windows to draw the changes that you make to them. Some Basic Drawing Fuctions In the code for this Fonts program there are 3 drawing functions used to draw shapes or fill areas of a DC. Most of the API drawing function's names tell you what they draw or do. There are 2 functions used to draw a shape, the Rectangle function draws a rectangle, and the Ellipse function draws circular shapes - function Rectangle(DC: HDC; X1, Y1, X2, Y2: Integer): Boolean; MSDN-Rectangle function Ellipse(DC: HDC; X1, Y1, X2, Y2: Integer): Boolean; MSDN-Ellipse Both of these functions have same 5 parameters. The HDC to draw on and the 4 integers for the Left, Top, Right, and Bottom dimentions of the shape. The shape drawing will be done by filling the shape with the selected brush of the HDC, and the shape will be outlined (border drawn) with the HDC's pen. Reading the API help for these functions will tell you more. Look at the code in the procedure DrawIt; below in the Fonts program to see how to use these. By setting the HDC's pen or brush to Null, , SelectObject(HDC, GetStockObject(NULL_BRUSH)); will cause the function to not draw the interior (null brush), or the border (null pen). The FillRect function also draws a rectangle, but it does not draw a border around the rectangle, It has 3 parameters, a HDC, a TRect and a hBrush. function FillRect(hDC: HDC; const lprc: TRect; hbr: HBRUSH): Integer; MSDN-FillRect You also need to have a Brush handle in it's parameters, but I will not try and explain brush creation in this lesson, thats in the next one. I will just use the GetStockObject(BLACK_BRUSH) to get the stock black brush. Look at the code in the procedure DrawIt; below. Fonts Displaying keyboard typing on a computer monitor, is one of the most basic and essential things that your computer does. It can do this without any operating system, when you first boot up. The Window's OS text display methods need to offer flexibility and creative variety, and be able to work if there are problems, like the font "Name" is not on the computer's OS (or there are NO fonts installed on that computer). So the CreateFont function has many parameters, which are some trouble to deal with, but can give you some variation for your font display. Fonts are Graphic Objects that you can create to use to write text on a Device Context or set as the display font for a control. Fonts are created and managed by the Windows System, to use a font you need it's "Handle", which is returned by the Font Creation functions. The font creation functions all need several parameters like Font Name, and Font Height to create a font. If you are used to using fonts in Delphi VCL, then you know to set the Font.Name and Font.Height and maybe set the Font.Style to fsBold is all you usually need. The same applies to API Font Creation, the 3 important parameters to set are lfFaceName, lfHeight, and lfWeight - the rest are extra. Here is some Delphi Source code from Graphics.pas for creating or changing a font. You may want to read your local Win32 API Help for CreateFont MSDN-CreateFont, CreateFontIndirect, MSDN-CreateFontIndirect WM_SETFONT, MSDN-WM_SETFONT, which are highlighted in red in the code below. function TFont.GetHandle: HFont; var LogFont: TLogFont; begin with FResource^ do begin if Handle = 0 then begin FontManager.Lock; with LogFont do try if Handle = 0 then begin lfHeight := Font.Height; lfWidth := 0; { have font mapper choose } lfEscapement := 0; { only straight fonts } lfOrientation := 0; { no rotation } if fsBold in Font.Style then lfWeight := FW_BOLD else lfWeight := FW_NORMAL; lfItalic := Byte(fsItalic in Font.Style); lfUnderline := Byte(fsUnderline in Font.Style); lfStrikeOut := Byte(fsStrikeOut in Font.Style); lfCharSet := Byte(Font.Charset); if AnsiCompareText(Font.Name, 'Default') = 0 then StrPCopy(lfFaceName, DefFontData.Name) else StrPCopy(lfFaceName, Font.Name); lfQuality := DEFAULT_QUALITY; { Everything else as default } lfOutPrecision := OUT_DEFAULT_PRECIS; lfClipPrecision := CLIP_DEFAULT_PRECIS; case Pitch of fpVariable: lfPitchAndFamily := VARIABLE_PITCH; fpFixed: lfPitchAndFamily := FIXED_PITCH; else lfPitchAndFamily := DEFAULT_PITCH; end; Handle := CreateFontIndirect(LogFont); end; finally FontManager.Unlock; end; end; Result := Handle; end; end; procedure TWinControl.CMFontChanged(var Message: TMessage); begin inherited; if HandleAllocated then Perform(WM_SETFONT, FFont.Handle, 0); NotifyControls(CM_PARENTFONTCHANGED); end; You must set the parameters for font creation, notice that delphi uses default parameters, lfQuality := DEFAULT_QUALITY; lfOutPrecision := OUT_DEFAULT_PRECIS; lfClipPrecision := CLIP_DEFAULT_PRECIS; which are the safe choises. Windows Font Mapping Vector and Bitmap Fonts Font Creation hFont1 := CreateFont( nHeight: Integer, // height of font nWidth: Integer, // average character width nEscapement: Integer, // angle of rotation nOrientation: Integer, // base-line orientation angle fnWeight: Integer, // font weight fdwItalic: Cardinal, // italic attribute fdwUnderline: Cardinal, // underline attribute fdwStrikeOut: Cardinal, // strikeout attribute fdwCharSet: Cardinal, // character set identifier fdwOutputPrecision: Cardinal, // output precision fdwClipPrecision: Cardinal, // clipping precision fdwQuality: Cardinal, // output quality fdwPitchAndFamily: Cardinal, // pitch and family lpszFace: PChar // typeface name PCharThe parameters in CreateFont( ) are also in TLogFont used in CreateFontIndirect( ). You may want to look at your local API Help for LOGFONT or web page at MSDN-LOGFONT var FontLog1: TLogFont; with FontLog1 do begin lfHeight := -12; lfWidth := 0; lfEscapement := 0; lfOrientation := 0; lfWeight := 0; lfItalic := 0; lfUnderline := 0; lfStrikeOut := 0; lfCharSet := DEFAULT_CHARSET; lfFaceName := 'MS Sans Serif'; {the params below are used only if the named font is NOT availible, then these are used to pick a substitute font, the Pitch and family beging the most important, so set them to default if you are unsure} lfOutPrecision := OUT_DEFAULT_PRECIS; lfClipPrecision := CLIP_DEFAULT_PRECIS; lfQuality := DEFAULT_QUALITY; lfPitchAndFamily := DEFAULT_PITCH; end; FontHandle := CreateFontIndirect(FontLog1);You can use the FontLog1 settings above and change the lfHeight and lfFaceName to what you need, and this will work well as long as the font Named is availible on that computer. You should look at the parameter explanations below to give you an Idea of what these are for, but you do Not have to know all the details to create fonts. Font Creation Parameters lpszFace This is the Last parameter in CreateFont( ), but the most important. You set the Font Face name here that you hope will be in the OS fonts, if this face name is availible from the system physical fonts then that font is used and the parameters fdwCharSet, fdwOutputPrecision, fdwClipPrecision, fdwQuality, and fdwPitchAndFamily are all ignored. All of these values are taken from the named font. If the named font is not present in the OS, then these values are used to select a replacement font. nHeight The first parameter of CreateFont is nHeight, which is in logical units not points (a font mesurement unit) so you can use positive and negitive values. Positive values matches the value to the Cell height of the font, negative values matches the value to the Character height of the font, See chart below.
nWidth This second parameter is font width, which is for the average width, in logical units, of characters in the font. If nWidth is zero, the font mapper chooses a close match (normal) value relative to the nHeight. This nWidth value is only used for True Type (vector) fonts, and ignored for non-vector fonts. With True Type fonts you can set the nWidth to more or less than normal to fit more text into an area, or expand text to make it eaiser to read. First you will need to determine what the "normal" nWidth is for the nHeight you have chosen, and then change it to make the font wider or narrower. Also, even if you want "normal" width (set nWidth to zero), you may want to set this nWidth to the normal width value in case your Named font is not available and a substitute font with the same width will be located or at least displayed on screen with a simular width. nEscapement and nOrientation The next 2 parameters are for "Angle of Rotation" and "Orientation" often used together to change the angle the font is drawn on the HDC. These parameter's units are tenths of a degree, from 0 to 3600. Only True Type fonts can be rotated, other fonts will ignore these parameters. fnWeight The next parameter is Weight, which sets the normal or Bold apperence of a font, this can range form 0 to 1000. Use a weight of zero to get normal text weight, here are some text weight constants. . .
The font mapper will not change the font weight to all the values between 1 and 1000, it may only change the displayed text weight on a few numbers in that range, depending on the the version of Windows, the Font and it's size and type. True Type fonts will have more options (size and weight) than non-True Type fonts. In early versions of Windows, any weight below 501 is shown as 400 and any weight above 500 is shown as 700. So the safe choices are 0 (or FW_NORMAL) and FW_BOLD. fdwItalic, fdwUnderline and fdwStrikeOut The next 3 parameters can be 0 or 1 to set the font as being Italic, Underlined, or LineThrough (Strike Out). A value of 1 will apply that charateristic. fdwPitchAndFamily You may want to match the Pitch and Family to the Font you named in lpszFace. Font Pitch is how the width of each of the font's charaters is determined. With Fixed Pitch (Monospaced), all of the font's charaters are the same width, a "i" is the same width as a "M". A Variable Pitch font sets the width of a character to whatever is needed by that character, so a "i" will be narrower (less wide) than a "M". The values availible here for font pitch are - DEFAULT_PITCH // usually variable FIXED_PITCH VARIABLE_PITCHThe font Family is a general catagory for the "Features" that a font may have. I cannot explain font design specifiations and naming here, but the primary font feature is Serif and Non-Serif (called Sans Serif, by the font geeks). A Serif font is "New Times Roman" a Sans Serif font is "Arial". The values availible for Font Family are - FF_DONTCARE // default, usually will get a Swiss, Sans Serif FF_DECORATIVE // special title artisic fonts FF_MODERN // a Fixed pitch font FF_ROMAN // fonts with Serifs FF_SCRIPT // fonts made to look like handwriting FF_SWISS // fonts without Serifs - Sans SerifYou should at least specify if you want a Fixed pitch or Variable pitch font. fdwCharSet Specifies the character set. The following values are predefined: ANSI_CHARSET DEFAULT_CHARSET SYMBOL_CHARSET SHIFTJIS_CHARSET GB2312_CHARSET HANGEUL_CHARSET CHINESEBIG5_CHARSET OEM_CHARSETYou might try to usually use the DEFAULT_CHARSET for font creation that might use many different font face names. If you only create one font face name, you should match the fdwCharSet to the Face Name fdwCharSet of your font. English text fonts mostly use a ANSI_CHARSET, however if you have a "Symbol" font face Name that is NOT text characters and have the ANSI_CHARSET, the font mapper will NOT use that font face name, it will use a font that is a ANSI font. The font mapper seems enforce your setting for the chacrater "Set" type you have in the fdwCharSet, no matter what font face name you may use. fdwOutputPrecision, fdwClipPrecision, fdwQuality Since these are used when your lpszFace Font name can not be found, setting these to the Defaults are the saftest and most common. The fdwOutputPrecision parameter is for matching the the way the text is drawn to the values in the logical font's requested height, width, pitch, font type, ect. Use the OUT_DEFAULT_PRECIS, another value to consider is the OUT_TT_ONLY_PRECIS, for True Type fonts. The fdwClipPrecision is supposed to set how to clip characters that are partially outside the clipping region, but it does not seem to make much difference? Since few fonts draw outside of this region, although some "Artistic" fonts draw all over the place. Use CLIP_DEFAULT_PRECIS. The fdwQuality is suppose to set how carefully the GDI must attempt to match the logical-font attributes to those of an actual physical font. I think at one time these may have applied to a "Printing" DC text draw, but most printers now have their own settings, but I have never tested to see that. The DEFAULT_QUALITY will do fine. If you only set the font's name and height, then this parameter is irrleavant. But if you set PROOF_QUALITY, then the font mapper may get a vector (TrueType) font. There is also a ANTIALIASED_QUALITY, but it does not seem to change any of the font drawing on screen or bitmap to be anti-Aliased, only to maybe pick a True Type font if your font name is not there. You can look at the examples in this Fonts program below, to see some other values used. Drawing Text on the HDC There are several HDC properties which all text drawing functions will use to determine how the text is drawn. The HDC properties are for - Text Color function SetTextColor( ); MSDN-SetTextColor Background Color function SetBkColor( ); MSDN-SetBkColor Background Mode function SetBkMode( ); MSDN-SetBkMode Text Alignment function SetTextAlign( ); MSDN-SetTextAlign Intercharacter Spacing function SetTextCharacterExtra( ); MSDN-SetTextCharacterExtra Text Jusification function SetTextJustification( ); MSDN-SetTextJustification I will not try and explain all of these Text-Formatting properties, since there is a good explanation in the API Help, under index "Text-Formatting Attributes". You can see some examples for changing these properties in the DrawIt procedure below. All of the text drawing functions will use the HDC's selected hFont for the drawing operation. The HDC's text-alignment property will be used to interpret the X and Y position parameters. There are several GDI font drawing functions used in the Fonts Program below. The most basic is the TextOut( ) function. function TextOut(DC: HDC; X, Y: Integer; Str: PChar; Count: Integer): BOOL; MSDN-TextOut Which just draws the text in the Str parameter. The DC parameter is for the hDC to be drawn on, and the X and Y are the position of the Top, Left point to start the text drawing (this is for the default Text-Align of Top-Left). The Count parameter is used to tell the system how may characters you want to draw from the Str. This Count parameter can be very useful when you only want to draw a seleted number of charactes from a long PChar string. DrawText( ) is a much more versitile function with more text drawing options. function DrawText(hDC: HDC; lpString: PChar; nCount: Integer; var lpRect: TRect; uFormat: UINT): Integer; MSDN-DrawText The API Help for this function will tell you many of the uFormat flags you can use to change how the text will be drawn. This uses a TRect instead of the X and Y integers for text positioning. By using a Rectangle this function can "Wrap" the words in a long string to fit in the rectangle, much like a windows Edit control does. You would use the DT_EDITCONTROL flag in the uFormat parameter for this text wraping. This can also Center the text in this rectangle. ExtTextOut( ) is also used in the Fonts Program. It has more options than TextOut - function ExtTextOut(DC: HDC; X, Y: Integer; Options: Integer; Rect: PRect; Str: PChar; Count: Integer; Dx: PInteger): BOOL; MSDN-ExtTextOut This function has an optional TRect which can be used to set a clipping area. There is also an optional array of Integers to set the distance between adjacent character spacing. An example of this in also in the DrawIt procedure. Setting Control Fonts Most Controls use the System font as it's default font, which may seem large if you are used to the Delphi VCL default font of MS Sans Serif. Using SendMessage( ) with the WM_SETFONT message will place that font as the Control's font. You can look at your local API Help for WM_SETFONT or web page at MSDN-WM_SETFONT. If you want the standard font look of MS Sans Serif then the easiest way is to get a Stock Object of ANSI_VAR_FONT. Like this - SendMessage(hExitBut, WM_SETFONT, GetStockObject(ANSI_VAR_FONT),0);Some other "Stock Object" Fonts are ANSI_FIXED_FONT, a fixed-pitch (monospaced) system font, DEFAULT_GUI_FONT, this is only avaible in Win95 and looks the same as ANSI_VAR_FONT, and the OEM_FIXED_FONT, this is the monospaced font that a computer uses to boot up and show a Bios info screen, rarely used in GUI. To get a "normal" windows control look, you may want to set your controls font to the Stock Object ANSI_VAR_FONT. |
This Fonts program demonstrates using fonts on a Device Context, and creating and deleting those fonts. In the previous Programs the Handle variables were set to a HWND or THandle type, which is the correct type for a HWND. But it is often neccessary to compare a Handle to an Integer, so you would need to typecaste the HWND (Cardinal) as an Integer or use abs( ) to prevent the compiler of giving you warning messages. Because the windows system will use Handles as an Integer type in many of it's functions (SendMessage and others), the value of a Handle will not exceed the 2,147,483,647 limit of an Integer, so it is safe to use the Integer type for Handles, instead of the Cardinal type (THandle). I did the same exchange of variable types in the Window's Proc MessageProc( ), I made the Result and all the parameters an Integer type. The Msg (message1) is an UINT type, which is not supported by the variable types in Delphi anyway, Delphi just casts is as a Cardinal type. The value of a Msg should not exceed the 2,147,483,647 Integer limit (the max value for messages is Hex FFFF), so using an integer will be OK here too. We need to add Commdlg to the uses clause, Commdlg has the constants and functions we need for the ChooseFont( ) Dialog Box. Let's look at the first thing after the main program begin, where a Font is created with CreateFont( ). The CreateFont( ) requires all of the font parameters to be entered for each CreatFont( ) you call, using CreateFontIndirect( ) may save some coding if you create several fonts. It uses a TLogFont record to store the font creation parameters, so you need to only enter values that change from the last font created. For Font3 the lfFaceName is set to 'v8j9k2a5', which will NOT be found, and it's lfPitchAndFamily is set to "VARIABLE_PITCH or FF_DECORATIVE". This will show you what type of font the font mapper picks from availible fonts if it can not find the font name you set. Change FF_DECORATIVE to FF_SCRIPT, and then to FF_SWISS to see if different fonts are picked. In Font4 , 5, 6 and 7 the lfEscapement is set to an angle (tenth of degree) for the text to be rotated. In the MessageProc( ) WM_PAINT message you will see SelectObject( ) and TextOut( ) that will draw these rotated fonts on the main Forms DC. More about WM_PAINT later. Look at the GetSystemFonts procedure, this uses a SystemParametersInfo( ) function to get Non Client Metrics info, from which we get some system fonts info(Caption Font, Menu Font, Status Font and Message Font). Now look at the GetFont procedure, first we set the parameters in a TChooseFont record. Next we call ChooseFont( ) with this TChooseFont as the parameter, so a Common Dialog, the Choose Font Dialog Box will be shown and the user can pick a font. The new font will be set into hLabel1 using SendMessage( ), since hLabel1's size depends on the Font size we need to resize it. I have used several "COLORREF" values to set the Text Color in this program, these are in a Hexadecimal notation, like $0000FF for the Red color. If you are not familar with this Hexadecimal notation for colors, just use the values I have given, in the next lesson I will say more about this. see comments in code for more info |
program Fonts; {this program shows some font creation and useage options and a some hDC drawing functions to paint text and shapes} uses Windows, Messages, Commdlg; {Commdlg is needed for the system Choose Font Dialog} {$R *.RES} var wClass: TWndClass; hMainForm, Font1, Font2, Font3, Font4, Font5, Font6, Font7, CaptionFont, IconFont, MenuFont, StatusFont, MessFont, hLabel1, hLabel2, hExitBut, hDrawFontBut, hChangeBut, hGetFontBut, OldObj: Integer;{THandle} {I have changed the type for these Handles from THandle , a Cardinal value, to Integer, both of these types use 4 bytes, and the value of a Handle should not exceed the 2,147,483,647 integer max. I did this because in the MessageProc the Handle value is compared to the LParam, an integer} mainMsg: TMSG; FontLog1: TLogFont; CapFontName, CapHeight: String; TempDC: HDC; FormRect: TRect; Size1: TSize; const LabelText = 'Create and Use Fonts Demo'; FontDr = ' Font Drawing '; procedure ShutDown; begin {Be sure to DeleteObject for all fonts} DeleteObject(Font1); DeleteObject(Font2); DeleteObject(Font3); DeleteObject(Font4); DeleteObject(Font5); DeleteObject(Font6); DeleteObject(Font7); DeleteObject(IconFont); DeleteObject(CaptionFont); DeleteObject(MenuFont); DeleteObject(StatusFont); DeleteObject(MessFont); PostQuitMessage(0); end; function Int2Str( Value : Int64 ) : String; var Minus : Boolean; begin Result := ''; if Value = 0 then Result := '0'; Minus := Value < 0; if Minus then Value := -Value; while Value > 0 do begin Result := Char( (Value mod 10) + Integer( '0' ) ) + Result; Value := Value div 10; end; if Minus then Result := '-' + Result; end; procedure GetSystemFonts; var NonClMetrics: TNonClientMetrics; TempLogF: TLogFont; begin NonClMetrics.cbSize := SizeOf(NonClMetrics); SystemParametersInfo( SPI_GETNONCLIENTMETRICS, // system parameter to query or set 0, @NonClMetrics, // address of NonClientMetrics 0 ); {The SystemParametersInfo function queries or sets systemwide parameters with SPI_GETNONCLIENTMETRICS you get the metrics associated with the standard nonclient area of windows.} CaptionFont := CreateFontIndirect(NonClMetrics.lfCaptionFont); CapFontName := String(NonClMetrics.lfCaptionFont.lfFaceName); MenuFont := CreateFontIndirect(NonClMetrics.lfMenuFont); StatusFont := CreateFontIndirect(NonClMetrics.lfStatusFont); MessFont := CreateFontIndirect(NonClMetrics.lfMessageFont); CapHeight := 'Caption button Height is '+ Int2Str(NonClMetrics.iCaptionHeight); {see API help for index NONCLIENTMETRICS, to see other info in NonClMetrics} SystemParametersInfo(SPI_GETICONTITLELOGFONT,SizeOf(TempLogF),@TempLogF,0); {this gets the font used under icons} IconFont := CreateFontIndirect(TempLogF); end; procedure GetFont; {this shows how to use the ChooseFont dialog to get a font and resize a control to the new font} var ChooseFont1: TChooseFont; TempRect: TRect; begin GetObject(Font3,SizeOf(FontLog1), @FontLog1); {this GetObject( ) will produce info for the font, brush or pen who's handle is sent. Notice that I have used Font3, the non-Availible v8j9k2a5 so you can see what is displayed in a ChooseFont dialog box for a font that is NOT there, run the program and do the GetFont twice to see the difference the second time after you choose a font the first time} with ChooseFont1 do begin {this is the Choose Font dialog box info to limit the chooses availible to the user} lStructSize := SizeOf(ChooseFont1); {you MUST set the lStructSize} hWndOwner := hMainForm; hDC := 0; {hDC is used mostly for printers} lpLogFont := @FontLog1; {lpLogFont will be the font selection shown in dialog} nSizeMax := 22; nSizeMin := 14; {because this font will be used in a control that is sized to the font, I need to limit the font size} Flags := CF_INITTOLOGFONTSTRUCT or CF_FORCEFONTEXIST or CF_LIMITSIZE or CF_SCREENFONTS or CF_SCRIPTSONLY; {there are many flages that can be helpful, see Win32 API Help for their definitions} lpfnHook := nil; end; {ChooseFont will fill the lpLogFont , FontLog1, with the font info} if ChooseFont(ChooseFont1) then begin GetWindowRect(hLabel1, TempRect); {we will need to redraw the MainForm's Rect where the hLabel1 was if hLabel1 is made smaller} ScreenToClient(hMainForm,TempRect.TopLeft); ScreenToClient(hMainForm,TempRect.BottomRight); {GetWindowRect( ) gets screen Coordinate and we need window points ScreenToClient will convert them} DeleteObject(Font3); Font3 := CreateFontIndirect(FontLog1); {FontLog1 has the new font info} SendMessage(hLabel1,WM_SETFONT,Font3,0); {this control's size should match the Font used so we will have to change the size of hLabel1 to the new font} TempDC := GetDC(hLabel1); OldObj := SelectObject(TempDC,Font3); GetTextExtentPoint32(TempDC, LabelText, lstrlen(LabelText), Size1); SelectObject(TempDC,OldObj); ReleaseDC(hLabel1,TempDC); MoveWindow(hLabel1,(FormRect.Right div 2)-((Size1.cx+(Size1.cx div 20)) div 2), 5,Size1.cx+(Size1.cx div 20),Size1.cy+1,False); InvalidateRect(hLabel1,nil,True); {since the font has changed, you need to make sure all of the text shown is redrawn with InvalidateRect( )} InvalidateRect(hMainForm,@TempRect,True); {if the font is smaller then the area around the Label will need to be invalidated TempRect is used so only part of MainForm is redwawn} end; end; procedure DrawIt; {this shows several different types of font drawing needed for text alignment} var Rect1: TRect; BigFont: THandle; ArryInt: Array[0..20] of Integer; i: Integer; ExText: PChar; begin TempDC := GetDC(hMainForm); SelectObject(TempDC,Font1); GetTextExtentPoint32(TempDC, FontDr, lstrlen(FontDr), Size1); SetRect(Rect1,180,FormRect.Bottom-(Size1.cy+8),Size1.cx+ 186,FormRect.Bottom-2); FillRect(TempDC,Rect1,GetStockObject(BLACK_BRUSH)); {GetStockObject is good to get system Brushs, Pens, and fonts you do NOT need to call DeleteObject for Stock Objects} TextOut(TempDC,Rect1.Left+3,Rect1.Top+3, FontDr, lstrlen(FontDr)); SelectObject(TempDC,MenuFont); SetRect(Rect1,28,170,39,190); DrawText(TempDC,'this is Menu Font',-1,Rect1,DT_SINGLELINE or DT_CALCRECT); DrawText(TempDC,'this is Menu Font',-1,Rect1,DT_SINGLELINE); {DrawText is more versitile than TextOut, here DT_CALCRECT is used to get the Rect1 size needed for DrawText, Rect1 is used again to draw a rectangle around this text, there are many other uses of DrawText which will be shown is later examples} SetTextAlign(TempDC, TA_RIGHT); {SetTextAlign( ) with TA_RIGHT will tell windows to use the X position as the Right side starting point instead of the default left side. see Win32 API Help for other alignment settings} TextOut(TempDC,460,256,'right aligned text',18); {the text is drawn starting at the right, notice that the X position is the same in the TextOut below and they will be right aligned} TextOut(TempDC,460,276,'this is over a button',21); {this will draw on top of the Exit button unless WS_CLIPCHILDREN is in the Style of the MainForm Window} SetTextAlign(TempDC, TA_Left); {restore text align to default Left} SelectObject(TempDC,StatusFont); SetTextColor(TempDC,$0000FF); SetBkColor(TempDC,$99FFFF); TextOut(TempDC,-8,150,'this is Status Font',19); {you can use negative positions for drawing} BigFont := CreateFont(-116,0,0,0,FW_BOLD,0,0,0,ANSI_CHARSET,OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,DEFAULT_QUALITY,VARIABLE_PITCH or FF_SWISS,'Arial'); {you can get very large fonts if you need them, even though many font selection boxes will stop at 72, you can go much larger, -116 above} SelectObject(TempDC,BigFont); SetTextColor(TempDC,$0000AA); SetBkColor(TempDC,$000000); TextOut(TempDC,1,3,'BIG Font',8); {2 basic drawing functions Rectangle and Ellipse} Rectangle(TempDC,114,210,332,304); {draws a Rectangle with the default pen and brush black pen and whith brush} SelectObject(TempDC,GetStockObject(GRAY_BRUSH)); Ellipse(TempDC,118,216,138,230); Ellipse(TempDC,306,216,327,230); {draws 2 Ellipses with new gray brush} SelectObject(TempDC,GetStockObject(NULL_BRUSH)); InflateRect(Rect1,2,2); {InflateRect, useful win API for Rects, it enlarges or decreases the Rect} Rectangle(TempDC,Rect1.Left,Rect1.Top,Rect1.Right,Rect1.Bottom); {this draws a rect around the MenuFont text without filling it with a brush and covering the text, because Null_Brush does not draw anything} SelectObject(TempDC,GetStockObject(ANSI_FIXED_FONT)); SetBkColor(TempDC,$FFFFFF); SetTextAlign(TempDC,TA_UPDATECP or TA_BASELINE); {SetTextAlign( ) with TA_UPDATECP will move the Pen Position to the last text drawing position. This will make drawing text from variables or different fonts alot easier. You call TextOut with X and Y set to 0. You need to call MoveToEx to set the begining point} MoveToEx(TempDC,152,227, nil); TextOut(TempDC,0,0,'pen position ',13); TextOut(TempDC,0,0,'text',4); MoveToEx(TempDC,120,248, nil); TextOut(TempDC,0,0,'Different ',10); SelectObject(TempDC,Font2); TextOut(TempDC,0,0,'Fonts ',6); SelectObject(TempDC,GetStockObject(ANSI_VAR_FONT)); TextOut(TempDC,0,0,'on same ',8); SelectObject(TempDC,Font3); TextOut(TempDC,0,0,'line',4); MoveToEx(TempDC,120,270, nil); SelectObject(TempDC,Font1); SetTextColor(TempDC,$CF0000); {since text variables can be of different lengths, TA_UPDATECP can draw the text and continue the line with the next TextOut} TextOut(TempDC,0,0,PChar(CapFontName),Length(CapFontName)); SelectObject(TempDC,Font2); TextOut(TempDC,0,0,PChar(', handle is '+Int2Str(CaptionFont)),Length(Int2Str(CaptionFont))+12); SetTextAlign(TempDC,TA_TOP); {reset text align back to Top} ExText := 'Try to LOOK at this'; for i := 0 to 20 do begin GetTextExtentPoint32(TempDC, @ExText[i], 1, Size1); ArryInt[i] := Size1.cx+1; end; {ExtTextOut can be used to do complex character positioning. ArryInt is used in ExtTextOut to set each character's begining position from the last characters begining position, So I get each characters width with GetTextExtentPoint32} for i := 8 to 10 do ArryInt[i] := 21; {ArryInt values 8, 9, and 10 are for the characters in LOOK} {a charater can draw OVER the folowing charater if the space is not enough, also narrow charaters like "l" will have a space to the right if the positioning is greater than needed, , , to see this try for i := 0 to 20 do ArryInt[i] := 5 or ArryInt[i] := 11 if you use ArryInt[i] := 0 then all the characters will be drawn at the same position on top of each other} SetRect(Rect1,166,278,252,295); {this Rect1 will be used in ExtTextOut} SetTextColor(TempDC,GetSysColor(COLOR_WINDOWTEXT)); SetBkColor(TempDC, GetSysColor(COLOR_HIGHLIGHT)); SetBkMode(TempDC,TRANSPARENT); ExtTextOut(TempDC,120,278, ETO_OPAQUE,@Rect1,ExText, 21, @ArryInt{use nil here for normal spacing}); {ExtTextOut has more options than TextOut, if ETO_OPAQUE is used then Rect1 will be filled with the background color, if you put an Array of Integers in the last parameter, then the character spacing will be set to the values of the Array, but these array values may difficult to apply to the character spacing. LOOK will be more spaced} ReleaseDC(hMainForm,TempDC); DeleteObject(BigFont); {Make sure you ReleaseDC and DeleteObject} end; procedure ChangeFonts; begin SendMessage(hLabel2,WM_SETFONT,Font3,0); {this font is too large for this static control, but window controls do NOT check or change anything that's assigned to them. You have to check and change the font assigned or change the control (size) for the font - see GetFont procedure above this WM_SETFONT message does NOT repaint hLabel2 so you need to call InvalidateRect( )} InvalidateRect(hLabel2,nil,True); SendMessage(hExitBut, WM_SETFONT, GetStockObject(ANSI_VAR_FONT),0); InvalidateRect(hExitBut,nil,True); SendMessage(hDrawFontBut, WM_SETFONT, GetStockObject(ANSI_FIXED_FONT),0); {notice the display does NOT show a new font on hDrawFontBut without InvalidateRect} end; function MessageProc(hWnd, message1, WParam, LParam: Integer): Integer; stdcall; {I have changed all the parameters in MessageProc to an Integer type, the UINT is not supported in Delphi anyway, and the value of a Handle will not go above the 2147483647 integer limit} var paintDC: HDC; PaintS: TPaintStruct; Rect1: TRect; CharSpace: Integer; begin case message1 of WM_PAINT: begin paintDC := BeginPaint(hWnd, PaintS); {the PaintS record has a rcPaint Rect, which has the Area that will be painted anything outside this Rect will NOT be drawn, even if it is in these Paint commands} if PaintS.rcPaint.Top < 37 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 displays it may not make much difference} SetRect(Rect1,1,1,FormRect.Right-2,37); FillRect(paintDC,Rect1,GetStockObject(BLACK_BRUSH)); end; SelectObject(paintDC,CaptionFont); {the BkColor defaults to white} TextOut(paintDC,190,40,PChar(#149' this is Caption Font '+CapFontName+#153), 24+Length(CapFontName)); SelectObject(paintDC,Font2); SetBkColor(paintDC,GetSysColor(COLOR_BTNFACE)); {set BkColor to match what is being drawn on} TextOut(paintDC,32,60,'Compare to text above',21); SelectObject(paintDC,Font4); TextOut(paintDC,248,114,'Font4 20 Escapement',21); SelectObject(paintDC,Font5); TextOut(paintDC,250,60,'Font5 -20 Escapement',22); SelectObject(paintDC,Font6); TextOut(paintDC,3,180,'Font6 90'#176' '#162#169#174#175#164,16); SelectObject(paintDC,Font7); TextOut(paintDC,480,70,#149' '#147'Font7'#148' 270'#176' 2'#178' 4'#179,21); {non Standard charaters #176 can be useful but are not contained in all fonts} SelectObject(paintDC,MenuFont); TextOut(paintDC,128,130,#149' this is Menu Font'#176,20); SelectObject(paintDC,StatusFont); SetTextColor(paintDC,$0000FF); CharSpace := SetTextCharacterExtra(paintDC, 8); {SetTextCharacterExtra( ) changes the amount of space between charaters and can be very useful, default CharSpace is 0} TextOut(paintDC,128,150,#149'wide spaced Status Font',24); SetTextCharacterExtra(paintDC, CharSpace); {Reset CharSpace} SelectObject(paintDC,MessFont); SetBkColor(paintDC,$FFCC99); SetBkMode(paintDC,TRANSPARENT); {setting the BkMode to TRANSPARENT will prevent the text background from being painted, leave this out to see the difference} TextOut(paintDC,128,170,#149' this is Message Font',22); SetTextColor(paintDC,$000000); SetBkMode(paintDC,OPAQUE); SelectObject(paintDC,GetStockObject(OEM_FIXED_FONT)); TextOut(paintDC,128,190,#149'this is OEM Fixed Font'#176,24); {non standard charaters #149 may not be the same in non-Microsoft fonts like the OEM font} EndPaint(hWnd,PaintS); {IMPORTANT, ALWAYS call EndPaint if you call BeginPaint in WM_PAINT} Result := 0; {If you do not Exit, DefWindowProc is called but the rcRect will be 0 because of EndPaint, so DefWindowProc does not do anything} Exit; end; WM_COMMAND: if lParam = hExitBut then PostMessage(hMainForm,WM_CLOSE,0,0) else if lParam = hDrawFontBut then DrawIt else if lParam = hChangeBut then ChangeFonts else if lParam = hGetFontBut then GetFont; {I changed the type for the Handles from a Cardinal to an Integer in the var clause above. So I do not need to use abs(hExitBut)} WM_DESTROY: ShutDown; end; Result := DefWindowProc(hWnd,message1,wParam,lParam); end; begin // Main Program begin // // // // {since Fonts are Window's System Objects I usually create them first. You must Delete these Objects before your Program ends, see ShutDown procedure above} // // // Font Creation // // {you can use CreateFont and set all the parameters} Font1:=CreateFont( -12, // Height 0, // Width 0, // Angle of Rotation 0, // Orientation FW_NORMAL, // Weight 0, // Italic 0, // Underline 0, // Strike Out ANSI_CHARSET, // Char Set OUT_DEFAULT_PRECIS, // Precision CLIP_DEFAULT_PRECIS, // Clipping DEFAULT_QUALITY, // Render Quality VARIABLE_PITCH or FF_SWISS, // Pitch & Family 'MS Sans Serif'); // Font Name {Or use CreateFontIndirect and fill in a LOGFONT record} with FontLog1 do begin lfHeight := -14; lfWidth := 0; {a lfWidth := 0 gets the default width normal width is about 6 try 5, 7 and 8 too see how it changes the displayed width of this font} {lfWidth := 5;} {lfWidth := 7;} {lfWidth := 8;} lfItalic := 0; lfWeight := FW_BOLD; {there are many constants for lfWeight, use any value between 0 and 1000 but only certain values are recogized by the font mapper, use 0 for default normal} lfCharSet := DEFAULT_CHARSET; {the params below are used only if the named font, lfFaceName, is NOT availible, then these are used to pick a substitute font, the Pitch and family beging the most important} lfOutPrecision := OUT_TT_PRECIS; {OUT_TT_PRECIS tells the font mapper to use a true type font if there are several fonts with the same name} lfClipPrecision := CLIP_DEFAULT_PRECIS; lfQuality := {DEFAULT_QUALITY}ANTIALIASED_QUALITY; {ANTIALIASED_QUALITY will choose a True Type font if your face name is not amoung the OS fonts} lfPitchAndFamily := VARIABLE_PITCH or FF_ROMAN; lfFaceName := 'Times New Roman'; end; Font2 := CreateFontIndirect(FontLog1); with FontLog1 do {you only need to reset values in FontLog1 that are different than the previous FontLog1} begin lfHeight := -20; lfWidth := 0; {if you change the width above, remember to change it back here} lfItalic := 1; {you can change the Italic and underline from 0 to 1} lfWeight := FW_NORMAL; lfPitchAndFamily := VARIABLE_PITCH or FF_DECORATIVE{FF_SCRIPT}{FF_SWISS}; {since this font will not be found, change the font family to script or swiss or roman to see what font will be picked.} lfFaceName := 'v8j9k2a5'; {there will be no Font named v8j9k2a5, this is to show you what happens if the font you name is NOT on the computer, you can never tell if a Font will be availible, even the "Standard" windows fonts like 'MS Sans Serif' or 'Arial' may have been deleted, so if a control's size or function depends on the font metrics, you may want to make provisions for that, see hLabel1 below and the WM_PAINT in the MessageProc } end; Font3 := CreateFontIndirect(FontLog1); with FontLog1 do begin lfHeight := -14; lfWidth := 0; lfItalic := 0; lfEscapement := 200; {lfEscapement is the amount of rotation in tenths of degree} lfOrientation := lfEscapement; {set the lfOrientation equal to lfEscapement} lfPitchAndFamily := VARIABLE_PITCH or FF_SWISS; lfFaceName := 'Arial'; end; Font4 := CreateFontIndirect(FontLog1); with FontLog1 do begin lfHeight := -14; lfEscapement := 3400; lfOrientation := lfEscapement; end; Font5 := CreateFontIndirect(FontLog1); with FontLog1 do begin lfHeight := -16; lfWeight := 0; lfEscapement := 900; {this font is rotated 90 degrees} lfOrientation := lfEscapement; end; Font6 := CreateFontIndirect(FontLog1); with FontLog1 do begin lfHeight := -16; lfEscapement := 2700; lfOrientation := lfEscapement; lfPitchAndFamily := VARIABLE_PITCH or FF_ROMAN; lfFaceName := 'Times New Roman'; end; Font7 := CreateFontIndirect(FontLog1); GetSystemFonts; // // // End of Font Creation // // // // // // Begin the MainForm Creation // // // wClass.hInstance := hInstance; with wClass do begin {no Style parametes are given} Style := 0; hIcon := LoadIcon(hInstance,'MAINICON'); lpfnWndProc := @MessageProc; hbrBackground := COLOR_BTNFACE+1; lpszClassName := 'mainForm Class'; hCursor := LoadCursor(0,IDC_ARROW); end; RegisterClass(wClass); hMainForm := CreateWindow( wClass.lpszClassName, // pointer to registered class name PChar(' Using Fonts - '+CapHeight), // pointer to window name (title bar Caption here) WS_OVERLAPPEDWINDOW {or WS_CLIPCHILDREN}, // window style {WS_OVERLAPPEDWINDOW is the default standard main window with a Title bar and system menu and sizing border WS_CLIPCHILDREN will prevent drawning over top of child controls see DrawIt above. Add WS_CLIPCHILDREN and see the difference with the DrawIt procedure} (GetSystemMetrics(SM_CXSCREEN) div 2)-276, // horizontal position of window (GetSystemMetrics(SM_CYSCREEN) div 2)-224, // vertical position of window 500, // window width 356, // 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 ); TempDC := GetDC(hMainForm); {I need to get the text size for hLabel1, but it has not been created yet. So I use a window that has been created, since the font metrics will be the same} OldObj := SelectObject(TempDC,Font3); {for a Device Context (HDC, see Win32 API help for "Device Contexts", like a Canvas.Handle in Delphi) you need to SelectObjects (fonts, pens, brushes) to use in Drawing on that DC} GetTextExtentPoint32(TempDC, LabelText, lstrlen(LabelText){25}, 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} SelectObject(TempDC,OldObj); {Restore the default font to your form's HDC, this is not necessary if you don't use that HDC again, the Font stays selected even if you release the DC} 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); {Get the FormRect to be used to get positions for painting on the form} hLabel1 := CreateWindow('Static', LabelText, WS_VISIBLE or WS_CHILD or SS_CENTER,(FormRect.Right div 2)-((Size1.cx+(Size1.cx div 20)) div 2), 5, Size1.cx+(Size1.cx div 20),Size1.cy+1,hMainForm,0,hInstance,nil); {hLabel1 is assigned a Font name (v8j9k2a5) that will not be found, 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); hLabel2 := CreateWindow('Static', 'Compare to text below', WS_VISIBLE or WS_CHILD or SS_LEFT,32,40,150,16,hMainForm,0,hInstance,nil); SendMessage(hLabel2,WM_SETFONT,Font2,0); {Static controls do NOT get user input (keyboard or mouse), I use it here like Delphi's TLabel. But you can just Draw the text in the WM_PAINT. See "Compare to text Above" in the paint message} hExitBut := CreateWindow('Button','Exit', WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT or BS_CENTER or WS_GROUP, 380,280,64,28,hMainForm,0,hInstance,nil); SendMessage(hExitBut, WM_SETFONT, GetStockObject(SYSTEM_FIXED_FONT),0); hGetFontBut := CreateWindow('Button','Get new Font', WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT, 20,206,88,27,hMainForm,0,hInstance,nil); SendMessage(hGetFontBut,WM_SETFONT,Font1,0); hChangeBut := CreateWindow('Button','Change Fonts', WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT, 20,246,80,27,hMainForm,0,hInstance,nil); SendMessage(hChangeBut,WM_SETFONT,Font1,0); hDrawFontBut := CreateWindow('Button','Draw It', WS_VISIBLE or WS_CHILD or BS_PUSHBUTTON or BS_TEXT, 20,286,74,27,hMainForm,0,hInstance,nil); SendMessage(hDrawFontBut,WM_SETFONT,Font1,0); ShowWindow(hMainForm, SW_SHOWNORMAL); {the WS_VISIBLE style was NOT set in the Main window creation} 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; CapHeight := ''; CapFontName := ''; // empty strings to release memory end. |
You should change the font creation paramerers and experiment with how a font will change (or not change), when you use different parameter values. Try and change the lfHeight with positive AND negative numbers, change the lfWidth and see how you might use this for special font display (wider or narrower text). Change the lfItalic, lfUnderline, and lfStrikeOut parameters. Be sure to experiment the lfWeight parameter, see if your windows OS will give more font weights than normal and bold. See if changing the lfQuality or lfOutPrecision will change anything when the font is displayed. It may be useful for you to learn how to get the width and height of a block of text in a certain font. You might try and make a button that will adjust it's size (width and height) to fit the size of the Font that is used for it, like what was done with hLabel1. Since text is a fundamental element of computer display, you should practice using the text drawing functions like TextOut( ) and DrawText( ) until you feel you know how they can be used. |
Next We have made a program that use fonts and basic drawing. Next we'll use brushes and pens and try out a Popup window as a splash screen. A Timer is also used. Lesson 6, Brushes, Pens and Timers |