Shh!!!

Between you and me, I’ve noticed the XE3 FMX demos are up on SourceForge (or at least, were when I last looked!). Actions are in (hurrah!), though the lack of a demo implies frames still aren’t (boo!). A few extra properties on things like TForm look promising in a small-but-useful kind of way, and dare I say it, but there’s the occasional indication that the FMX developers have stopped assuming the language and RTL stopped evolving in 2001 or so. Naturally, the OMG-talk-of-Win8-is-that-all-no-way-must-have-Android-BlackBerry-Qt-I-mean-native-and-a-pony-now-now-NOW!!! brigade won’t be happy, but if this leads to just my expectations of FMX a year ago fulfilled, I will be content.

Advertisements

XE2 update 3 is out

XE2 update 3 is out, and on initial inspection seems to repeat the pattern of the previous updates: useful bugfixes for the core product, but little by way of substantive improvements for FireMonkey, at least if you want to use the Delphi compiler. Naturally, the MSI/InstallAware combo is as ridiculous as ever: on installing, I had a core maxed out for 25 minutes on the ‘validating install stage’, after which the installer did its thing of deleting a file then complaining it couldn’t overwrite it.

Once installed, first thing I checked was whether the blatant memory leaks in Mac.ObjectiveC.pas have been fixed. While the list of bug fixes for update 3 had appropriately lowered my expectations by not mentioning them, my hopes were somewhat raised on seeing the file itself had been updated. Alas, but the extremely trivial task of calling Free after temporary objects have been finished with has not been performed. (As an aside, this task is so trivial even the clueless guy who mans the QC system didn’t robotically demand a sample project after I filed a bug report!) The moral seems to be: if you’re not on the formal beta program, don’t bother filing QC reports for FireMonkey, since even the most basic thing will be ignored. I vaguely live in hope things such as this and the blatant misdeclaration of NSString in Macapi.Foundation.pas will be fixed before XE3, but we’ll see.

Bounded strings using ‘advanced’ records

There was an interesting question on StackOverflow recently, asking whether it is possible to define a string type whose instances have a minimum and maximum length. More exactly, there was an interesting answer to that question, Andreas Rejbrand chipping in with a record-based solution that leverages operator overloading.

While very stimulating, on reflection, I think there is the odd issue with the solution suggested. In particular, instances of the type should be explicitly created, but there’s nothing enforcing that, and the behaviour of the + operator defined is perhaps not very intuitive – if you look back at the original poster’s subsequent edit to his own question, you’ll see him complaining about exceptions that are caused by his assumption that the result of the + operator will take on the bounds of what it is assigned to, rather than of either of the operands (in Andreas’ implementation, the result takes on the bounds of the left hand operand).

These points in mind, here’s my quick attempt at a slightly refined variant – plainly, it’s still indebted to its inspiration though:

unit BoundedString;

interface

uses
  System.SysUtils;
  
{$IFDEF DEBUG}
  {$RANGECHECKS ON}
{$ENDIF}
  
type
  TBoundedString = record
  public type
    TLength = 1..High(Integer);
  strict private
    FMinLength, FMaxLength: TLength;
    FValue: string;
    procedure CheckInitialized;
    function GetMinLength: TLength;
    function GetMaxLength: TLength;
    function GetValue: string;
    procedure SetValue(const S: string);
  public
    constructor Create(AMinLength, AMaxLength: TLength); overload;
    constructor Create(AMinLength, AMaxLength: TLength; const AValue: string); overload;
    class operator Add(const A, B: TBoundedString): string; overload;
    class operator Add(const A: TBoundedString; const B: string): string; overload;
    class operator Add(const A: string; const B: TBoundedString): string; overload;
    class operator Implicit(const S: TBoundedString): string;
    class operator Equal(const A, B: TBoundedString): Boolean;
    class operator NotEqual(const A, B: TBoundedString): Boolean;
    property MinLength: TLength read GetMinLength;
    property MaxLength: TLength read GetMaxLength;
    property Value: string read GetValue write SetValue;
  end;
  
