Tag: firemonkey

Firemonkey Particles

We see images with connected points used a lot in diagrams, illustrations, advertising and just cool abstract art. They visually represent networks of related things and ideas or connectivity. The web, for example. It's effective and visually appealing and when the effect is animated, it's mesmerising. And popular. There are any number of animated backgrounds and JavaScript libraries to add
Read More

Delphi with iOS vs iPAD – UI does not update

Using Delphi 10.4.1 Buttons, when set to Invisible, will hide in iOS. However, the same buttons will not hide in iPadOS 14.4. Similarly, the text on a TLabel will not refresh automatically when updated. However, when I click the iPad button (iPad Air), and re-show the iPad app, all the changes that were supposed to be made show up! I've tried TControl.Repaint() and even the inefficient Application.ProcessMessages(). Both do not work. Any suggestion?
Read More

TSingleHelper.AsString and packed records

Using Delphi 10.4 and a FMX program. I have a packed record with a "Single" type defined: rRec = packed record ... Inch: Single; ... end; Later in the logic, I wish to display the valve of Inch as a string. There are three ways to do this: var s: Single; vStr: string; vRec: rRec; begin vRec.Inch = 12.3; //use FloatToStrF vStr := FloatToStrF(vRec.Inch, ffFixed, 7, 1); ... //Assign record value to variable and use TSingleHelper.ToString which simply calls FloatToStrF s := vRec.Inch; vStr := s.ToString(ffFixed, 7, 1); ... //use TSingleHelper.ToString from the record value //***** this fails on Android ***** vStr := vRec.Inch.ToString(ffFixed, 7, 1); ... end; All three of the above examples work on Win10, OSX and IOS. However, the last one (vRec.Inch.ToString) fails on Android with the IDE showing the error "Project xx raised exception class error (10)" before showing an access violation. The "Bus" error appears in the IDE when I try to step into the ...Inch.ToString call. Is there a known reason TSingleHelper.ToString fails on Android when used with packed record fields? Note: I have not tested this with unpacked records, or tested the helper functions for other numeric types, or tested the TSingleHelper.ToString class function. Edit: turns out apparently Android does not support packed records very good or at all. I noticed in my code I had three "byte" fields before the "Single" field. By adding or removing a byte field, the vRec.Inch.ToString works. Guess I will have to use FloatToStrF.
Read More

Running PAServer on Mac through PAClient.exe manually

I'm trying to copy the application file for iOS or MacOS to my MacOS PAServer directly instead of using IDE. If I use .bat file to run PASERVER commands that I copied from Delphi's compile logs (at the bottom of screen), why does the PASERVER commands not work properly? NOTE: this script is run after the project is MAKE or BUILD already. "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --Clean="project1.app,c:\PROJECT1\project1._@emb_.tmp" -u8 --Clean="project1.app.dSYM,c:\PROJECT1\project1._@emb_.tmp" -u8 --Clean="project1.launchscreen,c:\PROJECT1\project1._@emb_.tmp" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --put="IC_APPL_180.png,project1.app\..\project1.launchscreen\Assets\AppIcon.appiconset\,1,IC_APPL_180.png" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --put="d:\temp\Release\project1.entitlements,project1.app\..\,1,project1.entitlements" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --put="IC_LAUNCH_344x594_a.png,project1.app\..\project1.launchscreen\Assets\LaunchScreenImage.imageset\,1,FM_LaunchScreenImage_iPadDark@2x.png" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --put="d:\temp\Release\project1,project1.app\,1,project1" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --put="d:\temp\Release\project1.launchscreen\**\*.*,project1.app\..\project1.launchscreen\,0" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --put="IC_SETTINGS_87x87.png,project1.app\..\project1.launchscreen\Assets\AppIcon.appiconset\,1,IC_SETTINGS_87x87.png" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --put="d:\temp\Release\project1.info.plist,project1.app\,1,Info.plist" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --put="IC_LAUNCH_458x.png,project1.app\..\project1.launchscreen\Assets\LaunchScreenImage.imageset\,1,FM_LaunchScreenImage_iPhoneDark@2x.png" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --put="IC_APPL_120.png,project1.app\..\project1.launchscreen\Assets\AppIcon.appiconset\,1,IC_APPL_120.png" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --put="IC_IPAD_APPL_167x167.png,project1.app\..\project1.launchscreen\Assets\AppIcon.appiconset\,1,IC_IPAD_APPL_167x167.png" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --put="IC_APPL_1024x1024.png,project1.app\..\project1.launchscreen\Assets\AppIcon.appiconset\,1,IC_APPL_1024x1024.png" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --put="IC_SPOTLIGHT_800.png,project1.app\..\project1.launchscreen\Assets\AppIcon.appiconset\,1,IC_SPOTLIGHT_80x80.png" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --put="IC_NOTIFY2_60.png,project1.app\..\project1.launchscreen\Assets\AppIcon.appiconset\,1,IC_NOTIFY2_60x60.png" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --put="IC_LAUNCH_1688.png,project1.app\..\project1.launchscreen\Assets\LaunchScreenImage.imageset\,1,FM_LaunchScreenImage_iPhoneDark@3x.png" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --put="IC_LAUNCH_229.png,project1.app\..\project1.launchscreen\Assets\LaunchScreenImage.imageset\,1,FM_LaunchScreenImage_iPad@2x.png" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --put="IC_SETTINGS_58.png,project1.app\..\project1.launchscreen\Assets\AppIcon.appiconset\,1,IC_SETTINGS_58x58.png" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --put="IC_IPAD_APPL_152.png,project1.app\..\project1.launchscreen\Assets\AppIcon.appiconset\,1,IC_IPAD_APPL_152.png" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --put="IC_NOTIFY2_40.png,project1.app\..\project1.launchscreen\Assets\AppIcon.appiconset\,1,IC_NOTIFY2_40.png" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --assetcatalog="project1.launchscreen/Assets,project1.app" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --storyboard="project1.launchscreen/LaunchScreen.storyboard,project1.app" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --Put="/Users/delphi/Library/MobileDevice/Provisioning Profiles/14537af0-24a8-47eb-8a8e-4f5fec1f5e7a.mobileprovision,project1.app,embedded.mobileprovision" "Macbook" "c:\program files (x86)\embarcadero\studio\21.0\bin\paclient.exe" -u8 --codesign="project1.app,'Apple Development: someaddress@gmail.com (H3ZK9VK5AA)',project1.app\..\project1.entitlements" "Macbook" but when I run it almost every file is: Connecting to 192.168.1.100 on port 64211... Copying file(s)... Total file(s) copied: 0 file(s) 0 bytes or Connecting to 192.168.1.53 on port 64211... Error: E6665 Stream read error Please advise how to call PACLIENT directly so that I can deploy my IOS or MacOS app directly. I have checked this forum for examples. Delphi 10.4.1 compiling to MacOS Big Sur
Read More

