2008. december 31., szerda
Convert 8 or 16 bit color images to 32 bit
Problem/Question/Abstract:
How do I convert a 8 bit color image or a 16 bit color image to 32 bits of color? In my case, I have files with 8bit or 16bit images which I read using scanline. So I have one or two bytes determining the pixel color. What I want to do is still read one or two bytes for each pixel, for either the 8bit or the 16bit, but convert it to 32 bits on display.
Answer:
I think you mean 8 or 16 bit per pixel, not 8 or 16 per sample. You must divide the 8 bit and the 16 bit. 8 bit is a indexed image (palette image). The pixel value is an index in a color table (palette). You need a pointer to this color table and get the color from this table. 16 bit is an RGB image, the pixel directly contains the color data. Unfortunately, you must determine the pixel mask. On Win9x the data can be 5 bit blue, 6 bit green, 5 bit red or 5 bit blue, 5 bit green, 5 bit red (bit 15 is unused). On WinNT or later the data can be stored in other combinations, but this is unlikely.
From a own project to convert such pixel data:
function DIB555ToBGR(Value: DWord): DWord; assembler;
asm
{blue}
mov ecx, eax
shl eax, 3
mov edx, eax
and eax, $0000F8
shr ecx, 2
and ecx, $000007
or eax, ecx
{green}
shl edx, 3
mov ecx, edx
and edx, $00F800
or eax, edx
shr edx, 5
and edx, $000700
or eax, edx
{red}
shl ecx, 3
and ecx, $F80000
or eax, ecx
shr ecx, 5
and ecx, $070000
or eax, ecx
end;
function DIB565ToBGR(Value: DWord): DWord; assembler;
asm
{blue}
mov ecx, eax
shl eax, 3
mov edx, eax
and eax, $0000F8
shr ecx, 2
and ecx, $000007
or eax, ecx
{green}
shl edx, 2
mov ecx, edx
and edx, $00FC00
or eax, edx
shr edx, 6
and edx, $000300
or eax, edx
{red}
shl ecx, 3
and ecx, $F80000
or eax, ecx
shr ecx, 5
and ecx, $070000
or eax, ecx
end;
However, I think you should simple use TBitmap.PixelFormat:
PixelFormat := pf32Bit;
This sets the bitmap to 32 Bit.
On the other side, you can directly display the 8 or 16 bit bitmap, the WinAPI convert the bitmap to the correct format.
2008. december 30., kedd
Coloring Cells in a StringGrid / DBGrid
Problem/Question/Abstract:
StringGrids / DBGrids with colored cells looks very nice and you can inform the user about importent data inside the Grid.
Answer:
Unfortunately you can't use the same method for coloring StringGrids and DBGrids. So first let's have a look to the StringGrid:
1. StringGrid
Use the "OnDrawCell"-event to make your StringGrids colorful! The following Code shows how to give your Grid a red background color. The second column will be colored with green background.
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
const //define your color here. Of course you
//can use default colors too.
clPaleGreen = TColor($CCFFCC);
clPaleRed = TColor($CCCCFF);
begin
//Does the cell have the focus you have to paint it with other colors
if (gdFocused in State) then
begin
StringGrid1.Canvas.Brush.Color := clBlack;
StringGrid1.Canvas.Font.Color := clWhite;
end
else //Does the cell have NOT the focus you can use
//your personal colors here
if ACol = 2 //the second Column should be
{//green, the other cells red } then
StringGrid1.Canvas.Brush.color := clPaleGreen
else
StringGrid1.canvas.brush.Color := clPaleRed;
//Now Paint the cells, but only, if the cell isn't the Title- Row/Column
//This of course depends whether you have title-Row/Columns or not.
if (ACol > 0) and (ARow > 0) then
begin
//Painting the Background
StringGrid1.canvas.fillRect(Rect);
//Painting the Text. Here you can improve the code with
// using alignment and so on.
StringGrid1.canvas.TextOut(Rect.Left, Rect.Top, StringGrid1.Cells[ACol, ARow]);
end;
end;
If you want to colorize your cells depending on values in the cells you can replace the 3 lines (if Acol = 2 ......) with something like this
if StringGrid1.Cells[ACol, ARow] = 'highlight it' then
StringGrid1.Canvas.Brush.color := clPalered
else
StringGrid1.canvas.brush.Color := clwhite;
But now lets coloring DBGrids:
2. DBGrid
It's much easier to give color to DBGrids. Here you have to use the "OnDrawColumnCell"-Event. The following example is coloring the Cells of Column "Status" when the value is not "a".
If you want to color the whole line you only have to delete the "If..." statement (see below)
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn;
State: TGridDrawState);
const
clPaleGreen = TColor($CCFFCC);
clPaleRed = TColor($CCCCFF);
begin
if Column.FieldName = 'Status' then //Remove this line, if you want
//to highlight the whole line
if Column.Field.Dataset.FieldbyName('Status').AsString <> 'a' then
if (gdFocused in State) {//does the cell have the focus? } then
dbgrid1.canvas.brush.color := clBlack //focused
else
dbgrid1.canvas.brush.color := clPaleGreen; //not focused
//Now let's paint the cell using a Default-Method:
dbgrid1.DefaultDrawColumnCell(rect, DataCol, Column, State)
end;
2008. december 29., hétfő
How to draw text on the Windows taskbar
Problem/Question/Abstract:
Does anyone know how to write a text on the main taskbar in Win95 using Delphi 3.0?
Answer:
This modified splash procedure that I use draws text directly on the Start button. Perhaps it helps. I normally use it to draw splash text directly on screen. To do that, use DC:= GetDC(0):
procedure TForm1.Button1Click(Sender: TObject);
var
DC: hDC;
Size: TSize;
Font: hFont;
const
DispText = 'Test';
begin
DC := GetDC(GetDlgItem(FindWindow(PChar('Shell_TrayWnd'), nil), $130));
ShowMessage(IntToStr(GetDlgItem(FindWindow(PChar('Shell_TrayWnd'), nil), $130)));
SetBkMode(DC, TRANSPARENT);
Font := CreateFont(12, 10, 0, 0, 1000, 0, 0, 0, ANSI_CHARSET, OUT_DEVICE_PRECIS,
CLIP_DEFAULT_PRECIS, PROOF_QUALITY, DEFAULT_PITCH, 'ARIAL');
SelectObject(DC, Font);
SetTextColor(DC, RGB(128, 128, 0));
GetTextExtentPoint(DC, PChar(DispText), Length(DispText), Size);
TextOut(DC, 0, 0, PChar(DispText), Length(DispText));
DeleteObject(Font);
ReleaseDC(GetDlgItem(FindWindow(PChar('Shell_TrayWnd'), nil), $130), DC);
end;
2008. december 28., vasárnap
How to use a TImage as the background for a TDBGrid
Problem/Question/Abstract:
I would like to have a DBGrid on a form that contains a bitmap and have the text in the cells of the grid float on the bitmap. I've tried using SetBKMode but apparently I'm using it incorrectly. Is there a separate canvas for each cell as well as the grid itself?
Answer:
Place a TImage on a form or create it off-screen and put the following (untested) code in the OnDrawColumnCell event of the grid:
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
var
w, h: Integer;
begin
with DBGrid1.Canvas do
begin
w := Rect.Right - Rect.Left;
h := Rect.Bottom - Rect.Top;
BitBlt(DBGrid1.Canvas.Handle, Rect.Left, Rect.Top, w, h,
Image1.Picture.Bitmap.Canvas.Handle, Rect.Left, Rect.Top, SRCCOPY);
Brush.Style := bsClear;
TextOut(Rect.left, Rect.Top, Column.Field.AsString);
end;
end;
2008. december 27., szombat
Return identity id from insert_SQL
Problem/Question/Abstract:
How can i obtain the value of the identity column in a table, when inserting a record with a INSERT SQL statement.
Answer:
Found the following note when surfing several sql-dba website forums:
You can use the SET NOCOUNT statement. SET NOCOUNT ON will prevent SQL Server from telling you how many rows each statement affected. SET NOCOUNT OFF will return SQL Server back to it's default setting. The variable @@ROWCOUNT will always contain the number of rows affected by the previous statement regardless of the setting of NOCOUNT
do use this statement to obtain the value of the inserted id:
whe have a table called TheTable with fields:
Field_ID (identity column)
Field001 (varchar)
Field002 (int)
Set Nocount on
Insert TheTable (Field001, Field002)
VALUES ('ABC', 1)
select IdentityInsert=@@identity
set nocount off
Example usage:
I have an order table with the primary order data, and a related table called orderdetails with the orderdetails per primary order data. The primary order table contains a key, field OrderID, of type identity-column autoincrement start at 0 and increment with 1. The table orderdetails contains the orderId as foreign key.
Case asp e-business website:
The visitor wants to checkout the order wich is composed. Now fire the insert sql to insert a record in the primary order table, using the script in this article to obtain the order id. With the obtained order id fire the several sql_inserts into the the order details table.
Tested with sqlserver 7, ado, d5, asp
2008. december 26., péntek
Create a Menu from XML-File
Problem/Question/Abstract:
How can you build up the content of e.g. a TMainMenu from a XML-File?
Perhaps you have a program which also includes an administration part for a database, but only some users should be able to see and use this administration part. Why don't create a XML-File, which contains the menu of the program. When the program starts, it builds its menu from this XML-File. And only the people who should be able to see and use the administration part get the XML-File which contains it, all other users have a file without this part.
This way it's very hard for hackers to get into the administration part.
And when you even encrypt your XML-File, it should nearly unpossible.
But how can we do this?
Answer:
A special feature of the code below: You only need to specify the Name of the procedure which then
will be attached to a OnClick handler (but all this procedures MUST be public)
At first, insert this code in your mainform and add a TMainMenu (without any content) and a TXMLDocument to your form.
procedure TMainForm.CreateMenuFromXMLFile;
function Get_Int(S: string): Integer;
begin
Result := 0;
try
Result := StrToInt(S);
except
end;
end;
procedure AddRecursive(Parent: TMenuItem; Item: IXMLNode);
var
I: Integer;
Node: TMenuItem;
Child: IXMLNode;
Address: TMethod;
begin
Node := TMenuItem.Create(Parent);
if (Uppercase(Item.Attributes['CAPTION']) <> 'SEPERATOR') then
begin
Node.Caption := Item.Attributes['CAPTION'];
if (Uppercase(Item.Attributes['ID']) <> 'NONE') then
begin
Address.Code := MethodAddress(Item.Attributes['ID']);
Address.Data := Self;
if (Item.ChildNodes.Count - 1 < 0) then
Node.OnClick := TNotifyEvent(Address);
end;
if (Uppercase(Item.Attributes['SHORTCUT']) <> 'NONE') then
Node.ShortCut := TextToShortCut(Item.Attributes['SHORTCUT']);
Node.Checked := (Item.Attributes['CHECKED'] = '1');
end
else
Node.Caption := '-';
Node.Visible := (Item.Attributes['VISIBLE'] = '1');
if Parent <> nil then
Parent.Add(Node)
else
MainMenu.Items.Add(Node);
for I := 0 to Item.ChildNodes.Count - 1 do
begin
Child := item.ChildNodes[i];
if (Child.NodeName = 'ENTRY') then
AddRecursive(Node, Child);
end;
end;
var
Root: IXMLMENUType;
Parent: TMenuItem;
I: Integer;
Child: IXMLNode;
begin
XMLDocument.FileName := ExtractFilePath(Application.ExeName) + XMLFile;
if not FileExists(XMLDocument.FileName) then
begin
MessageDlg('Menu-XML-Document not found!', mtError, [mbOK], 0);
Halt;
end;
XMLDocument.Active := True;
Screen.Cursor := crHourglass;
try
Root := GetXMLMenu(XMLDocument);
Parent := nil;
for I := 0 to Root.ChildNodes.Count - 1 do
begin
Child := Root.ChildNodes[i];
if (Child.NodeName = 'ENTRY') then
AddRecursive(Parent, Child);
end;
finally
Screen.Cursor := crDefault;
end;
end;
This was the first step.
You also need the encapsulation of the XML-File.
( Save the code below as unit and add it to your program.
Created with Delphi6 -> New -> XML Data Binding Wizard )
{***************************************************}
{ }
{ Delphi XML-Datenbindung }
{ }
{ Erzeugt am: 27.06.2002 13:25:01 }
{ }
{***************************************************}
unit XMLMenuTranslation;
interface
uses xmldom, XMLDoc, XMLIntf;
type
{ Forward-Deklarationen }
IXMLMENUType = interface;
IXMLENTRYType = interface;
{ IXMLMENUType }
IXMLMENUType = interface(IXMLNode)
['{8F36F5E2-834F-41D9-918F-9B1A441C9074}']
{ Zugriff auf Eigenschaften }
function Get_ENTRY: IXMLENTRYType;
{ Methoden & Eigenschaften }
property ENTRY: IXMLENTRYType read Get_ENTRY;
end;
{ IXMLENTRYType }
IXMLENTRYType = interface(IXMLNode)
['{AD85CD05-725E-40F8-A8D7-D6EC05FD4360}']
{ Zugriff auf Eigenschaften }
function Get_CAPTION: WideString;
function Get_VISIBLE: Integer;
function Get_ID: Integer;
function Get_ENTRY: IXMLENTRYType;
procedure Set_CAPTION(Value: WideString);
procedure Set_VISIBLE(Value: Integer);
procedure Set_ID(Value: Integer);
{ Methoden & Eigenschaften }
property Caption: WideString read Get_CAPTION write Set_CAPTION;
property Visible: Integer read Get_VISIBLE write Set_VISIBLE;
property ID: Integer read Get_ID write Set_ID;
property ENTRY: IXMLENTRYType read Get_ENTRY;
end;
{ Forward-Deklarationen }
TXMLMENUType = class;
TXMLENTRYType = class;
{ TXMLMENUType }
TXMLMENUType = class(TXMLNode, IXMLMENUType)
protected
{ IXMLMENUType }
function Get_ENTRY: IXMLENTRYType;
public
procedure AfterConstruction; override;
end;
{ TXMLENTRYType }
TXMLENTRYType = class(TXMLNode, IXMLENTRYType)
protected
{ IXMLENTRYType }
function Get_CAPTION: WideString;
function Get_VISIBLE: Integer;
function Get_ID: Integer;
function Get_ENTRY: IXMLENTRYType;
procedure Set_CAPTION(Value: WideString);
procedure Set_VISIBLE(Value: Integer);
procedure Set_ID(Value: Integer);
public
procedure AfterConstruction; override;
end;
{ Globale Funktionen }
function GetXMLMENU(Doc: IXMLDocument): IXMLMENUType;
function LoadMENU(const FileName: WideString): IXMLMENUType;
function NewMENU: IXMLMENUType;
implementation
{ Globale Funktionen }
function GetXMLMENU(Doc: IXMLDocument): IXMLMENUType;
begin
Result := Doc.GetDocBinding('MENU', TXMLMENUType) as IXMLMENUType;
end;
function LoadMENU(const FileName: WideString): IXMLMENUType;
begin
Result := LoadXMLDocument(FileName).GetDocBinding('MENU', TXMLMENUType) as
IXMLMENUType;
end;
function NewMENU: IXMLMENUType;
begin
Result := NewXMLDocument.GetDocBinding('MENU', TXMLMENUType) as IXMLMENUType;
end;
{ TXMLMENUType }
procedure TXMLMENUType.AfterConstruction;
begin
RegisterChildNode('ENTRY', TXMLENTRYType);
inherited;
end;
function TXMLMENUType.Get_ENTRY: IXMLENTRYType;
begin
Result := ChildNodes['ENTRY'] as IXMLENTRYType;
end;
{ TXMLENTRYType }
procedure TXMLENTRYType.AfterConstruction;
begin
RegisterChildNode('ENTRY', TXMLENTRYType);
inherited;
end;
function TXMLENTRYType.Get_CAPTION: WideString;
begin
Result := ChildNodes['CAPTION'].Text;
end;
procedure TXMLENTRYType.Set_CAPTION(Value: WideString);
begin
ChildNodes['CAPTION'].NodeValue := Value;
end;
function TXMLENTRYType.Get_VISIBLE: Integer;
begin
Result := ChildNodes['VISIBLE'].NodeValue;
end;
procedure TXMLENTRYType.Set_VISIBLE(Value: Integer);
begin
ChildNodes['VISIBLE'].NodeValue := Value;
end;
function TXMLENTRYType.Get_ID: Integer;
begin
Result := ChildNodes['ID'].NodeValue;
end;
procedure TXMLENTRYType.Set_ID(Value: Integer);
begin
ChildNodes['ID'].NodeValue := Value;
end;
function TXMLENTRYType.Get_ENTRY: IXMLENTRYType;
begin
Result := ChildNodes['ENTRY'] as IXMLENTRYType;
end;
end.
Finally, I'll show you an example for the XML-File.
The Procedure Name is assigned to the ID which then will be called.
<?xml version="1.0" encoding="ISO-8859-1"?>
<MENU>
<ENTRY CAPTION="Datei" VISIBLE="1" ID="None" SHORTCUT="None" CHECKED="0">
<ENTRY CAPTION="Beenden" VISIBLE="1" ID="CloseProgram" SHORTCUT="Strg+X" CHECKED="0"></ENTRY>
</ENTRY>
<ENTRY CAPTION="Anzeige" VISIBLE="1" ID="None" SHORTCUT="None" CHECKED="0">
<ENTRY CAPTION="Toolbar" VISIBLE="1" ID="ShowToolbar" SHORTCUT="None" CHECKED="1"></ENTRY>
<ENTRY CAPTION="Seperator" VISIBLE="1"></ENTRY>
<ENTRY CAPTION="Optionen" VISIBLE="1" ID="ShowOptionen" SHORTCUT="Strg+O" CHECKED="0"></ENTRY>
</ENTRY>
<ENTRY CAPTION="News" VISIBLE="1" ID="None" SHORTCUT="None" CHECKED="0">
<ENTRY CAPTION="Refresh" VISIBLE="1" ID="RefreshAll" SHORTCUT="F5" CHECKED="0"></ENTRY>
<ENTRY CAPTION="Seperator" VISIBLE="1"></ENTRY>
<ENTRY CAPTION="Administration" VISIBLE="1" ID="None" SHORTCUT="None" CHECKED="0">
<ENTRY CAPTION="neue Nachricht hinzuf�gen" VISIBLE="1" ID="NewMarkedNews" SHORTCUT="Strg+N" CHECKED="0"></ENTRY>
<ENTRY CAPTION="markierte Nachricht bearbeiten" VISIBLE="1" ID="EditMarkedNews" SHORTCUT="Strg+E" CHECKED="0"></ENTRY>
<ENTRY CAPTION="markierte Nachricht l�schen" VISIBLE="1" ID="DeleteMarkedNews" SHORTCUT="None" CHECKED="0"></ENTRY>
<ENTRY CAPTION="Seperator" VISIBLE="1"></ENTRY>
<ENTRY CAPTION="Film hinzuf�gen" VISIBLE="1" ID="AddMPG" SHORTCUT="None" CHECKED="0"></ENTRY>
<ENTRY CAPTION="markierten Film l�schen" VISIBLE="1" ID="DeleteMPG" SHORTCUT="None" CHECKED="0"></ENTRY>
</ENTRY>
</ENTRY>
<ENTRY CAPTION="Hilfe" VISIBLE="1" ID="None" SHORTCUT="None" CHECKED="0">
<ENTRY CAPTION="LogView" VISIBLE="1" ID="ShowLog" SHORTCUT="Strg+L" CHECKED="0"></ENTRY>
<ENTRY CAPTION="eMail schreiben" VISIBLE="1" ID="WriteEMail" SHORTCUT="None" CHECKED="0"></ENTRY>
<ENTRY CAPTION="Seperator" VISIBLE="1"></ENTRY>
<ENTRY CAPTION="�ber" VISIBLE="1" ID="About" SHORTCUT="None" CHECKED="0"></ENTRY>
</ENTRY>
</MENU>
The first Node should be <MENU> ... </MENU>
There you can the use <ENTRY ...></ENTRY>. When you write another entry before the Entry-Endtag, this will be a submenu item.
The parameters for ENTRY are:
CAPTION - this is the string which is displayed in the Menu. If this string is "Seperator", a Seperator will be insert
VISIBLE - when zero, the MenuItem will be generated but not displayed
ID - this is None for nothing or the Name of the procedure to call when the Item is clicked (BUT BE CAREFUL: THIS PROCEDURE MUST BE PUBLIC!)
SHORTCUT - None for nothing or e.g. Ctrl+X (read the Delphi-Help for 'TextToShortCut' to understand this)
CHECKED - when not zero, the MenuItem will be checked
2008. december 25., csütörtök
How to track a TEdit at an OnExit event
Problem/Question/Abstract:
I'm getting a trouble with an special event inside a OnExit event. I need to know at OnExit of a TEdit Control, when the user clicks a button such as the Cancel button. The user may exit the TEdit just with a correct data or when the Cancel Button was clicked. How can I track this?
Answer:
Assuming that "BlockExit" is a global variable or field of your form:
procedure TForm1.FormCreate(Sender: TObject);
begin
BlockExit := false;
end;
procedure TForm1.Edit1Exit(Sender: TObject);
begin
BlockExit := (Edit1.Text <> 'OK');
end;
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
if BlockExit then
begin
Beep;
Beep;
MessageDlg('Wrong data in Edit1', mtError, [mbOK], -1);
Edit1.SetFocus;
CanClose := false;
end
else
CanClose := true;
end;
procedure TForm1.btnCancelClick(Sender: TObject);
begin
BlockExit := false;
Close;
end;
procedure TForm1.btnOKClick(Sender: TObject);
begin
Close;
end;
2008. december 24., szerda
How to determine the caret position in a TMemo
Problem/Question/Abstract:
How to determine the caret position in a TMemo
Answer:
You can use the Windows API messages EM_LINEFROMCHAR and EM_LINEINDEX to determine the current line and offset within that line (starting from SelStart).
var
LineNum: longint;
CharsBeforeLine: longint;
begin
LineNum := SendMessage(Memo1.Handle, EM_LINEFROMCHAR, Memo1.SelStart, 0);
CharsBeforeLine := SendMessage(Memo1.Handle, EM_LINEINDEX, LineNum, 0);
Label1.Caption := ' Line ' + IntToStr(LineNum + 1);
Label2.Caption := ' Position ' + IntToStr((Memo1.SelStart - CharsBeforeLine) + 1);
end;
2008. december 23., kedd
Select first five records from table
Problem/Question/Abstract:
How to select first five records from table
Answer:
I know that SQL looks like easy thing but sometimes we face some problems, try this solvation.
SELECT TOP 5 Field1, Field2, ....FROM Table1...
2008. december 22., hétfő
Check if Internet Explorer is running and get the source of the page that is displayed
Problem/Question/Abstract:
I need to identify if IE is already running on the machine, and if so, read the source of the HTML being displayed
Answer:
{ ... }
uses
SHDocVw, MSHtml;
{ ...}
var
ShellWindows: IShellWindows;
Browser: IWebBrowser2;
i: integer;
Doc: IHTMLDocument2;
{ ...}
{ Use ShellWindows to get the active browser window }
ShellWindows := CoShellWindows.Create;
for i := 0 to ShellWindows.Count - 1 do
begin
if Supports(ShellWindows.Item(i), IWebBrowser2, Browser) then
begin
Doc := Browser.Document as IHTMLDocument2;
Memo1.Clear;
Memo1.Lines.Add(Doc.body.innerText);
{ ...}
2008. december 21., vasárnap
Copy directory structures
Problem/Question/Abstract:
How to copy directory structures
Answer:
Solve 1:
The most appropriate way would be with the SHFileOperation API call. This is a snippet of a demo I have written for this. You should be able to see the functionality.
procedure TForm1.SHFileOperationCopy(sFrom, sTo, STitle: string);
var
lpFileOp: TSHFILEOPSTRUCT;
op, flag: Integer;
begin
case rgOP.ItemIndex of
0: op := FO_COPY;
1: op := FO_DELETE;
2: op := FO_MOVE;
3: op := FO_RENAME;
end;
flag := 0;
if AllowUndo.Checked then
flag := flag or FOF_ALLOWUNDO;
if ConfirmMouse.checked then
flag := flag or FOF_CONFIRMMOUSE;
if FilesOnly.checked then
flag := flag or FOF_FILESONLY;
if NoConfirm.Checked then
flag := flag or FOF_NOCONFIRMATION;
if NoConfirmMkdir.Checked then
flag := flag or FOF_NOCONFIRMMKDIR;
if RenameColl.Checked then
flag := flag or FOF_RENAMEONCOLLISION;
if Silent.Checked then
flag := flag or FOF_SILENT;
if SimpleProgress.Checked then
flag := flag or FOF_SIMPLEPROGRESS;
with lpFileOp do
begin
Wnd := Form1.Handle;
wFunc := op;
pFrom := pChar(sFrom);
pTo := pChar(sTo);
fFlags := Flag;
hNameMappings := nil;
lpszProgressTitle := pChar(sTitle);
end;
if (SHFileOperation(lpFileOp) <> 0) then
ShowMessage('Error processing request.');
if lpFileOp.fAnyOperationsAborted then
ShowMessage('Operation Aborted');
end;
Solve 2:
Here is desired function - with recursion:
function copyfilesindir(const source, dest, mask: string; subdirs: Boolean): Boolean;
var
ts: TSearchRec;
function filewithpath(const dir, file: string): string;
begin
if (length(dir) > 0) and (copy(dir, length(dir), 1) <> '\') then
result := dir + '\' + file
else
result := dir + file;
end;
begin
result := directoryexists(dest);
if not result then
result := createdir(dest);
if not result then
exit;
if findfirst(filewithpath(source, mask), faanyfile, ts) = 0 then
repeat
if not ((ts.name = '.') or (ts.name = '..')) then
begin
if ts.Attr and fadirectory > 0 then
begin
if subdirs then
result := copyfilesindir(filewithpath(source, ts.name),
filewithpath(dest, ts.name), mask, subdirs);
end
else
result := copyfile(pchar(filewithpath(source, ts.name)),
pchar(filewithpath(dest, ts.name)), false);
if not result then
break;
end;
until
findnext(ts) <> 0;
findclose(ts);
end;
2008. december 20., szombat
How to convert UNIX time to TDateTime and vice versa
Problem/Question/Abstract:
There is a date/ time format that I'm trying to translate, but I can't find anything that could match. This example is 2000-12-20 around 22:15. Integer: 977347109, Hex: 3A412225. Anyone know how to translate it?
Answer:
The value is a Unix Time, defined as seconds since 1970-01-01T00:00:00,0Z. Important is the Letter Z, you live in Sweden, in consequence you must add 1 hour for StandardDate and 2 hours for DaylightDate to the date. The infos you can get with GetTimeZoneInformation. But you must determine, which Bias (Standard or Daylight) is valid for the date (in this case -60). You can convert the date value with the function below.
The Date for 977347109 is 2000-12-20T22:18:29+01:00.
const
UnixDateDelta = 25569; { 1970-01-01T00:00:00,0 }
SecPerMin = 60;
SecPerHour = SecPerMin * 60;
SecPerDay = SecPerHour * 24;
MinDayFraction = 1 / (24 * 60);
{Convert Unix time to TDatetime}
function UnixTimeToDateTime(AUnixTime: DWord; ABias: Integer): TDateTime;
begin
Result := UnixDateDelta + (AUnixTime div SecPerDay) { Days }
+ ((AUnixTime mod SecPerDay) / SecPerDay) { Seconds }
- ABias * MinDayFraction { Bias to UTC in minutes };
end;
{Convert Unix time to String with locale settings}
function UnixTimeToStr(AUnixTime: DWord; ABias: Integer): string;
begin
Result := FormatDateTime('ddddd hh:nn:ss', UnixTimeToDateTime(AUnixTime, ABias));
end;
{Convert TDateTime to Unix time}
function DateTimeToUnixTime(ADateTime: TDateTime; ABias: Integer): DWord;
begin
Result := Trunc((ADateTime - UnixDateDelta) * SecPerDay) + ABias * SecPerMin;
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
Label1.Caption := UnixTimeToStr(977347109, -60);
end;
2008. december 19., péntek
Create without worrying of destroy a component
Problem/Question/Abstract:
How can I prevent resources leak.
Answer:
Make use of Interface characteristic where when the reference goes out of scope it will free itself.
type
IAutoClean = interface
['{61D9CBA6-B1CE-4297-9319-66CC86CE6922}']
end;
TAutoClean = class(TInterfacedObject, IAutoClean)
private
FObj: TObject;
public
constructor Create(AObj: TObject);
destructor Destroy; override;
end;
implementation
constructor TAutoClean.Create(AObj: TObject);
begin
FObj := AObj;
end;
destructor TAutoClean.Destroy;
begin
FreeAndNil(FObj);
inherited;
end;
Application....
procedure TForm1.Button1Click(Sender: TObject);
var
a: IAutoClean;
//must declare as local variable, so when this procedure finished, it's out of scope
o: TOpenDialog; //any component
begin
o := TOpenDialog.Create(self);
a := TAutoClean.Create(o);
if o.Execute then
ShowMessage(o.FileName);
end;
2008. december 18., csütörtök
Achieve Record locking with MSSQL 7 or later
Problem/Question/Abstract:
How would you like to be able to determine if a record has been locked in MSSQL Server and not get that annoying 'Record has been changed by another User' when you finally try to post your changes? There is an easy approach (quite easy) but it has to be implemented programatically.
Answer:
For every record you want to lock to this:
Create a global temporary table that is named after the table on which the record is, together with the Unique Id of the table. For example, if you have a table named customers, with a unique id field called Uid and you want to lock the record with uid=14, create the table using this query:
Create table ##Customers14 (id int null)
When you want to unlock the record just drop that table:
Drop table ##Customers14
Now lets say that another user wants to use the same record. His client program tries to create the same global temporary table, but fails with an exception, because no two global temporary tables can have the same name. Trap the exception in a try-except clause and you are home free.
TIPS.
Use this only for SQLServer 7 and above. SQL 6.5 and below have a terrible way of handling Temprorary tables that gives a lot of overhead.
You can create any kind of collumn in your temporary table, so you can have info like what time the record was locked and by what user.
Never use this approach if there is a chance someone will forget his computer open on a record for hours, and that computer is located lets say 100 miles from the server!!!
If the connection is lost by lets say an application error, the table is automatically droped by the SQL Server.
If the computer shutsdown by a power failure, the SQL Server waits for about 15 minutes and then drops the temporary table, or if the computer logs on again the table is droped automatically.
If you don't want to have to handle an exception you can also check for the existance of the Temporary table in the Master database.
2008. december 17., szerda
How to copy multiple files into one
Problem/Question/Abstract:
Remember DOS? We can combine multiple ASCII files to one by using the copy command like: copy file1 + file2 + file3 file4 .That makes file4 to become the sum of file1, file2 and file3. Does the ShFileOperation API supports this feature or is there any other API support this?
Answer:
Solve 1:
procedure TForm1.Button1Click(Sender: TObject);
var
Stream1, Stream2: TFileStream;
begin
Stream1 := TFileStream.Create('c:\file4', fmCreate or fmShareExclusive);
try
{ first file }
Stream2 := TFileStream.Create('c:\file1', fmOpenRead or fmShareDenyNone);
try
Stream1.CopyFrom(Stream2, Stream2.Size);
finally
Stream2.Free;
end;
{ next file }
Stream2 := TFileStream.Create('c:\file2', fmOpenRead or fmShareDenyNone);
try
Stream1.CopyFrom(Stream2, Stream2.Size);
finally
Stream2.Free;
end;
{ and so on }
finally
Stream1.Free;
end;
end;
Solve 2:
function AppendFiles(Files: TStrings; const DestFile: string): integer;
var
srcFS, destFS: TFileStream;
i: integer;
F: string;
begin
result := 0;
if (Files.Count > 0) and (DestFile <> '') then
begin
destFS := TFileStream.Create(DestFile, fmCreate or fmShareExclusive);
try
i := 0;
while i < Files.Count do
begin
F := Files(i);
Inc(i);
if (CompareText(F, DestFile) <> 0) and (F <> '') then
begin
srcFS := TFileStream.Create(F, fmOpenRead or fmShareDenyWrite);
try
if destFS.CopyFrom(srcFS, 0) = srcFS.Size then
Inc(result);
finally
srcFS.Free;
end;
end
else
begin
{ error }
end;
end;
finally
destFS.Free;
end;
end;
end;
2008. december 16., kedd
Responding to Windows Messages
Problem/Question/Abstract:
It shows how to act on response to windows messages. Further information can be foun in the Online Help seeking "message handling"
Answer:
It is as easy as writing a method that complies with
Getting a TMessage Parameter or a specific Message Parameter (such as TWMMouse that makes it easier to get the message's parameters)
Putting the reserved word message followed by the windows message to which you want to react (such as WM_MOUSEMOVE)
Then write your method and VOILA!
Here is the code:
unit messageUnit;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
TForm1 = class(TForm)
private
procedure CatchMouseMove(var winMessage: TWMMouse); message wm_mousemove;
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
procedure TForm1.CatchMouseMove(var winMessage: TWMMouse);
{*
WM_MOUSEMOVE
fwKeys = wParam; // key flags
xPos = LOWORD(lParam); // horizontal position of cursor
yPos = HIWORD(lParam); // vertical position of cursor
*}
begin
self.Color := TColor(winmessage.XPos)
end;
{$R *.DFM}
end.
2008. december 15., hétfő
How to get the virtual series number of an audio CD
Problem/Question/Abstract:
How to get the virtual series number of an audio CD
Answer:
Answer 1:
Windows creates a "Virtual Series Number" for Audio CDs. You can use the following code to get the VSN of an audio CD:
type
TNumbBase = 1..36;
function NumbToStr(Numb: LongInt; Base: TNumbBase): string;
const
NumbDigits = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ';
begin
Result := EmptyStr;
while Numb > 0 do
begin
Result := NumbDigits[(Numb mod Base) + 1] + Result;
Numb := Numb div Base;
end;
if Result = EmptyStr then
Result := '0';
end;
function GetCDID(Drive: string): string;
var
Serial: DWord;
T: Cardinal;
begin
if GetVolumeInformation(PChar(Drive), nil, 0, @Serial, T, T, nil, 0) then
Result := NumbToStr(Serial, 16)
else
Result := EmptyStr;
end;
Drive should be the name of the root directory of your CD drive. Use it like
ShowMessage(GetCDID('I:\'));
or
ShowMessage(GetCDID('\\Computer2\\CDDrive\'));
Solve 2:
You have to use the API-call GetVolumeInformation. But first, you have to implement it correctly:
function GetVolumeInformation(lpRootPathName: PAnsiChar; lpVolumeNameBuffer: PAnsiChar; nVolumeNameSize: DWORD; var lpVolumeSerialNumber, lpMaximumComponentLength,
lpFileSystemFlags: DWORD; lpFileSystemNameBuffer: PAnsiChar; nFileSystemNameSize: DWORD): bool; stdcall; external kernel32 name 'GetVolumeInformationA';
In your application:
function GetCDId: string;
var
root: string;
VolumeNameBuffer, FileSystemNameBuffer: PChar;
VolumeSerialNumber, FileSystemFlags, MaximumComponentLength: LongInt;
function Int2Hex(number: LongInt): string;
var
i: LongInt;
s: string;
begin
s := '';
i := 0;
while number > 0 do
begin
i := number mod 16;
case i of
0..9: s := IntToStr(i) + s;
10: s := 'A' + s;
11: s := 'B' + s;
12: s := 'C' + s;
13: s := 'D' + s;
14: s := 'E' + s;
15: s := 'F' + s;
end;
number := number - i * 16;
end;
Result := s;
end;
begin
root := 'x:\'; {where X is the drive letter of your CD drive}
VolumeNameBuffer := StrAlloc(256);
FileSystemNameBuffer := StrAlloc(256);
if GetVolumeInformation(PChar(root), VolumeNameBuffer, 255, VolumeSerialNumber,
MaximumComponentLength, FileSystemFlags, FileSystemNameBuffer, 255) then
Result := Int2Hex(VolumeSerialNumber);
else
Result := '';
end;
2008. december 14., vasárnap
How to preserve the default popup menu for a component
Problem/Question/Abstract:
Is there a way to keep the default popup menus for e.g. TMemo, TEdit if there is an explicit popup menu for the parent control?
Answer:
You have to trap the WM_CONTEXTMENU message on the level of the parent. Assuming the parent is the form you would add a message handler to the form:
{ ... }
private
procedure WMContextMenu(var Message: TWMContextMenu); message WM_CONTEXTMENU;
{ ... }
and implement it like this:
procedure TForm1.WMContextMenu(var Message: TWMContextMenu);
var
wnd: HWND;
ctrl: TWinControl;
begin
if message.XPos > 0 then
begin
wnd := WindowFromPoint(Mouse.CursorPos);
if wnd <> handle then
begin
ctrl := FindControl(wnd);
if Assigned(ctrl) and (ctrl is TCustomEdit) then
Exit;
end;
end
else if ActiveControl is TCustomEdit then
Exit;
inherited;
end;
Doing the same if the parent with the menu is a panel or tabsheet or such is a bit more difficult, you have to subclass it via WindowProc, or make a descendent class to be able to trap the message.
2008. december 13., szombat
Fill In Combo Box (Component)
Problem/Question/Abstract:
In the following article I am going to give you a simple, enhanced combo box, that fills in the text area with possible options from the items list. Simple, but useful.
Note: Delphi 6 does this by default, already
Answer:
INTRODUCTION
In this article I show you how to enhance an already existing component, easily. Because of the nature of this article you should be familar with Delphi already, however, no deep knowledge is needed.
Developing a component is, thanks to Delphi, a rather simple task. You do not have to start from scratch everytime you want to enhance something, already existing. You can simple create a new class and derive it from the one you want to enhance.
GETTING STARTED
In our case we are going to enhance the TComboBox component, directly. We could choose the TCustomComboBox, however, they have different published properties from one Delphi version to another, therefore that want make much sense.
Delphi makes the simple task of creating a new component even more simple by offering a small wizard. From the Menu File | New... select the Component right from the first tab "New."
A simple wizard will show. Fill in accordingly:
Ancestor Type: TComboBox
Class Name: TFillComboBox
Palette Page: Samples (or any you want, i took "Standard")
Unit File Name: Select a folder and file to save your work
Press OK, we will install it at a later time.
The wizard will create a basic component for you, inlcuding the installation routine shown below.
procedure Register;
begin
RegisterComponents('Standard', [TFillComboBox]);
end;
This routine will be called by Delphi when you select install on your component package including this file. The first parameter of RegisterComponents names the palette page, where the components are installed, the second is an array of the components to be installed.
ADDING A NEW PROPERTY
To our new component we add a new property, called AutomaticFillin. When set to True we will search for a item matching the user input and add the remainder to the text box, otherwise we wont.
Therefore we have to declare one private variable that will save the value of the switch. By puting a property into the published part of the class declaration we allow the Delphi developer to change its value in the Object Inspector.
private
FAutomaticFillin: Boolean;
procedure SetAutomaticFillin(const Value: Boolean);
published
property AutomaticFillin: Boolean
read FAutomaticFillin
write SetAutomaticFillin
default True;
procedure TFillComboBox.SetAutomaticFillin(const Value: Boolean);
begin
FAutomaticFillin := Value;
end;
THE PROCESSING OF THE USER CHANGES
In order to become notified when the user changes the text field, we have to override the default message handler for the combo box.
protected
procedure ComboWndProc(
var Message: TMessage; ComboWnd: HWnd; ComboProc: Pointer
); override;
In our implementation, we check first whether the special handling is turned on. If it is turned on, we will get the current text, the user has typed, and then search for it in the items list. If we have a match, we will replace the text with the matching item and select the part added by our function.
THE CODE
If your have followed the directions from the "GETTING STARTED" section, simply replace the unit code with the following code and save your file.
unit FillComboBox;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TFillComboBox = class(TComboBox)
private
FAutomaticFillin: Boolean;
procedure SetAutomaticFillin(const Value: Boolean);
protected
procedure ComboWndProc(
var Message: TMessage; ComboWnd: HWnd; ComboProc: Pointer
); override;
public
published
constructor Create(AOwner: TComponent); override;
property AutomaticFillin: Boolean
read FAutomaticFillin
write SetAutomaticFillin
default True;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Standard', [TFillComboBox]);
end;
{ TFillComboBox }
procedure TFillComboBox.ComboWndProc(
var Message: TMessage; ComboWnd: HWnd; ComboProc: Pointer
);
var
I: Integer;
CurrentText: string;
begin
inherited ComboWndProc(Message, ComboWnd, ComboProc);
// skip processing, if turned off
if not FAutomaticFillin then
Exit;
// first check whether the backspace key was pressed, we do not fill in
// in such case!
if Message.Msg = WM_CHAR then
begin
// all characters from 32 (Space) through 127 (Upper ANSI) are matched
if TWMChar(Message).CharCode in [$20..$7F] then
begin
// fill in the rest of the text
// save the current text, the user has typed
CurrentText := Text;
// get the first string, matching the text partially
I := SendMessage(Handle, CB_FINDSTRING, -1, LongInt(PChar(CurrentText)));
if I >= 0 then
begin
// match found!
// load matching text, I is the position of the matching string
Text := Items.Strings[I];
// select the text beyond the text typed
SelStart := Length(CurrentText);
SelLength := Length(Text) - Length(CurrentText);
end;
end;
end;
end;
constructor TFillComboBox.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FAutomaticFillin := True;
end;
procedure TFillComboBox.SetAutomaticFillin(const Value: Boolean);
begin
FAutomaticFillin := Value;
end;
end.
INSTALLING THE COMPONENT
The last step is to install the component you have just created. Go to the menu and select Component | Install Component.... Select your FillComboBox.pas in the "Unit file name" field and press "OK."
That's all. Now you can create a new application and use the component whenever you feel like.
2008. december 12., péntek
Simple Animated Bit- and SpeedButtons
Problem/Question/Abstract:
How do I create buttons with animated bitmaps?
Answer:
One thing that's not well-known (or maybe more accurately, not too obvious) is that the glyphs used for bit buttons are actually multi-framed bitmaps arranged horizontally from left to right. The rule is that you can have up to four frames in the bitmap, with each frame representing a particular button state. What you normally see is the first frame of the bitmap displayed on the button; this glyph represents the "up" state of the button. The other frames represent other states. Here's their layout:
Figure 1 - The arrangement of a multi-framed bitmap�����������������������
Table 1 describes what each bitmap frame represents:
Frame
Button State
Description
1
Up
This frame appears when the button is unselect. If no other frames exist in the bitmap, Delphi uses this image for all other images.
2
Disabled
This frame is typically a dimmed bitmap indicating that the button is disabled and can't be selected.
3
Down
This frame appears when a button is clicked. Frame 1 then reappears when the user releases the mouse button.
4
?
I have absolutely no idea what this particular frame is used for, and the online help doesn't offer any explanations whatsoever.
Table 1 - Description of multi-image bitmap frames and when they're used
So what's the point to all this? Armed with the information I just provided, you can create simple, two-frame animations that will show a different picture based upon the up or down state of a button. Here's sample:
(enlarged view)
The bitmap above shows a bitmap that I use for a product that I created that runs on a CD. Frame one (the leftmost bitmap) is displayed with the button is in its up position. Frame two displayes when the button is disabled, and frame three displays when the button is pressed. Notice in frame three how the logo and CD have "moved" down and to the right, and the shadow disappears. The net effect achieved here is that the logo appears to move down as the button is pressed.
So how do you construct the bitmaps? I've found that the image editor works just great. Just create a new bitmap by selecting File|New|Bitmap from the main menu. When the image properties dialog box appears on the screen, set the dimensions like so:
Give the image a height value first (Borland's standard buttons are 16-pixels high - I like mine to be more than that)
Then, set the width as 3X the height. So for instance, if you set a height of 16 pixels, your corresponding width would be 48 pixels. Pretty simple
Then all you have to do is use your imagination to create your bitmaps. Have fun!
2008. december 11., csütörtök
Disable the IDE splash screen
Problem/Question/Abstract:
Disable the IDE splash screen
Answer:
Start Delphi with the parameter -ns ('no splash'):
Delphi32.EXE -ns
Also works with C++ Builder.
2008. december 10., szerda
Change the color of a specific subitem in a TListView
Problem/Question/Abstract:
How to change the color of a specific subitem in a TListView.
Answer:
To change the color of a specific SubItem in a TListView all you have to do is to put some code in the OnCustomDrawSubItems event of the TListView. Your probably thinking of putting the OwnerDraw property of the ListView to True...
don't do this, yes I know normally it should be set to True, but in case of a TListView this is not the case...a bug somewhere in Delphi. Unlike the OnCustomDraw the OnCustomDrawSubItems event is sent no matter the state of the OwnerDraw property.
The OnCustomDrawSubItems is fired prior to drawing the SubItem on the TListView.
To alter the default drawing process at other stages (e.g. after the SubItem is drawn,... .), you must use the OnAdvancedCustomDrawSubItem event.
You can put code here to change the appeance of the SubItems, the ViewStyle of the TListView must be set to vsReport in order for this to function correctly.
Then you can use the canvas of the ListView as a drawing surface.
Let's say you want the font color of a SubItem to turn red whenever it's below is negative then put the following code in the OnCustomDrawSubItems event:
procedure TForm1.ListViewCustomDrawSubItem(
Sender: TCustomListView; Item: TListItem; SubItem: Integer;
State: TCustomDrawState; var DefaultDraw: Boolean);
begin
//Check if the value of the third column is negative,
//if so change it's font color to Red (clRed).
if SubItem = 3 then
try
if StrToInt(Item.SubItems.Strings[SubItem - 1]) < 0 then
Sender.Canvas.Font.Color := clRed;
except
on EConvertError do
next;
end;
end;
Used Parameters:
Sender : Specifies the ListView that owns the SubItems
Item : Is the current Item being drawn
SubItem: Index of the SubItem of the ListItem (Item) in its SubItems property
State : Indicates various attributes that affect the way the SubItem is drawn
DefaultDraw: Set it to False to prevent the ListView from adding the SubItem's text after the event handler exits.
Tested with Delphi 6 Professional on Windows 2000 Professional.
2008. december 9., kedd
Create a TTreeView with a three state checkbox
Problem/Question/Abstract:
I tried many combinations of GW_STYLE with TVS_CHECKBOXES or BS_AUTO3STATE and I can't get a three state checkbox. All I have is a plain 2 state box. Any ideas?
Answer:
Actually, you can have any number of checkbox states you like. The number of the images in the state image list determines the number of the states. By default, the image list has two bitmaps: checked and unchecked. But you are always able to add yours for a third (forth ...) state. The code below shows a TTreeView with checkboxes and a third state. I've tested it on D4 and it seemed to work alright. You can set the third state to the tree node by setting 3 to the StateIndex property in the form's OnCreate event or in any other suitable place:
MyTreeView1.Items[0].StateIndex := 3;
{ ... }
type
TMyTreeView = class(TTreeView)
protected
procedure CNNotify(var Message: TWMNotify); message CN_NOTIFY;
procedure CreateParams(var Params: TCreateParams); override;
public
procedure AddNewStateImage;
end;
{ ... }
procedure TMyTreeView.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
Params.Style := Params.Style or TVS_CHECKBOXES;
end;
procedure TMyTreeView.CNNotify(var Message: TWMNotify);
begin
with Message do
if NMHdr^.code = NM_CUSTOMDRAW then
AddNewStateImage;
inherited;
end;
procedure TMyTreeView.AddNewStateImage;
var
XImageList: TImageList;
XImage: HIMAGELIST;
XBitMap: TBitMap;
i: integer;
begin
XImage := TreeView_GetImageList(Handle, TVSIL_STATE);
if (XImage <> 0) and (ImageList_GetImageCount(XImage) < 4) then
begin
XImageList := TImageList.Create(Self);
XBitMap := TBitMap.Create;
try
XImageList.ShareImages := true;
XImageList.Handle := XImage;
XBitMap.Width := XImageList.Width;
XBitMap.Height := XImageList.Height;
XImageList.Draw(XBitMap.Canvas, 0, 0, 2, false);
XImageList.Add(XBitMap, nil);
finally
XImageList.Free;
XBitMap.Free;
end;
for i := 0 to Items.Count - 1 do
if Items[i].StateIndex > 0 then
Items[i].StateIndex := Items[i].StateIndex;
end;
end;
2008. december 8., hétfő
How to create a program that deletes itself after running
Problem/Question/Abstract:
I would like to run a program that finishes by deleting itself (similar to the DOS TSR programs). Is this possible?
Answer:
Solve 1:
One technique is to use a batch file. It works on all versions of Windows. An example:
{ ... }
s := 'SelfDelete.bat';
s := ExtractFilePath(ParamStr(0)) + s;
assign(f, s);
rewrite(f);
writeln(f, ':f');
writeln(f, 'del "' + ParanStr(0));
writeln(f, 'if EXIST "' + ParamStr(0) + '" goto f');
writeln(f, 'del "' + s);
closefile(f);
WinExec(PChar(s), SW_HIDE)
Solve 2:
This simple method uses a Windows Registry entry, which in turn, makes Command.com to do the job for us, whenever the next Windows restart occurs. Add the following code to a procedure of your choice:
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls,
Registry;
type
TForm1 = class(TForm)
Button1: TButton;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.Button1Click(Sender: TObject);
var
APath: array[0..255] of char;
begin
{Command.com does not support long paths, so convert to short}
if GetShortPathName(PChar(ParamStr(0)), APath, SizeOf(APath) - 1) <> 0 then
begin
{Work with TRegistry}
with TRegistry.Create do
try
{Set Root Key}
RootKey := HKEY_LOCAL_MACHINE;
{Open Key, creating key if it does not exist}
if OpenKey('\Software\Microsoft\Windows\CurrentVersion\RunOnce', True) then
begin
{Add our String Value to the Key}
WriteString('MyApp', 'command.com /c del ' + APath);
{Close the Key}
CloseKey;
end;
finally
{Free TRegistry}
free;
end;
end;
end;
This example makes use of ParamStr(0) to meet the expectations that the title of this article has elicited: By pointing to the path + filename of the application that executes this procedure, the program will in fact bring about the removal of itself. Windows NT/2000 Note: Users running programs that utilize this code, must have the right to modify the HKEY_LOCAL_MACHINE section of the Windows Registry.
Solve 3:
Try this (not tested under WinXP, but works under Win95, Win98, WinNT 4.0 and Win2000):
procedure DeleteExeAndDir;
var
hModule: THandle;
szModuleName, szDirName: array[0..MAX_PATH] of Char;
hKrnl32: THandle;
pExitProcess, pDeleteFile, pFreeLibrary, pUnmapViewOfFile, pRemoveDir: pointer;
ExitCode: UINT;
var
r: integer;
begin
hModule := GetModuleHandle(nil);
GetModuleFileName(hModule, szModuleName, sizeof(szModuleName));
StrPCopy(szDirName, ExtractFileDir(szModuleName));
hKrnl32 := 'kernel32');
pExitProcess := GetProcAddress(hKrnl32, 'ExitProcess');
pDeleteFile := GetProcAddress(hKrnl32, 'DeleteFileA');
pFreeLibrary := GetProcAddress(hKrnl32, 'FreeLibrary');
pUnmapViewOfFile := GetProcAddress(hKrnl32, 'UnmapViewOfFile');
pRemoveDir := GetProcAddress(hKrnl32, 'RemoveDirectoryA');
ExitCode := system.ExitCode;
SetCurrentDirectory(pchar(ExtractFileDir(szDirName)));
if ($80000000 and GetVersion()) <> 0 then
{Win95, 98, Me}
asm
lea eax, szModuleName
lea ecx, szDirName
push ExitCode
push 0
push ecx
push pExitProcess
push eax
push pRemoveDir
push hModule
push pDeleteFile
push pFreeLibrary
ret
end
else
begin
for r := 1 to 100 do
begin
CloseHandle(r shl 2);
end;
{CloseHandle(THANDLE(4));}
asm
lea eax, szModuleName
lea ecx, szDirName
push ExitCode
push 0
push ecx
push pExitProcess
push eax
push pRemoveDir
push hModule
push pDeleteFile
push pUnmapViewOfFile
ret
end
end;
end;
Solve 4:
program delself;
uses
windows;
procedure DeleteSelf;
var
module: HMODULE;
buf: array[0..MAX_PATH - 1] of char;
p: ULONG;
hKrnl32: HMODULE;
pExitProcess, pDeleteFile, pFreeLibrary: pointer;
begin
module := GetModuleHandle(nil);
GetModuleFileName(module, buf, sizeof(buf));
CloseHandle(THandle(4));
p := ULONG(module) + 1;
hKrnl32 := GetModuleHandle('kernel32');
pExitProcess := GetProcAddress(hKrnl32, 'ExitProcess');
pDeleteFile := GetProcAddress(hKrnl32, 'DeleteFileA');
pFreeLibrary := GetProcAddress(hKrnl32, 'FreeLibrary');
asm
lea eax, buf
push 0
push 0
push eax
push pExitProcess
push p
push pDeleteFile
push pFreeLibrary
ret
end;
end;
begin
DeleteSelf;
end.
2008. december 7., vasárnap
How to connect a TRadioGroup to a TCheckListBox
Problem/Question/Abstract:
I have a form that contains a radio groupbox with 4 items associated with it and a checklistbox with 4 items as well. In this case the radio box dictates which items in a checklistbox can be selected. For example, if the radio groupbox is set to item 3, that means only the first 3 items in the checklistbox can be selected. Therefore, if the user changes the radiobox setting to the second item, only the first 2 selections in the checklistbox are allowed to be marked and the third item that was previously checked should be cleared.
Answer:
procedure MyProc(RadioBoxSender: TRadioGroup; CheckListBoxSender: TCheckListBox);
var
I: Integer;
begin
with CheckListBoxSender do
for i := 1 to 3 do { no need to do 0}
if i > RadioBoxSender.ItemIndex then
checked[i] := false;
end;
2008. december 6., szombat
How to save items of a TComboBox to an ini file
Problem/Question/Abstract:
How to save items of a TComboBox to an ini file
Answer:
This is one possibility. It will put the items in a seperate section:
procedure TForm1.WriteComboToIni;
var
IniFile: TIniFile;
Cnt: integer;
begin
IniFile := TIniFile.Create('c:\test.ini');
try
with ComboBox1 do
if Items.Count > 0 then
for Cnt := 0 to Items.Count - 1 do
IniFile.WriteString('Section', 'Items' + IntToSTr(Cnt), Items[Cnt]);
finally
IniFile.Free;
end;
end;
2008. december 5., péntek
Outlook Automation - Contactlist
Problem/Question/Abstract:
How use Outlook's Contact list in Applications
Answer:
This is sample how look and change information in Outlook's Contactlist from external application.
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
const
// constants from MSOUTL8.olb
olByValue = 1;
olByReference = 4;
olEmbeddedItem = 5;
olOLE = 6;
olMailItem = 0;
olAppointmentItem = 1;
olContactItem = 2;
olTaskItem = 3;
olJournalItem = 4;
olNoteItem = 5;
olPostItem = 6;
olFolderDeletedItems = 3;
olFolderOutbox = 4;
olFolderSentMail = 5;
olFolderInbox = 6;
olFolderCalendar = 9;
olFolderContacts = 10;
olFolderJournal = 11;
olFolderNotes = 12;
olFolderTasks = 13;
type
TForm1 = class(TForm)
Label1: TLabel;
Label2: TLabel;
mName: TMemo;
mFamily: TMemo;
mFullName: TMemo;
mCompany: TMemo;
mSaveAs: TMemo;
mBody: TMemo;
btnSave: TButton;
sbContacts: TScrollBar;
btnConnect: TButton;
procedure btnConnectClick(Sender: TObject);
procedure mNameChange(Sender: TObject);
procedure sbContactsChange(Sender: TObject);
procedure btnSaveClick(Sender: TObject);
private
{ Private declarations }
IsDirty: boolean;
public
{ Public declarations }
OlApp, Namespace, ContactFolder: OleVariant;
procedure LoadContact(I: Integer; folder: OleVariant);
procedure SaveContact(I: Integer; folder: OleVariant);
end;
var
Form1: TForm1;
implementation
uses ComObj;
{$R *.DFM}
procedure TForm1.btnConnectClick(Sender: TObject);
begin
OlApp := CreateOleObject('Outlook.Application');
Namespace := OlApp.GetNameSpace('MAPI');
ContactFolder := Namespace.GetDefaultFolder(olFolderContacts);
sbContacts.Max := ContactFolder.Items.Count;
sbContacts.Position := 1;
LoadContact(1, ContactFolder);
end;
procedure TForm1.LoadContact(I: Integer; folder: OleVariant);
var
Item: OleVariant;
begin
Caption := 'Rec ' + IntToStr(i) + ' from ' + IntToStr(sbContacts.Max);
Item := folder.Items(I);
mName.Text := Item.FirstName;
mFamily.Text := Item.LastName;
mFullName.Text := Item.FullName;
mCompany.Text := Item.CompanyName;
mSaveAs.Text := Item.FileAs;
mBody.Text := Item.Body;
end;
procedure TForm1.SaveContact(I: Integer; folder: OleVariant);
var
Item: OleVariant;
begin
Item := folder.Items(I);
Item.FirstName := mName.Text;
Item.LastName := mFamily.Text;
Item.FullName := mFullName.Text;
Item.CompanyName := mCompany.Text;
Item.FileAs := mSaveAs.Text;
Item.Body := mBody.Text;
Item.Save;
isDirty := False;
end;
procedure TForm1.mNameChange(Sender: TObject);
begin
IsDirty := True;
end;
procedure TForm1.sbContactsChange(Sender: TObject);
begin
LoadContact(sbContacts.Position, ContactFolder);
end;
procedure TForm1.btnSaveClick(Sender: TObject);
begin
SaveContact(sbContacts.Position, ContactFolder);
end;
end.
2008. december 3., szerda
How to save the font settings of a control to the registry
Problem/Question/Abstract:
How can I save the font settings of a control to registry? Saving name , size, etc. as string/int doesn't seem the best way ... (as far as I remember , I can't even save all font options this way)
Answer:
You can create a little component (needs not be installed on the palette) that allows you to stream a fonts properties to a stream. The stream contents could then be saved to a binary key in the registry.
{ ... }
type
TFontWrapper = class(TComponent)
private
FFont: TFont;
constructor Create(aOwner: TComponent); override;
destructor Destroy; override;
procedure SetFont(value: TFont);
published
property Font: TFont read FFont write SetFont;
end;
{ TFontWrapper }
constructor TFontWrapper.Create(aOwner: TComponent);
begin
inherited;
FFont := TFont.Create;
end;
destructor TFontWrapper.Destroy;
begin
FFont.Free;
inherited;
end;
procedure TFontWrapper.SetFont(value: TFont);
begin
FFont.Assign(value);
end;
{ ms is a field of the form }
procedure TForm1.Button1Click(Sender: TObject);
var
helper: TFontWrapper;
begin
if not Assigned(ms) then
ms := TMemoryStream.Create
else
ms.Clear;
helper := TFontWrapper.Create(nil);
try
helper.font := label1.font;
ms.WriteComponent(helper);
finally
helper.free;
end;
label1.font.size := label1.font.size + 2;
end;
procedure TForm1.Button2Click(Sender: TObject);
var
helper: TFontWrapper;
begin
if not Assigned(ms) then
Exit;
ms.Position := 0;
helper := TFontWrapper.Create(nil);
try
ms.ReadComponent(helper);
label1.font := helper.font;
finally
helper.free;
end;
end;
If reg is a TRegistry instance already with key open a
reg.WriteBinaryData(valuename, ms.Memory^, ms.Size);
would save the streamed data to the registry,
ms.size := reg.GetDatasize(valuename);
reg.ReadBinaryData(valuename, ms.Memory^, ms.Size);
would read it back. Mind the caret!
2008. december 2., kedd
Application Settings (article 1)
Problem/Question/Abstract:
Managing Application Settings
Answer:
Introduction
Almost every program we write these days has a set of application settings, commonly called Program Options, that needs to be managed. Typically, the application needs to be able to save and restore these options to the registry and display them to the user for modification. Most developers simply create a unit to hold these option settings as a set of a global variables, or as a properties of a global application settings object.
The problem with this approach is that a lot of tedious code is required in order to save and load these settings. Additional code is then required to display these settings to the end user and allow the user to modify them as required. For example, you've probably written code like this many times before:
procedure TForm1.LoadSettings;
begin
cbWordWrap.Checked := Settings.WordWrap;
edFontName.Text := Settings.FontName;
end;
procedure TForm1.SaveSettings;
begin
Settings.WordWrap := cbWordWrap.Checked;
Settings.FontName := edFontName.Text;
end;
The purpose of this article is to demonstrate an alternative way to manage application settings by taking advantage of RTTI, Run Time Type Information. In part 1 of this article, we talk about creating a basic application settings object that will automatically save and load itself to and from the registry. In part 2, we will create an object dataset that enables you to connect this application settings object to data aware controls, thereby eliminating the tedious code above.
RTTI 101
The goal of part 1 is to create an application settings object that can save and load itself to and from the registry automatically. For those of you familiar with RTTI, this part may seem to be quite trivial, however for those of you new to RTTI, the very concept of RTTI can seem somewhat magical. Thus the first thing we should do is briefly cover RTTI, what it is and how it works. This will not be an in depth discussion of RTTI, but will hopefully be sufficient for the purpose of this article. Note that the best discussion of RTTI is in Ray Lischners book, Secrets of Delphi 2.
RTTI is a mechanism provided by Delphi that describes the published properties of an object. It provides a means for third party code to be able to interact with objects even though this code has no intimate knowledge of the objects. The object inspector in Delphi is a great example of RTTI in action. Have you ever wondered how the object inspector is able to display all published properties of any component even though it obviously has no intimate knowledge of the component it is displaying. The answer is RTTI. By using RTTI the object inspector is able to list all of the properties of a component and what the current values of those properties are. By again using RTTI, the object inspector is able to allow an end user, the Delphi developer in this case, to change the values of those properties as desired.
RTTI functionality is encapsulated in the VCL unit TypInfo.pas. This unit is not documented, however you can find it in your VCL/Source directory. Starting with Delphi 5, Borland added a significant number of easy access RTTI methods to TypInfo.pas in an effort to make using RTTI easier.
I have include a small RTTI utility unit called GXRTTI.pas with the code of this article. Let's take a look at one of those routines to get an idea of how we can use RTTI.
function GetPropName(Instance: TPersistent; Index: Integer): string;
var
PropList: PPropList;
PropInfo: PPropInfo;
Data: PTypeData;
begin
Result := '';
Data := GetTypeData(Instance.Classinfo);
GetMem(PropList, Data^.PropCount * Sizeof(PPropInfo));
try
GetPropInfos(Instance.ClassInfo, PropList);
PropInfo := PropList^[Index];
Result := PropInfo^.Name;
finally
FreeMem(PropList, Data^.PropCount * Sizeof(PPropInfo));
end;
end;
The function above returns a property name for a given object at a given index in the list of published properties for that object. For example, Name might be the first property of TListBox. Thus calling GetPropName(ListBox1,0) would return the string "Name".
This function works by first getting the type data for the given instance using the GetTypeData function in TypInfo.pas. Once we have the type data, we can then retrieve a list of properties for this class. Note that you must allocate memory to hold this property list as above. Once we have the list of properties in the PropList pointer, it's easy to retrieve the property name of a given property.
Creating a Base Application Settings Object
Now that we understand a bit more about RTTI, let's take a look at creating our base application settings object. The intent is that we should be able to derive a project specific settings object from the base settings object. For example we might have a base object called TGXAppSettings and for a word processor app we might create a TWordAppSettings class to hold the specific options for this project. The TWordAppSettings object descends from TAppSettings. The point of creating a base class TGXAppSettings is that the base class will contain all of the logic needed to load and save itself to and from the registry, regardless of the properties we add to descendant classes. Thus if we added a published WordWrap property to the TWordAppSettings class, the code in the base TAppSettings class will automatically save and load the new WordWrap property forcing the developer to add any new code.
Thus in a nutshell the purpose of the base object is to provide a mechanism to automatically save and load itself to the registry, regardless of what properties are added in descendant classes. So let's take a look at the type declaration of our TGXAppSettings object.
type
TGXAppSettings = class(TComponent)
private
FRegistryKey: string;
FIgnoreProperty: TStrings;
FAutoLoad: Boolean;
procedure SetIgnoreProperty(Value: TStrings);
protected
procedure Loaded; override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure SaveToRegistry; virtual;
procedure LoadFromRegistry; virtual;
procedure Assign(Source: TPersistent); override;
property IgnoreProperty: TStrings read FIgnoreProperty write SetIgnoreProperty;
published
property AutoLoad: Boolean read FAutoLoad write FAutoLoad;
property RegistryKey: string read FRegistryKey write FRegistryKey;
end;
Note that the object descends from TComponent rather then TPersistent as might be expected. I do this because I want to be able to place the project specific setting objects that will descend from TGXAppSettings on the component palette in order to drop them on a form. This feature will be used when I present the object dataset in Part 2.
As we can see above there are actually very few methods in the base TGXAppSettings object. There is a SaveToRegistry method to save the object to the registry and a LoadFromRegistry method to load it from the registry. We have also overriden the assign method in order to write code to enable us to easily copy one settings object to another.
Next two properties have been added, AutoLoad and RegistryKey. AutoLoad specifies where or not the component automatically loads itself from the registry when Delphi has loaded a form the component is sitting on. It also tells the component to save itself back to the registry automatically when it is being destroyed. The property called RegistryKey has been added to enable the developer to specify where to save and load the object in the registry.
The Constructor and Destructor have been overriden to allow us to create the IgnoreProperty stringlist. The IgnoreProperty stringlist is used to instruct the class of which properties to ignore when writing properties out to the registry. Descendant setting classes should not have to utilize this feature as it is primarily intented to prevent the AutoLoad, RegistryKey, Name and Tag properties from being written to the Registry.
Finally, the Loaded method has been overriden to give the component the chance to load itself from the registry if the AutoLoad property is set to true.
SaveToRegistry and LoadToRegistry Method
The SaveToRegistry method contains the code needed to save the object to the registry. It uses RTTI to automatically save all published properties of the object to the registry. Now you might be think what's the point of saving the published properties since this object doesn't have any published properties. While it is true that this object has no published properties, application setting objects that descend from this base class will have published properties and this method will save those properties auto-magically (to use my favourite term).
Here is the code for SaveToRegistry:
procedure TGXAppSettings.SaveToRegistry;
var
Registry: TRegistry;
Index: Integer;
PropName: string;
MStream: TMemoryStream;
begin
if RegistryKey = '' then
exit;
Registry := TRegistry.Create;
try
Registry.RootKey := HKEY_CURRENT_USER;
Registry.OpenKey(RegistryKey, True);
for Index := 0 to GetPropCount(Self) - 1 do
begin
PropName := GetPropName(Self, Index);
if (FIgnoreProperty.Indexof(Propname) >= 0) then
Continue;
case PropType(Self, GetPropName(Self, Index)) of
tkLString, tkWString, tkString: Registry.WriteString(PropName,
GetStrProp(Self, PropName));
tkChar, tkEnumeration, tkInteger: Registry.WriteInteger(PropName,
GetOrdProp(Self, PropName));
tkInt64: Registry.WriteString(PropName, IntToStr(GetInt64Prop(Self,
PropName)));
tkFloat: Registry.WriteString(PropName, FloatToStr(GetFloatProp(Self,
PropName)));
tkClass:
begin
if (TPersistent(GetOrdProp(Self, PropName)) is TStrings) then
begin
MStream := TMemoryStream.Create;
try
TStrings(GetOrdProp(Self, PropName)).SaveToStream(MStream);
Registry.WriteBinaryData(PropName, MStream.Memory^, MStream.Size);
finally
MStream.Free;
end;
end;
end;
end;
end;
finally
Registry.Free;
end;
end;
In the code above, we first open the registry at the desired key. We then iterate through each property of the settings object and write it out to the registry. The function GetPropCount is a utility function in GXRTTI.pas that returns the number of published properties in a given object. As we go through each property, we first get the property name using the GetPropName function in GXRTTI.pas. Finally, dependant on the type of property, we write the property out to the registry using the appropriate registry function. Functions like GetStrProp and GetOrdProp retrieve the value of the given property and are contained in the unit TypeInfo.pas.
For properties based on TStrings, we retrieve a pointer to the TStrings object using GetOrdProp and write it to the registry using WriteBinaryData. A similar technique could be used for TPicture based properties if you wished to add this feature to the class.
The LoadToRegistry method is almost identical, except the reverse functionality is performed. I won't show it here, however you can see it in the downloable code at the end of this article.
Assign method
We override the assign method in order to enable us to copy one application settings object to another. This will let us create a temporary application settings object the user can edit. We need this capability so that if the user hits the cancel button, the changes the user made are thrown away with the temporary application settings object.
The assign method appears as such:
procedure TGXAppSettings.Assign(Source: TPersistent);
begin
if Source is Self.ClassType then
CloneClass(Source, Self)
else
inherited Assign(Source);
end;
This method is deceptively simple, however note the call to CloneClass. This routine is in GXRTTI.pas and it copies all published properties from one class to another by using RTTI.
An Example Project
Now that we have done all of that work, let's create an example to see how this all fits together. I've copied the code from Borland's Richedit demo and added an options dialog to the project in order to see how this works. Our rich edit settings class appears as follows:
type
TRichEditSettings = class(TGXAppSettings)
private
FWordWrap: Boolean;
FFontName: string;
FFontSize: Integer;
public
procedure UpdateSettings(Editor: TRichEdit);
published
property FontName: string read FFontName write FFontName;
property FontSize: Integer read FFontSize write FFontSize;
property WordWrap: Boolean read FWordWrap write FWordWrap;
end;
As we see above, three properties have been added. These properties are the options for the Richedit application.
Next, I added one method called UpdateSettings. I use this method to apply the options to the actual application. In this example, the application passes the richedit control to the method that it desires to have the application settings applied to. How you apply option settings to the project will vary considerably from project to project and it is entirely up to you to decide on the best way to do this. The UpdateSettings method appears as follows:
procedure TRichEditSettings.UpdateSettings(Editor: TRichedit);
begin
Editor.WordWrap := WordWrap;
Editor.DefAttributes.Name := FontName;
Editor.DefAttributes.Size := FontSize;
end;
Now that we have created our TRichEditSettings component, we need to integrate it into the application which as we will see, could not be any easier. The first thing we do is create a project specific package, GXRichEdit.dpk. We add the unit GXProjSt.pas which contains our TRichEditSettings class. We then compile and install the package, thereby adding the TRichEditSettings component to the component palette.
Once we have the TRichEditSettings component on the palette, we simply drop it on the main form. We set the RegistryKey property to where we want to save the settings in the registry. Next we set the FontName, FontSize and WordWrap properties to the desired default values. Here are the property values as set in the example code.
The beauty of this approach is that it leverages RAD development techniques to minimize the hassle of dealing with application settings. If at some point in the future, you need to add a new setting, simply define a new property in TRichEditSettings and recompile the package. You can then use the object inspector to set the default value of the new setting.
This concludes Part 1 of how to manage application settings, in Part 2 we will see how we can connect the RichEditSettings component to an object dataset to enable the user to quickly and easily change application settings.
Limitations
The code I have presented above has a few limitations that you should be aware of before applying it in your own projects. The major limitation is that class properties other then TStrings is not currently supported. Adding support for TPicture is relatively easy but TFont is more difficult primarly due to the limitations of the object dataset presented in Part 2.
The code presented in this article has not been tested in a production environment, buyer beware.
Code
Download the code from this article here. Please be sure to read Install.txt included in the zip file before opening the project in Delphi.
2008. december 1., hétfő
Show the buffer contents of the GetLogicalDriveStrings function in a TMemo
Problem/Question/Abstract:
How to show the buffer contents of the GetLogicalDriveStrings function in a TMemo
Answer:
procedure GetLogicalDrives(aList: TStrings);
var
buff: PChar;
size, i, j: DWORD;
begin
{first we get the number of bytes required}
j := GetLogicalDriveStrings(0, PChar(@j));
size := j;
Getmem(buff, size);
try
j := GetLogicalDriveStrings(size, buff);
for i := 0 to j - 1 do
if (buff[i] = #0) then
buff[i] := #13;
aList.text := buff;
finally
Freemem(buff, size);
end;
end;
Feliratkozás:
Bejegyzések (Atom)