Using Metafiles

A

AliR \(VC++ MVP\)

Hi everyone,

I need to draw some rft to the screen. And I found some code out there that
uses a Richedit control and sends it an EM_FORMATRANGE to do exactly that.

Now I need to add scaling to this code. What I'm trying to do is to have
the richedit control draw to a metafile and then I can set the ScaleTrasform
of the Graphics object and draw the metafile.

But everytime I call Metafile.GetHenhmetafile() it throws a "Parameter is
not valid" execption.

I haven't been able to find a solution to this problem anywhere.

public void Draw(Graphics graphics, RectangleF layoutArea, float
xFactor)
{
//Calculate the area to render.
SafeNativeMethods.RECT rectLayoutArea;
rectLayoutArea.Top = (int)(layoutArea.Top * anInch);
rectLayoutArea.Bottom = (int)(layoutArea.Bottom * anInch);
rectLayoutArea.Left = (int)(layoutArea.Left * anInch);
rectLayoutArea.Right = (int)(layoutArea.Right * anInch);

IntPtr hdc = graphics.GetHdc();

Metafile metafile = new Metafile(hdc, layoutArea);

//Release the device context handle obtained by a previous call
graphics.ReleaseHdc(hdc);

IntPtr hDCEMF = metafile.GetHenhmetafile(); <-------- I can't
get passed this line!

SafeNativeMethods.FORMATRANGE fmtRange;
fmtRange.chrg.cpMax = -1; //Indicate character from
to character to
fmtRange.chrg.cpMin = 0;
fmtRange.hdc = hDCEMF; //Use the same DC for
measuring and rendering
fmtRange.hdcTarget = hDCEMF; //Point at printer hDC
fmtRange.rc = rectLayoutArea; //Indicate the area on page
to print
fmtRange.rcPage = rectLayoutArea; //Indicate size of page

IntPtr wParam = IntPtr.Zero;
wParam = new IntPtr(1);

//Get the pointer to the FORMATRANGE structure in memory
IntPtr lParam = IntPtr.Zero;
lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
Marshal.StructureToPtr(fmtRange, lParam, false);

SafeNativeMethods.SendMessage(this.Handle,
SafeNativeMethods.EM_FORMATRANGE, wParam, lParam);

//Free the block of memory allocated
Marshal.FreeCoTaskMem(lParam);

graphics.ScaleTransform(xFactor, xFactor);

graphics.DrawImage(metafile, layoutArea.Location);
}


AliR.
 
A

AliR \(VC++ MVP\)

Peter Duniho said:
Why not use Graphics.FromImage() to get a Graphics instance from the
Metafile, and then Graphics.GetHdc() to get the HDC to assign to
fmtRange.chrg.hdc?

By the way, the class "SafeNativeMethods" looks very much like code from
the .NET Framework itself. I don't know where you got that, but you may
want to be very careful to not be using code copied from .NET.
Microsoft's published the code, but I'm pretty sure the license is for
debugging purposes only, not for reuse within your own code.

Pete

I got the code from here http://www.andrewvos.com/?p=392
which is pretty much the same code as
http://support.microsoft.com/kb/812425/en-us
Which .Net code are you refering to? Am I reinventing the wheele? Is there
built-in code that does this already?

I don't really see how GraphicFromImage is going to help. Under native c++
code, I have to draw to a meta file and then I can use world transformations
to scale the text in a way that would not case detering and things like
that.

I tried your suggestion but it didn't draw anything.

public void Draw(Graphics graphics, RectangleF layoutArea, float
xFactor)
{
//Calculate the area to render.
SafeNativeMethods.RECT rectLayoutArea;
rectLayoutArea.Top = (int)(layoutArea.Top * anInch);
rectLayoutArea.Bottom = (int)(layoutArea.Bottom * anInch);
rectLayoutArea.Left = (int)(layoutArea.Left * anInch);
rectLayoutArea.Right = (int)(layoutArea.Right * anInch);

IntPtr hdc = graphics.GetHdc();

Metafile metafile = new Metafile(hdc, layoutArea);

Graphics metagraphic = Graphics.FromImage(metafile);
IntPtr hDCEMF = metagraphic.GetHdc();

SafeNativeMethods.FORMATRANGE fmtRange;
fmtRange.chrg.cpMax = -1; //Indicate character from
to character to
fmtRange.chrg.cpMin = 0;
fmtRange.hdc = hDCEMF; //Use the same DC for
measuring and rendering
fmtRange.hdcTarget = hDCEMF; //Point at printer hDC
fmtRange.rc = rectLayoutArea; //Indicate the area on page
to print
fmtRange.rcPage = rectLayoutArea; //Indicate size of page

IntPtr wParam = IntPtr.Zero;
wParam = new IntPtr(1);

//Get the pointer to the FORMATRANGE structure in memory
IntPtr lParam = IntPtr.Zero;
lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
Marshal.StructureToPtr(fmtRange, lParam, false);

SafeNativeMethods.SendMessage(this.Handle,
SafeNativeMethods.EM_FORMATRANGE, wParam, lParam);

//Free the block of memory allocated
Marshal.FreeCoTaskMem(lParam);

metagraphic.ReleaseHdc(hDCEMF);
//Release the device context handle obtained by a previous call
graphics.ReleaseHdc(hdc);

graphics.ScaleTransform(xFactor, xFactor);

graphics.DrawImage(metafile, layoutArea.Location);
}
 