FMX TPaintBox Canvas.FillText not working as exspected on Windows 10

I have a multi device application with fmx. On Form1 there is only a TPaintBox named pbox. In pboxPaint I do all drawing. With Bitmaps it functions better and faster than exspected! (Good for game developers, more advertising would help to kick off older bad working solutions). But with Canvas.FillText there are several errors. Horizontal alignment is wrong. TTextAlign.Leading aligns text to the right and TTextAlign.Trailing aligns text to the left. Center is ok. Vertical alignment works as exspected. The text 'Hello Peter Kloß!' appears as '!Hello Peter Kloß'. That is surprising! The '!' comes first, then the rest of the text. Perhaps I would understand it when the text comes like '!ßolK reteP olleH'. Several tests with some other constellations: ABC! * -> * !ABC || A.B.C -> A.B.C || A.BC! 0 -> A.BC! 0 [TFillTextFlag.RightToLeft] is the only selectable flag. What sense does it have? SAMPLE CODE uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Objects, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs; type TForm1 = class(TForm) pbox: TPaintBox; procedure FormCreate(Sender: TObject); procedure pboxPaint(Sender: TObject; Canvas: TCanvas); procedure TForm1.pboxPaint(Sender: TObject; Canvas: TCanvas); begin Canvas.Fill.Color := TAlphaColors.Red; mRect := TRectF.Create(form1.ClientRect); Canvas.FillText(mRect, 'Hello Peter Kloß!', false, 0.5, [TFillTextFlag.RightToLeft], TTextAlign.Center, TTextAlign.Leading); end; ENVIRONMENT Delphi Community Edition 10.3.3 Windows 10
Read More

Error [dcc32 Fatal Error] F2084 Internal Error: AV06028036(05FB0000)-R2CD489B0-0

I know that probably if i recreate my project this error will get away, but i wish to know why this happens. Could someone explain me? I am using Delphi 10.4. I was testing this project without saving it, but i saved and it keeps happening. I just want to understand why this happens. My code: uses System.SysUtils, System.Types, System.UITypes, System.Classes, System.Variants, FMX.Types, FMX.Controls, FMX.Forms, FMX.Graphics, FMX.Dialogs, FMX.Memo.Types, FMX.Controls.Presentation, FMX.ScrollBox, FMX.Memo, FMX.StdCtrls;//, Winapi.Windows; type TForm1 = class(TForm) memSource: TMemo; memDest: TMemo; btnInfo: TSpeedButton; btnMai: TSpeedButton; btnCript: TSpeedButton; btnMin: TSpeedButton; procedure trataTexto(Sender: TObject); procedure FormCreate(Sender: TObject); end; var Form1: TForm1; implementation {$R *.fmx} procedure TForm1.trataTexto(Sender: TObject); var S: AnsiString; i,cont: Integer; palavra, agora: boolean; begin S := memSource.Text; if btnInfo.isPressed then begin palavra := False; cont := 0; for i := 1 to Length(S) do begin agora := upcase(BestFit(S[i])[1]) in ['A'..'Z', '-']; if palavra <> agora then begin if agora then inc(cont); palavra := agora; end; end; memDest.Text := format('%d caracteres'#10'%d palavras', [ Length(S),cont ]); end else if btnMai.isPressed then begin memDest.Text := Uppercase(S); end else if btnMin.isPressed then begin memDest.Text := Lowercase(S); end else if btnCript.isPressed then begin S := uppercase(S); for i := 1 to Length(S) do if S[i] in ['A'..'Z'] then S[i] := S[i]; end; end; end.
Read More

TDialogService.ShowMessage not blocking on Win10

In my Delphi 10.4 FMX program, I am asking the user for a new file name using the code below procedure TForm6.btnBlockingClick(Sender: TObject); begin //In Win10, this blocks form access when ShowMessage is called NameCallBack(mrOk, ['name']); end; procedure TForm6.btnNonBlockingClick(Sender: TObject); begin //In Win10, this does not block form access when ShowMessage is called in the NameCallBack routine. TDialogService.InputQuery('Enter name', ['Name'], [''], NameCallBack); end; procedure TForm6.NameCallBack(const AResult: TModalResult; const AValues: array of string); begin if aResult = mrOK then TDialogService.ShowMessage('Ok pressed') else TDialogService.ShowMessage('Cancel pressed'); end; Any idea why ShowMessage is not blocking when NameCallBack is used as the Callback event for InputQuery? In Win10, what is the best way to show a message to a user in this type of callback routine that keeps the user from accessing the underlying form until the dialog is closed in some way. FYI: the same thing happens if you use MessageDialog, to allow user interaction, instead of ShowMessage in the callback routine. Note: this logic works in OSX and IOS, with both dialogs blocking. On Android, neither dialog is blocking but is not a problem, as touching anywhere but the dialogs closes the dialog and requires a second touch to interact with the underlying form again. On Win10, I can doing anything I want with the underlying form while the ShowMessage dialog is visible when used in a callback event. Thanks for any help with this.
Read More

GraphQL, workflow engine, multitenancy and more: what you will do with Delphi in 2021

Here at TMS, we are working on so much new exciting frameworks and features that I decided to share with you what we expect to bring to you in 2021!


Photo by Rhett Wesley on Unsplash

First, a disclaimer: this is not an official commitment. Some of the things listed here might be delayed or not be released at all. But of course, that’s not our intention, since we are listing it here. We want to (and probably will) release all of this in 2021. But, since unexpected things might happen, we can never guarantee what will happen in future. One second thing: thislist only covers what is coming around TMS Business line of products. There is much more to come from TMS in 2021!

