2008. május 31., szombat
Large DSM files - can they be truncated?
Problem/Question/Abstract:
A quick scan thru my hard disk showed me over 300 megabytes with .DSM files. As I can delete them and Delphi will generate them later when building, is there any reason to keep them on disk?
Answer:
Go to 'Environment Options' and select 'save desktop only' and it won't save DSM files. The only effect is that you need to compile before the symbols are available.
2008. május 30., péntek
ListBox.Items.Add is slow and flickers
Problem/Question/Abstract:
ListBox.Items.Add is slow and flickers
Answer:
Adding a (larger) group of entries to a ListBox is very slow, because after every "items.add" call the ListBox is repainted.
There are two ways to overcome this:
Use the Windows message WM_SETREDRAW (see Win32.hlp for details). The VCL provides two methods for this: BeginUpdate and EndUpdate. I would assume that this is faster than solve #2.
Read the strings in a temporary TStringList object. Maybe you already have such a list - in this case you should use your existing list. Then use the Assign method to transfer the whole list.
Solve 1:
procedure TForm1.Button1Click(Sender: TObject);
var
i: integer;
begin
ListBox1.Items.BeginUpdate;
for i := 1 to maxitems do
ListBox1.Items.add(IntToStr(i));
ListBox1.Items.EndUpate;
end;
Solve 2:
procedure TForm1.Button2Click(Sender: TObject);
var
i: integer;
tmp: tstringlist;
begin
tmp := TStringList.Create;
for i := 1 to maxitems do
tmp.add(inttostr(i));
ListBox1.Items.Assign(tmp);
tmp.Free;
end;
2008. május 29., csütörtök
Display a popup menu at a certain position in a TTreeView
Problem/Question/Abstract:
I want to get the exact position (in terms of x, y coordinates) within a treeview. The reason is that I want a popup menu to appear after a certain keypress.
Answer:
Solve 1:
procedure TForm1.TreeView1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var
rect: TRect;
begin
if assigned(TreeView1.Selected) then
begin
rect := TreeView1.selected.DisplayRect(true);
{Because the popup function pops up on the screen, you need to add the form
coordinates, the treeview coordinates, and then the displayrect coordinates of
the item.}
PopupMenu1.Popup(Form1.Top + TreeView1.Top + rect.Top,
Form1.Left + TreeView1.Left + rect.Left);
end;
end;
Solve 2:
Here's an example of a popup menu that launches when a user clicks on a node in a TTreeView.
procedure TfrmExplorer.TreeViewMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
var
P: TPoint;
begin
if Button <> mbRight then
exit;
TreeMenu.AutoPopup := False;
if TreeView.GetNodeAt(X, Y) <> nil then
begin
TreeView.Selected := TreeView.GetNodeAt(X, Y);
P.X := X;
P.Y := Y;
P := TreeView.ClientToScreen(P);
TreeMenu.Popup(P.X, P.Y);
end;
end;
2008. május 28., szerda
How to create a lookup field at runtime
Problem/Question/Abstract:
How to create a lookup field at runtime
Answer:
var
f: TField;
i: Integer;
begin
Table1.FieldDefs.Update
Table1.Close;
for i := 0 to Table1.FieldDefs.Count - 1 do
if table1.FindField(table1.FieldDefs[i].Name) = nil then
{persistent field does not exist}
Table1.FieldDefs.Items[i].CreateField(Table1);
f := TStringField.Create(Table1);
f.Name := 'Table1lookup';
f.FieldName := 'lookup';
f.DisplayLabel := 'lookup';
f.fieldType := fklookup;
f.Calculated := True;
f.DataSet := Table1;
f.lookupDataSet := table2;
f.Keyfields := 'Keyfield1';
f.Lookupfields := 'Keyfield1';
f.LookupResultField := 'ResultField';
Table1.Open;
end;
2008. május 27., kedd
How to add data to the columns PickList property of a TDBGrid at runtime
Problem/Question/Abstract:
How can we add data (say, x, y, z) to a DBGrid Column's PickList property at runtime?
Answer:
To add to the third column:
DBGrid1.Columns[2].PickList.Add('x');
DBGrid1.Columns[2].PickList.Add('y');
DBGrid1.Columns[2].PickList.Add('z');
2008. május 26., hétfő
Check if ActiveX is installed on a target machine
Problem/Question/Abstract:
How to check if ActiveX is installed on a target machine
Answer:
Solve 1:
Use the CLSIDFromProgID method:
{ ... }
var
strOLE: string;
begin
strOLE = "YourCOMServer.Application" {your ProgID}
if (CLSIDFromProgID(PWideChar(WideString(strOLE), ClassID) = S_OK) then
begin
{ ... }
end;
end;
Solve 2:
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;
2008. május 25., vasárnap
Editor macro recording in Delphi
Problem/Question/Abstract:
Is there a feature in Delphi 5 that lets you record your keystrokes and then allows you to re-play it any number of times?
Answer:
Ctrl+Shift+R : Start Recording
Ctrl+Shift+R (again) : Stop Recording
Ctrl+Shift+P : Play it
You can't save your recording, though.
It has several features that make it very powerful:
Ability to record copy, cut and paste keystrokes (Ctrl-C, Ctrl-X and Ctrl-V).
Abilty to record Ctrl-Left and Crtl-Right cursor movements - useful for navigating on several lines of similar structure but varying lengths.
Ability to record the Incremental search keystroke (Ctrl-E) which makes it easy to record a macro that will search for the next occurance of a string and then carry out further manipulation of the text.
Another good feature in D5: Quick navigating between class declaration and implementation using Shitf+Control+Cursor Up/Down. That will jump from the definition to the implementation and back. Very handy!
2008. május 24., szombat
How to color TPanels on a form according to their Tag properties
Problem/Question/Abstract:
How to color TPanels on a form according to their Tag properties
Answer:
{ ... }
var
ix: Integer;
pnl: TPanel;
{ ... }
for ix := 0 to ComponentCount - 1 do
begin
if Components[ix] is TPanel then
begin
pnl := TPanel(Components[ix]);
case pnl.Tag of
1: pnl.Color := clRed;
2: pnl.Color := clBlue;
else
pnl.Color := clBtnFace;
end;
end;
end;
Try placing the above code in a button OnClick event handler (having dropped some TPanels on the form with different Tag values).
2008. május 23., péntek
How to read very large text files fast
Problem/Question/Abstract:
Does anyone know the fastest way to read large text files (10Mb) into a string. Readln is just too slow.
Answer:
Solve 1:
You may try this:
function R(const FileName: string): string;
var
M: TFileStream;
begin
M := TFileStream.Create(FileName, fmOpenRead);
try
SetLength(Result, M.Size);
M.Read(Result[1], M.Size);
finally
M.Free;
end;
end;
Solve 2:
As an alternative to Christian's suggestion, you can also use a memory-mapped file:
function MMFileToString(const AFilename: string): string;
var
hFile: THandle;
hFileMap: THandle;
hiSize: DWORD;
loSize: DWORD;
text: string;
view: pointer;
begin
Result := '';
if AFilename = '' then
Exit;
if not FileExists(AFilename) then
Exit;
{Open the file}
hFile := CreateFile(PChar(AFilename), GENERIC_READ, FILE_SHARE_READ, nil,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if hFile <> INVALID_HANDLE_VALUE then
begin
loSize := GetFileSize(hFile, @hiSize);
{File was opened successfully, now map it:}
hFileMap := CreateFileMapping(hFile, nil, PAGE_READONLY, hiSize,
loSize, 'TextForString');
if (hFileMap <> 0) then
begin
if (GetLastError() = ERROR_ALREADY_EXISTS) then
begin
MessageDlg('Mapping already exists - not created.', mtWarning, [mbOk], 0);
CloseHandle(hFileMap)
end
else
begin
try
{File mapped successfully, now map a view of the file into
the address space:}
view := MapViewOfFile(hFileMap, FILE_MAP_READ, 0, 0, 0);
if (view <> nil) then
begin {View mapped successfully}
CloseHandle(hFile);
{Close file handle - as long is view is open it will persist}
SetLength(Result, loSize);
Move(view^, Result[1], loSize);
end
else
MessageDlg('Unable to map view of file. ' + SysErrorMessage(GetLastError),
mtWarning, [mbOk], 0);
finally
UnmapViewOfFile(view); {Close view}
CloseHandle(hFileMap); {Close mapping}
end
end
end
else
begin
MessageDlg('Unable to create file mapping. ' + SysErrorMessage(GetLastError),
mtWarning, [mbOk], 0);
end;
end
else
begin
MessageDlg('Unable to open file. ' + SysErrorMessage(GetLastError),
mtWarning, [mbOk], 0);
end;
end;
2008. május 22., csütörtök
Display forms full screen
Problem/Question/Abstract:
How can I show a form so that it covers all available screen space including the taskbar?
Answer:
Covering the entire screen with a form is relatively easy to accomplish as shown below.
procedure TfrmMainForm.FormCreate(Sender: TObject);
begin
{ Position form }
Top := 0;
Left := 0;
{ Go full screen }
WindowState := wsmaximized;
ClientWidth := Screen.Width;
ClientHeight := Screen.Height;
Refresh;
end;
If this is a typical form it will have borders which you might consider removing by setting BorderStyle property to bsNone as shown below.
procedure TfrmMainForm.FormCreate(Sender: TObject);
begin
{ Position form }
Top := 0;
Left := 0;
{ Go full screen }
BorderStyle := bsNone;
WindowState := wsmaximized;
ClientWidth := Screen.Width;
ClientHeight := Screen.Height;
Refresh;
end;
Sometimes the code shown above will go full screen but still display the Windows TaskBar, if this happens we can force the form on top using either SetForeGroundWindow or SetActiveWindow. From my testing it is best to use both if the problem persist.
procedure TfrmMainForm.FormCreate(Sender: TObject);
begin
{ Position form }
Top := 0;
Left := 0;
{ Go full screen }
BorderStyle := bsNone;
WindowState := wsmaximized;
ClientWidth := Screen.Width;
ClientHeight := Screen.Height;
Refresh;
SetForegroundWindow(Handle);
SetActiveWindow(Application.Handle);
end;
Other considerations (see attachment for code to address these items) If the form is already in maximized window state the above code will not work. Controlling the system menu commands as per above needs to be considered Ghost items in the TaskBar after terminating your application.
Delphi makes it simple to cover the display screen but what if you need to duplicate the functionality in another programming language such as Microsoft Visual Basic? Well in this case you might want to learn the API methods (again see attachment).
Component Download: KG_FullScreen.zip
2008. május 21., szerda
Create a virtual drive
Problem/Question/Abstract:
Does anybody know how to create a virtual drive from a Delphi program?
Answer:
You can try this. I tested it in Windows ME and it works ok.
{ ... }
if DefineDosDevice(DDD_RAW_TARGET_PATH, 'P:', 'F:\Backup\Music\Modules') then
ShowMessage('Drive was created successfully')
else
ShowMessage('Error creating drive");
{ ... }
2008. május 20., kedd
Storing color values in the registry/INI files
Problem/Question/Abstract:
Storing color values in the registry/INI files
Answer:
The GraphicsUnit has two useful functions:
StringToColor and ColorToString.
StringToColor will receive one of the valid 37 colors (such as clWhite) or a number (as a string) representing the color and return a TColor for it.
ColorToString will do the reverse.
Nice if you want to store colors as a name instead of a number.
2008. május 19., hétfő
Setting read-only columns in StringGrid
Problem/Question/Abstract:
Setting read-only columns in StringGrid
Answer:
In the OnSelectCell event, this works fine (every even column is editable)
if Col mod 2 = 0 then
grd.Options := grd.Options + [goEditing]
else
grd.Options := grd.Options - [goEditing];
2008. május 18., vasárnap
How to extend the maximum length of a TRichEdit
Problem/Question/Abstract:
I'm trying to load a 200kb file into a richedit but cannot add any new text to it. I can delete and replace, but not add.
Answer:
Probably the MaxLength of the TRichEdit gets set to the file size. Set it to something several times larger than the file after load, e.g.
with richedit1 do
maxLength := 10 * GetTextLen;
2008. május 17., szombat
How to connect to a mySQL database
Problem/Question/Abstract:
How to connect to a mySQL database
Answer:
Perhaps you have already seen the uses clause. You may download mySQL.pas from www.fichtner.net/delphi
uses mySQL;
procedure Connect;
var
myServer: PMysql;
Tables: PMYSQL_RES;
TableRows: my_ulonglong;
Table: PMYSQL_ROW;
begin
myServer := mysql_init(nil);
if myServer <> nil then
begin
if mysql_options(myServer, MYSQL_OPT_CONNECT_TIMEOUT, '30') = 0 then
begin
if mysql_real_connect(myServer, 'host', 'user', 'password', 'database', 3306,
nil, CLIENT_COMPRESS) <> nil then
begin
Tables := mysql_list_tables(myServer, nil);
if Tables <> nil then
begin
TableRows := mysql_num_rows(Tables);
while TableRows > 0 do
begin
Table := mysql_fetch_row(Tables);
Tabelle := Table[0];
Dec(TableRows);
end;
end;
end;
end;
end;
end;
2008. május 16., péntek
Get special Windows folder location
Problem/Question/Abstract:
How to get special Windows folder location
Answer:
Solve 1:
uses
...ShlObj...
var
SFolder: pItemIDList;
SpecialPath: array[0..MAX_PATH] of Char;
begin
SHGetSpecialFolderLocation(Form1.Handle, CSIDL_STARTUP, SFolder);
SHGetPathFromIDList(SFolder, SpecialPath);
Label1.Caption := StrPas(SpecialPath);
end;
Other folders :
CSIDL_BITBUCKET
CSIDL_CONTROLS
CSIDL_DESKTOP - WINDOWS\Desktop
CSIDL_DESKTOPDIRECTORY - WINDOWS\Desktop
CSIDL_DRIVES
CSIDL_FONTS - WINDOWS\FONTS
CSIDL_NETHOOD - WINDOWS\NetHood
CSIDL_NETWORK
CSIDL_PERSONAL - X:\My Documents
CSIDL_PRINTERS
CSIDL_PROGRAMS - WINDOWS\StartMenu\Programs
CSIDL_RECENT - WINDOWS\Recent
CSIDL_SENDTO - WINDOWS\SendTo
CSIDL_STARTMENU - WINDOWS\Start Menu
CSIDL_STARTUP - WINDOWS\Start Menu\Programs\StartUp
CSIDL_TEMPLATES - WINDOWS\ShellNew
Solve 2:
function WinAPI_GetWindowsDirectory: string;
begin
SetLength(Result, MAX_PATH);
SetLength(Result, GetWindowsDirectory(pchar(Result), MAX_PATH));
end;
function WinAPI_GetSystemDirectory: string;
begin
SetLength(Result, MAX_PATH);
SetLength(Result, GetSystemDirectory(pchar(Result), MAX_PATH));
end;
In fact you can get just about every special folder location
function WinAPI_SHGetSpecialFolderLocation(nFolder: integer): string;
var
pidl: PItemIDList;
begin
SHGetSpecialFolderLocation(0, nFolder, pidl);
SetLength(Result, MAX_PATH);
SHGetPathFromIDList(pidl, pchar(Result));
SetLength(Result, pchar_StrLen(pchar(Result)));
end;
Solve 3:
function GetSystemPath(Folder: Integer): string;
var
PIDL: PItemIDList;
Path: LPSTR;
AMalloc: IMalloc;
begin
Path := StrAlloc(MAX_PATH);
SHGetSpecialFolderLocation(Application.Handle, Folder, PIDL);
if SHGetPathFromIDList(PIDL, Path) then
Result := Path;
SHGetMalloc(AMalloc);
AMalloc.Free(PIDL);
StrDispose(Path);
end;
2008. május 15., csütörtök
Copy selected data from a TStringGrid to the clipboard
Problem/Question/Abstract:
I have various TStringGrid objects within my application and I want to allow the user to copy selected data to the clipboard for insertion into other programs such as Excel.
Answer:
Solve 1:
var
S: string;
i, k: Integer;
begin
S := EmptyStr;
with StringGrid1 do
begin
for i := FixedRows to RowCount - 1 do
begin
for k := FixedCols to ColCount - 1 do
begin
if k > FixedCols then
S := S + #9;
S := S + Cells[k, i];
end;
S := S + #13#10;
end;
end;
Clipboard.AsText := S;
This generates a string in which columns are separated by Tab characters and rows by CR/LF linebreaks. Most spreadsheets are able to paste this into cells correctly.
Same for the selection in a grid:
S := '';
with grid do
for i := Selection.Top to Selection.Bottom do
begin
for k := Selection.Left to Selection.Right do
begin
S := S + Cells[k, i];
if k <> Selection.Right then
S := S + #9;
end;
S := S + #13#10;
end;
Clipboard.AsText := S;
Solve 2:
Here is how you can copy a selection.
uses
ClipBrd;
procedure CopyGridSelectionToClipBoard(Grid: TStringGrid; Selection: TGridRect);
const
TAB = Chr(VK_TAB);
CR = #13;
var
r, c: integer;
S: string;
begin
S := '';
for r := Selection.Top to Selection.Bottom do
begin
for c := Selection.Left to Selection.Right do
begin
S := S + Grid.Cells[c, r];
if c < Selection.Right then
S := S + TAB;
end;
if r < Selection.Bottom then
S := S + CR;
end;
ClipBoard.SetTextBuf(PChar(S));
end;
Pasting would be the reverse. An idea would be to get the text from the clipboard and assign it to a TStringList. This way you'll know how many rows you have.
var
F: TStringList;
begin
F := TStringList.Create;
F.Text := Clipboard.AsText;
F.Free;
end;
You'll still need to parse each row to get the columns.
By the way, you can use the text you copied with the above procedure to paste into Excel. Excel knows how to parse it if you use the TAB to delimit columns.
2008. május 14., szerda
Open a form based on a text value
Problem/Question/Abstract:
In Access and PB applications, I have implemented menus based on database tables. Ie., you select a particular entry and it load a particular form based on the text in the table. The question is, how do I do something similar in Delphi?
Answer:
Solve 1:
Let's assume that you want to show one of the forms of class TForm1, TForm2 or TForm3 and that the classname is held in a string called FormString.
Add the units holding the forms to a Uses statement - implementation section if possible (i.e Uses Form1, Form2, Form3)
Add an initialization section (before final end.):
initialization
RegisterClasses([TForm1, TForm2, TForm3]);
end.
Write a procedure to show a form. For example:
procedure TForm1.ShowForm(const fname: string);
begin
with TFormClass(FindClass(fname)).Create(Application) do
try
ShowModal;
finally
Free;
end;
end;
Thus you would call this via:
ShowForm(FormString);
Solve 2:
Yes, you can do this. The requirement is that you register all your forms (at least those that need to be created by name) using the RegisterClass method. That adds the form class and its name to an internal list maintained by the VCL. You can now call FindClass or GetClass to get the class reference back using the class name.
var
fc: TFormClass; {class of TForm, a class reference type}
form: TForm;
begin
fc := TFormClass(getClass(formclassnamefromdatabase));
if Assigned(fc) then
begin
form := fc.Create(Application);
try
form.ShowModal
finally
form.free
end;
end;
end;
The main problem here is that you cannot use the default form variables the IDE puts into the form units Interface, so better delete them to prevent accidental references to them. There is no mechanism to find a variable by name build into the VCL, but if you really need to use the form variables you could build your own registration mechanism that would build a list associating form class name, the form class, and the address of the form variable.
Once the form is created you can find a reference to it from elsewhere in the app by iterating through the Screen.Forms array, looking for a form with a specific class name.
Solve 3:
Here are a couple of approaches. As long as you are only accessing Methods and Properties common to TForm, the following will work for you. Pay special attention to the RegisterClasses call in the initialization section:
unit GenericFormCreate;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls,
Forms, Dialogs, StdCtrls, FileCtrl, Buttons, ExtCtrls;
type
TFrmGenericFormCreate = class(TForm)
BtnCreateFromString: TButton;
BtnCreateFromClass: TButton;
RgrSelect: TRadioGroup;
procedure FormCreate(Sender: TObject);
procedure BtnCreateFromStringClick(Sender: TObject);
procedure BtnCreateFromClassClick(Sender: TObject);
private
procedure CreateFromClass(AForm: TClass);
procedure CreateFromClassName(AForm: string);
public
end;
var
FrmGenericFormCreate: TFrmGenericFormCreate;
implementation
{$R *.DFM}
uses
Unit2, Unit3;
procedure TFrmGenericFormCreate.FormCreate(Sender: TObject);
begin
RgrSelect.ItemIndex := 0;
end;
procedure TFrmGenericFormCreate.CreateFromClass(AForm: TClass);
begin
with TFormClass(AForm).Create(Application) do
try
ShowModal;
finally
Release;
end;
end;
procedure TFrmGenericFormCreate.CreateFromClassName(AForm: string);
begin
try
with TFormClass(FindClass(AForm)).Create(Application) do
try
ShowModal;
finally
Release;
end;
except
ShowMessage(Format('Class %s not found', [AForm]));
end;
end;
procedure TFrmGenericFormCreate.BtnCreateFromStringClick(Sender: TObject);
begin
CreateFromClassName('TForm' + IntToStr(RgrSelect.ItemIndex + 2));
RgrSelect.SetFocus;
end;
procedure TFrmGenericFormCreate.BtnCreateFromClassClick(Sender: TObject);
begin
case RgrSelect.ItemIndex of
0: CreateFromClass(TForm2);
1: CreateFromClass(TForm3);
end;
RgrSelect.SetFocus;
end;
initialization
RegisterClasses([TForm2, TForm3]);
end.
2008. május 13., kedd
How to draw a hexagon
Problem/Question/Abstract:
How to draw a hexagon
Answer:
Solve 1:
procedure PlotPolygon(const Canvas: TCanvas; const N: Integer; const R: Single;
const XC: Integer; const YC: Integer);
type
TPolygon = array of TPoint;
var
Polygon: TPolygon;
I: Integer;
C: Extended;
S: Extended;
A: Single;
begin
SetLength(Polygon, N);
A := 2 * Pi / N;
for I := 0 to (N - 1) do
begin
SinCos(I * A, S, C);
Polygon[I].X := XC + Round(R * C);
Polygon[I].Y := YC + Round(R * S);
end;
Canvas.Polygon(Polygon);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
W: Single;
H: Single;
X: Integer;
Y: Integer;
const
N = 6;
R = 10;
begin
W := 1.5 * R;
H := R * Sqrt(3);
for X := 0 to Round(ClientWidth / W) do
for Y := 0 to Round(ClientHeight / H) do
if Odd(X) then
PlotPolygon(Canvas, N, R, Round(X * W), Round((Y + 0.5) * H))
else
PlotPolygon(Canvas, N, R, Round(X * W), Round(Y * H));
end;
Solve 2:
unit HexGrid;
interface
uses
Windows, Messages, SysUtils, Classes, Controls, ExtCtrls, Math;
type
TOrientation = (hxVertical, hxhorizontal);
THexGrid = class(TCustomPanel)
private
FOrientation: TOrientation;
FHexSize: Integer;
FPoints: array[0..5] of TPoint;
FDisplayCaption: Boolean;
procedure ChangedDimensions;
procedure SetOrientation(Value: TOrientation);
procedure SetHexSize(const Value: Integer);
procedure DrawVerticalGrid;
procedure DrawhorizontalGrid;
procedure SetDisplayCaption(Value: Boolean);
protected
public
constructor Create(AOwner: TComponent); override;
procedure Paint; override;
property Orientation: TOrientation read FOrientation write SetOrientation;
published
property Align;
property Alignment;
property Anchors;
property AutoSize;
property BevelInner;
property BevelOuter;
property BevelWidth;
property BiDiMode;
property BorderWidth;
property BorderStyle;
property Caption;
property Color;
property Constraints;
property Ctl3D;
property UseDockManager default True;
property DockSite;
property DragCursor;
property DragKind;
property DragMode;
property Enabled;
property FullRepaint;
property Font;
property Locked;
property ParentBiDiMode;
property ParentColor;
property ParentCtl3D;
property ParentFont;
property ParentShowHint;
property PopupMenu;
property ShowHint;
property TabOrder;
property TabStop;
property Visible;
property OnCanResize;
property OnClick;
property OnConstrainedResize;
property OnContextPopup;
property OnDockDrop;
property OnDockOver;
property OnDblClick;
property OnDragDrop;
property OnDragOver;
property OnEndDock;
property OnEndDrag;
property OnEnter;
property OnExit;
property OnGetSiteInfo;
property OnMouseDown;
property OnMouseMove;
property OnMouseUp;
property OnResize;
property OnStartDock;
property OnStartDrag;
property OnUnDock;
property Left;
property Top;
property Width;
property Height;
property Cursor;
property Hint;
property HelpType;
property HelpKeyword;
property HelpContext;
property HexSize: Integer read FHexSize write SetHexSize;
property DisplayCaption: Boolean read FDisplayCaption write SetDisplayCaption;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [THexGrid]);
end;
procedure THexGrid.ChangedDimensions;
var
I: Integer;
begin
for I := 0 to High(FPoints) do
begin
FPoints[I].X := 0;
FPoints[I].Y := 0;
end;
if Orientation = hxhorizontal then
begin
FPoints[0].X := Hexsize div 4;
FPoints[1].X := HexSize - (Hexsize div 4);
FPoints[2].X := HexSize;
FPoints[2].Y := HexSize div 2;
FPoints[3].X := HexSize - (Hexsize div 4);
FPoints[3].Y := HexSize;
FPoints[4].X := HexSize div 4;
FPoints[4].Y := HexSize;
FPoints[5].Y := HexSize div 2;
end;
if Orientation = hxVertical then
begin
FPoints[0].X := HexSize div 2;
FPoints[1].X := HexSize;
FPoints[1].Y := HexSize div 4;
FPoints[2].X := HexSize;
FPoints[2].Y := HexSize - (Hexsize div 4);
FPoints[3].X := HexSize div 2;
FPoints[3].Y := HexSize;
FPoints[4].Y := HexSize - (Hexsize div 4);
FPoints[5].Y := HexSize div 4;
end;
end;
procedure THexGrid.SetOrientation(Value: TOrientation);
begin
if FOrientation <> Value then
begin
FOrientation := Value;
ChangedDimensions;
invalidate;
end;
end;
procedure THexGrid.SetHexSize(const Value: Integer);
begin
if FHexSize <> Value then
begin
FHexSize := Value;
ChangedDimensions;
invalidate;
end;
end;
constructor THexGrid.Create(AOwner: TComponent);
begin
inherited;
FOrientation := hxVertical;
FHexSize := 64;
ChangedDimensions;
Width := 128;
Height := 128;
end;
procedure THexGrid.Paint;
begin
inherited;
if Orientation = hxhorizontal then
DrawhorizontalGrid
else
DrawVerticalGrid;
end;
procedure THexGrid.DrawhorizontalGrid;
var
I: Integer;
X, Y, Offset: Integer;
FHex: array[0..5] of TPoint;
begin
X := 0;
Y := 0;
Offset := 0;
while X + HexSize < Width do
begin
Y := 0;
while Y + HexSize < Height do
begin
with Self.Canvas do
begin
for I := 0 to High(FPoints) do
begin
FHex[I].X := X + FPoints[I].X;
FHex[I].Y := Y + FPoints[I].Y + Offset;
end;
Polygon(FHex);
end;
Y := Y + HexSize;
end;
if Offset = 0 then
Offset := (0 - (HexSize div 2))
else
Offset := 0;
X := X + (HexSize - (HexSize div 4));
end;
end;
procedure THexGrid.DrawVerticalGrid;
var
I: Integer;
X, Y, Offset: Integer;
FHex: array[0..5] of TPoint;
begin
X := 0;
Y := 0;
Offset := 0;
while Y + HexSize < Height do
begin
X := 0;
while X + HexSize < Width do
begin
with Self.Canvas do
begin
for I := 0 to High(FPoints) do
begin
FHex[I].X := X + FPoints[I].X + Offset;
FHex[I].Y := Y + FPoints[I].Y;
end;
Polygon(FHex);
end;
X := X + HexSize;
end;
if Offset = 0 then
Offset := (0 - (HexSize div 2))
else
Offset := 0;
Y := Y + (HexSize - (HexSize div 4));
end;
end;
procedure THexGrid.SetDisplayCaption(Value: Boolean);
begin
end;
end.
2008. május 12., hétfő
How to save a TJPEGImage to a blob field
Problem/Question/Abstract:
How to save a TJPEGImage to a blob field
Answer:
{ ... }
var
mem: TMemoryStream;
begin
mem := tmemorystream.create;
try
table1.open;
table1blobfield.savetostream(mem);
mem.seek(0, 0);
jpeg1.loadfromstream(mem);
finally
mem.free;
end;
end;
2008. május 11., vasárnap
Sort rows in a TStringGrid
Problem/Question/Abstract:
How to sort rows in a TStringGrid
Answer:
type
TMoveSG = class(TCustomGrid); {reveals protected MoveRow procedure}
procedure SortGridByCols(Grid: TStringGrid; ColOrder: array of integer);
var
i, j: integer;
Sorted: boolean;
function Sort(Row1, Row2: integer): integer;
var
C: integer;
begin
C := 0;
result := AnsiCompareStr(Grid.Cols[ColOrder[C]][Row1],
Grid.Cols[ColOrder[C]][Row2]);
if result = 0 then
begin
Inc(C);
while (C <= High(ColOrder)) and (result = 0) do
begin
result := AnsiCompareStr(Grid.Cols[ColOrder[C]][Row1],
Grid.Cols[ColOrder[C]][Row2]);
Inc(C);
end;
end;
end;
begin
if SizeOf(ColOrder) div SizeOf(i) <> Grid.ColCount then
exit;
for i := 0 to High(ColOrder) do
if (ColOrder[i] < 0) or (ColOrder[i] >= Grid.ColCount) then
exit;
j := 0;
Sorted := false;
repeat
inc(j);
with Grid do
for i := 0 to RowCount - 2 do
if Sort(i, i + 1) > 0 then
begin
TMoveSG(Grid).MoveRow(i + 1, i);
Sorted := false;
end;
until
Sorted or (j = 1000);
Grid.Repaint;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
c, r: integer;
begin
{just fills with random numbers for example}
for r := 0 to StringGrid1.RowCount - 1 do
begin
for c := 0 to StringGrid1.ColCount - 1 do
begin
StringGrid1.Cols[c][r] := Format('%.3d', [Random(255)]);
end;
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
{example}
SortGridByCols(StringGrid1, [1, 0, 2, 3, 4]);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
Randomize;
end;
2008. május 10., szombat
How to move a TImage on a scrollbox
Problem/Question/Abstract:
I placed some TImage components on a TScrollBox. Now I would like my users to be able to scroll these images by clicking on them and moving the cursor with the mouse button down.
Answer:
Attach handlers to the OnMouseDown, Move, Up events of the image. Modify as below. The key here is to not use the X and Y mouse positions the handlers get. Each time the image is scrolled the origin for this position moves and that screws up the calculation. The code below uses the screen-relative mouse position.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, JPEG, StdCtrls;
type
TForm1 = class(TForm)
ScrollBox1: TScrollBox;
Image1: TImage;
Label1: TLabel;
procedure Image1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure Image1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
procedure Image1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
private
{ Private declarations }
FLastDown: TPoint;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Image1MouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
GetCursorPos(FLastDown);
end;
procedure TForm1.Image1MouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
FLastDown := Point(-1, -1);
end;
procedure TForm1.Image1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
pt: TPoint;
begin
if (ssLeft in Shift) and (FLastDown.X >= 0) then
begin
GetCursorPos(pt);
Scrollbox1.VertScrollBar.Position := Scrollbox1.VertScrollBar.Position + FLastDown.Y - pt.Y;
Scrollbox1.HorzScrollBar.POsition := Scrollbox1.HorzScrollBar.Position + FLastDown.X - pt.X;
FLastDown := pt;
label1.caption := format('%d:%d', [pt.x, pt.y]);
end;
end;
end.
2008. május 9., péntek
How to search a string with wildcards
Problem/Question/Abstract:
I have a body of text. I want to allow the user to enter a string that could contain wildcards (well, just the " * ") and search for it.
Answer:
Your first task is to split the paragraph into words (since i take it from your description that the match has to be inside a word). The next is to match each word to the mask. The following implementation is certainly not the fastest possible but it should make the algorithm clear.
procedure SplitTextIntoWords(const S: string; words: TStringlist);
var
startpos, endpos: Integer;
begin
Assert(Assigned(words));
words.clear;
startpos := 1;
while startpos <= Length(S) do
begin
{skip non-letters }
while (startpos <= Length(S)) and not IsCharAlpha(S[startpos]) do
Inc(startpos);
if startpos <= Length(S) then
begin
{find next non-letter}
endpos := startpos + 1;
while (endpos <= Length(S)) and IsCharAlpha(S[endpos]) do
Inc(endpos);
words.add(Copy(S, startpos, endpos - startpos));
startpos := endpos + 1;
end;
end;
end;
function StringMatchesMask(S, mask: string; case_sensitive: Boolean): Boolean;
var
sIndex, maskIndex: Integer;
begin
if not case_sensitive then
begin
S := AnsiUpperCase(S);
mask := AnsiUpperCase(mask);
end;
Result := True; {blatant optimism}
sIndex := 1;
maskIndex := 1;
while (sIndex <= Length(S)) and (maskIndex <= Length(mask)) do
begin
case mask[maskIndex] of
'?':
begin
{matches any character}
Inc(sIndex);
Inc(maskIndex);
end;
'*':
begin
{matches 0 or more characters, so need to check for next character in mask}
Inc(maskIndex);
if maskIndex > Length(mask) then
{ * at end matches rest of string}
Exit
else if mask[maskindex] in ['*', '?'] then
raise Exception.Create('Invalid mask');
{look for mask character in S}
while (sIndex <= Length(S)) and (S[sIndex] <> mask[maskIndex]) do
Inc(sIndex);
if sIndex > Length(S) then
begin
{character not found, no match}
Result := false;
Exit;
end;
end;
else
if S[sIndex] = mask[maskIndex] then
begin
Inc(sIndex);
Inc(maskIndex);
end
else
begin
{no match}
Result := False;
Exit;
end;
end;
end;
{if we have reached the end of both S and mask we have a complete match,
otherwise we only have a partial match}
if (sIndex <= Length(S)) or (maskIndex <= Length(mask)) then
Result := false;
end;
procedure FindMatchingWords(const S, mask: string; case_sensitive: Boolean;
matches: TStringlist);
var
words: TStringlist;
i: Integer;
begin
Assert(Assigned(matches));
words := TStringlist.Create;
try
SplitTextIntoWords(S, words);
matches.clear;
for i := 0 to words.count - 1 do
begin
if StringMatchesMask(words[i], mask, case_sensitive) then
matches.Add(words[i]);
end;
finally
words.free;
end;
end;
{Form has one memo for the text to check, one edit for the mask, one checkbox
(check = case sensitive), one listbox for the results, one button }
procedure TForm1.Button1Click(Sender: TObject);
begin
FindMatchingWords(memo1.text, edit1.text, checkbox1.checked, listbox1.items);
end;
2008. május 8., csütörtök
How to make a popup menu appear at a certain position over the Windows Taskbar
Problem/Question/Abstract:
I create my popup menu items dynamically (not ownerdrawn). To place the popup menu at the right position (above a component), I need to determine (read) the menu item height. How would I do this?
Answer:
It's quite shocking but the API offers no way to do this. The API method to get a menu to pop up in a given area is TrackPopupMenuEx. Unfortunately it is not easily applicable with a Delphi popup menu, since you cannot get the handle of the tool window the Menus unit uses to process the menu messages. So you would have to duplicate that windows message processing in another window you can get at, e.g. the form.
Make a popup menu pop up over the taskbar, bottom aligned to taskbar top:
procedure TForm1.Button1Click(Sender: TObject);
var
pm: TTPMParams;
DisplayPoint: TPoint;
r: TRect;
begin
SystemParametersInfo(SPI_GETWORKAREA, 0, @r, 0);
r.top := r.bottom + 1;
r.bottom := screen.height;
DisplayPoint := Point(699, r.top);
with pm, pm.rcexclude do
begin
Top := r.top;
Bottom := r.bottom;
Left := 0;
Right := screen.width;
cbSize := SizeOf(pm);
end;
TrackPopupMenuEx(PopupMenu1.Handle, TPM_VERTICAL or TPM_HORIZONTAL,
DisplayPoint.x, DisplayPoint.y, Handle, @pm);
end;
2008. május 7., szerda
How to determine the RAM size of the display adapter
Problem/Question/Abstract:
Which Win API function can give me the amount of memory of my computer's display adapter?
Answer:
EnumDisplaySettings gives you the different settings for the adapter. The amount of RAM can then be calculated (width * height * colors).
procedure TForm1.Button1Click(Sender: TObject);
var
DevMode: TDeviceMode;
i, m, max: Integer;
begin
max := 0;
i := 0;
while EnumDisplaySettings(nil, i, DevMode) do
begin
with DevMode do
m := Round(dmPelsWidth * dmPelsHeight * (dmBitsPerPel / 8));
if m > max then
max := m;
inc(i);
end;
label1.caption := IntToStr(max);
end;
2008. május 6., kedd
How to create a TComboBox with incremental search capabilities
Problem/Question/Abstract:
Is there a way to let a TComboBox do incremental search in its drop down list? Currently it uses only the first letter so if multiple items start with the same letter it's not very useful.
Answer:
Create a new Combo inherited from class(TcustomComboBox) and change the Change Event to the following few lines:
{...}
protected
procedure Change; override;
{...}
procedure TExtComboBox.Change;
var
str: string;
Index: Integer;
begin
inherited Change;
str := Text;
if (FLastKey = VK_DELETE) or (FLastKey = VK_BACK) then
begin
SelStart := Length(str);
SelLength := 0;
Exit;
end;
{try to find the closest matching item}
Index := Perform(CB_FINDSTRING, -1, LPARAM(str));
if Index <> CB_ERR then
begin
ItemIndex := Index;
SelStart := Length(str);
SelLength := Length(Items[Index]) - SelStart;
end
else
Text := str;
{call standard event}
if Assigned(FOnChange) then
FOnChange(Self);
end;
end.
2008. május 5., hétfő
Get the width and height of a bitmap without opening the file
Problem/Question/Abstract:
How to get the width and height of a bitmap without opening the file
Answer:
A bitmap starts with a file header (TBitmapFileHeader). Then followed by a bitmap info header, dependent on the bitmap version. The first DWORD contains the size of the info structure, so you can read and analyze this to chose the correct handling. Bitmap version 2 uses a TBitmapCoreHeader. The width and height value follow in the next two Words. Version 3 uses a TBitmapInfoHeader, version 4 a TBitmapV4Header and version 5 a TBitmapV5Header. In all these headers the width and height follow in the next two LongInts.
You can use this information to build a function to get at the width and height data. The following is untested and you should add a check, to see whether the file is a bitmap or not:
function GetBitmapSizeFromFile(const Filename: string; var Width, Height: Integer):
Boolean;
type
TBitmapHeaders = packed record
FileHeader: TBitmapFileHeader;
case Integer of
2: (CoreHeader: TBitmapCoreHeader);
3: (InfoHeader: TBitmapInfoHeader);
end;
var
fs: TFileStream;
Headers: TBitmapHeaders;
begin
Result := False;
fs := TFileStream.Create(Filename, fmOpenRead or fmShareDenyWrite);
try
f.ReadBuffer(Headers, SizeOf(Headers));
{Check the bitmap}
{ ... }
{Get the size}
case Headers.CoreHeader.bcSize of
SizeOf(TBitmapCoreHeader):
begin
Width := Headers.CoreHeader.bcWidth;
Height := Headers.CoreHeader.bcHeight;
Result := True;
end;
SizeOf(TBitmapInfoHeader), SizeOf(TBitmapV4Header), SizeOf(TBitmapV5Header):
begin
Width := Headers.InfoHeader.biWidth;
{Negative Height values are possible -> Abs}
Height := Abs(Headers.InfoHeader.biHeight);
Result := True;
end;
else
{Place a special error message, i.e. wrong header, here}
end;
finally
f.Free;
end;
end;
The pixel data start at the file position Headers.FileHeader.bfOffBits. Read the data from the file. For more information on the pixel data, read the WinAPI help, e.g. the topic BITMAPINFOHEADER.
2008. május 4., vasárnap
How to get the first visible line in a TRichEdit
Problem/Question/Abstract:
How can I get the number of the first line that is shown in a TRichEdit when it has been scrolled down?
Answer:
Take a look at source of messages.pas unit and search for "EM_" to see a list of related messages.
RichEdit1.Perform(EM_GETFIRSTVISIBLELINE, 0, 0)
2008. május 3., szombat
How to create a MessageBox with a timeout
Problem/Question/Abstract:
I need to create a messagebox asking the user to reply either Yes or No, but with a timeout. Is there a way to use a messagebox function with some timeout, like open messagebox with Yes or No, and after 20 seconds reply with other information to know that user is out?
Answer:
Solve 1:
Show the MessageBox in another thread and kill the thread when you want to remove the messagebox under program control:
{ ... }
var
ThreadId: Integer;
ThreadHandle: Integer;
MsgResult: Integer;
function thread_proc(p: Pointer): integer; stdcall;
begin
MsgResult := MessageBox(0, 'Some question?', 'Hey', MB_ICONQUESTION or MB_YESNO);
ThreadHandle := 0;
EndThread(0);
end;
procedure TForm1.Button1Click(Sender: TObject);
var
counter: integer;
begin
MsgResult := IDNO; {default answer}
{show MessageBox}
ThreadHandle := BeginThread(nil, 0, @Thread_proc, nil, 0, ThreadID);
counter := 20; {wait for 20 seconds}
while (ThreadHandle <> 0) and (counter > 0) do
begin
Sleep(1000);
counter := counter - 1;
end;
{if MessageBox is still visible after 20 seconds, remove it}
if Counter = 0 then
TerminateThread(ThreadHandle, 0);
if MsgResult = IDYES then
{ ... }
else
{ ... }
end;
Solve 2:
procedure TForm1.Button1Click(Sender: TObject);
var
R: Integer;
begin
Timer1.Interval := 20000;
Timer1.Enabled := true;
R := MessageDlg('Yes or no?', mtConfirmation, [mbYes, mbNo], 0);
Caption := IntToStr(R);
Timer1.Enabled := false;
end;
procedure TForm1.Timer1Timer(Sender: TObject);
var
F: TForm;
begin
F := Screen.ActiveForm;
if fsModal in F.FormState then
F.ModalResult := mrYesToAll;
end;
2008. május 2., péntek
Force a drop-down combo to drop its list down
Problem/Question/Abstract:
How can I force a drop-down combo to drop its list down?
Answer:
This is done by using a Windows message called CB_SHOWDROPDOWN.
I recommend that you look in the WinAPI help under messages to see what else you can do with them.
The nice thing about messaging in Windows is that the calls are all handled through the Windows API SendMessage routine, which requires four parameters:
Parameters of SendMessage function
Window Handle (can be an object handle)
Message — specifies the message to be sent (in our case, CB_SHOWDROPDOWN)
wParam, a 16-bit message-dependent parameter
lParam, a 32-bit message-dependent parameter (see WinHelp for specifics on what goes into wParam and lParam)
The gist of this is that Windows messages are performed in a very standard way, so if you haven't done them much, I encourage you to investigate ways to employ them in your code.
To get a combo-box list to automatically drop down when you enter it, put the following code into the OnEnter event:
procedure TForm1.ComboBox1Enter(Sender: TObject);
begin
SendMessage(ComboBox1.handle, CB_SHOWDROPDOWN, Integer(True), 0);
end;
Likewise, you can close the drop-down when you exit by putting the following code into the OnExit event of the combo box:
procedure TForm1.ComboBox1Exit(Sender: TObject);
begin
SendMessage(ComboBox1.handle, CB_SHOWDROPDOWN, Integer(False), 0);
end;
This is probably how the Intuit guys did it with Quicken. So go for it!
2008. május 1., csütörtök
How to create a random "Tip of the Day" from a Paradox table
Problem/Question/Abstract:
How to create a random "Tip of the Day" from a Paradox table
Answer:
Here's a rather simple example of how to reduce your exe size by randomly retrieving a typical "Tip of the Day" (with associated image) from a table instead of getting it from a resource file. It uses a Paradox table with the following structure:
Number (FieldName)
(Type: Alpha)
Text (FieldName)
(Type: Blob)
Image (FieldName)
(Type: Blob)
1 (Variable I)
RTF
JPG Image
2
RTF
JPG Image
3
RTF
JPG Image
The code should be self-explanatory, but you can also download a little sample project at the bottom of this page if you like. Please note, that there are only four records in the table. The same tip might therefore appear several times in a row when you click on the "Next Tip" button.
Here are the basic steps:
Create a new project and drop a TDataSource, a TTable and a TPanel on the main form. Place a TImage and a TDBRichEdit on the panel. Hook Table1 and DBRichEdit1 to Datasource1 and assign the field "Text" of Table1 to the DBRichEdit control. Finally, set table1.active to true.
Put JPEG into the Uses clause and in the private declarations of the main form, add this procedure ...
private
{Private Declarations}
procedure RandomTip;
... and define it as follows:
procedure TForm1.RandomTip;
var
Stream1: TBlobStream;
TipImage: TJPEGImage;
I: Integer;
begin
{Random(I) would pick a free random number}
I := Random(4) + 1;
{Take the random number, locate it in the field "Number" and jump to the record}
table1.Locate('Number', I, [loPartialKey]);
begin
{Create a blobstream and read the stored jpg image from the blob
field into the stream}
Stream1 := TBlobStream.Create(Table1.FieldByName('Image') as TBlobField, bmRead);
TipImage := TJPEGImage.create;
try
{Load the JPEGImage from the blob stream and assign it to the TImage}
TipImage.LoadFromStream(Stream1);
Image1.Picture.Assign(TipImage);
finally
Stream1.Free;
TipImage.Free;
end;
end;
end;
To initialize the random number generator, we need to call it (for example) in the OnCreate event handler of Form1. Randomize should only be called once in your application - best done probably in the main units Initialization section.
procedure TForm1.FormCreate(Sender: TObject);
begin
Randomize; {Call the random number generator}
end;
To fill the Tip of the Day from the table the first time, we call the RandomTip procedure in the OnShow event handler of Form1:
procedure TForm1.FormShow(Sender: TObject);
begin
RandomTip;
end;
That's all there is to do. Dead easy, isn't it? This simple technique could also be used to display random splash screens or random backgrounds for forms in your program. Just use your imagination ...
Download sample project (32K)
Feliratkozás:
Bejegyzések (Atom)