Quantcast
Channel: twm's blog
Viewing all 652 articles
Browse latest View live

TMemo vs. Esc key

$
0
0

If you have ever had a TMemo on a modal form with an OK and Cancel button, where the latter has its Cancel property set to true, you might have found that Esc does not cancel the dialog while the memo has got the focus.

According to David Heffernan that’s because the VCL tells it to use all keys but it then doesn’t handle Esc. David also provides a fix for this via an interposer class. While this works it means that you have to add this interposer to every form in your application.

If you are already using Andras Hausladen’s excellent VCL Fix Pack there is another option: Add David’s fix to the hook installed by InitContextMenuFix (works only for Delphi 6-2007) or add a special hook for the fix only (for later Delphi versions where the context menu bug has been fixed).

So far I have only done the first. Look for the code between the two “TMemo Esc Fix” comments

procedure TContextMenuFixWinControl.DefaultHandler(var Message);
type
  TDefHandler = procedure(Self: TControl; var Message);
begin
  if HandleAllocated then
  begin
    with TMessage(Message) do
    begin
      { Here was the WM_CONTEXTMENU Code that is not necessary because
        DefWndProc will send this message to the parent control. }

      { Keep the odd bahavior for grids because everybody seems to be used to it. }
      if (Msg = WM_CONTEXTMENU) and (Parent <> nil) and (Parent is TCustomGrid) then
      begin
        Result := Parent.Perform(Msg, WParam, LParam);
        if Result <> 0 then Exit;
      end;

      // Begin - TMemo Esc Fix
      if (Msg = WM_GETDLGCODE) and (Parent <> nil) and Self.InheritsFrom(TCustomMemo) then
      begin
        //inherited DefaultHandler(Message);
        TDefHandler(@TControl.DefaultHandler)(Self, Message);
        Result := Result and not DLGC_WANTALLKEYS;
        Exit;
      end;

      if (Msg = CM_WANTSPECIALKEY) and (Parent <> nil) and Self.InheritsFrom(TCustomMemo) then
      begin
        case TCMWantSpecialKey(Message).CharCode of
        VK_ESCAPE:
          begin
            Result := 0;
            Exit;
          end;
        VK_RETURN, VK_EXECUTE, VK_CANCEL:
          begin
            Result :=1;
            Exit;
          end;
        end;
      end;
      // End - TMemo Esc Fix

      case Msg of
        WM_CTLCOLORMSGBOX..WM_CTLCOLORSTATIC:
          Result := SendMessage(LParam, CN_BASE + Msg, WParam, LParam);
        CN_CTLCOLORMSGBOX..CN_CTLCOLORSTATIC:
          begin
            SetTextColor(WParam, ColorToRGB(Font.Color));
            SetBkColor(WParam, ColorToRGB(Brush.Color));
            Result := Brush.Handle;
          end;
      else
        if Msg = RM_GetObjectInstance then
          Result := LRESULT(Self)
        else
          Result := CallWindowProc(DefWndProc, Handle, Msg, WParam, LParam);
      end;
      if Msg = WM_SETTEXT then
        SendDockNotification(Msg, WParam, LParam);
    end;
  end
  else
    //inherited DefaultHandler(Message);
    TDefHandler(@TControl.DefaultHandler)(Self, Message);
end;

What it does is basically the same as David’s interposer class:

  1. It handles WM_GETDLGCODE by calling the inherited message handler (in this case: The original WindowProc) and removing the DLGC_WANTALLKEYS bit from the result.
  2. It handles CM_WANTSPECIALKEY, checks for VK_ESCAPE for which it sets Result to 0, meaning “I’m not interested in this key.”, and VK_RETURN, VK_EXECUTE, VK_CANCEL setting Result to 1 meaning “I’m interested in these keys.”.

Andy’s code hooks TWinControl.DefaultHandler so the code above gets called for all TWinControls, but we don’t want to meddle with the Esc key handling of other controls. There was a small problem with checking whether the control is actually a Memo. “Self is TMemo” did not work because TContextMenuFixWinControl.DefaultHandler is a class method, so the compiler thinks that Self is a class rather than a class instance and didn’t want to compile this code. Changing the condition to Self.InheritsFrom(TCustomMemo) did the trick.