I was going to title this post as “TMS Business Roadmap for 2021”. But I thought that the current title brings more attention and curiosity. And it looks like it worked, thanks for coming! ??

Ok, jokes aside, another reason is that we still don’t know if all of the libraries, features and technologies listed here will be part of the TMS Business bundle, and the original title could be misleading.

But regardless, everything listed here will for sure be smoothly integrated with TMS Business – either using ORM (TMS Aurelius) or REST/JSON framework (TMS XData), or any other core product, what is for sure is that everything listed here makes up a huge ecosystem around TMS Business technologies.

An exciting year is coming ahead! So, without further ado, this is what we expect to bring to Delphi world this year, with TMS Business.

GraphQL

TMS is bringing GraphQL to Delphi! We have it already in a beta state, the core is complete, all is going fine. We still need to document everything, and add a few more features here and there, but it’s in a very advanced stage.

This teaser video shows GraphQL server in action, written in Delphi! The frontend is GraphQL Playground JS framework, but still being served via a Delphi server.

The relevant parts of the code used to build the server in the video are the following. First, the GraphQL schema is built:

SchemaDocument := TGraphQLDocumentParser.ParseSchema(
    'type Query { hello(name: String!): String }');
  Schema := TGraphQLSchemaBuilder.Build(SchemaDocument);

  Schema.SetResolver('Query', 'hello', function(Args: TFieldResolverArgs): TValue
    begin
      Result := 'Hello, ' + Args.GetArgument('name').AsString;
    end
  );

Then, the endpoints for the GraphQL API and the GraphQL Playground are created:

  ADispatcher.AddModule(
    TGraphQLServerModule.Create('http://+:2001/tms/graphql', FSchema));

  ADispatcher.AddModule(
    TGraphQLPlaygroundModule.Create('http://+:2001/tms/playground', '/tms/graphql'));

And that’s it for the “Hello, world” demo!

TMS Auth

A complete framework for adding authentication to your application, with user, roles and permissions management and providing features like login, registration, e-mail confirmation and more.

Relying on Aurelius ORM and XData REST framework, with a few lines of code you will get all your database setup and have an authentication API server running, with lots of features.

Well, it’s said one picture is worth a thousand words, so here is an example of the available API out of box:

And these are screenshots of an existing app built with TMS Web Core which is already using TMS Auth for user registration and login:

It will never been as easy to add user and permissions management and login system to your Delphi application and server!

BPM Workflow

A full workflow engine is coming. You might ask what is the difference between this new library and TMS Workflow Studio.

It’s a night and day difference. TMS Workflow Studio is nice product, but it was made 13 years ago with a different goal in mind – desktop VCL applications – and when lots of technologies didn’t even exist, and even several TMS products. Cross-platform, REST servers, ORM, cloud applications.

This new project is a workflow engine written from scratch, with all modern technologies and paradigms in mind. It will have a REST API, it will be cross-database, thread-safe, scalable, and of course will have lots of new features.

The following workflow, for example:

Can be built from code this way:

RenewalProcess := TProcessBuilder.CreateProcess
    .Variable('process_id')
    .StartEvent
    .Condition(GroupedCondition([
      TRenewalAllowedCondition.Create,
      SubscriptionNotInAnyProcess
    ]))
    .Activity(SetProcessStatus(TProcessStatus.processing))
    .Activity(IssueInvoice)
    .ExclusiveGateway.Id('gateway1')
      .Condition(THasErrorCondition.Create)
      .Activity(TRenewalFailureActivity.Create).Id('renewal_failure')
      .Activity(SetProcessStatus(TProcessStatus.failed))
      .EndEvent
    .GotoElement('gateway1')
      .Condition(InvoiceIsPaid)
      .Activity(RenewSubscription).Id('renew_subscription')
      .ExclusiveGateway.Id('gateway4')
        .Condition(THasErrorCondition.Create)
        .LinkTo('renewal_failure')
      .GotoElement('gateway4')
        .Activity(TRenewalSuccessActivity).Id('renewal_success')
        .Activity(SetProcessStatus(TProcessStatus.succeeded))
        .EndEvent
    .GotoElement('gateway1')
      .Condition(InvoiceIsOpen)
      .Activity(ChargePayment).Id('charge_payment')
      .Activity(GetPaymentStatus).Id('get_payment_status')
      .ExclusiveGateway.Id('gateway2')
        .Condition(THasErrorCondition.Create)
        .LinkTo('renewal_failure')
      .GotoElement('gateway2')
        .Condition(GroupedCondition(
          TConditionOperator._or, [PaymentIsCanceled, PaymentIsFailed]))
        .LinkTo('renewal_failure')
      .GotoElement('gateway2')
        .Condition(PaymentIsSucceeded)
        .ExclusiveGateway.Id('gateway3')
          .Condition(InvoiceIsPaid)
          .LinkTo('renew_subscription')
        .GotoElement('gateway3')
          .Condition(InvoiceIsOpen)
          .LinkTo('charge_payment')
    .Done;
end;

And can be executed in a thread queue, can be instantiated using a REST API, and many more to come. Actually, you can follow what is being developed so far in its GitHub repository. We are still working on it of course, in both features and licensing terms.

REST Server Authorization

TMS XData, our amazing library for building REST APIs and multitier applications, will have an authorization mechanism that will be very easy to use.

Developers will be able to just add authorization attributes to methods (service operations) or entities (automatic CRUD endpoints) and everything will be applied accordingly. Fine-tuning the protection of your REST API will never be as simple.

  [Authorize]
  IDocumentService = interface(IInvokable)
    procedure Insert(Value: TDoc);

    [AuthorizeScopes('user, editor')]
    procedure Modify(Value: TDoc);

    [AuthorizeScopes('admin')]
    procedure Delete(DocId: string);

    [AuthorizeClaims('email')]
    procedure Approve(DocId: string);
  end;

In the example above, all methods (endpoints) require authentication, because the interface has an Authorize attribute that propagates to all methods. So, to invoke Insert, user must be authenticated. Still, to invoke Modify, the user must be authenticated and have either user or editor scope in its credentials. He must be admin to invoke Delete, and finally to approve a document, user must have an email in its claims.

