Working with JS events

By Jon-Lennart Aasenden

JavaScript is a bit different from the strict and rigid reality of C/C++ and Object Pascal that many developers are used to, and this is especially visible when it comes to event handling.

In this article I will focus on a relatively advanced topic, meant for those of you that implement components and widgets yourself. Especially if you are writing components that is meant to be used by JavaScript developers outside your object pascal ecosystem – or sold as a commercial package for Quartex Pascal.

Custom events?

As you no doubt have noticed QTX supports both event paradigms:

  • Modern delegate objects (also called multi-cast events)
  • Classical procedural events

I have touched on delegates before so I wont rehash too much. But in short: delegates as events as object. Objects that you can attach any number of procedural handlers to, and they all fire in the same sequence you added them. So yes, you can have 100 separate OnClick handlers attached to a button if you like.

So in this article we will cover the full path from creating the event your component issues – to making delegate object that you register with the IDE. Once registered the delegate becomes available in the ‘add new delegate’ dialog for your components.

Widget vs Component?

Before we continue we need to talk about the differences between the DOM (browser) and NodeJs (server coding). All visual controls in QTX inherits from TQTXWidget. This is a root class that creates a specific DOM element, manages it during it’s lifecycle -and finally destroys the element in the destructor.

Visual and non-visual are radically different

More or less all visual DOM elements expose the same delegates (or supports the same events). Delegates such as Mouse, Pointer and Keyboard are all intrinsic. Meaning, that we did not manually implement the mechanisms behind it – we simply wrapped what was already there in the browser.

With non-visual components that inherit from TQTXComponent the story is very different. The DOM is all about visual elements that has a handle (a reference to the HTML element itself) that we manage. So when a TQTXWidget is created, it also creates the HTML element that it represent -and thus absorb all the features DOM elements have. Delegates included.

With TQTXComponent that doesn’t happen, because by default there is no underlying element to manage. There is no handle property that we can typecast and get easy access to event mechanisms that are already there. The positive side of this is that it grants us complete freedom.

The EventTarget class

Hidden in the qtx.delegates.pas unit there is a class definition called JEventTarget. This is (simply put) the class you need to deal with event subscription and dispatching the JavaScript way.

Thankfully, the class is small and fairly self explanatory:

The definition is a clean external class, exposing essential functions

If you are wondering why on earth they named this event-target when it’s more of an event-source, you are not alone! The rationale behind the name is that it must be seen from the event object’s point of view; where the event goes into this target and is dispatched to the subscribers.

Note: If you are snooping around the unit you will also notice another class, JEventEmitter, that looks more impressive. But that is a NodeJS exclusive class. If you want to write code that works for both the DOM and NodeJS then stick to JEventTarget.

Since our TQTXComponent has the scaffolding in place to manage a ‘handle’ but doesn’t magically create anything by default, the handle property is actually the perfect spot to store our JEventEmitter object!

Alright, enough theory – let’s setup the basics:

type
  // Forward declaration
  TQTXEventComponent = class;

  // Our constructor callback
  TQTXEventComponentConstructor = procedure (AComponent: TQTXEventComponent);

  // Our class definition
  TQTXEventComponent = class( TQTXComponent )
  protected
    function GetEventTarget: JEventTarget; virtual;
    property EventTarget: JEventTarget read GetEventTarget;
  public
    constructor Create(AOwner: TQTXComponent; CB: TQTXEventComponentConstructor); reintroduce; virtual;
    destructor Destroy; override;
  end;

constructor TQTXEventComponent.Create(AOwner: TQTXComponent; CB: TQTXEventComponentConstructor);
begin
  inherited Create(AOwner, procedure (AComponent: TQTXComponent)
  begin
    SetHandle( new JEventTarget );
    if assigned( CB ) then
      CB( self );
  end);
end;

destructor TQTXEventComponent.Destroy;
begin
  SetHandle( unassigned );
  inherited;
end;

function TQTXEventComponent.GetEventTarget: JEventTarget;
begin
  result := JEventTarget( GetHandle() );
end;

Since the public Handle property just exposes a plain-old untyped THandle, I have made a protected, typed property called EventHandle that casts the Handle for us. If you want to make that property public is up to you. I added it to make access easier from within the component.

Triggering events

Now that we have a base-line component for dealing with event subscription, removal and dispatching – how exactly do we trigger an event?

Before we can dispatch an event we first need to understand what JavaScript expects from us. And what it wants is that we isolate the data for the event in a separate object. So we end up with two parts here: first the event itself, and secondly the data structure we want to inform our subscribers about.

Let’s look at the actual event itself. JavaScript provides a special class for this called ‘CustomEvent’. This inherits from JEvent so it has all the bells and whistles we need. As you can guess from this class definition, the detail field is where it wants to store our data.

JCustomEvent allows us to create, well, custom javascript event objects

Since we will be needing a type for our detail field -let’s define a JS friendly class for that first. Classes that will be used directly with JavaScript should inherit from JObject rather than TObject. Let’s setup two fields for our event:

JMyData = class( JObject )
  mdValue:  int32;
  mdText: string;
end;

With that in place, let’s copy the JCustomEvent definition in full and change that variant datatype to our new JMyData type. There is no point inheriting out from JCustomEvent since we want to alter that detail datatype:

JMyCustomEvent = class external "CustomEvent" (JEvent)
public
  property    detail: JMyData;
  constructor Create(&type: string; EventData: variant); overload;
end;

With both the data and event itself defined, let’s make life easier for ourselves. Instead of having to create the event object all over the place – let’s isolate it in a method we can use:

procedure TQTXEventComponent.SignalCool(const Value: int32; const Text: string);
begin
  var lEvent := JMyCustomEvent.Create('cool',
    class
      mdValue := value;
      mdText := text;
    end);
  EventTarget.dispatchEvent( lEvent );
end;

If you are looking at that code scratching your head about the ‘class’ keyword in the middle of a constructor – that is understandable. This is a unique and awesome feature called an inline class. This feature makes JavaScript coding more sane, especially when wrapping external libraries.

Inline class

When you define a normal class in Quartex Pascal that inherit from TObject, you actually get a full VMT (virtual method table) powered object! We are not simply emitting paper thin JavaScript object. The compiler operates more or less exactly like Delphi or Lazarus does and emits a VMT structure for each instance. Without this it would be impossible to support virtual and abstract methods, inheritance, partial classes and interfaces.

But the VMT doesn’t play well with JavaScript, because JavaScript expects a clean structure. We have a couple of options at hand for this scenario:

  • Inherit from JObject instead of TObject
  • Use a record type
  • Create an empty JS object and manually add name/value pairs
  • Use an inline class