A

AliR \(VC++ MVP\)

AliR (VC++ MVP) said:
I got the code from here http://www.andrewvos.com/?p=392
which is pretty much the same code as
http://support.microsoft.com/kb/812425/en-us
Which .Net code are you refering to? Am I reinventing the wheele? Is
there built-in code that does this already?

I don't really see how GraphicFromImage is going to help. Under native
c++ code, I have to draw to a meta file and then I can use world
transformations to scale the text in a way that would not case detering
and things like that.

I tried your suggestion but it didn't draw anything.

public void Draw(Graphics graphics, RectangleF layoutArea, float
xFactor)
{
//Calculate the area to render.
SafeNativeMethods.RECT rectLayoutArea;
rectLayoutArea.Top = (int)(layoutArea.Top * anInch);
rectLayoutArea.Bottom = (int)(layoutArea.Bottom * anInch);
rectLayoutArea.Left = (int)(layoutArea.Left * anInch);
rectLayoutArea.Right = (int)(layoutArea.Right * anInch);

IntPtr hdc = graphics.GetHdc();

Metafile metafile = new Metafile(hdc, layoutArea);

Graphics metagraphic = Graphics.FromImage(metafile);
IntPtr hDCEMF = metagraphic.GetHdc();

SafeNativeMethods.FORMATRANGE fmtRange;
fmtRange.chrg.cpMax = -1; //Indicate character from
to character to
fmtRange.chrg.cpMin = 0;
fmtRange.hdc = hDCEMF; //Use the same DC for
measuring and rendering
fmtRange.hdcTarget = hDCEMF; //Point at printer hDC
fmtRange.rc = rectLayoutArea; //Indicate the area on
page to print
fmtRange.rcPage = rectLayoutArea; //Indicate size of page

IntPtr wParam = IntPtr.Zero;
wParam = new IntPtr(1);

//Get the pointer to the FORMATRANGE structure in memory
IntPtr lParam = IntPtr.Zero;
lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
Marshal.StructureToPtr(fmtRange, lParam, false);

SafeNativeMethods.SendMessage(this.Handle,
SafeNativeMethods.EM_FORMATRANGE, wParam, lParam);

//Free the block of memory allocated
Marshal.FreeCoTaskMem(lParam);

metagraphic.ReleaseHdc(hDCEMF);
//Release the device context handle obtained by a previous call
graphics.ReleaseHdc(hdc);

graphics.ScaleTransform(xFactor, xFactor);

graphics.DrawImage(metafile, layoutArea.Location);
}

Here is the MFC version of this:
// setup rectangles for metafile fiddling
DC->DPtoHIMETRIC(&cTargetSize); // from MM_Text to MM_HIMETRIC
CRect cHiMetricRect(0,0,cTargetSize.cx,cTargetSize.cy);
CRect cTwipsRect(0,0,(TWIPS_INCH * cTargetSize.cx + HIMETRIC_INCH / 2) /
HIMETRIC_INCH,
(TWIPS_INCH * cTargetSize.cy + HIMETRIC_INCH / 2) / HIMETRIC_INCH);

// create the enhanced metafile
HDC hDCEMF = CreateEnhMetaFile(DC->m_hDC,NULL,cHiMetricRect,NULL);

// setup the format struct and do the RTF range formatting call
FORMATRANGE stFR;
stFR.hdcTarget = stFR.hdc = hDCEMF; // render to the memory helper EMF
stFR.rcPage = stFR.rc = cTwipsRect; // using this rectangle (in twips)
stFR.chrg.cpMin = 0; // and render all of the text
stFR.chrg.cpMax = -1;
pCtrl->SendMessage(EM_FORMATRANGE,TRUE,(LPARAM) &stFR);
pCtrl->SendMessage(EM_FORMATRANGE,TRUE,NULL); // this call clears the cache

