Showing posts with label Delphi. Show all posts
Showing posts with label Delphi. Show all posts

Wednesday, October 12, 2011

.Net string.format to Delphi StringFormat

Well I have been busy working in .NET and thought to add "string.format" functionality in Delphi. I am not quite sure if this functionality exists in new versions of Delphi??? AFAIK, this is not available until D7.
{------------------------------------------------------------------------
  Procedure: StringFormat
  Author:    Kiran Kurapaty
  Date:      02-Jan-2011
  Arguments: const AFormat: string; AParams: array of const
  Result:    String
  Usage:     ShowMessage(StringFormat( 'Records ({0}) {1} in {2} mins {3} secs ', [123, 'loaded', varMins, varSecs]);  
-------------------------------------------------------------------------}
function StringFormat(const AFormat: string; AParams: array of const): string;
  function GetAsString(varRec: TVarRec): String;
  begin
    try
      case varRec.VType of
        vtAnsiString: result := varRec.VPChar;
        vtBoolean:    result := IfThen(varRec.VBoolean, 'True', 'False');
        vtChar:       result := varRec.VChar;
        vtClass:      result := varRec.VClass.ClassName;
        vtCurrency:   result := format('%m', [varRec.VCurrency^]);
        vtExtended:   result := format('%f', [varRec.VExtended^]);
        vtInt64:      result := format('%d', [varRec.VInt64^]);
        vtInteger:    result := format('%d', [varRec.VInteger]);
        vtInterface:  result := format('%p', [varRec.VPointer]);
        vtObject:     result := varRec.VObject.ClassName;
        vtPChar:      result := varRec.VPChar;
        vtPointer:    result := format('%p', [varRec.VPointer]);
        vtPWideChar:  result := varRec.VPWideChar;
        vtString:     result := varRec.VPChar;
        vtVariant:    result := varRec.VVariant^;
        vtWideChar:   result := varRec.VWideChar;
        vtWideString: result := varRec.VPWideChar;
      end;
    except
      Result := 'Unknown';
    end;
  end;

var
  I: Integer;
  itemVal: string;
begin
  Result := AFormat;
  for I := Low(AParams) to High(AParams) do
  begin
    itemVal := GetAsString(AParams[I]);
    Result := StringReplace(Result, Format('{%d}', [I]), itemVal, [rfReplaceAll]);
  end;
end;

Wednesday, December 01, 2010

Using MS Word as a Spelling and Grammar Checker for Delphi

The COM interface exposed by MS Word gives a number of mechanisms for the use of the spelling engine.
  • The Word.Application object can call the engine for a single word or string. The return value is a Boolean indicating True (no errors) or False (errors). Further work must be done to provide spelling suggestions / correction.
  • The Document object contained by the Word.Application object exposes all the functionality of MS Word, in particular, the MS Word dialog boxes for spelling and grammar checking can be used.
The technique used in this component is the second method. Text is copied to the (hidden) word document, then the appropriate function (spelling or grammar) is called. To discover if the user made any changes to the text, MS Word's ability to track changes is utilised.

RTF text can be checked easily by using the CheckClipBoardSpell / Grammar functions.

Custom Dictionaries: Using custom dictionaries is a powerful feature of Word's spelling checker but there appears to be an error in the documentation (at least I can't get it to work as written in Delphi or VBA within Word itself!). Custom dictionaries are text files (usually with a '.dic' extension) with a sorted list of words - one word on each line. Included the ability to "install" a custom dictionary(s) which will be used in all spell checks. The down side is that the dictionary will now be seen by Word all the time, so it's probably a good idea to remove any such dictionary before exiting your program.

Checking if the user wants to cancel spell/grammar checking: In your own program you could check for the press of a "cancel" button or key. This isn't available when using Word's built in dialogs, so I tend to use the SpellingChecked and GrammarChecked properties to check if there are still errors in the text after a spell check has been completed. If the user has decided to quit, these properties should be False. I have mapped them into the UserCancel property for ease of programming.

Installation is very simple, just copy the code and save it as uSpellCheck.pas and include any of your existing/new dpk file.

How to use:
i) Drop the TActionList component onto a form.
ii) Select StandardAction from TActionList
iii)You will see new TSpellCheck action item under Edit category. select it ;-)
By default this action item is associated with F7 shortcut.
iv) Drop few Editable controls like TEdit, TMemo, TRichEdit etc and run the application and press F7 to trigger Spell Check.

That's it really!

{
Unit Name   : uSpellCheck.pas
Description : TSpellCheck is derived from TEditAction, which is available on
              TActionList. This newly created action acts as a standard EditAction
              for example: It gets enabled when the control is in Editable mode.
Enhancements: Couldn't think of any more as of now...
Create Date : 1st December 2010
Author      : Kiran Kumar Kurapaty
Code enhanced from original author: Andrew Baylis (ajbaylis@melbpc.org.au), Thanks Andrew.
}

unit uSpellCheck;

interface

uses Windows, SysUtils, Classes, ActnList, Forms, StdCtrls, Dialogs, StdActns, ActnRes, Messages, ComObj,
     ActiveX, Variants;

type
  TWordTimer = class(TThread)
  private
    FCaption: string;
    FDelay: Integer;
    FDialogWndClass: string;
    FPopUpExists: Boolean;
    procedure SetDelay(Value: Integer);
  protected
    procedure Execute; override;
  public
    constructor Create(CreateSuspended: Boolean);
    procedure Resume;
    property Caption: string read FCaption write FCaption;
    property Delay: Integer read FDelay write SetDelay;
    property DialogWndClass: string read FDialogWndClass write FDialogWndClass;
  end;

  TSpellLanguage = Integer;

  TSpellCheck = class(TEditAction)
  private
    FCancel: Boolean;
    FChangedText: string;
    FConnected: Boolean;
    FHandle: HWND;
    FNumChanges: Integer;
    FOleOn: Boolean;
    FSpellCaption: string;
    FTimer: TWordTimer;
    FUseSpellCaption: Boolean;
    FWordApp, FRange, FWordDoc, FCustDics: OLEVariant;
    FWordDialogClass: string;
    FWordVersion: string;
    FLanguage: TSpellLanguage;
    function GetCheckGWS: Boolean;
    function GetGrammarErrors: Integer;
    function GetSpellChecked: Boolean;
    function GetSpellErrors: Integer;
    function GetUserCancel: Boolean;
    function GetVersion: string;
    function GetWordVersion: string;
    procedure SetCheckGWS(const Value: Boolean);
    procedure SetLanguage(Value: TSpellLanguage);
  protected
    function Internal_checkGrammar: Boolean;
    function Internal_checkSpelling: Boolean;
    procedure StartTimer;
    procedure StopTimer;
  public
    procedure ExecuteTarget(Target: TObject); override;
    procedure UpdateTarget(Target: TObject); override;

    function AddCustomDic(const FileName: string): Integer;
    function CheckClipboardGrammar: Boolean;
    function CheckClipboardSpell: Boolean;
    function CheckGrammar(const Text: string): Boolean;
    function CheckSpelling(const Text: string): Boolean;
    procedure ClearText;
    procedure Connect;
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    procedure Disconnect;
    procedure RemoveCustomDic(const Name: string); overload;
    procedure RemoveCustomDic(const Index: Integer); overload;
    procedure ResetIgnoreAll;
    procedure SpellingOptions;

    property ChangedText: string read FChangedText;
    property CheckGrammarWithSpelling: Boolean read GetCheckGWS write SetCheckGWS;
    property Connected: Boolean read FConnected;
    property GrammarErrorCount: Integer read GetGrammarErrors;
    property NumChanges: Integer read FNumChanges;
    property SpellChecked: Boolean read GetSpellChecked;
    property SpellErrorCount: Integer read GetSpellErrors;
    property UserCancelled: Boolean read GetUserCancel write FCancel;
    property WordVersion: string read GetWordVersion;
  published
    property SpellCaption: string read FSpellCaption write FSpellCaption;
    property UseSpellCaption: Boolean read FUseSpellCaption write FUseSpellCaption;
    property Version: string read GetVersion;
    property Language: TSpellLanguage read FLanguage write SetLanguage;
  end;

  LangRecord = record
    LocalName: string;
    ID: Integer;
  end;

