Home General
New Blog Posts: Merging Reports - Part 1 and Part 2

TppShape RoundRect corner Radius

edited February 2010 in General
We are running RBuilder Pro 10.07 and I'm looking for a way to set the
radius of the corners for TppShape of type rtRoundRect. I see in the
CalculateDimensions that RBuilder is determining this automatically and I
was wondering if anyone has some code to set these values up as properties
instead. I don't have much experience with RBuilder so I'm not clear on
what I would need to do to modify the code with these properties myself. I
did find an old post where someone has created a new component the would do
what I want, but it was for RBuilder 6 and doesn't compile anymore.

Thanks
Chris

Comments

  • edited February 2010
    Hi Chris,

    The native TppShape does not allow altering the radius of the rounded
    corners in a round rectangle. It is automatically set to one forth the size
    of the smallest side.

    Two options...

    1. Create your own shape component and register it with ReportBuilder
    adding properties to alter this value. Take a look at the custom component
    example located in the \Demos\RCL directory for how this can be done. The
    component will be almost identical to the existing TppShape class with added
    properties and an altered CalcDimensions routine.

    2. You can alter the existing RB source for a very quick fix (however this
    is not recommended as a permanent solution). See the
    TppShape.CalcDimentions routine located in the ppCtrls.pas file for where
    the XCornerRound and YCornerRound values are set. Each one represents the
    ellipse radius of the rounded corner.

    --
    Regards,

    Nico Cizik
    Digital Metaphors
    http://www.digital-metaphors.com

    Best Regards,

    Nico Cizik
    Digital Metaphors
    http://www.digital-metaphors.com
  • edited February 2010
    I managed to create a myShape component but I'm having some issues with it.
    Setting the X/Y radius when running the designer from within my application
    works properly as does printing to the screen and printer. But, when I
    bring up the designer from within Delphi it doesn't work nor does printing
    to a PDF file. I did a quick search of the source and found that
    stRoundRect is referenced in the following units: ppCtrls,
    ppDesignControlsEx, ppPDFRendererShape, ppPDFShape, ppPrnDev, ppRegion,
    ppViewr. Do all these modules use myShape?

    I had to add the Virtual declaration to TppShape.CalcDimensions in ppCtrls
    in order to get my new CalcDimensions method to work. Here's the source to
    my component:

    Unit myShape;

    Interface

    Uses
    Classes, ppCtrls, ppDevice, Graphics;

    Type
    TmyShape = Class(TppShape)
    Private
    FRadiusX: Double;
    FRadiusY: Double;
    Procedure SetRadiusX(aRadiusX: Double);
    Procedure SetRadiusY(aRadiusY: Double);
    Protected
    Procedure PropertiesToDrawCommand(aDrawCommand: TppDrawCommand);
    Override;
    Public
    Constructor Create(aOwner: TComponent); Override;
    Procedure CalcDimensions(Var aLeft, aTop, aRight, aBottom, aRadiusX,
    aRadiusY: Longint); Override;
    Published
    property Anchors;
    property Brush;
    property OnDrawCommandClick;
    property OnDrawCommandCreate;
    property ParentHeight;
    property ParentWidth;
    property Pen;
    property ReprintOnOverFlow;
    property Shape;
    property StretchWithParent;
    property UserName;
    property Visible;
    property Height stored False;
    property Left stored False;
    property Top stored False;
    property Width stored False;
    Property RadiusX: Double Read FRadiusX Write SetRadiusX;
    Property RadiusY: Double Read FRadiusY Write SetRadiusY;
    End;

    Implementation

    {$R myShape.res}

    Uses ppClass, Types, ExtCtrls, ppUtils, ppTypes, ppDrwCmd,
    Dialogs,sysutils;

    Constructor TmyShape.Create(aOwner: TComponent);
    Begin
    Inherited Create(aOwner);

    FRadiusX := 0;
    FRadiusY := 0;
    End;

    Procedure TmyShape.SetRadiusX(aRadiusX: Double);
    Begin
    If aRadiusX <> FRadiusX Then
    Begin
    BeforePropertyChange('RadiusX');
    FRadiusX := aRadiusX;
    PropertyChange;
    InvalidateDesignControl;
    End;
    End;

    Procedure TmyShape.SetRadiusY(aRadiusY: Double);
    Begin
    If aRadiusY <> FRadiusY Then
    Begin
    BeforePropertyChange('RadiusY');
    FRadiusY := aRadiusY;
    PropertyChange;
    InvalidateDesignControl;
    End;
    End;

    Procedure TmyShape.CalcDimensions(Var aLeft, aTop, aRight, aBottom,
    aRadiusX, aRadiusY: Longint);
    Var
    liWidth: Integer;
    liHeight: Integer;
    liSide: Integer;
    Begin
    aLeft := Pen.Width Div 2;
    aTop := aLeft;

    If Printing Then
    Begin
    liWidth := ppToScreenPixels(PrintPosRect.Right - PrintPosRect.Left,
    utMMThousandths, pprtHorizontal, Nil);
    liHeight := ppToScreenPixels(PrintPosRect.Bottom - PrintPosRect.Top,
    utMMThousandths, pprtVertical, Nil);
    End
    Else
    Begin
    liWidth := spWidth;
    liHeight := spHeight;
    End;

    aRight := (aLeft + liWidth) - ((Pen.Width Div 2) * 2);
    aBottom := (aTop + liHeight) - ((Pen.Width Div 2) * 2);

    {determine length of the shortest side, use for circles, squares and
    corner rounding}
    If (liWidth < liHeight) Then
    liSide := liWidth
    Else
    liSide := liHeight;

    {if shape is square or circle, make 'sides' of shape equal}
    If Shape In [stSquare, stRoundSquare, stCircle] Then
    Begin
    {recalc top & left drawing positions based on centered side}
    Inc(aLeft, (liWidth - liSide) Div 2);
    Inc(aTop, (liHeight - liSide) Div 2);

    {set width and height to side}
    Dec(aRight, (liWidth - liSide) Div 2);
    Dec(aBottom, (liHeight - liSide) Div 2);
    End; {if shape has equal 'sides'}

    aRadiusX := ppToScreenPixels(FRadiusX, Units, pprtHorizontal, Nil);
    aRadiusY := ppToScreenPixels(FRadiusY, Units, pprtVertical, Nil);
    End; {procedrue, CalcDimensions}

    Procedure TmyShape.PropertiesToDrawCommand(aDrawCommand: TppDrawCommand);
    Begin
    Inherited PropertiesToDrawCommand(aDrawCommand);

    TppDrawShape(aDrawCommand).XCornerRound := ppToMMThousandths(FRadiusX,
    Units, pprtHorizontal, Nil);
    TppDrawShape(aDrawCommand).YCornerRound := ppToMMThousandths(FRadiusY,
    Units, pprtHorizontal, Nil);
    End;

    Initialization

    ppRegisterComponent(TmyShape, 'Standard Components', 10, 0, 'MyShape',
    HInstance);

    Finalization

    ppUnRegisterComponent(TmyShape);

    End.




  • edited February 2010
    Hi Chris,

    Please either post questions here or send them to
    support@digital-metaphors.com, not both.


    See the demo located in \Demos\RCL\ for how to create a design package and
    install your new component into the Delphi IDE.


    The PDF device isn't designed to handle variable corner radiuses on a round
    rectangle. You have two options in this case...

    1. Create a TppDrawShape descendent drawcommand and add an AsMetaFile
    routine which returns the round rectangle as a native windows metafile.
    Then create and register a new PDFRenderer similar to the
    TppPDFRendererGeneric class that calls the AsMetaFile routine, creates a
    TppPDFRendererImage and sends the metafile to be processed by the PDF
    device. This is how advanced drawcommands are rendered to PDF such as RTF
    and Barcode.

    File to look at (ppPDFRendererGeneric, ppPDFRendererManager, ppDrwCmd,
    ppRichTxDrwCmd).

    2. Create and register a new TppPDFRendererShape class that processes your
    new properties and draws the round rectangle directly to the PDF file. Note
    that rendering graphics to a PDF take at least a general knowledge of how
    PDF graphics are drawn.
    (http://www.adobe.com/devnet/pdf/pdf_reference.html) Currently the round
    rectangle is drawn to PDF using lines and bezier curves.
    (TppPDFCanvas.RoundRectangle routine)

    Files to look at (ppPDFRendererShape, ppPDFCanvas, ppPDFRendererManager).

    --
    Regards,

    Nico Cizik
    Digital Metaphors
    http://www.digital-metaphors.com

    Best Regards,

    Nico Cizik
    Digital Metaphors
    http://www.digital-metaphors.com
  • edited February 2010

  • edited February 2010
    Hi Chris,

    You are also going to need to create a new component design control for your
    new shape component. See the myChkboxDesign.pas file in the \Demos\RCL\...
    directory.

    Currently, your component is calling the TppShapeControl.PaintDesignControl
    routine to draw your round rectangle to the designer. If you look at this
    code (in the ppDesignControlsEx.pas file), you will see that it typecasts
    the component as a TppShape and calls CalcDimensions (which will return
    incorrect values in your case). You will need to create a different design
    control that calls the CalcDimensions routine of your new component rather
    than TppShape. Other than that, your new control should be the same as the
    shape control.

    --
    Regards,

    Nico Cizik
    Digital Metaphors
    http://www.digital-metaphors.com

    Best Regards,

    Nico Cizik
    Digital Metaphors
    http://www.digital-metaphors.com
  • edited February 2010
    Thanks for your continuing help with this. I created a TppShapeControl
    descendant for my TMyShape control as follows:

    Unit CornerRoundShapeDesign;

    Interface

    Uses
    Windows, Classes, Graphics, Controls, Forms, ppTypes, ppDesignControls,
    ppDesignControlsEx, ppInspector, CornerRoundShape;

    Type
    TMyShapeControl = Class(TppShapeControl)
    Protected
    Procedure PaintDesignControl(aCanvas: TCanvas); Override;
    End;

    Implementation

    Uses ExtCtrls;

    Procedure TMyShapeControl.PaintDesignControl(aCanvas: TCanvas);
    Var
    lClientRect: TRect;
    llXCornerRound: Longint;
    llYCornerRound: Longint;
    llLeft: Longint;
    llTop: Longint;
    llRight: Longint;
    llBottom: Longint;
    lBrush: TBrush;
    lPen: TPen;
    lShapeType: TShapeType;
    Begin
    GetPropValue('Pen', lPen);
    GetPropValue('Brush', lBrush);
    GetPropValue('Shape', lShapeType);

    lClientRect := ClientRect;

    aCanvas.Pen := lPen;
    aCanvas.Brush := lBrush;

    TMyShape(Component).CalcDimensions(llLeft, llTop, llRight, llBottom,
    llXCornerRound, llYCornerRound);

    Case lShapeType Of
    stRectangle,
    stSquare: aCanvas.Rectangle(llLeft, llTop, llRight, llBottom);
    stCircle,
    stEllipse: aCanvas.Ellipse(llLeft, llTop, llRight, llBottom);
    stRoundRect,
    stRoundSquare: aCanvas.RoundRect(llLeft, llTop, llRight, llBottom,
    llXCornerRound, llYCornerRound);
    End;
    End;

    Initialization

    TppDesignControlFactory.RegisterDesignControlClass(TMyShape,
    TMyShapeControl);

    Finalization

    TppDesignControlFactory.UnRegisterDesignControlClass(TMyShape);

    End.

    I have added this to a runtime only package in the same way that the
    myChkBoxDesign and myChkDlg are added to rbUSERDesign9.bpl. But as this is
    a runtime only package, it cannot be installed in to the IDE so how does my
    new control above get instantiated when the Report Designer is started from
    within the Delphi IDE?

    Thanks
    Chris


  • edited February 2010

    The Demos\RCL folder contains a complete example for the Checkbox
    components. There are 3 packages. The 'dcl' prefixed packaged is the
    design-time only package. There is also a package for the component and
    another package for the Designer. I recommend reviewing each the .pas and
    .dpk files contained in the folder - they are all essential to building a
    complete solution.


    --
    Nard Moseley
    Digital Metaphors
    www.digital-metaphors.com



    Best regards,

    Nard Moseley
    Digital Metaphors
    www.digital-metaphors.com
This discussion has been closed.