Word file

advertisement
Windows Graphics Principles
Erasing the Background
There is a Windows message WM_ERASEBKGND. When this message is
received by a window, the default processing erases the background, which
means that the client area of the window is filled with the window's
background color. Each window has a background color. If you do nothing
about changing it, your view class's window will have background color
white, if you have derived it from CFormView, or possibly gray if you
derived it from CFormView.
Normally, each WM_PAINT message is preceded by a
WM_ERASEBKGND, so that your OnDraw code draws into a nice clean
window (or more precisely, a nice clean clipping rectangle, since the erasing
of the background is also affected by clipping).
But, sometimes you may want to prevent erasing the background, as in
animation or dragging code it can sometimes cause flicker. One way to do
that is to use Invalidate(FALSE) instead of just Invalidate(). If that doesn't
do the job you can add a message handler for WM_ERASEBKGND using
Class Wizard, and just put in one line of code:
return FALSE;
// Do nothing and don't call the default handler for this message.
CWnd::Invalidate
void Invalidate( BOOL bErase = TRUE );
Parameters
bErase
Specifies whether the background within the update region is to be erased.
Remarks
Invalidates the entire client area of CWnd. The client area is marked for painting
when the next WM_PAINT message occurs. The region can also be validated before a
WM_PAINT message occurs by the ValidateRect or ValidateRgn member function.
The bErase parameter specifies whether the background within the update area is to
be erased when the update region is processed. If bErase is TRUE, the background
is erased when the BeginPaint member function is called; if bErase is FALSE, the
background remains unchanged. If bErase is TRUE for any part of the update region,
the background in the entire region, not just in the given part, is erased.
Windows sends a WM_PAINT message whenever the CWnd update region is not
empty and there are no other messages in the application queue for that window.
Drawing Lines
The CDC class maintains as a data member the "current position".
CDC::MoveToMoveTo
This member function moves the current position to the point specified by x and y (or by
point).
Syntax
CPoint MoveTo( int x, int y );
CPoint MoveTo( POINT point );
CDC::LineTo
This member function draws a line from the current position up to, but not including, the
point specified by x and y (or point).
Syntax
BOOL LineTo( int x, int y );
BOOL LineTo( POINT point );
Thus a complex figure (such as a polygon) would be drawn by one MoveTo
to set the starting position, followed by a sequence of LineTo calls, possibly
controlled by a for-loop.
Remember that the coordinates (by default) are "client coordinates",
measured in pixels with (0,0) at the upper left of the client area.
You may have to compute coordinates using mathematical functions such as
cosine or sine. If so, in C++ you need to put the line
#include <math.h>
at the top of the file to get the functions cos(x) and sin(x), etc., recognized.
(In Java you would have to mention or import the math package; in C++
you just use this include command.)
Selecting and deselecting tools
Drawing is done with "pens" and painting is done with "brushes". The
device context holds one pen and one brush at any given time. This is called
the "selected" pen or brush. The selected pen and brush will be used by the
graphics commands. By default, you get a one-pixel-wide black pen and a
solid black brush. If you want any other width or color, you must do
something about getting the right pen or brush selected.
Pens and brushes occupy memory and hence have to be created and
destroyed. The destructors of pens and brushes delete the C++ wrappers for
the Windows objects and also delete the underlying Windows object, by
calling the Windows API function DeleteObject: You don't call this
function directly.
CGdiObject::DeleteObject
BOOL DeleteObject( );
Return Value
Nonzero if the GDI object was successfully deleted; otherwise 0.
Remarks
Deletes the attached Windows GDI object from memory by freeing all system
storage associated with the Windows GDI object. The storage associated with the
CGdiObject object is not affected by this call. An application should not call
DeleteObject on a CGdiObject object that is currently selected into a device
context.
Note the last line of that entry: one cardinal rule of Windows programming
is that you may not destroy an object that is currently selected into a device
context.
The correct coding sequence:
Create a pen (or get one that exists already)
Select the pen
do your drawing
Select another pen (perhaps the original one)
Delete the pen
Here it is in code (which belongs in OnDraw; pDC is passed to OnDraw).
CPen *RedPen = new CPen(PS_SOLID, 1, RGB(255,0,0)); // create
CPen *OldPen = pDC->SelectObject(RedPen);
...// do some drawing with pDC->MoveTo and pDC->LineTo
pDC->SelectObject(OldPen); // deselect
delete RedPen;
Alternately:
CPen RedPen(PS_SOLID,1,RGB(255,0,0)); //create local object
CPen *OldPen = pDC->SelectObject(&RedPen);
// draw
pDC->SelectObject(OldPen);
// RedPen deleted automatically on exit from OnDraw
Failure to delete the objects when you should will clog up memory and
might even bring your application to a grinding halt. (In Win16 it used to
bring the whole operating system to a grinding halt!)
For inexperienced C++ programmers: Let's take a closer look at these two
code samples.
CPen *RedPen = new CPen(PS_SOLID, 1, RGB(255,0,0)); // create
CPen *OldPen = pDC->SelectObject(RedPen);
...// do some drawing with pDC->MoveTo and pDC->LineTo
pDC->SelectObject(OldPen); // deselect
delete RedPen;
Alternately:
CPen RedPen(PS_SOLID,1,RGB(255,0,0)); //create local object
CPen *OldPen = pDC->SelectObject(&RedPen);
// draw
pDC->SelectObject(OldPen);
// RedPen deleted automatically on exit from OnDraw
What is the difference between these two code samples? In the first one,
RedPen, which lives on the stack, is only a pointer (4 bytes of space)
containing the address of the actual Pen data structure, which is allocated on
the heap by the new command. This data allocated on the heap will remain
"in use" until it is freed by the delete command. In the second coding
example, RedPen is not just a pointer, but the actual CPen object. It lives
on the stack, and the space it occupies will be considered available again
when control exits from OnDraw. We say that the "stack frame" of
OnDraw has been popped from the stack. The next function call will
overwrite the space used by RedPen.
This is probably an oversimplification: probably the MFC class CPen
contains a pointer to a Win32 PEN object. We don't care, because when
OnDraw exits, the C++ code generated by the compiler will call the
destructor of CPen, since RedPen is a class variable, and not just a pointer.
This destructor will delete the GDI object (pen) "wrapped" by the CPen
object.
CDC::Ellipse
This member function draws an ellipse.
Syntax
BOOL Ellipse( int x1, int y1, int x2, int y2 );
BOOL Ellipse( LPCRECT lpRect );
The rectangle specifies the bounding box of the ellipse.
A circle is a special case of an ellipse. But because pixels are not
necessarily square, you are not guaranteed to get a circle by specifying the
height and width of the rectangle to be equal. That is, a rectangle with
equal height and width (in pixels) is not necessarily geometrically square,
though on many monitors it will be, in particular in our lab.
Preparation for Friday's lab
Let's take a simple exercise: Draw a big red (filled) circle on the screen.
We will need a red brush. There are two plans: create the brush in OnDraw
and let it be destroyed automatically on exit from OnDraw. Or, create the
brush as a member variable of the view class, initialize it in the constructor,
and destroy it in the destructor. We will illustrate both methods.
First, making the brush a member variable. Then we add a member
variable m_RedBrush to the view class. In the constructor of the view class
we then call
m_RedBrush.CreateSolidBrush(RGB(255,0,0));
Then we must put
m_RedBrush.DeleteObject();
in the destructor of the view class.
In OnDraw we write
CBrush *oldBrush = pDC->SelectObject(&m_RedBrush);
pDC->Ellipse(10,10,50,50);
pDC->SelectObject(oldBrush);
for example to draw a circle. (Remember, it's not guaranteed to be circular
on all monitors, but it will do for now.)
To prepare for the lab, you should create a project and actually write and
run this code. Inexperienced C++ programmers should carefully note the
use of * and & in this code. The declaration CBrush *oldBrush should
be read as, oldBrush is of type, pointer to CBrush. That is, the * goes with
CBrush rather than with oldBrush. Since SelectObject needs a pointer
variable, we have to pass &m_RedBrush, but oldBrush is already a pointer
variable, so &oldBrush would be a mistake.
Now for Plan B. Let's use a green brush to demonstrate that. This time the
code is entirely in OnDraw:
CBrush greenBrush;
greenBrush.CreateSolidBrush(RGB(0,255,0));
CBrush *oldBrush = pDC->SelectObject(&greenBrush);
pDC->Ellipse(10,10,50,50);
pDC->SelectObject(oldBrush);
// greenBrush.deleteObject() is not necessary
Why is it not necessary to delete the Win32 brush attached to greenBrush?
Because
(1) C++ calls the destructor of class variables allocated on the stack,
when the function exits (i.e., when the variable goes out of scope), and
(2) The destructor of the CBrush class calls deleteObject.
The first two lines above can be replaced by using one of the constructors of
the CBrush class:
CBrush greenBrush(RGB(0,255,0)); // correct C++ syntax for constructor
as a minor variation on Plan B. Note that you don't say
CBrush greenBrush = CBrush(RGB(0,255,0)); // wrong
Now here's Plan C: allocate the brush locally, but on the heap.
CBrush *pGreenBrush = new CBrush();
pGreenBrush->CreateSolidBrush(RGB(0,255,0));
CBrush *oldBrush = pDC->SelectObject(pGreenBrush);
pDC->Ellipse(10,10,50,50);
pDC->SelectObject(oldBrush);
delete pGreenBrush; // this calls greenBrush->deleteObject for you.
Again, inexperienced C++ programmers should pay close attention to the
use of * and & in the above code samples. Type in and run all these three
approaches.
CDC::FillRect
This member function fills a given rectangle using the specified
brush.
Syntax
void FillRect( LPCRECT lpRect, CBrush* pBrush );
CDC::FillSolidRect
This member function fills the given rectangle with the specified
solid color.
Syntax
void FillSolidRect( LPCRECT lpRect, COLORREF clr );
void FillSolidRect( int x, int y, int cx, int cy, COLORREF clr );
CDC::Rectangle
This member function draws a rectangle using the current pen. The
interior of the rectangle is filled using the current brush.
Syntax
BOOL Rectangle( int x1, int y1, int x2, int y2 );
BOOL Rectangle( LPCRECT lpRect );
Drawing an unfilled rectangle is done by selecting a NULL_BRUSH, which
is transparent.
CBrush * pOldBrush =
(CBrush *) pDC->SelectStockObject(NULL_BRUSH);
pDC->Rectangle(10,10,100,100);
pDC->SelectObject(pOldBrush);
This illustrates the fact that there are some brushes and pens you do NOT
have to create. The explicit cast (Cbrush *) is needed because
SelectStockObject returns a pointer to CGDIObject, which could be a
pointer to a font, brush, or pen. Without the cast the code won't compile.
CDC::SelectStockObject
virtual CGdiObject* SelectStockObject( int nIndex );
Return Value
A pointer to the CGdiObject object that was replaced if the
function is successful. The actual object pointed to is a CPen,
CBrush, or CFont object. If the call is unsuccessful, the
return value is NULL.
Parameters
nIndex
Specifies the kind of stock object desired. It can be one of
the following values:
 BLACK_BRUSH Black brush.
 DKGRAY_BRUSH Dark gray brush.
 GRAY_BRUSH Gray brush.
 HOLLOW_BRUSH Hollow brush. [synonym for
NULL_BRUSH]
 LTGRAY_BRUSH Light gray brush.
 NULL_BRUSH Null brush. [does nothing]
 WHITE_BRUSH White brush.
 BLACK_PEN Black pen.
 NULL_PEN Null pen. [does nothing]
 WHITE_PEN White pen.
