A Delphi propertybag
A long, long time ago, way back in the previous century, I often had to adjust a Visual Basic project my company maintained. Going from object-pascal to VB was more than a little debilitating; Visual Basic was not a compiled language like Delphi is, and it lacked more or less every feature you needed to produce good software.

I could probably make a VB clone using Delphi pretty easily. But I think the world has experienced enough suffering, no need to add more evil to the universe
Having said that, I have always been a huge fan of Basic (it was my first language after all, it’s what schools taught in the 70s and 80s). I think it was a terrible mistake for Microsoft to retire Basic as a language, because it’s a great way to teach kids the fundamentals of programming.
Visual Basic is still there though, available for the .Net framework, but to call it Basic is an insult of the likes of GFA Basic, Amos Basic and Blitz Basic; the mighty compilers of the past. If you enjoyed basic before Microsoft pushed out the monstrosity that is Visual Basic, then perhaps swing by GitHub and pick up a copy of BlitzBasic? BlitzBasic is a completely different beast. It compiles to machine-code, allows inline assembly, and has been wildly popular for game developers over the years.
A property bag
The only feature that I found somewhat useful in Visual Basic, was an object called a propertybag. It’s just a fancy name for a dictionary, but it had a couple of redeeming factors beyond lookup ability. Like being able to load name-value-pairs from a string, recognizing datatypes and exposing type-aware read/write methods. Nothing fancy but handy when dealing with database connection-strings, shell parameters and the like.
So you could feed it strings like this:
first=12;second=hello there;third=3.14
And the class would parse out the names and values, stuff it in a dictionary, and you could easily extract the data you needed. Nothing fancy, but handy on rare occasions.
A Delphi version
Im mostly porting code from Delphi to Oxygene these days, but here is my Delphi implementation of the propertybag object. Please note that I haven’t bothered to implement the propertybag available in .Net. The Delphi version below is based on the Visual Basic 6 version, with some dependency injection thrown in for good measure.
unit fslib.params; interface {.$DEFINE SUPPORT_URI_ENCODING} uses System.SysUtils, System.Classes, Generics.Collections; type (* Exceptions *) EPropertybag = class(exception); EPropertybagReadError = class(EPropertybag); EPropertybagWriteError = class(EPropertybag); EPropertybagParseError = class(EPropertybag); (* Datatypes *) TPropertyBagDictionary = TDictionary ; IPropertyElement = interface ['{C6C937DF-50FA-4984-BA6F-EBB0B367D3F3}'] function GetAsInt: integer; procedure SetAsInt(const Value: integer); function GetAsString: string; procedure SetAsString(const Value: string); function GetAsBool: boolean; procedure SetAsBool(const Value: boolean); function GetAsFloat: double; procedure SetAsFloat(const Value: double); function GetEmpty: boolean; property Empty: boolean read GetEmpty; property AsFloat: double read GetAsFloat write SetAsFloat; property AsBoolean: boolean read GetAsBool write SetAsBool; property AsInteger: integer read GetAsInt write SetAsInt; property AsString: string read GetAsString write SetAsString; end; TPropertyBag = Class(TInterfacedObject) strict private FLUT: TPropertyBagDictionary; strict protected procedure Parse(NameValuePairs: string); public function Read(Name: string): IPropertyElement; function Write(Name: string; Value: string): IPropertyElement; procedure SaveToStream(const Stream: TStream); procedure LoadFromStream(const Stream: TStream); function ToString: string; override; procedure Clear; virtual; constructor Create(NameValuePairs: string); virtual; destructor Destroy; override; end; implementation {$IFDEF SUPPORT_URI_ENCODING} uses system.NetEncoding; {$ENDIF} const cnt_err_sourceparameters_parse = 'Failed to parse input, invalid or damaged text error [%s]'; cnt_err_sourceparameters_write_id = 'Write failed, invalid or empty identifier error'; cnt_err_sourceparameters_read_id = 'Read failed, invalid or empty identifier error'; type TPropertyElement = class(TInterfacedObject, IPropertyElement) strict private FName: string; FData: string; FStorage: TPropertyBagDictionary; strict protected function GetEmpty: boolean; inline; function GetAsInt: integer; inline; procedure SetAsInt(const Value: integer); inline; function GetAsString: string; inline; procedure SetAsString(const Value: string); inline; function GetAsBool: boolean; inline; procedure SetAsBool(const Value: boolean); inline; function GetAsFloat: double; inline; procedure SetAsFloat(const Value: double); inline; public property AsFloat: double read GetAsFloat write SetAsFloat; property AsBoolean: boolean read GetAsBool write SetAsBool; property AsInteger: integer read GetAsInt write SetAsInt; property AsString: string read GetAsString write SetAsString; property Empty: boolean read GetEmpty; constructor Create(const Storage: TPropertyBagDictionary; Name: string; Data: string); overload; virtual; constructor Create(Data: string); overload; virtual; end; //############################################################################# // TPropertyElement //############################################################################# constructor TPropertyElement.Create(Data: string); begin inherited Create; FData := Data.Trim(); end; constructor TPropertyElement.Create(const Storage: TPropertyBagDictionary; Name: string; Data: string); begin inherited Create; FStorage := Storage; FName := Name.Trim().ToLower(); FData := Data.Trim(); end; function TPropertyElement.GetEmpty: boolean; begin result := FData.Length < 1; end; function TPropertyElement.GetAsString: string; begin result := FData; end; procedure TPropertyElement.SetAsString(const Value: string); begin if Value FData then begin FData := Value; if FName.Length > 0 then begin if FStorage nil then FStorage.AddOrSetValue(FName, Value); end; end; end; function TPropertyElement.GetAsBool: boolean; begin TryStrToBool(FData, result); end; procedure TPropertyElement.SetAsBool(const Value: boolean); begin FData := BoolToStr(Value, true); if FName.Length > 0 then begin if FStorage nil then FStorage.AddOrSetValue(FName, FData); end; end; function TPropertyElement.GetAsFloat: double; begin TryStrToFloat(FData, result); end; procedure TPropertyElement.SetAsFloat(const Value: double); begin FData := FloatToStr(Value); if FName.Length > 0 then begin if FStorage nil then FStorage.AddOrSetValue(FName, FData); end; end; function TPropertyElement.GetAsInt: integer; begin TryStrToInt(FData, Result); end; procedure TPropertyElement.SetAsInt(const Value: integer); begin FData := IntToStr(Value); if FName.Length > 0 then begin if FStorage nil then FStorage.AddOrSetValue(FName, FData); end; end; //############################################################################# // TPropertyBag //############################################################################# constructor TPropertyBag.Create(NameValuePairs: string); begin inherited Create; FLUT := TDictionary.Create(); NameValuePairs := NameValuePairs.Trim(); if NameValuePairs.Length > 0 then Parse(NameValuePairs); end; destructor TPropertyBag.Destroy; begin FLut.Free; inherited; end; procedure TPropertyBag.Clear; begin FLut.Clear; end; procedure TPropertyBag.Parse(NameValuePairs: string); var LList: TStringList; x: integer; LId: string; LValue: string; LOriginal: string; {$IFDEF SUPPORT_URI_ENCODING} LPos: integer; {$ENDIF} begin // Reset content FLUT.Clear(); // Make a copy of the original text LOriginal := NameValuePairs; // Trim and prepare NameValuePairs := NameValuePairs.Trim(); // Anything to work with? if NameValuePairs.Length > 0 then begin {$IFDEF SUPPORT_URI_ENCODING} // Check if the data is URL-encoded LPos := pos('%', NameValuePairs); if (LPos >= low(NameValuePairs) ) and (LPos 0 then Begin (* Populate our lookup table *) LList := TStringList.Create; try LList.Delimiter := ';'; LList.StrictDelimiter := true; LList.DelimitedText := NameValuePairs; if LList.Count = 0 then raise EPropertybagParseError.CreateFmt(cnt_err_sourceparameters_parse, [LOriginal]); try for x := 0 to LList.Count-1 do begin LId := LList.Names[x].Trim().ToLower(); if (LId.Length > 0) then begin LValue := LList.ValueFromIndex[x].Trim(); Write(LId, LValue); end; end; except on e: exception do raise EPropertybagParseError.CreateFmt(cnt_err_sourceparameters_parse, [LOriginal]); end; finally LList.Free; end; end; end; end; function TPropertyBag.ToString: string; var LItem: TPair; begin setlength(result, 0); for LItem in FLut do begin if LItem.Key.Trim().Length > 0 then begin result := result + Format('%s=%s;', [LItem.Key, LItem.Value]); end; end; end; procedure TPropertyBag.SaveToStream(const Stream: TStream); var LData: TStringStream; begin LData := TStringStream.Create(ToString(), TEncoding.UTF8); try LData.SaveToStream(Stream); finally LData.Free; end; end; procedure TPropertyBag.LoadFromStream(const Stream: TStream); var LData: TStringStream; begin LData := TStringStream.Create('', TEncoding.UTF8); try LData.LoadFromStream(Stream); Parse(LData.DataString); finally LData.Free; end; end; function TPropertyBag.Write(Name: string; Value: string): IPropertyElement; begin Name := Name.Trim().ToLower(); if Name.Length > 0 then begin if not FLUT.ContainsKey(Name) then FLut.Add(Name, Value); result := TPropertyElement.Create(FLut, Name, Value) as IPropertyElement; end else raise EPropertybagWriteError.Create(cnt_err_sourceparameters_write_id); end; function TPropertyBag.Read(Name: string): IPropertyElement; var LData: String; begin Name := Name.Trim().ToLower(); if Name.Length > 0 then begin if FLut.TryGetValue(Name, LData) then result := TPropertyElement.Create(LData) as IPropertyElement else raise EPropertybagReadError.Create(cnt_err_sourceparameters_read_id); end else raise EPropertybagReadError.Create(cnt_err_sourceparameters_read_id); end; end.
Do you really use plain constants for your error messages? No resource strings? Why?
Try changing it and you will see why
Hi Jon, thanks for the code, seems like a very useful unit. Just one thing, the syntax highlighter is stripping pairs & whatever is between. Best regards
Hi! Yes I know, it keeps happening for some reason. Seems to be a curse for WordPress. Ill try to place everything on git soon, its pretty tiredsome having to copy/paste 100 times and still lose characters
Uploaded the file to Delphi Developer on facebook
Unable to use the code copied here and I strongly suspect it is someting gone wrong with copy and paste from here. Can you please post it on github? I do not want to signup on facebook: this may sound crazy, but I strongly hate facebook.
[quote]I could probably make a VB clone using Delphi pretty easily. But I think the world has experienced enough suffering, no need to add more evil to the universe[/quote]
Lately it seems you have been bragging a lot about your skill as a programming tool developer.
Just for your kind info till date no one has been able to build an IDE as productive as VB6 IDE. So much so that even Delphi had tried its best to duplicate the user experience of VB IDE in Delphi IDE right from the very first version.
Let alone the language and its COM based features, and the smoothness with which it handles ActiveX objects and controls is still missing even in .NET also.
I am sure you cannot even build a IDE which has 50% of its features let alone clone it to the ditto. Because if you could you would have implements some of its productive features like code formatting, properties editor in a dialog box, etc. in SMS.
I challenge you openly to clone VB6 to the ditto….
Is there something wrong with you head? Or are you just naturally ignorant? First of all, i didnt work on the IDE, i worked on the RTL. I created an IDE prototype that worked. But there were 7 developers involved, and they managed to break pretty much everything i put into the prototype. Secondly, are you seriously posing that Delphi was less productive than VB? If that is the case then you need your head checked.,
Im starting a new IDE next year, so you will have to wait for that. Tz.. 10 year fanboyism ha ha
Yes in the initial years Delphi was definitely less productive then VB. It is a different story today as VB is considered to be a dead product.
Any good compile without a visual IDE is useless in today’s world.
I develop profusely with Delphi along with other tools like WinDev, LiveCode, etc. So when I compare Delphi with these tools it looks very vanilla and is still not developer friendly even today. Let me give an example: You cannot scroll the Design Form with you mouse scroll wheel while may other tools that I use provide this very basic feature.
Personally I like Pascal as a language though.
As of cloning VB6 run time is concerned. I can say with confident that you still cannot do it because many great developers have tried their hands and failed miserably.
Some did get quite close to VB like:
RapidQ/Envelop Basic by William Yu (who later was employed by RealBsic/Xojo as he sold the source code)
Jabaco (the JAVA code generator) [http://www.vbforums.com/showthread.php?565238-Jabaco-Java-VB6-clone]
Currently B4J which is not VB clone per see but the language is very close to VB is making buzz in the VB fan clubs.
Out of curiosity, why do you think many people failed to create an IDE like in VB6? What’s the uniqueness of the IDE that can not be reproduced easily?
I am asking cause I am looking at different IDEs recently.
FWIW, I’ve done a couple of projects in Xojo and the IDE is fun. But I find it very mac-ish
There are many unique features that are still not implemented in any other IDE.
Like for example:
1. Perfect and intuitive code completion by this I mean if there is only one single option in code completion VB6’s IDE will just auto complete it instead of showing a list from which one has to either click by mouse or hit enter to auto complete.
2. Auto code formatting (adding space where necessary, automatically changing case of built in functions, etc.) as one enters code is still better then any of modern IDEs.
3. When debugging one can put a break point and when the IDE stops at break point one can easily go to any code below the break point and change it and the best part is that these changes are immediately applied and one does not have to stop the app and restart from scratch. This really helps a lot in fine tuning ones code progressively.
4. Add/referring to ActiveX controls or DLLs is just a breeze. Just add them and they will show in tool box but in case of all other IDEs including DotNet & Delphi when an ActiveX is required it has to be imported meaning it will create an import module and then that module will have to be included in ones code, etc. so the process of using COM in other languages and IDEs is a pain in the butt at best!
5. On can use a .tlb file to access a static DLL as though is as part of the project. There is not need to declare the functions/procedures exported in the static DLL!!!
Having “worked” with VB6 for a few years I beg to differ. With VB6 I was a lot less productive than with Delphi 5/6 or 7 (not sure which one was the current release back then). I moved on an never looked back.
Hello,
The code listed here seem to be affected by HTML formatting and does not work
with Copy/Paste.
Is there any downloadable version?
Indeed, wordpress is not pascal friendly.
The files are available on Facebook, in the Delphi Developer group. Just go to the “files” section on the group, and you should see it there. Thanks!