Products



Stay in touch

Add your e-mail address here to receive the monthly TMS Software alerts.


    

Content:
Product releases
Product articles
Technical articles
Website changes
Software development

Products:

<< >>
May 2016



Tuesday, May 03, 2016

Revisiting persisting app settings in the cloud

Back in 2013, I wrote a blog on persisting application settings in the cloud like Google Chrome for example does. The solution presented at that time was using the DropBox cloud service. The technique was quite simple. Persist settings in an INI file and automatically store and retrieve this INI file from a DropBox account.

Now, 3 years later, I wanted to revisit this technique and present a new solution that is more flexible and fine-grained and can be used from apps on Windows, Mac OSX, iOS, Android and Linux. The new technique will use the TMS structured data cloud storage service myCloudData.net.

As a brief introduction to myCloudData, think of it as configurable tables in the cloud, accessible with a REST API and using OAUTH 2 for authentication & authorization. If you want to learn more about myCloudData.net, check its API doc or another blog article written about it.

Time to get down to the nitty gritties! As an example, a VCL app was created with a left and right aligned panel and inbetween a memo. The user can customize the left panel and right panel width with a splitter control and by right-clicking the panel, the panel color can be changed. Further, there is a checkbox for both panels to toggle the visibility of the panel. This is a typical and basic skeleton of what many user configurable apps could be.



We'll now use the myCloudData.net service to store not only how the user configures this left and right panel, but also the last form's size and position on the screen. Whenever the user restarts the app, it will bring back the app as he originally left it on whatever machine the user starts this app. He will always find it back as he left it.
We'll drop a TAdvmyCloudData component on the form and in the form's constructor, we'll initialize it with the application key & secret obtained to use the myCloudData.net service. Further, this component is initialized to persist its application token & secret and finally, the call to AdvmyCloudData.Connect will test validity of possible existing tokens or will perform a new authentication & authorization when no valid tokens are found.
procedure TmCDDemo.FormCreate(Sender: TObject);
begin
  FConnected := false;
  AdvMyCloudData1.App.Key := MYCLOUDDATAKEY;
  AdvMyCloudData1.App.Secret := MYCLOUDDATASECRET;
  AdvMyCloudData1.App.CallBackPort := 8888;
  AdvMyCloudData1.App.CallBackURL := 'http://127.0.0.1:8888';

  AdvMyCloudData1.PersistTokens.Location := plIniFile;
  AdvMyCloudData1.PersistTokens.Key := '.myCloudData.ini';
  AdvMyCloudData1.PersistTokens.Section := 'tokens';
  AdvMyCloudData1.Connect;
end;
Once this step is performed, the AdvMyCloudData component triggers the OnConnected event from where we call the form's ReadSettings method.

In the ReadSettings method, it is checked whether the table to hold the settings exists for the user account on the service or not. When found, the first record from the table is read to retrieve the settings. When not found, the metadata for the table is created. Of course when the new table is created with its metadata, it's not yet possible to retrieve stored settings. This table will only be used the first time then when the user closes the app.
procedure TmCDDemo.ReadSettings;
var
  table: TMyCloudDataTable;

begin
  table := AdvMyCloudData1.TableByName('SETTINGS');

  if not Assigned(table) then
  begin
    table := AdvMyCloudData1.CreateTable('SETTINGS');
    table.MetaData.Add('PNLVISL', ftBoolean);
    table.MetaData.Add('PNLVISR', ftBoolean);
    table.MetaData.Add('PNLWL', ftInteger);
    table.MetaData.Add('PNLWR', ftInteger);
    table.MetaData.Add('PNLCLRL', ftInteger);
    table.MetaData.Add('PNLCLRR', ftInteger);
    table.MetaData.Add('FORMW', ftInteger);
    table.MetaData.Add('FORMH', ftInteger);
    table.MetaData.Add('FORML', ftInteger);
    table.MetaData.Add('FORMT', ftInteger);
    table.SetMetaData;
  end
  else
  begin
    table.Query;
    if table.Entities.Count > 0 then
    begin
      Self.Left := table.Entities[0].Value['FORML'];
      Self.Top := table.Entities[0].Value['FORMT'];
      Self.Width := table.Entities[0].Value['FORMW'];
      Self.Height := table.Entities[0].Value['FORMH'];
      LeftPanel.Visible := table.Entities[0].Value['PNLVISL'];
      RightPanel.Visible := table.Entities[0].Value['PNLVISR'];
      LeftPanel.Width := table.Entities[0].Value['PNLWL'];
      RightPanel.Width := table.Entities[0].Value['PNLWR'];
      LeftPanel.Color := TColor(table.Entities[0].Value['PNLCLRL']);
      RightPanel.Color := TColor(table.Entities[0].Value['PNLCLRR']);

      LeftCheckBox.Checked := LeftPanel.Visible;
      RightCheckBox.Checked := RightPanel.Visible;
    end;
  end;
end;
It was a choice to create this metadata programmatically. It could as well have been created in advance online via the metadata editor. When checking the myCloudData.net account, after this programmatic creation of metadata, it is shown as:

To store the settings, all we need to do is call WriteSettings from the form's OnClose event. This of course is only done when TAdvmyCloudData was able to succesfully connect to an account on the service:
procedure TmCDDemo.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  if FConnected then
    WriteSettings;
end;
Persisting the settings in the WriteSettings procedure becomes:
procedure TmCDDemo.WriteSettings;
var
  table: TMyCloudDataTable;
  entity: TMyCloudDataEntity;
  ins: boolean;
begin
  table := AdvMyCloudData1.TableByName('SETTINGS');

  table.Query;

  ins := table.Entities.Count = 0;

  if not ins then
    entity := table.Entities[0]
  else
    entity := table.Entities.Add;

  entity.Value['FORML'] := Self.Left;
  entity.Value['FORMT'] := Self.Top;
  entity.Value['FORMW'] := Self.Width;
  entity.Value['FORMH'] := Self.Height;
  entity.Value['PNLVISL'] := LeftPanel.Visible;
  entity.Value['PNLVISR'] := RightPanel.Visible;
  entity.Value['PNLWL'] := LeftPanel.Width;
  entity.Value['PNLCLRL'] := integer(LeftPanel.Color);
  entity.Value['PNLWR'] := RightPanel.Width;
  entity.Value['PNLCLRR'] := integer(RightPanel.Color);

  if not ins then
    entity.Update
  else
    entity.Insert;
end;
So, with this small amount of code and a one-time authentication and authorization of the myCloudData account, the user will always have the last settings of the app he used, on any machine from where he is using the application.
I hope this serves as a small example of uses for the myCloudData.net service and actually only scratches the surface of possibilities. The use of myCloudData.net for the functionality demonstrated here is completely free, as in free beer. Only when using large amounts of data or blob fields, a subscription to myCloudData.net is required.
Our TMS Cloud Pack component TAdvmyCloudData makes it completely abstract and dead-simple to use the underlying REST API from the service for VCL applications and this component is also available with exactly the same interface for FireMonkey applications and Lazarus applications.

We are eager to learn about your creative and innovative applications using these components and service and in what further directions this can be developed to satisfy your needs!

The full source code of this sample application can be downloaded here.

Bookmarks: 

Bruno Fierens




This blog post has not received any comments yet. Add a comment.



Tuesday, April 26, 2016

Introducing TMS FNC UI Pack

TMS FNC UI Controls, a set of framework neutral controls

Introduction

As your customers use an ever increasing number of devices & operating systems, it is a daily challenge for us, software developers to make our software available for the myriad of target platforms in use. Fortunately, as Pascal developers, we already have a huge benefit that Delphi meanwhile targets 4 platforms: Windows, Mac OSX, iOS and Android. Add Lazarus to that, and Pascal developers can also target Linux and its derivatives such as Raspbian and many more. The 3 main frameworks we have available to create our software for these platforms are: VCL, FMX and LCL. The framework to use will be determined by the target and the IDE used. That implies that the controls that can be used are also typically determined by this choice and might limit your abilities when some controls or control features are not available for one of these frameworks. Addressing that limitation is exactly one of the primary goals of the TMS FNC UI Controls. It offers you a set of powerful & feature-rich UI controls that you can use in Delphi's VCL framework, FMX framework and Lazarus LCL framework. It allows to create Win32, Win64, Mac OS-X, iOS, Android, Linux, Raspbian, ... applications with a single codebase and a single learning curve.

Concept

FNC controls enable you to write and use 100% identical Pascal code, be it in a VCL app, FMX app or LCL app. The FNC components methods, properties and events are therefore 100% identical regardless of the framework being used. As an example, the following code creates a new event in our FNC Planner control:

var
  plIt: TTMSFNCPlannerItem;
begin
  plIt := TMSFNCPlanner1.Items.Add;
  plIt.StartTime := Date + EncodeTime(8,30,0,0);
  plIt.EndTime := Date + EncodeTime(11,0,0,0);
  plIt.Title := 'New event';
  plIt.Text := 'Content';
end;
and from this code, it is impossible to tell whether it will be from a VCL, FMX or LCL app. In the application UI, it will also look exactly the same regardless of framework or operating system:


This means that if you properly separate your logic or adopt an MVC approach, you can easily share .PAS files between VCL and FMX projects, between VCL and LCL projects etc... There are in this respect actually only two limitations. First limitation is the design-time form fileformat that is different between VCL, FMX and LCL. VCL uses the .dfm file, FMX uses the .fmx file and LCL uses the .lfm file. For applications for different devices with different form factors, it typically already requires to design the form separately for separate frameworks, so this isn't too much of a limitation. For other applications, a solution is to create the controls at runtime. A second limitation is the namespaces (unit names). To be able to register identical classnames for different framework controls in Delphi, it is required that these live in different namespaces. As such, the FNC VCL controls unit names have the prefix VCL., the FNC FMX controls unit names have the prefix FMX. and the FNC LCL controls use prefix LCL (without dot, to be able to support FPC versions older than v3.0)