implementation
  
resourcestring
  SNotInitialized = 'A TBoundedString instance requiries explicit creation before use';
  SStringTooSmall = 'String too small for the TBoundedString instance';
  SStringTooBig = 'String too big for the TBoundedString instance';

constructor TBoundedString.Create(AMinLength, AMaxLength: TLength);
begin
  FMinLength := AMinLength;
  FMaxLength := AMaxLength;
  FValue := StringOfChar(' ', AMinLength);
end;

constructor TBoundedString.Create(AMinLength, AMaxLength: TLength;
  const AValue: string);
begin
  Create(AMinLength, AMaxLength);
  SetValue(AValue);
end;

procedure TBoundedString.CheckInitialized;
begin
  if FValue = '' then 
    raise EInvalidOpException.CreateRes(@SNotInitialized);
end;

function TBoundedString.GetMinLength: TLength;
begin
  CheckInitialized;
  Result := FMinLength;
end;

function TBoundedString.GetMaxLength: TLength;
begin
  CheckInitialized;
  Result := FMaxLength;
end;

function TBoundedString.GetValue: string;
begin
  CheckInitialized;
  Result := FValue;
end;

procedure TBoundedString.SetValue(const S: string);
begin
  CheckInitialized;
  if Length(S) < FMinLength then 
    raise ERangeError.CreateRes(@SStringTooSmall);
  if Length(S) > FMaxLength then
    raise ERangeError.CreateRes(@SStringTooSmall);
  FValue := S;
end;

class operator TBoundedString.Add(const A, B: TBoundedString): string;
begin
  Result := A.Value + B.Value;
end;

class operator TBoundedString.Add(const A: TBoundedString; const B: string): string;
begin
  Result := A.Value + B;
end;

class operator TBoundedString.Add(const A: string; const B: TBoundedString): string;
begin
  Result := A + B.Value;
end;

class operator TBoundedString.Equal(const A, B: TBoundedString): Boolean;
begin
  Result := A.Value = B.Value;
end;

class operator TBoundedString.NotEqual(const A, B: TBoundedString): Boolean;
begin
  Result := A.Value <> B.Value;
end;

class operator TBoundedString.Implicit(const S: TBoundedString): string;
begin
  Result := S.Value;
end;

end.

Aside from a bit of renaming, I’ve (a) deliberately left out the ability to assign a string directly to a TBoundedString (instead, the Value property must be set or the constructor [re-]called), (b) enforced calling Create before use, and (c) changed the result type of the + operator overload to string rather than TBoundedString. The last amendment allows the code desired by the StackOverflow questioner to now work:

var
  Str1, Str2, Str3: TBoundedString;
begin
  Str1 := TBoundedString.Create(2, 5, 'pp');
  Str2 := TBoundedString.Create(2, 5, 'aaaa');
  Str3 := TBoundedString.Create(2, 10, Str1 + Str2);
  WriteLn(Str3.Value)
end.

It works pretty nicely I think, and yet… I can’t help thinking that the ‘advanced’ record implementation introduced back in D2006 is only half-finished. While you can create useful things with it, it frequently can’t ‘stand alone’ and requires assitance from a managed type of some sort – in the present example’s case, I rely on the fact a string is initialised to nil (”), which allows me to overload the FValue field’s purpose to be both an ‘initialised or not initialised’ flag and contain the actual data once initialised.

On Delphi 2009’s release back in 2008, Allen Bauer wrote

During the development of Delphi 2009, there were several language features that were dropped in favor of generics and anonymous methods. One such feature was what we called “managed records.” This allowed for some very interesting things where you could now implement a constructor, destructor and several assignment class operators on a record that would be automatically called when the record came into scope, went out of scope, and was assigned to something or something was assigned to it (like another instance of itself).