Opening an explorer window from the folder select dialog

$
0
0

Ever used a program that showed one of the folder select dialogs and you wanted to open a normal explorer window showing that folder? There is no button for that and no entry in the popup menu, but you can add one:

Create a new shortcut in

C:\Users\&lg;yourname>\AppData\Roaming\Microsoft\Windows\SendTo

Enter “Explorer” as the location and name of the shortcut.

Done.

Now you have an “Explorer” entry the Send To submenu of all popup menus which in the case of a folder opens a new explorer instance for this folder.

SendToExplorer

Updated GExperts Documentation

$
0
0

I added some new pages to my blog to document all the changes and improvements I have made to GExperts that so far are not in the official documentation.

GEXperts-SearchPathEnhancements2

I probably still missed a few…

Default color of a hint window

$
0
0

Since it doesn’t seem to be documented anywhere and at least in Delphi 2007 there is no constant for it:

The default background color of a hint window is

const
  clHintColor = $80FFFF;

(Taken from THintWindow.Create)

GExperts 1.38 experimental twm 2016-07-24 released

$
0
0

This is another test release before Erik is going to do an official 1.39 release. Please report any bugs you may find (preferentially in the GExperts community on Google+ or the bug tracker on SourceForge)

Again, I have built installers for each Delphi version. These installers should install everything that is necessary, including the files for the Code Formatter.

Apart from several bugfixes I have heavily added to the improvements for the search path dialog.

Head over to the Experimental GExperts page to download it.

Specifying a date for MS SQL Server

$
0
0

Today I had the “pleasure” to fix a problem in a customer’s SQL server database where some records had wrong values in date fields. The theory is quite simple. Find out which records are affected (there were only 7 of them) and correct the problem with a statement like this:

update tablename
set datefield='2008-02-14'
where someotherfield='somestringvalue'

Unfortunately the SQL server complained that it could not convert the string to a date. Google didn’t really help much because apparently it works like this for everybody else but me. But I found a hint how to test it quite simply:

select isdate('2008-02-14')

After a bit of try and error if found the problem:

The server did not actually assume a date of the form yyyy-mm-dd to be ISO 8601 as every human probably would. It assumed it to be yyyy-dd-mm so

select isdate('2008-14-02')

worked.

Americans! With yy/dd/mm You have given the world the most stupid date format ever, but Microsoft managed to top even that with yyyy-dd-mm.

So, eventually I used

update tablename
set datefield='2008-14-02'
where someotherfield='somestringvalue'

and it worked.

EDIT:

As Stefan Glienke pointet out in this Google+ post the order of y,m,d in a date is configurable with SET DATEFORMAT

So apparently on the machine I was working on it was set to

SET DATEFORMAT ydm

and I could have fixed the problem with

SET DATEFORMAT ymd

Colin Wilson’s XN Resource Editor

$
0
0

A really useful tool that I have used for many years, was Colin Wilson’s XN Resource Editor.

XNResourceEditor

Before he apparently disappeared from the face of the earth, Colin was so kind to release the source code under the MPL on his homepage. Unfortunately this page now is also gone, only a few snapshots in the Internet Archive remain. Even more unfortunately the source code archive is not complete. It’s missing quite a few components and utility units.

I have started to try and recompile XN Resource Editor several times, usually because I stumbled upon yet another copy of its source code somewhere on the Internet, in the vain hope that this time it might be complete. The latest was in Stefan Sundin’s github repository. Unfortunately it turned out that – even though he claims that the repository contains all prerequisites – several files were missing, in particular the VirtualTreeView extension written by Colin.

This time I went back to the Internet Archive and tried to find a snapshot that contains the files necessary. It turned out that they were all there, just not in the same snapshot.

To cut a long story short: I managed to dig the complete source code of XN Resource Editor out of the Internet Archive of his homepage. While I am not 100% sure that it is the latest version, it at least compiles and seems to work. I put everything into the svn repository of the new XN Resource Editor project on SourceForge. It contains everything necessary, including the TNTComponents Suite and Virtual Treeview.