The reason I used the inline class approach is simply that it’s easier. You don’t need to pre-define a type, you don’t need to create an instance of said definition, and you can sculpt the object ad-hoc there and then. It also protects the field and method names so these are not scrambled if you compile with obfuscation (records are not protected and should only be used internally by your code, not as parameters for a JavaScript call).

So this code:

type
TMyData = class( JObject )
  mdValue: int32;
  mdText: string;
end;
var data := new TMyData;
data.mdValue := 12;
data.mdText := "hello there";

Can be more elegantly written as:

var data = class
  mdValue := 12;
  mdText := "hello there";
  end;

Since there is no need for us to keep the data once it’s dispatched, I simply defined it in-place as a parameter. You can use a variable for it if you like, but there really is no need and just wastes memory.

Making a delegate object

If all you want to do is add handlers at runtime via code and trigger events manually – you are all set. Simply expose the EventTarget property as public, and you can add as many handlers as you like.

But since the whole point of a rapid application development (RAD) tool is to quickly build up functionality through drag & drop – we really need to create a proper delegate class that can be registered with the IDE.

First we are going to need a procedural event type which represents the handler that the IDE will automatically create for you when you add a delegate:

type
TQTXCoolHandler = procedure (Sender: TQTXCoolDelegate; EventObj: JMyCustomEvent);

Next we need the actual delegate class, this is the class that is registered with the IDE later. I know this looks like a lot of work, but it’s pretty much copy & paste from the RTL and just change the datatypes to match:

TQTXCoolDelegate = class( TQTXDelegate )
protected
  procedure   DoExecute(Event: JEvent); override;
  function    GetEventName: string; virtual;
public
  procedure   Bind; reintroduce; overload; virtual;
  procedure   Bind(Handler: TQTXCoolHandler); reintroduce; overload; virtual;
published
  property    OnExecute: TQTXCoolHandler;
end;

function TQTXCoolDelegate.GetEventName: string;
begin
  result := 'cool';
end;

procedure TQTXCoolDelegate.DoExecute(Event: JEvent);
begin
  if assigned(OnExecute) then
    OnExecute(self, JMyCustomEvent(Event) );
end;

procedure TQTXCoolDelegate.Bind;
begin
  inherited Bind( GetEventName() );
end;

procedure TQTXCoolDelegate.Bind(Handler: TQTXCoolHandler);
begin
  var lTemp: TQTXDelegateHandler;
  asm
    @lTemp = @Handler;
  end;
  inherited Bind( GetEventName(), @lTemp);
end;

And the final piece is that we register our fancy new delegate with the IDE. This is done through attributes on the component(s) that support said delegate. So if you have an event called “cool” in other components, you can register the delegate for that class as well.

So let’s attach the delegate to the component we created earlier:

[BindDelegates([TQTXCoolDelegate])];
TQTXEventComponent = class( TQTXComponent )
protected
  function GetEventTarget: JEventTarget; virtual;
  property EventTarget: JEventTarget read GetEventTarget;
public
  procedure   SignalCool(const value: int32; const text: string);
  constructor Create(AOwner: TQTXComponent; CB: TQTXEventComponentConstructor); reintroduce; virtual;
  destructor Destroy; override;
end;

And that is it! You now have a non-visual component that supports established JavaScript event management, and a delegate class that registers with the IDE and that shows up in the “new delegate” dialog!

Ta-da! Our fancy new non-visual component! And our “cool” delegate shows up in the new delegate dialog

Tips and suggestions

If every event your component supports have different data then there will be a bit of copy and pasting for each event. I strongly suggest you plan your events before you begin writing delegate classes.

Also remember that you can use partial classes to put the delegates in their own unit, just to make large and complex components with many delegates more manageable.

I also suggest that you support the old procedural events at the same time. I usually define these first while I work, because it’s so easy to change them when needed. Once my code is fully operational and the events deliver what I need them to do – then I write delegates that match.

If the data you deliver in your delegates is the same for several events, then you can just use inheritance to cut down on the work. Simply override the GetEventName() and you can re-use the baseline.

Global defines and ARM targets

A day ago I wrote about the new platform defines that helps you implement platform specific code in the same unit. On second thought we have instead decided to just add these values to each unit when we process them. This way the values are always known to the compiler.

This has the benefit of being faster than resolving the includefile symbol. On modern computers you don’t really notice any difference, but if we can shave a few clock cycles off something and it doesn’t cause any issues – we will do that (looking at you Raspberry Pi!)

Testing Quartex on ARM

Yes you read that right! Behind the scenes we have been very busy adapting our entire codebase so it compiles with both Delphi and Lazarus, with the aim of delivering a fully native IDE for MacOS, Windows and Linux – under both x64 and ARM chipsets.

Quartex Pascal IDE running on an ODroid N2 single-board computer, here executing the web desktop project

Needless to say this is going to take some work. While Lazarus is a wonderful system with some amazing components and features, the LCL has a few quirks that needs to dealt with. There are also quite a few components that simply do not exist under Lazarus that we have to implement from scratch – or find a suitable replacement for.

We have decided to go with the TrollTech QT bindings, and Kjell has built everything from scratch from the C/C++ code so we could get the webview working too. This is not supported by default in the Lazarus bindings.

The reason I mentioned clock-cycles and speed earlier taps into ARM support. Namely that ARM single board computers are nowhere near as powerful as x64. Granted, the Raspberry PI 5b performs exceptionally well, as does more expensive boards like the Radxa Rock Pi 5b and Orange PI 5 or 6. But those kinds of boards are not cheap. A Radxa Rock Pi 5b with 32 gigabytes of ram will set you back around $400-450 with shipping and taxes (depends where you live, Norway is tax hell).

Finding the lowest spec’s to support

One of my all time favorite SBC’s (single board computer) is the ODroid N2. It is a spectacular piece of equipment that is a incredibly reliable and powerful. But the N2 is close to two generation behind us, and while it’s a perfect board for a number of scenarios – it’s not suited to host a full development studio like QTX.

The N2 was released back when the Raspberry PI 4b was king of the hill. It’s roughly 20% faster for single-core tasks and 50% faster for multi-core tasks (just ballpark numbers, don’t quote me on this). Compared to the current Raspberry PI 5b (which is what most people buy) it’s not even a contender. The PI 5b is around 3 times faster. And the Rock Pi 5b is around 5 times faster.

The ODroid N2 is a wonderful SBC and one of my favorites