const // Spelling Language
   SpellLanguage: array[0..63] of LangRecord =
      ((LocalName: 'LanguageNone'; ID: $00000000),
      (LocalName: 'NoProofing '; ID: $00000400),
      (LocalName: 'Danish '; ID: $00000406),
      (LocalName: 'German '; ID: $00000407),
      (LocalName: 'SwissGerman '; ID: $00000807),
      (LocalName: 'EnglishAUS '; ID: $00000C09),
      (LocalName: 'EnglishUK '; ID: $00000809),
      (LocalName: 'EnglishUS '; ID: $00000409),
      (LocalName: 'EnglishCanadian '; ID: $00001009),
      (LocalName: 'EnglishNewZealand '; ID: $00001409),
      (LocalName: 'EnglishSouthAfrica '; ID: $00001C09),
      (LocalName: 'Spanish '; ID: $0000040A),
      (LocalName: 'French '; ID: $0000040C),
      (LocalName: 'FrenchCanadian '; ID: $00000C0C),
      (LocalName: 'Italian '; ID: $00000410),
      (LocalName: 'Dutch '; ID: $00000413),
      (LocalName: 'NorwegianBokmol '; ID: $00000414),
      (LocalName: 'NorwegianNynorsk '; ID: $00000814),
      (LocalName: 'BrazilianPortuguese '; ID: $00000416),
      (LocalName: 'Portuguese '; ID: $00000816),
      (LocalName: 'Finnish '; ID: $0000040B),
      (LocalName: 'Swedish '; ID: $0000041D),
      (LocalName: 'Catalan '; ID: $00000403),
      (LocalName: 'Greek '; ID: $00000408),
      (LocalName: 'Turkish '; ID: $0000041F),
      (LocalName: 'Russian '; ID: $00000419),
      (LocalName: 'Czech '; ID: $00000405),
      (LocalName: 'Hungarian '; ID: $0000040E),
      (LocalName: 'Polish '; ID: $00000415),
      (LocalName: 'Slovenian '; ID: $00000424),
      (LocalName: 'Basque '; ID: $0000042D),
      (LocalName: 'Malaysian '; ID: $0000043E),
      (LocalName: 'Japanese '; ID: $00000411),
      (LocalName: 'Korean '; ID: $00000412),
      (LocalName: 'SimplifiedChinese '; ID: $00000804),
      (LocalName: 'TraditionalChinese '; ID: $00000404),
      (LocalName: 'SwissFrench '; ID: $0000100C),
      (LocalName: 'Sesotho '; ID: $00000430),
      (LocalName: 'Tsonga '; ID: $00000431),
      (LocalName: 'Tswana '; ID: $00000432),
      (LocalName: 'Venda '; ID: $00000433),
      (LocalName: 'Xhosa '; ID: $00000434),
      (LocalName: 'Zulu '; ID: $00000435),
      (LocalName: 'Afrikaans '; ID: $00000436),
      (LocalName: 'Arabic '; ID: $00000401),
      (LocalName: 'Hebrew '; ID: $0000040D),
      (LocalName: 'Slovak '; ID: $0000041B),
      (LocalName: 'Farsi '; ID: $00000429),
      (LocalName: 'Romanian '; ID: $00000418),
      (LocalName: 'Croatian '; ID: $0000041A),
      (LocalName: 'Ukrainian '; ID: $00000422),
      (LocalName: 'Byelorussian '; ID: $00000423),
      (LocalName: 'Estonian '; ID: $00000425),
      (LocalName: 'Latvian '; ID: $00000426),
      (LocalName: 'Macedonian '; ID: $0000042F),
      (LocalName: 'SerbianLatin '; ID: $0000081A),
      (LocalName: 'SerbianCyrillic '; ID: $00000C1A),
      (LocalName: 'Icelandic '; ID: $0000040F),
      (LocalName: 'BelgianFrench '; ID: $0000080C),
      (LocalName: 'BelgianDutch '; ID: $00000813),
      (LocalName: 'Bulgarian '; ID: $00000402),
      (LocalName: 'MexicanSpanish '; ID: $0000080A),
      (LocalName: 'SpanishModernSort '; ID: $00000C0A),
      (LocalName: 'SwissItalian '; ID: $00000810));


  procedure Register;

implementation

uses Registry;

{ TSpellCheck }
procedure TSpellCheck.ExecuteTarget(Target: TObject);
begin
  if Assigned(GetControl(Target)) then
  begin
    try
      if CheckSpelling(GetControl(Target).Text) then
        GetControl(Target).Text := ChangedText;
    except
      on E:Exception do
        MessageDlg(Format('There was an error calling the Spell Checker (%s) - '+
                          'you may need to close some Windows Applications and try again',
                          [E.message]), mtError, [mbOk], 0);
    end;
  end;
end;

procedure TSpellCheck.UpdateTarget(Target: TObject);
begin
  inherited;
  Enabled := Length(GetControl(Target).Text) > 0;
end;



type
  TSpellCheckActionClass = class of TSpellCheck;
  TEditActionClass = class of TEditAction;

  TEditActionWrapper = class(StdActns.TEditAction);

  TSpellCheckEditActionInfo = record
    SpellCheckActionClass: TSpellCheckActionClass;
    EditActionClass: TEditActionClass;
    DefaultCaption: string;
    DefaultHint: string;
    DefaultImageIndex: Integer;
    DefaultShortCut: TShortCut;
  end;

  TStandardActionsEx = class(TStandardActions)
  protected
    procedure CreateByInfo(AEditActionsInfo: TSpellCheckEditActionInfo);
    function FindEditActionByClassType(AEditActionClass: TEditActionClass): TEditAction;
  public
    constructor Create(AOwner: TComponent); override;
  end;

{purpose: Add default properties of future actions here...}
const
  CH4EditActionsCount = 1;
  CH4EditActionsInfo: array [0..CH4EditActionsCount-1] of TSpellCheckEditActionInfo =
  (
    ( SpellCheckActionClass: TSpellCheck;
      EditActionClass: TEditAction;
      DefaultCaption: '&Spell Check';
      DefaultHint: 'Spell Check|Check for Spellings and Grammer.';
      DefaultImageIndex: -1;
      DefaultShortCut: VK_F7)
  );

constructor TStandardActionsEx.Create(AOwner: TComponent);
var
  I: Integer;
begin
  inherited;
  for I := Low(CH4EditActionsInfo) to High(Ch4EditActionsInfo) do
    CreateByInfo(CH4EditActionsInfo[I]);
end;

procedure TStandardActionsEx.CreateByInfo(AEditActionsInfo: TSpellCheckEditActionInfo);
var
  AEditAction: TEditAction;
  ASpellCheckEditAction: TEditAction;
begin
  AEditAction := FindEditActionByClassType(AEditActionsInfo.EditActionClass);
  ASpellCheckEditAction := AEditActionsInfo.SpellCheckActionClass.Create(Self);
  with ASpellCheckEditAction do
  begin
    if AEditAction <> nil then
    begin
      Caption := TEditActionWrapper(AEditAction).Caption;
      Hint := TEditActionWrapper(AEditAction).Hint;
      ImageIndex := TEditActionWrapper(AEditAction).ImageIndex;
      ShortCut := TEditActionWrapper(AEditAction).ShortCut;
    end
    else
    begin
      Caption := AEditActionsInfo.DefaultCaption;
      Hint := AEditActionsInfo.DefaultHint;
      ImageIndex := AEditActionsInfo.DefaultImageIndex;
      ShortCut := AEditActionsInfo.DefaultShortCut;
    end;
  end;
end;

function TStandardActionsEx.FindEditActionByClassType(AEditActionClass: TEditActionClass): TEditAction;
var
  I: Integer;
begin
  Result := nil;
  for I := 0 to ComponentCount - 1 do
    if (Components[I].ClassType = AEditActionClass) and (Components[I] is TEditAction) then
    begin
      Result := Components[I] as TEditAction;
      Break;
    end;
end;

procedure Register;
begin
  RegisterActions('Edit', [TSpellCheck], TStandardActionsEx);
end;

// these constants used to identify the window class of Word's dialog box
const
  //Constants for MS Word
   MSDialogWndClass2000 = 'bosa_sdm_Microsoft Word 9.0';
   MSDialogWndClass97 = 'bosa_sdm_Microsoft Word 8.0';
   MSWordWndClass = 'OpusApp';
   CSpellerVersion = '1.0';

function IsWordPresent: Boolean;
var
  Reg: TRegistry;
begin
  Reg := TRegistry.Create;
  try
    Reg.RootKey := HKEY_CLASSES_ROOT;
    Result := Reg.KeyExists('Word.Application');
  finally
    Reg.Free;
  end;
end;

{ TSpellCheck }
function TSpellCheck.GetCheckGWS: Boolean;
begin
  Result := False;
  if FConnected then Result := FWordApp.Options.CheckGrammarWithSpelling;
end;

function TSpellCheck.GetGrammarErrors: Integer;
begin
  if FConnected then
    Result := FRange.GrammaticalErrors.Count
  else
    Result := 0;
end;

function TSpellCheck.GetSpellChecked: Boolean;
// returns false if spelling has yet to be checked
begin
  Result := True;
  if FConnected then Result := not FRange.SpellingChecked;
end;

function TSpellCheck.GetSpellErrors: Integer;
begin
  if FConnected then
    Result := FRange.SpellingErrors.Count
  else
    Result := 0;
end;

function TSpellCheck.GetUserCancel: Boolean;
begin
  Result := FCancel;
end;

function TSpellCheck.GetVersion: string;
begin
  Result := CSpellerVersion;
end;

function TSpellCheck.GetWordVersion: string;
begin
  Result := FWordVersion;
end;