There is a !Readme.txt file describing the steps necessary to compile the program (You also need Delphi 2006 for that). Or, if you just need the executable and can’t find it anywhere else, look here.

New expert: Warn directives


New Expert IFDEF

Compiling GExperts

GExperts IFDEF Expert support for include files

$
0
0

The GExperts IFDEF expert now supports include files.

GExperts IFDEF Expert

For each include file in the current unit it displays an additional tab containing all symbols defined in that include file with {$DEFINE }, {$UNDEF } and the usual disabled notation of these {.$DEFINE }, {.$UNDEF }. It searches for the include files in the search path of the project and – like the compiler – uses the first one it finds.

The post GExperts IFDEF Expert support for include files appeared first on twm's blog.

GExperts 1.38 experimental twm 2016-09-18 released

$
0
0

Since apparently Erik needs more time for the official GExperts 1.39 release, here is another experimental test release. Please report any bugs you may find (preferentially in the GExperts community on Google+ or the bug tracker on SourceForge)

Again, I have built installers for each Delphi version. These installers should install everything that is necessary, including the files for the Code Formatter.

Apart from several bugfixes I have added three new experts:

  • There is the Warn Directives Expert which allows you to select a warn directive like SYMBOL_PLATFORM and insert it as {$WARN SYMBOL_PLATFORM OFF}, {$WARN SYMBOL_PLATFORM ON}, {$WARN SYMBOL_PLATFORM DEFAULT} or {$WARN SYMBOL_PLATFORM ERROR}.
  • Then there is the IfDEF Expert which alows you to insert various IFDEF, IF or IFOPT directives. It automatically scans for include files in the current unit and offers the conditional defines from there as well. You can also add another include file from the search path and if you add directives for conditional defines from it, the expert will automatically add an {$I <filename>} directive to the unit.
  • And last but not least there is the Add to Formatter Capitalization Expert which allows you to add the current identifier to the code formatter’s captitalization file without clicking your way through the various configuration dialog levels.

GExperts IFDEF Expert

Head over to the Experimental GExperts page to download it.

The post GExperts 1.38 experimental twm 2016-09-18 released appeared first on twm's blog.

Hotfix for GExperts crashing with Delphi 10.1 update 1

$
0
0

GExperts 1.38 experimental twm 2016-09-18 has a few issues with Delphi / Rad Studio 10.1 Berlin Update 1. Simply recompiling the DLL made them go away for me, so I provide the recompiled DLL as a hotfix for this release. Go the release page to get it.

First steps with REST and JSON

$
0
0

I must admit that I haven’t done much programming regarding the web. There was an attempt to write an RSS reader some time back and there is my virtual radio alarm clock (with which I’m currently listening to SWR3). But this is my first try to do something with REST and JSON. The server side is provided by the Delphi Dabbler SWAG archive the client is written with Delphi 10.1 Berlin.

At first I tried to make sense from the tutorial provided by Embarcadero but I found it rather confusing and it stops exactly when actually trying to access the data returned in a meaningful way.

So, I threw away the TRESTxxx components I had put on the form and went back to some actual programming rather than dropping components and setting properties without understanding what I am doing. Here is what I ended up with:

swagbrowser

The program itself consists of two parts: Requesting data from the server and interpreting its answers. Requests are sent via http using a TidHttp object which is dynamically created and destroyed for each request.

procedure Tf_DelphiDabblerSwag.doRequest(const _Command: string; out _Response: TJSONObject);
var
  HTTP: TIdHTTP;
  JSON: string;
begin
  HTTP := TIdHTTP.Create(nil);
  try
    JSON := HTTP.Get('http://swag.delphidabbler.com/api/v1/' + _Command);
//    m_Result.Lines.text := JSON;
  finally
    FreeAndNil(HTTP);
  end;
  _Response := TJSONObject.ParseJSONValue(JSON) as TJSONObject;
end;

It expects a Command which is simply sent to the server by appending it to the API url as described in the documentation.