[font objects omitted here..]
If you just want to create a stock object but not select it into your device
context, you need CreateStockObject. There is a function in the SDK
called GetStockObject, which retrieves a handle to the stock object you
want, but the handle still needs to be wrapped in a CBrush (or CPen, etc.)
class. Here's sample code:
CRect r;
r.SetRect(10,10,100,100);
CBrush BlackBrush;
BlackBrush.CreateStockObject(BLACK_BRUSH);
pDC->FillRect(&r,&BlackBrush);
Text Output
Text is not drawn with pens and brushes. The currently selected pen and
brush have no effect on text output. The workhorse function for text output
is TextOut:
CDC::TextOut
virtual BOOL TextOut( int x, int y, LPCTSTR
lpszString, int nCount );
BOOL TextOut( int x, int y, const CString& str );
Return Value
Nonzero if the function is successful; otherwise 0.
Parameters
x
Specifies the logical x-coordinate of the starting
point of the text.
y
Specifies the logical y-coordinate of the starting
point of the text.
lpszString
Points to the character string to be drawn.
nCount
Specifies the number of bytes in the string.
str
A CString object that contains the characters to be
drawn.
Remarks
Writes a character string at the specified location
using the currently selected font.
Character origins are at the upper-left corner of the
character cell. By default, the current position is not
used or updated by the function.
To change the size or style of text, you have to change the font. This will be
the subject of a later lecture. To change the color is easy:
CDC::SetTextColor
virtual COLORREF SetTextColor( COLORREF
crColor );
Return Value
An RGB value for the previous text color.
Parameters
crColor
Specifies the color of the text as an RGB color value.
Remarks
Sets the text color to the specified color. The system
will use this text color when writing text to this
device context and also when converting bitmaps
between color and monochrome device contexts.
If the device cannot represent the specified color,
the system sets the text color to the nearest physical
color. The background color for a character is
specified by the SetBkColor and SetBkMode
member functions.
Download