Friday, May 13, 2016

Some weeks ago, we released the TMS FNC UI Pack, a set of Framework Neutral Components (FNC), i.e. UI controls that can be used from VCL Windows applications, FireMonkey (FMX) Windows, Mac OS-X, iOS, Android applications and LCL framework basedLazarus applications for Windows, Linux, Mac OS-X,..
The TMS FNC UI Pack contains highly complex & feature-rich components such as grid, planner, rich editor, treeview, toolbars. To create such complex components that work under 3 frameworks and a myriad of operating systems is not a trivial excercise and requires intricate knowledge about the VCL, FMX and LCL frameworks as well as the operating systems the controls need to work under. 
To help ourselves and the users of the TMS FNC UI Pack, we have introduced several abstractions that facilitate creating framework neutral components and this is what we want to cover in this brief introduction to developing FNC custom controls.

FNC custom control basics

The structure of the FNC custom control we want to present is this of a classic UI control. The control is responsible for painting itself and interacts with keyboard and/or mouse. The control has several properties to control its appearance and behavior. If we look at this concept from the perspective of implementing this for 3 different frameworks, the biggest challenges faced are:

1) abstractions in the code for dealing with graphics: especially VCL and FMX are quite different in this respect, so abstraction is welcome.
2) abstractions in keyboard and mouse handling: also here there are differences, although subtle, between VCL, FMX and LCL.
3) abstractions in types: types such as font, color, rectangles, points are different in FMX and VCL.

So, we'll cover what is included in FNC to facilitate dealing with this.

Unit organisation

Perhaps the biggest stumbling block is unit organisation. As it is desirable to create components that can be installed in the IDE, (which means for Delphi for both the VCL and FireMonkey framework simultanously) we'll need units for VCL and units for FireMonkey. Although we can use the same class name, the class hierarchy for the VCL control and the FMX control will be different. The VCL FNC control will descend from the VCL TCustomControl and the FMX FNC control will descend from the FMX TControl. In Lazarus, the FNC control will descend from the LCL TCustomControl. In a nutshell, to solve this, we create 3 units for a component that will be nearly identical and we provide a conversion step to allow you to write the code in one unit and automatically generate the other units for the other frameworks. For this example, in total, we'll have 6 units: 3 units with the code for the control for the 3 supported frameworks and 3 units for the component registration in the IDE:

  1. // Units for the VCL variant of the FNC control
  2. VCL.TMSFNCCust.pas
  3. VCL.TMSFNCCustReg.pas
  4. // Units for the FMX variant of the FNC control
  5. FMX.TMSFNCCust.pas
  6. FMX.TMSFNCCustReg.pas
  7. // Units for the LCL variant of the FNC control
  8. LCLTMSFNCCust.pas
  9. LCLTMSFNCCustReg.pas

We'll also use 3 packages: a package for VCL, a package for FMX and a package for LCL. We can install the VCL & FMX package simultanously in the Delphi IDE and the LCL package in the Lazarus IDE. The package for the custom control will have a dependency to the framework plus to the TMS FNC UI Pack for the framework. The structure of the packages in the Delphi 10.1 Berlin IDE is:


and in Lazarus, this is:

Getting to grips

Ok, now that the unit structure is setup, we can focus on writing the code. To write this code, we'll use 3 TMS FNC UI Pack units with abstractions: xxx.TMSFNCCustomControl, xxx.TMSFNCGraphics and xxx.TMSFNCTypes (with xxx = the framework). In this example, we'll write the code in VCL and automatically generate the FMX & LCL equivalents from it, so the uses list becomes:

for FMX

  1. uses
  2. Classes, Types, FMX.TMSFNCCustomControl, FMX.TMSFNCGraphics, FMX.TMSFNCTypes;

for LCL

  1. uses
  2. Classes, Types, LCLTMSFNCCustomControl, LCLTMSFNCGraphics, LCLTMSFNCTypes;

The FNC control we'll write here is very rudimentary for the sake of focusing on the abstractions. It is basically a gauge control that can be clicked with the mouse to set the gauge value, has a gauge value property to set it and can use the key up & down to change the gauge value. So, we need painting of the gauge, we need some gauge line color & font settings and we need handling of the mouse click and keyboard.

Control initialization