It returns a TJSONObject which is then interpreted by the caller. (I don’t do much error handling here, it’s merely a learning experiment.)

The API is simple, it knows only 4 different commands:

  • categories
  • snippet
  • snippet-count
  • snippets

I implemented each of them as a method:

categories

procedure Tf_DelphiDabblerSwag.RequestCategories(_Categories: TObjectList<TIdTitleObj>);
var
  JObj: TJSONObject;
  JCategories: TJSONArray;
  i: Integer;
  JCategory: TJSONObject;
  Category: TIdTitleObj;
begin
  doRequest('categories', JObj);
  try
    m_Result.Lines.Add('status: ' + JObj.GetValue('status').Value);
    m_Result.Lines.Add('command: ' + JObj.GetValue('command').Value);
    JCategories := JObj.Get('categories').JsonValue as TJSONArray;
    for i := 0 to JCategories.Count - 1 do begin
      JCategory := JCategories.Items[i] as TJSONObject;
//        m_Result.Lines.Add(Format('Category: (ID: %s, Title: %s', [JCategory.GetValue('id').Value, JCategory.GetValue('title').Value]));
      Category := TIdTitleObj.Create(JCategory.GetValue('id').Value, JCategory.GetValue('title').Value);
      _Categories.Add(Category);
    end;
  finally
    FreeAndNil(JObj);
  end;
end;

The first thing the program does is getting a list of all categories. For that it sends the “category” command to the server and interprets its response.

{
  "status":"ok",
  "command":"categories",
  "categories":[
    {
      "id":"ANSI",
      "title":"ANSI Control & Ouput"
    },
    {
      "id":"ARCHIVES",
      "title":"Archive Handling"
    },

    ... more category elements ...

  ]
}

All categories are stored in a TObjectList and returned to the caller. It took me a while to figure out how to access the array of categories: You access a name value entry and type cast it to TJSONArray. After I understood that, everything else was easy.

snippets

Next is getting a list of all snippets of a category.

procedure Tf_DelphiDabblerSwag.RequestSnippets(const _CategoryId: string; _Snippets: TObjectList<TIdTitleObj>);
var
  JObj: TJSONObject;
  i: Integer;
  JSnippets: TJSONArray;
  JSnippet: TJSONObject;
  Snippet: TIdTitleObj;
begin
  _Snippets.Clear;
  doRequest('snippets/' + _CategoryId + '?fields=id,title', JObj);
  m_Result.Lines.Add('status: ' + JObj.GetValue('status').Value);
  m_Result.Lines.Add('command: ' + JObj.GetValue('command').Value);
  JSnippets := JObj.Get('snippets').JsonValue as TJSONArray;
  for i := 0 to JSnippets.Count - 1 do begin
    JSnippet := JSnippets.Items[i] as TJSONObject;
    Snippet := TIdTitleObj.Create(JSnippet.GetValue('id').Value, JSnippet.GetValue('title').Value);
//    m_Result.Lines.Add(Format('Snippet: (ID: %s, Title: %s', [JSnippet.GetValue('id').Value, JSnippet.GetValue('title').Value]));
    _Snippets.Add(Snippet);
  end;
end;

The “snippets” command needs a some parameters:

  • The id of the category
  • The fields we are interested in

They are appended to the command.

Then again, we simply parse the result and return it in a TObjectList.

{
  "status":"ok",
  "command":"snippets",
  "snippets":[
    {
      "id":491,
      "title":"Getting the Line number in a memo Field",
    },
    {
      "id":492,
      "title":"Capturing the Desktop to a form",
    },
    {
      "id":493,
      "title":"File Copying in DELPHI",
    },{
      "id":494,
      "title":"File Manager Drag\/Drop",
    }
  ]
}

snippet

The last part is getting a snippet’s text.

procedure Tf_DelphiDabblerSwag.RequestSnippetText(const _SnippetId: string; out _Text: string);
var
  JObj: TJSONObject;
  JSnippet: TJSONObject;
