Monday, November 30, 2009

Using messages to notify events

Lately, i've been using frames extensively in my projects and recently i faced a little problem while working with them: how to notify the parent control (often a TForm) changes in the state of the frame controls/data?

I tried some approaches:

1) Add an event handler to the TDatasource.OnDataChange event.
This has the disadvantage of not being a generic solution since not always the frame has a TDataSource. Also this event is fired in changes to all fields of the respective TDataset, and often only a few fields need to be monitored, leading to some overhead. Yet is not possible to set the event at design time due to bug 14947.

2)Create handlers for events of child controls.
Works by setting in the parent form handlers for events, e.g. OnChange, of child controls of the frame. It has the drawback of needing more than one event handler in the case where is necessary to monitor more than one control/field.
Another problem is that such event handlers can be cleared by the IDE due to bug 14835. Not to say that sometimes the changes in the frame are not propagated to frame instances. In this case the workaround i found to sync the frames was to remove and add the frame again, loosing all properties set in the frame parent control.

So, i decided to implement a specific event for the frame. The first approach i thought was adding a TNotifyEvent property to the frame but this has almost the same drawbacks of approach 2. At this point the idea of using messages came in my mind.

AFAIK messages are used mostly in LCL and seldom in user projects so i had doubts if would work. Here is what i did:

1) Declare a custom message id constant
I declared the following constant in a shared unit:

LM_CHILDDATACHANGED = LM_USER + 1;
2) Create a method in the frame to send the message
I created a method SendDataChangeMsg with the following code:

var
Form: TCustomForm;
begin
Form := GetFirstParentForm(Self);
if Form <> nil then
Form.Perform(LM_CHILDDATACHANGED, 0, 0);
end;

The code is pretty straight. It get the parent form and then send the message through Perform method

3) Hook the events of the controls that should be monitored
Mostly OnChange events. Just called SendDataChangeMsg inside them

4) Add a property ValidData
This property checks if the data in the monitored controls are valid

5) Intercept the message at the frame parent (TForm)
In the TForm i put the frame, i created a method to intercept the message declared as follow:

procedure ChildDataChanged(var Msg: TLMessage); message
LM_CHILDDATACHANGED;


I'm using that to allow saving or not the data so i put a code to enable/disable the save button:

SaveButton.Enabled := Frame.ValidData;
That's all. It's working fine for me. In the end i got a pretty clear notify system. Now i can remove/add the frame as necessary without the need to reconnect the event handlers every time.



Sunday, November 15, 2009

Qt and Lazarus: a nice surprise (again)

Long, long time ago i wrote how the Lazarus Qt interface impressed me by its functionality. Yesterday, after updating my Lazarus working copy to do some debugging in Linux, i rebuilt the IDE as usual. But the IDE looked a little different, most notably the source editor font. I've got sometime to figure that the IDE was compiled using the Qt interface. I use the gtk2 interface but somehow i left the LCL build configured to Qt, so the unexpected result.

Again, the Qt interface surprised me. The IDE ran fine with good looking and very responsive except by bugs 15101 and 15103. Unfortunately, i use a lot the features that these bugs affect and i will stay with gtk2 for now. But as soon as these bugs are fixed i will give a try to Qt again.

Sunday, November 01, 2009

Using TDBEdit to handle date fields

If you try to link a date field with a TDBEdit, when an user inserts an invalid date, the control reacts with a non user friendly exception message. So, for long time, i used a TMaskEdit to edit date values and then update manually the dataset.
After some debate about TMaskEdit in the mail list i tried to workaround this problem.

The first think i did was to set the EditMask property to !99/99/9999;1;_ and hardcode the ShortDateFormat with a compatible format, e.g. 'dd/mm/yyyy'.

To validate the value inserted i created a handler to be linked to TDBEdit OnExit event(the color setting is optional):

var
D: TDateTime;
DateEdit: TDBEdit;
begin
DateEdit := Sender as TDBEdit;
if not TryStrToDate(DateEdit.Text, D) then
begin
ShowMessage(DateEdit.Text + ' is not a valid date');
DateEdit.Clear;
DateEdit.Color := clRed;
//or set a default value
//DateEdit.Text := '10/10/1900';
end
else
DateEdit.Color := clWindow;
end;

