2005. október 31., hétfő
Load and save a TStringGrid from/ to a stream
Problem/Question/Abstract:
I have a form with 4 StringGrids which I fill at run time with data. It takes some time to enter the data and so I thought it would save me some time if I could save and load the form with its data - something I've never done in Delphi before. I've sorted the menu and the dialogue boxes but what method do I have to write to save the form, its StringGrids and the data therein - simply?
Answer:
You will have noticed that the StringGrid is a control that does not allow you to enter strings into it at design-time. The reason is that the streaming system cannot handle array properties like Cells, so the standard component streaming is no use for your task. But you can write your own routines to save a StringGrids content, of course. Something like this for example:
procedure SaveGridToStream(aStream: TStream; aGrid: TStringGrid);
var
i, k: Integer;
iBuf: Integer;
S: string;
procedure WrInt(anInt: Integer);
begin
aStream.WriteBuffer(anInt, Sizeof(anInt));
end;
begin
with aGrid do
begin
WrInt(ColCount);
WrInt(rowCount);
for i := 0 to rowCount - 1 do
for k := 0 to colCount - 1 do
begin
S := Cells[k, i];
WrInt(Length(S));
if Length(S) > 0 then
aStream.WriteBuffer(S[1], Length(S));
end;
end;
end;
procedure LoadGridFromStream(aStream: TStream; aGrid: TStringGrid);
var
i, k: Integer;
iBuf: Integer;
S: string;
function RdInt: Integer;
begin
aStream.ReadBuffer(Result, Sizeof(Result));
end;
begin
with aGrid do
begin
ColCount := RdInt;
RowCount := RdInt;
for i := 0 to rowCount - 1 do
for k := 0 to colCount - 1 do
begin
iBuf := RdInt;
if iBuf > 0 then
begin
SetLength(S, iBuf);
aStream.ReadBuffer(S[1], iBuf);
Cells[k, i] := S;
end;
end;
end;
end;
2005. október 30., vasárnap
Creating visual objects at run-time
Problem/Question/Abstract:
Sometimes it's necessary to create a visual object, like a button or a label, at run time. Here I'll show how to do it with a simple example.
Answer:
To explain how to create a visual object at run-time, there's some simple notions that is necessary to understood. I'll try to explain it in a simple and fast way (so don't bother with any "less accurate" information). Simplifying things, objects have two things: properties and methods. A visual object is not different, it has properties, methods and can act on event handling.
A visual object's life cycle is just like any other object life cycle, it must be created, will "live" and at the end will die. Follwoing this three basic life cycle steps:
When creating a visual object, it's necessary to say who will be the parent, who will have it has a child. Example: when deploying a TPanel on a TForm, the TForm is the TPanels' parent, when deploying a TLabel on the TPanel, the TPanel is the TLabels' parent.
To give the object some "life", it's necessary to code the answer to an event. Example: the OnKeyUp event on a TForm tells the TForm what to do when the user releases a key.
When the object is no longer needed, it should be removed. Example: when you have a TLabel that you no long need, you delete it.
So, let's take a simple case to exemplefy this. Let's create a TButton and give it some life.
Start a new project, name your main form as "Form1" and specify these private variables:
MyButton: TButton;
IsAlive: Boolean;
Deploy a TButton on Form1, caption it as "Manage Button" and on the OnClick event write the following code:
// create the button
MyButton := TButton.Create(Form1);
with MyButton do
begin
// setting the buttons' parent
Parent := Form1;
// setting the alignment and size
Top := 50;
Left := 10;
Width := 100;
// showing it
Caption := "&It Works!";
Visible := True;
end;
This creates the button when the "Manage Button" is pushed. Run the application and click the "Manage Button".
This is not really usefull util the button created ar run-time actually do anything, so let's give it life. Let's build a procedure able to respond to a OnClick event. On your Form1 private declaration write:
procedure MyButtonOnClickEvent(Sender: TObject);
and write the following code for it:
procedure TForm1.MyButtonOnClickEvent(Sender: TObject);
begin
ShowMessage("Well, it really works!");
end;
How do we know this will work? Well, if you check the TButton help, you will see that the OnClick event has the same signature as the MyButtonOnClickEvent that was just coded. Whenever you need to build an event, check the documentation on that object's event and make a procedure with the same signature.
Now let's make the MyButtonOnClickEvent the OnClick event of the button, just add
OnClick := MyButtonOnClickEvent;
to the code, it should look like this now:
// create the button
MyButton := TButton.Create(Form1);
with MyButton do
begin
// setting the buttons' parent
Parent := Form1;
// setting the alignment and size
Top := 10;
Left := 10;
Width := 100;
// making it respond to the OnClick event
onClick := MyButtonOnClickEvent;
// showing it
Caption := "&It Works!";
Visible := True;
end;
Now run it, click the "Manage Button" and then click the "It Works!" button! ;)
Now that the button has some "life", all it needs is to "die" to have a full life cycle. So, let's kill it by calling the standard destructor. Let's change the "Manage Button" code to create and destroy the MyButton button:
if IsAlive = False then
begin
// create the button
MyButton := TButton.Create(Form1);
with MyButton do
begin
// setting the buttons' parent
Parent := Form1;
// setting the alignment and size
Top := 10;
Left := 10;
Width := 100;
// making it respond to the OnClick event
onClick := MyButtonOnClickEvent;
// showing it
Caption := "&It Works!";
Visible := True;
end;
IsAlive := True;
end
else
begin
MyButton.Free; // kills the button
IsAlive := False;
end;
The IsAlive flag will tell us if the button is alive or not. To be positive that this has no faults, the IsAlive variable should be initialized, let's fo it on Form1's OnShow event:
IsAlive := False;
Now run the application. Click the "Manage Button", click the "It Works!" button, click the "Manage Button" again.
Simple, isn't it ? :)
2005. október 29., szombat
Database "index out of date" error
Problem/Question/Abstract:
I was testing a program I wrote and I received an error message saying, "Index is out of date." I have no idea what this error means. I have looked everywhere in my Delphi books for some help on this problem, and can't seem to find any information about it. Can you help me?
Answer:
This is a BDE/Paradox error message. For newbies, BDE error messages are daunting, cryptic messages. Actually, even for seasoned veterans, they can sometimes be real "stumpers." Unfortunately, there's no real good reference available that I know of, so all I can offer with respect to this error message is my experience.
The "Index out of date" message can mean a couple of things:
One of the more common causes of this error is one in which you have a couple of copies of a table existing on your network or machine. For instance, when I develop applications, I have my application tables residing in my development system, then have copies of them on my network. When I need to update my tables, I usually do the updates in my development system, then copy them over to my deployment system on the network. I've run into this exact error when I've copied only the table (.DB) file and not its accompanying index file(s) (.PX, .X01, .Y01, etc) as well. You see, when you update a table by changing it in any way, its index files are also resynched to reflect the changes. So if you copy just the table to a new place on your system and don't include its family members, you'll index files that aren't in synch with your table. Okay that's one cause.
The next cause could be just this: One of your indexes is corrupt. This could be due to sector errors on your hard disk, or the rare, but possible, direct corruption of an index. This usually happens if your program abended while performing an update to a table with an index of some sort. In that case, the index doesn't get updated.
But in any case, the only way I know of to correct the problem is to do the following:
Open up your table in Database Desktop.
Restructure it.
Define/Rebuild all your indexes.
Save the file.
2005. október 28., péntek
How to filter on substrings within a string field's value
Problem/Question/Abstract:
How to filter on substrings within a string field's value
Answer:
In the TTable OnFilter event:
var
sp, ss: ShortString
begin
with DataSet do
begin
sp := FilterPosition.Text; {TEdit containing substring to be filtered}
ss := AnsiUpperCase(FieldByName('MyField').asString);
Accept := Pos(sp, ss) > 0;
end;
end;
or with an SQL script:
SELECT * FROM MyTable
WHERE UPPER(MyField)LIKE "%UPPER(MySubstring)%";
2005. október 27., csütörtök
How to check if a MDI child has been created or destroyed
Problem/Question/Abstract:
I have to write a kind of task list of all MDI child windows which are opened in the application. For this reason I tried to use the WM_PARENTNOTIFY to get an event, if a MDI child is created or destroyed. But I don't receive a message when the child windows are created or destroyed.
Answer:
Try the following:
const
WM_AddWin = WM_User + 300;
WM_DelWin = WM_User + 301;
type
TForm1
{ ... }
private
procedure OnAddWindow(var msg: TMessage); message WM_AddWin;
procedure OnDelWindow(var msg: TMessage); message WM_DelWin;
public
end;
procedure TForm1.OnAddWindow(var msg: TMessage);
begin
List.Add(Strpas(Pointer(msg.lparam)));
end;
procedure TForm1.OnDelWindow(var msg: TMessage);
begin
List.delete(List.indexof(Strpas(Pointer(msg.lparam))));
end;
procedure OnChildFormCreate(Sender: TObject);
begin
PostMessage(TForm(Owner).handle, WM_AddWin, 0, Integer(PChar('ChildFormname')));
end;
procedure OnChildFormDestroy;
begin
PostMessage(TForm(Owner).handle, WM_DelWin, 0, Integer(PChar('ChildFormname')));
end;
This will post a message to the owner of a child form to add it to the master list and delete it
when it dies.
2005. október 26., szerda
How to change file attributes
Problem/Question/Abstract:
How do I change the attributes of a file like write protection, hidden etc. ?
Answer:
var
F_Attr: Word;
F_Attr := FileGetAttr('MyFile');
{Turn ReadOnly OFF}
FileSetAttr('MyFile', F_Attr and not faReadOnly);
2005. október 25., kedd
How to implement autocompletion in a TEdit
Problem/Question/Abstract:
How to implement autocompletion in a TEdit
Answer:
Solve 1:
Here is a procedure using the OnKeyDown that will autocomplete an edit box using a lookup source table. Change it to suit your needs but it should give you an idea of how to do the selections and stuff with an edit control. This will work with just about any type of edit control and I use it for combo boxes as well. You just need to change the typecasting.
procedure TForm1.EditKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
var
s1: string;
s2: string;
begin
if TEdit(Sender).Text = '' then
exit;
s1 := TEdit(Sender).Text;
s2 := s1;
with mtDM.LookTable do {change here for your own lookup stuff...}
begin
if not Locate(LookField, TEdit(Sender).Text, [loPartialKey]) then
begin
Key := 0;
if length(s2) = 1 then
begin
TEdit(Sender).Text := '';
exit;
end;
System.delete(s2, length(s2), 1);
TEdit(Sender).Text := s2;
s1 := s2;
Locate(LookField, TEdit(Sender).Text, [loPartialKey]);
end;
s1 := FieldByName(LookField).AsString;
TEdit(Sender).Text := copy(s1, 1, length(s2)) + copy(s1, length(s2) + 1, length(s1));
TEdit(Sender).SelStart := Length(s2);
TEdit(Sender).SelLength := length(s1) - length(s2);
end;
inherited;
end;
Solve 2:
unit AutoEdit;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, StdCtrls, Controls,
Dialogs, Forms;
type
TAutoEdit = class(TEdit)
private
fList: TListBox;
fItems: TStringList;
fLabel: TLabel;
fCaption: string;
fBackColor: TColor;
fCaptionColor: TColor;
fAutoComplete: Boolean;
fListCount: Integer;
fOldText: string;
procedure SetCaption(S: string);
procedure SetCaptionColor(const Color: TColor);
procedure SetBackColor(const Color: TColor);
procedure SetAutoComplete(AutoCompleteOn: Boolean);
procedure ShowList;
protected
procedure CreateWnd; override;
procedure CreateParams(var params: TCreateParams); override;
procedure SetParent(AParent: TWinControl); override;
procedure SetName(const Value: TComponentName); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure SetBounds(ALeft, ATop, AWidth, AHeight: Integer); override;
procedure ListMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
procedure HideList;
procedure DoExit; override;
property Items: TStringList read fItems write fItems;
published
procedure KeyPress(var Key: Char); override;
procedure KeyDown(var Key: Word; Shift: TShiftState); override;
property Caption: string read fCaption write SetCaption;
property CaptionColor: TColor read fCaptionColor write SetCaptionColor;
property BackColor: TColor read fBackColor write SetBackColor;
property AutoComplete: Boolean read fAutoComplete write SetAutoComplete;
property ListCount: Integer read fListCount write fListCount default 5;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Freeware', [TAutoEdit]);
end;
{ TAutoEdit }
constructor TAutoEdit.Create(AOwner: TComponent);
begin
inherited;
fItems := TStringList.Create;
fList := TListBox.Create(Self);
fLabel := TLabel.Create(Self);
fLabel.ParentColor := True;
fLabel.AutoSize := False;
fLabel.FocusControl := Self;
fCaptionColor := fLabel.Font.Color;
fBackColor := fLabel.Color;
fList.Parent := Self;
fList.IntegralHeight := True;
fList.ParentCtl3D := False;
fList.Ctl3D := False;
fList.TabStop := False;
fList.Visible := False;
fListCount := 5;
end;
destructor TAutoEdit.Destroy;
begin
{fList.Free;}
fItems.Free;
fLabel.Free;
inherited;
end;
procedure TAutoEdit.SetParent(AParent: TWinControl);
var
FirstSetting: Boolean;
begin
if Parent = nil then
FirstSetting := True
else
FirstSetting := False;
inherited;
if Parent <> nil then
begin
fList.Parent := Self.Parent;
fLabel.Parent := Self.Parent;
if FirstSetting then
begin
fLabel.ParentColor := True;
SetBounds(Left, Top, Width, Height);
end;
end;
end;
procedure TAutoEdit.SetBounds(ALeft, ATop, AWidth, AHeight: Integer);
begin
inherited SetBounds(ALeft, ATop, AWidth, AHeight);
if Parent <> nil then
begin
if (fCaption > '') and (fLabel.Parent <> nil) then
begin
fLabel.Top := ATop - (1 + fLabel.Canvas.TextHeight('lj'));
fLabel.Height := AHeight + 4 + fLabel.Canvas.TextHeight('lj');
end
else
begin
fLabel.Top := ATop - 2;
fLabel.Height := AHeight + 4;
end;
fLabel.Left := ALeft - 2;
fLabel.Width := AWidth + 4;
if csDesigning in ComponentState then
begin
fList.Parent := Self;
HideList;
end
else if fList.Visible then
ShowList;
end;
end;
procedure TAutoEdit.SetName(const Value: TComponentName);
begin
if Name > '' then
if fCaption = Name then
Caption := Value;
inherited SetName(Value);
if Text = Name then
begin
Text := '';
Caption := Value;
end;
end;
procedure TAutoEdit.CreateWnd;
begin
inherited;
end;
procedure TAutoEdit.CreateParams(var params: TCreateParams);
begin
inherited;
fList.Color := Self.Color;
fList.Font := Self.Font;
fList.OnMouseUp := ListMouseUp;
HideList;
end;
procedure TAutoEdit.SetCaption(S: string);
begin
fCaption := S;
fLabel.Caption := ' ' + S;
SetBounds(Left, Top, Width, Height)
end;
procedure TAutoEdit.SetCaptionColor(const Color: TColor);
begin
if fCaptionColor <> Color then
begin
fCaptionColor := Color;
fLabel.Font.Color := Color;
SetBounds(Left, Top, Width, Height)
end;
end;
procedure TAutoEdit.SetBackColor(const Color: TColor);
begin
if fBackColor <> Color then
begin
fBackColor := Color;
fLabel.Color := Color;
SetBounds(Left, Top, Width, Height)
end;
end;
procedure TAutoEdit.SetAutoComplete(AutoCompleteOn: Boolean);
begin
fAutoComplete := AutoCompleteOn;
end;
procedure TAutoEdit.ListMouseUp(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
begin
Text := fList.Items[fList.ItemIndex];
SelStart := Length(Text);
HideList;
fList.Clear;
PostMessage(Handle, WM_KEYDOWN, VK_TAB, 0);
PostMessage(Handle, WM_KEYUP, VK_TAB, 0);
end;
procedure TAutoEdit.DoExit;
begin
if not fList.Focused then
HideList;
inherited;
end;
procedure TAutoEdit.KeyPress(var Key: Char);
var
K, T: string;
I, S: Integer;
begin
if ReadOnly then
begin
inherited;
Exit;
end;
K := Key;
if (Key = #27) and (fList.Visible) then
begin
Key := #0;
Text := Copy(Text, 1, SelStart);
SelStart := Length(Text);
fList.Clear;
HideList;
end
else if fAutoComplete then
if ((K > #27) and (K < #129)) or (K = #8) then
begin
if (K = #8) then
T := Copy(Text, 1, SelStart - 1)
else
T := Copy(Text, 1, SelStart) + K;
K := Uppercase(T);
fList.Clear;
if fItems.Count > 0 then
for I := 0 to fItems.Count - 1 do
begin
if (Pos(K, Uppercase(fItems[I])) = 1) then
fList.Items.Add(fItems[I]);
if fList.Items.Count > fListCount - 1 then
Break;
end;
S := Length(T);
if (fList.Items.Count > 0) and (Key <> #8) then
begin
Text := Copy(T, 1, S) + Copy(fList.Items[0], S + 1, Length(fList.Items[0]));
end
else
Text := T;
Key := #0;
SelStart := S;
SelLength := Length(Text) - S;
fOldText := Copy(Text, 1, SelStart);
end;
if fList.Items.Count > 0 then
ShowList
else
HideList;
inherited;
end;
procedure TAutoEdit.KeyDown(var Key: Word; Shift: TShiftState);
var
I, S: Integer;
begin
if Key = VK_DELETE then
begin
fList.Clear;
HideList;
end
else if fList.Visible then
if (Key = VK_DOWN) or (Key = VK_UP) then
begin
S := SelStart;
if Key = VK_DOWN then
I := fList.ItemIndex + 1
else
I := fList.ItemIndex - 1;
if I < -1 then
I := fList.Items.Count - 1;
if I > fList.Items.Count - 1 then
I := -1;
fList.ItemIndex := I;
if I = -1 then
begin
Text := fOldText;
SelStart := Length(Text);
SelLength := 0;
end
else
begin
Text := fList.Items[fList.ItemIndex];
SelStart := S;
SelLength := Length(Text) - S;
end;
Key := 0;
end;
if (not fList.Visible) and ((Key = VK_LEFT) or (Key = VK_RIGHT)) then
if SelLength = Length(Text) then
if (Shift = []) and (Length(Text) > 0) then
begin
SelLength := 0;
{if (Key = VK_LEFT) and (SelStart > 1) then
SelStart := SelStart - 1
else
if (Key = VK_RIGHT) and (SelStart < Length(Text)) then
SelStart := SelStart + 1;}
Key := 0;
end;
inherited;
end;
procedure TAutoEdit.ShowList;
begin
if Parent <> nil then
begin
fList.Top := Top + ClientHeight;
fList.Left := Left;
fList.Width := Width;
fList.Height := fList.ItemHeight * (fList.Items.Count + 1);
fList.BringToFront;
fList.Show;
end;
end;
procedure TAutoEdit.HideList;
var
I: Integer;
begin
if (Text > '') then
for I := 0 to fList.Items.Count - 1 do
if Uppercase(fList.Items[I]) = Uppercase(Text) then
begin
Text := fList.Items[I];
Break;
end;
fList.Hide;
fList.Top := Top;
fList.Height := 0;
fList.Left := Left;
fList.Width := 0;
end;
initialization
begin
RegisterClass(TLabel);
end;
end.
2005. október 24., hétfő
How to make a palette and a pf1bit bitmap not necessary in B/W
Problem/Question/Abstract:
How to make a palette and don't bother about Range Checking ON or OFF ({$R+/-}?
How to convert a bitmap to a pf1bit bitmap, but not necessary in B/W?
Answer:
// This method will give an error if Range Checking is on
// ======================================================
var
LogPal: PLogPalette;
Palette: HPalette;
PalSize: LongInt;
begin
{ ... }
PalSize := 2 * SizeOf(Word) + n_Colors * SizeOf(TPaletteEntry));
{2 * SizeOf(Word) to get space for palVersion and palNumEntries, n_Colors is the number
of colors in the palette}
GetMem(LogPal, PalSize);
LogPal^.palVersion := $0300;
LogPal^.palNumEntries := n_Colors;
LogPal^.palPalEntry[0] := {Some colour};
LogPal^.palPalEntry[1] := {Some other colour};
{ etc. }
FreeMem(LogPal, PalSize);
{ ... }
end;
// This method will NOT give a Range Check Error!
// =============================================
var
pal: TMaxLogPalette;
hpal: HPalette;
DummyImage: TImage;
begin
pal.palVersion := $300; // Magic number
pal.palNumEntries := 2; // Palette for 1bit images not black and white!
// Set foreground color
pal.palPalEntry[0].peRed := GetRValue(Color1);
pal.palPalEntry[0].peGreen := GetGValue(Color1);
pal.palPalEntry[0].peBlue := GetBValue(Color1);
pal.palPalEntry[0].peFlags := 0;
// Set backGroundColor
pal.palPalEntry[1].peRed := GetRValue(Color2);
pal.palPalEntry[1].peGreen := GetGValue(Color2);
pal.palPalEntry[1].peBlue := GetBValue(Color2);
pal.palPalEntry[1].peFlags := 0;
// Create the palette
hpal := CreatePalette(PLogPalette(@pal)^);
// Create a new image
DummyImage := TImage.Create(Self);
DummyImage.Picture.Bitmap.Width := Image1.Picture.Bitmap.Width;
DummyImage.Picture.Bitmap.Height := Image1.Picture.Bitmap.Height;
DummyImage.Picture.Bitmap.PixelFormat := pf1bit;
DummyImage.Picture.Bitmap.Palette := hpal; // Assign the palette
DummyImage.Picture.Bitmap.Canvas.Draw(0, 0, Image1.Picture.Graphic); // Draw it
{...}
{...}
end;
// DummyImage now holds the pf1bit represenation of Image1.
// Yes! A pf1bit image has a palette and it doesn't have to be black and white either!
2005. október 23., vasárnap
How to handle bitmap layers
Problem/Question/Abstract:
I want to overlay bitmap2 over bitmap. The bitmap2 should be on top of bitmap1, i.e. the end result should be that bitmap2 should also show bits of bitmap1. What is the best way in Delphi to handle this?
Answer:
There are two methods. One works using a TImageList the other without. So the latter will work with other file types besides BMP's. Here's the code for both, it's pretty clear how both work. The key thing is you must specify a transparent color:
procedure DrawTrans(DestCanvas: TCanvas; X, Y: smallint; SrcBitmap: TBitmap; AColor: TColor);
var
ANDBitmap, ORBitmap: TBitmap;
CM: TCopyMode;
Src: TRect;
begin
ANDBitmap := nil;
ORBitmap := nil;
try
ANDBitmap := TBitmap.Create;
ORBitmap := TBitmap.Create;
Src := Bounds(0, 0, SrcBitmap.Width, SrcBitmap.Height);
with ORBitmap do
begin
Width := SrcBitmap.Width;
Height := SrcBitmap.Height;
Canvas.Brush.Color := clBlack;
Canvas.CopyMode := cmSrcCopy;
Canvas.BrushCopy(Src, SrcBitmap, Src, AColor);
end;
with ANDBitmap do
begin
Width := SrcBitmap.Width;
Height := SrcBitmap.Height;
Canvas.Brush.Color := clWhite;
Canvas.CopyMode := cmSrcInvert;
Canvas.BrushCopy(Src, SrcBitmap, Src, AColor);
end;
with DestCanvas do
begin
CM := CopyMode;
CopyMode := cmSrcAnd;
Draw(X, Y, ANDBitmap);
CopyMode := cmSrcPaint;
Draw(X, Y, ORBitmap);
CopyMode := CM;
end;
finally
ANDBitmap.Free;
ORBitmap.Free;
end;
end;
The other way:
abitmap := TBitmap.Create;
imagelist1.Clear;
abitmap.LoadFromFile('1.bmp');
imagelist1.AddMasked(abitmap, clNone);
abitmap.Empty;
abitmap.LoadFromFile('e1.bmp');
imagelist1.AddMasked(abitmap, clWhite);
abitmap.Empty;
abitmap.LoadFromFile('h1.bmp');
imagelist1.AddMasked(abitmap, clWhite);
imagelist1.Draw(image1.Canvas, 0, 0, 0);
imagelist1.Draw(image1.Canvas, 0, 0, 1);
imagelist1.Draw(image1.Canvas, 0, 0, 2);
abitmap.free;
2005. október 22., szombat
Retrieve all table names from an Interbase database
Problem/Question/Abstract:
How to retrieve all table names from an Interbase database
Answer:
unit InterbaseDbTables;
interface
uses
IbDatabase, IbCustomDataSet, SysUtils;
type
TTableType = (ttTable, ttView, ttSystemTable);
type
TTableTypes = set of TTableType;
type
TTableItem = record
ItemName: string;
ItemType: string;
end;
type
TTableItems = array of TTableItem;
function addFilter(string1, string2: string): string;
function IbDbTables(IbDatabase: TIbDatabase; types: TTableTypes): TTableItems;
implementation
function addFilter(string1, string2: string): string;
begin
if string1 <> '' then
Result := string1 + ' or ' + string2
else
Result := string2;
end;
function IbDbTables(IbDatabase: TIbDatabase; types: TTableTypes): TTableItems;
var
IbDataSet: TIbDataSet;
IbTransaction: TIbTransaction;
i: integer;
Filtro: string;
begin
IbDataSet := TIbDataSet.Create(nil);
IbTransaction := TIbTransaction.Create(nil);
IbTransaction.DefaultDatabase := IbDatabase;
IbDataSet.Transaction := IbTransaction;
IbDataSet.SelectSQL.Text := 'SELECT RDB$RELATION_NAME, RDB$SYSTEM_FLAG,
RDB$VIEW_SOURCE FROM RDB$RELATIONS';
if (ttTable in types) then
Filtro := addFilter(Filtro, '((RDB$VIEW_SOURCE IS NULL) and
((RDB$SYSTEM_FLAG = 0) or (RDB$SYSTEM_FLAG is NULL)))');
if (ttView in types) then
Filtro := addFilter(Filtro, '(RDB$VIEW_SOURCE IS NOT NULL)');
if (ttSystemTable in types) then
Filtro := addFilter(Filtro,
'((RDB$SYSTEM_FLAG <> 0) and (RDB$SYSTEM_FLAG IS NOT NULL))');
if Filtro <> '' then
IbDataSet.SelectSQL.Text := IbDataSet.SelectSQL.Text + ' where ' + Filtro;
IbDataSet.Open;
IbDataSet.Last;
SetLength(Result, IbDataSet.RecordCount);
i := 0;
with IbDataSet do
begin
First;
while not Eof do
begin
with Result[i] do
begin
ItemName := Trim(FieldByName('RDB$RELATION_NAME').AsString);
if (not FieldByName('RDB$VIEW_SOURCE').IsNull) then
ItemType := 'VIEW'
else if (FieldByName('RDB$SYSTEM_FLAG').AsInteger <> 0) and
(not FieldByName('RDB$SYSTEM_FLAG').IsNull) then
ItemType := 'SYSTEM'
else
ItemType := 'TABLE';
end;
Inc(i);
Next;
end;
end;
IbDataSet.Close;
IbTransaction.CommitRetaining;
IbDataSet.Free;
IbTransaction.Free;
end;
end.
Example:
Create a new project and add a TIbDatabase (IbDatabase1), a TButton (Button1) and a TMemo (Memo1). Assign the DatabaseName property of the IbDatabase1 component and set "IbDatabase1.Connected := True".
procedure TForm1.Button1Click(Sender: TObject);
var
output: TTableItems;
i: integer;
begin
output := IbDbTables(IbDatabase1, [ttTable, ttView]);
{ output := IbDbTables(IbDatabase1, [ttView]);
output := IbDbTables(IbDatabase1, [ttSystemTable]); }
for i := low(output) to high(output) do
begin
Memo1.Lines.Add(output[i].ItemName + '---' + output[i].ItemType);
end;
output := nil;
end;
2005. október 21., péntek
Edit *.pif files programmatically
Problem/Question/Abstract:
Does anybody know how to create and/ or modify a *.pif programmatically? Windows creates *.pif files for all DOS programs but does not provide any method to edit it except manually. Is that right?
Answer:
procedure CreateShortcut(const FileName: string; Location: ShortcutType);
{Procedure to create a shortcut on the desktop or startmenu}
var
MyObject: IUnknown;
MySLink: IShellLink;
MyPFile: IPersistFile;
Directory: string;
LinkName: string;
IconName: string;
DirName: string;
pifName: WideString;
WFileName: WideString;
QuickLaunchReg: TRegIniFile;
aPidl: PItemIDList;
Res: HResult;
Buf: PByteArray;
PPif: pif_record_ref_type absolute Buf;
Flag: boolean;
i, j: integer;
n: longint;
PHeading: section_heading_record_ref_type;
PVMMSection: windows_vmm_section_ref_type;
PW386Section: ^windows_386_section_type;
f: file;
begin
MyObject := CreateComObject(CLSID_ShellLink);
MySLink := MyObject as IShellLink;
MyPFile := MyObject as IPersistFile;
MySLink.SetPath(PChar(FileName));
LinkName := ChangeFileExt(FileName, '.lnk');
LinkName := ExtractFileName(LinkName);
case Location of
_DESKTOP:
Res := SHGetSpecialFolderLocation(Application.Handle, CSIDL_DESKTOPDIRECTORY,
aPidl);
_STARTMENU:
Res := SHGetSpecialFolderLocation(Application.Handle, CSIDL_STARTMENU, aPidl);
_SENDTO:
Res := SHGetSpecialFolderLocation(Application.Handle, CSIDL_SENDTO, aPidl);
_QUICKLAUNCH:
Res := 0;
end;
if Res <> NOERROR then
begin
case Location of
_DESKTOP:
Directory := 'ShellFolders->Desktop';
_STARTMENU:
Directory := 'ShellFolders->Start Menu';
_SENDTO:
Directory := 'ShellFolders->SendTo';
_QUICKLAUNCH:
Directory := 'MapGroups->Quick Launch';
end;
ShowMessage(Directory + ': Failed');
end
else
begin
{Get the actual path from the PItemIDList}
SetLength(Directory, MAX_PATH);
SHGetPathFromIDList(aPidl, PChar(Directory));
SetLength(Directory, StrLen(PChar(Directory)));
WFileName := Directory + '\' + LinkName;
if (Location = _DESKTOP) and (LinkName = 'PAULITA.lnk') then
begin
pifName := ExtractFilePath(FileName);
Res := MyPFile.Load(PWChar(pifName + 'SYS\PauLita.pif'), 0);
if Res = E_OUTOFMEMORY then
ShowMessage('.PIF LOAD: Out of Memory')
else if Res = E_FAIL then
ShowMessage('.PIF LOAD: Failed');
IconName := pifName + 'SYS\PAULITA.ICO';
Res := MySLink.SetIconLocation(PChar(IconName), 0);
if Res <> NOERROR then
ShowMessage('SetIconLocation: Failed');
end;
MySLink.SetPath(PChar(FileName));
DirName := ExtractFilePath(FileName);
DirName := Copy(DirName, 1, Length(DirName) - 1);
MySLink.SetWorkingDirectory(PChar(DirName));
Res := MyPFile.Save(PWChar(WFileName), FALSE);
if Res <> S_OK then
ShowMessage('Save ' + WFileName + ' Failed');
if (Location = _DESKTOP) and (LinkName = 'PAULITA.lnk') then
begin
Buf := nil;
Assign(f, Directory + '\PAULITA.PIF');
try
Reset(f, 1);
n := FileSize(f);
GetMem(Buf, n);
BlockRead(f, Buf^, n);
PW386Section := nil;
Flag := FALSE;
i := $187;
while i + SizeOf(section_heading_record_type) <= n do
begin
PHeading := @Buf^[i];
{ShowMessage(PHeading^.Name); }
{Look for WINDOWS 386 3.0 group}
if StrPas(@PHeading^.Name) = 'WINDOWS 386 3.0' then
begin
PW386Section := @Buf^[i + SizeOf(section_heading_record_type)];
end;
{Look for WINDOWS VMM 4.0 group}
if StrPas(@PHeading^.Name) = 'WINDOWS VMM 4.0' then
begin
Flag := TRUE;
Break;
end;
i := i + SizeOf(section_heading_record_type) + PHeading^.Len;
end;
if not Flag then
begin
ShowMessage('WINDOWS VMM 4.0 not Found in' + Directory + '\PAULITA.PIF');
end
else
begin
Flag := FALSE;
if (PPif^.Flags1 and CLOSE_ON_EXIT) = $0000 then
begin
PPif^.Flags1 := PPif^.Flags1 or CLOSE_ON_EXIT;
Flag := TRUE;
end;
j := Pos('PAULITA.EXE', PPif^.FileName);
if j > 0 then
begin
StrPCopy(PPif^.FileName, Copy(StrPas(@PPif^.FileName), 1, j - 1) +
'LITA.BAT'#0);
Flag := TRUE;
end;
if PW386Section <> nil then
begin
if (PW386Section^.Flags1 and $00000008) = $0000 then
begin
{Used}
PW386Section^.Flags1 := PW386Section^.Flags1or $00000008;
{Full screen mode}
Flag := TRUE;
end;
if (PW386Section^.MaxEMS <> $FFFF) or (PW386Section^.ReqEMS <> $0000) or
(PW386Section^.MaxXMS <> $FFFF) or (PW386Section^.ReqXMS <> $0000) then
begin
PW386Section^.MaxEMS := $FFFF;
PW386Section^.ReqEMS := $0000;
PW386Section^.MaxXMS := $FFFF;
PW386Section^.ReqXMS := $0000;
Flag := TRUE;
end;
end;
PVMMSection := @Buf^[i + SizeOf(section_heading_record_type)];
if (PVMMSection^.Flags2 and FULL_SCREEN_MODE) = $0000 then
begin
{Not used}
PVMMSection^.Flags2 := PVMMSection^.Flags2 or FULL_SCREEN_MODE;
Flag := TRUE;
end;
if Flag then
begin
Seek(f, 0);
BlockWrite(f, Buf^, n);
end;
end;
finally
Close(f);
if Buf <> nil then
FreeMem(Buf, n);
end;
end;
end;
end;
2005. október 20., csütörtök
How to iterate through the fields of a TTable
Problem/Question/Abstract:
How to iterate through the fields of a TTable
Answer:
There are a number of reasons why a program might need to query the structure of a table used in the application. One reason is a prelude to creating TField components at run-time that represent the fields in the table. The information gleaned from the structure of the table form the basis of the TField components to be created.
The example below demonstrates how to iterate through the fields available in a TTable or TQuery. The example extracts information about the available fields and displays the information in a TListBox, but the same methodology can be used to provide information necessary for the dynamic building of TField descendants. The example uses a TTable as the data set, but a TQuery can be used in the same manner as both TTable and TQuery components incorporate the FieldDefs property the same way.
procedure TForm1.Button1Click(Sender: TObject);
var
i: Integer;
F: TFieldDef;
D: string;
begin
Table1.Active := True;
ListBox1.Items.Clear;
with Table1 do
begin
for i := 0 to FieldDefs.Count - 1 do
begin
F := FieldDefs.Items[i];
case F.DataType of
ftUnknown: D := 'Unknown';
ftString: D := 'String';
ftSmallint: D := 'SmallInt';
ftInteger: D := 'Integer';
ftWord: D := 'Word';
ftBoolean: D := 'Boolean';
ftFloat: D := 'Float';
ftCurrency: D := 'Currency';
ftBCD: D := 'BCD';
ftDate: D := 'Date';
ftTime: D := 'Time';
ftDateTime: D := 'DateTime';
ftBytes: D := 'Bytes';
ftVarBytes: D := '';
ftBlob: D := 'BLOB';
ftMemo: D := 'Memo';
ftGraphic: D := 'Graphic';
else
D := '';
end;
ListBox1.Items.Add(F.Name + ', ' + D);
end;
end;
Table1.Active := False;
end;
2005. október 19., szerda
How to clip a TBitmap inside an irregular shape
Problem/Question/Abstract:
If I have an irregular shape, say a rectangle with the top corners rounded. How can I fill the inside of that shape with a bitmap or limit pen drawing to inside the area of the shape?
Answer:
Below is a small sample of how the problem can be resolved, using a Path converting the Path to a Region and setting the ClippingRgn:
procedure DrawHeader
var
Bmp: TBitmap;
clEnd, clStart: TColor;
R: TRect;
Rd: Integer;
Rgn: HRGN;
begin
Rd := spnTest.IntValue;
GetActiveColors(clStart, clEnd);
Bmp := TBitmap.Create;
try
R.Left := btnClipGradient.Left + btnClipGradient.Width + 20;
R.Top := btnClipGradient.Top;
R.Right := R.Left + 100;
R.Bottom := R.Top + 100;
with Bmp do
begin
Height := 20;
Width := 100;
DrawGradient(Canvas, Rect(0, 0, 100, 20), 50, goHorizontal, clStart, clEnd);
end;
with Canvas do
begin
BeginPath(Handle);
Pen.Color := clYellow;
Pen.Style := psSolid;
{Bottom line}
MoveTo(R.Left, R.Bottom);
LineTo(R.Right, R.Bottom);
{Right Line}
LineTo(R.Right, R.Top + (Rd div 2));
{Top Right}
ArcTo(Handle, R.Right - Rd + 1, R.Top, R.Right + 1, R.Top + Rd, R.Right + 1,
R.Top + (Rd div 2), R.Right - (Rd div 2) + 1, R.Top);
{Top Line}
LineTo(R.Right - (Rd div 2), R.Top);
{Top Left}
ArcTo(Handle, R.Left, R.Top, R.Left + Rd, R.Top + Rd, R.Left + (Rd div 2),
R.Top, R.Left, R.Top + (Rd div 2));
{Left line}
LineTo(R.Left, R.Top + (Rd div 2));
EndPath(Handle);
Rgn := PathToRegion(Handle);
SelectClipRgn(Handle, Rgn);
Draw(R.Left + 1, R.Top + 1, Bmp);
SelectClipRgn(Handle, HRGN(nil));
Pen.Color := clYellow;
Pen.Style := psSolid;
{Bottom line}
MoveTo(R.Left, R.Bottom);
LineTo(R.Right, R.Bottom);
{Right Line}
LineTo(R.Right, R.Top + (Rd div 2));
{Top Right}
ArcTo(Handle, R.Right - Rd + 1, R.Top, R.Right + 1, R.Top + Rd, R.Right + 1,
R.Top + (Rd div 2), R.Right - (Rd div 2) + 1, R.Top);
{Top Line}
LineTo(R.Right - (Rd div 2) + 1, R.Top);
{Top Left}
ArcTo(Handle, R.Left, R.Top, R.Left + Rd, R.Top + Rd, R.Left + (Rd div 2),
R.Top, R.Left, R.Top + (Rd div 2));
{Left line}
LineTo(R.Left, R.Bottom);
DeleteObject(Rgn);
end;
finally
Bmp.Free;
end;
end;
2005. október 18., kedd
Create a transparent form using regions
Problem/Question/Abstract:
When I override the CreateParams method of the TForm class and put an "or" clause to include a WS_EX_TRANSPARENT effect, all work fine. My Form appears with a transparent effect (or better, it does not appear. Only controls appear). But if I change the icons positions on the Desktop screen, my form does not get the changes and redraws itself. Well, I call redraw manually, but the form cannot get the desktop screen rectangle to correct the background of the form. How do I solve this problem? Is there a way to get a selected rectangle of the desktop screen to be copied to the canvas on the form?
Answer:
Here's a transparent form method that uses regions. It's so transparent, you can click right through to the underlying windows:
unit Unit1;
{The transparent form effect is done with regions. First create a region that encompasses the entire form. Then, find the client area of the form (Client vs. non-Client) and combine with the full region with RGN_DIFF to make the borders and title bar visible. Then create a region for each of the controls and combine them with the original (FullRgn) region.}
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls;
type
TForm1 = class(TForm)
Button1: TButton;
Panel1: TPanel;
Button2: TButton;
procedure FormDestroy(Sender: TObject);
procedure FormCreate(Sender: TObject);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure FormResize(Sender: TObject);
private
{ Private declarations }
procedure DoVisible;
procedure DoInvisible;
public
{ Public declarations }
end;
var
Form1: TForm1;
FullRgn, ClientRgn, CtlRgn: THandle;
implementation
{$R *.DFM}
procedure TForm1.DoInvisible;
var
AControl: TControl;
A, Margin, X, Y, CtlX, CtlY: Integer;
begin
Margin := (Width - ClientWidth) div 2;
{First, get form region}
FullRgn := CreateRectRgn(0, 0, Width, Height);
{Find client area region}
X := Margin;
Y := Height - ClientHeight - Margin;
ClientRgn := CreateRectRgn(X, Y, X + ClientWidth, Y + ClientHeight);
{'Mask' out all but non-client areas}
CombineRgn(FullRgn, FullRgn, ClientRgn, RGN_DIFF);
{Now, walk through all the controls on the form and 'OR' them into the existing Full region}
for A := 0 to ControlCount - 1 do
begin
AControl := Controls[A];
if (AControl is TWinControl) or (AControl is TGraphicControl) then
with AControl do
begin
if Visible then
begin
CtlX := X + Left;
CtlY := Y + Top;
CtlRgn := CreateRectRgn(CtlX, CtlY, CtlX + Width, CtlY + Height);
CombineRgn(FullRgn, FullRgn, CtlRgn, RGN_OR);
end;
end;
end;
{When the region is all ready, put it into effect:}
SetWindowRgn(Handle, FullRgn, TRUE);
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
{Clean up the regions we created}
DeleteObject(ClientRgn);
DeleteObject(FullRgn);
DeleteObject(CtlRgn);
end;
procedure TForm1.DoVisible;
begin
{To restore complete visibility:}
FullRgn := CreateRectRgn(0, 0, Width, Height);
CombineRgn(FullRgn, FullRgn, FullRgn, RGN_COPY);
SetWindowRgn(Handle, FullRgn, TRUE);
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
{We start out as a transparent form ...}
DoInvisible;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
{This button just toggles between transparent and not transparent}
if Button1.Caption = 'Show Form' then
begin
DoVisible;
Button1.Caption := 'Hide Form';
end
else
begin
DoInvisible;
Button1.Caption := 'Show Form';
end;
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
Application.Terminate;
end;
procedure TForm1.FormResize(Sender: TObject);
begin
{Need to address the transparency if the form gets resized. Also, note that Form1 scroll bars are set to VISIBLE/FALSE. I did that to save a little coding here....}
if Button1.Caption = 'Show Form' then
DoInvisible
else
DoVisible;
end;
end.
2005. október 17., hétfő
Some remarks on working with very large images
Problem/Question/Abstract:
I have a function that reads about 100 images of about 3000 x 2000 pixels and makes a small image of 200 x 100 of each. My problem is that I can only read 10 or 15 and then the application crashes. I have inspected the memory and it maintains stable and enough, so I don't know where the problem is. If the images that I load are smaller, I can read all.
Answer:
Differing versions of Windows, graphics drivers, and hardware will fail at some point when working with "huge bitmaps" or "huge DIBs".
I define a "huge bitmap/dib" as a bitmap or DIB that either exceeds the screen's width and or height, or exceeds the memory allocationof one single screen at the native color depth of screen (ie even though a 24 bit bitmap was the same size as the screen in width, it would be considered to be "huge" if it was more than one third the height of the screen when used under a 8 bit (256 color) video mode.
I make this definition of "huge", as we know that it is safe to work with bitmaps/dibs that are screensize or smaller, and the failures will generally occur at some size that is larger. This is not ment to say that working with bitmaps that are screensize or smaller will always work, as that depends on the amount GDI resources already in use when compared with the GDI resources that might be available at a given time. I am also not saying that you will always get a failure when working with a huge bitmap, only that it is very common (as you have already found out).
Let me quickly mention that resizing can bring one other possible limitation to the table. Certain versions of Windows (and/or Drivers) may be limited to stretching an image to a minimum of 8 times its original size.
Your only safe solution for "huge bitmaps/dibs" is to work with them without getting Windows and its drivers involved in the process until you actually display them. This means you would work with the bitmaps as DIBS (device independent bitmaps) (for example in the raw form that they exist on the disk), and do all resizing using internal code that does not depend on Windows or its drivers.
I will mention that it requires a large amount of low level code to work with the differing bitmap formats, where you might want to copy, and resize between them. The trick for drawing to huge dibs is you would need to copy workable sized sections from the DIB, convert the section to a bitmap (or use a DIB section) draw on it, convert back to a DIB, and copy the section back to the original huge DIB.
2005. október 16., vasárnap
How to draw disabled (greyed-out) text on a TCanvas
Problem/Question/Abstract:
Is there any way I can set some flags for Canvas.TextOut or something similar to draw text in disabled mode, or do I need to do this by writing my own routine?
Answer:
procedure DrawDisabledCaption(aCanvas: TCanvas; X, Y: Integer; aCaption: string);
var
oldcolor: TColor;
begin
{ ... }
with aCanvas do
begin
oldcolor := font.color;
font.color := clBtnHighlight;
TextOut(X, Y, aCaption);
font.color := clBtnShadow;
TextOut(X + 1, Y + 1, aCaption);
end;
end;
2005. október 15., szombat
How to fix BDE Error "2B04 Too many open files"
Problem/Question/Abstract:
What should I do with error 11012 ("2B04 Too many open files"). You may need to increase MAXFILEHANDLE limit in IDAPI configuration.
Answer:
Open the tool 'BDE Administrator' and choose the right tab 'Configuration'. There open the node 'System' and click on subnode 'INIT'. Then update 'MaxFileHandles' on the right side (it is 48 by default).
2005. október 14., péntek
How to list all links of a page of a TWebbrowser.txt
Problem/Question/Abstract:
How to list all links of a page of a TWebbrowser
Answer:
procedure TForm1.Button1Click(Sender: TObject);
var
i: Integer;
begin
for i := 0 to Webbrowser1.OleObject.Document.links.Length - 1 do
Listbox1.Items.Add(Webbrowser1.OleObject.Document.Links.Item(i));
end;
{*****************}
{ if there are frames }
procedure TForm1.Button2Click(Sender: TObject);
var
u: variant;
v: IDispatch;
s: string;
procedure RecurseLinks(htmlDoc: variant);
var
BodyElement: variant;
ElementCo: variant;
HTMLFrames: variant;
HTMLWnd: variant;
j, i: integer;
begin
if VarIsEmpty(htmlDoc) then
exit;
BodyElement := htmlDoc.body;
if BodyElement.tagName = 'BODY' then
begin
ElementCo := htmlDoc.links;
j := ElementCo.Length - 1;
for i := 0 to j do
begin
u := ElementCo.item(i);
s := u.href;
listLinks.Items.Add(s);
end;
end;
HTMLFrames := htmlDoc.Frames;
j := HTMLFrames.length - 1;
for i := 0 to j do
begin
HTMLWnd := HTMLFrames.Item(i);
RecurseLinks(HTMLWnd.Document);
end;
end; // RecurseLinks
begin
v := WebBrowser1.document;
listLinks.Clear;
RecurseLinks(v);
end;
2005. október 13., csütörtök
How to find values in a string
Problem/Question/Abstract:
I have a string which contains values separated by "," and not necessarily in numeric order (1, 50, 100, 2, 5, 10, ...). What is the best and fastest way to search through this string to find a value, for example 100?
Answer:
type
TIntArray = array of integer
procedure StringToIntArray(const S: string; var List: TIntArray);
{ Converts "S" to an array of integer -> "List" }
const
ValidChars: set of char = ['0'..'9', '-'];
var
Ix, Ix2, Len, C: Integer;
SubStr: string;
Value, Code: Integer;
begin
Len := Length(S);
SetLength(List, Len);
if Len = 0 then
Exit;
C := 0;
Ix := 1;
while Ix <= Len do
begin
while (Ix <= Len) and (not (S[Ix] in ValidChars)) do
Inc(Ix);
Ix2 := Ix;
while (Ix <= Len) and (S[Ix] in ValidChars) do
Inc(Ix);
SubStr := Copy(S, Ix2, Ix - Ix2);
Val(SubStr, Value, Code);
if Code = 0 then
begin
List[C] := Value;
Inc(C);
end;
end;
SetLength(List, C);
end;
function FindValue(Value: Integer; List: TIntArray): Integer;
{ Returns index of requested value, or -1 if not found. }
var
Ix: Integer;
begin
Result := -1;
Ix := 0;
while Ix < Length(List) do
begin
if List[Ix] = Value then
begin
Result := Ix;
Exit;
end;
Inc(Ix);
end;
end;
Example:
StringToIntArray('(1, 50, 100, 2, 5, 10,.....)', MyIntArray)
sets the contents of MyIntArray to [1,50,100,2,5,10].
then FindValue(100, MyIntArray) returns 2, as MyIntArray[2] = 100;
2005. október 12., szerda
How to jump to a specific line in a text file and return the line in a string
Problem/Question/Abstract:
I'm trying to write a function that, given a FileName and a line number, returns the entire line in a string.
Answer:
This technique is useful for high-speed processing. Save the sample program file with a .pas or .dpr file name and compile it with dcc32:
{$APPTYPE CONSOLE}
uses
SysUtils, Classes;
function GrabLine(const AFileName: string; ALine: Integer): string;
var
fs: TFileStream;
buf: packed array[0..4095] of Char;
bufRead: Integer;
bufPos: PChar;
lineStart: PChar;
tmp: string;
begin
fs := TFileStream.Create(AFileName, fmOpenRead);
try
Dec(ALine);
bufRead := 0;
bufPos := nil;
{ read the first line specially }
if ALine = 0 then
begin
bufRead := fs.Read(buf, SizeOf(buf));
if bufRead = 0 then
raise Exception.Create('Line not found');
bufPos := buf;
end
else
while ALine > 0 do
begin
{ read in a buffer }
bufRead := fs.Read(buf, SizeOf(buf));
if bufRead = 0 then
raise Exception.Create('Line not found');
bufPos := buf;
while (bufRead > 0) and (ALine > 0) do
begin
if bufPos^ = #10 then
Dec(ALine);
Inc(bufPos);
Dec(bufRead);
end;
end;
{ Found the beginning of the line at bufPos... scan for end.
Two cases:
1) we'll find it before the end of this buffer
2) it'll go beyond this buffer and into n more buffers }
lineStart := bufPos;
while (bufRead > 0) and (bufPos^ <> #10) do
begin
Inc(bufPos);
Dec(bufRead);
end;
{ if bufRead is positive, we'll have found the end and we can leave. }
SetString(Result, lineStart, bufPos - lineStart);
{ determine if there are more buffers to process }
while bufRead = 0 do
begin
bufRead := fs.Read(buf, SizeOf(buf));
lineStart := buf;
bufPos := buf;
while (bufRead > 0) and (bufPos^ <> #10) do
begin
Inc(bufPos);
Dec(bufRead);
end;
SetString(tmp, lineStart, bufPos - lineStart);
Result := Result + tmp;
end;
finally
fs.Free;
end;
end;
function GrabLine2(const s: string; ALine: Integer): string;
var
sl: TStringList;
begin
sl := TStringList.Create;
try
sl.LoadFromFile(s);
Result := sl[ALine - 1]; { index off by one }
finally
sl.Free;
end;
end;
begin
Writeln(GrabLine(ParamStr(1), StrToInt(ParamStr(2))));
Writeln(GrabLine2(ParamStr(1), StrToInt(ParamStr(2))));
end.
Call it like 'getline testfile.txt 20000', depending on what you call the .pas (or .dpr) file. For large (i.e. tens of megabytes) files, the (rather complex) scanning function easily beats the memory expensive StringList version.
2005. október 11., kedd
Enable and disable sound from your application
Problem/Question/Abstract:
Ever needed to completely disable and then re-enable audio capabilities from your application? There's the way to simple do it.
Answer:
unit AudioCtrl;
interface
procedure EnableAudio;
procedure DisableAudio;
implementation
uses
MMSystem;
var
MyVolume: array[0..10] of LongInt;
mDevs: Integer;
procedure EnableAudio;
var
I: Integer;
begin
for I := 0 to mDevs do
begin
auxSetVolume(I, MyVolume[I]);
end;
end;
procedure DisableAudio;
var
I: Integer;
begin
mDevs := auxGetNumDevs;
for I := 0 to mDevs do
begin
auxGetVolume(I, Addr(MyVolume[I]));
auxSetVolume(I, LongInt(9000) * 65536 + LongInt(9000));
end;
end;
end.
2005. október 10., hétfő
How to search a TQuery result set
Problem/Question/Abstract:
The TQuery component does not offer the index-based search capabilities of the TTable component (FindKey, GotoKey, and GotoNearest). So how do you search within the result data set from a TQuery to find a row with a specific field value?
Answer:
One way to search a query result set is a sequential search. This type of search starts at the first row in the data set and, in a While loop, sequentially compares the value of a field in the row with a search value. One of two results are possible: a value will be found (success) or the end of the data set will be reached (failure). The problem with this way of searching the data set is that the further into the data set a row with a matching value is, the longer it takes to arrive at that row. And, a failed search takes longest of all because it must go all the way to the last row in the data set. If the data set being searched is a large one, this process may take a considerable amount of time.
Here is a function that will perfoorm a sequential search of the result set from a TQuery:
function SeqSearch(AQuery: TQuery; AField, AValue: string): Boolean;
begin
with AQuery do
begin
First;
while (not Eof) and (not (FieldByName(AField).AsString = AValue)) do
Next;
SeqSearch := not Eof;
end;
end;
This function takes three parameters:
AQuery: type TQuery; the TQuery component in which the search is to be executed.
AField: type String; the name of the field against which the search value will be compared.
AValue: type String; the value being searched for. If the field is of a data type other than String, this search value should be changed to the same data type.
The Boolean return value of this function indicates the success (True) or failure (False) of the search.
An alternative is using a bracketing approach. On a conceptual level, this method acts somewhat like a bb-tree index. It is based on the given that for a row at a given point in the data set, the value of the field being searched compared to the search value will produce one of three possible conditions:
The field value will be greater than the search value, or
The field value will be less than the search value, or
The field value will be equal to the search value.
A bracketing search process uses this means of looking at the current row in respect to the search value and uses it to successively reduce the rows to be search by half, until only one row remains. This search field value for this sole remaining row will either be a match to the search value (success) or it will not (failure, and no match exists in the data set).
Functionally, this process lumps the condition of the search field being less than or equal to the search value into a single condition. This leaves only two possible results for the comparison of the current search field valuue with the search value: less than/equal to or greater than. Initially, a range of numbers is established. The low end of the range is represented by an Integer, at the start of the search process set to 0 or one less than the first row in the data set. The far end of the range is also an Integer, with the value of the RecordCount property of the TQuery. The current row pointer is then moved to a point half way between the low and high ends of the range. The search field value at that row is then compared to the search value. If the field value is less than or equal to the search value, the row being sought must be in the lower half of the range of rows so the high end of the range is reduced to the current row position. If the field value is greater than the search value, the sought value must be in the higher half of the range and so the low end is raised to the current point. By repeating this process, the number of rows that are encompassed in the range are successivelly reduced by half. Eventually, only one row will remain.
Putting this into a modular, transportable function, the code would look like that below:
function Locate(AQuery: TQuery; AField, AValue: string): Boolean;
var
Hi, Lo: Integer;
begin
with AQuery do
begin
First;
{Set high end of range of rows}
Hi := RecordCount;
{Set low end of range of rows}
Lo := 0;
{Move to point half way between high and low ends of range}
MoveBy(RecordCount div 2);
while (Hi - Lo) > 1 do
begin
{Search field greater than search value, value in first half}
if (FieldByName(AField).AsString > AValue) then
begin
{Lower high end of range by half of total range}
Hi := Hi - ((Hi - Lo) div 2);
MoveBy(((Hi - Lo) div 2) * -1);
end
{Search field less than search value, value in far half}
else
begin
{Raise low end of range by half of total range}
Lo := Lo + ((Hi - Lo) div 2);
MoveBy((Hi - Lo) div 2);
end;
end;
{Fudge for odd numbered rows}
if (FieldByName(AField).AsString > AValue) then
Prior;
Locate := (FieldByName(AField).AsString = AValue)
end;
end;
Because there will never be a difference of less than one between the low and high ends of the range of rows, a final fudge was added to allow the search to find the search value in odd numbered rows. This function takes the same three three parameters as the SeqSearch function described earlier.
The return value of this function is of type Boolean, and reflects the success or failure of the search. As the search does move the row pointer, the effects of this movement on the implicit posting of changed data and on where the desired position of the row pointer should be after a failed search should be taken into account in the calling application. For instance, a TBookmark pointer might be used to return the row pointer to where it was prior to a search if that search fails.
How is this process better than a sequential search? First, in bracketing the search value, only a fraction of the number of rows will be visited as would be the case in a sequential search. Unless the row with the value being sought is in the first 1,000 rows, this search method will be faster than a sequential search. Because this process always uses the same number of records, the search time will be consistent whether searching for the value in row 1,000 or row 90,000. This is in contrast with the sequential search that takes longer the farther into the data set the desired row is.
Can this method be used with any TQuery result set? No. Because of the way this method works in basing the direction of the search as either high or low, it depends on the row being ordered in a descending manner based on the field in which the search will be conducted. This means that it can only be used if the data set is naturally in a sequential order or an ORDER BY clause is used in the SQL statement for the TQuery. The size of the result set will also be a factor when deciding whether to perform a sequential or bracketing search. This process is most advantageous for speed when used with larger result sets. With smaller sets (1,00 or less rows), though, a sequential search will often be as fast or faster.
2005. október 9., vasárnap
Move/ Resize components in IDE without the mouse
Problem/Question/Abstract:
Move/ Resize components in IDE without the mouse
Answer:
You can move visual components on a form with Ctrl+Arrow Key, and resize them with Shift+Arrow Key.
2005. október 8., szombat
How to detect if a menu as a whole is currently open or selected
Problem/Question/Abstract:
How to detect if a menu as a whole is currently open or selected
Answer:
{ ... }
type
TForm1 = class(TForm)
MainMenu1: TMainMenu;
item01: TMenuItem;
item11: TMenuItem;
item21: TMenuItem;
private
{ Private declarations }
public
procedure WMMENUSELECT(var M: TWMMENUSELECT); message WM_MENUSELECT;
end;
{...}
procedure TForm1.WMMENUSELECT(var M: TWMMENUSELECT);
begin
inherited;
{This beeps even if it is the sysmenu (control menu) and/or on any selected item: }
{ messagebeep(MB_ICONASTERISK); }
{ This beeps when MainMenu1 is opened, but only beeps on item[0]: }
if M.menu = Mainmenu1.handle then
messagebeep(MB_ICONASTERISK);
end;
end.
2005. október 7., péntek
How to create a TStatusBar with resizable panels
Problem/Question/Abstract:
How to create a TStatusBar with resizable panels
Answer:
In this demo the TStatusBar has three panels. Only panels 1 and 2 need to be adjustable and each has a set minimum width of 20. The StatusBar OnResize event is used to keep the panels in view, regardless of form resizing, except where the StatusBar width is less than 40.
{ ... }
private
{Private declarations}
StatusMouseDown: Boolean;
Split: Integer;
{ ... }
procedure TMainForm.FormCreate(Sender: TObject);
begin
StatusMouseDown := false;
end;
procedure TMainForm.StatusBar1MouseUp(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
StatusMouseDown := false;
end;
procedure TMainForm.StatusBar1MouseDown(Sender: TObject;
Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
var
S1, S2: Integer;
begin
if Button = mbLeft then
begin
StatusMouseDown := true;
Split := 0;
S1 := StatusBar1.Panels[0].Width;
S2 := StatusBar1.Panels[1].Width + S1;
if ((X > S1 - 3) and (X < (S1 + 3))) then
Split := 1
else if ((X > S2 - 3) and (X < (S2 + 3))) then
Split := 2;
end;
end;
procedure TMainForm.StatusBar1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
S1, S2, S3: Integer;
Split1, Split2: Boolean;
begin
Split1 := false;
Split2 := false;
S1 := StatusBar1.Panels[0].Width;
S2 := StatusBar1.Panels[1].Width + StatusBar1.Panels[0].Width;
S3 := StatusBar1.Width;
if ((X > S1 - 3) and (X < (S1 + 3))) then
Split1 := true
else if ((X > S2 - 3) and (X < (S2 + 3))) then
Split2 := true;
if (Split1 or Split2) then
StatusBar1.Cursor := crHSplit
else
StatusBar1.Cursor := crDefault;
if StatusMouseDown then
begin
if (Split = 1) then
begin
if (X < 20) then
StatusBar1.Panels[0].Width := 20
else if (X > (S3 - 40)) then
begin
StatusBar1.Panels[0].Width := S3 - 40;
StatusBar1.Panels[1].Width := S3 - S1 - 20;
end
else if (X >= 20) and (X <= S3 - 20) then
begin
StatusBar1.Panels[0].Width := X;
if ((X + StatusBar1.Panels[1].Width + 20) >= S3) then
StatusBar1.Panels[1].Width := S3 - X - 20;
end;
end;
if (Split = 2) then
begin
if (X < (S1 + 20)) then
StatusBar1.Panels[1].Width := 20
else if (X > (S3 - 20)) then
StatusBar1.Panels[1].Width := S3 - S1 - 20
else if (X >= S1 + 20) and (X <= S3 - 20) then
StatusBar1.Panels[1].Width := X - S1;
end;
end;
end;
procedure TMainForm.StatusBar1Resize(Sender: TObject);
var
S1, S2, S3: Integer;
begin
S1 := StatusBar1.Panels[0].Width;
S2 := StatusBar1.Panels[1].Width + StatusBar1.Panels[0].Width;
S3 := StatusBar1.Width;
if (S1 >= (S3 - 40)) then
begin
StatusBar1.Panels[0].Width := S3 - 40;
StatusBar1.Panels[1].Width := 20;
end
else if (S2 >= (S3 - 20)) then
StatusBar1.Panels[1].Width := S3 - S1 - 20;
end;
2005. október 6., csütörtök
Display a *.hlp file on a TForm
Problem/Question/Abstract:
Is there some sort of component that allows me to show a certain page from a normal *.hlp file (non HTML) in a form?
Answer:
As far as I know it cannot be done. The closest you can come to it is to take the standard WinHelp window and parent it to your form via Windows.SetParent. The WinHelp main window has a classname of 'MS_WINDOC', you can use that with FindWindow to find its window handle.
procedure TForm1.Button1Click(Sender: TObject);
var
wnd: HWND;
begin
wnd := FindWindow('MS_WINDOC', nil);
if wnd <> 0 then
begin
Windows.SetParent(wnd, handle);
Windows.MoveWindow(wnd, 0, 0, clientwidth, clientheight, true);
end;
end;
2005. október 5., szerda
How to create only one instance of a MDI child form (2)
Problem/Question/Abstract:
I am writing a MDI application. In this application, I just want to create one instance of the MDI child form. That is, if the user clicks New the first time, a MDI child form is created, but on subsequent click, no MDI child will be created. How can I do this and how can I refer to the MDI child that I have created.
Answer:
I assume you know the method to create an instance of a MDIChild form, but I'm going to go through it just in case:
Create your form, set its formstyle property to fsMDIChild, remove it from the autocreate list in your Project|Options.
Use the following code to create the form at run-time (I'm using Button1.Click to create the event):
procedure TMainForm.Button1Click(Sender: TObject);
var
MyChildForm: TMDIChild {where TMDIChild is TNameOfYourForm}
begin
MyChildForm := TMDIChild.Create(Application);
end;
Now, to ensure that only 1 instance of a MDIChild is created change the above code to read:
begin
if MDIChildCount < 1 then
MyChildForm := TMDIChild.Create(Application);
end;
This code will ensure that only one MDIChild can be open at a given time. Remember to place Action:=caFree in the OnClose event handler of your MDIChild to free memory and resources when it's closed.
To access properties of your MDIChild (Even though there's only going to be 1 in existence) you must use the following type of code (Here an event of the MDIParent is going to disable a component on the MDIChild):
procedure TMainForm.TurnItOffClick(Sender: TObject);
begin
MyChildForm(ActiveMDIChild).Button1.Enabled := False;
end;
Now, you didn't state whether you only wanted 1 MDIChild form open in the parent form or only 1 of each particular type. If it's the latter, you'll need to access the classes of all your MDI children to see if an instance already exists.
2005. október 4., kedd
Debugging a DLL
Problem/Question/Abstract:
Debugging a DLL
Answer:
This is done by setting a host application and choosing Delphi 5 (delphi32.exe) itself as the host application. Theoretically very easy - available since Delphi 3.
But there are two catches:
It seems that
the DLL has to be in the same directory as its DPR file (in other words: don't have a separate output directory)
your project path should not contain directories with a space in the name. There seems to be a problem with debugging DLL's with spaces in the project path.
2005. október 3., hétfő
Delphi hangs on startup
Problem/Question/Abstract:
There is a bug in some video cards in the way they handle the ImageList API. When an ImageList is being built in memory that cards will freeze the computer or corrupt the images when they have to swap the ImageList back out to main memory to complete the building on large imagelist's like the 180+ image component pallet list. The S3 chipset has been particularly problematic (Win98 is even worse).
Answer:
Try these:
Apply SP2 and SP3 to your Delphi4 at http://www.inprise.com/devsupport/delphi/downloads/index.html. SP2 tries to work around the problem by pre building the imagelist large enough so it should not start in internal memory and start in main memory.
Get the latest driver (www.s3.com or your vendor if your vendors doesn't work, try the S3 as they tend to be more compliant)
Start Delphi with the -ns switch
Under the [display] section of the SYSTEM.INI file add the following line BusThrottle=1 (reboot afterwards)
Reboot in safe mode and start Delphi. Goto the properties of the component pallet and hide all the controls that you rarely use. This will reduce the imagelist that is getting built and might get you under the threshold.
Reduce the hardware acceleration for the card. (display->properties->settings->advances->performance)
Play with color depth/resolution 1024x768 in 16 bit color seems the worse (particularly with Delphi 3).
Get the latest DirectX drivers.
Also Delphi 4 can not run in Win98 under 16 color mode. You must be at least at 256 colors.
Both SP2 and SP3 try to work around this by creating the initial ImageList large enough so that it either
a) starts in main memory or
b) will not have to grow while adding.
There really is not much more that Borland can do. This is a driver bug and the work around have actually exposed bugs in other drivers that deal with creating large initial ImageLists. In this case Borland was damned if they did, damned if they didn't. SP1 was included in SP2 so when you installed SP2 you also got the first patch. If the BusThrottle works for you this is by far the best solution. All it does is instruct the driver that it must give a
little time slice to the OS so affects on performance are not noticable (so far no one has reported back to me that they can even tell a difference between using and not using the BusThrottle setting except that Delphi runs). Unfortunately not all drivers respect this setting.
2005. október 2., vasárnap
Select a recipient from addressbook
Problem/Question/Abstract:
How can I select a recipient from addressbook?
Answer:
Today I want to post a tip about copying of recipients from default addressbook. For example, user must select a list of recipients and you'll process this list in own code (to send a message to these recipients or just import them into own database).
For this task you can use the MAPIAddress procedure from MAPI. This procedure requires a handle of current session (which you'll receive from MAPILogOn procedure), custom caption of dialog, structure for recepient attributes and variable where you'll receive a number of selected recipients.
If MSPIAddress returns SUCCESS_SUCCESS, this mean that user closed a dialog and selected some recipients. After that you must navigater by recipient structure (which you defined as parameter) and process the each recipient.
For example:
var
lpRecip: TMapiRecipDesc;
intRecips: ULONG;
lpRecips: PMapiRecipDesc;
i: Integer;
begin
if (MAPIAddress(intMAPISession, 0, 'Select the recipients', 4, '', 0, lpRecip, 0, 0,
@intRecips, lpRecips) = SUCCESS_SUCCESS) then
begin
for i := 0 to intRecips - 1 do
yourListBox.Items.Add(PMapiRecipDesc(PChar(lpRecips) + i *
SizeOf(TMapiRecipDesc))^.lpszAddress);
MAPIFreeBuffer(lpRecips)
end;
end;
I hope that this tip will help you and save some time. At least when I wrote (in October 2000) this code for own GroupMail, I spent a lot of time for correct work without errors:-)
2005. október 1., szombat
How to reset the content of a TDBMemo
Problem/Question/Abstract:
I have a form with a TDBMemo control. I also have an "Abort" button which, when pressed, will reset the contents of the TDBMemo control. I know that if you hit the escape key in a TDBMemo this will reset the contents. However, my users are used to hitting the Esc key to exit forms, so I disabled this feature so that they would not lose everything they entered. I did this by changing the value of the Key code in the OnKeyPress event:
if key = #27 then
begin
key := #0;
I have looked in the VCL and have noticed that when ESC is pressed FDataLink.Reset is invoked. However, this is private, so there is not way to reset the contents. Is there another way around this?
Answer:
All DbCtrls have a "procedure CMGetDataLink(var Message: TMessage); message CM_GETDATALINK;" which can be executed to return the TFieldDataLink as follows:
procedure TForm1.SpeedButton1Click(Sender: TObject);
{must be TSpeedButton so Focus change does take place first}
var
fDataLink: TFieldDataLink;
begin
fDataLink := TFieldDataLink(DBMemo1.Perform(CM_GETDATALINK, 0, 0));
if assigned(fDataLink) then
fDataLink.Reset;
end;
Feliratkozás:
Bejegyzések (Atom)