It’s also worth noting that the same strategy applies to entities that generate automatic CRUD endpoints:

  [Entity, Automapping]
  [EntityAuthorize]
  [EntityAuthorizeScopes('editor', [TEntitySetPermission.Modify, TEntitySetPermission.Insert])]
  [EntityAuthorizeScopes('admin', [TEntitySetPermission.Delete])]
  TCustomer = class
  {...}
  public
    property Id: Integer read FId write FId;
    property Name: string read FName write FName;
  end;

To access customer endpoints, user must be authenticated. But he must have editor privileges to modify and insert (PUT and POST) and must be admin to invoke DELETE. Easy and straightforward.

Development for the web: even easier

TMS XData is smoothly integrated with TMS Web Core, the TMS framework to build web applications. It provides easy-to-use, high-level client classes to access XData servers from Web Core applications. With the amazing release of TMS Web Core 1.6, several language features were introduced, like generics, async/await, attributes, among others.

This will make TMS XData client objects easier than ever to be used in web applications. The experience will be pretty similar to VCL/FMX applications, like retrieving entity objects from the server without having to deal with JSON directly and invoking endpoints using interfaces without having to worry about HTTP methods or URL endpoints.

And even more, async/await can be used. As an example, invoking a XData REST API endpoint asynchronously will be as easy as doing this:

PendingOrders := await(XClient.List<TOrder>('$filter=Status eq pending'));
if PendingOrders.Count = 0 then
  Exit; // no pending orders to process

The single line above will build the HTTP request with proper URL endpoint and HTTP method, invoke it, deserialize the returned JSON into a list of TOrder objects, and all asynchronously! The await function will guarantee that the next line will be executed only after the async execution is executed. Can’t get easier than that.

Global filters and multitenancy

Our state-of-art ORM for Delphi, TMS Aurelius, will have amazing additions this year. One of them is the global filter mechanism. Users will be able to define filters globally, including parameters, and choose which entities will have it applied.

While global filters can obviously be used for any purpose, using it to build multitenant applications is the first one that comes to mind. Consider the following entity class mapped with filtering attributes:

  [Entity, Automapping]
  [Filter('Multitenant')]
  [FilterDef('Multitenant', '{TenantId} = :tenantId')]
  [FilterDefParam('Multitenant', 'tenantId', TypeInfo(string))]
  TProduct = class
  private
    FId: Integer;
    FName: string;
    FTenantId: string;
  public
    property Id: Integer read FId write FId;
    property Name: string read FName write FName;
    property TenantId: string read FTenantId write FTenantId;
  end;

You can use the class normally, without any filter. But you can also enable the “Multitenant” filter like this:

  Manager.EnableFilter('Multitenant')
    .SetParam('tenantId', 'acme');
  Products := Manager.Find<TProduct>.OrderBy('Name').List;

After you’ve enabled the “Multitenant” filter passing the proper id, you can use the Aurelius object manager as usual. But any request you do, like in the example, asking a list of products, will add the tenant filter.

Aurelius will not only allow global filters for queries, but also will allow you to enforce the filter in INSERT, UPDATE and DELETE operations:

  FEnforcer := TFilterEnforcer.Create('Multitenant', 'TenantId', 'FTenantId');
  FEnforcer.AutoComplyOnInsert := True;
  FEnforcer.AutoComplyOnUpdate := True;
  FEnforcer.Activate(TMappingExplorer.Get('Samples'));

Thus, you can simply create or modify a product as you would usually do in single tenant application, but the filter enforcer will make sure to set data to the proper tenant id (or raise an error if the tenant is wrong, depending on your configuration).

Building single-database multitenant applications with TMS Aurelius is already possible, of course. But it will be really easy with the upcoming features. And not only that: building multi-database multitenant applications will also be easy with the new TMultiTenantConnectionPool:

  FMultiPool := TMultiTenantConnectionPool.Create(
    TXDataHttpHeaderTenantResolver.Create('tenant-id'),
    TDBConnectionPoolFactory.Create
    );

The pool will automatically choose the correct database based on the HTTP header tenant-id – or any other criteria you might choose. All transparently.

In summary: you will be able to build your app as if you are coding for single tenant applications, and let Aurelius do everything under the hood to make it multitenant, with minimum effort and in a robust and reliable way.

Data validation

Another great feature coming is data validation. Again, you can already do it using events, but soon it will be orders of magnitude easier. You will be able to add validators to your entities in several ways, attributes being the most common (irrelevant parts of the following class declaration removed for clarity):

  TCustomer = class
  private
    [Required]
    FName: string;
    
    [EmailAddress]
    FEmail: string;
  
    [DisplayName('class rate')]
    [Range(1, 10, 'Values must be %1:d up to %2:d for field %0:s')]
    FInternal_Rate: Integer;

All fields will be proper validated according to the validation attributes applied. Name will be required, Email must be a valid address and FInternal_Rate value must be between 1 and 10. With a few lines you will guarantee that the entity will be persisted with a valid state.

Validation will also help you with your API and GUI. Note how you can provide readable display names and error messages. When using validation in your API build with XData, the API responses will be detailed and informative, and all automatically:

And, of course, you can benefit from such information to easily build nice user interfaces informing the user about the validation results. The following screenshot is the same one used for the TMS Auth example above, but displaying results for password validation:

Attribute-based event handlers

Events are an important feature of any framework, and with Aurelius is not different. It provides several events you can use to add custom business logic. You can add specific code just before an entity is being saved, after an entity is deleted, when an SQL is being executed, among others.

The interesting feature coming is that you will now be able to add event handlers directly in your classes, using attributes. This will make your code even more organized, will improve readability and allow you to better follow the single-responsibility principle. It will be as simple as this:

TCustomer = class
  [OnInserting]
  procedure DoSomethingBeforeSave;

  [OnDelete]
  procedure DoSomethingAfterDelete;

Whenever a customer entity is about to be persisted, OnInserting method will be called and you can add any relevant code in that method. You can receive arguments in OnInserting, or you can even use with without them, like in the example above, making you class really independent of 3rd party custom types.

And more events are coming as well, for example, the new data validation mechanism mentioned above will also offer you an OnValidate event, which you can easily use like this:

TCustomer = 
  [OnValidate]
  function CheckBirthday: IValidationResult;

{...}  

  function TCustomer.CheckBirthday: IValidationResult;
  begin
    Result := TValidationResult.Create;

    if YearOf(Birthday) < 1899 then
      Result.Errors.Add(TValidationError
        .Create('A person born in the XIX century is not accepted'));

    if (MonthOf(Birthday) = 8) and (DayOf(Birthday) = 13) then
      Result.Errors.Add(TValidationError
        .Create('A person born on August, 13th is not accepted'));
  end;

That’s all…?

I hope you have followed me until here, and I hope you are as excited as us with the upcoming features and and libraries. Yes, that’s a lot of things to expect for Delphi and TMS Business in 2021! But I’d like to stress that what’s coming is not necessarily limited to what we put here. There might be even more! (Things we cannot disclose right now).

Did you like the news? Please comment below, let us know what you think and what are your opinions and expectations on this. And don’t forget to subscribe to our news channels to be informed when things are released and for many other exciting news from TMS: subscribe to our e-mail newsletter, follow our Facebook page and subscribe to our YouTube channel!

Update: Want to see these listed features in action? Register for the free webinar “What is coming in TMS BIZ in 2021”, to happen on Tuesday, February 23, 2021 3:00:00 PM UTC at the TMS Web Academy!

Read More

Read More

GraphQL, workflow engine, multitenancy and more: what you will do with Delphi in 2021

Here at TMS, we are working on so much new exciting frameworks and features that I decided to share with you what we expect to bring to you in 2021!


Photo by Rhett Wesley on Unsplash

First, a disclaimer: this is not an official commitment. Some of the things listed here might be delayed or not be released at all. But of course, that’s not our intention, since we are listing it here. We want to (and probably will) release all of this in 2021. But, since unexpected things might happen, we can never guarantee what will happen in future. One second thing: thislist only covers what is coming around TMS Business line of products. There is much more to come from TMS in 2021!

I was going to title this post as “TMS Business Roadmap for 2021”. But I thought that the current title brings more attention and curiosity. And it looks like it worked, thanks for coming! ??

Ok, jokes aside, another reason is that we still don’t know if all of the libraries, features and technologies listed here will be part of the TMS Business bundle, and the original title could be misleading.

But regardless, everything listed here will for sure be smoothly integrated with TMS Business – either using ORM (TMS Aurelius) or REST/JSON framework (TMS XData), or any other core product, what is for sure is that everything listed here makes up a huge ecosystem around TMS Business technologies.

An exciting year is coming ahead! So, without further ado, this is what we expect to bring to Delphi world this year, with TMS Business.

GraphQL

TMS is bringing GraphQL to Delphi! We have it already in a beta state, the core is complete, all is going fine. We still need to document everything, and add a few more features here and there, but it’s in a very advanced stage.

This teaser video shows GraphQL server in action, written in Delphi! The frontend is GraphQL Playground JS framework, but still being served via a Delphi server.

The relevant parts of the code used to build the server in the video are the following. First, the GraphQL schema is built:

SchemaDocument := TGraphQLDocumentParser.ParseSchema(
    'type Query { hello(name: String!): String }');
  Schema := TGraphQLSchemaBuilder.Build(SchemaDocument);

  Schema.SetResolver('Query', 'hello', function(Args: TFieldResolverArgs): TValue
    begin
      Result := 'Hello, ' + Args.GetArgument('name').AsString;
    end
  );

Then, the endpoints for the GraphQL API and the GraphQL Playground are created:

  ADispatcher.AddModule(
    TGraphQLServerModule.Create('http://+:2001/tms/graphql', FSchema));

  ADispatcher.AddModule(
    TGraphQLPlaygroundModule.Create('http://+:2001/tms/playground', '/tms/graphql'));

And that’s it for the “Hello, world” demo!

TMS Auth

A complete framework for adding authentication to your application, with user, roles and permissions management and providing features like login, registration, e-mail confirmation and more.

Relying on Aurelius ORM and XData REST framework, with a few lines of code you will get all your database setup and have an authentication API server running, with lots of features.

Well, it’s said one picture is worth a thousand words, so here is an example of the available API out of box:

And these are screenshots of an existing app built with TMS Web Core which is already using TMS Auth for user registration and login:

It will never been as easy to add user and permissions management and login system to your Delphi application and server!

BPM Workflow

A full workflow engine is coming. You might ask what is the difference between this new library and TMS Workflow Studio.

It’s a night and day difference. TMS Workflow Studio is nice product, but it was made 13 years ago with a different goal in mind – desktop VCL applications – and when lots of technologies didn’t even exist, and even several TMS products. Cross-platform, REST servers, ORM, cloud applications.

This new project is a workflow engine written from scratch, with all modern technologies and paradigms in mind. It will have a REST API, it will be cross-database, thread-safe, scalable, and of course will have lots of new features.

The following workflow, for example:

Can be built from code this way:

RenewalProcess := TProcessBuilder.CreateProcess
    .Variable('process_id')
    .StartEvent
    .Condition(GroupedCondition([
      TRenewalAllowedCondition.Create,
      SubscriptionNotInAnyProcess
    ]))
    .Activity(SetProcessStatus(TProcessStatus.processing))
    .Activity(IssueInvoice)
    .ExclusiveGateway.Id('gateway1')
      .Condition(THasErrorCondition.Create)
      .Activity(TRenewalFailureActivity.Create).Id('renewal_failure')
      .Activity(SetProcessStatus(TProcessStatus.failed))
      .EndEvent
    .GotoElement('gateway1')
      .Condition(InvoiceIsPaid)
      .Activity(RenewSubscription).Id('renew_subscription')
      .ExclusiveGateway.Id('gateway4')
        .Condition(THasErrorCondition.Create)
        .LinkTo('renewal_failure')
      .GotoElement('gateway4')
        .Activity(TRenewalSuccessActivity).Id('renewal_success')
        .Activity(SetProcessStatus(TProcessStatus.succeeded))
        .EndEvent
    .GotoElement('gateway1')
      .Condition(InvoiceIsOpen)
      .Activity(ChargePayment).Id('charge_payment')
      .Activity(GetPaymentStatus).Id('get_payment_status')
      .ExclusiveGateway.Id('gateway2')
        .Condition(THasErrorCondition.Create)
        .LinkTo('renewal_failure')
      .GotoElement('gateway2')
        .Condition(GroupedCondition(
          TConditionOperator._or, [PaymentIsCanceled, PaymentIsFailed]))
        .LinkTo('renewal_failure')
      .GotoElement('gateway2')
        .Condition(PaymentIsSucceeded)
        .ExclusiveGateway.Id('gateway3')
          .Condition(InvoiceIsPaid)
          .LinkTo('renew_subscription')
        .GotoElement('gateway3')
          .Condition(InvoiceIsOpen)
          .LinkTo('charge_payment')
    .Done;