So you clear the TDBEdit and get a empty string that is valid value right? Not so fast. The message dialog will trigger an KillFocus message updating the Dataset with the old (and invalid) value. Calling ShowMessage after Clear does not help because the the control will try to update the dataset with " / / ", that is also not a valid value.

The solution i found was to handle the OnSetText event of the date field to skip all invalid values:

var
D: TDateTime;
begin
if not TryStrToDate(aText, D) then
Sender.Value := Null
else
Sender.AsDateTime := D;
end;

With this approach is possible to allow date edition with masks safely.

However, setting events for each TDBEdit and date field is annoying, so i created an TDBEdit descendant to handle this issue. It can be found in the LuiControls package (svn version).

In time:
  • Rx package has a TDBDateEdit component, but it does not allow editing with masks and don't manage invalid dates.
  • It's required a recent (post 0.9.28) svn version because of a bug in TMaskEdit in the earlier versions

Saturday, November 15, 2008

Effect of using a constant parameter for string types

Is not rare to find implementations of procedures/functions/methods that uses a value parameter for read only string arguments. While i always use constant parameters for such cases, the real benefit of this code practice was not clear. Until today.

I made a small application that implements two versions of a procedure identical except by the type of parameter (Value vs Constant)...

program asmConstParameter;

{$Mode ObjFpc}
{$H+}

uses
SysUtils, Types;

procedure DoIt(V: String);
begin
Writeln(V);
end;

procedure ByValue(V: String);
var
S: String;
begin

S := V;
DoIt(S);
end;

procedure ByReference(const V: String);
var
S: String;
begin
S := V;
DoIt(S);
end;

var
X: String;

begin
X := 'Test';
ByValue(X);
ByReference(X);
end.

...and examined the assembler output. See the difference yourself. Using optimizations through -O compiler options does not change the produced code.

So using a constant parameter has a practical effect, is not only a good code practice.

BTW: just for curiosity i put {$IMPLICITEXCEPTIONS OFF} in the program header. Not bad. Be aware that this info is here just for curiosity ;-) .

UPDATE: Using constant arguments also benefits ShortString types. See.

Sunday, October 26, 2008

Status of Virtual Treeview port

It has been more than one year after the last blog update about the Virtual Treeview (VTV) port and some people may be asking what happened with it.

The port is far from dead. In fact, it is fully working under Win32, Gtk1/2 and Qt since, at least, the previous six months. I was just waiting to the Lazarus 0.9.26 release to do an official release. Lazarus 0.9.26 is out, so where is the new VTV?

One of the main features of Lazarus 0.9.26 is the Unicode support for Win32 interface that, in your turn, uses UTF8 encoding. Currently VTV supports Unicode by using UTF-16/WideString and was working fine. After the LCL Unicode switch some encoding conversion problems appeared when iterating with strings returned by databases or LCL controls, so i decided to anticipate the migration to UTF-8 before doing a release.

This task is not trivial and i'll start to work on it only after December, so don't expect an release soon.

This has some advantages:
  • There will be no need to further string types changes (WideString -> String) in applications created with the released component
  • It will be faster since WideString is known to be slower specially under Win32
Anyway if someone is interested in testing the component check the instructions on how to use the svn version here. Be aware that a change from WideString to String type will be necessary in the long run.

Sunday, June 01, 2008

LCL, Gtk2, Pango and Cairo

Introduction

Lately, in the Lazarus mail list, has been a lot of discussion about the performance of LCL/TextOut under Gtk2 and many (erroneous) arguments used derives from the lack of proper knowledge, including from myself. So in this article, i'll try to clear the things a bit.

When Gtk2 was released back in 2002, Pango was one of the Gtk2 main new features. Pango provides support to render Unicode text (encoded in utf8) with advanced layouts. But i was also one of the reasons of the degraded performance of Gtk2 when compared with its predecessor.

Pango has a flexible design allowing to use different backends (Cairo, Xft, Win32, X) to render text. Until version 2.6 Gtk2 used the Xft backend that was replaced, starting from version 2.8, by cairo (a 2D vector drawing library) as the default renderer. At that time the performance dropped even more, but after some work in pango and in cairo, the things got better.

So, the first point to take in consideration when evaluating pango performance is the version of pango and cairo. More on this later.

Testing LCL

In order to get an accurate diagnostic, i wrote an test application that fills an entire window with text using TextOut and compared the results (output quality and time to draw) of the Linux widgetsets (Gtk1, Gtk2, Qt).