In practice, this means that for the example above with the TMSFNCPlanner, the unit clauses for the different frameworks would be as below. To keep using a single source file, a solution is to set a define at project level depending on the framework and write:

uses 
{$IFDEF VCL}
  VCL.TMSFNCPlannerBase, VCL.TMSFNCPlannerData, VCL.TMSFNCPlanner, VCL.TMSFNCCustomControl;
{$ENDIF}

{$IFDEF FMX}
  FMX.TMSFNCPlannerBase, FMX.TMSFNCPlannerData, FMX.TMSFNCPlanner, FMX.TMSFNCCustomControl;
{$ENDIF}

{$IFDEF LCL}
 LCLTMSFNCPlannerBase,  LCLTMSFNCPlannerData, LCLTMSFNCPlanner, LCLTMSFNCCustomControl;    
{$ENDIF}
In the same way, when used, we could include the resource of our form file that is different in each framework via a conditional define:

{$IFDEF VCL}
{$R *.dfm}
{$ENDIF}

{$IFDEF FMX}
{$R *.fmx}
{$ENDIF}

{$IFDEF LCL}
{$R *.lfm}
{$ENDIF}
These are of course the things you need to take in account when you want to create a single codebase to build projects with multiple frameworks. In other cases, you do not need to take care of this and you can enjoy the exact same feature set of this UI component library irrespective of the IDE and platform you target.

Another important core concept is the introduction of the TMS FNC Graphics library that is included. This enables you to write graphics code that is framework independent. This includes framework neutral colors, fill, stroke, alignment, font, path types and the TTMSFNCGraphics class using this to draw everything you need. This is a sample code snippet of framework neutral drawing:

var
  gr: TTMSFNCGraphics;
begin
  gr := TTMSFNCGraphics.Create(PaintBox1.Canvas);

  gr.Fill.Color := gcYellow;
  gr.Stroke.Color := gcGray;

  gr.DrawRectangle(0,0,100,20);

  gr.Font.Color := gcRed;
  gr.DrawText(2,0,100,20,'Hello world',false)
  gr.Free;
end;
The result is:


and is exactly the same on every framework, target, device, ...

Controls

In TMS FNC UI Pack v1.0, there are already 29 controls included. On the tool palette this looks like:

VCL, FMX



LCL



This includes a grid, planner, richeditor, treeview, various color, font, fontsize, bitmap ... picker , toolbar, ... and more.

Introducing the TMS FNC Grid

The TMS FNC Grid is a high-performance, not data-bound grid capable of dealing with hundreds of thousands of rows, has a wide range of cell types and inplace editors, offers built-in sorting, filtering and grouping and can import and export data in several file formats. To illustrate some of the capabilities of the TMS FNC Grid, here is some framework code initializing the grid to show several cell capability features:

begin
  TMSFNCGrid1.FixedRows := 2;
  TMSFNCGrid1.ColumnCount := 7;
  TMSFNCGrid1.MergeCells(1,0,2,1);
  TMSFNCGrid1.MergeCells(3,0,2,1);
  TMSFNCGrid1.MergeCells(5,0,2,1);

  TMSFNCGrid1.Cells[1,0] := 'Monday';
  TMSFNCGrid1.HorzAlignments[1,0] := gtaCenter;
  TMSFNCGrid1.Cells[1,1] := 'AM';
  TMSFNCGrid1.Cells[2,1] := 'PM';

  TMSFNCGrid1.Cells[3,0] := 'Tuesday';
  TMSFNCGrid1.HorzAlignments[3,0] := gtaCenter;
  TMSFNCGrid1.Cells[3,1] := 'AM';
  TMSFNCGrid1.Cells[4,1] := 'PM';

  TMSFNCGrid1.Cells[5,0] := 'Wednesday';
  TMSFNCGrid1.HorzAlignments[5,0] := gtaCenter;
  TMSFNCGrid1.Cells[5,1] := 'AM';
  TMSFNCGrid1.Cells[6,1] := 'PM';
  TMSFNCGrid1.AutoNumberCol(0);

  TMSFNCGrid1.AddCheckBox(1,2,false);
  TMSFNCGrid1.AddRadioButton(1,3,1);

  TMSFNCGrid1.AddProgressBar(3,2,50);
  TMSFNCGrid1.Cells[3,3] := 'Hello world';

  TMSFNCGrid1.AddBitmapFile(5,2,'e:	mscalendar.png');
  TMSFNCGrid1.AddBitmapFile(5,3,'e:	msmail.png');

  TMSFNCGrid1.Cells[1,4] := 'Red';
  TMSFNCGrid1.Colors[1,4] := gcRed;
  TMSFNCGrid1.Cells[3,4] := 'Yellow';
  TMSFNCGrid1.Colors[3,4] := gcYellow;
  TMSFNCGrid1.Cells[5,4] := 'Lime';
  TMSFNCGrid1.Colors[5,4] := gcLime;

  TMSFNCGrid1.FontNames[1,4] := 'Courier New';
  TMSFNCGrid1.FontStyles[3,4] := [fsBold];
  TMSFNCGrid1.FontSizes[5,4] := 12;

  TMSFNCGrid1.AddNode(2,2);