The control descends from the abstract FNC class TTMSFNCCustomControl and exposes one extra color property for the gauge line color (note that this are of the abstract type TTMSFNCGraphicsColor), a font (of the abstract type TTMSFNCGraphicsFont, that has also a font color in all frameworks) and a gauge value property of the type TControlValue. Note that the Stroke & Fill properties are published. This contains the control border & control background color and even enables things such as setting a background gradient, a border with a specific pen style etc...

  1. TControlValue = 0..100;
  2. TTMSFNCCustomControlSample = class(TTMSFNCCustomControl)
  3. private
  4. FLineColor: TTMSFNCGraphicsColor;
  5. FFont: TTMSFNCGraphicsFont;
  6. FValue: TControlValue;
  7. protected
  8. procedure Draw({%H-}AGraphics: TTMSFNCGraphics; {%H-}ARect: TRectF); override;
  9. public
  10. constructor Create(AOwner: TComponent); override;
  11. destructor Destroy; override;
  12. published
  13. property Stroke;
  14. property Fill;
  15. property Font: TTMSFNCGraphicsFont read FFont write SetFont;
  16. property LineColor: TTMSFNCGraphicsColor read FLineColor write SetLineColor default gcRed;
  17. property Value: TControlValue read FValue write SetValue default 0;
  18. end;

In the constructor, initialization of the line color property value is done as well as the border color and the font is created. This is of the type TTMSFNCGraphicsFont and the main purpose is to have a font with font color in all frameworks. There is one more thing particular in the constructor and that is the assignment of the Font.OnChange event handler. In Delphi Pascal, we can simply assign the object method but for the FPC compiler, this needs to be prefixed with the @ symbol. Fortunately, the Lazarus environment has the LCL define we can use to handle this.

  1. { TTMSFNCCustomControlSample }
  2. constructor TTMSFNCCustomControlSample.Create(AOwner: TComponent);
  3. begin
  4. inherited;
  5. Stroke.Color := gcBlack;
  6. FLineColor := gcRed;
  7. FFont := TTMSFNCGraphicsFont.Create;
  8. FFont.OnChanged := {$IFDEF LCL}@{$ENDIF}FontChanged;
  9. Width := 100;
  10. Height := 100;
  11. end;
  12. destructor TTMSFNCCustomControlSample.Destroy;
  13. begin
  14. FFont.Free;
  15. inherited;
  16. end;

Painting

The control descends from the abstract FNC class TTMSFNCCustomControl and exposes 3 color properties, the border, background and gauge line color (note that these are of the type TTMSFNCGraphicsColor), a font (of the type TTMSFNCGraphicsFont, that has also a font color in all frameworks) and a gauge value property of the type TControlValue.

Painting is done in the override of the Draw() method that has 2 parameters: AGraphics: TTMSFNCGraphics, a framework neutral graphics library and the rectangle of the control via ARect of the type TRectF. In VCL and LCL only the not fractional part of the floating point numbers is used but of course in the FireMonkey framework, this can use the fractional parts as well.

The painting code itself is:

  1. procedure TTMSFNCCustomControlSample.Draw(AGraphics: TTMSFNCGraphics;
  2. ARect: TRectF);
  3. var
  4. angle: double;
  5. lf,tf: TPointF;
  6. w: single;
  7. begin
  8. inherited;
  9. angle := Value/High(Value)*2*PI;
  10. w := Min(ARect.Right - ARect.Left, ARect.Bottom - ARect.Top) / 2;
  11. lf.X := (ARect.Right - ARect.Left)/2;
  12. lf.Y := (ARect.Bottom - ARect.Top)/2;
  13. tf.X := lf.X + Cos(angle) * w;
  14. tf.Y := lf.Y - Sin(angle) * w;
  15. AGraphics.Stroke.Color := LineColor;
  16. AGraphics.DrawLine(lf,tf);
  17. AGraphics.Font.Assign(Font);
  18. AGraphics.DrawText(ARect, InttoStr(Value),false, gtaCenter, gtaCenter);
  19. end;


Mouse and keyboard handling

The mouse and keyboard handling is via the HandleKeyDown and HandleMouseDown virtual method overrides:

  1. TTMSFNCCustomControlSample = class(TTMSFNCCustomControl)
  2. protected
  3. procedure HandleKeyDown(var {%H-}Key: Word; {%H-}Shift: TShiftState); override;
  4. procedure HandleMouseDown({%H-}Button: TMouseButton; {%H-}Shift: TShiftState; {%H-}X, {%H-}Y: Single); override;
  5. end;