end;

And can be executed in a thread queue, can be instantiated using a REST API, and many more to come. Actually, you can follow what is being developed so far in its GitHub repository. We are still working on it of course, in both features and licensing terms.

REST Server Authorization

TMS XData, our amazing library for building REST APIs and multitier applications, will have an authorization mechanism that will be very easy to use.

Developers will be able to just add authorization attributes to methods (service operations) or entities (automatic CRUD endpoints) and everything will be applied accordingly. Fine-tuning the protection of your REST API will never be as simple.

  [Authorize]
  IDocumentService = interface(IInvokable)
    procedure Insert(Value: TDoc);

    [AuthorizeScopes('user, editor')]
    procedure Modify(Value: TDoc);

    [AuthorizeScopes('admin')]
    procedure Delete(DocId: string);

    [AuthorizeClaims('email')]
    procedure Approve(DocId: string);
  end;

In the example above, all methods (endpoints) require authentication, because the interface has an Authorize attribute that propagates to all methods. So, to invoke Insert, user must be authenticated. Still, to invoke Modify, the user must be authenticated and have either user or editor scope in its credentials. He must be admin to invoke Delete, and finally to approve a document, user must have an email in its claims.

It’s also worth noting that the same strategy applies to entities that generate automatic CRUD endpoints:

  [Entity, Automapping]
  [EntityAuthorize]
  [EntityAuthorizeScopes('editor', [TEntitySetPermission.Modify, TEntitySetPermission.Insert])]
  [EntityAuthorizeScopes('admin', [TEntitySetPermission.Delete])]
  TCustomer = class
  {...}
  public
    property Id: Integer read FId write FId;
    property Name: string read FName write FName;
  end;

To access customer endpoints, user must be authenticated. But he must have editor privileges to modify and insert (PUT and POST) and must be admin to invoke DELETE. Easy and straightforward.

Development for the web: even easier

TMS XData is smoothly integrated with TMS Web Core, the TMS framework to build web applications. It provides easy-to-use, high-level client classes to access XData servers from Web Core applications. With the amazing release of TMS Web Core 1.6, several language features were introduced, like generics, async/await, attributes, among others.

This will make TMS XData client objects easier than ever to be used in web applications. The experience will be pretty similar to VCL/FMX applications, like retrieving entity objects from the server without having to deal with JSON directly and invoking endpoints using interfaces without having to worry about HTTP methods or URL endpoints.

And even more, async/await can be used. As an example, invoking a XData REST API endpoint asynchronously will be as easy as doing this:

PendingOrders := await(XClient.List<TOrder>('$filter=Status eq pending'));
if PendingOrders.Count = 0 then
  Exit; // no pending orders to process

The single line above will build the HTTP request with proper URL endpoint and HTTP method, invoke it, deserialize the returned JSON into a list of TOrder objects, and all asynchronously! The await function will guarantee that the next line will be executed only after the async execution is executed. Can’t get easier than that.

Global filters and multitenancy

Our state-of-art ORM for Delphi, TMS Aurelius, will have amazing additions this year. One of them is the global filter mechanism. Users will be able to define filters globally, including parameters, and choose which entities will have it applied.

While global filters can obviously be used for any purpose, using it to build multitenant applications is the first one that comes to mind. Consider the following entity class mapped with filtering attributes:

  [Entity, Automapping]
  [Filter('Multitenant')]
  [FilterDef('Multitenant', '{TenantId} = :tenantId')]
  [FilterDefParam('Multitenant', 'tenantId', TypeInfo(string))]
  TProduct = class
  private
    FId: Integer;
    FName: string;
    FTenantId: string;
  public
    property Id: Integer read FId write FId;
    property Name: string read FName write FName;
    property TenantId: string read FTenantId write FTenantId;
  end;

You can use the class normally, without any filter. But you can also enable the “Multitenant” filter like this:

  Manager.EnableFilter('Multitenant')
    .SetParam('tenantId', 'acme');
  Products := Manager.Find<TProduct>.OrderBy('Name').List;

After you’ve enabled the “Multitenant” filter passing the proper id, you can use the Aurelius object manager as usual. But any request you do, like in the example, asking a list of products, will add the tenant filter.

Aurelius will not only allow global filters for queries, but also will allow you to enforce the filter in INSERT, UPDATE and DELETE operations:

  FEnforcer := TFilterEnforcer.Create('Multitenant', 'TenantId', 'FTenantId');
  FEnforcer.AutoComplyOnInsert := True;
  FEnforcer.AutoComplyOnUpdate := True;
  FEnforcer.Activate(TMappingExplorer.Get('Samples'));

Thus, you can simply create or modify a product as you would usually do in single tenant application, but the filter enforcer will make sure to set data to the proper tenant id (or raise an error if the tenant is wrong, depending on your configuration).

Building single-database multitenant applications with TMS Aurelius is already possible, of course. But it will be really easy with the upcoming features. And not only that: building multi-database multitenant applications will also be easy with the new TMultiTenantConnectionPool:

  FMultiPool := TMultiTenantConnectionPool.Create(
    TXDataHttpHeaderTenantResolver.Create('tenant-id'),
    TDBConnectionPoolFactory.Create
    );

The pool will automatically choose the correct database based on the HTTP header tenant-id – or any other criteria you might choose. All transparently.

In summary: you will be able to build your app as if you are coding for single tenant applications, and let Aurelius do everything under the hood to make it multitenant, with minimum effort and in a robust and reliable way.

Data validation