end;


As another quick introduction to the grid, this 2 line snippet demonstrates how data from a CSV file can be loaded and automatic filtering via a drop down in the column header is enabled:

  TMSFNCGrid1.LoadFromCSV('e:	mscars.csv');
  TMSFNCGrid1.Options.Filtering.DropDown := true;

Of course, this is just a very brief introduction to the TMS FNC Grid. Just the FNC grid alone could deserve multiple articles to cover it in detail. You can familiarize yourself with the TMS FNC Grid by reading the full PDF developers guide you can find at http://www.tmssoftware.biz/download/manuals/TMSFNCGridDevGuide.pdf or use the trial or full version of the component that comes with several samples.

Introducing the TMS FNC Planner


Our TMS FNC Planner is a scheduling component with various built-in time-axis options, i.e. a day, week, month, period, half-day period, timeline as well as custom time-axis mode where you can fully control the duration of each timeslot in the Planner. The Planner supports single and multi resource views and can have the time-axis horizontal or vertical. When targetting the Planner to a mobile device, it will automatically use a touch-friendly approach to select, insert, delete, pan in the Planner. In a previous blog, we have presented a fully framework neutral sample TVGuide application that uses our TMS FNC Planner in this blog article: http://www.tmssoftware.com/site/blog.asp?post=335

Introducing the TMS FNC RichEditor

With the TMS FNC Rich Editor you can assemble a WordPad-style editor or Outlook style mail application in a matter of minutes. TMS FNC Rich Editor comes with capabilities to do WYSIWYG editing of rich text with images, URLs, bullet lists, custom graphics, mail merging etc... To make development even faster, there is a pre-built toolbar for rich editor editing and formatting and non-visual components to facilitate the import and export from HTML & RTF files and that of course in all frameworks, operating systems and target devices supported.

In this introduction sample, drop the TTMSFNCRichEditor on the form as well as the TTMSFNCRichEditorFormatToolbar and assign the TTMSFNCRichEditor to TTMSFNCRichEditorFormatToolbar.RichEditor. Also add a TTMSFNCRichEditorHTMLIO and TTMSFNCRichEditorRTFIO non-visual component on the form and also assign the TTMSFNCRichEditor to TTMSFNCRichEditorHTMLIO.RichEditor and TTMSFNCRichEditorRTFIO.RichEditor.

The rich editor content can be initialized with following code to perform a mail-merge that uses here two merge fields : NAME and EMAIL.

begin
  TMSFNCRichEditor1.AddText('Dear Mr. NAME');
  TMSFNCRichEditor1.AddLineBreak;
  TMSFNCRichEditor1.AddText('CC: EMAIL');
  TMSFNCRichEditor1.SelStart := 9;
  TMSFNCRichEditor1.SelLength := 4;
  TMSFNCRichEditor1.SetSelectionMergeField('NAME');

  TMSFNCRichEditor1.SelStart := 21;
  TMSFNCRichEditor1.SelLength := 5;
  TMSFNCRichEditor1.SetSelectionMergeField('EMAIL');
end;
When the app is started, the text can be further decorated by editing & formatting via the toolbar. When it is ready, following code performs the merge with the NAME and EMAIL field and is exported to RTF via TTMSFNCRichEditorRTFIO and after this, the merge is undone:


var
  sl: TStringList;
begin
  sl := TStringList.Create;

  try
    sl.Add('NAME=Elon Musk');
    sl.Add('EMAIL=elon@tesla.com');
    TMSFNCRichEditor1.Merge(sl);
  finally
    sl.Free;
  end;

  TMSFNCRichEditorRTFIO1.Save('e:	msmerge.rtf');

  TMSFNCRichEditor1.UnMerge;
end;

Introducing the TMS FNC TreeView

Finally, another large feature-packed control from the TMS FNC UI Controls set we want to introduce is the TMS FNC TreeView, TTMSFNCTreeView. This is a multi-column treeview control with regular mode and virtual mode and designed for and capable of using millions of nodes. In addition, the nodes support rich information, text atttributes can be customized per node cell, HTML formatted text in node cells is possible, images, checkboxes can be added and optional inplace editing is available.

In this introduction we'd like to demonstrate the difference in regular (node collection) based mode and virtual mode when using the TTMSFNCTreeView. The first code snippet demonstrates an initialization of a 2 column treeview:

var
  tn,cn: TTMSFNCTreeViewNode;
