2006. augusztus 31., csütörtök
Check if a TTreeView is fully expanded or collapsed
Problem/Question/Abstract:
How to check if a TTreeView is fully expanded or collapsed
Answer:
function IsTreeviewFullyExpanded(tv: TTreeview): Boolean;
var
node: TTreenode;
begin
Assert(Assigned(tv));
if tv.items.count > 0 then
begin
node := tv.Items[0];
Result := true;
while Result and Assigned(node) do
begin
Result := node.Expanded or not node.HasChildren;
node := node.GetNext;
end;
end
else
Result := false
end;
function IsTreeviewFullyCollapsed(tv: TTreeview): Boolean;
var
node: TTreenode;
begin
Assert(Assigned(tv));
if tv.items.count > 0 then
begin
node := tv.Items[0];
Result := true;
while Result and Assigned(node) do
begin
Result := not (node.Expanded and node.HasChildren);
node := node.GetNext;
end;
end
else
Result := false
end;
2006. augusztus 30., szerda
How to open a specific drive when you have two CD-ROM drives
Problem/Question/Abstract:
How to open a specific drive when you have two CD-ROM drives
Answer:
function IsDriveCD(Drive: Char): boolean;
var
DrivePath: string;
begin
DrivePath := Drive + ':\';
Result := (GetDriveType(PChar(DrivePath)) = DRIVE_CDROM);
end;
function EjectCD(Drive: Char): boolean;
var
mp: TMediaPlayer;
begin
Result := false;
if not IsDriveCD(Drive) then
exit;
mp := TMediaPlayer.Create(nil);
try
mp.Visible := false;
mp.Parent := Application.Mainform;
mp.Shareable := true;
mp.DeviceType := dtCDAudio;
mp.FileName := Drive + ':';
mp.Open;
mp.Eject;
mp.Close;
finally
mp.Free;
end;
end;
2006. augusztus 29., kedd
How to make a dynamically created TLabel draggable
Problem/Question/Abstract:
How to make a dynamically created TLabel draggable
Answer:
Create a new project with an empty form, add StdCtls to the Uses clause (for the TLabel class, you can also add a single label at design time). Add a handler to the forms OnClick method, then modify the unit as below. Compile and run, click on the form to create a label, drag on a label to move it.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls;
type
TForm1 = class(TForm)
procedure FormClick(Sender: TObject);
private
{ Private declarations }
downX, downY: Integer;
dragging: Boolean;
procedure ControlMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure ControlMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
procedure ControlMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
type
TCracker = class(TControl);
{ Needed since TControl.MouseCapture is protected }
procedure TForm1.FormClick(Sender: TObject);
var
pt: TPoint;
begin
{get cursor position, convert to client coordinates}
GetCursorPos(pt);
pt := ScreenToClient(pt);
{create label with top left corner at mouse position}
with TLabel.Create(Self) do
begin
SetBounds(pt.x, pt.y, width, height);
Caption := Format('Hit at %d, %d', [pt.x, pt.y]);
Color := clBlue;
Font.Color := clWhite;
Autosize := true;
Parent := Self;
{attach the drag handlers}
OnMouseDown := ControlMouseDown;
OnMouseUp := ControlMouseUp;
OnMouseMove := ControlMouseMove;
end;
end;
procedure TForm1.ControlMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
downX := X;
downY := Y;
dragging := TRue;
with TCracker(Sender) do
begin
MouseCapture := True;
Color := clRed;
end;
end;
procedure TForm1.ControlMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
begin
if dragging then
with Sender as TControl do
begin
Left := X - downX + Left;
Top := Y - downY + Top;
end;
end;
procedure TForm1.ControlMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
if dragging then
begin
dragging := False;
with TCracker(Sender) do
begin
MouseCapture := False;
Color := clBlue;
end;
end;
end;
end.
2006. augusztus 28., hétfő
How to detect if a point lies within a polygon
Problem/Question/Abstract:
Can anyone help me with detecting if the screen coordinates that a user has clicked on lies within a defined polygon?
Answer:
The code below is from Wm. Randolph Franklin with some minor modifications for speed. It returns 1 for strictly interior points, 0 for strictly exterior, and 0 or 1 for points on the boundary.
function PointInPolygonTest(x, y: Integer; aList: array of TPoint): Boolean;
var
L, I, J: Integer;
function xp(aVal: Integer): Integer;
begin
Result := PPoint(@aList[aVal]).X;
end;
function yp(aVal: Integer): Integer;
begin
Result := PPoint(@aList[aVal]).Y;
end;
begin
Result := False;
L := Length(aList);
if L = 0 then
exit;
J := L - 1;
for I := 0 to L - 1 do
begin
if ((((yp(I) <= y) and (y < yp(J))) or ((yp(J) <= y) and (y < yp(I)))) and
(x < (xp(J) - xp(I)) * (y - yp(I)) / (yp(J) - yp(I)) + xp(I))) then
Result := not Result;
J := I;
end;
end;
2006. augusztus 27., vasárnap
How to write a list of strings to the registry
Problem/Question/Abstract:
I want to save the contents of a stringlist to the registry and later read it back. How can I do this?
Answer:
Save a list of strings to the registry. It will write each string as a key value with the key being the index of each string element and the value being the key.
procedure TDPRegistry.SaveStringListInRegistry(_RootKey: HKEY; _Localkey: string;
Strings: TStrings);
var
TR: TRegIniFile;
LStringIndex: Integer;
begin
TR := TRegIniFile.Create('');
try
case _RootKey of { default is RootKey=HKEY_CURRENT_USER }
HKEY_CLASSES_ROOT,
HKEY_CURRENT_USER,
HKEY_LOCAL_MACHINE,
HKEY_USERS,
HKEY_PERFORMANCE_DATA,
HKEY_CURRENT_CONFIG,
HKEY_DYN_DATA: TR.RootKey := _RootKey;
end;
TR.EraseSection(_Localkey); {make sure no entries for this section/ key}
with TRegistry(TR) do
begin
if OpenKey(_Localkey, true) then
begin
try
for LStringIndex := 0 to Strings.Count - 1 do
begin
WriteString(IntToStr(LStringIndex), Strings[LStringIndex]);
end; {for each string in the list}
finally
CloseKey;
end;
end;
end;
finally
TR.Free;
end;
end;
{Get list of strings from registry}
procedure TDPRegistry.GetStringListFromRegistry(_RootKey: HKEY; _Localkey: string;
Strings: TStrings);
var
TR: TRegIniFile;
LStringIndex: Integer;
RegKeyInfo: TRegKeyInfo;
begin
Strings.Clear; {start with no elements in string list}
TR := TRegIniFile.Create('');
try
case _RootKey of { default is RootKey=HKEY_CURRENT_USER }
HKEY_CLASSES_ROOT,
HKEY_CURRENT_USER,
HKEY_LOCAL_MACHINE,
HKEY_USERS,
HKEY_PERFORMANCE_DATA,
HKEY_CURRENT_CONFIG,
HKEY_DYN_DATA: TR.RootKey := _RootKey;
end;
{TR.ReadSectionValues(_Localkey, Strings); doesn't work nicely because it
returns strings as "1=Value", "2=Value"...}
with TRegistry(TR) do
begin
if OpenKey(_Localkey, true) then
begin
try
if (GetKeyInfo(RegKeyInfo)) then
begin
for LStringIndex := 0 to RegKeyInfo.NumValues - 1 do
begin
Strings.Add(ReadString(IntToStr(LStringIndex)));
end; {for each value associated with this key}
end; {got key information}
finally
CloseKey;
end;
end;
end;
finally
TR.Free;
end;
end;
2006. augusztus 26., szombat
Resize a TControl object graphically
Problem/Question/Abstract:
Move or resize graphically a TControl object by using another objet that has all the necessary code.
Answer:
The unit showed bellow is for the TControlHandler class.
TControlHandler can manipulate graphically any descendant of the TControl class. A TControl objet can be selected by the TControlHandler Control property : a border appears surrounding the control passed to this method as reference. At run-time, you can then change its location or change its size with the mouse.
For example, in an application, create a TControlHandler at startup :
FControlHandler := TControlHandler.Create(Self);
To manipulate a Button1 objet placed on the form, write the following instruction :
FControlHandler.Control := Button1;
You can choose another control by assigning a new TControl's reference ; the previous one is deselected.
unit ControlHandler;
// Written by Bertrand Goetzmann (http://www.object-everywhere.com)
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, ExtCtrls, ComCtrls;
type
TControlPoint = (pcTopLeft, pcTopRight, pcBottomLeft, pcBottomRight, pcOther);
TControlHandler = class(TCustomControl)
private
Rgn: HRGN;
R, R1: TRect;
Pos: TPoint;
Pt: TControlPoint;
bDrag: Boolean;
protected
FControl: TControl;
procedure SetRegion;
function GetControlPoint(const Point: TPoint): TControlPoint;
procedure Paint; override;
procedure MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X,
Y: Integer);
procedure MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y:
Integer);
procedure MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure SetControl(Control: TControl);
published
property Control: TControl read FControl write SetControl;
end;
implementation
const
LARGEUR = 5;
// M�thodes de TControlHandler
constructor TControlHandler.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Parent := AOwner as TWinControl;
Rgn := 0;
bDrag := False;
OnMouseDown := MouseDown;
OnMouseMove := MouseMove;
OnMouseUp := MouseUp;
end;
destructor TControlHandler.Destroy;
begin
if Rgn <> 0 then
DeleteObject(Rgn);
inherited Destroy;
end;
function TControlHandler.GetControlPoint(const Point: TPoint): TControlPoint;
begin
Result := pcOther;
if PtInRect(Rect(0, 0, LARGEUR, LARGEUR), Point) then
Result := pcTopLeft
else if PtInRect(Rect(Width - LARGEUR, 0, Width, LARGEUR), Point) then
Result := pcTopRight
else if PtInRect(Rect(0, Height - LARGEUR, LARGEUR, Height), Point) then
Result := pcBottomLeft
else if PtInRect(Rect(Width - LARGEUR, Height - LARGEUR, Width, Height), Point) then
Result := pcBottomRight;
end;
procedure TControlHandler.MouseDown(Sender: TObject; Button: TMouseButton; Shift:
TShiftState; X, Y: Integer);
begin
Pos.x := X;
Pos.y := Y;
bDrag := True;
R := Rect(FControl.Left, FControl.Top, FControl.Left + FControl.Width, FControl.Top
+ FControl.Height);
R1 := Rect(FControl.Left, FControl.Top, FControl.Left + FControl.Width, FControl.Top
+ FControl.Height);
Pt := GetControlPoint(Pos);
Visible := False;
end;
procedure TControlHandler.MouseUp(Sender: TObject; Button: TMouseButton; Shift:
TShiftState; X, Y: Integer);
begin
Screen.Cursor := crDefault;
bDrag := False;
Control.Left := R.Left;
Control.Top := R.Top;
Control.Width := R.Right - R.Left;
Control.Height := R.Bottom - R.Top;
SetRegion;
Visible := True;
end;
procedure TControlHandler.MouseMove(Sender: TObject; Shift: TShiftState; X, Y:
Integer);
begin
case GetControlPoint(Point(X, Y)) of
pcTopLeft:
Cursor := crSizeNWSE;
pcTopRight:
Cursor := crSizeNESW;
pcBottomLeft:
Cursor := crSizeNESW;
pcBottomRight:
Cursor := crSizeNWSE;
pcOther:
Cursor := crDrag;
end;
if not bDrag then
Exit;
case Pt of
pcTopLeft:
begin
R.Left := R1.Left + X - Pos.x;
R.Top := R1.Top + Y - Pos.y;
end;
pcTopRight:
begin
R.Right := R1.Right + X - Pos.x;
R.Top := R1.Top + Y - Pos.y;
end;
pcBottomLeft:
begin
R.Left := R1.Left + X - Pos.x;
R.Bottom := R1.Bottom + Y - Pos.y;
end;
pcBottomRight:
begin
R.Right := R1.Right + X - Pos.x;
R.Bottom := R1.Bottom + Y - Pos.y;
end;
pcOther:
begin
R.Left := R1.Left + X - Pos.x;
R.Top := R1.Top + Y - Pos.y;
R.Right := R1.Right + X - Pos.x;
R.Bottom := R1.Bottom + Y - Pos.y;
end;
end;
with FControl do
begin
Left := R.Left;
Top := R.Top;
Width := R.Right - R.Left;
Height := R.Bottom - R.Top;
end;
end;
procedure TControlHandler.SetRegion;
var
Rgn1, Rgn2: HRGN;
begin
if Rgn <> 0 then
DeleteObject(Rgn);
Visible := False;
Left := Control.Left - LARGEUR;
Top := Control.Top - LARGEUR;
Width := Control.Width + 2 * LARGEUR;
Height := Control.Height + 2 * LARGEUR;
Rgn := CreateRectRgn(0, 0, Width, Height);
Rgn1 := CreateRectRgn(0, 0, Width, Height);
Rgn2 := CreateRectRgn(LARGEUR, LARGEUR, Control.Width + LARGEUR, Control.Height +
LARGEUR);
CombineRgn(Rgn, Rgn1, Rgn2, RGN_DIFF);
DeleteObject(Rgn1);
DeleteObject(Rgn2);
SetWindowRgn(Handle, Rgn, True);
Visible := True;
end;
procedure TControlHandler.SetControl(Control: TControl);
begin
FControl := Control;
SetRegion;
end;
procedure TControlHandler.Paint;
begin
with Canvas do
begin
Brush.Color := clBlack;
Brush.Style := bsBDiagonal;
Rectangle(0, 0, Width, Height);
// Dessiner les poignets
Brush.Style := bsSolid;
FillRect(Rect(0, 0, LARGEUR, LARGEUR));
FillRect(Rect(Width - LARGEUR, 0, Width, LARGEUR));
FillRect(Rect(0, Height - LARGEUR, LARGEUR, Height));
FillRect(Rect(Width - LARGEUR, Height - LARGEUR, Width, Height));
end;
end;
end.
Component Download: http://perso.worldonline.fr/objecteverywhere/control.zip
2006. augusztus 25., péntek
Remove all components of a certain type at run time
Problem/Question/Abstract:
How to remove all components of a certain type at run time
Answer:
//implementation:
procedure Tform1.freeInstances(aClass: TClass);
var
i: Integer;
begin
for i := formFacture.ControlCount - 1 downto 0 do
if (Controls[i] is aClass) then
begin
(Controls[i] as aClass).Free;
//instead of free you can put anything you need here
end;
end;
//calling:
procedure Tform1.Button1Click(Sender: TObject);
begin
freeInstances(TEdit); //will free all tedit on form1
end;
2006. augusztus 24., csütörtök
How to get the size of a text file without opening it
Problem/Question/Abstract:
How to get the size of a text file without opening it
Answer:
Returns the size, in bytes, of the passed file:
function TextfileSize(const name: string): LongInt;
var
SRec: TSearchRec;
begin
if FindFirst(name, faAnyfile, SRec) = 0 then
begin
Result := SRec.Size;
Sysutils.FindClose(SRec);
end
else
Result := 0;
end;
2006. augusztus 23., szerda
How to create a hotspot
Problem/Question/Abstract:
I need something like a hotspot that would lie above everything else and under certain conditions trap mouse events and under other conditions, let them through. I understand, to get the mouse events before anybody else it must be a TWinControl descendant? How do I make it transparent then?
Answer:
Here's one I wrote a couple of years ago. It isn't a TWinControl descendant, TGraphicControl instead, but it handles MouseEnter and MouseLeave. If you want others, you need to add them
{
TEXSHotSpot:
HotSpot Component which allows developers to insert a "HotSpot" on top of an image or panel but lets the background show through and handle mouse entering and leaving the rectangle region of the component.
New Property:
ShowBorder- Use this if you want a border to show around the hotspot when the mouse is over it. Similar to the flat speedbutton look.
}
unit EXSHotSpot;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
extctrls, StdCtrls;
type
TEXSHotSpot = class(TGraphicControl)
private
{ Private declarations }
FMouseInControl: Boolean;
FShowBorder: Boolean;
procedure CMMouseEnter(var Message: TMessage); message CM_MOUSEENTER;
procedure CMMouseLeave(var Message: TMessage); message CM_MOUSELEAVE;
procedure DrawBorder;
function GetShowBorder: Boolean;
procedure SetShowBorder(Value: Boolean);
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
{ Published declarations }
property Align;
property Enabled;
property OnClick;
property OnDblClick;
property OnMouseDown;
property OnMouseUp;
property OnMouseMove;
property OnDragDrop;
property OnEndDrag;
property ShowBorder: Boolean read GetShowBorder write SetShowBorder;
property ShowHint;
property Visible;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TEXSHotSpot]);
end;
procedure TEXSHotSpot.DrawBorder;
var
R: TRect;
begin
R := ClientRect;
InflateRect(R, -1, -1);
Frame3D(Canvas, R, clBtnHighlight, clBtnShadow, 1);
end;
procedure TEXSHotSpot.CMMouseEnter(var Message: TMessage);
begin
inherited;
if not FMouseInControl and Enabled then
begin
FMouseInControl := True;
if FShowBorder then
DrawBorder;
end;
end;
procedure TEXSHotSpot.CMMouseLeave(var Message: TMessage);
begin
inherited;
if FMouseInControl and Enabled then
begin
FMouseInControl := False;
Invalidate;
end;
end;
constructor TEXSHotSpot.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
ControlStyle := ControlStyle + [csReplicatable];
Height := 105;
Width := 105;
end;
destructor TEXSHotSpot.Destroy;
begin
inherited Destroy;
end;
function TEXSHotSpot.GetShowBorder: Boolean;
begin
Result := FShowBorder;
end;
procedure TEXSHotSpot.SetShowBorder(Value: Boolean);
begin
if Value <> FShowBorder then
FShowBorder := Value;
Repaint;
end;
end.
2006. augusztus 22., kedd
Set a printer settings
Problem/Question/Abstract:
How can I set a some printer settings?
Answer:
At first, of course, you must open the printer device (as I described in previous tip " to get a printer settings").
Now you can set the any settings (supported only, of course) in properties of DevMode^ variable and add a "assigned" flag in DevMode^.dmFields.
After that you need call a SetPrinter procedure and unlock device.
View small example:
procedure SetPrinterSettings(FPrinter: TPrinter);
var
FDevice: PChar;
FDriver: PChar;
FPort: PChar;
DeviceMode: THandle;
DevMode: PDeviceMode;
begin
{to get a current printer settings}
FPrinter.GetPrinter(FDevice, FDriver, FPort, DeviceMode);
{lock a printer device}
DevMode := GlobalLock(DeviceMode);
{set a paper size as A4-Transverse}
if ((DevMode^.dmFields and DM_PAPERSIZE) = DM_PAPERSIZE) then
begin
DevMode^.dmFields := DevMode^.dmFields or DM_PAPERSIZE;
DevMode^.dmPaperSize := DMPAPER_A4_TRANSVERSE;
end;
{set a paper source as Tractor bin}
if ((DevMode^.dmFields and DM_DEFAULTSOURCE) = DM_DEFAULTSOURCE) then
begin
DevMode^.dmFields := DevMode^.dmFields or DM_DEFAULTSOURCE;
DevMode^.dmDefaultSource := DMBIN_TRACTOR;
end;
{set a Landscape orientation}
if ((DevMode^.dmFields and DM_ORIENTATION) = DM_ORIENTATION) then
begin
DevMode^.dmFields := DevMode^.dmFields or DM_ORIENTATION;
DevMode^.dmOrientation := DMORIENT_LANDSCAPE;
end;
{set a printer settings}
FPrinter.SetPrinter(FDevice, FDriver, FPort, DeviceMode);
{unlock a device}
GlobalUnlock(DeviceMode);
end;
If you need to change the paper size to custom size for example, 100mm x 100mm, you must assign the custom width and height to dmPaperWidth and dmPaperLength and include the DM_PAPERWIDTH/DM_PAPERLENGTH flags to dmFields property:
DevMode^.dmPaperWidth := PaperSizeWidth;
DevMode^.dmPaperLength := PaperSizeHeight;
DevMode^.dmFields := DevMode^.dmFields or DM_PAPERWIDTH or DM_PAPERLENGTH;
2006. augusztus 21., hétfő
Copy one table from Access database in to another Access database
Problem/Question/Abstract:
How to copy one table from Access database in to another Access database
Answer:
If I am not wrong you have an Access db with multiple tables and you want to copy one of these tables into another Access db. For this case i would do the next:
Create database TrasportDB.mdb - use ADOX.
Copy table from source table into TransportDB.mdb with Select * Into [TransportTable] in "FullPath\TransportDB.mdb" From SourceTable.
Deliver TransportDB.mdb on destination computer.
Copy table from TransportTable into DestTable with Select * Into [DestTable] From [TransportTable] in "FullPath\TransportDB.mdb".
FullPath is the path to TransportDB.mdb and is different on source and dest computers.
This way you will use native access methods that should be more reliable and faster than using ADO methods. If you need to perform more complete tasks you should use replication from Microsoft Jet and Replication objects (import this typelib).
2006. augusztus 20., vasárnap
Determine if a COM object is registered
Problem/Question/Abstract:
At startup I need to determine if a COM server has been registered. If not, I will call RegisterComServer. Now I am calling RegisterComServer every time the application starts, which I do not think is a good idea. Is there a proper way to test for it being registered?
Answer:
You can try several methods:
a) Use CLSIDFromProgID method:
{ ... }
var
strOLE: string;
begin
strOLE = "YourCOMServer.Application"; {your ProgID}
if (CLSIDFromProgID(PWideChar(WideString(strOLE), ClassID) = S_OK) then
begin
{ ... }
end;
end;
b) Check the registry:
{ ... }
const
cKEY = '\SOFTWARE\Classes\CLSID\%s\InprocServer32';
var
sKey: string;
sComServer: string;
exists: boolean;
Reg: TRegistry;
begin
Reg := TRegistry.Create;
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
sKey := format(cKEY, [GuidToString(ClassID)]);
if Reg.OpenKey(sKey, False) then
begin
sComServer := Reg.ReadString('');
if FileExists(sComServer) then
begin
{ ... }
end;
end;
finally
Reg.free;
end;
end;
2006. augusztus 19., szombat
How to get a range of text from a TRichEdit without setting a selection
Problem/Question/Abstract:
How to get a range of text from a TRichEdit without setting a selection
Answer:
Sometimes while using RichEdit you need to get just a part of the text from that control, without setting a selection and using the SelText property. The code below shows the way to do that :
{overrides wrong TTextRange definition in RichEdit.pas}
TTextRange = record
chrg: TCharRange;
lpstrText: PAnsiChar;
end;
function REGetTextRange(RichEdit: TRichEdit; BeginPos, MaxLength: Integer): string;
{RichEdit: RichEdit control
BeginPos: absolute index of first char
MaxLength: maximum chars to retrieve}
var
TextRange: TTextRange;
begin
if MaxLength > 0 then
begin
SetLength(Result, MaxLength);
with TextRange do
begin
chrg.cpMin := BeginPos;
chrg.cpMax := BeginPos + MaxLength;
lpstrText := PChar(Result);
end;
SetLength(Result, SendMessage(RichEdit.Handle, EM_GETTEXTRANGE, 0,
longint(@TextRange)));
end
else
Result := '';
end;
This function can be used to extract a word under the current mouse pointer position:
function RECharIndexByPos(RichEdit: TRichEdit; X, Y: Integer): Integer;
{ function returns absolute character position for given cursor coordinates}
var
P: TPoint;
begin
P := Point(X, Y);
Result := SendMessage(RichEdit.Handle, EM_CHARFROMPOS, 0, longint(@P));
end;
function REExtractWordFromPos(RichEdit: TRichEdit; X, Y: Integer): string;
{ X, Y: point coordinates in rich edit control }
{returns word under current cursor position}
var
BegPos, EndPos: Integer;
begin
BegPos := RECharIndexByPos(RichEdit, X, Y);
if (BegPos < 0) or
(SendMessage(RichEdit.Handle, EM_FINDWORDBREAK,
WB_CLASSIFY, BegPos) and (WBF_BREAKLINE or WBF_ISWHITE) < > 0) then
begin
result := '';
exit;
end;
if SendMessage(RichEdit.Handle, EM_FINDWORDBREAK,
WB_CLASSIFY, BegPos - 1) and (WBF_BREAKLINE or WBF_ISWHITE) = 0 then
BegPos := SendMessage(RichEdit.Handle, EM_FINDWORDBREAK,
WB_MOVEWORDLEFT, BegPos);
EndPos := SendMessage(RichEdit.Handle, EM_FINDWORDBREAK,
WB_MOVEWORDRIGHT, BegPos);
Result := TrimRight(REGetTextRange(RichEdit, BegPos, EndPos - BegPos));
end;
2006. augusztus 18., péntek
Check if OLE object is installed
Problem/Question/Abstract:
How can I check if OLE object is installed?
Answer:
Sometimes in development if you use OLE automation of some obejct, your application will not work because application is not installed on client computer. For example, you use MS excel automation but MS Excel is not installed.
You can easy check if OLE object is installed and correctly registered using CLSIDFromProgID function (for MS Excel as example only):
var
ClassID: TCLSID;
strOLEObject: string;
begin
strOLEObject := 'Excel.Application';
if (CLSIDFromProgID(PWideChar(WideString(strOLEObject)), ClassID) = S_OK) then
begin
end
else
begin
end
end;
In same manner you can check any other required OLE object.
2006. augusztus 17., csütörtök
Find the contrasting colour (2)
Problem/Question/Abstract:
I have x items, and I want to assign a color for each, but I want the colors to be as different as possible. I think the best would be to distribute x points with the greatest distance possible in an RGB cube. I have no idea how to implement that, or is there some other solution?
Answer:
You can use the invers color:
InversColor := ColorToRGB(AColor) xor $FFFFFF;
I you need a maximal contrast, the result is black or white, dependent of the brightness:
function MaximumContrastColor(Value: TColor): TColor;
var
R, G, B: Integer;
Min, Max: Integer;
begin
Value := ColorToRGB(Value);
R := GetRValue(Value);
G := GetGValue(Value);
B := GetBValue(Value);
Min := R;
if Min > G then
Min := G;
if Min > B then
Min := B;
Max := R;
if Max < G then
Max := G;
if Max < B then
Max := B;
if Min + Max > 255 then
Result := clBlack
else
Result := clWhite;
end;
2006. augusztus 16., szerda
How to save a complete directory
Problem/Question/Abstract:
Is there an API function which gives all the subdirectories and all the files of one particular directory (in order to save a whole directory for example )?
Answer:
You can copy a whole directory with one instruction using the ShFileOperation API function:
procedure TForm1.Button2Click(Sender: TObject);
var
OpStruc: TSHFileOpStruct;
frombuf, tobuf: array[0..128] of Char;
begin
FillChar(frombuf, Sizeof(frombuf), 0);
FillChar(tobuf, Sizeof(tobuf), 0);
StrPCopy(frombuf, 'd:\brief\*.*');
StrPCopy(tobuf, 'd:\temp\brief');
with OpStruc do
begin
Wnd := Handle;
wFunc := FO_COPY;
pFrom := @frombuf;
pTo := @tobuf;
fFlags := FOF_NOCONFIRMATION or FOF_RENAMEONCOLLISION;
fAnyOperationsAborted := False;
hNameMappings := nil;
lpszProgressTitle := nil;
end;
ShFileOperation(OpStruc);
end;
If you need a list of all files and subdirs you have to do a recursive scan using FindFirst/ FindNext.
2006. augusztus 15., kedd
Custom message identifier: WM_APP +? or WM_USER +?
Problem/Question/Abstract:
I'm declaring a custom message identifier. Do I start from WM_APP or WM_USER?
Answer:
If you use a message internally in a custom control you typically base the message ID on WM_USER. If you use a custom message with a control derived from a standard windows or third-party control you are better off using WM_APP as the base, unless you definitely know from the documentation of the parent control what the highest message number it uses is (many controls use messages > WM_USER).
So it mainly depends in what context you intend to use the message. If only a TForm descendent will ever handle it you can base it on WM_USER since a form only handles messages < WM_USER or >= CM_BASE.
2006. augusztus 14., hétfő
Extracting And Validating Email Addresses
Problem/Question/Abstract:
This code example introduces a new way to validate Email Addresses with domain name (TLD) validation !!
Answer:
Code #1
function IsEMail(EMail: string): Boolean;
var
s: string;
ETpos: Integer;
begin
ETpos := pos('@', EMail);
if ETpos > 1 then
begin
s := copy(EMail, ETpos + 1, Length(EMail));
if (pos('.', s) > 1) and (pos('.', s) < length(s)) then
Result := true
else
Result := false;
end
else
Result := false;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if isemail(Edit1.Text) then
begin
ShowMessage('eMail-Adresse!');
end;
end;
Code #2
MaxDomains = 174;
DomainList: array[0..MaxDomains] of string[3] = (
'AD', 'AE', 'AG', 'AI', 'AM', 'AR', 'AS', 'AT', 'AU', 'AW', 'AZ', 'BA', 'BE', 'BF',
'BG', 'BH', 'BM', 'BN', 'BO', 'BR', 'BS', 'BW', 'BY', 'BZ', 'CA', 'CC', 'CH', 'CI',
'CK', 'CL', 'CN', 'CO', 'COM', 'CR', 'CU', 'CY', 'CZ', 'DE', 'DK', 'DM', 'DO', 'DZ',
'EC', 'EDU', 'EE', 'EG', 'ES', 'ET', 'FI', 'FJ', 'FK', 'FM', 'FO', 'FR', 'GB', 'GE',
'GF', 'GH', 'GI', 'GL', 'GOV', 'GR', 'GT', 'GU', 'GW', 'GY', 'HK', 'HN', 'HR', 'HU',
'ID', 'IE', 'IL', 'IN', 'INT', 'IO', 'IR', 'IS', 'IT', 'JM', 'JO', 'JP', 'KE', 'KG',
'KH', 'KI', 'KM', 'KR', 'KW', 'KY', 'KZ', 'LB', 'LI', 'LK', 'LT', 'LU', 'LV', 'MA',
'MC', 'MD', 'MIL', 'MK', 'MN', 'MO', 'MR', 'MT', 'MU', 'MV', 'MX', 'MY', 'MZ', 'NA',
'NC', 'NE', 'NET', 'NF', 'NG', 'NI', 'NL', 'NO', 'NP', 'NU', 'NZ', 'OM', 'ORG',
'PA', 'PE', 'PF', 'PG', 'PH', 'PK', 'PL', 'PR', 'PT', 'PY', 'QA', 'RO', 'RU', 'SA',
'SE', 'SG', 'SI', 'SK', 'SL', 'SM', 'SN', 'ST', 'SU', 'SV', 'SZ', 'TC', 'TF', 'TG',
'TH', 'TM', 'TO', 'TR', 'TT', 'TW', 'TZ', 'UA', 'UG', 'UK', 'US', 'UY', 'UZ', 'VA',
'VE', 'VI', 'VN', 'YE', 'YU', 'ZA', 'ZM', 'ZW');
function InDomainList(email: string): boolean;
var
tel: word;
st: string;
begin
tel := length(email);
while (tel > 0) and (email[tel] <> '.') do
dec(tel);
st := copy(email, tel + 1, length(email));
for tel := 0 to maxdomains do
if st = DomainList[tel] then
begin
InDomainList := true;
inc(hittable[tel]);
exit;
end;
InDomainList := false;
end;
Have Fun !!!
2006. augusztus 13., vasárnap
How to make a TMemo show the chosen line
Problem/Question/Abstract:
How can I have a memo show a chosen line? In fact my program does some work and puts information in a memo, I would like the last line of the memo to be always visible. I tried MyMemo.ScrollBy(0, MaxInt), but that does not work.
Answer:
with Memo1 do
begin
SelLength := 0;
SelStart := Perform(EM_LINEINDEX, LineNo, 0);
Perform(EM_SCROLLCARET, 0, 0);
end;
LineNo hold the line number you want to scroll to.
2006. augusztus 12., szombat
How to validate input in a TEdit
Problem/Question/Abstract:
How to validate input in a TEdit
Answer:
Assuming you're using regular TEdit components, during OnExit, you will see irregular behavior from controls if you attempt to change focus at that time. The solution is to post a message to your form in the TEdit's OnExit event handler. This user-defined posted message will indicate that the coast is clear to begin validating input. Since posted messages are placed at the end of the message queue, this gives Windows the opportunity to complete the focus change before you attempt to change the focus back to another control:
unit Unit5;
interface
uses
SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, Mask;
const
{User-defined message}
um_ValidateInput = wm_User + 100;
type
TForm5 = class(TForm)
Edit1: TEdit;
Edit2: TEdit;
Edit3: TEdit;
Edit4: TEdit;
Button1: TButton;
procedure EditExit(Sender: TObject);
procedure EditEnter(Sender: TObject);
private
Refocusing: TObject;
{User-defined message handler}
procedure ValidateInput(var M: TMessage); message um_ValidateInput;
end;
var
Form5: TForm5;
implementation
{$R *.DFM}
procedure TForm5.ValidateInput(var M: TMessage);
var
E: TEdit;
begin
{The following line is my validation. I want to make sure the first character is a lower case
alpha character. Note the typecast of lParam to a TEdit}
E := TEdit(M.lParam);
if not (E.Text[1] in ['a'..'z']) then
begin
Refocusing := E; {Avoid a loop}
ShowMessage('Bad input'); {Yell at the user}
TEdit(E).SetFocus; {Set focus back}
end;
end;
procedure TForm5.EditExit(Sender: TObject);
begin
{Post a message to myself which indicates it's time to validate the input. Pass the TEdit
instance (Self) as the message lParam}
if Refocusing = nil then
PostMessage(Handle, um_ValidateInput, 0, longint(Sender));
end;
procedure TForm5.EditEnter(Sender: TObject);
begin
if Refocusing = Sender then
Refocusing := nil;
end;
end.
2006. augusztus 11., péntek
Editor keyboard shortcut - column selection
Problem/Question/Abstract:
Editor Keyboard Shortcut - Column Selection
Answer:
The Delphi editor is something we all take for granted - I know I do. In past versions of Delphi, the editor lacked features that forced many of us to do as we did with Paradox: Use another editor add-in that provided more editing features. One thing that I use a lot is column selection; that is, instead of selecting an entire block of text in the editor, I only want to select a certain number of columns over several lines. For instance, this is incredibly useful for setting up several arrays with the same element count. There are lots of uses.
Column selection keyboard shorcuts are defined as follows:
Shortcut
Description
Alt+Shift+Left Arrow
Selects the column to the left of the cursor
Alt+Shift+Right Arrow
Selects the column to the right of the cursor
Alt+Shift+Up Arrow
Moves the cursor up one line and selects the column from the left of the starting cursor position
Alt+Shift+Down Arrow
Moves the cursor down one line and selects the column from the left of the starting cursor position
Alt+Shift+Page Up
Moves the cursor up one screen and selects the column from the left of the starting cursor position
Alt+Shift+Page Down
Moves the cursor down one line and selects the column from the right of the starting cursor position
Alt+Shift+End
Selects the column from the cursor position to the end of the current line
Alt+Shift+Home
Selects the column from the cursor position to the start of the current line
There are lots of great editor shortcuts included in the Delphi Editor. For a listing of them, search the Delphi online help for "editor shortcuts." You'll find them as useful as I do!
2006. augusztus 10., csütörtök
Set the background colour of a word in a TRichEdit
Problem/Question/Abstract:
I have a RichEdit control where my code outputs text into, formatting certain words or phrases with different font colors. This works fine, but I would also like to set the background of certain words to be different colors. MSWord allows this with the Highlight option, and I can copy and paste highlighted text from MSWord into my RichEdit control, so I have to assume that this is possible to do in code as well. Can anyone provide me with sample code or guidance on doing this in code?
Answer:
The version 1 richedit control introduced with Win95 did not have this ability and this version is what the TRichedit class was wrapped around. So it does not give you access to all the new abilities in the version 3 control you find on WinMe, 2K, and XP. You could delve into the murky depth of the API and send appropriate messages to the control:
uses
richedit;
procedure TForm1.Button1Click(Sender: TObject);
var
cf: TCharFormat2;
begin
fillchar(cf, sizeof(cf), 0);
cf.cbSize := sizeof(cf);
cf.dwMask := CFM_BACKCOLOR;
cf.crBackColor := ColorToRGB(clYellow);
richedit1.Perform(EM_SETCHARFORMAT, SCF_SELECTION, lparam(@cf));
end;
2006. augusztus 9., szerda
Storing Font information in the registry - with one key only
Problem/Question/Abstract:
Storing Font information in the registry - with one key only
Answer:
If you came in a situation to store font information in the registry, because you want to allow your users to customize your application, then you may have faced the fact that the TRegistry class does not provide WriteFont(), ReadFont() functions.
The first thought would be to make a sub key for each item in your application and write the font information as a combination of Strings and Integers.
WriteString(key, Font.FaceName);
WriteInteger(key, Font.Size);
Obviously not the most elegant code. Here's an elegant solution - store the font information as binary data! The Windows API provides a TLogFont structure that describes a font. It includes all properties that the Borland TFont class provides except the font's color. We'll use an extended logical description that contains the Windows (T)LogFont and the color. For information on TLogFont open help file Win32.hlp and search for LogFont.
// saves/ reads a font to/ from the registry
//
// read like this:
// fEditorFont := TFont.Create;
// fEditorFont.name := 'Courier New';
// fEditorFont.Size := 10;
// LoadFont(sKey, 'Editor', fEditorFont);
//
// and save like this:
// SaveFont(sKey, 'Editor', fEditorFont);
unit sFontStorage;
interface
uses
Graphics, Windows, Registry;
procedure LoadFont(const sKey, sItemID: string; var aFont: TFont);
procedure SaveFont(const sKey, sItemID: string; aFont: TFont);
implementation
type
TFontDescription = packed record
Color: TColor;
LogFont: TLogFont;
end;
procedure LoadFont(const sKey, sItemID: string; var aFont: TFont);
var
iSiz: Integer;
FontDesc: TFontDescription;
begin
with TRegistry.Create do
begin
if OpenKey(sKey, False) then
try
iSiz := SizeOf(FontDesc);
if ReadBinaryData(sItemID, FontDesc, iSiz) = SizeOf(FontDesc) then
begin
aFont.Handle := CreateFontIndirect(FontDesc.LogFont);
end;
aFont.Color := FontDesc.Color;
finally
CloseKey;
end;
// free the registry object
Free;
end;
end;
procedure SaveFont(const sKey, sItemID: string; aFont: TFont);
var
iSiz: Integer;
FontDesc: TFontDescription;
begin
with TRegistry.Create do
begin
iSiz := SizeOf(FontDesc.LogFont);
if GetObject(aFont.Handle, iSiz, @FontDesc.LogFont) > 0 then
begin
f OpenKey(sKey, True) then
try
FontDesc.Color := aFont.Color;
WriteBinaryData(sItemID, FontDesc, SizeOf(FontDesc));
finally
CloseKey;
end;
end;
// free the registry object
Free;
end;
end;
end.
2006. augusztus 8., kedd
Default Array Properties
Problem/Question/Abstract:
By using default array properties, you can abbreviate your calls by using the syntax Class[I] as opposed to Class.Items[I].
Answer:
Delphi supports abbreviated calls to the default class array property. This can be used in any delphi class that uses defaults such as TStringList.
For Example:
use MyStringList[Index] in place of MyStringList.Strings[Index]
This is somewhat useful for cleaning up the code, especially if you are accessing the property in question often.
To add this feature to your own classes, simply add the "default" directive (storage specifier) after the array (indexed) property you want to use as the default.
For Example:
type
TMyGraphicList = class
public
property Names[Index: Integer]: string read GetName write SetName;
property Objects[Index: Integer]: TObject read GetObj write SetObj;
property Images[Index: Integer]: TImage read Get write set; default;
{...}
end;
By adding the "default" directive after the Images property we have designated the Images array property as the default.
Access can now be abbreviated such as this:
for I := 0 to MyGraphicList.Count - 1 do
begin
AName := MyGraphicList.Names[I];
AObject := MyGraphicList.Objects[I];
AImage := MyGraphicList[I]; // instead of MyGraphicList.Images[I]
end;
NOTE: There can be only one default array property for each class. Entering a second would generate a compiler error.
IMPORTANT: "Array" properties are different than "attribute" properties, and the default directive takes on a different meaning for each. When used on an attribute property, the default directive (storage specifier) releates to how Delphi saves the values of published properties in form (.DFM) files.
2006. augusztus 7., hétfő
Shaking a form
Problem/Question/Abstract:
Shaking a Form
Answer:
Solve 1:
Just put a button in your form and insert these commands in the OnClick Event !
procedure TForm1.Button1Click(Sender: TObject);
var
N: Integer; //Counter
TL, TT: Integer; //Backup for LEFT and TOP of teh FORM
begin
TL := Left; //Backups Left of the Form
TT := Top; //Backups Top of the Form
//*********************************************************************
for N := 1 to 40 do
begin //Counter Time
Left := (TL - 10) + (Random(20)); {Shake Range in Horizontal Driection}
Top := (TT - 10) + (Random(20)); {Shake Range in Vertical Driection}
end;
//*********************************************************************
Left := TL; //Restores Left of the Form
Top := TT; //Restores Top of the Form
end;
Solve 2:
var
iipt: Integer;
procedure Tform1.FormCreate;
begin
iipt := Form1.Left;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
so: Integer;
begin
so := 0;
repeat
if Form1.Left = iipt - 10 then
begin
so := so + 1;
repeat
Form1.Left := Form1.Left + 1;
until Form1.Left = iipt
end
else
repeat
Form1.Left := Form1.Left - 1;
until Form1.Left = iipt - 10;
until so = 2;
end;
Solve 3:
for i := 2 to 7 do
begin
for j := 0 to 10 do
begin
if int(int((i - 1) / 2) / 2) * 2 = int((i - 1) / 2) then
frmMain.Left := frmMain.Left + 1
else
frmMain.Left := frmMain.Left - 1;
end;
end;
2006. augusztus 6., vasárnap
Dynamic Arrays in Delphi
Problem/Question/Abstract:
Dynamic Arrays in Delphi
Answer:
Borland Delphi 4 features a number of Object Pascal language enhancements, as usual. In this article, I'll address a very handy language enhancement takes the ideas of Open Parameters and Long Strings back to the basics of arrays in the so-called Dynamic Arrays.
In Delphi 4, in addition to declaring static arrays such as
X: array[1..42] of string;
we can now also declare dynamic arrays. Dynamic arrays specify type information (the number of dimensions and the type of the elements) but not the number of elements. Thus
X: array of string;
M: array of array of Integer;
declares two dynamic arrays. X is a one-dimensional arrays of Strings, while M is a two dimensional array of Integers (like a Matrix).
Dynamic arrays do not have a fixed size or length. Instead, memory for a dynamic array is (re-)allocated when we assign a value to the array or pass it to the SetLength procedure. Hence, the above declarations for X and M do not allocate memory. To create the array in memory, call SetLength. For example, given the declarations above,
SetLength(X, 42);
allocates an array of 42 Strings, indexed 0 to 41. Dynamic arrays are always integer-indexed, always starting from 0.
After calling SetLength, the previous content of the dynamic array - if any - is copied along (so data never gets lost if we constantly increase or decrease the length of the array). Using the above knowledge, we can write a small - and very inefficient, of course - program to read a number of lines from a textfile, and only allocate the exact number of strings that are needed.
{$R+}
{$APPTYPE CONSOLE}
var
X: array of string;
i: Integer;
begin
while not eof do
begin
SetLength(X, Length(X) + 1); // very inefficient...
readln(X[High(X)])
end;
for i := 0 to High(X) do
writeln(X[i])
end.
Dynamic-array variables are implicitly pointers and are managed by the same reference-counting technique used for Long Strings. To deallocate a dynamic array, assign nil to a variable that references the array or pass the variable to Finalize; either of these methods disposes of the array, provided there are no other references to it.
{$R+}
program Delphi4;
{$APPTYPE CONSOLE}
uses
Dialogs;
var
X, Y: array of string;
i: Integer;
begin
SetLength(X, 7);
Y := X;
X[3] := 'Dynamic Arrays in Delphi 4';
SetLength(X, 42);
Y := X;
SetLength(Y, 4);
ShowMessage(Y[3]);
X := nil;
Finalize(Y);
end.
Warning: we should not apply the dereference operator (^) to a dynamic-array variable or pass it to the New or Dispose procedure.
If X and Y are variables of the same dynamic-array type, X:=Y allocates X to the length of Y and points X to the same array as Y. Unlike strings, arrays are not automatically copied (i.e. made unique) before they are written to, but they keep pointed to the same - shared - memory area! For example, after this code executes
var
X, Y: array of string;
begin
SetLength(X, 1);
X[0] := 'Hello, world';
Y := X;
Y[0] := 'Answer';
end;
the value of X[0] is 'Answer'.
Unlike Long Strings, that get "split" when we change one of them (to get a unique copy), dynamic arrays keep pointed to the same area. A bit unexpected, perhaps, but at least we don't get delayed performance hits (like with Long Strings)...
Of course, since dynamic array contents are copied when we call SetLength, that's also all it takes (a call to SetLength) to create a unique copy of a dynamic array.
Assigning to a dynamic-array index (for example, X[42] := 'Answer') does not reallocate the array (we need to call SetLength to do that). Out-of-range indexes are not reported at compile time, but will raise an exception at run-time (with $R+ compiler directive).
When dynamic-array variables are compared, their references are compared, not their array values. Thus, after execution of the code
var
X, Y: array of string;
begin
SetLength(X, 1);
SetLength(Y, 1);
X[0] := 'Hello, world!';
Y[0] := 'Hello, world!';
end;
X = Y returns False but X[0] = Y[0] returns True.
To truncate a dynamic array, pass it to the Copy function and assign the result back to the array variable. For example, if X is a dynamic array, X := Copy(X, 0, 2) truncates all but the first 2 elements of X.
Once a dynamic array has been allocated, we can pass it to the standard functions Length, High, and Low. Length returns the number of elements in the array, High returns the array's highest possible index (Length - 1), and Low always returns 0. For a zero-length array, High indeed returns -1, so in that case High(X) < Low(X).
To instantiate the multi-dimensional array M (see declaration on top of this paper), we need to call SetLength with two integer arguments:
SetLength(M, 10, 5);
allocates an 10-by-5 array, and M[9,4] denotes an element of that array.
We can also create multidimensional dynamic arrays that are not rectangular. The first step is to call SetLength, passing it parameters for the first n dimensions of the array. For example,
var
M: array of array of Integer;
begin
SetLength(M, 10);
allocates ten rows for M but no columns. Then, we can allocate the columns one at a time (giving them different lengths); for example
SetLength(M[2], 42);
makes the third column of M 42 integers long. At this point (even if the other columns haven't been allocated) we can assign values to the third column for example, M[2][41] := 7.
2006. augusztus 5., szombat
Deleting or renaming open files
Problem/Question/Abstract:
Sometimes I need to handle files that are used by windows before they are loaded in the boot process, like a DLL or a VxD for example. How do I do that?
Answer:
Windows NT have a function called MoveFileEx that deletes files at reboot if used with the MOVEFILE_DELAY_UNTIL_REBOOT flag. Unfortunately, Windows 9x doesn't support this flag. So what do we do?
Every time you reboot, windows look for a file called WININIT.INI in the Windows directory. This file can contains Delete / Rename / Copy directives that will be excuted before anything is loaded (or almost). You can place commands in the [Rename] section using the syntax DESTINATION=SOURCE. If Destination is NUL, then the file will be deleted. Filenames and paths must use SHORT FILENAMES (because this file is processed before long filenames support is even loaded).
Please note that contrary to the example found in win32.hlp, you cannot use WritePrivateProfileString() or TIniFile to access this file because there might be duplicates values. If there is already one NUL value, TIniFile would overwrite it instead of creating a new one. So you better use TStringList instead.
Here are some example entries:
[rename]
NUL=C:\TEMP.TXT
NUL=C:\TEMP2.TXT
C:\NEW_DIR\EXISTING.TXT=C:\EXISTING.TXT
C:\NEW_DIR\NEWNAME.TXT=C:\OLDNAME.TXT
C:\EXISTING.TXT=C:\TEMP\NEWFILE.TXT
Below is the function DeleteLater that will just add NUL=Filename to wininit.ini, create the file if it doesn't exist, and also create the section if needed.
procedure DeleteLater(Filename: string);
var
Wininit: string;
Buffer: array[0..MAX_PATH] of char;
I, J: integer;
Ini: TStringList;
begin
FillChar(Buffer, SizeOf(Buffer), 0);
GetWindowsDirectory(Buffer, SizeOf(Buffer));
Wininit := IncludeTrailingBackslash(Buffer) + 'Wininit.ini';
Ini := TStringList.Create;
try
if FileExists(Wininit) then
Ini.LoadFromFile(Wininit);
for I := 0 to Ini.Count - 1 do
Ini[I] := Uppercase(Ini[I]);
J := Ini.IndexOf('[RENAME]');
if J = -1 then
begin
Ini.Add('[Rename]');
J := 0;
end;
FillChar(Buffer, SizeOf(Buffer), 0);
GetShortPathName(PChar(Filename), Buffer, SizeOf(Buffer));
Ini.Insert(J + 1, 'NUL=' + Buffer);
Ini.SaveToFile(Wininit);
finally
Ini.Free;
end;
end;
2006. augusztus 4., péntek
Redefining TCP/IP Client...
Problem/Question/Abstract:
How do we create a TCP/IP Server/Client in Delphi?
Answer:
This article is a redefined version of my previous article "Making an application a TCP/IP Client(with sample code)". May be this is of late but hope it’s helpful. In this article, I tried to explain/discuss, in general, how to create TCP-IP clients and servers with Delphi.
What I tried to demonstrate in that sample code?
I tried to demonstrate a simple TCP-IP client application that connects to a specific port on a TCP-IP server and exchanges data. I did not say anything about the server except that my application was talking to a TCP-IP server written in Java. But all you need to talk to a server is the address/host and port/service details. And to talk to the server, you have to make sure that the server is running. That’s it. You don’t need to worry about how that server is implemented unless you want to develop both the server and client by yourself. In this article, let me give you a brief overview of how to write a TCP-IP server in Delphi.
This is what I have done in that source code:
Established a connection to the server when the application starts.
Used a SendXml procedure to send data to the server.
Used OnClientSocketRead event to read back the data from the server.
Used OnClientSocketError event to catch the errors
You will see a Boolean flag called fWaiting in both the SendXml and OnClientSocketRead procedures. In my application, I will send some data to the server and wait for the server to respond back. I’ll essentially wait for the OnClientSocketRead event to happen hoping that the server responds ASAP. That is the reason I used the Boolean flag fWaiting.
Where to start to have an idea of what TCP-IP client and TCP-IP server?
For people who want to try, I would suggest them to have a look at the demo project called Chat.dpr in both Delphi 5 and 6. Following are the paths where you can find the demo project:
Delphi 5:
C:\ Program Files\Borland\Delphi5\Demos\Internet\Chat\chat.dpr
Delphi 6:
C:\ Program Files\Borland\Delphi6\Demos\Internet\Chat\chat.dpr
What is basically a TCP-IP client?
It’s an application that connects to a specific port on a TCP-IP server and exchanges data either as a stream or text.
What do you need to create one with Delphi?
All you need is a TClientSocket component available on the internet palette and set the following properties:
Address: You can enter the IP address of the TCP-IP server that this client connects to.
Host: Instead of Address you can enter an alias name for the IP address here in the Host property. It’s obvious to set either the Address or Host property. But what happens if you set both? Host property takes precedence over the Address property.
Setting the Host property would be better compared to the Address property since even if you change the TCP-IP server to some other machine or change the IP address to something else, as long as you keep the alias name same, you are fine; you don’t need to worry about changing it on the TCP-IP client. (Even though it’s a slight overhead of resolving the host name to its corresponding IP address, it’s worth having it)
Port: It’s a valid integer port number where the TCP-IP server listens. (and responds.) It’s always a fixed number assigned by the server. To this port number, the client will connect to and send and receive data.
Service: As Host and Address properties are linked together in one way, Port and Service properties are also linked. In general, Service is something the TCP-IP server can provide to its clients like http, ftp. These standard services have been assigned a specific port number on the server. (e.g. http – port 80). How that works? On the server, there is a services file which maps services to their respective ports. So like that, you can have your own service description that maps to a specific port number on the server. In that case, the client can connect to the service using the Service property and interact.
ClientType: This property determines whether the interaction between the server and client occurs synchronously or asynchronously.
ctNonBlocking – The default. This indicates that the interaction between the server and client occurs asynchronously. i.e. the client can send data to the server and wait for the OnRead data to occur whenever the data is sent back from the server.
ctBlocking – This is used for interaction to occur synchronously between the server and the client.
When to make the client code thread-safe?
If more than one request is sent to the server from a client application at a time, then the client code should be thread-safe. Otherwise, the request-response may collide.
TCP-IP Server
TCP-IP Client 1
Code should be
Multiple requests at a time
Thread-safe
Code should be thread-safe
ServerType = stThreadBlocking
ClientType = ctBlocking
Fig. 1. A TCP-IP server receiving multiple requests from a single client at a time.
TCP-IP Server
TCP-IP Client 1
Code should be
Single request at a time
Thread-safe
Code need not be thread-safe
ServerType = stThreadBlocking
ClientType = ctNonBlocking
TCP-IP Client 2
Single request at a time
Code need not be thread-safe
ClientType = ctNonBlocking
Fig. 2. A TCP-IP Server receiving multiple requests from various clients at a time.
When to make the server code thread-safe?
If more than one request is received either from a client application or different client applications at a time, then the server code should be thread-safe. This is applicable to all TCP-IP servers written in other languages also.
Hope I’m making myself clear on the thread-safe part.
Which is the best place to put the TClientSocket component in an application?
You can put the component either in a Form or DataModule. If you just have a single form in the client application that talks to a server, then it’s okay to put the TClientSocket component in that form. But if you have many forms in the client application, then it’s better to put the component in a DataModule and use that DataModule wherever needed. I used a DataModule in my client application since I had many forms each talking to the server at a different point of time.
How do you establish a connection to the server?
Set all those properties mentioned above and then set either the Active property to true or call the Open method. Once you establish a connection, you can send the data using either the SendText or SendStream method. And you can use the OnClientSocketRead event to read the data back from the server.
What is a TCP-IP server?
It’s an application that listens at a particular port and responds to clients. It could be any standard servers like http, ftp or a custom server made for your specific application.
What do you need to create on with Delphi?
As with the client, you need a TServerSocket component with the following properties set:
Port: You can assign any valid integer value. To this port, the client can connect to and interact with the server.
Service: As I told previously, you can have a service name associated with a port number.
Whenever we say server, it should be able to serve more than one client obviously; then only it makes sense to have a server. Right? Now the next question is how these clients are talking to the server: more than one client at a time or one client at a time. It leads to the following property setting that determines whether the clients talk to the server synchronously or asynchronously.
How do we handle more than one client requests at a time?
The answer is to spawn a new thread for each client request. This can be achieved by setting the ServerType property to stThreadBlocking. Does spawning and destroying a thread for each client request an overhead? Yes. Obviously. But if our application design requires it, then there is no other way; you have to have that overhead. Can we reduce that overhead of creating and destroying threads? Yes. We can. How? Cache those threads. Right.
The ThreadCacheSize property serves that purpose. The default value is 10 but this value depends on your client application needs. You should be very careful in setting this value. If you set it to a maximum value, you will end up in memory problems. If you set it to a very low value, the client will have a wait time for each request. So you have to determine a best value based on the client statistics.
If the client requests are coming one at a time, then you can set the ServerType property to stNonBlocking.
How do we read data back from the client?
As with ClientSocket, we have OnClientRead event to read data from the client among other events.
That’s all. Hope you can write yourself both a client and server with this information.
2006. augusztus 3., csütörtök
Virtual Methods and Polymorphism Part 1
Problem/Question/Abstract:
Virtual Methods, Inside Out
Answer:
Polymorphism is perhaps the cornerstone of object-oriented programming (OOP). Without it, OOP would have only encapsulation and inheritance - data buckets and hierarchical families of data buckets - but no way to uniformly manipulate related objects.
Polymorphism is the key to leveraging your programming investments to enable a relatively small amount of code to drive a wide variety of behaviors, without requiring carnal knowledge of the implementation details of those behaviors. However, before you can extend existing Delphi components, or design new, extensible component classes, you must have a firm understanding of how polymorphism works and the opportunities it provides.
True to its name, polymorphism allows objects to have "many forms" in Delphi, and a component writer typically uses a mix of all these forms to implement a new component. In this article, we'll closely review the implementation and use of one of Delphi's polymorphism providers, the virtual method, and some of its more peculiar sand traps and exotic applications, e.g. its part in making .EXEs smaller. (Dynamic methods, message methods, and class reference types are Delphi's other polymorphism providers, but are outside the scope of this article.)
This article assumes you are familiar with Delphi class declaration syntax and general OOP principles. If you're a bit rusty with these concepts, you should first refer to the Delphi Language Reference. Also note that in this article, "virtual" denotes the general term that applies to all forms of virtual methods (i.e. methods declared with virtual, dynamic, or override), and "virtual" denotes the specific term that refers only to methods declared with the virtual directive. For example, most polymorphism concepts and issues apply to all virtual methods, but there are a few noteworthy items that apply only to virtual methods.
Review: Syntax of Virtual Methods
Here's a review of the two kinds of virtual methods and four language directives used to declare them:
Virtual methods come in two flavors: virtual and dynamic. The only difference between them is their internal implementations; that is, they use different techniques to achieve the same results.
Calls to virtual methods are dispatched more quickly than calls to dynamic methods.
Seldom-overridden virtual methods require much more storage space for their compiler-generated tables than dynamic methods.
The keywords, virtual and dynamic, always introduce a new method name into a class' name space.
The override directive redefines the implementation of an existing virtual method (virtual or dynamic) that a class inherits from an ancestor.
The override method uses the same dispatch mechanism (virtual or dynamic) as the inherited virtual method it replaces.
The abstract directive indicates that no method body is associated with that virtual method declaration. Abstract declarations are useful for defining a purely conceptual interface, which is in turn useful for maintaining absolute separation between the user of a class and its implementation.
The abstract directive can only be used in the declaration of new virtual (virtual or dynamic) methods; you can't make an implemented method abstract after the fact.
A class type that contains one or more abstract methods is an abstract class.
A class type that contains nothing but abstract methods (no static methods, no virtual methods, no data fields) is called an abstract interface (or, in C++ circles, a pure virtual interface).
Polymorphism in Action
What do virtual methods do? In general, they allow a method call to be directed, at run time, to the appropriate piece of code, appropriate for the type of the object instance used to make the call. For this to be interesting, you must have more than one class type, and the class types must be related by inheritance from a common ancestor.
Figure 1 shows three classes we'll use to explore the execution characteristics of polymorphism: a simple base class named TBaseGadget that defines a static method named NotVirtual and a virtual method, ThisIsVirtual; and two descendant classes, TKitchenGadget and TOfficeGadget, that override the ThisIsVirtual method they inherit from TBaseGadget. TOfficeGadget also introduces a new static method named NotVirtual and a new virtual method named NewMethod.
type
TBaseGadget = class
procedure NotVirtual(X: Integer);
procedure ThisIsVirtual(Y: Integer); virtual;
end;
TKitchenGadget = class(TBaseGadget)
procedure ThisIsVirtual(Y: Integer); override;
end;
TOfficeGadget = class(TBaseGadget);
function NewMethod: Longint; virtual;
procedure NotVirtual(X, Y, Z: Integer);
procedure ThisIsVirtual(Y: Integer); override;
end;
Figure 1: Three classes to explore polymorphism.
Identical names in different classes aren't related. Declaring a static method in a descendant that happens to have the same name as a static method in an ancestor is not a true override. Other than same-name similarity, no relationship exists between static methods declared in a descendant and static methods declared in an ancestor class. Your brain makes an association, but the compiler does not. For instance, TBaseGadget has a NotVirtual method, and TOfficeGadget has a disparate method, also named NotVirtual.
If we start with a variable P of type TBaseGadget, we can assign to it an instance of a TBaseGadget; or an instance of one of its descendants, such as a TKitchenGadget or TOfficeGadget. Recall that Delphi object instance variables are pointers to the instance data allocated from the global heap, and that pointers of a class type are type compatible with all descendants of that type. We can then call methods using the instance variable P:
var
P: TBaseGadget;
begin
P := TBaseGadget.Create;
P.NotVirtual(10); { Call TBaseGadget.NotVirtual }
P.ThisIsVirtual(5); { Call TBaseGadget.ThisIsVirtual }
P.Free;
end;
(In the interest of brevity, I'll fold the execution traces into comments in the source code. You can step through the sample code to verify the execution trace.)
If P refers to an instance of TKitchenGadget, the execution trace would resemble the code in Figure 2. Nothing remarkable here; we have one call to a static method going to the version defined in the ancestor type, and one call to a virtual method going to the version of the method associated with the object instance type.
var
P: TBaseGadget;
begin
P := TKitchenGadget.Create;
P.NotVirtual(10); { Call TBaseGadget.NotVirtual }
P.ThisIsVirtual(5); { Call TKitchenGadget.ThisIsVirtual }
P.Free;
end;
Figure 2: Execution with an instance of TKitchenGadget.
You may deduce that the inherited static method, NotVirtual, is called because TKitchenGadget doesn't override it. This observation is correct, but the explanation is flawed, as Figure 3 shows. If P refers to an instance of TOfficeGadget, you may be a little puzzled by the result.
var
P: TBaseGadget;
begin
P := TOfficeGadget.Create;
P.NotVirtual(10); { Call TBaseGadget.NotVirtual }
{ The compiler will not allow the following two lines:
P.NotVirtual(1,2,3); "Too many parameters"
P.NewMethod; "Method identifier expected" }
P.ThisIsVirtual(5); { Call TOfficeGadget.ThisIsVirtual }
P.Free;
end;
Figure 3: Execution with an instance of TOfficeGadget.
Static method calls are resolved by variable type. Although TOfficeGadget has its own NotVirtual method, and P refers to an instance of TOfficeGadget, why does TBaseGadget.NotVirtual get called instead? This occurs because static (non-virtual) method calls are resolved at compile time according to the type of the variable used to make the call. For static methods, what the variable refers to is immaterial. In this case, P's type is TBaseGadget, meaning the NotVirtual method associated with P's declared type is TBaseGadget.NotVirtual.
Notice that NewMethod defined in TOfficeGadget is out of reach of a TBaseGadget variable. P can only access fields and methods defined in its TBaseGadget object type.
New names obscure inherited names. Let's say P is declared as a variable of type TOfficeGadget. The following method call would be allowed:
P.NotVirtual(1, 2, 3)
However, this method call:
P.NotVirtual(1)
would not be allowed, because TOfficeGadget.NotVirtual requires three parameters.
TOfficeGadget.NotVirtual obscures the TBaseGadget.NotVirtual method name in all instances and descendants of TOfficeGadget. The inherited method is still a part of TOfficeGadget (proven by the code in Figure 3); you just can't get to it directly from TOfficeGadget and descendant types.
To get past this, you must typecast the instance variable:
TBaseGadget(P).NotVirtual(1)
If P were declared as a TOfficeGadget variable, P.NewMethod would also be allowed, because the compiler can "see" NewMethod in a TOfficeGadget variable.
Descendant >= ancestor. An instance of a descendant type could be greater than its ancestor type in both services and data. However, the descendant-type instance can never be less than what its ancestors define. This makes it possible for you to use a variable of an ancestral type (e.g. TBaseGadget) to refer to an instance of a descendant type without loss of information.
Inheritance is a one-way street. With a variable of a particular class type, you can access any public symbol (field, property, or method) defined in any of that class' ancestors. You can assign an instance of a descendant class into that variable, but cannot access any new fields or methods defined by the descendant class. The fields of the descendant class are certainly in the instance data that the variable refers to, yet the compiler has no way of knowing that run-time situation at compile time.
There are two ways around this "nearsightedness" of ancestral class types:
Typecasting - The programmer assumes a lot and forces the compiler to treat the variable as a descendant type.
Virtual methods - The magic of virtual will call the method appropriate to the type of the associated instance, determined at run time.
Ancestors set the standard. Why do we care about the nearsightedness of ancestral classes? Why not simply use the matching variable type when you create or manipulate an object instance? Sometimes this is the simplest thing to do. However, this "simplest" solution falls apart when you begin talking about manipulating multiple classes that do almost the same things.
Ancestral class types set the minimum interface standard through which we can access a set of related objects. Polymorphism is the use of virtual methods to make one verb (method name) produce one of many possible actions depending on the context (the instance). To have multiple, possible actions, you must have multiple class types (e.g. TKitchenGadget and TOfficeGadget) each potentially defining a different implementation of a particular method.
To be able to make one call that could cover those multiple class types, the method must be defined in a class from which all the multiple class types descend - in an ancestral class such as TBaseGadget. The ancestral class, then, is the least common denominator for behavior across a set of related classes.
For polymorphism to work, all the actions common to the group of classes need to at least be named in a common ancestor. If every descendant is required to override the ancestor's method, the ancestral method doesn't need to do anything at all; it can be declared abstract.
If there is a behavior that is common to most of the classes in the group, the ancestor class can pick up that default behavior and leave the descendants to override the defaults only when necessary. This consolidates code higher in the class hierarchy, for greater code reuse and smaller total code size. However, providing default behaviors in an ancestor class can also complicate the design issues of creating flexible, extensible classes, since what is done by ancestors usually cannot be entirely undone.
Polymorphism lets ancestors reach into descendants. Another aspect of polymorphism doesn't appear to involve instance pointer types at all - at least not explicitly.
Consider the code fragment in Figure 4. The TBaseGadget.NotVirtual method contains an unqualified call to ThisIsVirtual. When P refers to an instance of TKitchenGadget, P.NotVirtual will call TBaseGadget.NotVirtual. Nothing new, so far. However, when that code calls ThisIsVirtual, it will execute TKitchenGadget.ThisIsVirtual. Surprise! Even within the depths of TBaseGadget, a non-virtual method, a virtual method call is directed to the appropriate code.
procedure TBaseGadget.NotVirtual;
begin
ThisIsVirtual(17);
end;
var
P: TBaseGadget;
begin
P := TKitchenGadget.Create;
P.NotVirtual(10); { Call TBaseGadget.NotVirtual }
P.Free;
end.
Figure 4: Polymorphism allows ancestors to call into descendants.
How can this be? The resolution of virtual method calls depends on the object instance associated with the call. A pointer to the object instance is secretly passed into all method calls, surfacing inside methods as the Self identifier. Inside TBaseGadget.NotVirtual, a call to ThisIsVirtual is actually a call to Self. ThisIsVirtual. Self, in this context, operates like a variable of type TBaseGadget that refers to an instance of type TKitchenGadget. Thus, when the instance type is TKitchenGadget, the virtual method call resolves, at run time, to TKitchenGadget.ThisIsVirtual.
How is this useful? An ancestral method - virtual or not - can call a sequence of virtual methods. The descendants can determine the specific behavior of one or more of those virtual methods. The ancestor determines the sequence in which the methods are called, plus miscellaneous setup and cleanup code. The ancestor, however, does not completely determine the final behavior of the descendants. The descendants inherit the sequence logic from the ancestor, and can override one or more of the steps in that sequence. But, the descendants don't have to reproduce the entire sequence logic. This is one of the ways OOP promotes code reuse.
Fully-qualified method calls are reduced to static calls. As a footnote, consider what happens if TBaseGadget.NotVirtual contains a qualified call to TBaseGadget.ThisIsVirtual:
procedure TBaseGadget.NotVirtual;
begin
TBaseGadget.ThisIsVirtual(17);
end;
Although ThisIsVirtual is a virtual method, a fully-qualified method call will compile down to a regular static method call. You've specified that you want only the TBaseGadget.ThisIsVirtual method called, so the compiler does exactly what you tell it to do. Dispatching this as a virtual method call may call some other version of that method, which would violate your explicit instructions. Except in special circumstances, you don't want this in your code because it defeats the whole purpose of making ThisIsVirtual virtual.
The Virtual Method Table
A Virtual Method Table (VMT) is an array of pointers to all the virtual methods defined in a class and all the virtual methods the class inherits from its ancestors. A VMT is created by the compiler for every class type, because all classes descend from TObject and TObject has a virtual destructor named Destroy. In Delphi, VMTs are stored in the program's code space. Only one VMT exists per class type; multiple instances of the same class type refer to the same VMT. At run time, the VMT is a read-only lookup table.
Structure of the VMT. The first four bytes of data in an object instance are a pointer to that class type's VMT. The VMT pointer points to the first entry in the VMT's list of four-byte pointers to the entry points of the class' virtual methods. Since methods can never be deleted in descendant classes, the location of a virtual method in the VMT is the same throughout all descendant classes. Thus, the compiler can view a virtual method simply as a unique entry in the class' VMT. As we'll see shortly, this is exactly how virtual method calls are dispatched. Thinking of virtual methods as indexes into an array of code pointers will also help us visualize how method name conflicts are resolved by the compiler.
The VMT does not contain information indicating how many virtual methods are stored in it or where the VMT ends. The VMT is constructed by the compiler and accessed by compiler-generated code, so it doesn't need to make notes to itself about size or number of entries. (This does, however, make it difficult for BASM code to call virtual methods.)
Optimization note. A descendant of a class with virtual methods gets a new copy of the ancestor's VMT table. The descendant can then add new virtual methods or override inherited virtual methods without affecting the ancestor's VMT. For example, if the ancestor has a 12-entry VMT, the descendant has at least a 12-entry VMT. Every descendant class type of that ancestor, and all descendants of those descendants, will have at least 12 entries in their individual VMTs.
All these VMTs occupy memory. For most programs, this won't be a problem, but extraordinarily large class types with thousands of virtual methods and/or thousands of descendants could consume quite a bit of memory, both in RAM and .EXE file size; dynamic methods are much more space efficient, but incur a slight execution speed penalty.
Now let's examine the mechanics behind the magic of virtual method calls.
Inside a virtual method call. When the compiler is compiling your source code and encounters a call to a virtual method identifier, it generates a special sequence of machine instructions that will unravel the appropriate call destination at run time. The following machine code snippets assume compiler optimizations are enabled, and stack frames are disabled:
// Machine code for statement P.SomeVirtualMethod;
{ Move instance data address (P^) into EAX }
MOV EAX, [EBP + 4]
{ Move instance's VMT address into ECX }
MOV ECX, [EAX]
{ Call address stored at VMT index 2 }
CALL[ECX + 08]
The VMT pointer is always stored at offset 0 (zero) in the instance data. In this example, the method being called is the third virtual method of a class, including inherited virtual methods. The first virtual method is at offset 0, the second at offset 4, and the third at offset 8.
Conclusion
That's it - all the magic of virtual methods and polymorphism boils down to this: the indicator of which virtual method to invoke on the instance data is stored in the instance data itself.
In Part II, we'll conclude our series with a discussion of abstract interfaces and how virtual methods can defeat and enhance "smart linking." See you then.
2006. augusztus 2., szerda
Parse a wave file
Problem/Question/Abstract:
Access each chunk within a wave file is a tricky business but sometime you need to access the actual samples/data to get what you want...so how can that be done?
Answer:
A WAV file is binary file in the RIFF format, RIFF format enables the user to haev multiple information in the same file which can either be used or not.
The information is stored in chunks, each chunk have its type (4 chars) and side (dword) so it can be skipped if you are not interested in the data or to be read from the file.
You can download the demo software that shows wave file in a signal display graph with functions as: paning, zoom, multiple audio channels and more from
http://www.com-n-sense.com/ftproot/SignalDisplay.zip
(the zip file contains the wavefileparser component and signaldisplay component).
The following code parses WAV files into accessable chunks:
{*==============================================================================
Copyright (C) 2002, All rights reserved, Com-N-Sense Ltd
================================================================================
File: WaveFileParser.pas
Author: Liran Shahar, Com-N-Sense Ltd
Updated: 24/03/2002
Purpose: Parsing wave file into chunks
================================================================================
24/03/2002, Liran Shahar
- Initial release.
==============================================================================*}
unit WaveFileParser;
interface
uses
Sysutils, Classes;
type
TChunkType = array[1..4] of char;
PChunk = ^TChunk;
TChunk = packed record
cType: TChunkType;
dwSize: cardinal;
pData: pointer;
end;
TcnsWaveFileParser = class(TPersistent)
private
FFilename: AnsiString;
Chunks: TList;
protected
procedure SetFilename(AFilename: AnsiString); virtual;
function GetChunksCount: integer; virtual;
function GetChunk(Index: integer): PChunk; virtual;
procedure ProcessFile; virtual;
procedure ClearChunks; virtual;
public
constructor Create; virtual;
destructor Destroy; override;
function GetChunkByType(ChunkType: TChunkType): PChunk; virtual;
property Filename: AnsiString read FFilename write SetFilename;
property ChunksCount: integer read GetChunksCount;
property Chunk[Index: integer]: PChunk read GetChunk;
end;
implementation
const
RIFF_SIGNATURE = 'RIFF';
WAVE_SIGNATURE = 'WAVE';
type
TRIFFHeader = packed record
cSignature: TChunkType;
dwSize: cardinal;
cType: TChunkType;
end;
constructor TcnsWaveFileParser.Create;
begin
inherited Create;
FFilename := '';
Chunks := TList.Create;
end;
destructor TcnsWaveFileParser.Destroy;
begin
ClearChunks;
inherited Destroy;
end;
procedure TcnsWaveFileParser.SetFilename(AFilename: AnsiString);
begin
if FFilename <> AFilename then
begin
ClearChunks;
FFilename := AFilename;
ProcessFile;
end; // if
end;
function TcnsWaveFileParser.GetChunksCount: integer;
begin
Result := Chunks.Count;
end;
function TcnsWaveFileParser.GetChunk(Index: integer): PChunk;
begin
Result := nil;
if (Index > -1) and (Index < Chunks.Count) then
Result := Chunks[Index];
end;
procedure TcnsWaveFileParser.ProcessFile;
var
WaveFile: TFileStream;
Header: TRIFFHeader;
Chunk: PChunk;
begin
try
WaveFile := TFileStream.Create(FFilename, fmOpenRead + fmShareDenyWrite);
WaveFile.Read(Header, sizeof(Header));
if (AnsiCompareText(Header.cSignature, RIFF_SIGNATURE) = 0) and
(AnsiCompareText(Header.cType, WAVE_SIGNATURE) = 0) then
begin
while WaveFile.Position < WaveFile.Size do
begin
Chunk := AllocMem(sizeof(TChunk));
with Chunk^ do
begin
WaveFile.Read(cType, sizeof(cType));
WaveFile.Read(dwSize, sizeof(dwSize));
pData := AllocMem(dwSize);
WaveFile.Read(pData^, dwSize);
end; // with
Chunks.Add(Chunk);
end; // while
end; // if
finally
FreeAndNil(WaveFile);
end;
end;
procedure TcnsWaveFileParser.ClearChunks;
var
Chunk: PChunk;
begin
while Chunks.Count > 0 do
begin
Chunk := Chunks[0];
Chunks.Delete(0);
if assigned(Chunk^.pData) then
FreeMem(Chunk^.pData);
dispose(Chunk);
end; // while
end;
function TcnsWaveFileParser.GetChunkByType(ChunkType: TChunkType): PChunk;
var
iIndex: integer;
begin
Result := nil;
iIndex := 0;
while iIndex < Chunks.Count do
if AnsiCompareText(PChunk(Chunks[iIndex])^.cType, ChunkType) = 0 then
begin
Result := Chunks[iIndex];
break;
end
else
iIndex := iIndex + 1;
end;
end.
Component Download: http://www.com-n-sense.com/ftproot/SignalDisplay.zip
2006. augusztus 1., kedd
Avoid direct input into a TDBGrid when there is a lookup list available (2)
Problem/Question/Abstract:
Could somebody please tell me how I can make a column in a dbgrid only accept a value from a picklist without the user being able to type something into the cell manually?
Answer:
Override the Grid's protected KeyPress, like this:
procedure TPresPLRMask_DbGrid.KeyPress(var Key: Char);
var
col: TColumn;
begin
inherited;
if SelectedIndex < 0 then
exit;
col := Columns[SelectedIndex];
if (col.PickList.Count > 0) and (col.ButtonStyle = cbsAuto) then
begin
{no keys allowed except the TAB key (arrow keys down fire KeyPress,
they don't need to be handled here)}
if (Key <> #9) then
begin
Key := #0;
Abort;
end;
end;
end;
Feliratkozás:
Bejegyzések (Atom)