My point here is that once you start running your code on these types of devices – you quickly notice bottlenecks and speed penalties that you never even knew was there. You are not going to notice much difference on a Rock PI 5b because that is closer to a Intel i5 mobile SoC in performance. But the moment you start targeting Raspberry PI 4b, ODroid N2 and that generation of devices – you feel every hit and speed bump.

But the good news is: we were expecting this.

We have avoided multi-threading in the IDE on purpose, because we have been able to cover all our needs on a single core. The IDE, the compiler, parsers, code generation — all of it runs in the main application thread.

At the same time we have made sure to add thread safe mechanisms in most places where we need them. Obviously we need to add a bit more here and there, but the broad strokes already exist. In short: the infrastructure for threading the compiler and code generation is partly in place by default.

Performance affinity

Linux operates with affinity rather than concrete thread priority (well, both actually). Affinity is closer to preference in this case, where whatever affinity you assign to a process or thread – informs the task scheduler which core to use when running your code.

This is very important because ARM SBC’s typically use something called ‘little-big architecture’. Meaning that a board will have some performance cores that run at higher clock-speeds, and X number of service cores that are weaker, often A55 or A57 (as opposed to A75). So you can have two performance cores for example, and 4 service cores. As the name implies you want to run as much of the OS services on those weaker cores, while reserving the performance cores for important stuff.

The tests we have done so far is without any particular affinity, meaning that the we didn’t specify any particular preference. As a result the kernel put the IDE under a service-core every single time.

So the agenda for ARM development going forward is to put the compiler, codegen and parser (we have several parsers) into their own threads – and ensure that it’s affinity is set to performance cores. The IDE in general might get by running on a vanilla service-core, I will have to test this more in depth.

For now, the lowest spec is a Raspberry PI 5b, but we do hope to optimize and make Raspberry PI 4b and ODroid N2 (and similar) a good experience too.

Platform specific defines

With datamodules and non-visual component support in place, requirements that makes writing components for both NodeJS and the DOM from the same unit begins to surface. While the RTL takes care for most situations – there are spots where being able to have a $IFDEF to test against saves you some time.

Magic includes

In QTX we have a few so-called ‘magic’ include files that the IDE and compiler generates on the fly. When you start out with QTX one of the first things you notice is that forms have a {$I “intf::form”} coupled with a {$I “impl::form”}, like you see below:

Solves a very real issue quite elegantly with zero overhead at runtime

Since you are compiling for either the browser (DOM) or server (NodeJS) the idea of loading a DFM resource that contains your design is something we want to avoid. So what we do is that we convert your form design into code, and inject that into the constructor of your class.

So in the above picture the {$I “intf::form1”} will be replaced by your named widgets that you have dropped on your form — while the {$I “impl::form1”} will be replaced by the code that creates these widgets (hence its positioned inside the constructor).

Application magic

The third magic include is {$I “app::form.init”} that you can find in the app.entrypoint.pas file of your project. As you probably suspect, this is replaced by the generated code that creates your forms or datamodules.

Simple but very effective

These “magic includes” solve challenges in a simple, straight forward and ad-hoc way, with as little overhead as possible.

App::config

Today we added a new token to the “APP::” magic namespace, namely “config”. As the name hints at this provides information about the current build configuration. Depending on the platform you target, the following definitions will be emitted:

  • {$DEFINE PLATFORM_DOM}
  • {$DEFINE PLATFORM_NODE}
  • {$DEFINE PLATFORM_ANY}

And depending on the type of project, the following:

  • {$DEFINE TARGET_DOM}
  • {$DEFINE TARGET_NODE}
  • {$DEFINE TARGET_LIBRARY}
  • {$DEFINE TARGET_CLUSTER}

A normal visual project will have {$DEFINE PLATFORM_DOM} and {$DEFINE TARGET_DOM}

A library project will have {$DEFINE PLATFORM_ANY} and {$DEFINE TARGET_LIBRARY}

So instead of having two separate units for the same component (where that applies of course) you can now do stuff like this:

Easier to implement platform dependent code

This new feature will be in the next update!

Using LLM’s with Quartex Pascal

Nico Wouterse is no stranger to anyone in the Quartex community and have been with us since the project’s inception. He has worked quite closely with LLM technologies the past few years, building his own design library and design principle framework.

He just published a booklet and ‘how toon using LLM’s with QTX, which is an exciting read. So head on over to his website and have peek:
https://lynkfs.com/docs/QTX-and-LLM-integration/

Future roadmap

We are presently at the cusps of finishing both our roadmap steps, and one of the points on the next roadmap – is a command-line compiler. Both for Linux, MacOS and Windows (covering both x86 and ARM).

When that becomes available it should make QTX a dream for people generating projects using AI, invoking the command-line compiler to build the final product.

We could even look at licensing options for no-code websites that wants to use QTX as their back-end, spinning it up in containers to do the heavy lifting for their own no-code product.

Anyways – head over to Nico’s! He also has other booklets that could be of interest!

Datamodules are now here!

In the previous post I talked about our support for non-visual components. This is now in place and I moved on to the next ticket on our list: datamodules! And we already have this working!

Datamodules changes everything

For those coming from Delphi, C++Builder or Lazarus – datamodules need no introduction. It allows us to quickly drag & drop non-visual components together which at runtime becomes a complex object structure.

Datamodules are now operational! I need to make the form designer more aware, as it creates a few properties that is strictly not needed for datamodules (left, top, width etc). So we should have everything ready in our next update!

While this is ‘old news’ for native languages, it is actually entirely new for the world of JavaScript. And it really comes into it’s own under Node and Deno (server side programming).

Node made easier

Node is what people use to create servers or local system-services that are platform agnostic and async in nature. The non-visual nature of the many modules and packages that Node servers are made from – makes it difficult to create any form of visual tooling for it.

This is where Datamodules come in! All those fancy classes we have created for NodeJS can now be elevated to TQTXComponent and registered with the component palette. That way you can drag & drop servers and non-visual classes just like you would in Delphi or C++Builder.

We can also introduce new project types where datamodules are created automatically by the application object (just like visual applications), which ties in nicely with the existing project build-config. This also applies for the library project type and should turbo charge creating complex libraries that can be consumed by JS developers.

Exciting times ahead!

Support for non-visual components

Up until now the form designer in Quartex Pascal focused purely on visual controls. But as the IDE matures -more and more features common to RAD development is surfacing. Non visual components is finally supported by the IDE and package manager!

Non-Visual Components