begin
  TMSFNCTreeView1.BeginUpdate;

  TMSFNCTreeView1.Columns.Clear;
  TMSFNCTreeView1.Nodes.Clear;

  TMSFNCTreeView1.Columns.Add.Text := 'Country';
  TMSFNCTreeView1.Columns.Add.Text := 'Capital';

  tn := TMSFNCTreeView1.AddNode(nil);
  tn.Text[0] := 'Europe';

  cn := TMSFNCTreeView1.AddNode(tn);
  cn.Text[0] := 'Germany';
  cn.Text[1] := 'Berlin';

  cn := TMSFNCTreeView1.AddNode(tn);
  cn.Text[0] := 'France';
  cn.Text[1] := 'Paris';

  cn := TMSFNCTreeView1.AddNode(tn);
  cn.Text[0] := 'United Kingdom';
  cn.Text[1] := 'London';

  tn := TMSFNCTreeView1.AddNode(nil);
  tn.Text[0] := 'Asia';

  cn := TMSFNCTreeView1.AddNode(tn);
  cn.Text[0] := 'Japan';
  cn.Text[1] := 'Tokyo';

  cn := TMSFNCTreeView1.AddNode(tn);
  cn.Text[0] := 'China';
  cn.Text[1] := 'Peking';

  TMSFNCTreeView1.EndUpdate;
end;



Important to note here is that the text in the multiple columns of the treeview can be simply accessed with an array indexed Node.Text[]: string property.

In a next step, we'll use the TTMSFNCTreeView in virtual mode and insert 1 million nodes! Columns are not virtual, so this must be initialized and to keep it simple, this will be initialized to one column:

begin
  TMSFNCTreeView1.Columns.Clear;
  TMSFNCTreeView1.Columns.Add;
  TMSFNCTreeView1.Columns[0].Text := 'Large treeview';
end;

To use the TTMSFNCTreeView in virtual mode, two events are crucial: the OnGetNumberOfNodes() event and the OnGetNodeText() event. The first is triggered to know how many nodes at root level or child level should be added. The latter is used to retrieve the column text of the node. Let's start with the OnGetNumberOfNodes event. This event has parameters ANode and a var parameter ANumberOfNodes. ANode is either a node with ANode.Level set to -1 indicating the number of root level nodes is requested or it contains the node for which the number of child nodes is requested. With the ANode.Level property, you can know how many hierarchical levels deep the node is. In this example, we'll insert 1 million (100x100x100) nodes by inserting 100 root level nodes that have each 100 childs and each child has again 100 subchilds.

This event takes care of this:

procedure TForm1.TMSFNCTreeView1GetNumberOfNodes(Sender: TObject;
  ANode: TTMSFNCTreeViewVirtualNode; var ANumberOfNodes: Integer);
begin
    if ANode.Level = -1 then
    ANumberOfNodes := 100
  else  
  if ANode.Level = 0 then
    ANumberOfNodes := 100
  else  
  if ANode.Level = 1 then
    ANumberOfNodes := 100;
end;

Then, the other event for virtual node handling, OnGetNodeText is used to return the text for node columns. Note that this event will be triggered for each node and for each column for this node. The column for which the event is triggered is indicated with the AColumn parameter. As we have only 1 column in this example, this is ignored and the node text is directly returned:

procedure TForm1.TMSFNCTreeView1GetNodeText(Sender: TObject;
  ANode: TTMSFNCTreeViewVirtualNode; AColumn: Integer;
  AMode: TTMSFNCTreeViewNodeTextMode; var AText: string);
begin
  if ANode.Level = 0 then
    AText := 'Root node '+inttostr(ANode.Index)
  else
  if ANode.Level = 1 then
    AText := 'Child node '+inttostr(ANode.Index)
  else
  if ANode.Level = 2 then
    AText := 'Subchild node '+inttostr(ANode.Index);
end;




We hope this brief introduction of the major controls in the TMS FNC UI Pack whetted your appetite to start exploring the components, discovering the benefits and efficiency of having one UI component set to cover all the target operating systems you want to target and perhaps cook up your first Linux GUI apps with LCL. We're eager to learn how your experience is going and to hear your feedback, comments and further wishes and needs in this direction.

Bookmarks: 

Bruno Fierens




This blog post has received 4 comments. Add a comment.



Friday, April 22, 2016

TMS rollout of components with RAD Studio 10.1 Berlin support started


This is a short notice for our customers who are eager to get started with RAD Studio 10.1 Berlin: we've started rolling out our bi-yearly wave of product updates with official built-in support for this new release. Starting from today, following product updates now have built-in support for RAD Studio 10.1 Berlin:

TMS Component Pack: Our suite of 400 feature-packed, sophisticated user interface controls, including advanced grids, ribbon, syntax highlighting memo, rich text editor, visual scheduling/planning control and much more... TMS Component Pack exists and helps software developers all over the world for almost 20 years now and has grown into a stable, vast, powerful, intuitive to use set of user interface controls you cannot miss.

TMS Pack for FireMonkey: Our suite of FMX controls for full cross-platform development targeting Windows, Mac OS-X, iOS, Android. TMS Pack for FireMonkey is a set of powerful controls that help you building advanced desktop or mobile applications including grid, planner, navigational controls, instrumentation controls, rich text editor and much more... Simply the most comprehensive bundle of cross-platform UI controls.

TMS FNC UI Pack: This is our brand new set of framework neutral UI controls allowing you to build single source code business logic based applications for VCL, FMX and LCL frameworks. Whether you'll write traditional Windows desktop VCL applications, cross-platform desktop or mobile applications with the FMX framework or use the free Lazarus IDE and FPC compiler to build Windows, Linux, Raspbian, ... applications, ... there is only one learning curve and only one source code base needed to use these framework neutral controls. With TMS FNC UI Pack, you're in full control, irrespective of the IDE or framework you use.