Here is the output:




The time to draw an entire screen (average times of 15 iterations):

gtk1: 24ms
gtk2: 150ms
qt: 92ms

The gtk1 widgetset is really fast but it has two drawbacks: the font quality is low and the screen flickers while updating.
The gtk2 widgetset is the slowest loosing to qt by 60%, but in other hand has the sharpest font draw. An important point is that when double buffer is disabled (LCL disables it by default), the screen flickers just like gtk1, but if double buffer is enabled there's no flicker and the screen is updated instantaneous.
The qt widgetset is in the midterm both in quality and in speed. Not sure why qt text looks blurred: if is a configuration or the default font is not so good. There's also no screen flicker since qt is all double buffered.

Gtk2 dissected (almost)

Pango allows more than one engine to be used and there's also the options to draw directly using cairo or the old gdk functions (that use the X11 bitmap fonts). So i wrote an application using direct calls to the gtk2 api. It draws text with default Gtk2 (Pango/Gdk), Pango/Xft, Xft, Cairo, Gdk/X11.

The output (Cairo is equal to Gtk2 and Gdk/X11 is equal to LCL/Gtk1):



The time results:

Gtk2 (Pango/Gdk): 130ms
Cairo: 90ms
Xft: 70ms
Gdk/X11: 12ms
Pango/Xft: 110ms

Gtk2: very close to LCL/Gtk2
Cairo: the same quality of Pango/Gdk (I would be very surprised if was different ;-)) but significantly faster
Xft: Almost two time faster then Pango/Gdk but with lower quality output (An configuration issue?)
Gdk/X11: basically the same output of gtk1. Again really faster but without the screen flicker of gtk1.
Pango/Xft: faster than Pango/Gdk, slower than Xft. Out of option since is not working at all: it draws always at 0,0 coordinates.

Alternatives to pango?

From this tests, using directly Cairo to draw text seems to be a good option to replace Pango: the same quality and faster. But is not that easy. With direct calls to Cairo is needed to do all sort of text position (bidi, alignment) and text styles (underline) manually. Also it would be necessary to do the font selection and loading logic manually in a system specific way,i.e., is necessary different code to Linux/Win32/MacOSX.

Another option is using Xft directly. Aside from the different/worse output, it would be necessary to change the widgetset to retrieve the XftDraw handle for each drawable which is a complex task principally for double buffered controls. All in a system specific way.

Gdk/X11: the quality of output and lack of Unicode support makes a no-no option. At least for me.

Conclusion

Is there a direct/easy/faster replacement for Pango under LCL/Gtk2? No.

Here is necessary to make another question: there's a real need to replace it? Like shown earlier, default Pango is really slow compared to other alternatives, but, principally when double buffer is enabled, there's no visible glitches or general system slowdown. Is also necessary to take in account the increase of code size and complexity in LCL/Gtk2 side to make such change.

Notes

  • The test applications can be found here and here. Is necessary the package chronolog;
  • The test were conducted in a Celeron 1.4, 512MB, with an intel integrated video running Ubuntu 8.04;
  • Upgrading from Ubuntu 7.10 to 8.04 leaded to an significant speed in all widgetsets (Gtk2 250 > 150 / Gtk1 50 > 24ms);
  • I also tested drawing with low level Pango api. I'll post the results later;
  • Win32 took impressive 5ms in the same test Not to be considered since the win32 test was done in another (more powerful) machine;
  • A point that is not directly related to text draw but affects performance of LCL/Gtk2 is the fact that the LCL/Gtk2 test application calls 4 times FcFontSort, while a Gtk2 pure application calls only once. This is an expensive call that takes almost 20% of the application time.

Saturday, March 15, 2008

Reduce memory usage of LCL

To test the conclusions of the last post i modified the field order of some LCL classes to group together Boolean fields.

Here's the return value of the InstanceSize property:

TButton
before: 874 bytes
after: 829 bytes (saved 24, 16 and 5 bytes in TControl, TWinControl and TCustomButton respectively)

TMenuItem
before: 152 bytes
after: 136 bytes

In an application with 100 controls and 20 menu items you save 2400 (considering only TControl memory) and 320 bytes respectively.

Maybe be this is negligible in computers with 1GB or more of RAM, but for mobile platforms it makes a difference.

The patch is here. Have fun!