// drawing into the metafile is done, get ourselves an handle to it (used
for the replay)
HENHMETAFILE hEMF = CloseEnhMetaFile(hDCEMF);

// calculate the automagic fudge factors by getting the device metrics
int nHorzSize = DC->GetDeviceCaps(HORZSIZE ); // width in millimeters
int nVertSize = DC->GetDeviceCaps(VERTSIZE ); // height in millimeters
int nHorzRes = DC->GetDeviceCaps(HORZRES ); // width in pixels
int nVertRes = DC->GetDeviceCaps(VERTRES ); // height in pixels
int nLogPixelsX = DC->GetDeviceCaps(LOGPIXELSX); // # of pixels per inch
horizontally
int nLogPixelsY = DC->GetDeviceCaps(LOGPIXELSY); // # of pixels per inch
vertically

float fHorzSizeInches = nHorzSize / 25.4f;
float fVertSizeInches = nVertSize / 25.4f;
float fHorzFudgeFactor = (nHorzRes / fHorzSizeInches) / nLogPixelsX; //
divide expected DPI with actual DPI
float fVertFudgeFactor = (nVertRes / fVertSizeInches) / nLogPixelsY; //

// do the world transforms, first apply the scale and fudge factors
XFORM stX;
ZeroMemory(&stX,sizeof(stX));
stX.eM11 = fHorzFudgeFactor * (m_XFactor);
stX.eM22 = fVertFudgeFactor * (m_XFactor);
SetWorldTransform(DC->m_hDC,&stX);

long Top = WinRect.top;
long Left = WinRect.left;
WinRect.OffsetRect(-Left,-Top);
WinRect.OffsetRect((int)(Left/fHorzFudgeFactor),int(Top/fVertFudgeFactor));

DC->PlayMetaFile(hEMF,&WinRect);

// that's it, kiss the metafile goodbye
DeleteEnhMetaFile(hEMF);
 
A

AliR \(VC++ MVP\)

I think I got it: (thank you Peter for your help)

public void Draw(Graphics graphics, RectangleF layoutArea, float
xFactor)
{
//Calculate the area to render.
SafeNativeMethods.RECT rectLayoutArea;
rectLayoutArea.Top = (int)(0 * anInch);
rectLayoutArea.Bottom = (int)(layoutArea.Bottom * anInch);
rectLayoutArea.Left = (int)(0 * anInch);
rectLayoutArea.Right = (int)(layoutArea.Right * anInch);


Bitmap Bmp = new Bitmap((int)layoutArea.Width,
(int)layoutArea.Height, PixelFormat.Format32bppArgb);
Graphics g = Graphics.FromImage(Bmp);
IntPtr hdc = g.GetHdc();

//create a metafile, convert the size to himetrics
Metafile metafile = new Metafile(hdc, new
RectangleF(0,0,layoutArea.Width*26,layoutArea.Height*26));

g.ReleaseHdc(hdc);
g.Dispose();

g = Graphics.FromImage(metafile);
IntPtr hDCEMF = g.GetHdc();

SafeNativeMethods.FORMATRANGE fmtRange;
fmtRange.chrg.cpMax = -1; //Indicate character from
to character to
fmtRange.chrg.cpMin = 0;
fmtRange.hdc = hDCEMF; //Use the same DC for
measuring and rendering
fmtRange.hdcTarget = hDCEMF; //Point at printer hDC
fmtRange.rc = rectLayoutArea; //Indicate the area on page
to print
fmtRange.rcPage = rectLayoutArea; //Indicate size of page

IntPtr wParam = IntPtr.Zero;
wParam = new IntPtr(1);

//Get the pointer to the FORMATRANGE structure in memory
IntPtr lParam = IntPtr.Zero;
lParam = Marshal.AllocCoTaskMem(Marshal.SizeOf(fmtRange));
Marshal.StructureToPtr(fmtRange, lParam, false);

SafeNativeMethods.SendMessage(this.Handle,
SafeNativeMethods.EM_FORMATRANGE, wParam, lParam);

//Free the block of memory allocated
Marshal.FreeCoTaskMem(lParam);

//Release the device context handle obtained by a previous call
g.ReleaseHdc(hDCEMF);
g.Dispose();

graphics.ScaleTransform(xFactor, xFactor);
graphics.DrawImage(metafile, layoutArea.Location);
}

AliR.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Top