Our team is working hard to continue to add RAD Studio 10.1 Berlin support to our other wide range of component products. You can track here the progress of our work to cover other products.

Bookmarks: 

Nancy Lescouhier




This blog post has not received any comments yet. Add a comment.



Friday, April 08, 2016

Free IDE Tools for Delphi 10 Seattle

For easily installing our free IDE plugins we have created a bundled installer with support for the latest Delphi 10 Seattle IDE.

TMS Project Manager

Free IDE plugin to easily ZIP and upload projects.



TMS Rich Clipboard

Free IDE plugin to copy source code with syntax highlighting to the clipboard.



TMS Presentation Tool

Free IDE plugin to monitor clipboard, track clipboard history and to save & load code snippets for giving presentations with Delphi.



TMS What's New

Free IDE plugin to keep current with the latest releases, blogs & tweets from TMS software.



The bundled free IDE plugins can be downloaded here.

Bookmarks: 

Pieter Scheldeman




This blog post has received 5 comments. Add a comment.



Wednesday, April 06, 2016

A single-source TV-guide for 6 operating systems

Introduction

With the first release of our brand new TMS FNC UI Pack we are venturing into a new way of designing and creating components. A way that allows developers to easily switch between 3 frameworks (FMX, VCL and LCL). As the TMS FNC UI Pack targets these three frameworks it automatically comes with support for a multitude of operating systems. As we wanted to take "easily switching between 3 frameworks" to the test we have created a TV-guide application that uses the planner component, parses JSON retrieved with our TMS Cloud Components and made it running on 6! major operating systems: Windows 10, Mac OS X Yosemite, iOS 9.0, Android Lollipop, Ubuntu and Raspbian.

Click image for more screenshots.

Cross-platform, cross-framework code

Creating our business logic

After installing the TMS FNC UI Pack the TTMSFNCPlanner component is available on FMX, VCL and LCL and we are ready to start developing applications. Now, to start using it, it would be wise to think a few moments on how to write an application that is running on multiple frameworks, multiple operating systems. If we want to start with VCL and want to move to FMX in a couple of months, it would not be very resource and time friendly to write an application that does not use the power of FNC. Therefore we want to create a single source universal business logic unit that will be used in three different projects, one for every framework. To create a single source unit and use it in different projects, which is compatible with FMX, VCL and LCL we need to add a conditional define to our project to identify each framework, if only because unit names for FNC components must be different (requirement in the Delphi IDE hosting both FMX & VCL).





To initialize the planner and retrieving data from our service, we start by adding a TTVGuideLogic class that is instantiated in each separate project main form unit and contains the business logic for the app.
  TTVGuideLogic = class
  private
    FPlanner: TTMSFNCPlanner;
    FChannels: TTVChannels;
    FAccess: TCloudAccess;
  public
    destructor Destroy; override;
    function GetJSONArray(URL: string; AID: String = ''): TJSONArray;
    function FindChannelByName(AName: String): TTVChannel;
    procedure InitPlanner(APlanner: TTMSFNCPlanner);
    procedure InitChannels;
    procedure UpdateResources(AChannel: TTVChannel; AResource: Integer);
  end;
Each framework has its own set of units in order to compile succesfully. We use the conditional defines added to our project to make the difference between each framework.
uses
  {$IFDEF VCL}
  Classes, SysUtils, VCL.TMSFNCPlanner, VCL.TMSFNCCustomControl, VCL.TMSFNCPlannerBase, VCL.TMSFNCPlannerData, CloudBase,
  Generics.Collections, JSON, VCL.TMSFNCGraphics, VCL.TMSFNCUtils, DateUtils;
  {$ENDIF}

  {$IFDEF FMX}
  Classes, SysUtils, FMX.TMSFNCPlanner, FMX.TMSFNCCustomControl, FMX.TMSFNCPlannerBase, FMX.TMSFNCPlannerData, FMX.TMSCloudBase,
  Generics.Collections, JSON, FMX.TMSFNCGraphics, FMX.TMSFNCUtils, DateUtils;
  {$ENDIF}

  {$IFDEF LCL}
  Classes, SysUtils, LCLTMSCloudBase, LCLTMSFNCPlanner, LCLTMSFNCPlannerBase, LCLTMSFNCPlannerData, LCLTMSFNCGraphics, LCLTMSFNCUTils, DateUtils,
  fgl, fpjson, jsonparser;
  {$ENDIF}

Initializing the planner

The initialization code for the planner look & feel is added to the InitPlanner method, which is called after creating an instance of the TTVGuideLogic class in your project. When comparing this to our unit section, you will notice it doesn't require any conditional defines in order to succesfully compile. With the TMS FNC UI Pack we have added a few helper units to set the font size, set the color and have also created our own fill and stroke classes that are used in every FNC component.
procedure TTVGuideLogic.InitPlanner(APlanner: TTMSFNCPlanner);
var
  I: Integer;