In traditional development systems such as Delphi, C++Builder or Visual Studio -non visual components is a species of classes that the form designer can manage and visually represent. Technically it is the same as creating a class by code, but being able to drag & drop such objects side by side with visual controls, makes them infinitely useful.

Typical non-visual components are things like database connections, table or field bindings, image lists, event producers and config options. In most scenarios these non-visual objects are connected to a visual control through property assignments.

Same methodology

From the viewpoint of the IDE a non-visual component is the exact same as a visual control. It is registered and handled by the same internal mechanisms, and likewise created and destroyed alongside it’s visual siblings. The only difference is that they inherit from TQTXComponent rather than the DOM specific TQTXWidget.

You can now drag & drop non-visual components onto your forms!

This also means that TQTXComponent does not manage the life-cycle of a HTML5 element. There is no Handle property that reference any elements in the DOM or the NodeJS runtimes (unless the component is written that way).

You likewise use a non-visual control in the same way you would a visual one. You drag & drop the component on a form, change it’s properties using the property inspector – and that’s all there is to it.

Datamodules

The next task is to introduce datamodules. You can think of datamodules as a non-visual form. Much like TQTXComponent a datamodule does not manage any visual elements, it is purely a construct for organizing non-visual components.

In large applications with many forms where you want to share resources, such as images or a database connection – it is common to isolate these components on a global scope datamodule. Instead of each form having it’s own copy of said components, they all share one instance via a datamodule.

Perfect for NodeJS or Deno

Datamodules will revolutionize building Nodejs applications. This really is the missing link for RAD (rapid application development) server-side application building.

You will be able to drag & drop server components, database connections, communication protocols – and setup a complex server infrastructure in no time. Non-visual components and datamodules reduces tedious scaffolding to minutes rather than hours or days.

This is exactly why Delphi is so popular for building desktop applications.

Ragnarok communication protocols

With non-visual components under wraps, it will be possible for us to isolate different communication protocols as self-contained entities that you can drag & drop. So anyone can design a message protocol and emit it as a reusable component.

The IDE currently emits protocols as stock classes. This can soon be elevated to components.

Imagine dropping a Websocket server on a datamodule, then dropping a ready-to-use filesystem protocol component, then an identity protocol component, and finally a chat-room protocol components. Once the protocols are attached to the server object -the server will instantly support the message-types these protocols export. Each protocol can contain hundreds of messages.

We can also isolate the wealth of Nodejs modules as drag & drop components. Features that would otherwise require time consuming manual work.

Update

A new build will be available shortly! We are hoping to have the update out this weekend with support for non-visual components. It will take some time before we can offer a rich palette with ready to rock objects, but knowing our community I dont think it will take too long before they appear in the wild 😉

Hotfix for version 1.0.1.0

A small bug was discovered in the treeview on the main form that contains the Run-Time Library file overview. After downloading and updating to the latest packages – the items still retained references to the previous packages prior to the update.

This sadly caused an access violation if you began navigating the treeview before clicking “Refresh Runtime library files” from the tools menu.

We noticed this and fixed it immediately

This was an oversight on our part as the RTL treeview should be automatically refreshed when the package-download form closes.

This has now been fixed.

Windows Defender false positive

Quartex Pascal is written in Embarcadero Delphi, which has been the favorite language used by hackers for a long time. This has the downside that a lot of virus killers (Windows defender included) can mistakenly flag executables created in Delphi as containing a malware (trojan usually).

This is because Virus killers scan for unique signatures (hash mapping) inside executable files, looking for specific signature matches – and thus picks up on chunks from the Delphi RTL which has nothing to do with any malignant code.

In other words, most virus killers blindly compare against digital footprints which also exists in harmless applications. It’s like flagging paw prints as bad without checking if its from a tiger or golden retriever.

This is well documented case (see links below) that sadly affects a lot of software out there. There is absolutely no trojan code in our executables. Our build server runs on Linux as well, and our back-end has two separate security systems as the binary travels from the compiler to our installer build process.

Both executables that ship with our installer, the IDE and the update executable (which restarts the IDE after updating the main program) are signed with a commercial certificate – and there is simply no malicious code in our repositories. Everything is built from scratch on Linux, there is no way for a process to attach itself to a fresh binary like it could do on Windows.

So you can safely mark the executables as safe, telling Windows defender to ignore these.

The use of UPX

Many developers use a popular exe compressor called UPX. This takes your executable and compress it, basically creating a new exe file with the decompression code included. So when you start this program – the original application is decompressed into memory and executed.

While we doubt this has any effect on the issue we mention above, we have decided to stop using it. Just in case that can cause Windows Defender to overreact. There is nothing wrong with UPX and it’s been in use for decades by developers everywhere – so it’s doubtful that this will have any effect.

Either way, just to make sure we do what we can – its no longer used.

Get the latest update

Update or download the latest executable from our download page!

Quartex Pascal v1.0.1.0 is out!

The changes to this version are massive, but most of the work is not really visible since it involves a complete refactor of our codebase to Lazarus (read: making it possible to compile with Lazarus on ARM, we still very much use Delphi).

Plenty of RTL changes and updates, including the new TQTXStringGrid widget

We hope we have managed to fix most of the hiccups people have experienced, and we will of course continue to polish and add more and more features according to our plans and roadmap.

Full reinstall?

Technically you can just update your packages and exe from the update window, but since the new TQTXImage breaks demo code I strongly urge you to download the installer and do a re-install. The new demos for things like the string-grid and layout unit is a part of the installer.

