Introduction to Device Contexts |
|
|
A device context is an ensemble of the tools needed to
draw lines, shapes, and other graphics. It includes the platform you draw
on, the dimensioning of the platform, the orientation and other variations
of your drawing, the tools you need to draw on the platform, the colors, and
various other accessories that can complete your imagination. To provide
support for drawing on the Windows operating system, Microsoft created the
Graphical Device Interface, abbreviated as GDI. It is a set of classes,
functions, variables, and constants that group all or most of everything you
need to draw on an application. The GDI is provided as a library called
Gdi.dll and is already installed on your computer.
|
Practical
Learning: Introducing Graphics
|
|
- Start Embarcadero RAD Studio
- To create a new project, on the main menu, click File -> New ->
VCL Forms Application - C++Builder
- In the Object Inspector, click Caption and type Nature
and Resources
- Click Name and type frmMain
- To save the project, on the Standard toolbar, click the Save All
button
- Click the Create New Folder
- Type NatureResources1 as the name of the folder
and press Enter twice to display its content
- Change the file name to Nature and press Enter
- Change the project name as NatureResources and
press Enter
- Right-click the following picture and click Copy
- Use a file utility such as Windows Explorer and locate the folder
that contains this project
- Right-click it and click Paste
In a Win32 application, in order to draw, you must
create a device context. This can be taken care of by declaring a variable
of type HDC. To keep track of the various drawings, the device
context uses a coordinate system that has its origin (0, 0) on the
top-left corner of the desktop:
Anything that is positioned on the screen is based on
this origin. This coordinate system can get the location of an object
using a horizontal and a vertical measurement. The horizontal measures are
based on an x axis that moves from the origin to the right direction. The
vertical measures use a y axis that moves from the origin to the bottom
direction:
This means that, if you start drawing something such
as a line, it would start on the origin and continue where you want it to
stop. To significantly simplify drawing and make it compatible with the
VCL, the library has a class called TCanvas. TCanvas is
derived from the TCustomCanvas class that is based on
TPersistent, which is derived from TObject:
To further make it easy to draw, every control that
needs drawing already has a TCanvas variable available. This means
that you usually will not have to declare a TCanvas variable before
drawing.
In a VCL application, a canvas is the object on which you
draw but the TCanvas class actually includes everything that can be
used to draw. This includes the platform (like a piece of paper on which
you draw) and the tools (like the pens and colors, etc).
To support graphics and drawing, the Win32 library
provides a handle named HDC (handle to the device context). To give you
access to this handle, the TCanvas class is equipped with a
property named Handle, which is of type HDC:
__property HDC__ * Handle = {read=GetHandle,write=SetHandle};
The Handle property allows you to
perform additional operations that may not be available in the
TCanvas class.
A bitmap is a graphic object used to display a picture
on a window or to store it in the computer memory as a file. It is the
primary type of graphics used for various occasions. For example, a bitmap
can be used as a background for a window. That is the case for the Pinball
game that ships with some versions of Microsoft Windows:
A bitmap can also be used for aesthetic purposes to
decorate a dialog box. Probably the most regular use of bitmaps is as small
graphics on toolbars:
There are three main ways you create or add a bitmap
to your application. You can create an array of byte values that describe
the bitmap. You can design a bitmap using a low-level bitmap application
like Image Editor, or you can use a professional picture.
Visually Creating a Bitmap
|
|
There are three types of bitmaps we will be creating
for our lessons. The simplest consists of designing a regular picture made
of colors from an (external) application such Windows Paint. Another
technique consists of declaring an array of bits that describes the
bitmap; then translate this array into a handle to bitmap before actually
using it. The last technique, which requires some design or importing,
consists of using a more advance picture in an application.
Creating a bitmap and making it available to an
application is just one aspect. The goal is to use such a bitmap or to
decide what to use it for. Normally, the way you create a bitmap has some
influence on where and how that bitmap is used.
Paint (or Windows Paint) is an application that gets
installed with Microsoft Windows. It provides a cheap solution to creating
or maipulating bitmaps:
Introduction to the VCL Support For Bitmaps
|
|
Programmatically Creating a Bitmap
|
|
The VCL provides support for bitmaps through the
TBitmap class from the Graphics namespace. The
TBitmap class is based on TGraphic. The
TGraphic class is derived form
TInterfacedPersistent that itself is naturally based on the
TPersistent class:
Some classes already have a Bitmap property that you
can use to initialize appropriately. In most cases, you will need to
declare a pointer to TBitmap. To do this, you must
precede the name of the class with the Graphics
namespace. Here is an example:
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
Graphics::TBitmap *bmpSomething = new Graphics::TBitmap;
}
//---------------------------------------------------------------------------
Like all other dynamic objects, after using the
bitmap, you should delete it. If you declare it locally, you can also
delete it in the same event or method. Here is an example:
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDblClick(TObject *Sender)
{
Graphics::TBitmap *bmpSomething = new Graphics::TBitmap;
try {
// Treat the bitmap file here
}
__finally
{
// Time to delete the pointer
delete bmpSomething;
}
}
//---------------------------------------------------------------------------
If you declare the variable globally, make sure you
delete it when the application is destroyed. After declaring the variable,
you must initialize it appropriately before actually using it.
A picture is one of those of those objects that
consume computer resources. This means that, if you create a picture, use
it as you see fit. Once you don't need it anymore, you should release its
resources to make them available to other applications. To free the
resources of a bitmap, you have various options.
If you are using a picture locally, to declare its
pointer, you can use the auto_ptr class of the
std namespace of the STL. Here is an example:
//---------------------------------------------------------------------------
#include <vcl.h>
#include <memory>
#pragma hdrstop
#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormCreate(TObject *Sender)
{
std::auto_ptr<Graphics::TBitmap> bmpSomething(new Graphics::TBitmap);
}
//---------------------------------------------------------------------------
In this case, you let the operating system take care
of memory. In other words, once the pointer is not used anymore, such as
when the application closes, the operating system will delete the object
and reclaim the memory area the variable was using.
To support its own ability to free resources used by
its descendants, the TObject class is equipped with a
method named Free. Its syntax is:
int __fastcall Free(void);
Call this method to reclaim the resources that a
pointer was using once you know the pointer doesn't need those resources
anymore.
Once the file is ready, you can use it in your
application. For example, you can display it on a form. Because the form
is equipped with a canvas, to display a picture, you can call the
TCanvas::Draw() method. Its syntax is:
void __fastcall Draw(int X, int Y, TGraphic* Graphic);
The X and Y values specify the
corner from where to start drawing. Normally, it will be the top-left
coordinates of the picture. The Graphic parameter is the graphic
object to draw on the canvas. This would be done as follows:
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDblClick(TObject *Sender)
{
Graphics::TBitmap *bmpSomething = new Graphics::TBitmap;
try {
Canvas->Draw(10, 10, bmpSomething);
}
__finally
{
delete bmpSomething;
}
}
//---------------------------------------------------------------------------
Bitmap Loading From a File
|
|
In order to use a bitmap in your application, you must
import it from its file to the application. The easiest way to do this
consists of calling the TGraphic::LoadFromFile() method. Its syntax
is:
virtual void __fastcall LoadFromFile(System::UnicodeString Filename);
This method is particularly easy to use as long as you
know the location of the bitmap file. If the picture is in the same
directory as the current project, you can simply type its name and its
extension as a string and pass it as argument. An example would be:
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDblClick(TObject *Sender)
{
Graphics::TBitmap * picture = new Graphics::TBitmap;
try {
picture->LoadFromFile("student.bmp");
Canvas->Draw(10, 10, picture);
}
__finally
{
delete picture;
}
}
//---------------------------------------------------------------------------
If the file is not in the same directory as the
project, you may have to specify its complete path. Here is an example:
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDblClick(TObject *Sender)
{
Graphics::TBitmap * picture = new Graphics::TBitmap;
try {
picture->LoadFromFile("C:\\Exercise\\student.bmp");
Canvas->Draw(10, 10, picture);
}
__finally
{
delete picture;
}
}
//---------------------------------------------------------------------------
If the file, its path, and its extension are correct,
the file can be used:
If the file does not exist when you try accessing it,
in other words, if the file or the path you specified is not valid (the
file and the path are not checked at compilation time, they are checked
when the application is asked to retrieve the bitmap), you would receive
an error. The exception thrown is of type EFOpenError
(which stands for Exception-File-Open-Error), meaning that the information
given about opening the file is incorrect somewhere. You can display our
own message as follows:
//---------------------------------------------------------------------------
void __fastcall TForm1::FormDblClick(TObject *Sender)
{
Graphics::TBitmap * picture = new Graphics::TBitmap;
try {
try {
picture->LoadFromFile("C:\\Exercise\\something.bmp");
Canvas->Draw(10, 10, picture);
}
catch(EFOpenError *Error)
{
Application->MessageBox(L"The file path, its name, or its extension"
L"may be invalid or they don't exist.",
L"Exercise", MB_OK);
}
}
__finally
{
delete picture;
}
}
//---------------------------------------------------------------------------
Bitmap Loading From a Resource File
|
|
Another technique you can use to open a bitmap
consists of retrieving it from a resource file. Before doing this, you
must have a resource file with .res extension. You can create a Windows
resource file that has the .rc extension and create a header file that
lists its resources. The header file is used to specify a constant number
for each resource. For this example, the header file can be named
resource.h.
After creating the header file, you can create a
resource file, which is a text file with an extension of .rc and, in the
file, each resource can be specified using the name of the constant
created in the header file.
After creating the resource file, you must import it
into your project. This is done by click Project -> Add to Project… from
the main menu, selecting the rc file and clicking Open. After adding the
resource file, you should compile it to produce a .res file. This makes
your file ready.
Once a bitmap in a resource file is ready, to use
it in your application, you can call the LoadFromResourceName()
method. Its syntax is:
void __fastcall LoadFromResourceName(unsigned Instance, const AnsiString ResName);
The easiest way to do this is to create the resource
file in the same directory as the project that is using it. This is
because the LoadFromResourceName() method requires the instance of
the executable file that contains the resource file. If it is located in
the same folder, you can simply pass the instance of the current project
as the Instance argument. The second parameter, ResName, is the name of
the bitmap to be loaded. Normally, it should be the identifier of the
bitmap as defined in the header file.
Bitmap Loading From a Resource Identifier
|
|
One more alternative you have into preparing a picture
to display in your application consists of using an identifier from a
resource file. Since the steps are exactly the same as those used above,
we will follow them in a Practical Learning session.
Practical
Learning: Loading a Bitmap From Resource
|
|
- On the main menu of C++Builder, click File -> New -> Other…
- In the left list of the New Items dialog box, click C++Builder
Files
- In the right list, click Header File
- Click OK
- In the empty file, type
#define PAPAYATREE 1001
- Save the file as resource.h and make sure you
include the extension. Also, make sure you save it in the folder of
the current project
- On the Standard toolbar of C++Builder, click the New Items button
- In the left list of the New Items dialog box, click Other Files
- In the right list, click Text File
- Click OK
- In the New File dialog box, scroll down and click Resource Script
- Click OK
- empty file, type:
#include "resource.h"
PAPAYATREE BITMAP "papaya.bmp"
- To save the file, on the Standard toolbar, click the Save button
- Type natural.rc as the file name:
- Click Save
- While the natural.rc tab is displaying, to compile it, on the main
menu, click Project -> Build natural.rc
- When the compilation is complete, on the Build Unit dialog box,
click OK
- On the Object Inspector, click the Events tab and access the
OnPaint event of the form
- Implement it as follows:
//---------------------------------------------------------------------------
#include <vcl.h>
#pragma hdrstop
#include "Nature.h"
#include "resource.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
: TForm(Owner)
{
}
//---------------------------------------------------------------------------
void __fastcall TForm1::FormPaint(TObject *Sender)
{
Graphics::TBitmap *bmpPapaya = new Graphics::TBitmap;
try {
try {
bmpPapaya->LoadFromResourceID((int)HInstance, PAPAYATREE);
Canvas->Draw(0, 0, bmpPapaya);
}
catch(EResNotFound *Problem)
{
ShowMessage(L"The resource file was not found "
L"or its name is incorrect.");
}
catch(...)
{
ShowMessage("The picture cannot be displayed...");
}
}
__finally
{
delete bmpPapaya;
}
}
//---------------------------------------------------------------------------
- To execute the application, press F9
- Close the form and return to your programming environment
- To start a new application, on the main menu, click File -> New ->
VCL Forms Application - C++Builder
- In the Object Inspector, change the following properties:
Caption: Picture Viewer
FormStyle:
fsMDIForm
Name: frmPictureViewer
Position: poScreenCenter
- In the Tool Palette, click Dialogs
- Click the OpenFileDialog button
and click the form
- In the Object Inspector, change its characteristics as follows:
(Name): dlgOpen
Title: Open a Picture
- In the Tool Palette, click Standard
- Click the TMainMenu button
and click the form
- On the form, right-click MainMenu1 and click Menu Designer...
- Right-click somewhere in the Menu Designer and click Insert From
Template...
- In the Insert Template dialog box, click MDI Frame Menu
- Click OK
- In the Menu Designer, click the box under Exit
- In the Objects Inspector, change the following properties:
Caption: &Close
Enabled: (Uncheck to get)
False
Name: mnuFileClose
- Click the box under the Close menu item
- In the Object Inspector, click Caption, type - and press Enter
- Move the Close menu item, and the separator to position them under
Save As...
- Double-click the Close menu item
- Change the document as follows:
//---------------------------------------------------------------------------
void __fastcall TfrmPictureViewer::mnuFileCloseClick(TObject *Sender)
{
// If there is at least one child document, close it
if( MDIChildCount > 0 )
ActiveMDIChild->Close();
// After closing the last document,
// check the number of current chil forms.
// If there is none, disable the Close menu item
if( MDIChildCount == 0 )
mnuFileClose->Enabled = False;
}
//---------------------------------------------------------------------------
- In the Menu Designer, click File and double-click Exit
- Close the Menu Designer
- Implement the event as follows:
//---------------------------------------------------------------------------
void __fastcall TfrmPictureViewer::Exit1Click(TObject *Sender)
{
Close();
}
//---------------------------------------------------------------------------
- To add a new form, on the main menu, click File -> New -> Form -
C++Builder
- In the Object Inspector, change the Name to frmView
- On top of the Code Editor, click Unit1.cpp to display the first
form
- On the main menu, click File -> Use Unit...
- In the Use Unit dialog box, click Unit2.cpp
- Click Header and click OK