Another great feature coming is data validation. Again, you can already do it using events, but soon it will be orders of magnitude easier. You will be able to add validators to your entities in several ways, attributes being the most common (irrelevant parts of the following class declaration removed for clarity):

  TCustomer = class
  private
    [Required]
    FName: string;
    
    [EmailAddress]
    FEmail: string;
  
    [DisplayName('class rate')]
    [Range(1, 10, 'Values must be %1:d up to %2:d for field %0:s')]
    FInternal_Rate: Integer;

All fields will be proper validated according to the validation attributes applied. Name will be required, Email must be a valid address and FInternal_Rate value must be between 1 and 10. With a few lines you will guarantee that the entity will be persisted with a valid state.

Validation will also help you with your API and GUI. Note how you can provide readable display names and error messages. When using validation in your API build with XData, the API responses will be detailed and informative, and all automatically:

And, of course, you can benefit from such information to easily build nice user interfaces informing the user about the validation results. The following screenshot is the same one used for the TMS Auth example above, but displaying results for password validation:

Attribute-based event handlers

Events are an important feature of any framework, and with Aurelius is not different. It provides several events you can use to add custom business logic. You can add specific code just before an entity is being saved, after an entity is deleted, when an SQL is being executed, among others.

The interesting feature coming is that you will now be able to add event handlers directly in your classes, using attributes. This will make your code even more organized, will improve readability and allow you to better follow the single-responsibility principle. It will be as simple as this:

TCustomer = class
  [OnInserting]
  procedure DoSomethingBeforeSave;

  [OnDelete]
  procedure DoSomethingAfterDelete;

Whenever a customer entity is about to be persisted, OnInserting method will be called and you can add any relevant code in that method. You can receive arguments in OnInserting, or you can even use with without them, like in the example above, making you class really independent of 3rd party custom types.

And more events are coming as well, for example, the new data validation mechanism mentioned above will also offer you an OnValidate event, which you can easily use like this:

TCustomer = 
  [OnValidate]
  function CheckBirthday: IValidationResult;

{...}  

  function TCustomer.CheckBirthday: IValidationResult;
  begin
    Result := TValidationResult.Create;

    if YearOf(Birthday) < 1899 then
      Result.Errors.Add(TValidationError
        .Create('A person born in the XIX century is not accepted'));

    if (MonthOf(Birthday) = 8) and (DayOf(Birthday) = 13) then
      Result.Errors.Add(TValidationError
        .Create('A person born on August, 13th is not accepted'));
  end;

That’s all…?

I hope you have followed me until here, and I hope you are as excited as us with the upcoming features and and libraries. Yes, that’s a lot of things to expect for Delphi and TMS Business in 2021! But I’d like to stress that what’s coming is not necessarily limited to what we put here. There might be even more! (Things we cannot disclose right now).

Did you like the news? Please comment below, let us know what you think and what are your opinions and expectations on this. And don’t forget to subscribe to our news channels to be informed when things are released and for many other exciting news from TMS: subscribe to our e-mail newsletter, follow our Facebook page and subscribe to our YouTube channel!

Update: Want to see these listed features in action? Register for the free webinar “What is coming in TMS BIZ in 2021”, to happen on Tuesday, February 23, 2021 3:00:00 PM UTC at the TMS Web Academy!

Read More

Read More

Planning / Scheduling in FMX

Intro

The multi-device, true native app platform The FireMonkey® framework is the app development and runtime platform behind RAD Studio, Delphi and C++Builder. FireMonkey is designed for teams building multi-device, true native apps for Windows, OS X, Android and iOS, and getting them to app stores and enterprises fast.

source: https://www.embarcadero.com/products/rad-studio/fm-application-platform

FMX (FireMonkey) released in 2011 and shortly after we delivered a first set of components. Today, we want to show you the TTMSFNCPlanner component, a highly configurable planning/scheduling component.

Features

Below is a list of the most important features the TTMSNCPlanner has to offer. The features are not limited to this list, but this will give you a quick insight on what we offer to be able to view and edit appointments / tasks in FireMonkey.

  • Built-in and customizable inplace and dialog editing
  • Moveable and sizeable items with HTML formatted text and hyperlink detection
  • High performance virtual mode
  • Various display modes: day, month, day period, half day period, multi day, multi month, multi day resource, multi resource day and custom displays
  • Multiple events for all kinds of interactions such as editing, item inserting, updating, moving and sizing
  • Multiple events for custom drawing and customization of default drawing
  • Item hints and time indication helpers
  • Optional overlapping items
  • Touch scrolling and selection
  • Optimized for mobile devices
  • Recurrency support
  • Databinding support via the TTMSFNCPlannerDatabaseAdapter
  • Separate ToolBar Popup
  • PDF Export capabilities

Learn More!

Want to learn more about what the TTMSFNCPlanner can do? Here is a video that highlights some of the above features through a demo application.

Download & Explore!

The TTMSFNCPlanner component is part of the TMS FNC UI Pack, which, on top of FMX, also offers the ability to write your code once and target other frameworks (VCL, LCL and WEB). You can download a full featured trial version of the TMS FNC UI Pack and start exploring the capabilities of the TTMSFNCPlanner component.

Coming up

The TTMSFNCPlanner is the second of a series of components that is covered to empower your FMX (FireMonkey) developments. We started the series with a general overview of the most important components that we have to offer, followed by the TTMSFNCRichEditor. Next up will be the TTMSFNCTreeView component, a highly configurable, high performance tree view with virtual and collection-based modes able to deal with millions of nodes so stay tuned for more!.

Read More

Read More

Planning / Scheduling in FMX

Intro

The multi-device, true native app platform The FireMonkey® framework is the app development and runtime platform behind RAD Studio, Delphi and C++Builder. FireMonkey is designed for teams building multi-device, true native apps for Windows, OS X, Android and iOS, and getting them to app stores and enterprises fast.

source: https://www.embarcadero.com/products/rad-studio/fm-application-platform

FMX (FireMonkey) released in 2011 and shortly after we delivered a first set of components. Today, we want to show you the TTMSFNCPlanner component, a highly configurable planning/scheduling component.

Features