Changelog

  • Removed registration of old grid-layout widget, as well as example (now deprecated)
  • Fixed a problem with the css selectors in TQTXContentbox
  • Fixed issue with TQTXLabel not vertically centering due to css selector typo
  • Minor fix of TQTXLabelContent constructor
  • Updated TQTXToolbar quite heavily with overloads and methods for easily setting up buttons
  • Minor adjustment to TQTXSize
  • Updated the sqlite3.js driver
  • Refactored and fixed issues with the sqlite3 DOM driver
  • Minor adjustment to DB framework (connection pool etc)
  • Added TInt32.PercentageOf() method to RTL
  • Moved application object reference to unit level, avoid circular reference which left the app object in memory after unloading an app at runtime
  • Simplified the Alpha blending (and fixed wrong math where the exponent was left out)
  • Implemented full text-metric for TQTXFont, it is now possible to measure the width/height of a string with a single call
  • Implemented TQTXTable widget with full wrapping of the HTML5 table element (including the new header, body and footer constructs)
  • Implemented TQTXStringGrid widget with support for column editing
  • Implemented new and fancy Layout unit, making it a snap to create complex layouts by code, perfect for dialogs and quick form layouts
  • Implemented Marquee widget (scrolltext)
  • Rewrote TQTXImage which now contains the code that used to be in TQTXDomImage
  • Implemented TQTXImageBox, which contains the logic previously in TQTXImage (sigh)
  • Removed obsolete units from the Patreon days, which are now a part of the RTL
  • Added example for the StringGrid
  • Added example for the layout unit
  • Fixed bug in CSSClassRemoveEx() method for TQTXWidget
  • Fixed an issue of Paint() not being virtual in TQTXGraphicView
  • Minor changes to demos to make them compatible with the new TQTXImage widgets. This affected 3 demos which used them quite extensively
  • Went through all RTL units to get rid of hints and warnings (21 units)
  • Updated Phazer library with fixes to class fields
  • Fixed a blunder in TQTXORMObject
  • Fixed Node.js template files (8 files)
  • Fixed bug where WebView did not work if you started two instances of the IDE
  • Fixed issue where the property inspector sheet would not be repositioned during a resize
  • Fixed a massive issue with name qualifier being active on string properties in the inspector
  • Added support for https for node.js http server
  • Expanded back/next functionality in the IDE (history navigation) to handle positions in editor as well as widget selections
  • Full adaptation of the our codebase for Lazarus / Freepascal
  • Created our own proxy widgets to make porting easier
  • Recompiled QT for Linux and Mac from scratch, our codebase now used QT for all platforms (not yet released)
  • Fixed missing attribute read/write in TQTXListView
  • Adapted several parts of DWScript to better suit our needs.
  • Added Select All, Select None, Select RTL options to the popup menu in the package-download form, makes it easier to quickly selected all items etc
  • Full update of the HTML documentation in the IDE, with substantial changes and more info
  • Fixed issue with color selection in the inspector not being persisted in the Background and Border properties
  • Updated to latest HTMLComponents

From handle to widget

In the Quartex Pascal RTL we support both traditional pascal events, like people are used to from Delphi or Freepascal, as well as delegate objects native to JavaScript. Most widgets exposes standard events like OnClick when it’s useful, and if you need more specific event handling you can attach a delegate for it.

Delegates?

A delegate is an object that represents your event-handler, the procedure that should be executed when the event fires. The benefit of a delegate is that it allows you to attach as many handlers as you like to any given event. Internally in the JavaScript runtime when an event is triggered, it will traverse the delegate array associated with the event – and call each handler in sequence.

The JavaScript event model can look scary at first, but it’s really powerful once you get the hang of it

So you can attach 100 OnClick handlers to a button if you like, and they will all fire in the same sequence as you added them. This is very useful since you can attach to widgets from the outside rather than override methods or mess with the internal logic.

You can read more about event delegation in JavaScript here.

Delegate handlers

Delegate handlers are somewhat different in Quartex Pascal than you expect. We don’t shield you from the JS elements we have wrapped. Instead we have provided utilities that simplify working with these raw JS style handlers.

Let’s look at a pointer event (similar to mouse but works well on touch devices):

procedure HandleClick(Sender: TQTXDOMPointerDelegate; EventObj: JPointerEvent);

The important part in the above handler is the EventObj, which is the actual PointerEvent object from Javascript. We have painstakingly created a wrapper class for it (which is why it comes in as JPointerEvent) that allows you to work with the raw JavaScript object directly.

The most important property of any native JavaScript event object, is no doubt the target (and currentTarget) property. This contain the handle of the element you have clicked or interacted with.

Finding the instance

If you have created your own widget that maintains several child elements, you have two choices: either you attach a delegate to each child, which results in hundreds or potentially thousands of delegate objects being created. Or, usually more prudent, you attach a single handler to the widget itself (the container of the children) and then work out what child the user has interacted with.

This works because JavaScript events bubbles upwards. So whenever you click anything, the event will first fire on the document, then trigger in the application display element, then the form, then panels or anything else beneath the mouse pointer (and so on). Think of it like a deck of cards where the event will fire starting from the card at the bottom – and bubble upwards toward the top. The current card is represented by the currentTarget property, while whatever you hit on that card (child) is referenced in the target property.

To find the widget instance (class instance) that a handle belongs to, we can use the GetInstanceFor() method of TQTXWidgetRegistry. Whenever a widget is created it registers itself there, exactly so its easy to do a quick lookup of a handle:

procedure HandleClick(Sender: TQTXDOMPointerDelegate; EventObj: JPointerEvent);
begin
  var lHit := TQTXWidgetRegistry.GetInstanceFor( EventObj.target );
  if lHit <> nil then
  begin
    writelnF("you hit %s", [lHit.Classname]);
  end;
end;

But what if you have child elements that you populate with raw html or images at runtime? Like I mentioned above the runtime keeps track of widgets you create (or that the application itself has created), but this does not cover raw html that you insert at runtime.

If i have a button as a child on my custom widget (just an example) and insert the following at runtime, those elements will be unmanaged by your application. They are not created as widgets, and thus there will be no registration for them:

button1.innerHTML := '&lt;img src='glyph.png'>&amp;nbsp;Hello world';

So if a user now clicks the IMG element head-on, the target property will in fact point to the IMG element, not your button. And thus getInstanceFor() will return NIL. In this particular scenario the currentTarget propery will in fact point to the button, but we want to avoid such specific situation dependent code if we can.

Finding the owner

TQTXWidgetRegistry has another method that takes care of this, namely a function that will take any handle -then traverse backwards through it’s parents until it find a widget of a specific type – namely FindWidgetFromHandle().

So to mitigate cases where you (or anyone who use your cool new widget) have inserted raw html, you would do something like this:

procedure HandleClick(Sender: TQTXDOMPointerDelegate; EventObj: JPointerEvent);
begin
  var lHit := TQTXWidgetRegistry.GetInstanceFor( EventObj.target );
  if lHit = nil then
    TQTXWidgetRegistry.FindWidgetFromHandle(TQTXButton, EventObj.target, lHit);

  if lHit <> nil then
  begin
    writelnF("you hit %s", [lHit.Classname]);
  end;
end;

In the above code we call on FindWidgetFromHandle() and we specify that it should stop if it finds an instance of TQTXButton and keep that value (the third parameter is of type var, so the value is stored there when successful).

This obviously takes for granted that you know the types of child elements you manage. The benefit of custom widgets is that you already know what type of child elements you deal with. A listbox deals with listitems, a listview operates with listviewitems and so on.

