Category Archives: Delphi tips

Saving string or stringlist to unicode text file

TextAfter moving to recent Delphis (as 2009 or newer) saving to text files has changed as strings now are full unicode. So your old code writing to textfile also must change.

If you use TStringList to write its lines to text files, you most probably used:


var StrList : TStringList;

with StrList do
begin
 Add( “Msg” );
 SaveToFile(sFileName); //your filename
end;

This won’t work in recent Delphi, if you write unicode symbols. You need to provide Encoding for your file or else you will write only ANSI text file, so rebuild you need a TEncoding class:


var StrList : TStringList;

with StrList do
begin
 Add( “Msg” );
 SaveToFile(sFileName, TEncoding.Unicode); //your unicode filename
end;

Now, if you just want to write a single string to text file. Above function will work but you need to create a StringList first.

First you need to write a Unicode preambule to a text file and sadly convert your string to UTF8 string ( or widestring), but still you will  retain your unicode symbols. Here’s a function:


procedure StringtoFileUTF8(Filename, Line: String; Append : boolean);
var
fs: TFileStream;
preamble:TBytes;
outpututf8: RawByteString;
amode: Integer;
begin
if Append and FileExists(FileName)
then amode := fmOpenReadWrite
else amode := fmCreate;
fs := TFileStream.Create(filename, { mode } amode, fmShareDenyWrite);
{ sharing mode allows read during our writes }
try

{internal Char (UTF16) codepoint, to UTF8 encoding conversion:}
outpututf8 := Utf8Encode(line); // this converts UnicodeString to WideString, sadly.

if (amode = fmCreate) then
begin
preamble := TEncoding.UTF8.GetPreamble;
fs.WriteBuffer( PAnsiChar(preamble)^, Length(preamble));
end
else
begin
fs.Seek(fs.Size, 0); { go to the end, append }
end;