procedure TSpellCheck.SetCheckGWS(const Value: Boolean);
begin
  if FConnected then FWordApp.Options.CheckGrammarWithSpelling := Value;
end;

function TSpellCheck.Internal_checkGrammar: Boolean;
begin
  SetWindowPos(FHandle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE + SWP_HIDEWINDOW); // ensures dialogs appear in front
  FWordDoc.TrackRevisions := True; // note if changes are made
  FNumChanges := 0;

  FRange.GrammarChecked := False;
  if (FLanguage > 0) then // 0 means use the default setting
    FRange.LanguageID := SpellLanguage[FLanguage].ID;

  StartTimer;
  OleCheck(FRange.CheckGrammar);
  StopTimer;

  FCancel := not FRange.GrammarChecked;
  FWordApp.Visible := False; // need to stop ActiveDocument appearing

  FNumChanges := FRange.Revisions.Count; // seems revisions counts the old word and the new one separately
  Result := (FRange.Revisions.Count > 0);
  if Result then FRange.Revisions.AcceptAll; // accept all changes

  FWordDoc.TrackRevisions := False; // don't track future changes
end;

function TSpellCheck.Internal_checkSpelling: Boolean;
begin
  SetWindowPos(FHandle, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOACTIVATE + SWP_HIDEWINDOW); // ensures dialogs appear in front
  FWordDoc.TrackRevisions := True; // note if changes are made
  FNumChanges := 0;

  if (FLanguage > 0) then // 0 means use the default setting
     FRange.LanguageID := SpellLanguage[FLanguage].ID;

  StartTimer;
  OleCheck(FWordDoc.CheckSpelling);
  StopTimer;

  FCancel := not FWordDoc.SpellingChecked;
  FWordApp.Visible := False; // need to stop ActiveDocument appearing

  FNumChanges := FRange.Revisions.Count; // seems revisions counts the old word and the new one separately
  Result := (FRange.Revisions.Count > 0);
  if Result then FRange.Revisions.AcceptAll; // accept all changes

  FWordDoc.TrackRevisions := False; // don't track future changes
end;

procedure TSpellCheck.StartTimer;
begin
  if FUseSpellCaption then
  begin
    FTimer.Caption := FSpellCaption;
    FTimer.Resume;
  end;
end;

procedure TSpellCheck.StopTimer;
begin
  FTimer.Suspend;
end;

function TSpellCheck.AddCustomDic(const FileName: string): Integer;
begin
  FCustDics.Add(FileName);
  Result := FCustDics.Count;
end;

function TSpellCheck.CheckClipboardGrammar: Boolean;
// returns true if changes were made. Corrected text is on the clipboard
begin
   Result := False;
   if not FConnected then Connect;
   if not FConnected then Exit; // if still not connected then no MS Word!

   if FConnected then
   begin
      FRange.Paste; // replace with new text to check
      Result := Internal_CheckGrammar;
      if Result then FRange.Copy;
   end;
end;

function TSpellCheck.CheckClipboardSpell: Boolean;
// returns true if changes were made. Corrected text is on the clipboard
begin
   Result := False;
   if not FConnected then Connect;
   if not FConnected then Exit; // if still not connected then no MS Word!
   if FConnected then
   begin
      FRange.Paste; // replace with new text to check
      Result := Internal_checkSpelling;
      if Result then FRange.Copy; // put onto clipboard
   end;
end;

function TSpellCheck.CheckGrammar(const Text: string): Boolean;
// returns true if changes were made and the corrected text is placed in the Text string
begin
   Result := False;
   if not FConnected then Connect;
   if not FConnected then Exit; // if still not connected then no MS Word!
   if FConnected then
   begin
      FChangedText := EmptyStr;
      FRange.Text := Text; // replace with new text to check
      Result := Internal_CheckGrammar;
      if Result then FChangedText := FRange.Text;
   end;
end;

function TSpellCheck.CheckSpelling(const Text: string): Boolean;
// returns true if changes were made and the corrected text is placed in the Text string
begin
   Result := False;
   if not FConnected then Connect;
   if not FConnected then Exit; // if still not connected then no MS Word!
   if FConnected then
   begin
      FChangedText := EmptyStr;
      FRange.Text := Text; // replace with new text to check
      Result := Internal_CheckSpelling;
      if Result then FChangedText := FRange.Text;
   end
   else
      Result := False;
end;

procedure TSpellCheck.ClearText;
begin
  if FConnected then FRange.Text := EmptyStr;
end;

procedure TSpellCheck.Connect;
var
  s: string;
begin
  if FConnected then Exit; // don't create two instances
  try
    FWordApp := CreateOleObject('Word.Application');
    FConnected := True;
    FWordApp.Visible := False; // hides the application
    FWordApp.ScreenUpdating := False; // speed up winword's processing
    FWordApp.WindowState := $00000002; // minimise
    FWordDoc := FWordApp.Documents.Add(EmptyParam, False); // this will hold the text to be checked
    FRange := FWordDoc.Range;
    FRange.WholeStory; // makes FRange point to all text in document
    FCustDics := FWordApp.CustomDictionaries;
    FWordVersion := FWordApp.Version;
    s := FWordDoc.Name + ' - ' + FWordApp.Name;
    FHandle := FindWindow(MSWordWndClass, PChar(s)); // winword
    if FWordVersion[1] = '9' then
      FWordDialogClass := MSDialogWndClass2000
    else
      FWordDialogClass := MSDialogWndClass97;

    FTimer := TWordTimer.Create(True);
    FTimer.Delay := 50; // every 0.05 s
    FTimer.DialogWndClass := FWordDialogClass;
    FTimer.FreeOnTerminate := True;

  except
    FWordApp := Unassigned;
    FConnected := False;
    if Assigned(FTimer) then
    begin
      FTimer.Terminate;
      while FTimer.Suspended do
        FTimer.Resume;
    end;
    FTimer := nil;
    MessageDlg('Unable to initialise MS Word', mtError, [mbYes], 0);
  end;
end;

constructor TSpellCheck.Create(AOwner: TComponent);
var
  init: Integer;
begin
  inherited;
  FConnected := False;
  FCancel := False;
  FChangedText := EmptyStr;
  Init := CoInitialize(nil);
  if (Init = S_OK) or (Init = S_FALSE) then
    FOleOn := True
  else
    raise EOleSysError.CreateFmt('Error initialising COM library', []);

  FSpellCaption := EmptyStr;
  FUseSpellCaption := False;
  FLanguage := 0;
end;

destructor TSpellCheck.Destroy;
begin
  Disconnect;
  if FOleOn then CoUninitialize;
  inherited;
end;

procedure TSpellCheck.Disconnect;
var
  savechanges: OleVariant;
begin
  if not VarIsEmpty(FWordApp) then
  begin
    savechanges := False;
    FWordApp.Quit(savechanges); // don't save changes
    FRange := Unassigned;
    FWordDoc := Unassigned;
    FWordApp := Unassigned;
    FCustDics := Unassigned;
    FConnected := False;
    if Assigned(FTimer) then
    begin
      FTimer.Terminate;
      while FTimer.Suspended do
        FTimer.Resume; // need this in case thread was never started
    end;
    FTimer := nil;
  end;
end;

procedure TSpellCheck.RemoveCustomDic(const Name: string);
var
  dic: OleVariant;
begin
  dic := FCustDics.Item(Name);
  if not VarIsEmpty(dic) then
    dic.Delete;
  dic := Unassigned;
end;

procedure TSpellCheck.RemoveCustomDic(const Index: Integer);
var
  dic: OleVariant;
begin
  dic := FCustDics.Item(Index);
  if not VarIsEmpty(dic) then
    dic.Delete;
  dic := Unassigned;
end;

procedure TSpellCheck.ResetIgnoreAll;
begin
  if FConnected then
  begin
    FRange.Text := ''; // ResetIgnoreAll performs an automatic spell check
    FWordApp.ResetIgnoreAll;
  end;
end;

procedure TSpellCheck.SpellingOptions;
begin
  BringWindowToTop(FHandle); // ensures that dialog opens on top
  FWordApp.Dialogs.Item($000000D3).Show;
  FWordApp.Visible := False;
end;

procedure TSpellCheck.SetLanguage(Value: TSpellLanguage);
begin
  FLanguage := Value;
end;

{ TWordTimer }
procedure TWordTimer.SetDelay(Value: Integer);
begin
  if (Value > 0) then FDelay := Value;
end;

procedure TWordTimer.Execute;
var
  h: HWND;
begin
  while not Terminated do
  begin
    sleep(FDelay); // use this as a rough timer
    if (FDialogWndClass <> EmptyStr) then
    begin
      h := FindWindow(PChar(FDialogWndClass), nil);
      if (h <> 0) and (not FPopUpExists) then
      // only change caption if the window has just appeared
      begin
        SetWindowText(h, PChar(FCaption));
        FPopUpExists := True;
      end else
        FPopUpExists := False;
    end;
  end;
end;

