2

I have a canvas which I draw to, to make slips for purchases made, I am trying to print the canvas out but am getting an error

'Project Project2.exe raised exception class EConvertError with message 'Cannot assign a TCanvasD2D to a TCanvasGdiPlus'

where am I going wrong?

var
printDialog : TPrintDialog;
myPrinter   : TPrinter;

begin



printDialog := TPrintDialog.Create(Form5);

if printDialog.Execute then
begin
  myPrinter := printer;
  with myPrinter do
  begin

    BeginDoc;

    Canvas.Assign(slipPnl.Canvas);

    EndDoc;
  end;
end;
end;
bradley Rice
  • 107
  • 5
  • @Spektre thanks for the response, however I am a bit unsure what you mean, could you explain a bit more ? – bradley Rice Jun 08 '22 at 11:39
  • I moved my comment into answer along with working (at lest for me) code – Spektre Jun 08 '22 at 12:24
  • 1
    This is typically handled by separating your drawing code into its own function that takes a `TCanvas` as input, then you can draw to different kinds of canvases when needed. You don't copy one canvas to another. – Remy Lebeau Jun 08 '22 at 17:46
  • I added [Edit1] with alternative approach that should work even for Firemonkey ... in case my original approach does not work in it (I tested on VCL) – Spektre Jun 09 '22 at 05:45

1 Answers1

1

The error suggest that probable causes is that source canvas is DirectX accelerated and printer is usually just SW GDI rendered which might prevent direct Assign due to Rendering Context incompatibility (the objects are too foreign to eachother).

Another possibility is that Assign also changes the resolution which I expect is not allowed for printer Canvas ...

Why not CopyRect and/or StretchDraw instead?

So copy the contents of source Canvas into printer Canvas or temp Canvas instead of Assign it directly...

OK here is VCL C++ example of what I mean (adapted from mine standard printing routine I use in my apps):

void TMain::print(TCanvas *src,int xs,int ys)
    {
    // just check if any printers available first
    TPrinter *prn = Printer();
    int pxs,pys,psz,i=0;
    if (prn==NULL) i=1;
    if (prn->Printers==NULL) i=1;
    if (prn->Printers->Count<=0) i=1;
    if (i) return;

    // used print dialog component
    TPrintDialog *dlg_print=PrintDialog1;
    dlg_print->Options.Clear();
    dlg_print->Options<<poPrintToFile;
    if (!dlg_print->Execute()) return;

    // printer settings
    prn = Printer();
    pxs=prn->PageWidth;
    pys=prn->PageHeight;

    // create printer page backbuffer
    Graphics::TBitmap *bmp=new Graphics::TBitmap;
    bmp->HandleType=bmDIB;
    bmp->PixelFormat=pf32bit;
    bmp->SetSize(pxs,pys);

    // compute scale
    float zoom,xx,yy;
    xx=pxs/xs;
    yy=pys/ys;
    zoom=xx; if (zoom>yy) zoom=yy;
    xx=xs; xx*=zoom;
    yy=ys; yy*=zoom;

    // copy Incompatible canvas into GDI Canvas (bmp)
    bmp->Canvas->CopyRect
        (
        TRect(0,0,xx,yy),
        src,
        TRect(0,0,xs,ys)
        );

    // bmp->SaveToFile("print.bmp"); // just for debug so I do not need actually print

    prn->BeginDoc();
    for (i=0;i<dlg_print->Copies;i++)
        {
        if (i) prn->NewPage();
        // render GDI Canvas (bmp) to printer page
        prn->Canvas->Draw(0,0,bmp);
        }
    prn->EndDoc();

    // release stuff
    delete bmp;
    }

using BDS2006 Turbo C++ (direct successor to BCB6 and Delphi)... just convert the C++ syntax to Pascal the rest should be the same... The Idea is to use temp bitmap (you might also directly render to printer page instead) and CopyRect instead of Assign ... However you need to pass resolution of the TCanvas as that is not accessible directly (as its usually a property of parent object).

[Edit1]

As Marco van de Voort pointed out you are using Firemonkey which I have no experience with and missed it while answering so in case the above code is not working you can workaround with different approach ...

Simply copy the Canvas content using Winapi see:

and look for bullet #2 with name WinAPI approach in order to make it work you need Handle of the Canvas which should be a accessible property of the Canvas or its parent object. If it is not you can still do this see:

On how to obtain the Handle using Winapi again.

Spektre
  • 49,595
  • 11
  • 110
  • 380
  • 3
    Note his firemonkey tag. – Marco van de Voort Jun 08 '22 at 13:15
  • @MarcovandeVoort hmm didn't see that however looks like the class names and function order is the same anyway (but I have no experience with firemonkey are there any diferences?) – Spektre Jun 08 '22 at 13:31
  • 1
    Firemonkey is owner drawn and doesn't use GDI primitives. So printing can't assume a GDI canvas (as you can see from the error, it is a direct2d canvas), though there might ways around that for the initiated. I don't use FM, I only know the principles. – Marco van de Voort Jun 08 '22 at 14:49
  • @MarcovandeVoort I added [Edit1] with alternative approach that should work even for Firemonkey ... – Spektre Jun 09 '22 at 05:43