Table of Contents

Rendering standalone objects (Delphi)

Note

This demo is available in your FlexCel installation at <FlexCel Install Folder>\Demo\Delphi\Modules\25.Printing and Exporting\45.Render Objects and also at https:​//​github.​com/​tmssoftware/​TMS-​FlexCel.​VCL-​demos/​tree/​master/​Delphi/​Modules/​25.​Printing and Exporting/45.Render Objects

Overview

While you might normally want to render a full sheet (or a range of cells), you can also use FlexCel to render specific objects in the workbook.

Concepts

  • This is a simple application where we periodically update a number, and use FlexCel to recalculate the formulas and render a chart of the values. While you would normally not use FlexCel this way (and it is probably better to use a separate chart package), it gives a nice tasting on FlexCel capabilities.

  • How to use RenderObject to render a simple object in a sheet. In this demo, we are rendering the object named "datachart".

  • As the chart and the calculations are defined in the spreadsheet, you can add new themes to the application or modify the existing ones by creating and modifying the xls files in the templates folders, without needing to recompile the application. You can even do it in real time. Have a template open in Excel, make changes, save, and reload the template in the application by selecting it again in the listbox. Changes will appear instantly without needing to close the main application. This technique can be quite useful to let users customize your application.

  • In this example, we named the chart "DataChart", so we can identify it from the application. In order to name a chart object, ctrl-click in the chart (it should show white handles, not black), and then change the name in the name box at the top left in Excel.

Files

URenderObjects.pas

unit URenderObjects;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics,
  Controls, Forms, Dialogs, ImgList, ActnList, StdCtrls,
  ComCtrls, ToolWin, ExtCtrls,
  FlexCel.VCLSupport, FlexCel.Core, FlexCel.XlsAdapter, FlexCel.Render, FlexCel.Preview;

type
  TFRenderObjects = class(TForm)
    Actions: TActionList;
    ActionRun: TAction;
    ActionClose: TAction;
    ToolbarImages: TImageList;
    ToolbarImagesDisabled: TImageList;
    ToolBar2: TToolBar;
    ToolButton14: TToolButton;
    ToolButton15: TToolButton;
    ToolButton16: TToolButton;
    Panel1: TPanel;
    Label1: TLabel;
    cbTheme: TComboBox;
    ChartBox: TImage;
    AnimTimer: TTimer;
    PanelError: TPanel;
    ActionCancel: TAction;
    ToolButton1: TToolButton;
    ToolbarImages_100Scale: TImageList;
    ToolbarImages_300Scale: TImageList;
    ToolbarImagesDisabled_100Scale: TImageList;
    ToolbarImagesDisabled_300Scale: TImageList;
    procedure ActionCloseExecute(Sender: TObject);
    procedure ActionRunExecute(Sender: TObject);
    procedure AnimTimerTimer(Sender: TObject);
    procedure ActionCancelExecute(Sender: TObject);
    procedure cbThemeChange(Sender: TObject);
    procedure FormCreate(Sender: TObject);
  private
    Xls: TExcelFile;
    TemplatePath: string;
    ValueRange: TXlsNamedRange;
    MinValue: double;
    MaxValue: double;
    StepValue: double;
    ActualValue: double;
    ChartIndex: Int32;
    ChartProps: IShapeProperties;

    procedure InitApp;
    function ReadDoubleName(const Name: String): double;
    procedure LoadFile(const FileName: String);
    procedure GetChart;
  public
    destructor Destroy; override;
  end;

var
  FRenderObjects: TFRenderObjects;

implementation
uses IOUtils, Types, UPaths, Math, UFlexCelHDPI;

{$R *.dfm}
destructor TFRenderObjects.Destroy;
begin
  Xls.Free;
  inherited;
end;

procedure TFRenderObjects.FormCreate(Sender: TObject);
begin
  RegisterForHDPI(Self, nil);
end;

procedure TFRenderObjects.ActionRunExecute(Sender: TObject);
begin
 if Xls = nil then
    InitApp;

  AnimTimer.Enabled := true;
  ActionRun.Enabled := false;
  ActionCancel.Enabled := true;
end;

procedure TFRenderObjects.ActionCancelExecute(Sender: TObject);
begin
  AnimTimer.Enabled := false;
  ActionRun.Enabled := true;
  ActionCancel.Enabled := false;
  PanelError.Visible := false;
end;

procedure TFRenderObjects.ActionCloseExecute(Sender: TObject);
begin
  Close;
end;

procedure TFRenderObjects.InitApp;
var
  fi: TStringDynArray;
  f: string;