You can implement more elaborate code that determine if an element is a direct child of your widget (and thus allowed) and then write more delicate handling to deal with edge cases. If you snoop around the RTL and look at how we have solved things there you will find quite a lot of it!

The new TQTXTable widget handles everything through a single, elaborate handler

Adopting a handle

There is also a final option when it comes to “alien” handles, and that is to adopt them! Yes you read that right, you can in fact absorb any element if you have a handle reference.

TQTXWidget which is the fundamental class for all visible controls have a few constructors you can pick from, including one that takes an already existing handle.

So you can in fact do something like this:

var lAlien := TQTXWidget.CreateByRef(EventObj.Target, [], nil);

In some circumstances it’s actually very useful to inject raw HTML, and then adopt the injected structure as a widget. It all depends on your needs.

The second parameter is a set type, with a few options you can tweak:

  TQTXWidgetCreateOptions = set of (
    wmStyleObject,
    wmBindToParent,
    wmUsePositionMode,
    wmUseDisplayMode,
    wmUseOverflow,
    wmUseBoxSizing,
    wmUseParentFont,
    wmUseTouchAction,
    wmUseVisibility,
    wmAutoZindex
    );

These options enable or disable what should be applied to the handle. wmStyleObject will execute the StyleObject() method as a part of the constructor, Using wmUseParentFont will apply the parentfont styling to the widget and so on. In most cases you wont apply any of these as that would affect how the adopted element is styled.

Do keep in mind though that adopted widgets will not manage the element’s life-cycle like ordinary widgets. Since the element was not created by TQTXWidget, it will not destroy the HTML element it manages when the destructor executes.

Creating a scrolltext widget

The holidays is a perfect time to enjoy some quality coding. The stress leading up to Christmas is over, and we have time to reflect, catch up on some readying, and maybe do a spot of coding just for fun!

In this article we will be creating a scrolltext widget from scratch, which is a good way to demonstrate how easy it is to write your own controls in QTX. It is a lot easier than under Delphi or Lazarus since the browser provides most of the functionality out of the box for you, and the QTX runtime library provides the rest.

So start a new visual project and add a new unit, we will be isolating our fancy new widget there.

Looking at the documentation

The DOM has an element that already does what we want, namely the marquee tag. This tag will scroll or slide text from right-to-left (e.g normal scrolltext). So our first stop is to look at the HTML documentation for that specific tag.

There are a lot of websites that maintains HTML documentation. I usually just google the tag i want to work with and read up on it first. In almost all cases W3Schools is a good place to start.

One thing worth noting when working with HTML elements, is that there are two types of properties. There are properties that you can adjust via CSS (such as width, height, border, background and similar) and then there are attributes. Attributes are usually defined via the tag itself, which is slightly different than how CSS properties are read and written to (poke around the RTL and look at the code for the other widgets is a good idea when learning the ropes!):

<marquee scrolldelay=16>

Note: Further down in the article we will be exposing attributes as pascal properties, and these are read and written somewhat different to standard html properties. This is often a case of confusion when you are just starting out, but it really is very simple. Just remember that there is a difference between HTML properties and HTML attributes and you will pick it up in no time!

Isolating enum types

Now that we have found the official HTML documentation for the element, I usually look for properties that I can turn into enums types. In the above overview two properties in particular stand out: direction and behavior. These properties are both string, which means we can use our enum as a simple index value into a pre-defined array. So let’s start by defining those!

TQTXScrollDirection = (sdNone = 0, sdLeft, sdUp, sdDown, sdRight);
TQTXScrollBehavior = (sbNone = 0, sbSliding, sbScrolling, sbAlternate);

Note: Notice that i added a “none” option at the start of each enum type. This is to allow for a state where nothing is set, and it’s also useful when we try to read properties from the live DOM at runtime. If we for some reason are unable to recognize a string from the DOM, we can fall back on sdNone or sbNone cleanly without fuzz.

To make life a little easier for ourselves (and make our code run faster) we will create two arrays that match these enum types. The idea here is, that you can use the ordinal value of the enum to get the correct string value – and likewise to lookup a string value and get the enum value. I usually refer to arrays like this as lookup tables.

Immediately beneath the implementation section, we define our lookup tables, and two helper functions to convert a string value back to an enum type. This is useful when we read values directly from the DOM at runtime.

var
sDirections: array [TQTXScrollDirection] of string = ('', 'left', 'up', 'down', 'right');

sBehavior: array[TQTXScrollBehavior] of string = ('', 'sliding', 'scrolling', 'alternate');

function StrToDirection(const Value: string): TQTXScrollDirection;
begin
  var idx := sDirections.IndexOf( Value.Trim().ToLower());
  if idx >= sDirections.Low() then
    result :=  TQTXScrollDirection(idx);
end;

function StrToBehavior(const Value: string): TQTXScrollBehavior;
begin
  var idx := sBehavior.IndexOf( Value.Trim().ToLower());
  if idx >= sBehavior.Low() then
    result :=  TQTXScrollBehavior(idx);
end;

Next, we need to define our own widget class. By default all TQTXWidget’s will create a DIV element, so the first thing we need to do is to change that. We want our widget to create a marquee element instead.

TQTXScrollText = class( TQTXWidget )
protected
  function CreateElementInstance: TWidgetHandle; override;
end;

And further down in our implementation section of the unit we add the code for our new CreateElementInstance() method as such:

function TQTXScrolltext.CreateElementInstance: THandle;
begin
  asm
    @result = document.createElement("marquee");
  end;
end;

Getter and setter logic

Values that you have applied to a visual control in QTX during design time (in the form designer or inspector) are always written to the widget as a part of the constructor callback (you can read about that in the documentation). This means that we need to cache values if the widget is not in “ready state”. This is more or less the exact same as you would do in Delphi.

At this point I want to include a couple of the other properties for the HTML element (delay and amount) which map more cleanly to integer values (no need for any lookup tables there).

Our class now looks like this:

  TQTXScrollText = class( TQTXWidget )
  private
    fDirection: TQTXScrollDirection;
    fBehavior: TQTXScrollBehavior;
    fDelay:     int32;
    fAmount:    int32;
  protected
    function    GetDelay: int32; virtual;
    procedure   SetDelay(const Value: int32); virtual;

    function    GetAmount: int32; virtual;
    procedure   SetAmount(const Value: int32); virtual;

    function    GetDirection: TQTXScrollDirection; virtual;
    procedure   SetDirection(const value: TQTXScrollDirection); virtual;

    function    GetBehavior: TQTXScrollBehavior; virtual;
    procedure   SetBehavior(const value: TQTXScrollBehavior); virtual;

    function    CreateElementInstance: TWidgetHandle; override;

  public
    property  GetAmount: int32 read GetAmount write SetAmount;
    property  Delay: int32 read GetDelay write SetDelay;
    property  Direction: TQTXScrollDirection read GetDirection write SetDirection;
    property  Behavior: TQTXScrollBehavior read GetBehavior write SetBehavior;
  end;