outpututf8 := outpututf8 + AnsiChar(#13) + AnsiChar(#10);
fs.WriteBuffer( PAnsiChar(outpututf8)^, Length(outpututf8) );
finally
fs.Free;
end;
end;

There is also another method if you have Delphi 2010 or newer using TStreamWriter class. This is taken from embarcadero help:


procedure TMainForm.btLoadClick(Sender: TObject);
var
 Reader: TStreamReader;
begin
 { Create a new stream writer directly. }
 Reader := TStreamReader.Create( 'local_file.txt',
 TEncoding.UTF8);

 { Read the title and then the actual text. }
 edtTitle.Text := Reader.ReadLine();
 mmText.Text := Reader.ReadToEnd();

 { Close and Free the writer. }
 Reader.Free();
end;

procedure TMainForm.btSaveClick(Sender: TObject);
var
 Writer: TStreamWriter;
begin
 { Create a new stream writer directly. }
 Writer := TStreamWriter.Create('local_file.txt',
 false, TEncoding.UTF8);

 { Store the title and then the text. }
 Writer.WriteLine(edtTitle.Text);
 Writer.Write(mmText.Text);

 { Close and Free the writer. }
 Writer.Free();
end;
Advertisements

Creating a Unique FileName

Text To Create a Unique filename for your working temporary or permanent file you can use a WinAPI function  GetTempFileName .

Here is my variant of making unique filename in specified folder:

function CreateUniqueFileName(sPath, sPrefix, sExtension : string) : string;
  var sFileName : array[0..MAX_PATH] of Char;
begin
  Result := '';
  repeat
    if GetTempFileName(PChar(sPath), PChar(sPrefix), 0, sFileName) <> 0
      then begin
         DeleteFile(sFileName);
         Result := ChangeFileExt(sFileName, sExtension)
      end
      else break; //error or failed
  until not FileExists(Result);
end;

sPath – your folder (you can windows temp folder GetTempPath(SizeOf(Buffer) - 1, Buffer); )
sPrefix – any prefix to attach to filename
sExtension – your extension of filename
if Function fails , empty string is returned.

But I don’t like this approach very much, because Windows creates a temporary file with tmp extension, that you have to delete later, if you don’t want additional files in system. Also deleting files on some OS might be slow.

Much better approach is by creating Unique filenames using GUID – global identifier, it is very little probability that generated GUIDs will be the same.

So here it is my variant of function:

function CreateUniqueGUIDFileName(sPath, sPrefix, sExtension : string) : string;
   var sFileName : string;
        Guid : TGUID;
begin
  Result := '';
  repeat
   SFileName := '';
   CreateGUID(Guid);
   SFileName := sPath + sPrefix + GUIDtoString(GUID);
   Result := ChangeFileExt(sFileName, sExtension)
  until not FileExists(Result);
end;

The variables are the same.


Be careful with FormCloseQuery

help 64 Recently I had a very hard to find bug-problem with closing my application (made in Delphi).
It just refused to respond to Windows shutdown messages and prevented Windows to shutdown and restart..

I spent a lot of hours investigating this problem.
Initially the first part of the problem, because I was showing a dialog  for "Really close?" and preventing closing if some files are not saved, this was done in FormCloseQuery and it was stupid approach, because FormCloseQuery doesn’t know if windows is shutting down and thus preventing shutdown.

Then, I made a Message listeners for windows asking applications query for shutdown : WM_QueryEndSession and WM_EndSession:

// Shutdown messages

procedure WMEndSession(var Msg: TWMEndSession); message WM_ENDSESSION;

// Query to Shutdown

procedure WMQueryEndSession(var Msg: TMessage); message WM_QUERYENDSESSION;


The problem remained: the messages were received only second time. The first time when Windows was shutting down the application was still preventing shutdown, and only for the second Shutdown call, the messages were received and Application quit.

So something else was corrupting even the messages queue.

After some time, I accidentally came under another Form in same application, which also had FormCloseQuery, this time it was really needed because this Form was closing with fading effect and Query was preventing close until fader finished. So that was the second part of the problem.

Again I needed to listen for Shutdown messages in this Form.

So to conclude: you need to listen for Windows shut down Messages and Queries in every form that has FormCloseQuery or better yet avoid it, if you want to let windows shutdown normally.

Also to note: Delphi made applications (on Windows shutdown) exit instantly without freeing objects, so be careful and it is a good practice to listen to Shutdown procedure and to call your own Closing and Freeing procedure manually:

//Windows' Query for Shutdown

procedure TMainForm.WMQueryEndSession(var Msg: TMessage);

begin

  //Windows is about to shut down - Alow or not

  bForceClose := True;

  Msg.Result := 1; // 1 for Signal that it is OK to shut down

  inherited; // let the inherited message handler respond

end;

 

//Windows is shutting down

procedure TMainForm.WMEndSession(var Msg: TWMEndSession);

begin

  // shutdowning

  if Msg.EndSession = True then

  begin

    //Windows is shutting down -- Closing'

    bForceClose := True;

    //Be CAREFULLL - MIGTH BE DOUBLE OR NO FREEING AT ALL

    CleanUp;   // My Closing procedure

    FinalDestroy; // Destructor sometimes never called on shutdown !!!!!!

 

    inherited; // let the inherited message handler respond

  end

  else

    inherited;

    // Msg.Result:=0;  //Signal that we got the message - not needed

end;

bForceClose here is a global boolean variable, signaling that we should force our application closing, and if you still need FormCloseQuery  check for bForceClose there (of course you need to set it false on application start):

procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: Boolean);

begin

  CanClose := True;

  if not bForceClose then begin

        //no close 

        CanClose := False;

        //Do what you need here;

  end

  else CanClose := TRUE; //Forced close

end;

And you need this done in every form with FormCloseQuery.


Saving and Loading strings in stream the Delphi 2009 way

Refresh Lately, I had a simple task to save and load some short strings in file.
The quickest and fastest way is to do it with streams (TFileStream or TMemoryStream).

Because in Delphi 2009 string type strings are Unicode so they are also double byte sized than in previous Delphi strings.
So you need to be very strict to write exact BYTES of the string, not characters, because character size is now 2 Bytes!

For Writing String to any stream use these Functions :


procedure WriteStreamInt(Stream : TStream; Num : integer);
begin
 Stream.WriteBuffer(Num, SizeOf(Integer));
end;

procedure WriteStreamStr(Stream : TStream; Str : string);
var
 StrLen : integer;
begin
 StrLen := Length(Str); //All Delphi versions compatible
 WriteStreamInt(Stream, StrLen);
 Stream.WriteBuffer(Pointer(Str)^, StrLen * SizeOf(Char)); //All Delphi versions compatible
end;

The reading of string shoud be also exact with character size in mind. Because you can read only half of string:


function ReadStreamInt(Stream : TStream) : integer;
begin
Stream.ReadBuffer(Result, SizeOf(Integer));
end;

function ReadStreamStr(Stream : TStream) : string;
var
StrLen : integer;
TempStr : string;
begin
TempStr := '';
StrLen := ReadStreamInt(Stream);
if StrLen > -1 then
begin
SetLength(TempStr, StrLen);
//Here you should also check the character size
//Reading Bytes!
Stream.ReadBuffer(Pointer(TempStr)^, StrLen * SizeOf(Char));
result := TempStr;
end
else Result := '';
end;