Below is a list of the most important features the TTMSNCPlanner has to offer. The features are not limited to this list, but this will give you a quick insight on what we offer to be able to view and edit appointments / tasks in FireMonkey.

  • Built-in and customizable inplace and dialog editing
  • Moveable and sizeable items with HTML formatted text and hyperlink detection
  • High performance virtual mode
  • Various display modes: day, month, day period, half day period, multi day, multi month, multi day resource, multi resource day and custom displays
  • Multiple events for all kinds of interactions such as editing, item inserting, updating, moving and sizing
  • Multiple events for custom drawing and customization of default drawing
  • Item hints and time indication helpers
  • Optional overlapping items
  • Touch scrolling and selection
  • Optimized for mobile devices
  • Recurrency support
  • Databinding support via the TTMSFNCPlannerDatabaseAdapter
  • Separate ToolBar Popup
  • PDF Export capabilities

Learn More!

Want to learn more about what the TTMSFNCPlanner can do? Here is a video that highlights some of the above features through a demo application.

Download & Explore!

The TTMSFNCPlanner component is part of the TMS FNC UI Pack, which, on top of FMX, also offers the ability to write your code once and target other frameworks (VCL, LCL and WEB). You can download a full featured trial version of the TMS FNC UI Pack and start exploring the capabilities of the TTMSFNCPlanner component.

Coming up

The TTMSFNCPlanner is the second of a series of components that is covered to empower your FMX (FireMonkey) developments. We started the series with a general overview of the most important components that we have to offer, followed by the TTMSFNCRichEditor. Next up will be the TTMSFNCTreeView component, a highly configurable, high performance tree view with virtual and collection-based modes able to deal with millions of nodes so stay tuned for more!.

Read More

Read More

Make SSDP discovery on FMX (Android) Delphi

I'm trying to do SSDP discovery on a little FMX(Android) app of myself (to discover my ambilight server). This is where I'm right now : procedure TfrmHyperionManager.searchSSDP; var _udpClient: TIdUDPClient; _peerPort: Word; _peerIP, _response: string; begin lblServers.Text:= 'Recherche de serveurs Hyperion...'; _udpClient:= TIdUDPClient.Create(nil); try {$IFDEF ANDROID} _udpClient.Active := true; {$ENDIF} _udpClient.BoundIP:= GStack.LocalAddress; _udpClient.ReceiveTimeout:= 3000; _udpClient.Send( '239.255.255.250', 1900, 'M-SEARCH * HTTP/1.1' + #13#10 + 'HOST: 239.255.255.250:1900' + #13#10 + 'MAN: "ssdp:discover"'+ #13#10 + 'MX: 3'+ #13#10 + 'ST: urn:hyperion-project.org:device:basic:1'+ #13#10 + #13#10 ); repeat _response:= _udpClient.ReceiveString( _peerIP, _peerPort ); if ( _peerPort <> 0 ) then FServersList.Add( _peerIP ); until ( _peerPort = 0 ); fillServerCombo; finally _udpClient.Free; end; end; Now that works on my phone (Android 10), but it doesn't on some other phones...it is a bit of hit and miss, sometimes it works on a phone model (let's say Redmi Note 7) and it does not on the same model of another person...(app does not open). Do I do it the right way ? If not can you give me some example of how to do this properly ? I'm sorry for this question but it is my first attempt at an Android app with Delphi, the rest of time I only do windows development with Delphi for my company. Many thanks.
Read More

Delphi SDKtransform: many errors and missing classes in MacOS SDK

I have used SDKtransform for years (executed through "SDK Transform Assistant") to implement the iOS and OSX frameworks that are not included with Delphi. However, an increasing number of classes fail to be converted and are not included in the output .pas header file. Right now I need to convert AVAudioEngine from AVFoundation in MacOS 11 (Big Sur), but SDKtransform fails with a lot of these lines added to the log: "WARN: cannot add ObjCInterface AVAudioEngine - base class unknown" Could these be due to the way it is defined? API_AVAILABLE(macos(10.10), ios(8.0), watchos(2.0), tvos(9.0)) @interface AVAudioEngine : NSObject { @private void *_impl; } - (instancetype)init; - (void)attachNode:(AVAudioNode *)node; - (void)detachNode:(AVAudioNode *)node; ... @property (readonly, nonatomic) BOOL isInManualRenderingMode API_AVAILABLE(macos(10.13), ios(11.0), watchos(4.0), tvos(11.0)); ... @end // AVAudioEngine How do I autoconvert this type of header definitions?
Read More

SVG quality improvements

Intro

A while back, we introduced SVG in TMS VCL UI Pack. The first version focused on smaller icon sets. We have meanwhile tweaked and improved the engine and although there is still a lot of work to be done, the next update of TMS VCL UI Pack will offer 2 quality of life improvements:

  • Gradients (GDI+)
  • Rendering quality (GDI+)
  • Disabled drawing (TVirtualImageList)

Gradients

Gradients were left out of the first version because of shortcomings on other platforms, as we also have SVG rendering engine support for FNC targeting not only VCL, but also FMX, LCL and WEB. The underlying native graphics can be quite complex and have various differences between frameworks. Therefore we decided to take the initial step of offering linear & radial gradients support in VCL only using GDI+. Please note that we are still targeting smaller less complex icons, but we’ll add improvements over time.

Rendering quality

The initial version did not use the full quality that GDI+ had to offer. When working with SVG, we focused on adding features instead of quality and therefore the rendering quality was not optimal. We have now changed this so it has a smoother appearance independant of the size.

v1.0                       v1.1

      

Disabled drawing

In v1.0 we also added support for TVirtualImageList, which converts SVG files into bitmaps on the correct size without losing quality. You can add a TAdvSVGImageCollection attached to a TVirtualImageList and specify only one SVG suitable for any size you want. The TVirtualImageList also has the ability of automatically adding disabled state versions of the converted bitmaps. Initially, this was not working due to the way the bitmap alpha channel was configured and the SVG quality drawing on the bitmap before returning it to the TVirtualImageList. In this update, we have added correct alpha channel to the bitmap conversion routine.

What’s next?

The above applies to VCL only and will be part of the next update of TMS VCL UI Pack. The engine is a port from FNC, support for other operating systems (macOS, iOS, Android) is still ongoing. As soon as things get shape, we’ll post an update.

Read More

Read More