The implementation is straightforward. One thing that is noteworthy, is that the unit xxx.TTMSFNCTypes has a list of keys in a framework and operating system neutral way, i.e. we can check for the arrow up and arrow down keys with the constants KEY_DOWN and KEY_UP.

  1. procedure TTMSFNCCustomControlSample.HandleKeyDown(var Key: Word;
  2. Shift: TShiftState);
  3. begin
  4. inherited;
  5. if Key = KEY_DOWN then
  6. begin
  7. if Value > Low(Value) then
  8. Value := Value - 1;
  9. end;
  10. if Key = KEY_UP then
  11. begin
  12. if Value < High(Value) then
  13. Value := Value + 1;
  14. end;
  15. end;

In the mouse down handler, we set the focus to the control when possible and then calculate the value of the gauge that matches with the mouse click position. Note that the X,Y values are of the type single to accomodate FMX and will contain non fractional values in VCL and LCL.

  1. procedure TTMSFNCCustomControlSample.HandleMouseDown(Button: TTMSFNCMouseButton;
  2. Shift: TShiftState; X, Y: Single);
  3. var
  4. angle: single;
  5. dx,dy: single;
  6. begin
  7. inherited;
  8. if AllowFocus then
  9. SetFocus;
  10. dx := x - (Width/2);
  11. dy := - y + (Height/2);
  12. if dx = 0 then
  13. angle := sign(dy) * PI / 2
  14. else
  15. angle := ArcTan(dy/dx);
  16. if dx < 0 then
  17. angle := PI + angle;
  18. if angle < 0 then
  19. angle := angle + 2 * PI;
  20. Value := Round((angle / 2 / PI) * High(Value));
  21. end;

Creating the units for FMX and LCL

Now that we have the VCL framework FNC component ready that contains 100% framework neutral code, let's create automatically the FMX and LCL units from this. 3 steps are needed:

1) Rename the unit VCL.TMSFNCCust.pas to FMX.TMSFNCCust.pas and LCLTMSFNCCust.pas
2) Change in the unit .PAS file the unit name, i.e. replace VCL.TMSFNCCust by FMX.TMSFNCCust and LCLTMSFNCCust respectively
3) Change the unit references in the uses list from
VCL.TMSFNCCustomControl, VCL.TMSFNCGraphics, VCL.TMSFNCTypes;
to
FMX.TMSFNCCustomControl, FMX.TMSFNCGraphics, FMX.TMSFNCTypes; 
or
LCLTMSFNCCustomControl, LCLTMSFNCGraphics, LCLTMSFNCTypes;

To accomplish this, we call a simple powershell script that performs text replacement from VCL.TMS to FMX.TMS or LCLTMS respectively:

  1. powershell -command "(gc VCL.TMSFNCCust.pas) -replace 'VCL.TMS','LCLTMS' |Out-file LCLTMSFNCCust.pas -Encoding utf8"
  2. powershell -command "(gc VCL.TMSFNCCust.pas) -replace 'VCL.TMS','FMX.TMS' |Out-file FMX.TMSFNCCust.pas -Encoding utf8"

With these units created, we can compile the packages and install our FNC custom control for use from VCL, FMX and LCL applications in Lazarus.

The full source of this sample FNC custom control can be downloaded here. This sample can be used with the latest version of theTMS FNC UI Pack.

We hope this already whets your appetite for exploring FNC and the power of writing code for UI controls once for use in 3 frameworks. See also this blog article for a more general coverage of what is available in the TMS FNC UI Pack. In a next article, we'll go deeper in compound control creation and also the TTMSFNCGraphics library that offers a vast range of functions, going from drawing text, polygons, polylines, images in various formats, controls like checkboxes, radiobuttons, buttons, ... and much more.

Bruno Fierens

http://tmssoftware.com/site/blog.asp?post=346