With the definition in place, let’s get to the boring task of implementing the getter and setter methods!

function TQTXScrollText.GetDirection: TQTXScrollDirection;
begin
  if WidgetState = wsReady then
    result := StrToDirection( TQTXBrowser.ReadComputedStr(Handle, "direction") )
  else
    result := fDirection;
end;

procedure TQTXScrollText.SetDirection(const Value: TQTXScrollDirection);
begin
  if WidgetState = wsReady then
    Handle.direction :=  sDirections[ value ]
  else
    fDirection := value;
end;

function TQTXScrollText.GetBehavior: TQTXScrollBehavior;
begin
  if WidgetState = wsReady then
    result := StrToBehavior( TQTXBrowser.ReadComputedStr(Handle, "behavior") )
  else
    result := fBehavior;
end;

procedure TQTXScrollText.SetBehavior(const value: TQTXScrollBehavior);
begin
  if WidgetState = wsReady then
    Handle.behavior :=  sBehavior[ value ]
  else
    fBehavior := value;
end;

function TQTXScrollText.GetAmount: int32;
begin
  if WidgetState = wsReady then
    result := Attributes.AttributeRead("scrollamount")
  else
    result := fAmount;
end;

procedure TQTXScrollText.SetAmount(const Value: int32);
begin
  if WidgetState = wsReady then
    Attributes.AttributeWrite('scrollamount', value)
  else
    fAmount := value;
end;

function TQTXScrollText.GetDelay: int32;
begin
  if WidgetState = wsReady then
    result := Attributes.AttributeRead("scrolldelay")
  else
    result := fDelay;
end;

procedure TQTXScrollText.SetDelay(const Value: int32);
begin
  if WidgetState = wsReady then
    Attributes.AttributeWrite('scrolldelay', value)
  else
    fDelay := value;
end;

Scaffolding and stuff

You are probably wondering why there is no property for the, well, actual text that we will be scrolling? Well that is the fun part! The marquee element will scroll whatever text you have defined as it’s innerHTML property. And that property is already defined and published in TQTXWidget that we inherit from, so you dont really need to do anything for that.

While we do want a bit more code added, like our own constructor, and override the ApplyPropertyCache() method — lets just give it a spin first and see what we have so far!

Flip over to the tab for your mainform and add the following code to your constructor:

var lScroller := TQTXScrollText.Create( panel3, nil );
lScroller.Height := 32;
lScroller.Amount := 4;
lScroller.Behavior := TQTXScrollBehavior.sbScrolling;
lScroller.align := alBottom;
lScroller.InnerHtml := "<b>Welcome</b> to the fun and games of HTML controls!";
lScroller.ThemeBackground := TThemeBackground.bgDecorativeInverted;
lScroller.Font.Color := clWhite;

I added the blue gradient theme background purely so we can see the widget’s boundaries. As you can see below, it aligns neatly to the bottom of the form (I only took a small screengrab here, no point posting a large empty form).

Congratulations! You have now created your first visual QTX control! The next step is to do the typical organization for widgets that can be used by the form designer:

  1. Rename the class to TQTXCustomScrolltext
  2. Expose the HSpacing and VSpacing attrbutes
  3. Define another class called TQTXScrolltext which defines the properties as published with default values. This allows you to inherit new widgets from TQTXCustomScrolltext and chose what properties you want to expose.
  4. Override ApplyPropertyCache() which ensures that values set in the inspector is properly written to the underlying marquee element immediately after it reaches it’s ready-state.
  5. Define a custom constructor for TQTXScrolltext, which the IDE needs to generate code that targets the widget specifically.
  6. Add delegate definitions so standard delegate types can be added from the form designer

Here is the finished unit, ready for use. If you create a folder for it and add a filesource for that folder (see preferences window) the IDE will notice the attributes for the class and register it on your widget palette!

unit qtx.dom.control.scrolltext;

interface

uses
  qtx.sysutils,
  qtx.classes,
  qtx.dom.types,
  qtx.delegates,
  qtx.dom.events,
  qtx.dom.theme,
  qtx.dom.events.mouse,
  qtx.dom.events.pointer,
  qtx.dom.events.keyboard,
  qtx.dom.events.touch,
  qtx.dom.widgets;

type
  // Forward declarations
  TQTXCustomScrolltext = class;
  TQTXScrolltext = class;

  // Enum types
  TQTXScrollDirection = (sdNone = 0, sdLeft, sdUp, sdDown, sdRight);
  TQTXScrollBehavior = (sbNone = 0, sbSliding, sbScrolling, sbAlternate);

  // Base class that implements the Marquee element
  TQTXCustomScrolltext = class( TQTXWidget )
  private
    fDirection: TQTXScrollDirection;
    fBehavior: TQTXScrollBehavior;
    fDelay:     int32;
    fAmount:    int32;
    fHSpace:    int32;
    fVSpace:    int32;
  protected
    function    GetDelay: int32; virtual;
    procedure   SetDelay(const Value: int32); virtual;

    function    GetAmount: int32; virtual;
    procedure   SetAmount(const Value: int32); virtual;

    function    GetDirection: TQTXScrollDirection; virtual;
    procedure   SetDirection(const value: TQTXScrollDirection); virtual;

    function    GetBehavior: TQTXScrollBehavior; virtual;
    procedure   SetBehavior(const value: TQTXScrollBehavior); virtual;

    function    GetHSpace: int32;
    procedure   SetHSpace(const Value: int32);

    function    GetVSpace: int32;
    procedure   SetVSpace(const Value: int32);

    function    CreateElementInstance: TWidgetHandle; override;

    procedure   ApplyPropertyCache; override;
  end;

  // Custom constructor
  TQTXScrolltextConstructor = procedure (ScrollText: TQTXScrolltext);

  [PropertyDialog('InnerHtml', dlgHTML)]
  [PropertyDialog('InnerText', dlgTEXT)]
  [PropertyDialog('Value', dlgTEXT)]
  [RegisterInfo('Standard HTML5 Marquee scrolltext', 'qtx_ui_button.png')]
  [RegisterWidget(pidBrowser, ccGeneral)]
  [DefaultName('Scroller')]
  [BindDelegates([
    TQTXDOMMouseWheelDelegate,
    TQTXDOMMouseClickDelegate,
    TQTXDOMMouseDblClickDelegate,
    TQTXDOMMouseEnterDelegate,
    TQTXDOMMouseLeaveDelegate,
    TQTXDOMMouseDownDelegate,
    TQTXDOMMouseMoveDelegate,
    TQTXDOMMouseUpDelegate,
    TQTXDOMPointerLostCaptureDelegate,
    TQTXDOMPointerGotCaptureDelegate,
    TQTXDOMPointerUpDelegate,
    TQTXDOMPointerMoveDelegate,
    TQTXDOMPointerDownDelegate,
    TQTXDOMPointerLeaveDelegate,
    TQTXDOMPointerEnterDelegate,
    TQTXDOMKeyboardPressDelegate,
    TQTXDOMKeyboardUpDelegate,
    TQTXDOMKeyboardDownDelegate,
    TQTXDOMTouchStartDelegate,
    TQTXDOMTouchEndDelegate,
    TQTXDOMTouchMoveDelegate,
    TQTXDOMTouchCancelDelegate
    ])]
  TQTXScrolltext = class( TQTXCustomScrolltext )
  public
    constructor Create(AOwner: TQTXComponent; CB: TQTXScrolltextConstructor); reintroduce; virtual;
  published
    property  Amount: int32 read GetAmount write SetAmount default 1;
    property  Delay: int32 read GetDelay write SetDelay default 1;
    property  Direction: TQTXScrollDirection read GetDirection write SetDirection default sdLeft;
    property  Behavior: TQTXScrollBehavior read GetBehavior write SetBehavior default sbScrolling;
    property  HSpace: int32 read GetHSpace write SetHSpace default 0;
    property  VSpace: int32 read GetVSpace write SetVSpace default 0;
  end;


