A few cobwebs, but looks like everything’s still working! Can’t promise a lot more to come, but I have one or two things lined up…
I’ve just noticed RemObjects have released a new Oxygene version together with ‘RemObjects C#’, a C# sister product that was codenamed Hydrogene – check out the announcement here. The improved cross platform support sounds interesting (traditionally, the Oxygene language would differ somewhat depending on the target platform), and in the case of RemObjects C#… well, it seems a competitor is a bit rattled!
Dearie me – read that and you would never guess that when it comes to targeting Android, Delphi and Xamarin are on one side of the fence and Oxygene and RemObjects C# the other, for better or for worse…
And now we get this…
At least Delphi has done generics on OS X since 2011 😉
So… I was finally broken: I created a StackOverflow account. Annoyingly, a certain individual in particular is waaaayy too fast in answering most Delphi questions (genuine expertise + quick on the button = cheating, surely?)… so I started picking off Access ones instead (easy points there – write three lines of trivial SQL, and bingo). Alas, but even for a subject area in which one would expect to find a fair few novices asking novice questions, there remains a certain… priggishness about the fact.
(Actual picture of self-appointed SO
[PS: to the literal-minded, no I don’t actually think David is a ‘cheat’ any more than I think Eric Cartman actually patrols StackOverflow 😉 ]
Honestly, for how many versions now has the following got through?
unit FMX.Types; //... type TGradientPoint = class(TCollectionItem) private FColor: TAlphaColor; FOffset: Single; function GetColor: TAlphaColor; procedure SetColor(const Value: TAlphaColor); public procedure Assign(Source: TPersistent); override; property IntColor: TAlphaColor read FColor write FColor; published property Color: TAlphaColor read GetColor write SetColor; property Offset: Single read FOffset write FOffset nodefault; end; //... procedure TGradientPoint.Assign(Source: TPersistent); begin if Source is TGradientPoint then begin FColor := TGradientPoint(Source).FColor; FOffset := TGradientPoint(Source).FOffset; end else inherited; end; function TGradientPoint.GetColor: TAlphaColor; begin Result := FColor; end; procedure TGradientPoint.SetColor(const Value: TAlphaColor); begin FColor := Value; end;
What am I whinging about you say? This:
- What’s with the weird IntColor/Color duplication? Probably an historical thing… but why wasn’t the IntColor version taken out when the Color version was refactored?
- Why does Color have a getter that just directly returns the value of the backing field?
- Why doesn’t its setter (and Assign) call the Changed method?
- Where’s the Add method for TGradientPoints to cast the TCollection implementation’s result to TGradientPoint?
- Where’s the Update override for TGradientPoints? We want a property change to make a visible difference, right?
Oh, and don’t get me started on how the TGradient property editor is both horrible to use and set up to replace (not complement) what would have been perfectly reasonable default Object Inspector behaviour…
Arnaud Bouchez of Synopse open source fame (mORmot etc.) has written an interesting piece on the ‘nextgen’ compiler that debuted with XE4’s iOS support – check it out.
Back last July, I blogged about terrible example code posted by Stephen Ball, an Embarcadero ‘Product Evangelist’. Ultimately, the critique of Ball’s code was really just a lead-off for pointing out how the same anti-pattern used had appeared prominently in the FMX source too. Happily, XE3 RTM saw most of that removed (though not all of it). However, for reasons I don’t understand, Ball has now proudly turned his blog post into a YouTube video:
Honestly, view it and weep. I expect his defence will be ‘but I’m only illustrating class helpers’, but if so, that would be dubious given he’s already been warned the example makes him look foolish [on his original post, the automated pingback from my blog was accepted, but my actual comment – ‘I’ve just posted a critical (but friendly) commentary here’ – never got past the moderation queue. His reply implied he still read it though]. Moreover, it’s perfectly possible to demonstrate class helpers without writing rubbish – check out the relevant page on Lachlan Gemmell’s TIndex for examples. (*)
That said, the class helper anti-example wasn’t the first time Ball had put out poor code – if you want something just as bad, check out his ‘white paper’ on packaging a FMX form into a DLL or dylib, which was also something he originally put out in the XE2 timeframe and has now recently re-promoted. The example used in it is an image file picker, which is fair enough, but here’s how he writes his exports:
function SelectPicture(AFolder : PChar): PChar; cdecl; var ResultStr : string; Temp: PWideChar; begin ResultStr := ''; try ResultStr := TfrmImages.SelectPicture(AFolder); finally Result := StrAlloc(Length(ResultStr)); Temp := Addr(ResultStr); StrCopy(Result,Temp); end; end; procedure DisposePicture(SelectPictureResult : PChar); cdecl; begin StrDispose(SelectPictureResult); end;
If that doesn’t embody the mentality of ‘It compiles, so ship it!’, I don’t know what does.
(*) PS – the David Glassborow articles linked to on the TIndex are now found here and here – I’ve posted the corrections to the TIndex blog, so hopefully the links might even be fixed by the time you read this post.
PPS – eh?
In FireMonkey/XE2, most platform-specific functionality was filtered through a Platform singleton object. In this, the FMX.Platform unit defined an abstract TPlatform base class, with FMX.Platform.Win and FMX.Platform.Mac providing (private) concrete descendants of it (TPlatformWin and TPlatformMac respectively) that did the actual work. In FireMonkey/XE3, in contrast, the Platform singleton has been removed in favour of a TPlatformServices object that you query different interfaces from, each interface providing a different sort of ‘platform service’ – for example, there’s a clipboard service (IFMXClipboardService), menu service (IFMXMenuService), and so on.
In practice, TPlatformWin and TPlatformMac still exist, and at present, they also implement all the interfaces you can query for when it comes to their particular platform. Nonetheless, in principle, the new setup allows providing an alternative implementation for any given IFMXxxxService interface. For example, if you’ve had enough of the awful stock Mac menu bar code, you can write your own implementation of the IFMXMenuService interface, unregister the default implementation, then register your replacement:
type TMacMenuServiceFix = class(TInterfacedObject, IFMXMenuService) strict private FDefaultImpl: IFMXMenuService; procedure DestroyMenuItem(const AItem: IItemsContainer); //... public constructor Create; end; constructor TMacMenuServiceFix.Create; begin inherited Create; FDefaultImpl := IFMXMenuService( TPlatformServices.Current.GetPlatformService(IFMXMenuService)); TPlatformServices.Current.RemovePlatformService(IFMXMenuService); TPlatformServices.Current.AddPlatformService(IFMXMenuService, Self); end;
In this example I store a reference to the stock implementation in order to delegate to it for methods that I don’t wish to customise.
All in all I think this change a positive one. Nonetheless, the API is – how can I put this – rather ugly! Check out how I retrieve the default implementation of IFMXMenuService above:
FDefaultImpl := IFMXMenuService( TPlatformServices.Current.GetPlatformService(IFMXMenuService));
Notice how I have to type the word ‘service’ *four* times, the phrase ‘PlatformServices’ and ‘IFMXMenuService’ twice each, and the word ‘current’ once yet completely pointlessly – for some reason the person who wrote TPlatformServices doesn’t seem to realise there are things called class vars and class methods. I wonder if he’s related to whoever designed TTimeZone for the XE release…?
That said, wouldn’t it be better if we could just write this instead:
FDefaultImpl := TPlatformService.Get<IFMXMenuService>;
Similarly, in cases where we are open to the possibility there is no default implementation, you have to write code like the following, annoying cast included:
var MenuService: IFMXMenuService; begin if TPlatformServices.Current.SupportsPlatformService( IFMXMenuService, IInterface(MenuService) then
Wouldn’t that be better like this though?
var MenuService: IFMXMenuService; begin if TPlatformService.Available<IFMXMenuService>(MenuService) then
I think so, and whadya know, it took about two minutes to write:
TPlatformService = record class function Available<IntfType: IInterface>( out Service: IntfType): Boolean; static; class function Get<IntfType: IInterface>: IntfType; static; end; uses System.TypInfo; class function TPlatformService.Available<IntfType>( out Service: IntfType): Boolean; var Guid: TGUID; begin Guid := PTypeInfo(TypeInfo(IntfType)).TypeData.Guid; Result := TPlatformServices.Current.SupportsPlatformService( Guid, IInterface(Service)); end; class function TPlatformService.Get<IntfType>: IntfType; var Guid: TGUID; begin Guid := PTypeInfo(TypeInfo(IntfType)).TypeData.Guid; Result := IntfType(TPlatformServices.Current.GetPlatformService( Guid)); end;
[Edit: originally I had ‘TCurrentPlaformService’ rather than ‘TPlatformService’, but as Eric Grange rightly says in the comments, why have the word ‘current’ at all? There’s also an argument for dropping the ‘T’ prefix, but I’ve kept that in to be consistent with the wider D2009+ RTL.]