begin
  FPlanner := APlanner;
  FPlanner.BeginUpdate;
  FPlanner.Items.Clear;
  FPlanner.Positions.Count := 6;
  FPlanner.OrientationMode := pomHorizontal;

  FPlanner.DefaultItem.TitleColor := gcSlategray;
  FPlanner.DefaultItem.TitleFontColor := gcWhite;
  FPlanner.DefaultItem.Color := gcWhitesmoke;
  FPlanner.DefaultItem.ActiveColor := gcSlateGray;

  FPlanner.Interaction.ReadOnly := True;

  FPlanner.Resources.Clear;
  for I := 0 to FPlanner.Positions.Count - 1 do
    FPlanner.Resources.Add;

  FPlanner.ItemsAppearance.Stroke.Color := gcWhite;
  FPlanner.ItemsAppearance.Stroke.Kind := gskSolid;
  FPlanner.ItemsAppearance.Stroke.Width := 2;
  FPlanner.ItemsAppearance.TitleStroke.Assign(FPlanner.ItemsAppearance.Stroke);

  FPlanner.GridCellAppearance.InActiveFill.Assign(FPlanner.GridCellAppearance.Fill);
  FPlanner.PositionsAppearance.Layouts := [pplTop, pplBottom];

  FPlanner.ModeSettings.StartTime := Now;
  FPlanner.ModeSettings.EndTime := Now;
  FPlanner.Mode := pmDay;

  FPlanner.TimeLineAppearance.Layouts := [ptlLeft, ptlRight];
  FPlanner.TimeLineAppearance.RightVerticalTextAlign := gtaTrailing;
  FPlanner.TimeLineAppearance.RightSubVerticalTextAlign := gtaLeading;
  FPlanner.TimeLine.CurrentTimePosition := pctpOverItems;
  FPlanner.TimeLine.DisplayUnitType := pduMinute;
  FPlanner.TimeLine.DisplayUnit := 5;
  FPlanner.TimeLine.DisplayStart := 0;
  FPlanner.TimeLine.DisplayEnd := (MinsPerDay div FPlanner.TimeLine.DisplayUnit) - 1;

  TTMSFNCUtils.SetFontSize(FPlanner.ItemsAppearance.TitleFont, 14);
  TTMSFNCUtils.SetFontSize(FPlanner.PositionsAppearance.BottomFont, 14);
  TTMSFNCUtils.SetFontSize(FPlanner.PositionsAppearance.TopFont, 14);

  FPlanner.EndUpdate;
  FPlanner.TimeLine.ViewStart := IncHour(Now, -2);

  InitChannels;

  UpdateResources(FindChannelByName('MTV'), 0);
  UpdateResources(FindChannelByName('Eurosport 1'), 1);
  UpdateResources(FindChannelByName('BBC 1'), 2);
  UpdateResources(FindChannelByName('TLC'), 3);
  UpdateResources(FindChannelByName('Disney XD'), 4);
  UpdateResources(FindChannelByName('CNN'), 5);
end;

Using the cloud to access information

A TV-guide application would only be a TV-guide application if it would show some TV-channels and the TV-shows that are playing at a specific time range. In the previous code snippet we have initialized the planner to show a time range of 24 hours, and the service that is used to retrieve the TV-shows of a specific TV-channel is parameterized to always return the TV-shows of today. To keep a reference to TV-channels and TV-shows we additionally add the classes needed to retrieve and persist information. In this code snippet, we have just conditional defines because of a small difference in handling generic lists between the Delphi compiler and the FPC compiler and the TMS Cloud access classes that have a different class name for VCL, FMX and LCL.
  TTVChannel = class;
  TTVShow = class;

  {$IFDEF VCL}
  TCloudAccess = class(TCloudBase);
  TTVShows = TObjectList<TTVShow>;
  TTVChannels = TObjectList<TTVChannel>;
  {$ENDIF}

  {$IFDEF FMX}
  TCloudAccess = class(TTMSFMXCloudBase);
  TTVShows = TObjectList<TTVShow>;
  TTVChannels = TObjectList<TTVChannel>;
  {$ENDIF}

  {$IFDEF LCL}
  TCloudAccess = class(TTMSLCLCloudBase);
  TTVShows = specialize TFPGObjectList<TTVShow>;
  TTVChannels = specialize TFPGObjectList<TTVChannel>;
  {$ENDIF}

  TTVShow = class
  private
    FGenre: string;
    FStartTime: TDateTime;
    FTitle: string;
    FID: string;
    FEndTime: TDateTime;
    FKind: string;
  public
    property ID: string read FID write FID;
    property Title: string read FTitle write FTitle;
    property Genre: string read FGenre write FGenre;
    property Kind: string read FKind write FKind;
    property StartTime: TDateTime read FStartTime write FStartTime;
    property EndTime: TDateTime read FEndTime write FEndTime;
  end;

  TTVChannel = class
  private
    FName: string;
    FID: string;
    FShows: TTVShows;
  public
    constructor Create;
    destructor Destroy; override;
    property ID: string read FID write FID;
    property Name: string read FName write FName;
    property Shows: TTVShows read FShows;
  end;