begin
  doRequest('snippet/' + _SnippetId + '?fields=source_code', JObj);
  try
    m_Result.Lines.Add('status: ' + JObj.GetValue('status').Value);
    m_Result.Lines.Add('command: ' + JObj.GetValue('command').Value);
    JSnippet := JObj.GetValue('snippet') as TJSONObject;
    m_Snippet.Lines.Text := JSnippet.GetValue('source_code').Value;
  finally
    FreeAndNil(JObj);
  end;
end;

The “snippet” command requires a snipped id and we also must specify the fields we want to get. In this case we only want the source_code.

{
  "status":"ok",
  "command":"snippet",
  "snippet":{
    "source_code":"\r\n{This way uses a File stream.}\r\nProcedure ..."
  }
}

The response is then parsed and returned as a string. Note that “snippet” is an object, so in order to get the source code we need to type cast the snippet value to a TJSONObject before we can access its source_code value.

That’s about it. The rest of the program is a few event handlers that call the methods described above and fill the result into the GUI. I won’t get into that, it’s boring.

The source code is available for download here and in case you simply want to browse the SWAG archive using that program, there is also a precompiled executable

As I already said in the title: These are my first steps. I’m pretty satisfied with the result, but I’m sure I made some mistakes and there are probably easier ways to implement this.

dzMdbViewer 1.0.2 released

$
0
0

dzMdbViewer is a small tool I wrote, when I was stuck without a MS Access installation but needed to have a look into a .MDB file (and later .ACCDB file, if the Access Database Engine 2012 is installed).

It can open these files and display a list of queries and tables stored in them as well as the data they contain.

Table Data

Also, for tables, it can show the field definitions.

Table Definition

Today I added support for command line parameters, so it can be used as the default program for opening these kind of files. Download it from SourceForge. The source code is available too. (I got tired of the inconveniences of Mercurial, so I moved this project to Subversion.)


GExperts 1.38 experimental twm 2016-10-03 released

$
0
0

I must admit I’m getting tired of waiting for Erik to make a new official GExperts release. So, here is another experimental one.

The motive for making this release is twofold:

  1. The Delphi 10.1 Berlin update 1 for which I released a hotfix
  2. A bug I discovered and fixed yesterday that caused GExperts to crash with an access violation when the IDE was exiting (I’ve only seen this in Delphi 10.1 Berlin update 1 so far, but it could happen in any version.)

There are also some other changes / bugfixes:

  • The Make Relative and Make Absolute buttons on the search path dialog (only visible if you switch to Memo) did not convert the content of the edit control. This might also solve the problem that sometimes the content of the edit control was added to the search path when the dialog was closed, resulting in duplicate entries.
  • Make Absolute also converted entries starting with $(BDS) which was definitely not what you want.
  • I replaced the dialog that was shown by the Change Case expert with a popup menu. Basically the usage remains unchanged because the popup menu uses the same accelerator chars and it also remembers the last option you picked and makes it the default entry which you can select by pressing Enter. (Yes, I known the vote on this feature was undecided, but I did i anyway, just for the hell of it.)
  • There is a new Convert Strings editor expert. It is my first try to replace the existing Paste Strings As, Copy Raw Strings and Convert Raw Strings experts with something that’s easy to use without actually having to memorise how they work. It can probably still be improved.
    gexperts_convert-strings
  • Bugfix: The Uses Clause Manager no longer removes units and adds them to the end of the list when adding or removing unit prefixes.
  • Bugfix by
    Jeroen Wiert Pluimers
    for Add/Remove Unit Prefixes not catching generics.collections
  • Another improvement by Jeroen Wiert Pluimers to fix the case of the unit names to match the actual file names in the Add/Remove Unit Prefixes functioality.
  • Some internal refactoring e.g. I got rid of the FParser field in TUsesManager because that field was only ever used in a single method

One last thing I always wondered about: Several Embarcadero employees are blogging regularly about what happened in other blogs and which tools were updated. My posts about GExperts releases were only mentioned once but usually ignored. So I wonder whether this is somehow on purpose. Is it me? (I sometimes post quite critical things about Deplhi and Embarcadero on G+.) Is it GExperts? (It still supports old Delphi versions back to Delphi 6. I can understand that Embarcadero would like people to have yet another reason to upgrade to the latest and (in my opinion not always so) greatest Delphi version.)