Then you can write functions for writing and loading strings for example:


function SaveToDFile( FileName: String; sDescr : string; ) : boolean;
var  MemStr: TMemoryStream;
begin
MemStr:= TMemoryStream.Create;
try
try
WriteStreamStr( MemStr, sDescr );
MemStr.SaveToFile(FileName);
result := True;
except
on E:EWriteError do result := False;
end;
finally
MemStr.Free;
end;
end;

function LoadFromDFile( FileName: String; var sDescr : string; ) : boolean;
var MemStr: TMemoryStream;
begin
MemStr:= TMemoryStream.Create;
try
try
MemStr.LoadFromFile(FileName);
sDescr := ReadStreamStr( MemStr );
result := True;
except
on E:EReadError do result := False;
end;
finally
MemStr.Free;
end;
end;

Hope it helps some.


How to show your own custom popup in TWebBrowser component

If you want to show your custom popup menu in your TWebBrowser component I really recommend to you to download a EmbeddedWB component by bsalsa productions ( <http://www.bsalsa.com/product.html> ).
It is all the same TWebBowser only more user friendly, with more events and methods, added functionality.
By the way, it is all free.

With this EmbeddedWB you can easily make your own popup menu, even diffrent pop-ups for different selected items in web browser.

So for example, make a default popup when no selection exists, and one custom popup for selected text.

If you have EmbeddedWB installed and have one on your form, look for OnShowContextMenu event.

Then your custom popup can be shown with this example code:

function TForm.WebShowContextMenu(const dwID: Cardinal;

  const ppt: PPoint; const pcmdtReserved: IInterface;

  const pdispReserved: IDispatch): HRESULT;

begin

  //When we want to show our custum menu

  if (dwID = 4) //any text selection menu is about to show

    then begin

      TextPopup.Popup(ppt.X,ppt.Y);

    end

    else

      if Assigned(MyPopup) //if our popup is created

         then begin

           MyPopup.Popup(ppt.X, ppt.Y);

           Result := S_OK; //don't show WB popup

         end

         else Result := S_FALSE; //else show IE Popup

end;

 

IE has several different menus for images, text and so on. You can check what menu is about to show by

checking dwID:



CONTEXT_MENU_DEFAULT = 0;

CONTEXT_MENU_IMAGE = 1;

CONTEXT_MENU_CONTROL = 2;

CONTEXT_MENU_TABLE = 3;

CONTEXT_MENU_TEXTSELECT = 4;

CONTEXT_MENU_ANCHOR = 5;

CONTEXT_MENU_UNKNOWN = 6;

CONTEXT_MENU_IMGDYNSRC = 7;

CONTEXT_MENU_IMGART = 8;

CONTEXT_MENU_DEBUG = 9;

More info can be found here:

http://msdn2.microsoft.com/en-us/library/aa753264.aspx

http://www.bsalsa.com/ewb_on_show_context.html

Hope, this was helpful.


FileStream Create error in Delphi

Refresh I’ll share some tip on File Creation in Delphi. Some day I had to make a solution to create same file stream a lot times in a cycle and to show it at regular intervals (it was opened in another application). Well, if file is already created and time interval get’s too short, you might get an exception, that file is "in use on another process" and even setting file permissions doesn’t help:

   1: var File : TFileStream 

   2: File := TFileStream.Create(OutputFile, fmCreate or fmShareExclusive)

The solution is, first to check out if file is in use. We could use Windows API CreateFile function for this task:

   1: function IsFileInUse(FileName: TFileName): Boolean;

   2:   var HFileRes: HFILE;

   3: begin

   4:   Result := False;

   5:   if not FileExists(FileName) then Exit;

   6:   HFileRes := CreateFile( PChar(FileName), //Windows API Create

   7:     GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING,

   8:     FILE_ATTRIBUTE_NORMAL, 0);

   9:   Result := (HFileRes = INVALID_HANDLE_VALUE);

  10:   //returns false File handle if file is in use

  11:   if not Result then CloseHandle(HFileRes); //Deny and close handle

  12: end;

Then you can check if file is in use:

   1: if NOT IsFileInUse(OutputFile)

   2:    then File := TFileStream.Create(OutputFile, fmCreate or  fmShareExclusive);

update: Zarko Gajis from About.com made another universal function for checking if "FileIsInUse" here:

Programmatically Check If File is In Use – Delphi Code