‘Managed’ records – sounds good! Of course, I’d like Delphi generics taken to another level too, and a few steps developing anonymous methods in the direction of C#-style lambdas would be great as well…

We can expect ‘frequent and regular FireMonkey updates’

sayeth the new Delphi/RAD Studio project manager, echoing both a previous statement of his and similar words by Michael Swindell. Well, we’ll see about that, won’t we? From where I’m looking, FireMonkey is doing an excellent impression of being another Delphi for PHP. Maybe that’s unfair – it’s not like the designer crashes all the time. Rather, the problem is that the framework is no where near ‘finished’ feature-wise. To put it bluntly, FireMonkey in its current state isn’t good enough even for writing a Notepad clone (I know, because I’ve been trying). You can check out Herbert Sauro’s blog for various details (here, also a follow up post here). For my part, here’s a highish-level list of missing features and dubious coding practices, written from the POV of FireMonkey being a VCL substitute on the Mac (since on OS X, that is what it is):

Stuff missing

  • Actions and action lists – I personally found this omission a major disappointment.
  • Frames. [*]
  • Keyboard handling beyond the very basics. E.g., TForm doesn’t have a KeyPreview property and OnKeyDown/Up events, even though implementing them would be a doddle (well, I say that – can the person working on TCommonCustomForm.KeyDown please be told of the existence of case statements?). Also, looking at the source, someone started to port the CM_DIALOGKEY/CM_DIALOGCHAR system (again, something that shouldn’t be difficult, so long as you aren’t fixated on recreating the Windows API message loop), but abandoned the attempt half way through. If you’ve never written a custom VCL control, talk of CM_DIALOGKEY is probably gibberish, but a practical implication is how the Cancel and Default properties of TButton don’t work (be warned the special handling of the ESCAPE key by TCustomCommonForm itself can fool you into not seeing this at first).
  • Any ‘large fonts’ setting on Windows is ignored, and I haven’t found a decent workaround. Another WTF omission for me.
  • Printing on the Mac – there’s stubs but nothing else. In fact, printing on Windows doesn’t work for me either – by the time StartDoc gets called, it fails. Much of the implementation for FMX.Printer.Win.pas has simply been copy and pasted from the VCL TPrinter code. That in itself wasn’t a bad idea, however the splitting out of an abstract base class has necessitated a bit of refactoring which hasn’t been tested properly – try accessing Printer.Fonts without calling Printer.ActivePrinter first, for example, and trace through to find the cause.
  • Property/method reference documentation. In fact, this absence is universal to all the things new to XE2, FMX or otherwise. This is exasperating, and worth repeating: after all the complaints, Embarcadero release a new version with barely any reference help for even the simplest new thing. WTF were the doc team doing for two years? It’s not like they’ve invested time in a DExplore replacement, or fixing blatant usability bugs in the XE help (e.g. the checkbox for hiding C++ declarations not working, and how previously separate Delphi reference entries have been combined into mini-essays, causing the typical user to think F1 brings up useless results when it actually isn’t). The lack of documentation for the new stuff us then made worse by unit scoping (i.e., the prefixing of standard units with a ‘namespace’ – something in itself I quite like BTW), which is rarely acknowledged by the help files, and sometimes confuses them.
  • A TRichEdit-type control. As the FireMonkey TMemo is buggy as hell, and writing a FireMonkey TRichEdit will require a level of dedication far higher than most other controls, I don’t expect to see one any time soon. In fact, given there’s so much work to be done across FireMonkey generally, I think it would be a waste of time for Embarcadero to devote resources to a TRichEdit implementation of their own – if FireMonkey becomes good enough for real work, then a third party or two will come along to fill the gap.

Dubious coding styles and interface decisions