After defining the necessary classes, we create an instance of our cloud base component for FMX (TTMSFMXCloudBase) and we use that instance to retrieve our TV-channels in JSON.
procedure TTVGuideLogic.InitChannels;
var
  i: integer;
  arr: TJSONArray;
  o: TJSONObject;
  c: TTVChannel;
begin
  FChannels := TTVChannels.Create;

  FAccess := TCloudAccess.Create(nil);
  arr := GetJSONArray('http://www.tvgids.nl/json/lists/channels.php');
  if Assigned(arr) then
  begin
    for i := 0 to GetArraySize(arr) - 1 do
    begin
      o := GetArrayItem(arr, i) as TJSONObject;
      c := TTVChannel.Create;
      c.ID := FAccess.GetJSONProp(o,'id');
      c.Name := StringReplace(FAccess.GetJSONProp(o,'name'), '&eacute;', 'é', [rfReplaceAll]);
      FChannels.Add(c);
    end;
  end;
end;
And we call InitChannels in our planner initialization code
  InitChannels;
After retrieving the TV-channels, the TV-shows are also retrieved, parsed and added as items to our TTMSFNCPlanner.
procedure TTVGuideForm.UpdateResources(AChannel: TTVChannel; AResource: Integer);
var
  c: TTVChannel;
  s: TTVShow;
  arr: TJSONArray;
  i: Integer;
  o: TJSONObject;
  it: TTMSFNCPlannerItem;
  dt: TDateTime;
begin
  dt := Now;
  TMSFNCPlanner1.BeginUpdate;
  for I := TMSFNCPlanner1.Items.Count - 1 downto 0 do
  begin
    if TMSFNCPlanner1.Items[I].Resource = AResource then
      TMSFNCPlanner1.Items[I].Free;
  end;

  TMSFNCPlanner1.Resources[AResource].Text := 'No Channel Selected';
  if Assigned(AChannel) then
  begin
    c := AChannel;
    c.Shows.Clear;
    TMSFNCPlanner1.Resources[AResource].Text := c.Name;
    arr := GetJSONArray('http://www.tvgids.nl/json/lists/programs.php?channels='+c.ID+'&day=0', c.ID);
    if Assigned(arr) then
    begin
      for i := 0 to GetArraySize(arr) - 1 do
      begin
        o := GetArrayItem(arr, i) as TJSONObject;
        s := TTVShow.Create;
        s.ID := FAccess.GetJSONProp(o,'db_id');
        s.Title := FAccess.GetJSONProp(o,'titel');
        s.Genre := FAccess.GetJSONProp(o,'genre');
        s.Kind  := FAccess.GetJSONProp(o,'soort');
        s.StartTime := FAccess.IsoToDateTime(FAccess.GetJSONProp(o,'datum_start'));
        s.EndTime := FAccess.IsoToDateTime(FAccess.GetJSONProp(o,'datum_end'));
        c.Shows.Add(s);
        it := TMSFNCPlanner1.AddOrUpdateItem(s.StartTime, s.EndTime, s.Title, s.Kind);
        it.Resource := AResource;
        it.Hint := it.Title + ' [' + TimeToStr(Frac(s.StartTime)) + ' - ' + TimeToStr(Frac(s.EndTime)) + ']';
        if (dt >= it.StartTime) and (dt <= it.EndTime) then
        begin
          it.Color := gcYellowgreen;
          it.FontColor := gcWhite;
        end;
      end;
    end;
  end;

  TMSFNCPlanner1.EndUpdate;
end;
The UpdateResources call is also added to our planner initialization code and retrieves the channel of choice and adds an item for each show to the planner.
  UpdateResources(FindChannelByName('MTV'), 0);
  UpdateResources(FindChannelByName('Eurosport 1'), 1);
  UpdateResources(FindChannelByName('BBC 1'), 2);
  UpdateResources(FindChannelByName('TLC'), 3);
  UpdateResources(FindChannelByName('Disney XD'), 4);
  UpdateResources(FindChannelByName('CNN'), 5);
To starting using the code from this separate unit, we add the unit to our project and use the following code, after dropping an instance of the TTMSFNCPlanner on the form.
procedure TTVGuideForm.FormCreate(Sender: TObject);
begin
  FTVGuideLogic := TTVGuideLogic.Create;
  FTVGuideLogic.InitPlanner(TMSFNCPlanner1);
end;

procedure TTVGuideForm.FormDestroy(Sender: TObject);
begin
  FTVGuideLogic.Free;
end;
After succesfully retrieving our TV-channels and TV-shows, the application runs on Windows 10, but it could very well be iOS 9, or MAC OS X Yosemite, or Raspbian, or ...

The full source code is available for download

Click image for more screenshots.

Bookmarks: 

Pieter Scheldeman




This blog post has not received any comments yet. Add a comment.




Previous  |  Next  |  Index


Copyright © 1995 - 2016 TMS Software v4.0