Head over to the Experimental GExperts page to download the latest release it.

Delphi Live Templates: raise exception.CreateFmt

$
0
0

Delphi (XE2) comes with various predefined so called live templates which you can display with View->Templates. One of them is called “raise” and expands to

raise exception.Create('Error Message');

I don’t know about you but I use Exception.CreateFmt much more often than simply Exception.Create, so it annoys the hell out of me when every time I type raise I get the simple Create call and have to edit it. Also, I usually use DxGetText to translate my error messages using the _() function. So I have to add that as well.

Today I got tired of this and simply changed the template:

  1. Since you cannot edit the predefined templates, the first thing you do, is open it and copy the whole text into the clipboard.
  2. Add a new template and paste the text into it.
  3. Change it to your liking.
  4. Save it with the same file name but to the directory for user defined code templates, which is [your documents folder]\RAD Studio\code_templates.

That’s it, now the new user defined template will be called rather than the predefined one.

Now to the template itself. This is the original template:

<?xml version="1.0" encoding="utf-8" ?>
<codetemplate	xmlns="http://schemas.borland.com/Delphi/2005/codetemplates"
				version="1.0.0">
<template name="raise" invoke="auto">
  <description>
	Create and raise an instance of an Exception class
  </description>
  <author>
	Embarcadero (with thanks to Erik Berry)
  </author>
  <point name="exception">
	<script language="Delphi">InvokeCodeCompletion;</script>
	<text>Exception</text>
	<hint>Exception class</hint>
  </point>
  <point name="errormessage">
	<text>Error Message</text>
	<hint>Exception Message</hint>
  </point>
  <code language="Delphi" context="methodbody" delimiter="|">
   <![CDATA[raise |exception|.Create('|errormessage|');|end|]]>
  </code>
</template>
</codetemplate>

And this is my changed template:

<?xml version="1.0" encoding="utf-8" ?>
<codetemplate	xmlns="http://schemas.borland.com/Delphi/2005/codetemplates"
				version="1.0.0">
<template name="raise" invoke="auto">
  <description>
	Create and raise an instance of an Exception class
  </description>
  <author>
	Embarcadero (with thanks to Erik Berry)
  </author>
  <point name="exception">
	<script language="Delphi">InvokeCodeCompletion;</script>
	<text>Exception</text>
	<hint>Exception class</hint>
  </point>
  <point name="errormessage">
	<text>Error Message</text>
	<hint>Exception Message</hint>
  </point>
  <point name="parameters">
	<text>Parameters</text>
	<hint>Parameters go here</hint>
  </point>
  <code language="Delphi" context="methodbody" delimiter="|">
   <![CDATA[raise |exception|.CreateFmt(_('|errormessage|'), [|parameters|]);|end|]]>
  </code>
</template>
</codetemplate>

As you can see, I changed Create to CreateFmt, added the call to _() and also added a point element “parameters” for entering the parameters for the format specifiers, which is then referenced in the code element.

You can find more about live templates here:

New GExperts IDE form enhancement for the Goto dialog

$
0
0

GExperts has got several options to enhance various IDE forms. They can be enabled and disabled on the configuration dialog’s IDE tab.

gexperts-ide-enhancement-goto-dialog

There is now a new one: Enhance Goto dialog. It’s the dialog you get when selecting Search -> Go to Line Number which looks like this in it’s original full glory:

delphi-ide-goto-dialog

It hasn’t changed since Delphi 1 (OK, I’m not sure about Delphi 1, but definitely not since Delphi 6), including such meaningful component names as Label1 and Bevel1.

This is how it looks when you enable the new enhancement:

gexperts-ide-enhanced-goto-dialog

As you can see I have added a listbox which is filled with the interesting positions in a unit:

  • Unit
  • Interface
  • Interface Uses
  • Implementation
  • Implementation Uses
  • Initialization (or Begin)
  • Finalization
  • End.