By ‘dubious’, I mean relative to FireMonkey being a core standard library going forward, though frankly, I would be inclined to criticise some of these things even when found in a custom visual framework developed and sold by a talented indie:

  • The Caption property is now Text… except when it isn’t (TForm still has a Caption property; TStringColumn has a Header property).
  • The FireMonkey TFont.Size property has a slightly different basis to the VCL TFont.Size property. Why so? The ‘points’ used by the VCL were already device independent.
  • The Margins and Padding properties have had their meanings switched over, again causing needless confusion.
  • For a would-be core standard library, there is an irritating laziness about aspects of the FMX source files’ coding style. For example, instead of declaring virtual protected methods whose sole role is to raise events, events are raised directly, sometimes even by child controls.
  • Naturally, the unit-wide visibility of ‘private’ sections is liberally exploited, allowing a tight interconnection between classes that is impossible for descendants to hook into. May I suggest a policy that requires use of ‘strict private’…?
  • A case in point: TTreeView has no OnExpanding/OnExpanded events, which means you can’t ‘lazy load’ tree nodes, amongst other things. If TTreeItem.SetIsExpanded were virtual, this could be worked around. However, it isn’t, so you’re left with editing the original source if you want this functionality.
  • By default, no control has the focus when a form is first shown. This contrasts with the VCL, where the first tabable control is given the focus if nothing else has explicitly been set up for it. Perhaps this was thought better behaviour, but it has the big drawback that you have to remember to explicitly set the ActiveControl property of a form, something that the implementors of InputQuery (for example) forgot about.
  • The forms CreateMessageDialog create are just ugly, sorry – they get a tiny font, massive, non-standard icons, and don’t look anything like standard message boxes on any of XP, Vista/7, or OS X. The least the maintainers could do is to add a lighter background to the top two thirds of the form… (Also, while we’re on the subject of small fonts: the Vista/W7 standard is 9 pts, not 8. OS X seems similar to my eyes, if less consistent, but the FMX stock styles appear to use 8 pts, or the nearest equivalent.)
  • Much of FMX.Dialogs.pas and its support code in FMX.Platform.Win.pas has been copy and pasted from Vcl.Dialogs.pas. Like with the printers situation, that isn’t so bad in itself, but I wonder whether it would be better to redo the interface of the dialog components to strip Windows-isms. (As an aside: TPageSetupDialog – apart from only being implemented on Windows, and with an interface that hasn’t been adapted at all for cross platform purposes – causes a weird bug when created prior to the application’s main form being completely initialised: the form is not foregrounded when first shown. The workaround is to only instantiate TPageSetupDialog when required.)

If you’re reading this and thinking of saying ‘have you QC’ed that?’, don’t bother – I’m not a beta tester. I may QC non-FireMonkey things, which are at least genuine bugs or oversights (see what TZipFile writes in terms of date/time stamps when running on a Mac, for example). The FireMonkey issues, in contrast, stem from having released something on the pretence it is a workably ‘finished’ product when it patently isn’t. Until it receives the updates promised, don’t fall for the line it is a ‘version 1.0 product’ (‘it’ meaning FireMonkey – 64 bit seems to be another, happier matter), because that’s a lie as things stand, and a stupidly short-termist lie at that – you’re being asked to pay good money for the privilege of seeing a mere technical preview of a ‘version 1.0 product’.

[*] Admittedly, this one I’m not too bothered about myself, though if and when FireMonkey acquires frames, reimagining them on the lines of VB/WinForms-style user controls would be nice (unlike VCL-style frames, VB/WinForms user controls keep sub-control references and event handlers private).

Interesting…

Most of it’s just a copy of the XE (or should I say XE1?) demo tree, but a couple are new:

http://radstudiodemos.svn.sourceforge.net/viewvc/radstudiodemos/branches/RadStudio_XE2/Delphi/

No EXEs though!

I’ll leave the Sovietology to a certain frothy blogger-cum-team of bloggers (if said blogger is reading this though, do please ensure your minions spell my name correctly, regardless of whether you or they are going to slightly misrepresent what I say. Thanks!).

[Update, a few hours later: and they’re gone!]