Spellcheck components for Delphi - delphi

One of the requirements for the twitter client we are developing for the community is a spellcheck component. What are some of the spellcheck components/systems you have used in applications and what was your experience using it?

Addict Component Suite is the most complete one for Delphi, but it's not free.
But I think you are looking for freeware for your twitter utility, I have used LS Speller for free project and worked fine with me, it's based on ISpell, so you can update it with newer dictories.
But there's no D2009 update yet, and seems it's not actively developed.
Another option to use the MS Word built in dictionary.

Windows comes with a spell checker API (Windows 8).
TWindow8SpellChecker = class(TCustomSpellChecker)
private
FSpellChecker: ISpellChecker;
public
constructor Create(LanguageTag: UnicodeString='en-US');
procedure Check(const text: UnicodeString; const Errors: TList); override; //gives a list of TSpellingError objects
function Suggest(const word: UnicodeString; const Suggestions: TStrings): Boolean; override;
end;
With implementation:
constructor TWindow8SpellChecker.Create(LanguageTag: UnicodeString='en-US');
var
factory: ISpellCheckerFactory;
begin
inherited Create;
factory := CoSpellCheckerFactory.Create;
OleCheck(factory.CreateSpellChecker(LanguageTag, {out}FSpellChecker));
end;
procedure TWindow8SpellChecker.Check(const text: UnicodeString; const Errors: TList);
var
enumErrors: IEnumSpellingError;
error: ISpellingError;
spellingError: TSpellingError;
begin
if text = '' then
Exit;
OleCheck(FSpellChecker.Check(text, {out}enumErrors));
while (enumErrors.Next({out}error) = S_OK) do
begin
spellingError := TSpellingError.Create(
error.StartIndex,
error.Length,
error.CorrectiveAction,
error.Replacement);
Errors.Add(spellingError);
end;
end;
function TWindow8SpellChecker.Suggest(const word: UnicodeString; const Suggestions: TStrings): Boolean;
var
hr: HRESULT;
enumSuggestions: IEnumString;
ws: PWideChar;
fetched: LongInt;
begin
if (word = '') then
begin
Result := False;
Exit;
end;
hr := FSpellChecker.Suggest(word, {out}enumSuggestions);
OleCheck(hr);
Result := (hr = S_OK); //returns S_FALSE if the word is spelled correctly
ws := '';
while enumSuggestions.Next(1, {out}ws, {out}#fetched) = S_OK do
begin
if fetched < 1 then
Continue;
Suggestions.Add(ws);
CoTaskMemFree(ws);
end;
end;
The TSpellingError object is a trivial wrapper around four values:
TSpellingError = class(TObject)
protected
FStartIndex: ULONG;
FLength: ULONG;
FCorrectiveAction: CORRECTIVE_ACTION;
FReplacement: UnicodeString;
public
constructor Create(StartIndex, Length: ULONG; CorrectiveAction: CORRECTIVE_ACTION; Replacement: UnicodeString);
property StartIndex: ULONG read FStartIndex;
property Length: ULONG read FLength;
property CorrectiveAction: CORRECTIVE_ACTION read FCorrectiveAction;
property Replacement: UnicodeString read FReplacement;
end;

In the blog comments Ken just suggested LS Spell which uses the ISpell dictionaries. It is for Delphi 5, 6 and 7, so as long as it doesn't make explicit use of other string types might work fine.

I've been using Addict and have been pretty happy with it. I've used it mainly in conjunction with WPTools for mail merge & emailing.

You can use Aspell (Win32 version: http://aspell.net/win32/).
In your Delphi project you could use the command line pipe interface: aspell pipe:
C:\Programme\Aspell\bin>aspell pipe
#(#) International Ispell Version 3.1.20 (but really Aspell 0.50.3)
hello
*
world
*
helllo
& helllo 18 0: hello, Helli, hell lo, hell-lo, hell, Heall, hallo, he'll, hullo, Heller, heller, hellos, Jello, jello, Halli, Holli, hallow, hollow
wourld
& wourld 12 0: world, would, wold, whorled, wield, weld, wild, wooled, whirled, worlds, woulds, word

I use the TRichView component as my "text editor" in my Delphi application.
It supports many spellcheckers that work with Delphi. You may want to compare the ones that it supports:
http://www.trichview.com/features/spellcheck.html

DevExpress VCL also has a spell checker, though I have only played with a bit. I also own Addict which I use in software projects.

If you can guarantee that your client always has MS Word installed, I'd suggest MS Word's built in spellchecker too with OLE automation.

Related

NumbersOnly TEdit Delphi Hint not working

I'm using Delphi Seattle with the theme of Windows 10, creating programs for Windows Desktop.
In a TEdit if the active NumbersOnly property, when trying to type words, you see a standard Windows hint.
If I leave the program without the theme, the hint appears correctly, with the message explaining that you can only enter numbers. But if the active theme the message is unreadable.
Anyone have any idea where I can change this, because I was looking inside the Vcl.StdCtrls.pas and could not find the time that is generated this message to the user.
Correct hint:
Wrong hint:
This issue was fixed in RAD Studio 10.1 Berlin. But if you can't upgrade your RAD Studio Version try the VCL Styles Utils project which includes a fix for this. Only you need add the Vcl.Styles.Utils.ScreenTips unit to your project.
Update to Delphi 10.1 (Berlin) - it seems to be fixed there as I cannot reproduce this while I can with 10.0 (Seattle).
The bugfix list for Berlin shows several issues being fixed that are related to VCL Styles.
A workaround for this is to not rely on the rather useless Microsoft implementation behind the ES_NUMBER style, but implement your own logic.
type
TEdit = class(VCL.StdCtrls.TEdit)
protected
FInsideChange: boolean;
function RemoveNonNumbers(const MyText: string): string;
procedure KeyPress(var Key: Char); override;
procedure Change; override;
end;
procedure TEdit.KeyPress(var Key: Char);
begin
if NumbersOnly then begin
if not(Key in ['0'..'9','-',#8,#9,#10,#13,#127]) then begin
Key:= #0;
//Put user feedback code here, e.g.
MessageBeep;
StatusBar.Text:= 'Only numbers allowed';
end else StatusBar.Text:= '';
end;
inherited KeyPress(Key);
end;
procedure TEdit.Change; override;
begin
if FInsideChange then exit;
FInsideChange:= true;
try
inherited Change;
Self.Text:= RemoveNonNumbers(Self.Text);
finally
FInsideChange:= false;
end;
end;
function TEdit.RemoveNonNumbers(const MyText: string): string;
var
i,a: integer;
NewLength: integer;
begin
NewLength:= Length(MyText);
SetLength(Result, NewLength);
a:= 1;
for i:= 1 to Length(MyText) do begin
if MyText[i] in ['0'..'9'] or ((i=1) and (MyText[i] = '-')) then begin
Result[a]:= MyText[i];
Inc(a);
end else begin
Dec(NewLength);
end;
end; {for i}
SetLength(Result, NewLength);
end;
Now non-numbers will not be accepted, not even when pasting text.

Using LockBox3 with Delphi XE4 without installation

I'm porting program from Delphi 2009 to XE4 and got problem with LockBox encryption. Encrypt/decrypt unit is using just one component:
interface
function Encrypt(aStr: String): String;
function Decrypt(aStr: String): String;
function NeedEncrypt(): Boolean;
implementation
uses
windows,
strUtils,
LbClass;
var
LbRijndael: TLbRijndael;
localNeedEncrypt: Boolean;
function NeedEncrypt(): Boolean;
begin
Result := localNeedEncrypt;
localNeedEncrypt := False;
end;
function Encrypt(aStr: AnsiString): AnsiString;
begin
Result := aStr;
if RightStr(aStr, 2) = '==' then
Exit;
Result := LbRijndael.EncryptString(aStr);
end;
function Decrypt(aStr: AnsiString): AnsiString;
begin
Result := aStr;
if RightStr(aStr, 2) = '==' then
Result := LbRijndael.DecryptString(aStr)
else
localNeedEncrypt := True;
end;
initialization
LbRijndael := TLbRijndael.Create(nil);
LbRijndael.GenerateKey('KEYABC');
LbRijndael.CipherMode := cmECB;
LbRijndael.KeySize := ks128;
end.
As I understood there is no LockBox2 for Delphi XE4.
Can I use LockBox3 for this purpose? If yes, can I use just needed units without installation into Delphi (this was done with LockBox2)?
Whilst the LB2 and LB3 APIs are very different, you should be able to port this code across without too much difficulty. As you are creating the components dynamically at runtime, you shouldn't need to install the packages into your IDE, providing your library path is set to include the LB3 source.

Strange Format result in Delphi XE2 when using Currency data types

In Delphi XE2, I bumped against a strange formatting difference when formatting Currency. Using Double works as expected.
It looks that when using %F or %N (floating point or numeric) you always get 3 decimal digits, even if you request fewer.
With format '%.1f' a Double value of 3.1415 will become '3.1', but a Currency value of 3.1415 will become '3.142' (assuming en-US locale).
With format '%4.0n' a Double value of 3.1415 will become ' 3', but a Currency value of 3.1415 will become '3.142' (assuming en-US locale).
I wrote the below quick DUnit test case, and will investigate further tomorrow.
This particular project cannot be changed to anything other than Delphi XE2 (big corporates are not flexible in what tools they use), so I'm looking for a solution that solves this in Delphi XE2.
In the mean time: what are your thoughts?
unit TestSysUtilsFormatUnit;
interface
uses
TestFramework, System.SysUtils;
type
TestSysUtilsFormat = class(TTestCase)
strict private
DoublePi: Double;
CurrencyPi: Currency;
FloatFormat: string;
NumericFormat: string;
Expected_Format_F: string;
Expected_Format_N: string;
public
procedure SetUp; override;
procedure TearDown; override;
published
procedure Test_Format_F_Double;
procedure Test_Format_F_Currency;
procedure Test_Format_N_Double;
procedure Test_Format_N_Currency;
end;
implementation
procedure TestSysUtilsFormat.Test_Format_F_Double;
var
ReturnValue: string;
begin
ReturnValue := System.SysUtils.Format(FloatFormat, [DoublePi]);
Self.CheckEqualsString(Expected_Format_F, ReturnValue); // actual '3.1'
end;
procedure TestSysUtilsFormat.Test_Format_F_Currency;
var
ReturnValue: string;
begin
ReturnValue := System.SysUtils.Format(FloatFormat, [CurrencyPi]);
Self.CheckEqualsString(Expected_Format_F, ReturnValue); // actual '3.142'
end;
procedure TestSysUtilsFormat.Test_Format_N_Double;
var
ReturnValue: string;
begin
ReturnValue := System.SysUtils.Format(NumericFormat, [DoublePi]);
Self.CheckEqualsString(Expected_Format_N, ReturnValue); // actual ' 3'
end;
procedure TestSysUtilsFormat.Test_Format_N_Currency;
var
ReturnValue: string;
begin
ReturnValue := System.SysUtils.Format(NumericFormat, [CurrencyPi]);
Self.CheckEqualsString(Expected_Format_N, ReturnValue); // actual '3.142'
end;
procedure TestSysUtilsFormat.SetUp;
begin
DoublePi := 3.1415;
CurrencyPi := 3.1415;
FloatFormat := '%.1f';
Expected_Format_F := '3.1';
NumericFormat := '%4.0n';
Expected_Format_N := ' 3';
end;
procedure TestSysUtilsFormat.TearDown;
begin
end;
initialization
RegisterTest(TestSysUtilsFormat.Suite);
end.
Posting this as an answer on the request of the asker in the comments to the question above.)
I can't reproduce the issue on either XE2 or XE3, with a plain console application. (It was just quicker to set up for me.)
Here's the code I used in it's entirely (on both XE2/XE3):
program Project1;
{$APPTYPE CONSOLE}
{$R *.res}
uses
SysUtils;
const
DoublePi: Double = 3.1415;
CurrencyPi: Currency = 3.1415;
FloatFormat = '%.1f';
NumericFormat = '%4.0n';
begin
WriteLn(Format('Double (.1f) : '#9 + FloatFormat, [DoublePi]));
WriteLn(Format('Currency (.1f) : '#9 + FloatFormat, [CurrencyPi]));
WriteLn(Format('Currency (4.0n): '#9 + NumericFormat, [CurrencyPi]));
ReadLn;
end.
Here's the output from the XE2 run (Delphi® XE2 Version 16.0.4429.46931):
:
This was a bug in early Delphi XE 2 versions in these methods:
function WideFormatBuf(var Buffer; BufLen: Cardinal; const Format;
FmtLen: Cardinal; const Args: array of const;
const AFormatSettings: TFormatSettings): Cardinal;
function FormatBuf(var Buffer; BufLen: Cardinal; const Format;
FmtLen: Cardinal; const Args: array of const;
const AFormatSettings: TFormatSettings): Cardinal;
Fails:
Embarcadero® RAD Studio XE2 Version 16.0.4256.43595 (Update 2)
(The odd thing is: that version indicates "no updates available" with starting the "check for updates")
I did not have time to check intermediate versions.
Works:
Embarcadero® RAD Studio XE2 Version 16.0.4429.46931 (Update 4))
Embarcadero® Delphi® XE2 Version 16.0.4504.48759 (Update 4 hotfix 1)
One of the things that XE2 Update 4 (with or without the hotfix) breaks is the creation of a standard (non-IntraWeb) unit test project.
This menu entry is missing: File -> New -> Other -> Unit Test -> Test Project.
As a reminder to myself, this is the skeleton code to quickly get started with the missing Test Project entry:
program UnitTest1;
{
Delphi DUnit Test Project
-------------------------
This project contains the DUnit test framework and the GUI/Console test runners.
Add "CONSOLE_TESTRUNNER" to the conditional defines entry in the project options
to use the console test runner. Otherwise the GUI test runner will be used by
default.
}
{$IFDEF CONSOLE_TESTRUNNER}
{$APPTYPE CONSOLE}
{$ENDIF}
uses
Forms,
TestFramework,
GUITestRunner,
TextTestRunner;
{$R *.RES}
begin
Application.Initialize;
if IsConsole then
with TextTestRunner.RunRegisteredTests do
Free
else
GUITestRunner.RunRegisteredTests;
end.

Apply Windows Theme to Office Com add-in

For ages, Delphi has supported the Enable runtime themes switch on the Application Settings tab. However, this only works for executables. DLLs are assumed to take over the theming (and other) setings from their parent application.
Unfortunately, Microsoft Office doesn't play nice there. Their 'themed' look is achieved using custom controls, not through Windows' own Common Controls.
In the MSDN article 830033 - How to apply Windows XP themes to Office COM add-ins
Microsoft explains how to apply a manifest to a DLL, making it Isolation Aware such that settings from the parent process are ignored.
Basically, it comes down to two steps:
Include the default manifest resource in your process, using an int-resource id of 2 (as opposed to the 1 you'd normally use).
Compile with the ISOLATION_AWARE_ENABLED define. **Which isn't available in Delphi.**
I think I've got (1) nailed down, although I'm never quite sure whether brcc32 picks up resource IDs as integers or as literal strings. The real problem lies with (2). Supposedly, this define changes several DLL function bindings.
Has anyone solved this problem in Delphi? Should I further investigate this route, should I try and manually creating activation contexts, or are there other elegant solutions to this problem?
I've done this for my COM add-in. I used activation contexts. It's pretty easy for a COM add-in because the surface area of the add-in interface is so small. I could post code but I won't be at a machine with it on until tomorrow. Hope this helps!
UPDATE
As promised, here is the code that I use:
type
(* TActivationContext is a loose wrapper around the Windows Activation Context API and can be used
to ensure that comctl32 v6 and visual styles are available for UI elements created from a DLL .*)
TActivationContext = class
private
FCookie: LongWord;
FSucceeded: Boolean;
public
constructor Create;
destructor Destroy; override;
end;
var
ActCtxHandle: THandle=INVALID_HANDLE_VALUE;
CreateActCtx: function(var pActCtx: TActCtx): THandle; stdcall;
ActivateActCtx: function(hActCtx: THandle; var lpCookie: LongWord): BOOL; stdcall;
DeactivateActCtx: function(dwFlags: DWORD; ulCookie: LongWord): BOOL; stdcall;
ReleaseActCtx: procedure(hActCtx: THandle); stdcall;
constructor TActivationContext.Create;
begin
inherited;
FSucceeded := (ActCtxHandle<>INVALID_HANDLE_VALUE) and ActivateActCtx(ActCtxHandle, FCookie);
end;
destructor TActivationContext.Destroy;
begin
if FSucceeded then begin
DeactivateActCtx(0, FCookie);
end;
inherited;
end;
procedure InitialiseActivationContext;
var
ActCtx: TActCtx;
hKernel32: HMODULE;
begin
if IsLibrary then begin
hKernel32 := GetModuleHandle(kernel32);
CreateActCtx := GetProcAddress(hKernel32, 'CreateActCtxW');
if Assigned(CreateActCtx) then begin
ReleaseActCtx := GetProcAddress(hKernel32, 'ReleaseActCtx');
ActivateActCtx := GetProcAddress(hKernel32, 'ActivateActCtx');
DeactivateActCtx := GetProcAddress(hKernel32, 'DeactivateActCtx');
ZeroMemory(#ActCtx, SizeOf(ActCtx));
ActCtx.cbSize := SizeOf(ActCtx);
ActCtx.dwFlags := ACTCTX_FLAG_RESOURCE_NAME_VALID or ACTCTX_FLAG_HMODULE_VALID;
ActCtx.lpResourceName := MakeIntResource(2);//ID of manifest resource in isolation aware DLL
ActCtx.hModule := HInstance;
ActCtxHandle := CreateActCtx(ActCtx);
end;
end;
end;
procedure FinaliseActivationContext;
begin
if ActCtxHandle<>INVALID_HANDLE_VALUE then begin
ReleaseActCtx(ActCtxHandle);
end;
end;
initialization
InitialiseActivationContext;
finalization
FinaliseActivationContext;
When you want to use this, you simply write code like so:
var
ActivationContext: TActivationContext;
....
ActivationContext := TActivationContext.Create;
try
//GUI code in here will support XP themes
finally
ActivationContext.Free;
end;
You need each entry point that does GUI work to be wrapped in such code.
Note that in my COM add-in DLL I have taken special measures to avoid running code during DLLMain, and so my calls to InitialiseActivationContext and FinaliseActivationContext are not in unit initialization/finalization sections. However, I see no reason why this code would not be safe to place there.

What's the equivalent in Delphi 3 of Supports for Interfaces?

I support an application written in Delphi 3 and I would like to put in some improvements to the source code while waiting for the opportunity to upgrade it to a newer version of Delphi. One of the things I would like to use is Interfaces. I know Delphi 3 already has the concept of Interfaces but I am having trouble finding out how to do the equivalent of
if Supports(ObjectInstance, IMyInterface) then
Write your own implementation of "Supports" function. In Delphi 2009 you can use
function MySupports(const Instance: TObject; const IID: TGUID): Boolean;
var
Temp: IInterface;
LUnknown: IUnknown;
begin
Result:= (Instance <> nil) and
((Instance.GetInterface(IUnknown, LUnknown)
and (LUnknown.QueryInterface(IID, Temp) = 0)) or
Instance.GetInterface(IID, Temp));
end;
Test:
procedure TForm4.Button3Click(Sender: TObject);
var
Obj: TInterfacedObject;
begin
Obj:= TInterfacedObject.Create;
if MySupports(Obj, IUnknown) then
ShowMessage('!!');
end;
Hope it will work in Delphi 3

Resources