Developing your first FNC custom control的更多相关文章

  1. WinForm中Component Class、User Control及Custom Control的区别和使用建议

    reference: http://blog.csdn.net/redstonehe/article/details/1536549 .NET Framework 为您提供了开发和实现新控件的能力.除 ...

  2. WinForm中Component Class、User Control及Custom Control的区别和使用-转

    转http://www.cnblogs.com/jhtchina/archive/2010/11/28/1028591.html NET Framework 为您提供了开发和实现新控件的能力.除了常见 ...

  3. Custom Control

    How to create custom control http://www.silverlightshow.net/items/Creating-a-Silverlight-Custom-Cont ...

  4. WinForm中Component Class、User Control及Custom Control的区别和使用

    NET Framework 为您提供了开发和实现新控件的能力.除了常见的用户控件外,现在您会发现,您可以编写能执行自身绘图的自定义控件,甚至还可以通过继承扩展现有控件的功 能.确定创建何种类型的控件可 ...

  5. Writing a Reusable Custom Control in WPF

    In my previous post, I have already defined how you can inherit from an existing control and define ...

  6. Recommended Practices for WPF Custom Control Developers

    I have always found that there isn’t enough documentation about Custom Control development in WPF. M ...

  7. ClassLibary和WPF User Control LIbary和WPF Custom Control Libary的异同

    说来惭愧,接触WPF这么长时间了,今天在写自定义控件时遇到一个问题:运行界面中并没有显示自定义控件,经调试发现原来没有加载Themes中的Generic.xaml. 可是为什么在其他solution中 ...

  8. VS中Component Class、User Control及Custom Control的区别 .

    .NET Framework 为您提供了开发和实现新控件的能力.除了常见的用户控件外,现在您会发现,您可以编写能执行自身绘图的自定义控件,甚至还可以通过继承扩展现有控件的功能.确定创建何种类型的控件可 ...

  9. WPF中的Generic.xaml, theme以及custom control

    原文:WPF中的Generic.xaml, theme以及custom control 在Visual Studio中创建自定义控件时,所有控件都将添加到/Themes/Generic.xaml. 最 ...

随机推荐

  1. js——cookie

    cookie:存储数据,当用户访问了某个网站(网页)的时候,我们就可以通过cookie来向访问者电脑上存储数据 1.不同的浏览器存放的cookie位置不一样,也是不能通用的 2. cookie的存储是 ...

  2. Linux学习之tail命令

    tail 命令从指定点开始将文件写到标准输出.使用tail命令的-f选项可以方便的查阅正在改变的日志文件,tail -f filename会把filename里最尾部的内容显示在屏幕上,并且不但刷新, ...

  3. jsp获取枚举的值

    Struts2的Action传回页面一个list,页面迭代这个list,获取下拉框的值,获取过来是枚举类型. 在jsp页面获取枚举的常量值和枚举的值的例子如下: jsp页面: <td >状 ...

  4. swift和objc之對比

    http://mobidev.biz/blog/swift_how_we_code_your_ios_apps_twice_faster

  5. php测试时不出现错误信息

    来源:http://blog.sina.com.cn/s/blog_6c9d65a101013vdj.html 在练习程序时,有时候写错了,在浏览器会打印出出错信息. 可我的程序始终没有出现. 我的环 ...

  6. 关系数据库标准语言SQL的基本问答

    1 .试述 sQL 语言的特点. 答: (l)综合统一. sQL 语言集数据定义语言 DDL .数据操纵语言 DML .数据控制语言 DCL 的功能于一体. (2)高度非过程化.用 sQL 语言进行数 ...

  7. vb6.0 时间日期

    使用year(now)可以得到4位数的年    你还可以用Format来得到, 还有FormatDateTime 下面两种都是一样的结果:  FormatDateTime(now,vbLongDate ...

  8. 什么是JSON对象

    1.什么是json? JSON全称是JavaScript Object Notation,是一种轻量级的数据交换格式.JSON 与XML具有相同的特性,是一种数据存储格式,但是JSON相比XML 更易 ...

  9. Eclipse下如何打开ftl文件

    ftl文件是freemarker模板文件,用freemarker时,常用该文件模板:但是该文件在eclipse编辑时,黑白底的,没有任何提示,下面介绍如何用JSP编辑器打开该文件. 工具/原料   e ...

  10. JS中的prototype属性

    JavaScript是基于对象的,任何元素都可以看成对象.然而,类型和对象是不同的.本文中,我们除了讨论类型和对象的一些特点之外,更重要的 是研究  如何写出好的并且利于重用的类型.毕竟,JavaSc ...