You can simply press the up or down arrow to select them and the corresponding line number will automatically entered for you. But manually entering a line number or selecting one from the combobox’s dropdown list still works as before.

In addition, I have fixed several form positioning bugs in the Delphi IDE in a multi monitor setup, where dialogs were always placed on the primary monitor regardless of where the IDE window was located.

  • In Delphi 2005 to 2007 it was the Search -> Replace dialog
  • In Delphi 2009 to 10 Seattle it were the File -> New -> Other and the Project -> Resources And Images dialogs

If you enable “Enhance IDE dialogs (and fix form positioning bugs)”, these dialogs will now be moved to the monitor of the IDEs main window.

There is no release yet, so if you want these goodies, you’ll have to compile GExperts yourself.

Getting the system boot time in Windows

$
0
0

Today I needed to get the system boot time of my computer.

You can either open the system log and look for the entries a Windows start up writes there, or you can let a tool do the work:

@echo off
systeminfo | find "System Boot Time"
pause

In my case the result looks like this:

System Boot Time:          13.10.2016, 09:14:50
Press any key to continue . . .

There are a lot more options, detailed in this answer on StackOverflow.

Writing large INI files

$
0
0

INI files, once the most used way for storing program configurations, are still popular due to their simplicity. Delphi offers two ways for accessing them:

If you only want to read or write a few values, it’s fine to use TIniFile. The underlying Windows API functions have a size limit of 64 KB though, which you might or might not reach. Once you reach it, you will experience very odd behaviour. I could not find any official documentation on whether this limit has been lifted in recent versions of Windows.

TMemIniFile does not suffer from that restriction, but it has its own: First, it might have worse performance if you only read or write a few values, because it reads the full file into memory. It also ignores any comments marked by ‘;’, so writing the file back will remove these comments. (You can still use a different comment marker e.g. // or # though.) And don’t forget to call UpdateFile if you changed it! Otherwise your changes are lost.

For writing large INI files, don’t even consider using TIniFile. It takes forever! But what about TMemIniFile? It’s much faster, but is it as fast as possible? No, it’s not. Here are two methods for writing an INI file that are even faster:

  • TStringList – but only if you recreate the full file anyway and the entries are simple
  • TFastIniFile – if you want to use a 3rd party library

The GExperts Class Browser expert writes .gex files which are INI files with a different extension to store the class list it retrieved from source files. These .gex files can be large e.g. if you add the VCL directory of Delphi 2007 the resulting file is 68 KB and contains 617 entries. The class browser used to use TIniFile to read and write these files. Here are my timing results:

Writing with TIniFile took 2,368 s
Writing with TMemIniFile took 0,236 s
Writing with TMemIniFile.SetStrings took 0,005 s
Writing with TStringList.SaveToFile took 0,004 s

So, there is a factor of 10 between TMemIniFile and TIniFile and a factor of *drumroll* 50 between TStringList and and TMemIniFile. Changing the code from TIniFile to TStringList sped up that code by a factor of whooping 500! And since these files tend to get large that’s significant.

But what if you don’t rewrite the whole file? If you just want to, say, add a new section with 100 entries to it? Yes, you could still use TStringList but that would be rather inconvenient. INI files are mostly about convenience. So I looked for a faster implementation and found TFastIniFile by Pieter Zijlstra. According to Pieter it has been updated to Delphi XE4. I tested it with Delphi 2007 and the results are impressive. It’s nearly as fast as using a TStringList and has a few features that TMemIniFile has not: It preserves comments and blank lines. It supports inline comments. Here is the result of the speed benchmark from the demo program he supplies:

(Re)write 400 sections each with 10 ident=value’s
TIniFile 25,333 s 87224 Kb
TMemIniFile 0,037 s 88504 Kb
TFastIniFile ANSI 0,008 s 88504 Kb
TFastIniFile 0,010 s 88504 Kb

I didn’t use it in GExperts because it was not necessary but it might be well worth checking out if your program routinely works with large INI files.

My thanks to Daniela Osterhagen for reminding me that writing an INI file can easily be done with a TStringList.

Viewing all 652 articles
Browse latest View live


Latest Images