Navigation
[ Illustration | Examples | Function reference | Tips & tricks | To do | Download | Latest ]
A Memory Device Context (CMemDC)
I recently downloaded the CMemDC
class published by Keith Rule (see his article here at codeguru's).
It faces the following problem: If you perform rather huge operations on a device context (in an OnPaint()
, for example), your display sometimes flickers around rather badly.
To circumvent this problem, Keith has designed CMemDC
which creates a compatible "memory" device context in which you draw alternatively.
After you finished drawing to this device context (in memory), you clip it to the original one - only one blit takes place in the visible context. Therefore, the output doesn't flicker anymore (nearly, at least).
However, his implementation assumes that you always want to draw the entire clipping area - even if it has already been filled up by WM_ERASEBKGND
or sutff.
Why Rotating Text Isn't That Easy
Moreover, I recently designed a new button that displays it text rotated.
Now, you can create a CFont
that outputs text rotated (use a LOGFONT
and set lfOrientation
and lfEscapement
) but this works AFAIK for TrueType
fonts only - it doesn't work for MS Sans Serif at last.
At least, a CDC's text functions seem to get up confused if you set up such a font - the produce "random" results ... try it if you don't believe me :O
Therefore, I had a look at Keith' CMemDC
and then at the CDC::PlgBlt()
function. And I decided to extend the CMemDC
to a "rotatable" memory context, i.e., a memory context that does not only clip one memory bitmap to an output device context only, but one that even rotates it if you like it to.
cdxCRot90DC - A Rotatable Memory DC
Well, cdxCRot90DC
takes place to solve these problems. Next to specifying a "destination" device context (your output device, normally), it allows you to even provide a rectangle out of it (CMemDC
always used the current output context's clip box) and an "even angle" of 0°, 90°, 180°, 270°, ... (that's why it's called cdxCRot90DC
and not cdxCRotDC
:).
Note: I use the term "even angle" when I mean an angle dividable by 90 without any rest.
Moreover, note that if I talk about an angle x, the same applies to x+n*360 and x-n*360, n out of N.
An example may illustrate what the device context does (using cdxCRot90DC
with an angle of 90°):
You may acknowledge that the rotation is not limited to text - you can freely draw into the memory device context as you like and the result will be put into your original one as having been drawn rotated - unfortunately, you cannot draw into the cdxCRot90DC
using your original rectangle - you need to use the one transformed automatically for you (get it using GetRotRect()).
If you choose an "even angle" of 0°, the cdxCRot90DC
class basically acts like CMemDC
with some advantages:
- You can freely define the rectangle of your destination DC you want to work with [see constructor/Create()]
- You can initially copy the previous device context's contents into your memory bitmap (that preserves any work done by
WM_ERASEBKGND
or stuff that is been drawn by an ownerdrawn control if you use its DrawItem()
function) [see constructor/Create()] - You can discard your changes.
CMemDC
always copies back its bitmap to your original device context - that may not be suitable (you found out a text is empty and nothing is in the bitmap, for example) [function Invalidate()]
Other features for rotated output:
- Transparent use of the angle.
You code doesn't need to be changed for different angles (if you use GetRotRect()). - Automatically clones the original DC's font, background color, text mode and text color (and restores them after use).
- An object can be reused with another destination context rectangle (no new DC is created and the old bitmap will be reused if possible).
[See Create()]
Please note:
- This device context doesn't support printing (in contrast to
CMemDC
).
If anybody could help me in implementing it, I'd be very happy.
I for myself don't have a printer that seems to support rotated output ...
A simple OnPaint()
, that draws a rotated text to its device context:
[this example makes use of the constructor, GetRotRect(), IsCreated() and the destructor that calls Finish()].
void MyWnd::OnPaint()
{
CPaintDC destDC(this);
CRect rectClient;
GetClientRect(rectClient);
{
cdxCRot90DC rotDC(destDC,rectClient,90 );
if(!rotDC.IsCreated())
return;
CRect rectRot = dc.GetRotRect();
CString s = _T("Hello world");
CSize sz = rotDC.GetTextExtent(s);
rotDC.TextOut(rectRot.left + (rectRot.Width() - sz.cx) / 2,
rectRot.top + (rectRot.Height() - sz.cy) / 2,
s);
}
}
An example OnPaint()
that draws a 3d-border around your rotated stuff could look like (here, we use the Finish() function directly):
[This example makes use of the constructor, GetRotRect(), IsCreated() and Finish()].
void MyWnd::OnPaint()
{
CPaintDC destDC(this);
CRect rectClient;
GetClientRect(rectClient);
CRect rectInner = rectClient;
rectInner.DeflateRect(::GetSystemMetrics(SM_CXEDGE),::GetSystemMetrics(SM_CYEDGE));
cdxCRot90DC rotDC(origDC,rectInner,90);
if(rotDC.IsCreated())
{
CRect rectRotClient = rotDC.GetRotRect();
...
rotDC.TextOut(rectRotClient.left,rectRotClient.top,"Left-top text"));
...
}
rotDC.Finish();
destDC.DrawEdge(rectClient,EDGE_RAISED,BF_RECT);
}
Note that the following text covers only the most important functions:
cdxCRot90DC();
cdxCRot90DC(CDC & destDC, const CRect & rectDC, int iAngle, bool bCopy = false);
cdxCRot90DC(CDC *pdestDC, const CRect & rectDC, int iAngle, bool bCopy = false);
cdxCRot90DC(CDC & destDC, int iAngle, bool bCopy = false);
cdxCRot90DC(CDC *pdestDC, int iAngle, bool bCopy = false);
Constructs a new object.
The lower four creators immediately call Create() that creates a new device context for you.
If you use them, use IsCreated() to check whether the device context has successfully been set up (otherwise, if Create() failed, cdxCRot90DC::m_hDC
(incorporated from CDC) won't be properly set up and you'll get thousands of ASSERTs...).
See Create() for further information.
Note that the destructor will automatically call Finish() if the device context has been set up properly.
bool Create(CDC & destDC, const CRect & rectDC, int iAngle, bool bCopy = false);
bool Create(CDC * pdestDC, const CRect & rectDC, int iAngle, bool bCopy = false);
bool Create(CDC & destDC, int iAngle, bool bCopy = false);
bool Create(CDC * pdestDC, int iAngle, bool bCopy = false);
Creates the device context.
You can start drawing into it if this function successfully returned.
Call Finish() to copy back the bitmap of the rotated DC into your destination DC (the destructor will do so automatically - see Finish() for further information).
Note #1
Since your rectangle (see parameters below) might be rotated, you cannot use the coordinates of rectDC
to draw your data (they may lay outside the rotated rectangle).
Therefore, use the GetRotRect() function to receive the device context's rectangle that matches your rectDC
.
Note #2
You can call Create()
several times - each time you call it, it will create a "new" cdxCRot90DC
for you (it won't allocate a new bitmap if the previous one meets the new denies).
However, if you want to copy your previous bitmap back to its destination device context, you have to call Finish() first.
Parameters:
destDC, pdestDC
The "original" device context; pDestDC
may not be NULL
. rectDC
The rectangle out of the original device context you want to draw to using this memory context.
If not provided, the current clip box will be used.
Note that in either case, rectDC
will be intersected with the current original device's clip box.
If the resulting rectangle is empty, this function will fail. iAngle
The rotation angle for your output.
i.e., if you provide a "90", an arrow you draw from the left to the right will be displayed pointing from bottom to top (see example image above).
If you use 0, the cdxCRot90DC
will act as CMemDC
(no rotation will take place). bCopy
Set to true
if you want to copy the contents of rectDC
of your destDC
into your newly created device context.
This would be useful if there're already any (non-rotated) graphics that you don't want to overdraw if the memory device context clips back its bitmap.
Please note that CDC::PlgBlt
isn't one of the fastest functions thus you may first created the rotated data, clip them back and create the other ones afterwards.
This function returns false
if:
destDC
is used for printing (I don't have a printer that is able to print rotated images...) rectDC
intersected with your destDC
's clip box is empty or - if you don't provide rectDC
- your destDC
's clip box is empty.
bool IsCreated() const;
Returns true
if the device context is successfully created or false
, if not.
const CRect & GetRotRect() const;
operator const CRect & () const { return GetRotRect(); }
Can be used to receive the rectangle of the rotated memory device context that matches the rectangle of the destination ("output") device context.
If you refer to the example image, this function returns (-120,10,-20,250) for the destination rectangle (10,20,250,120) (rotate() can be used to transform other 2D objects).
This function returns (0,0,0,0)
if the device context had not been successfully set up.
bool Finish();
Informs the cdxCRot90DC
that you've done with your work - it copies its bitmap back to the destination device context.
This function is automatically called by the destructor if:
- Create() was successful and
- You didn't used Invalidate() and
- You did not called
Finish()
by yourself.
Please note that another call to finish won't copy the bitmap back again.
void Invalidate();
Invalidates the cdxCRot90DC
device context - Finish() won't draw the bitmap back to the destination DC (and therefore the destructor won't do so, too).
Moreover, this function will set the current clipping region to an empty rectangle - all further drawing operations won't affect the memory device context.
You can use this function if you found out that your rotated image would be empty or stuff to avoid the device context to blit it back to the destination device context (this will be faster :).
CRect rotate(const CRect & r) const;
CPoint rotate(const CPoint & p) const;
CSize rotate(const CSize & s) const;
void rotate(POINT *pPnts, UINT nCnt) const;
Transform destination 2D objects into rotated 2D objects.
Example: If you pass a rectangle "r" to Create(), GetRotRect() would return rotate(r)
.
CRect rotateBack(const CRect & r) const;
CPoint rotateBack(const CPoint & p) const;
CSize rotateBack(const CSize & s) const;
void rotateBack(POINT *pPnts, UINT nCnt) const;
Transform rotated 2D objects into destination 2D objects.
CRect operator()(const CRect & r, bool bFwd = true) const;
CPoint operator()(const CPoint & p, bool bFwd = true) const;
CSize operator()(const CSize & sz, bool bFwd = true) const;
void operator()(POINT *pPnts, UINT nCnt, bool bFwd = true) const;
Short-cut operators for the above function groups.
If bFwd
is true
, rotate()
is used, rotateBack()
otherwise.
static CRect rotate(const CRect & r, int iAngle);
static CPoint rotate(const CPoint & p, int iAngle);
static CSize rotate(const CSize & s, int iAngle);
static void rotate(POINT *pPnts, UINT nCnt, int iAngle);
Any transformation from a destination 2D object into a rotated 2D object that has been rotated using the even angle iAngle
. If you want to transform an object from a rotated to a destination DC, use rotate(...,-iAngle)
.
You can use these functions to perform calculations without creating a device context for this task.
operator cdxCRot90DC * ();
cdxCRot90DC *operator->();
const cdxCRot90DC *operator->() const;
Use an object of class cdxCRot90DC
as a pointer.
This might be useful for CDC member functions that expect a pointer to a CDC.
- Drawing 3D text in the rotated device context may need you to know in which direction in the rotated device context the lower right corner of the destination lays.
For example, if you want to draw some disabled text (as seen in a disabled CStatic
), you normally would use:
...
dc.SetBkMode(TRANSPARENT);
dc.SetTextColor(::GetSysColor(COLOR_3DHILIGHT));
dc.TextOut(pnt + CPoint(1,1),strText);
dc.SetTextColor(::GetSysColor(COLOR_3DSHADOW));
dc.TextOut(pnt,strText);
...
But what happens if you do so in a rotated DC ?
Previously, your "shadow" text would be up-left. But if the device context rotates your output by 180°, it would be down-right ... ugly !
The solution is to use rotate() to transform the vector (1,1) into the corresponding vector in the rotated device context:
...
rotDC.SetBkMode(TRANSPARENT);
rotDC.SetTextColor(::GetSysColor(COLOR_3DHILIGHT));
rotDC.TextOut(pnt + rotDC.rotate( CPoint(1,1) ),strText);
rotDC.SetTextColor(::GetSysColor(COLOR_3DSHADOW));
rotDC.TextOut(pnt,strText);
...
... and everything is fine :)
Note that there's a member function DrawControl()
(which is not described here) that may kill most of your problems :)
CDC::DrawEdge() and CDC::Draw3dRect()
may have the same problem !
I recommend to use some code as initially shown as example 2 to set up objects that need these functions (although one could easily write a short-cut for them. :)
- Support printing
- Post the following new classes to codeguru:
cdxCRotButton
: A CButton
with rotated text & icon. cdxCRotStatic
: A CStatic
with rotated text & icon. cdxCRotBevelLine
: A bevelline with rotated text.
These classes are already finished but I don't have the time to write a proper text for codeguru ... I hope to find some time soon.
- Add
cdxCRotDC
that allows rotation by any angle :)
Llew Goodstadt of the University Laboratory of Physiology, Oxford, UK has provided an updated project that addresses some of the problems of the original code in that it moves away from using PlgBlt
and instead uses DIBs to perform the rotation. Llew did not have time to write up his solution, nor complete the code to his satisfaction, and so the demo is provided here "as is" in case others wish to extend this further.
License
This article has no explicit license attached to it, but may contain usage terms in the article text or the download files themselves. If in doubt, please contact the author via the discussion board below.
A list of licenses authors might use can be found here.