constructor TWordTimer.Create(CreateSuspended: Boolean);
begin
  FDelay := 100; // default delay (ms)
  inherited Create(CreateSuspended);
end;

procedure TWordTimer.Resume;
begin
  FPopUpExists := False;
  inherited;
end;

end.

Friday, November 26, 2010

Draw Grabber Line on Canvas / Form

Below is the code snippet to draw a Grabber line... Its all self explainatory
const
  GrabberSize = 12;

procedure TForm1.DoPaintSidePanel();

procedure DrawGrabberLine(ALeft, ATop, ARight, ABottom: Integer);
begin
  with Canvas do
  begin
    Pen.Color := clBtnHighlight;
    MoveTo(ARight, ATop);
    LineTo(ALeft, ATop);
    LineTo(ALeft, ABottom);
    Pen.Color := clBtnShadow;
    LineTo(ARight, ABottom);
    LineTo(ARight, ATop-1);
  end;
end;
begin
  Canvas.Lock;
  try
    DrawGrabberLine(3, 12, 5, Height-2);
    DrawGrabberLine(6, 12, 8, Height-2);
  finally
    Canvas.Unlock;
  end;
end;

procedure TForm1.FormPaint(Sender: TObject);
begin
  DoPaintSidePanel();
end;

Friday, March 26, 2010

Delphi 6 Commandline Helper Class

unit uCmdLineHelper;

interface

uses Windows, Classes, SysUtils;

  type
  TCommandLineHelper = class(TObject)
  private
    function GetNextParam(var CmdLine: PChar; Buffer: PChar; Len: PInteger): Boolean;
    function GetParamCount: Integer;
    function GetCommandLine: string;
    function GetParamStr(Index: Integer): String;
    function GetHasParam(Value: String): Boolean;
  public
    property ParamStr[Index: Integer]: String read GetParamStr;
    property ParamCount: Integer read GetParamCount;
    property CommandLine: String read GetCommandLine;
    property HasParam[Value: String]: Boolean read GetHasParam;
  end;

implementation


function TCommandLineHelper.GetCommandLine : string;
begin
  result := Windows.GetCommandLine;
end;

function TCommandLineHelper.GetHasParam(Value: String): Boolean;
var
 I: Integer;
 param: String;