// Expose helper functions so anyone inheriting from TQTXCustomScrolltext
// can easily map values without having to expose the lookup tables
function StrToDirection(const Value: string): TQTXScrollDirection;
function StrToBehavior(const Value: string): TQTXScrollBehavior;

implementation

var
  sDirections: array [TQTXScrollDirection] of string = ('', 'left', 'up', 'down', 'right');
  sBehavior: array[TQTXScrollBehavior] of string = ('', 'sliding', 'scrolling', 'alternate');

function StrToDirection(const Value: string): TQTXScrollDirection;
begin
  var idx := sDirections.IndexOf( Value.Trim().ToLower() );
  if idx >= sDirections.Low() then
    result :=  TQTXScrollDirection(idx);
end;

function StrToBehavior(const Value: string): TQTXScrollBehavior;
begin
  var idx := sBehavior.IndexOf( Value.Trim().ToLower() );
  if idx >= sBehavior.Low() then
    result :=  TQTXScrollBehavior(idx);
end;

//#############################################################################
// TQTXScrolltext
//#############################################################################

constructor TQTXScrolltext.Create(AOwner: TQTXComponent; CB: TQTXScrolltextConstructor);
begin
  inherited Create(AOwner, procedure (Widget: TQTXWidget)
  begin
    // This serves two purposes. We init the default values when creating
    // the widget by code (not on a form design), and we endure that the
    // HTML attrbutes are initialized before we read from them
    Amount := 1;
    Delay := 1;
    Direction := sdLeft;
    behavior := sbScrolling;
    HSpace := 0;
    VSpace := 0;

    if assigned( CB ) then
      CB( self );
  end);
end;

//#############################################################################
// TQTXCustomScrolltext
//#############################################################################

function TQTXCustomScrolltext.CreateElementInstance: THandle;
begin
  asm
    @result = document.createElement("marquee");
  end;
end;

procedure TQTXCustomScrolltext.ApplyPropertyCache;
begin
  inherited;
  SetAmount( fAmount );
  SetDelay( fDelay );
  SetDirection( fDirection );
  SetBehavior( fBehavior );
  SetHSpace( fHSpace );
  SetVSpace( FVSpace );
end;

function TQTXCustomScrolltext.GetDirection: TQTXScrollDirection;
begin
  if WidgetState = wsReady then
    result := StrToDirection( TQTXBrowser.ReadComputedStr(Handle, "direction") )
  else
    result := fDirection;
end;

procedure TQTXCustomScrolltext.SetDirection(const Value: TQTXScrollDirection);
begin
  if WidgetState = wsReady then
    Handle.direction :=  sDirections[ value ]
  else
    fDirection := value;
end;

function TQTXCustomScrolltext.GetBehavior: TQTXScrollBehavior;
begin
  if WidgetState = wsReady then
    result := StrToBehavior( TQTXBrowser.ReadComputedStr(Handle, "behavior") )
  else
    result := fBehavior;
end;

procedure TQTXCustomScrolltext.SetBehavior(const value: TQTXScrollBehavior);
begin
  if WidgetState = wsReady then
    Handle.behavior :=  sBehavior[ value ]
  else
    fBehavior := value;
end;


function TQTXCustomScrolltext.GetHSpace: int32;
begin
  if WidgetState = wsReady then
    result := Attributes.AttributeRead("hspace")
  else
    result := fHSpace;
end;

procedure TQTXCustomScrolltext.SetHSpace(const Value: int32);
begin
  if WidgetState = wsReady then
    AttributeWrite('hspace', value)
  else
    fHSpace := value;
end;

function TQTXCustomScrolltext.GetVSpace: int32;
begin
  if WidgetState = wsReady then
    result := Attributes.AttributeRead("vspace")
  else
    result := fVSpace;
end;

procedure TQTXCustomScrolltext.SetVSpace(const Value: int32);
begin
  if WidgetState = wsReady then
    AttributeWrite('vspace', value)
  else
    fVSpace := value;
end;

function TQTXCustomScrolltext.GetAmount: int32;
begin
  if WidgetState = wsReady then
    result := Attributes.AttributeRead("scrollamount")
  else
    result := fAmount;
end;

procedure TQTXCustomScrolltext.SetAmount(const Value: int32);
begin
  if WidgetState = wsReady then
    AttributeWrite('scrollamount', value)
  else
    fAmount := value;
end;

function TQTXCustomScrolltext.GetDelay: int32;
begin
  if WidgetState = wsReady then
    result := Attributes.AttributeRead("scrolldelay")
  else
    result := fDelay;
end;

procedure TQTXCustomScrolltext.SetDelay(const Value: int32);
begin
  if WidgetState = wsReady then
    AttributeWrite('scrolldelay', value)
  else
    fDelay := value;
end;

end.