begin
  Xls := TXlsFile.Create;
  TemplatePath := TPath.Combine(DataFolder, 'templates') + TPath.DirectorySeparatorChar;
  fi := TDirectory.GetFiles(TemplatePath, '*.xls');
  if Length(fi) = 0 then
    raise Exception.Create('Sorry, no templates found in the templates folder.');

  cbTheme.Items.Clear;
  for f in fi do
  begin
    cbTheme.Items.Add(TPath.GetFileName(f));
  end;
  cbTheme.ItemIndex := 0;
  LoadFile(fi[0]);
end;

function TFRenderObjects.ReadDoubleName(const Name: String): double;
var
  Range: TXlsNamedRange;
  val: TCellValue;
begin
  Range := Xls.GetNamedRange(Name, 0);
  if Range.IsNull then
    raise Exception.Create(('There is no range named ' + Name) + ' in the template');

  val := Xls.GetCellValue(Range.Top, Range.Left);
  if not (val.IsNumber) then
    raise Exception.Create(('The range named ' + Name) + ' does not contain a number');

  Result := val.AsNumber;
end;


procedure TFRenderObjects.LoadFile(const FileName: String);
var
  i: Int32;
  ObjName: String;
begin
  Xls.Open(FileName);
  ActualValue := 0;
  ValueRange := Xls.GetNamedRange('Value', 0);
  if ValueRange.IsNull then
    raise Exception.Create('There is no range named "value" in the template');

  MinValue := ReadDoubleName('Minimum');
  MaxValue := ReadDoubleName('Maximum');
  StepValue := ReadDoubleName('Step');
  ChartIndex := -1;
  for i := 1 to Xls.ObjectCount do
  begin
    ObjName := Xls.GetObjectName(i);
    if SameText(ObjName, 'DataChart') then
    begin
      ChartIndex := i;
      break;
    end;

  end;

  if ChartIndex < 0 then
    raise Exception.Create('There is no object named "DataChart" in the template');

  ChartProps := Xls.GetObjectProperties(ChartIndex, true);
end;


procedure TFRenderObjects.AnimTimerTimer(Sender: TObject);
begin
  if Xls = nil then exit;

  try
    ActualValue:= ActualValue + StepValue;
    if ActualValue > MaxValue then
      ActualValue := MinValue;

    Xls.SetCellValue(ValueRange.Top, ValueRange.Left, ActualValue);
    Xls.Recalc;

    GetChart;
  except
     //We don't want any dialog popping up every second.
    on ex: Exception do
      begin
        PanelError.Caption := ex.Message;
        PanelError.Align := alClient;
        PanelError.Visible := true;
        AnimTimer.Enabled := false;
      end;
  end;
end;

procedure TFRenderObjects.cbThemeChange(Sender: TObject);
begin
  if (cbTheme.ItemIndex < 0) then exit;
			LoadFile(TPath.Combine(TemplatePath, cbTheme.Items[cbTheme.ItemIndex]));

end;

procedure TFRenderObjects.GetChart;
var
  ImageDimensions: TUIRectangle;
  Origin: TUIPointF;
  SizePixels: TUISize;
  dpi: RealNumber;
  AspectX: RealNumber;
  AspectY: RealNumber;
  Aspect: RealNumber;
  Img: TUIImage;
begin
   //We could get the chart with the following command,
   //but it would be fixed size. In this example we are going to be a little more complex.
   //Xls.RenderObject(ChartIndex);

   //A more complex way to retrieve the chart, to show how to use
   //all parameters in renderobject.

   //First calculate the chart dimensions without actually rendering it. This is fast.
  Xls.RenderObject(ChartIndex, Font.PixelsPerInch, ChartProps, TUISmoothingMode.AntiAlias, TUIInterpolationMode.HighQualityBicubic, true, false, Origin, ImageDimensions, SizePixels);
  dpi := Font.PixelsPerInch;  //default screen resolution
  Aspect := 1;
  if (SizePixels.Height > 0) and (SizePixels.Width > 0) then
  begin
    AspectX := 1.0 * chartBox.Width / SizePixels.Width;
    AspectY := 1.0 * chartBox.Height / SizePixels.Height;
    Aspect := Min(AspectX, AspectY);
     //Make the dpi adjust the screen resolution and the size of the form.
    dpi := Font.PixelsPerInch * Aspect;
    if dpi < 20 then
      dpi := 20;

    if dpi > 500 then
      dpi := 500;

  end;

  Img := Xls.RenderObject(ChartIndex, dpi, ChartProps, TUISmoothingMode.AntiAlias, TUIInterpolationMode.HighQualityBicubic, true, true, Origin, ImageDimensions, SizePixels);
  try
    if (ChartBox.Picture.Width <> ChartBox.Width) or (ChartBox.Picture.Height <> ChartBox.Height)
        then ChartBox.Picture := nil;
    Img.ToNativeImage(Pointer(ChartBox.Canvas.Handle), Aspect);
    Invalidate;
  finally
    Img.Free;
  end;
end;

end.