begin
  result := False;
  for I:= 1 to ParamCount do
  begin
    param := UpperCase(ParamStr[I]);
    Value := UpperCase(Value);
    result := ((param = Value) or
               (param = '/'+Value) or ('/'+param = Value) or
               (param = '\'+Value) or ('\'+param = Value) or
               (param = '-'+Value) or ('-'+param = Value) or
               (param = ':'+Value) or (':'+param = Value));
    if result then Exit;
  end;
end;

function TCommandLineHelper.GetNextParam(var CmdLine: PChar; Buffer: PChar; Len: PInteger): Boolean;
var
  InQuotedStr, IsOdd: Boolean;
  NumSlashes, NewLen, cnt: Integer;
begin
  Result := False;
  if Len <> nil then Len^ := 0;
  if CmdLine = nil then Exit;

  while (CmdLine^ <= ' ') and (CmdLine^ <> #0) do CmdLine := CharNext(CmdLine) ;
  if CmdLine^ = #0 then Exit;

  InQuotedStr := False;
  NewLen := 0;
  repeat
    if CmdLine^ = '\' then
    begin
      NumSlashes := 0;
      repeat
        Inc(NumSlashes) ;
        CmdLine := CharNext(CmdLine) ;
      until CmdLine^ <> '\';

      if CmdLine^ = '"' then
      begin
        IsOdd := (NumSlashes mod 2) <> 0;
        NumSlashes := NumSlashes div 2;
        Inc(NewLen, NumSlashes) ;

        if IsOdd then Inc(NewLen);

        if Buffer <> nil then
        begin
          for cnt := 0 to NumSlashes-1 do
          begin
            Buffer^ := '\';
            Inc(Buffer) ;
          end;

          if IsOdd then
          begin
            Buffer^ := '"';
            Inc(Buffer) ;
          end;
        end;
        if IsOdd then CmdLine := CharNext(CmdLine) ;
      end else
      begin
        Inc(NewLen, NumSlashes);

        if Buffer <> nil then
        begin
          for cnt := 0 to NumSlashes-1 do
          begin
            Buffer^ := '\';
            Inc(Buffer) ;
          end;
        end;
      end;
      Continue;
    end;
    if CmdLine^ <> '"' then
    begin
      if (CmdLine^ <= ' ') and (not InQuotedStr) then Break;
      Inc(NewLen) ;
      if Buffer <> nil then
      begin
        Buffer^ := CmdLine^;
        Inc(Buffer) ;
      end;
    end
    else
      InQuotedStr := not InQuotedStr;
    CmdLine := CharNext(CmdLine) ;
  until CmdLine^ = #0;
  if Len <> nil then Len^ := NewLen;
  Result := True;
end;

function TCommandLineHelper.GetParamCount: Integer;
var
  CmdLine: PChar;
begin
  Result := 0;
  CmdLine := Windows.GetCommandLine;
  GetNextParam(CmdLine, nil, nil) ;
  while GetNextParam(CmdLine, nil, nil) do Inc(Result) ;
end;

function TCommandLineHelper.GetParamStr(Index: Integer): String;
var
  Buffer: array[0..MAX_PATH] of Char;
  CmdLine, P: PChar;
  Len: Integer;
begin
  Result := '';
  if Index <= 0 then
  begin
    Len := GetModuleFileName(0, Buffer, MAX_PATH+1) ;
    SetString(Result, Buffer, Len) ;
  end else
  begin
    CmdLine := windows.GetCommandLine;
    GetNextParam(CmdLine, nil, nil) ;
    repeat
      Dec(Index) ;
      if Index = 0 then Break;
      if not GetNextParam(CmdLine, nil, nil) then Exit;
    until False;
    P := CmdLine;
    if GetNextParam(P, nil, @Len) then
    begin
      SetLength(Result, Len) ;
      GetNextParam(CmdLine, PChar(Result), nil) ;
    end;
  end;
end;

end.


Usage:
var
  idx : integer;
  helper: TCommandLineHelper;
begin
  helper:= TCommandLineHelper.Create;
  with Memo1.Lines do
  begin
    Clear;

    Add('CMD Line: ' + helper.CommandLine + #13#10) ;
    Add('Number of params: ' + IntToStr(helper.ParamCount) + #13#10) ;

    for idx := 1 to helper.ParamCount do
    begin
      Memo1.Lines.Add(helper.ParamStr[idx]) ;
    end;
      Memo1.Lines.Add('Has Param - and ? '+ BoolToStr(helper.HasParam['third'], true));
  end;
  helper.Free;
end;

Thursday, August 06, 2009

Get Ready for Delphi RAD Studio 2010

New features include:
  • IDE Insight – a timesaving tool to easily find files, components, features and settings using simple keystrokes and search terms

  • Code Formatter – to implement consistent coding styles with minimal effort

  • Class Explorer – for a configurable hierarchical view of class libraries throughout a project and enabling fast navigation to declarations and implementations and now available for C++Builder

  • Data Visualizers – that make debugging easier by displaying visual representations of data in definable forms

  • Debugger Thread Control – to freeze, thaw and isolate individual threads within applications during debugging to track down problems faster
  • Monday, August 18, 2008

    Consuming ASP.NET 2.0 Web Services in Delphi for Win32

    This is a very good article by Dr.Bob. Please read it whenever you find some time, it really helps! Thanks so much to Dr.Bob.

    Thursday, May 29, 2008

    Delphi 7 is now compatible with Vista


    Delphi 7 is now compatible with Vista, or Vista is now compatible with Delphi 7... anyway you look at it, I find this amusing.
    Read it on marco's tech world.

    Managing NT Services from Delphi


    {-------------------------------------------------------------------------------------
    Unit: uServiceManager.pas
    Purpose: Wrapper around some of the Windows API Functions supporting NT-Services.
    The following class TServiceManager can be used to manage your NT-Services.
    You can do things like start, stop, pause or querying a services status.
    Author: Kiran Kurapaty.
    Copyright: Kurapaty Solutions
    Dated: 18th September, 2003 at 19.10 hours IST.
    Email Id: kiran@kurapaty.co.uk / kiran.delphi@gmail.com
    -------------------------------------------------------------------------------------}

    unit uServiceManager;

    interface

    uses
    SysUtils, Windows, WinSvc;

    type

    TServiceManager = class
    private
    { Private declarations }
    ServiceControlManager: SC_Handle;
    ServiceHandle: SC_Handle;
    fServiceName: String;
    protected
    function DoStartService(NumberOfArgument: DWORD; ServiceArgVectors: PChar): Boolean;
    public
    { Public declarations }
    destructor Destroy; override;
    function Connect(MachineName: PChar = nil; DatabaseName: PChar = nil;
    Access: DWORD = SC_MANAGER_ALL_ACCESS): Boolean; // Access may be SC_MANAGER_ALL_ACCESS
    function OpenServiceConnection(aServiceName: PChar): Boolean;
    function StartService: Boolean; overload; // Simple start
    function StartService(NumberOfArgument: DWORD; ServiceArgVectors: PChar): Boolean; overload; // More complex start
    function StopService: Boolean;
    function PauseService: Boolean;
    function ContinueService: Boolean;
    function ShutdownService: Boolean;
    function DisableService: Boolean;
    function GetStatus: DWORD;
    function IsServiceRunning: Boolean;
    function IsServiceStopped: Boolean;
    published
    property ServiceName : String read fServiceName write fServiceName;
    end;

    implementation

    { TServiceManager }

    function TServiceManager.Connect(MachineName, DatabaseName: PChar; Access: DWORD): Boolean;
    begin
    { open a connection to the windows service manager }
    ServiceControlManager := OpenSCManager(MachineName, DatabaseName, Access);
    Result := (ServiceControlManager <> 0);
    end;


    function TServiceManager.OpenServiceConnection(aServiceName: PChar): Boolean;
    begin
    { open a connetcion to a specific service }
    fServiceName := aServiceName;
    ServiceHandle := OpenService(ServiceControlManager, aServiceName, SERVICE_ALL_ACCESS);
    Result := (ServiceHandle <> 0);
    end;

    function TServiceManager.PauseService: Boolean;
    var
    ServiceStatus: TServiceStatus;
    begin
    { Pause the service: attention not supported by all services }
    Result := ControlService(ServiceHandle, SERVICE_CONTROL_PAUSE, ServiceStatus);
    end;

    function TServiceManager.StopService: Boolean;
    var
    ServiceStatus: TServiceStatus;
    begin
    { Stop the service }
    Result := ControlService(ServiceHandle, SERVICE_CONTROL_STOP, ServiceStatus);
    end;

    function TServiceManager.ContinueService: Boolean;
    var
    ServiceStatus: TServiceStatus;
    begin
    { Continue the service after a pause: attention not supported by all services }
    Result := ControlService(ServiceHandle, SERVICE_CONTROL_CONTINUE, ServiceStatus);
    end;

    function TServiceManager.ShutdownService: Boolean;
    var
    ServiceStatus: TServiceStatus;
    begin
    { Shut service down: attention not supported by all services }
    Result := ControlService(ServiceHandle, SERVICE_CONTROL_SHUTDOWN, ServiceStatus);
    end;

    function TServiceManager.StartService: Boolean;
    begin
    Result := DoStartService(0, '');
    end;

    function TServiceManager.StartService(NumberOfArgument: DWORD; ServiceArgVectors: PChar): Boolean;
    begin
    Result := DoStartService(NumberOfArgument, ServiceArgVectors);
    end;

    function TServiceManager.GetStatus: DWORD;
    var
    ServiceStatus: TServiceStatus;
    begin
    { Returns the status of the service. Maybe you want to check this
    more than once, so just call this function again.
    Results may be: SERVICE_STOPPED
    SERVICE_START_PENDING
    SERVICE_STOP_PENDING
    SERVICE_RUNNING
    SERVICE_CONTINUE_PENDING
    SERVICE_PAUSE_PENDING
    SERVICE_PAUSED }
    QueryServiceStatus(ServiceHandle, ServiceStatus);
    Result := ServiceStatus.dwCurrentState;
    end;

    function TServiceManager.DisableService: Boolean;
    begin
    { Need to Implement... }
    Result := False;
    end;

    function TServiceManager.IsServiceRunning: Boolean;
    begin
    Result := (GetStatus = SERVICE_RUNNING);
    end;

    function TServiceManager.IsServiceStopped: Boolean;
    begin
    Result := (GetStatus = SERVICE_STOPPED);
    end;

    function TServiceManager.DoStartService(NumberOfArgument: DWORD;
    ServiceArgVectors: PChar): Boolean;
    begin
    Result := WinSvc.StartService(ServiceHandle, NumberOfArgument, ServiceArgVectors);
    end;

    destructor TServiceManager.Destroy;
    begin
    if (ServiceHandle <> 0) then
    CloseServiceHandle(ServiceHandle);
    inherited;
    end;

    end.

    Friday, February 15, 2008

    Adding CheckBox to a TShellTreeView


    unit Unit1;

    interface

    uses
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ShellCtrls,
    ComCtrls;

    type
    TCheckBoxShellTreeView = class(TShellTreeView)
    public
    procedure CreateParams(var Params: TCreateParams); override;
    end;

    TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    private
    { Private declarations }
    FShellTreeView: TCheckBoxShellTreeView;
    public
    { Public declarations }
    end;

    var
    Form1: TForm1;

    implementation

    {$R *.dfm}

    const
    TVS_CHECKBOXES = $00000100;

    procedure TCheckBoxShellTreeView.CreateParams(var Params: TCreateParams);
    begin
    inherited;
    Params.Style := Params.Style or TVS_CHECKBOXES;
    end;

    procedure TForm1.FormCreate(Sender: TObject);
    begin
    FShellTreeView := TCheckBoxShellTreeView.Create(Self);
    with FShellTreeView do
    begin
    Name := 'FShellTreeView';
    Parent := Self;
    Root := 'rfDesktop';
    UseShellImages := True;
    Align := alClient;
    AutoRefresh := False;
    BorderStyle := bsNone;
    Indent := 19;
    ParentColor := False;
    RightClickSelect := True;
    ShowRoot := False;
    TabOrder := 0;
    end;
    end;

    procedure TForm1.FormDestroy(Sender: TObject);
    begin
    FShellTreeView.Free;
    end;

    end.

    Wednesday, January 30, 2008

    WNet Enum Class

    WNetEnumClass. This class implements the discovery of connected computers, drives, and printers, using the WNet functions.

    TODO: Add an array of all TNetResources, and functions to allow their access from the calling app. Or more properly, functions that will return useful info from the TNetResources, simplifying the determination of device type, and so on.

    USAGE:

    var
    Obj :TWNetEnumClass;
    begin
    Obj := TWNetEnumClass.Create(Self);
    try
    Obj.Refresh;
    Memo1.Lines := Obj.GetAllNames;
    Memo2.Lines := Obj.GetCompNames;
    Memo3.Lines := Obj.GetDiskNames;
    Memo4.Lines := Obj.GetDomainNames;
    Memo5.Lines := Obj.GetErrors;
    Memo6.Lines := Obj.GetPrintNames;
    finally
    Obj.Free;
    end;
    end;



    unit uWNetEnumClass;

    interface

    uses
    Classes, Sysutils, Windows;

    type
    TWNetEnumClass = class(TObject)
    private
    FslAllNames: TStringList;
    FslCompNames: TStringList;
    FslDiskNames: TStringList;
    FslDomainNames: TStringList;
    FslErrors: TStringList;
    FslPrintNames: TStringList;
    procedure ErrorHandler(errorNum: Cardinal; s: string);
    function EnumerateResources(startingPoint: TNetResource): Boolean;
    protected
    procedure EnumResources;
    public
    constructor Create(Owner: TComponent); virtual;
    destructor Destroys; virtual;

    function GetAllNames: TStringList;
    function GetCompNames: TStringList;
    function GetDiskNames: TStringList;
    function GetDomainNames: TStringList;
    function GetErrors: TStringList;
    function GetPrintNames: TStringList;

    procedure Refresh; // used by calling apps to populate the lists
    end;

    implementation

    { TWNetEnumClass }

    const
    BASE_RES = 128;
    MAX_RES = 8192;

    var
    // establish a buffer to use to prime the drill-down process
    base_buffer: array of TNetResource;

    constructor TWNetEnumClass.Create(Owner: TComponent);
    begin
    inherited Create;

    SetLength(base_buffer, BASE_RES); // initialize the base buffer

    // now create the stringlists we will use
    FslAllNames := TStringList.Create;
    FslCompNames := TStringList.Create;
    FslDiskNames := TStringList.Create;
    FslDomainNames := TStringList.Create;
    FslErrors := TStringList.Create;
    FslPrintNames := TStringList.Create;
    end;

    destructor TWNetEnumClass.Destroys;
    begin
    // free the stringlists
    FslPrintNames.Free;
    FslErrors.Free;
    FslDomainNames.Free;
    FslDiskNames.Free;
    FslCompNames.Free;
    FslAllNames.Free;

    base_buffer := nil; // free the base buffer

    inherited Destroy;
    end;

    //

    function TWNetEnumClass.EnumerateResources(startingPoint: TNetResource): Boolean;
    var
    res: Cardinal;
    resEnum: Cardinal;
    enumHandle: THandle;
    buffer: array of TNetResource;
    bufferSize: Cardinal;
    numEntries: Cardinal;
    i: Cardinal;
    begin // EnumerateResources
    // Open a container
    res := WNetOpenEnum(
    RESOURCE_GLOBALNET, RESOURCETYPE_ANY, 0, @startingPoint, enumHandle);
    if (res <> NO_ERROR) then
    ErrorHandler(res, 'WNetOpenEnum');

    // loop through all the elements in the container
    repeat
    numEntries := Cardinal(-1);
    SetLength(buffer, MAX_RES);
    bufferSize := SizeOf(TNetResource) * MAX_RES;

    // get resources
    resEnum := WNetEnumResource(enumHandle, numEntries, buffer, bufferSize);
    if (resEnum = NO_ERROR) then
    begin
    // loop through all entries
    for i := 0 to numEntries - 1 do
    begin
    if (buffer[i].dwDisplayType = RESOURCEDISPLAYTYPE_SERVER) then
    FslCompNames.Add(buffer[i].lpRemoteName)
    else if (buffer[i].dwType = RESOURCETYPE_PRINT) then
    FslPrintNames.Add(buffer[i].lpRemoteName)
    else if (buffer[i].dwType = RESOURCETYPE_DISK) then
    FslDiskNames.Add(buffer[i].lpRemoteName)
    else if (buffer[i].dwDisplayType = RESOURCEDISPLAYTYPE_DOMAIN) then
    FslDomainNames.Add(buffer[i].lpRemoteName);
    // if the entry is a container, recursively open it
    if (buffer[i].dwUsage and RESOURCEUSAGE_CONTAINER > 0) then
    if (not EnumerateResources(buffer[i])) then
    FslErrors.Add('Enumeration failed');
    end;
    end
    else if (resEnum <> ERROR_NO_MORE_ITEMS) then
    ErrorHandler(resEnum, 'WNetEnumResource');
    { added the test for ERROR_INVALID_HANDLE to deal with the case where a "remembered"
    connection is no longer in existence. I need to look for a cleaner fix. }
    until (resEnum = ERROR_NO_MORE_ITEMS) or (resEnum = ERROR_INVALID_HANDLE);

    // clean up
    buffer := nil;
    res := WNetCloseEnum(enumHandle);
    if (res <> NO_ERROR) then
    begin
    ErrorHandler(res, 'WNetCloseEnum');
    result := False;
    end
    else
    result := True;
    end;

    procedure TWNetEnumClass.EnumResources;
    begin
    EnumerateResources(base_buffer[0]);
    end;

    procedure TWNetEnumClass.ErrorHandler(errorNum: Cardinal; s: string);
    var
    res: Cardinal;
    error: Cardinal;
    errorStr: string;
    nameStr: string;
    begin
    if (errorNum <> ERROR_EXTENDED_ERROR) then
    begin
    FslErrors.Add('Error number ' + IntToStr(errorNum) + ' returned by ' + s);
    end
    else
    begin
    res := WNetGetLastError(
    error, PChar(errorStr), 1000, PChar(nameStr), 1000);
    if (res <> NO_ERROR) then
    FslErrors.Add('Failure in WNetGetLastError: ' + IntToStr(error))
    else
    begin
    FslErrors.Add('Extended Error: ' + errorStr + '. Provider: ' + nameStr);
    end;
    end;
    end;

    function TWNetEnumClass.GetAllNames: TStringList;
    begin
    FslAllNames.Sort;
    Result := FslAllNames;
    end;

    function TWNetEnumClass.GetCompNames: TStringList;
    begin
    FslCompNames.Sort;
    Result := FslCompNames;
    end;

    function TWNetEnumClass.GetDiskNames: TStringList;
    begin
    FslDiskNames.Sort;
    Result := FslDiskNames;
    end;

    function TWNetEnumClass.GetDomainNames: TStringList;
    begin
    FslDomainNames.Sort;
    Result := FslDomainNames;
    end;

    function TWNetEnumClass.GetErrors: TStringList;
    begin
    Result := FslErrors;
    end;

    function TWNetEnumClass.GetPrintNames: TStringList;
    begin
    FslPrintNames.Sort;
    Result := FslPrintNames;
    end;

    procedure TWNetEnumClass.Refresh;
    begin
    FslAllNames.Clear;
    FslCompNames.Clear;
    FslDiskNames.Clear;
    FslDomainNames.Clear;
    FslErrors.Clear;
    FslPrintNames.Clear;
    EnumResources;
    end;

    end.

    Creating Excel (XLS) from Delphi


    const
    CXlsBof: array[0..5] of Word = ($809, 8, 00, $10, 0, 0);
    CXlsEof: array[0..1] of Word = ($0A, 00);
    CXlsLabel: array[0..5] of Word = ($204, 0, 0, 0, 0, 0);
    CXlsNumber: array[0..4] of Word = ($203, 14, 0, 0, 0);
    CXlsRk: array[0..4] of Word = ($27E, 10, 0, 0, 0);

    procedure XlsBeginStream(XlsStream: TStream; const BuildNumber: Word);
    begin
    CXlsBof[4] := BuildNumber;
    XlsStream.WriteBuffer(CXlsBof, SizeOf(CXlsBof));
    end;

    procedure XlsEndStream(XlsStream: TStream);
    begin
    XlsStream.WriteBuffer(CXlsEof, SizeOf(CXlsEof));
    end;

    procedure XlsWriteCellRk(XlsStream: TStream; const ACol, ARow: Word; const AValue: Integer);
    var
    V: Integer;
    begin
    CXlsRk[2] := ARow;
    CXlsRk[3] := ACol;
    XlsStream.WriteBuffer(CXlsRk, SizeOf(CXlsRk));
    V := (AValue shl 2) or 2;
    XlsStream.WriteBuffer(V, 4);
    end;

    procedure XlsWriteCellNumber(XlsStream: TStream; const ACol, ARow: Word; const AValue: Double);
    begin
    CXlsNumber[2] := ARow;
    CXlsNumber[3] := ACol;
    XlsStream.WriteBuffer(CXlsNumber, SizeOf(CXlsNumber));
    XlsStream.WriteBuffer(AValue, 8);
    end;

    procedure XlsWriteCellLabel(XlsStream: TStream; const ACol, ARow: Word; const AValue: string);
    var
    L: Word;
    begin
    L := Length(AValue);
    CXlsLabel[1] := 8 + L;
    CXlsLabel[2] := ARow;
    CXlsLabel[3] := ACol;
    CXlsLabel[5] := L;
    XlsStream.WriteBuffer(CXlsLabel, SizeOf(CXlsLabel));
    XlsStream.WriteBuffer(Pointer(AValue)^, L);
    end;

    procedure TForm1.Button1Click(Sender: TObject);
    var
    FStream: TFileStream;
    I, J: Integer;
    begin
    FStream := TFileStream.Create('J:\e.xls', fmCreate);
    try
    XlsBeginStream(FStream, 0);
    for I := 0 to 99 do
    for J := 0 to 99 do
    begin
    XlsWriteCellNumber(FStream, I, J, 34.34);
    // XlsWriteCellRk(FStream, I, J, 3434);
    // XlsWriteCellLabel(FStream, I, J, Format('Cell: %d,%d', [I, J]));
    end;
    XlsEndStream(FStream);
    finally
    FStream.Free;
    end;
    end;

    Paint Dock Frame


    procedure TDockTree.PaintDockFrame(Canvas: TCanvas; Control: TControl; const ARect: TRect);

    procedure DrawCloseButton(Left, Top: Integer);
    begin
    DrawFrameControl(Canvas.Handle, Rect(Left, Top, Left+FGrabberSize-2,
    Top+FGrabberSize-2), DFC_CAPTION, DFCS_CAPTIONCLOSE);
    end;

    procedure DrawGrabberLine(Left, Top, Right, Bottom: Integer);
    begin
    with Canvas do
    begin
    Pen.Color := clBtnHighlight;
    MoveTo(Right, Top);
    LineTo(Left, Top);
    LineTo(Left, Bottom);
    Pen.Color := clBtnShadow;
    LineTo(Right, Bottom);
    LineTo(Right, Top-1);
    end;
    end;

    begin
    with ARect do
    if FDockSite.Align in [alTop, alBottom] then
    begin
    DrawCloseButton(Left+1, Top+1);
    DrawGrabberLine(Left+3, Top+FGrabberSize+1, Left+5, Bottom-2);
    DrawGrabberLine(Left+6, Top+FGrabberSize+1, Left+8, Bottom-2);
    end
    else
    begin
    DrawCloseButton(Right-FGrabberSize+1, Top+1);
    DrawGrabberLine(Left+2, Top+3, Right-FGrabberSize-2, Top+5);
    DrawGrabberLine(Left+2, Top+6, Right-FGrabberSize-2, Top+8);
    end;
    end;

    Customise your Message Dialog


    procedure TForm1.Button2Click(Sender: TObject);
    var
    Dlg: TForm;
    Rslt: Integer;
    begin
    Dlg := CreateMessageDialog('Customised MessageBox, hope this is helpful', mtConfirmation, [mbYes, mbNo, mbNoToAll]);

    { change the messagedlg caption }
    Dlg.Caption := 'Please Confirm';

    {change the button texts }
    TButton(Dlg.FindComponent('Yes')).Caption := 'Indeed!';
    TButton(Dlg.FindComponent('No')).Caption := 'Surely Not!';
    TButton(Dlg.FindComponent('NoToAll')).Caption := 'None please!';

    { change the button fonts }
    TButton(Dlg.FindComponent('Yes')).Font.Name := 'C4 Text';
    TButton(Dlg.FindComponent('No')).Font.Name := 'C4 TextMedium' ;
    TButton(Dlg.FindComponent('NoToAll')).Font.Name := 'Tahoma';
    //this sets the buttons font to Bold & Underlined
    TButton(Dlg.FindComponent('NoToAll')).Font.Style:=[fsBold];

    { change the Message's appearance }
    TLabel(Dlg.FindComponent('Message')).Font.Name := 'Courier New';
    TLabel(Dlg.FindComponent('Message')).Font.Size := 12;
    TLabel(Dlg.FindComponent('Message')).Font.Color := clRed;

    Rslt := Dlg.ShowModal;

    case Rslt of
    mrYes: { do "Yes" stuff };
    mrNo: { do "No" stuff };
    mrNoToAll: { do "No to All" stuff };
    end;
    end;

    Calculate estimated Download time of a File


    { Add this under your type declaration }

    type
    TDRec = record
    H, M, S: Integer;
    end;

    const
    Count = 6;
    BpsArray: array [0..Count] of Integer = (14400, 28800, 33600, 56000, 64000, 128000, 512000);

    function CalculateDLTime(const Value, Units, Connection: Integer): TDRec;
    var
    i, size_bits, filedltimesec, hourmod, HH, MM, SS: Integer;
    Rec: TDRec;

    function pow(a, b: Integer): Integer;
    function sl(nr, times: Integer): Integer;
    var
    i: Integer;
    begin
    Result := nr * nr;
    for i := 0 to times do Result := Result + nr * nr;
    end;
    begin
    if a > b then
    Result := sl(a, b)
    else
    Result := sl(b, a);
    end;
    begin
    case Units of
    1: size_bits := (8 div 1) * Value; // bytes
    2: size_bits := (8 div 1) * ((pow(2,10)) div 1) * Value; // kilobytes
    3: size_bits := (8 div 1) * ((pow(2,20)) div 1) * Value; // Megabytes
    end;

    // Calculate
    filedltimesec := Round(size_bits) div BpsArray[Connection];

    hourmod := filedltimesec mod (60 * 60); // Modulus.
    HH := Floor(filedltimesec / (60 * 60));
    MM := Floor(hourmod / 60);
    SS := Floor(filedltimesec mod 60); // Modulus.

    if SS > 0 then Inc(SS);

    with Rec do
    begin
    H := HH;
    M := MM;
    S := SS;
    end;

    Result := Rec;
    end;

    procedure TForm1.Button1Click(Sender: TObject);
    var
    i: Integer;
    Rec: TDRec;
    begin
    ListView1.Items.Clear;

    for i := 0 to Count do
    begin
    Rec := CalculateDLTime(StrToInt(Edit1.Text), ComboBox1.ItemIndex + 1,i);

    with ListView1.Items.Add do
    begin
    Caption := NameArray[i];
    SubItems.Add(IntToStr(Rec.H));
    SubItems.Add(IntToStr(Rec.M));
    SubItems.Add(IntToStr(Rec.S));
    end;
    end;
    end;

    Thursday, August 30, 2007

    Sending an email using MAPI

    There are lot of different methods to send mail using MAPI (Windows Simple Mail API)
    If you do not want to rely on Outlook to send an email but you know that MAPI is installed, then you can also send mails with the following handy routine SendMailMAPI(). You need to add unit MAPI to your uses clause. Note that MAPI is not always installed with Windows.

    unit uMAPIMail;
    
    interface
    
    uses MAPI;
    
    function SendMailMAPI(const Subject, Body, FileName, SenderName, SenderEMail,
    RecepientName, RecepientEMail: String) : Integer;
    
    implementation
    
    function SendMailMAPI(const Subject, Body, FileName, SenderName, SenderEMail,
    RecepientName, RecepientEMail: String) : Integer;
    var
    message: TMapiMessage;
    lpSender,
    lpRecepient: TMapiRecipDesc;
    FileAttach: TMapiFileDesc;
    SM: TFNMapiSendMail;
    MAPIModule: HModule;
    begin
    FillChar(message, SizeOf(message), 0);
    with message do
    begin
    if (Subject<>'') then
    begin
    lpszSubject := PChar(Subject)
    end;
    if (Body<>'') then
    begin
    lpszNoteText := PChar(Body)
    end;
    if (SenderEMail<>'') then
    begin
    lpSender.ulRecipClass := MAPI_ORIG;
    if (SenderName='') then
    begin
    lpSender.lpszName := PChar(SenderEMail)
    end
    else
    begin
    lpSender.lpszName := PChar(SenderName)
    end;
    lpSender.lpszAddress := PChar('SMTP:'+SenderEMail);
    lpSender.ulReserved := 0;
    lpSender.ulEIDSize := 0;
    lpSender.lpEntryID := nil;
    lpOriginator := @lpSender;
    end;
    if (RecepientEMail<>'') then
    begin
    lpRecepient.ulRecipClass := MAPI_TO;
    if (RecepientName='') then
    begin
    lpRecepient.lpszName := PChar(RecepientEMail)
    end
    else
    begin
    lpRecepient.lpszName := PChar(RecepientName)
    end;
    lpRecepient.lpszAddress := PChar('SMTP:'+RecepientEMail);
    lpRecepient.ulReserved := 0;
    lpRecepient.ulEIDSize := 0;
    lpRecepient.lpEntryID := nil;
    nRecipCount := 1;
    lpRecips := @lpRecepient;
    end
    else
    begin
    lpRecips := nil
    end;
    if (FileName='') then
    begin
    nFileCount := 0;
    lpFiles := nil;
    end
    else
    begin
    FillChar(FileAttach, SizeOf(FileAttach), 0);
    FileAttach.nPosition := Cardinal($FFFFFFFF);
    FileAttach.lpszPathName := PChar(FileName);
    nFileCount := 1;
    lpFiles := @FileAttach;
    end;
    end;
    MAPIModule := LoadLibrary(PChar(MAPIDLL));
    if MAPIModule=0 then
    begin
    Result := -1
    end else
    begin
    try
    @SM := GetProcAddress(MAPIModule, 'MAPISendMail');
    if @SM<>nil then
    begin
    Result := SM(0, Application.Handle, message, MAPI_DIALOG or MAPI_LOGON_UI,0);
    end else
    begin
    Result := 1
    end;
    
    finally
    FreeLibrary(MAPIModule);
    end;
    end
    if Result<>0 then
    begin
    MessageDlg('Error sending mail ('+IntToStr(Result)+').', mtError, [mbOk], 0)
    end;
    end;
    
    end.
    

    Monday, August 20, 2007

    Preventing second instance of the application

    There are many ways, but we'll see couple of examples here...

    a) Using "GlobalAddAtom" & "GlobalFindAtom" API calls

     
    procedure TForm1.FormCreate(Sender: TObject);
    begin
    {Searchs table to see if the program is already running}
    if GlobalFindAtom('PROGRAM_RUNNING') = 0 then
    { If not found then add it }
    atom := GlobalAddAtom('PROGRAM_RUNNING')
    else begin
    { If program is already running the show message and halt }
    MessageDlg('You have the program running all ready!!', mtWarning, [mbOK], 0);
    Halt;
    end;
    end;

    procedure TForm1.FormDestroy(Sender: TObject);
    begin
    {Removes the item from the table so it can be run again}
    GlobalDeleteAtom(atom);
    end;



    b) Using "CreateMutex" & "OpenMutex" you can control this from your *.dpr code itself. Here we will try to create the mutex (kernel) object on the OS when your application starts for the first time.
    ex:
    MutexHandle := CreateMutex(Nil, False, 'ANYTHING-WHICH-IS-UNIQUE-TO-UR-APP');
    if MutexHandle returns zero(0) or some error, that means the mutex is already created and your application is running in the background. Here you can just quit the application or you throw some error message to the user, using the below code...

    MutexHandle := OpenMutex(MUTEX_ALL_ACCESS, False, 'ANYTHING-WHICH-IS-UNIQUE-TO-UR-APP');

    If MutexHandle retruns zero (0) or some error, then there is some problem with your kernel object just exit from there, otherwise you can show the error message like I have shown in the above example and quit from the application.
    Finally, dont forget to free the MutexHandle. You will get more information when you search for "CreateMutex" "OpenMutex" in Google.com

    c) Using "CreateFileMapping" / "MapViewOfFile" / "UnmapViewOfFile", its bit complex to understand, but if you are familiar with Windows API calls then it would probably help you.

    Sunday, July 29, 2007

    Sending Mail using Thread


    unit uSMTPMailer;

    interface

    uses
    Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
    Dialogs, IdMessage, IdBaseComponent, IdComponent, IdTCPConnection,
    IdTCPClient, IdMessageClient, IdSMTP, StdCtrls;

    type
    TSendMail = class(TThread)
    private
    FPortNumber: Integer;
    FSubject: String;
    FPassword: String;
    FServerName: String;
    FFromAddress: String;
    FBodyMessage: TStrings;
    FRecipientsList: TStrings;
    procedure SetBodyMessage(const Value: TStrings);
    procedure SetFromAddress(const Value: String);
    procedure SetPasword(const Value: String);
    procedure SetPortNumber(const Value: Integer);
    procedure SetRecipientsList(const Value: TStrings);
    procedure SetServerName(const Value: String);
    procedure SetSubject(const Value: String);
    public
    constructor Create(ASuspended: Boolean);
    property PortNumber: Integer read FPortNumber write SetPortNumber;
    property ServerName: String read FServerName write SetServerName;
    property Password: String read FPassword write SetPasword;
    property FromAddress: String read FFromAddress write SetFromAddress;
    property Recipients: TStrings read FRecipientsList write SetRecipientsList;
    property Subject: String read FSubject write SetSubject;
    property Body: TStrings read FBodyMessage write SetBodyMessage;
    procedure SendEMail;
    protected
    procedure Execute; override;
    end;

    function SendSMTPMail(APort: Integer;
    ASMTPServer, APassword, AFromAddress: String;
    AToAddresses, ABodyText: TStrings): Boolean;

    implementation

    { TSendMail }
    constructor TSendMail.Create(ASuspended: Boolean);
    begin
    inherited Create(ASuspended);
    FreeOnTerminate := True;
    FBodyMessage := TStringList.Create;
    FRecipientsList := TStringList.Create;
    end;

    procedure TSendMail.Execute;
    var
    FIdSMTP: TIdSMTP;
    FIdMessage: TIdMessage;
    begin
    FIdSMTP := TIdSMTP.Create(nil);
    FIdMessage := TIdMessage.Create(nil);
    try
    FIdSMTP.Host := FServerName;
    FIdSMTP.Port := FPortNumber;
    FIdSMTP.Password:= FPassword;
    FIdMessage.From.Address := FFromAddress;
    FIdmessage.Recipients.Assign(FRecipientsList);
    FIdMessage.Subject := FSubject;
    FIdMessage.Body.Assign(FBodyMessage);
    try
    FIdSMTP.Connect;
    FIdSMTP.Send(FIdMessage);
    except end;
    finally
    if FIdSMTP.Connected then FIdSMTP.Disconnect;
    FreeAndNil(FIdMessage);
    FreeAndNil(FIdSMTP);
    end;
    end;

    procedure TSendMail.SendEMail;
    begin
    Resume;
    end;

    procedure TSendMail.SetBodyMessage(const Value: TStrings);
    begin
    FBodyMessage.Assign(Value);
    end;

    procedure TSendMail.SetFromAddress(const Value: String);
    begin
    FFromAddress := Value;
    end;

    procedure TSendMail.SetPasword(const Value: String);
    begin
    FPassword := Value;
    end;

    procedure TSendMail.SetPortNumber(const Value: Integer);
    begin
    FPortNumber := Value;
    end;

    procedure TSendMail.SetRecipientsList(const Value: TStrings);
    begin
    FRecipientsList.Assign(Value);
    end;

    procedure TSendMail.SetServerName(const Value: String);
    begin
    FServerName := Value;
    end;

    procedure TSendMail.SetSubject(const Value: String);
    begin
    FSubject := Value;
    end;

    function SendSMTPMail(APort: Integer; ASMTPServer, APassword,
    AFromAddress: String; AToAddresses, ABodyText: TStrings): Boolean;
    begin
    try
    with TSendMail.Create(True) do
    begin
    PortNumber := APort;
    ServerName := ASMTPServer;
    Password := APassword;
    FromAddress := AFromAddress;
    Recipients.Assign(AToAddresses);
    Body.Assign(ABodyText);
    SendEMail;
    {no need to free, its a self destructive thread}
    end;
    Result := True;
    except
    Result := False;
    end;
    end;

    end.

    Thursday, July 26, 2007

    Display a shaded column like Windows Explorer in XP

    The Windows Explorer in XP displays the sorted column in pale grey. We can bring up similar behaviour in Delphi TListView by handling all two OnCustomDraw, OnColumnClick events to display a list view with a specified column shaded in pale grey.


    procedure TForm1.ListView1ColumnClick(Sender: TObject; Column: TListColumn);
    begin
    FColumnToSort := Column.Index;
    ListView1.Invalidate;
    end;

    procedure TForm1.ListView1CustomDraw(Sender: TCustomListView; const ARect: TRect; var DefaultDraw: Boolean);
    var
    ColLeft: Integer;
    ColBounds: TRect;
    I: Integer;
    begin
    ColLeft := ARect.Left;
    for I := 0 to Pred(FColumnToSort) do
    ColLeft := ColLeft + ListView_GetColumnWidth(ListView1.Handle, I);

    ColBounds := Rect(ColLeft, ARect.Top, ColLeft + ListView_GetColumnWidth(ListView1.Handle, FColumnToSort), ARect.Bottom);

    ListView1.Canvas.Brush.Color := clSilver;
    ListView1.Canvas.FillRect(ColBounds);
    end;

    Monday, June 25, 2007

    Calculate Size of Folders, Sub folders and files


    function DirSize(const ADirName : string; ARecurseDirs : boolean = true): integer;
    const
    FIND_OK = 0;
    var
    nResult : integer;

    procedure _RecursiveDir(const ADirName : string);
    var
    sDirName : String;
    rDirInfo : TSearchRec;
    nFindResult : Integer;
    begin
    sDirName := IncludeTrailingPathDelimiter(ADirName);
    nFindResult := FindFirst(sDirName + '*.*',faAnyFile,rDirInfo);

    while nFindResult = FIND_OK do
    begin
    // Ignore . and .. directories
    if (rDirInfo.Name[1] <> '.') then
    begin
    if (rDirInfo.Attr and faDirectory = faDirectory) and ARecurseDirs then
    RecursiveDir(sDirName + rDirInfo.Name) // Keep Recursing
    else
    inc(nResult, rDirInfo.Size); // Accumulate Sizes
    end;

    nFindResult := FindNext(rDirInfo);
    if nFindResult <> FIND_OK then
    FindClose(rDirInfo);
    end;
    end;

    // DirSize Main
    begin
    Screen.Cursor := crHourGlass;
    Application.ProcessMessages;
    nResult := 0;
    RecursiveDir(ADirName);
    Screen.Cursor := crDefault;

    Result := nResult;
    end;

    How do I determine if the user has Administrator privileges under Windows 2000/NT?


    This is not a completely intuitive process, however, it can be done by combining a few different Windows API functions, as shown in the following code sample:

    function IsAdmin: Boolean;
    var
    hAccessToken : tHandle;
    ptgGroups : pTokenGroups;
    dwInfoBufferSize : DWORD;
    psidAdministrators : PSID;
    int : integer;
    blnResult : boolean;

    const
    SECURITY_NT_AUTHORITY: SID_IDENTIFIER_AUTHORITY =
    (Value: (0,0,0,0,0,5)); // ntfs
    SECURITY_BUILTIN_DOMAIN_RID: DWORD = $00000020;
    DOMAIN_ALIAS_RID_ADMINS: DWORD = $00000220;
    DOMAIN_ALIAS_RID_USERS : DWORD = $00000221;
    DOMAIN_ALIAS_RID_GUESTS: DWORD = $00000222;
    DOMAIN_ALIAS_RID_POWER_: DWORD = $00000223;

    begin
    Result := False;
    blnResult := OpenThreadToken( GetCurrentThread, TOKEN_QUERY,
    True, hAccessToken );
    if ( not blnResult ) then
    begin
    if GetLastError = ERROR_NO_TOKEN then
    blnResult := OpenProcessToken( GetCurrentProcess,
    TOKEN_QUERY, hAccessToken );
    end;

    if ( blnResult ) then
    try

    GetMem(ptgGroups, 1024);
    blnResult := GetTokenInformation( hAccessToken, TokenGroups,
    ptgGroups, 1024,
    dwInfoBufferSize );
    CloseHandle( hAccessToken );

    if ( blnResult ) then
    begin

    AllocateAndInitializeSid( SECURITY_NT_AUTHORITY, 2,
    SECURITY_BUILTIN_DOMAIN_RID,
    DOMAIN_ALIAS_RID_ADMINS,
    0, 0, 0, 0, 0, 0,
    psidAdministrators );
    {$R-}
    for int := 0 to ptgGroups.GroupCount - 1 do

    if EqualSid( psidAdministrators,
    ptgGroups.Groups[ int ].Sid ) then
    begin
    Result := True;
    Break;
    end;
    {$R+}

    FreeSid( psidAdministrators );
    end;

    finally
    FreeMem( ptgGroups );
    end;
    end;