2010. június 30., szerda
How to draw a line in a RichEdit (just like in the Deplhi Editor)
Problem/Question/Abstract:
How to draw a line in a RichEdit (just like in the Delphi editor)
Answer:
procedure DrawLine;
var
aCanvas: Tcanvas;
X1, X2, Y1: Integer;
byLineLength: BYTE;
begin
byLineLength := 80; // Draw the line after 80 chars on the RichEdit
aCanvas := TCanvas.Create;
Y1 := RichEdit1.Height;
try
aCanvas.Handle := GetDC(RichEdit1.Handle);
aCanvas.Font := RichEdit1.Font;
X1 := aCanvas.TextWidth('W');
X2 := aCanvas.TextWidth('i');
aCanvas.Pen.color := clSilver; // Color of line
if X1 = X2 then // Check for fixed or variable font
begin
aCanvas.MoveTo(byLineLength * X1, 0);
aCanvas.LineTo(byLineLength * X1, Y1);
end;
finally
ReleaseDC(RichEdit1.Handle, aCanvas.Handle);
aCanvas.Free;
end;
end;
2010. június 29., kedd
How to do scaling while keeping the aspect ratio
Problem/Question/Abstract:
Currently I am using a whole bunch of if..then statements to compare the width and height of two rectangles and determine the scaling factor by dividing the original rect size (width or height) by the second rectangle size. If the second is smaller than the first, the scaling factor is 1. There must be a better way and I'm thinking of StretchDIBits(). Remember, I am trying to reduce the rectangle size while keeping the aspect ratio.
Answer:
Even using StretchDIBits, you have to calculate the scaling factor.
var
XScale: Single;
YScale: Single;
Scale: Single;
begin
XScale := 1.0;
YScale := 1.0;
if TargetWidth < SourceWidth then
XScale := TargetWidth / SourceWidth;
if TargetHeight < SourceHeight then
YScale := TargetHeight / SourceHeight;
Scale := XScale;
if YScale < Scale then
Scale := YScale;
end;
Now use Scale as your scaling factor.
2010. június 28., hétfő
How to control where text is dropped into a TMemo
Problem/Question/Abstract:
How can I control where text is dropped into a TMemo? In other words, I am in the middle of a drag operation and want to drop selected text into a TMemo based on the mouse position of where I drop. How can I tell the caret to go to the mouse location prior to the drop action?
Answer:
Send a EM_CHARFROMPOS message to the control, passing the mouse position (in client coordinates).
procedure TForm1.Memo1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
ret: Longint;
begin
ret := memo1.perform(EM_CHARFROMPOS, 0, MakeLParam(X, Y));
label1.caption := format('row: %d, character index: %d', [HiWord(ret),
LoWord(ret)]);
end;
That is the first step, it gives you the mouse position in "character coordiantes". You still need to convert that to a character index, which you assign to SelStart to set the caret to that position.
with memo1 do
selstart := perform(EM_LINEINDEX, row, 0) + col;
You can now assign the dropped text to the memos SelText property to insert it.
Note that the EM_CHARFROMPOS message can also be used with TRichedit but the parameters
are different!
2010. június 27., vasárnap
How to convert a bitmap or icon to Hex code
Problem/Question/Abstract:
If you load an image into a Delphi component at design time, then view the form as Text, you can see the hex code that makes up the image. I assume this is how Windows/ Delphi stores the image. How can I get the data in the same format, at runtime, as a string, to save to a text file?
Answer:
function StreamToHexStr(Stream: TStream; LineSize: Integer = 80): TStringList;
var
Value: Byte;
begin
Result := TStringList.Create;
if Result.Count = 0 then
Result.Add('');
repeat
Stream.Read(Value, 1);
Result[Result.Count - 1] := Result[Result.Count - 1] + IntToHex(Value, 2);
if Length(Result[Result.Count - 1]) >= LineSize then
Result.Add('');
Stream.Seek(1, soFromCurrent);
until
Stream.Position >= Stream.Size - 1;
if Result[Result.Count - 1] = '' then
Result.Delete(Result.Count - 1);
end;
2010. június 26., szombat
Cannot single step into VCL source code anymore
Problem/Question/Abstract:
All of a sudden I cannot single step into the VCL source code anymore. The path to $(DELPHI)\Source\Vcl is in the Environment Options Directory and I also tried to add it to the Project Options > Debug Source Path but it did not help.
It appears that $(DELPHI)\ is correct since I added a absolute path to the list and D5 recognized this was the $(DELPHI)\ path and changed it back to $(DELPHI)\Source\Vcl automatically. What happened?
Answer:
Here are two possible reasons for your situation:
You may accidentally have unchecked "Use Debug DCUs" in Project | Options Compiler.
Perhaps you have switched "Build with run-time packages" on.
2010. június 25., péntek
How to set focus on a MessageDlg button
Problem/Question/Abstract:
Is there a way to set the focus on a certain button when using MessageDlg? I want to be able to set focus to the No button when the dialog executes. By default the focus is always on the Yes button, no matter what order I code them in the function.
Answer:
Solve 1:
I had a similar situation come up and I wanted to specify which button was considered the default when pressing ENTER and which one would be the default for pressing ESCAPE. Also, I wanted other text in the buttons. So instead of Yes/ No I would have liked Save File/ Skip Save.
Then it becomes easier for the user to determine which button to press. They don't have to read the whole message, they can just look at the button. So, I will give you my code for that. I call it MultiMessageDlg, you can specify up to 4 buttons. Here is the source for my form:
unit MultiAsk;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls;
type
TMultiAskMenu = class(TForm)
LAsk: TLabel;
PButtons: TPanel;
Button1: TButton;
Button2: TButton;
Button3: TButton;
Panel1: TPanel;
Image1: TImage;
Button4: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure FormShow(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure FormClose(Sender: TObject; var Action: TCloseAction);
private
{ Private declarations }
public
{ Public declarations }
procedure Setup(MsgType: TMsgDlgType; Num: Integer; Title, Ask, S1, S2, S3, S4: string);
end;
var
MultiAskMenu: TMultiAskMenu;
implementation
uses
GlobalRW;
{$R *.DFM}
procedure ButtonCode(const Butt1: TButton; var Cap: string);
begin
Butt1.Cancel := False;
Butt1.Tag := 0;
if Pos(' + ', Cap) = 1 then
begin
Butt1.Tag := 1;
Delete(Cap, 1, 1);
end;
if Pos(' - ', Cap) = 1 then
begin
Butt1.Cancel := True;
Delete(Cap, 1, 1);
end;
Butt1.Caption := Cap;
end;
procedure TMultiAskMenu.Setup(MsgType: TMsgDlgType; Num: Integer;
Title, Ask, S1, S2, S3, S4: string);
var
TmpBmp: TBitMap;
IconID: PChar;
X, W1, W2, W3, W4: Integer;
NonClientMetrics: TNonClientMetrics;
HIcon1: HIcon;
const
IconIDs: array[TMsgDlgType] of PChar = (IDI_EXCLAMATION, IDI_HAND, IDI_ASTERISK,
IDI_QUESTION, nil);
begin
case MsgType of
mtInformation:
begin
Self.Caption := ' Information ';
end;
mtWarning: b
begin
Self.Caption := ' Warning ';
end;
mtError:
begin
Self.Caption := ' Error ';
end;
end;
if Title <> '' then
Self.Caption := Title;
NonClientMetrics.cbSize := SizeOf(NonClientMetrics);
if SystemParametersInfo(SPI_GETNONCLIENTMETRICS, 0, @NonClientMetrics, 0) then
LAsk.Font.Handle := CreateFontIndirect(NonClientMetrics.lfMessageFont);
IconID := IconIDs[MsgType];
if IconID <> nil then
begin
with Image1 do
begin
HIcon1 := LoadIcon(0, IconID);
Picture.Icon.ReleaseHandle;
Picture.Icon.Handle := HIcon1;
end;
end;
TmpBmp := TBitMap.Create;
TmpBmp.Canvas.Font := Button1.Font;
W1 := TmpBmp.Canvas.TextWidth(S1) + 10;
Button1.Width := W1;
W2 := TmpBmp.Canvas.TextWidth(S2) + 10;
Button2.Width := W2;
W3 := TmpBmp.Canvas.TextWidth(S3) + 10;
Button3.Width := W3;
W4 := TmpBmp.Canvas.TextWidth(S4) + 10;
Button4.Width := W4;
TmpBmp.Free;
LAsk.Caption := Ask;
PButtons.Top := LAsk.Top + LAsk.Height + 30;
case Num of
1:
begin
Button1.Left := Button2.Left;
Button2.Visible := False;
Button3.Visible := False;
Button4.Visible := False;
Button1.Left := (Self.Width - W1) div 2;
end;
2:
begin
Button2.Left := Button3.Left;
Button3.Visible := False;
Button4.Visible := False;
Button1.Caption := S1;
X := (Self.Width - W1 - W2) div 3;
Button1.Left := X;
Button2.Left := X + W1 + X;
end;
3:
begin
Button4.Visible := False;
X := (Self.Width - W1 - W2 - W3) div 4;
Button1.Left := X;
Button2.Left := X + W1 + X;
Button3.Left := X + W1 + X + W2 + X;
end;
4:
begin
X := (Self.Width - W1 - W2 - W3 - W4) div 5;
Button1.Left := X;
Button2.Left := Button1.Left + W1 + X;
Button3.Left := Button2.Left + W2 + X;
Button4.Left := Button3.Left + W3 + X;
end;
end;
{Take into Account pressing ESCAPE and Default buttons!!!
+Yes + = Default
-No - = Escape}
ButtonCode(Button1, S1);
ButtonCode(Button2, S2);
ButtonCode(Button3, S3);
ButtonCode(Button4, S4);
Self.AutoSize := True;
end;
procedure TMultiAskMenu.Button1Click(Sender: TObject);
begin
ModalResult := 1;
end;
procedure TMultiAskMenu.Button2Click(Sender: TObject);
begin
ModalResult := 2;
end;
procedure TMultiAskMenu.Button3Click(Sender: TObject);
begin
ModalResult := 3;
end;
procedure TMultiAskMenu.Button4Click(Sender: TObject);
begin
ModalResult := 4;
end;
procedure TMultiAskMenu.FormShow(Sender: TObject);
begin
if Button1.Tag = 1 then
Button1.SetFocus;
if Button2.Tag = 1 then
Button2.SetFocus;
if Button3.Tag = 1 then
Button3.SetFocus;
if Button4.Tag = 1 then
Button4.SetFocus;
end;
procedure TMultiAskMenu.FormClose(Sender: TObject; var Action: TCloseAction);
begin
Self.Image1.Picture.Icon.ReleaseHandle;
end;
end.
So in order to use it you would do something like this:
if MultiMessageDlg('',
' It has been awhile since you last checked for updates. Do you wish to check the TurboView Internet site for updates to TurboView?',
mtInformation, 2, ' + Check NOW ', ' - Check Next Month', '', '') = 1 then
begin
{code to do checking for the latest version of program}
end;
So the format is MultiMessageDlg(TitleText, MessageText, MessageType, NumberOfButtons, Button1Text, Button2Text, Button3Text, Button4Text);
The return value is which button was pressed [1..4];
TitleText is optional, if not title is given then the normal MessageDlg title will be used for window dialog title.
MessageText is what message you want displayed
MessageType is the same thing you provide to the normal MessageDlg function
NumberOfButtons is how many buttons to actually display
ButtonText, you can provide text for up to 4 buttons.
Note: If you want a certain button to be the DEFAULT button, then you would put a "+" plus sign in front of the text. For example: "+Save File" . And if you want a button to be the default ESCAPE button, then put a "-" minus in front, like so: "-Dont Save".
Solve 2:
The following function will let you define the default button, then center the dialog above the OwnerWnd, and then play the sound associated with the message type:
function MessageDlgEx(OwnerWnd: HWND; DefButton: Integer; const Msg: string;
DlgType: TMsgDlgType; Buttons: TMsgDlgButtons): Integer;
var
vButton: TButton;
vRect: TRect;
vWidth: Integer;
vHeight: Integer;
vTop: Integer;
vLeft: Integer;
I: Integer;
begin
with CreateMessageDialog(Msg, DlgType, Buttons) do
begin
try
{ Get the TRect }
GetWindowRect(OwnerWnd, vRect);
{ Center the dialog }
vWidth := vRect.Right - vRect.Left;
vHeight := vRect.Bottom - vRect.Top;
vTop := vRect.Top;
vLeft := vRect.Left;
Top := vTop + ((vHeight - Height) div 2);
Left := vLeft + ((vWidth - Width) div 2);
{ Set the default button }
for I := 0 to Pred(ComponentCount) do
begin
if Components[I] is TButton then
begin
vButton := TButton(Components[I]);
vButton.Default := (vButton.ModalResult = DefButton);
if vButton.Default then
begin
ActiveControl := vButton;
end;
end;
end;
{ Play the sound associated with the DlgType }
case DlgType of
mtConfirmation: MessageBeep(MB_ICONQUESTION);
mtError: MessageBeep(MB_ICONERROR);
mtInformation: MessageBeep(MB_ICONINFORMATION);
mtWarning: MessageBeep(MB_ICONWARNING);
end;
{ Show the dialog }
Result := ShowModal;
finally
free;
end;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
if MessageDlgEx(Handle, mrNo, 'Do you wan''t this program to erase all of your files?',
mtWarning, [mbYes, mbNo]) = mrYes then
ShowMessage('Okay...');
end;
2010. június 24., csütörtök
Draw text in the node of a TTreeView in bold style
Problem/Question/Abstract:
I would like to write the text of each node in a TTreeview by including the text in the standard way with a trailing number written in bold and color blue.
Answer:
This is a code snippet of a descendant of a TTreeView that handles the bold state of a treenode:
function TTreeView1.GetNodeBoldState(Node: TTreeNode): boolean;
var
TVItem: TTVItem;
begin
result := false;
if not Assigned(Node) then
exit;
with TVItem do
begin
mask := TVIF_STATE;
hitem := Node.ItemId;
result := TreeView_GetItem(Node.Handle, TVItem) and ((State and TVIS_BOLD) <> 0);
end;
end;
procedure TTreeView1.SetNodeBoldState(Node: TTreeNode; value: boolean);
var
TVItem: TTVItem;
begin
if not Assigned(Node) then
exit;
fillchar(TVItem, sizeof(TVItem), 0);
with TVItem do
begin
mask := TVIF_STATE or TVIF_HANDLE;
hitem := Node.ItemId;
StateMask := TVIS_BOLD;
if value then
State := TVIS_BOLD;
TreeView_SetItem(Node.Handle, TVItem);
end;
end;
2010. június 23., szerda
Display the window of another application in a Delphi form
Problem/Question/Abstract:
I would like to have an application (f.e. notepad.exe) to run in a specified frame (TPanel,..) within my application.
Answer:
It does not work well but you can try this:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ComCtrls, StdCtrls, Menus, AppEvnts;
type
TForm1 = class(TForm)
ApplicationEvents1: TApplicationEvents;
procedure FormCreate(Sender: TObject);
procedure ApplicationEvents1Activate(Sender: TObject);
private
{ Private declarations }
FNotepad: HWND;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
var
wnd: HWND;
tries: Integer;
begin
WinExec('notepad.exe', SW_HIDE);
tries := 0;
repeat
wnd := Findwindow('notepad', nil);
if wnd = 0 then
begin
inc(tries);
sleep(100);
end;
until
(wnd <> 0) or (tries > 10);
if wnd <> 0 then
begin
windows.setparent(wnd, handle);
application.title := 'Notepad';
MoveWindow(wnd, 0, 0, clientwidth, clientheight, false);
ShowWindow(wnd, SW_SHOW);
SetForegroundWindow(wnd);
FNotepad := wnd;
end
else
showmessage('Failed');
end;
procedure TForm1.ApplicationEvents1Activate(Sender: TObject);
begin
if IsWindow(FNotepad) then
SetForegroundWindow(FNotepad)
else
Close;
end;
end.
To wire the notepad window to a panel you would simlpy use the panels handle in the SetParent call.
2010. június 22., kedd
How to do a SHIFT TAB through code
Problem/Question/Abstract:
I use SendMessage(EditHandle, WM_KEYDOWN, VK_TAB, 0) to mimic pressing TAB key, but how about SHIFT-TAB? I know I can use WM_NEXTDLGCTL, but that is exactly what I try to avoid.
Answer:
You can do both by sending the WM_KEYDOWN message to the control or generate the keyboard event through the keybd_event function. See the example below for details:
{ ... }
var
XKeyState, XNewKeyState: TKeyboardState;
begin
try
{set shift key down}
GetKeyboardState(XKeyState);
XNewKeyState := XKeyState;
XNewKeyState[VK_SHIFT] := $81;
SetKeyboardState(XNewKeyState);
{post tab key down message}
PostMessage(YourComponent.Handle, WM_KEYDOWN, VK_TAB, 0);
Application.ProcessMessages;
finally
{return old keyboard state back}
SetKeyboardState(XKeyState);
end;
end;
or you use
{ ... }
keybd_event(VK_SHIFT, 0, 0, 0);
keybd_event(VK_TAB, 0, 0, 0);
keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
end;
2010. június 21., hétfő
Sort a TStringGrid using the Bubblesort algorithm
Problem/Question/Abstract:
Does anyone know an easy way to sort TStringGrids, preferably by the first column?
Answer:
Here is a quick Bubblesort:
procedure TForm1.Button1Click(Sender: TObject);
var
pass, j: integer;
hold: TStringList;
begin
hold := TStringList.Create;
{sorting is based on first column, '0'}
for pass := 1 to StringGrid1.RowCount - 1 do
begin
for j := 0 to StringGrid1.RowCount - 2 do
if StringGrid1.Cells[0, j] > StringGrid1.Cells[0, j + 1] then
begin
hold.Assign(StringGrid1.Rows[j]);
StringGrid1.Rows[j].Assign(StringGrid1.Rows[j + 1]);
StringGrid1.Rows[j + 1].Assign(hold);
end;
end;
hold.Free;
end;
2010. június 20., vasárnap
Delphi .NET: Get computer IP address
Problem/Question/Abstract:
How to get the list of the computer IP addresses
Answer:
One of the many namespaces in the Base Class Framework is the System.Net namespace. It provides a simple programming interface to many of the protocols found on the network today. One of the classes of the namespace is DNS which provides simple domain name resolution functionality.
{$APPTYPE CONSOLE}
program getip;
uses
System,
System.Net,
Borland.Delphi.SysUtils;
var
strMachineName: string;
ipHost: IPHostEntry;
ipAddr: array of IPAddress;
count: Integer;
begin
//Get the Host Name
strMachineName := Dns.GetHostName();
Console.WriteLine('Host Name: ' + strMachineName);
//Get the Host by Name
ipHost := Dns.GetHostByName(strMachineName);
//You can also query the DNS database for info on a
//website like users.chello.be
//In that case replace the above line as:
//ipHost := Dns.GetHostByName('users.chello.be')
//Get the list of addresses associated with the host in an array
ipAddr := ipHost.AddressList;
//Enumerate the IP Addresses
for count := 0 to length(ipAddr) - 1 do
Console.Write(Format('IP Addresses %d: %s ', [count, ipAddr[count].ToString]));
end.
2010. június 19., szombat
Use the GetKerningPairs function
Problem/Question/Abstract:
I want to display text with the correct kerning (spacing). GetKerningPairs is a function I need for that, but I have no clue how to use it.
Answer:
{ ... }
type
TKerningpairarray = array[0..600] of Kerningpair;
{ ... }
var
kpa: TKerningpairarray;
{ ... }
var
i, Num: Integer;
begin
Canvas.Font.Name := 'Arial';
Num := GetKerningPairs(Canvas.Handle, 600, kpa);
Memo1.Text := '';
for i := 0 to Num - 1 do
Memo1.Lines.Add(IntToStr(kpa[i].wfirst) + ', ' + IntToStr(kpa[i].wsecond) + ', '
+ IntToStr(kpa[i].ikernamount));
end;
2010. június 18., péntek
SQL monitor magic
Problem/Question/Abstract:
Having problems with SQL monitor? Need better monitoring? How about multi-threaded monitoring? SQL monitor infrastructure provides this and more.
Answer:
Introduction
SQL monitor is one of the most useful tools in Delphi, when you develop a database application. It allows the programmer to debug the connection between an application and a Database. It is very useful when you have automatic SQL generation. The tool provides the time it takes for each SQL to run, so you can use it to profile you’re DB side of the application.
SQL monitor paints a nice picture. However, SQL monitor has some problems:
You must start SQL monitor before you start the client application. This is a problem with applications that need to run non-stop for long durations.
The tool is not designed to work with multithreaded applications. It can trace only one session at a time, and that session is the last one opened. You cannot select what thread to monitor, nor can you monitor more then one thread.
Some applications use an automatic trouble tickets (TT) in case of errors. When you have a DB related problem, it is useful to add the SQL trace to the TT. However, the SQL monitor is an external tool, and does not allow this kind of trace.
The SQL monitor tool uses an infrastructure provided by Delphi and the BDE to trace SQLs. We can connect to this infrastructure without the SQL monitor tool, in order to get an SQL trace internally to the application, with out any of the problems above.
How the SQL trace works
We need to tell the BDE that we want an SQL trace. We do that by registering a callback function with the BDE (Callback is the equivalent of an event in non Object Oriented systems). The BDE provides SQL trace by setting a memory buffer with some text, and then notifying us with a callback. The callback function gets one parameter – a pointer to a TtraceDesc type (defined in the BDE unit). In that structure is the text we see in the SQL monitor tool.
Setting a BDE SQL Trace
In order to set a trace on the BDE, we need to register a BDE callback using the DbiRegisterCallback function in the DBE unit. The unit takes a number of parameters that sound like gibberish when you look at them in the online help. The VCL provides a nice wrapper for this call with the TBDECallback Class in the DBTables unit. This class takes a number of parameters in its constructor, and sets the appropriate callback. When we free an object of this class, the callback is freed.
To use the TBDECallback object, we need to do a number of things:
The TBDECallback object can register all kinds of DBE callbacks. In order to trace SQL, we need a cbTRACE callback (the value of the CBType parameter in Create).
We need to create a callback function with the following prototype:
function(CBInfo: Pointer): CBRType of object;
We need to create a memory buffer of smTraceBufSize size. (smTraceBufSize is a constant defined in the DBTables unit).
The code to set a trace can look like this:
var
FSMBuffer: PTraceDesc;
TraceCallback: TBDECallback;
begin
GetMem(FSMBuffer, smTraceBufSize);
TraceCallback := TBDECallback.Create(Self, nil, cbTRACE,
FSMBuffer, smTraceBufSize, SqlTraceCallBack, False);
end;
The sqlTraceCallBack is a function defined in Delphi. It can look like this:
function TInternalSQLMonitor.SqlTraceCallBack(CBInfo: Pointer): CBRType;
var
Data: Pointer;
S: string;
begin
Data := @PTraceDesc(CBInfo).pszTrace;
SetLength(S, StrLen(Data));
StrCopy(PChar(S), Data);
// S holds the trace text!
Result := cbrUSEDEF;
end;
Stopping the trace
In order to stop the trace, all you need to do is
FreeMem(FSMBuffer, smTraceBufSize);
TraceCallback.Free;
And now for the advanced staff…
In the last section I explained how to setup an SQL trace. However, in the start of this article, a complained that the SQL monitor tool does not provide good support for multiple sessions and threads. In fact, the code in the last section has exactly the same problems. We need to overcome those problems.
If you look at the code in the last section, you will see that I do not specify what session and what database to trace. I also do not setup what are the trace options (as we have in the SQL monitor options window).
The problem is that we are opening a trace on the default session, default database and using the default settings (from the BDE driver).
When we run the above code, it registers a trace with the BDE current session. The current session is accessed via the sessions.CurrentSession global object property. By changing the current session, we can register a trace for any session we want. The callback function is registered per session, allowing us multi-threading trace. Don’t confuse the default session with the current session. The default session is one that is automatically opened by Delphi, and cannot be changed. The current session is current from the BDE point of view. It is the session that BDE functions work with. Because the current session is a global definition, we need some thread locking mechanism when we set a trace. The code for setting a trace can now look like:
var
ActivationLock: TCriticalSection;
procedure SetTrace;
begin
ActivationLock.Enter;
try
// set the current session to be the session we want to trace.
SaveCurrentSession := Sessions.CurrentSession;
Sessions.CurrentSession := Session;
// set the trace.
GetMem(FSMBuffer, smTraceBufSize);
TraceCallback := TBDECallback.Create(Self, nil, cbTRACE,
FSMBuffer, smTraceBufSize, SqlTraceCallBack, True);
// restore the current session to the saved session.
Sessions.CurrentSession := SaveCurrentSession;
finally
ActivationLock.Leave;
end;
end;
We need the same structure when we release the trace.
procedure CloseTrace;
begin
ActivationLock.Enter;
try
// set the current session to be the session we want to trace.
SaveCurrentSession := Sessions.CurrentSession;
Sessions.CurrentSession := Session;
// close the trace.
FreeMem(FSMBuffer, smTraceBufSize);
TraceCallback.Free;
// restore the current session to the saved session.
Sessions.CurrentSession := SaveCurrentSession;
finally
ActivationLock.Leave;
end;
end;
What about the trace options?
The trace options come from the driver configuration of the BDE. However, you can override them from Delphi by setting the TraceFlags property of a Tdatabase component. There is one fine point to notice. You must set the value of TraceFlags AFTER you open the database. For some reason, if you set the options before you open the database, this has no affect.
Example
The following example is a component providing SQL trace for one session and one database. The component fires a Delphi event for each SQL trace event, with the trace text as a parameter. In order to use this component, all you need to do is attach it to a Tsession and Tdatabase, set the trace options, set the event and activate the trace.
Note that you can only activate a trace on an open database.
Code
unit InternalSQLMonitor_thread;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
dbTables, bde, syncobjs;
const
cDefaultTraceOptions = [tfQPrepare, tfQExecute, tfError, tfStmt, tfDataIn];
type
TSQLTraceEvent = procedure(Sender: TObject; const SQLTrace: string) of object;
TInternalSQLMonitor = class(TComponent)
private
FActive: Boolean;
FOnSQLTraceEvent: TSQLTraceEvent;
FSMBuffer: PTraceDesc;
TraceCallback: TBDECallback;
FSession: TSession;
FDatabase: TDatabase;
FTraceOptions: TTraceFlags;
procedure ReplaceComponent(var Reference: TComponent; const Value: TComponent);
procedure SetActive(const Value: Boolean);
procedure SetOnSQLTraceEvent(const Value: TSQLTraceEvent);
procedure SetSession(const Value: TSession);
procedure SetDatabase(const Value: TDatabase);
function CanOpenTrace: Boolean;
procedure SetTraceOptions(const Value: TTraceFlags);
protected
function SqlTraceCallBack(CBInfo: Pointer): CBRType;
procedure Notification(AComponent: TComponent;
Operation: TOperation); override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
procedure Open;
procedure Close;
published
property OnSQLTraceEvent: TSQLTraceEvent read FOnSQLTraceEvent write
SetOnSQLTraceEvent;
property Active: Boolean read FActive write SetActive;
property Session: TSession read FSession write SetSession;
property Database: TDatabase read FDatabase write SetDatabase;
property TraceOptions: TTraceFlags read FTraceOptions write SetTraceOptions default
cDefaultTraceOptions;
end;
procedure Register;
implementation
var
ActivationLock: TCriticalSection;
procedure Register;
begin
RegisterComponents('Samples', [TInternalSQLMonitor]);
end;
{ TInternalSQLMonitor }
function TInternalSQLMonitor.CanOpenTrace: Boolean;
begin
Result := (Session <> nil) and
(Session.Active) and
(Database <> nil) and
(Database.Connected);
end;
procedure TInternalSQLMonitor.Close;
begin
SetActive(False);
end;
constructor TInternalSQLMonitor.Create(AOwner: TComponent);
begin
inherited;
TraceOptions := cDefaultTraceOptions;
end;
destructor TInternalSQLMonitor.Destroy;
begin
inherited;
SetActive(False);
end;
procedure TInternalSQLMonitor.Open;
begin
SetActive(True);
end;
procedure TInternalSQLMonitor.SetActive(const Value: Boolean);
var
SaveCurrentSession: TSession;
begin
// create the critical section, if needed.
if ActivationLock = nil then
ActivationLock := TCriticalSection.Create;
if FActive <> Value then
begin
// check that all the preconditions needed to set a trace are met.
if (Value = True) and (not CanOpenTrace) then
raise
Exception.Create('Cannot open trace when the session or database are closed');
// prevent other threads from hampering. If other trace objects are opened
// at the same time, prevent them from changing the current session until
// we finish with it.
ActivationLock.Enter;
try
FActive := Value;
// set the current session to be the session we want to trace.
SaveCurrentSession := Sessions.CurrentSession;
Sessions.CurrentSession := Session;
if FActive then
begin
// set the trace.
GetMem(FSMBuffer, smTraceBufSize);
TraceCallback := TBDECallback.Create(Self, nil, cbTRACE,
FSMBuffer, smTraceBufSize, SqlTraceCallBack, True);
// Set the trace Flags to the database
FDatabase.TraceFlags := TraceOptions;
end
else
begin
// release the trace.
FreeMem(FSMBuffer, smTraceBufSize);
TraceCallback.Free;
end;
// restore the current session to the saved session.
Sessions.CurrentSession := SaveCurrentSession;
finally
ActivationLock.Leave;
end;
end;
end;
procedure TInternalSQLMonitor.SetDatabase(const Value: TDatabase);
begin
if FDatabase <> Value then
begin
if Active then
Active := False;
if Assigned(FDatabase) then
FDatabase.RemoveFreeNotification(Self);
FDatabase := Value;
if Assigned(FDatabase) then
FDatabase.FreeNotification(Self);
end;
end;
procedure TInternalSQLMonitor.SetOnSQLTraceEvent(
const Value: TSQLTraceEvent);
begin
FOnSQLTraceEvent := Value;
end;
procedure TInternalSQLMonitor.SetSession(const Value: TSession);
begin
if FSession <> Value then
begin
if Active then
Active := False;
if Assigned(FSession) then
FSession.RemoveFreeNotification(Self);
FSession := Value;
if Assigned(FSession) then
FSession.FreeNotification(Self);
if (FDatabase <> nil) and (FDatabase.Session <> FSession) then
FDatabase := nil;
end;
end;
procedure TInternalSQLMonitor.SetTraceOptions(const Value: TTraceFlags);
begin
if FTraceOptions <> Value then
begin
FTraceOptions := Value;
if Active then
FDatabase.TraceFlags := Value;
end;
end;
function TInternalSQLMonitor.SqlTraceCallBack(CBInfo: Pointer): CBRType;
var
Data: Pointer;
S: string;
begin
try
if Assigned(FOnSQLTraceEvent) then
begin
Data := @PTraceDesc(CBInfo).pszTrace;
SetLength(S, StrLen(Data));
StrCopy(PChar(S), Data);
FOnSQLTraceEvent(Self, S);
end;
except
end;
Result := cbrUSEDEF;
end;
procedure TInternalSQLMonitor.ReplaceComponent(var Reference: TComponent;
const Value: TComponent);
begin
if Assigned(Value) then
Reference.RemoveFreeNotification(Self);
Reference := Value;
if Assigned(Reference) then
Value.FreeNotification(Self);
end;
procedure TInternalSQLMonitor.Notification(AComponent: TComponent;
Operation: TOperation);
begin
inherited;
if Operation = opRemove then
begin
if (AComponent = FDatabase) then
Database := nil;
if (AComponent = FSession) then
Session := nil;
end;
end;
initialization
finalization
if ActivationLock <> nil then
FreeAndNil(ActivationLock);
end.
2010. június 17., csütörtök
Check if the PC is connected to the Internet
Problem/Question/Abstract:
I just want to know a better way for checking an internet connection. One possible way is just to try to connect to a server with a TClientSocket or something like that. I can make this in a time interval. Is there a registry value or something like that which is changed if the computer goes online?
Answer:
Solve 1:
uses
wininet
function Connected: boolean;
var
flags: DWORD;
begin
Flags := INTERNET_CONNECTION_MODEM or INTERNET_CONNECTION_LAN or
INTERNET_CONNECTION_PROXY or INTERNET_CONNECTION_MODEM_BUSY;
result := InternetGetConnectedState(@Flags, 0);
end;
Solve 2:
function TestIsOnline(var IPAddress: string): Boolean;
type
TaPInAddr = array[0..10] of PInAddr;
PaPInAddr = ^TaPInAddr;
var
phe: PHostEnt;
pptr: PaPInAddr;
Buffer: array[0..63] of Char;
i: Integer;
GInitData: TWSAData;
IP: string;
begin
{$IFDEF OFFLINETEST}
IPAddress := '127.0.0.1';
Result := True;
{$ELSE}
WSAStartup($101, GInitData);
GetHostName(Buffer, SizeOf(Buffer));
phe := GetHostByName(Buffer);
if phe = nil then
Exit;
pPtr := PaPInAddr(phe^.h_addr_list);
i := 0;
while pPtr^[i] <> nil do
begin
IP := inet_ntoa(pptr^[i]^);
Inc(i)
end;
WSACleanup;
Result := (IP <> '') and (IP <> '127.0.0.1');
if Result then
IPAddress := IP
else
IPAddress := '';
{$ENDIF}
end;
Solve 3:
I use this with Delphi 6, but I think it works too with Delphi 2:
Other connection Value are
INTERNET_CONNECTION_MODEM = 1;
INTERNET_CONNECTION_LAN = 2;
INTERNET_CONNECTION_PROXY = 4;
INTERNET_CONNECTION_MODEM_BUSY = 8;
unit InternetConnected;
interface
uses
Windows;
const
INTERNET_CONNECTION_LAN = 2;
function InternetGetConnectedState(lpdwFlags: LPDWORD; dwReserved: DWORD): BOOL;
stdcall;
implementation
uses
SysUtils;
var
winetdllHandle: THandle = 0;
const
winetdll = 'wininet.dll';
function InternetGetConnectedState(lpdwFlags: LPDWORD; dwReserved: DWORD): BOOL;
stdcall;
var
fn_InternetGetConnectedState: function(lpdwFlags: LPDWORD; dwReserved: DWORD):
BOOL; stdcall;
begin
if (winetdllHandle = 0) then
winetdllHandle := SafeLoadLibrary(winetdll);
if (winetdllHandle <> 0) then
begin
@fn_InternetGetConnectedState := GetProcAddress(winetdllHandle,
'InternetGetConnectedState');
if (@fn_InternetGetConnectedState <> nil) then
Result := fn_InternetGetConnectedState(lpdwFlags, dwReserved)
else
raise Exception.Create('Unable to locate function InternetGetConnectedState
in library' + winetdll);
end
else
raise Exception.Create('Unable to load library ' + winetdll);
end;
initialization
finalization
try
if (winetdllHandle <> 0) then
FreeLibrary(winetdllHandle);
except
end;
end.
Solve 4:
function FuncAvail(VLibraryname, VFunctionname: string; var VPointer: pointer):
boolean;
//
// this function check if VFunctionname exists in VLibraryname
//
var
Vlib: tHandle;
begin
Result := false;
if LoadLibrary(PChar(VLibraryname)) = 0 then
exit;
Vlib := GetModuleHandle(PChar(VLibraryname));
if Vlib <> 0 then
begin
VPointer := GetProcAddress(Vlib, PChar(VFunctionname));
if VPointer <> nil then
Result := true;
end;
end;
Code Button1 on a Form1:
procedure TForm1.Button1Click(Sender: TObject);
//
// Call shell32.dll for highter Win98
// else call url.dll
//
var
InetIsOffline: function(dwFlags: DWORD): BOOL; stdcall;
begin
if FuncAvail('url.dll', 'InetIsOffline', @InetIsOffline) then
if InetIsOffLine(0) then
ShowMessage('Not connected')
else
ShowMessage('Connected!');
end;
2010. június 16., szerda
What's left of Delphi in Delphi 8?
Problem/Question/Abstract:
What's new in Delphi 8 and what do developers have to consider when migrating from Delphi 7 (or lower) to Delphi 8 (.NET)?
Answer:
The IDE itself differs a lot from previous Delphi versions and looks a bit like Visual Studio .NET. Although the menus have not changed and all "old" shortcuts work, the Object Inspector, the Project Manager and the Component Palette and even the design of the Help did change a lot.
There are two possibilities to create applications with Delphi 8 - VCL Forms Applications and Win Forms Applications. Both create IML (intermediate language code) and are .NET Applications, which means that they can only be deployed on a computer with the .NET framework installed.
VCL Forms Applications
A VCL Forms Applications have - at a first glance - most in common with a normal Delphi application. You will find known VCL components and the structure of your units is very familiar. Borland tries to hide the .NET framework from the programmer as far as it is possible.
The first (visual) point you come across is the References-Node in the Project Manager. It contains the dependencies of your application from existing .NET components. In an empty project they are by default: System.Data.dll, System.dll,
System.Drawing.dll and System.XML.dll. As soon as you drag a component (e.g. a button) to your form the following references will be added to your project: Borland.Delphi.dll, Borland.Vcl.dll and Borland VclRtl.dll.
This is the first point you come in contact with the .NET framework and its dogma of namespaces. The unit concept of Delphi is represented by the namespace concept of the .NET framework. In Delphi each class is referenced by its file (unit) – in the .NET environment each class is identified by its namespace (a unique string divided by dots). Therefore the dlls are named e.g. Borland.Vcl.dll, which means that from now on dots are allowed in unit names: e.g. unit Borland.Vcl.Forms.
To be able to work with the "old" VCL components Borland moved them (partially) to the .NET framework, wich means that the VCL components are based on pure .NET components (e.g. TPersistent = System.MarshalByRefObject, TObject = System.Object or Exception = System.Exception) whenever it was possible - in all other cases they included Win32 API calls (unmanaged code in the eyes of .NET). But that also means that your VCL applications are platform dependent while pure .NET Applications are not (theoretically).
The next difference is that forms are no longer saved in a .dfm file. In VCL Applications the file has the extension .nfm, in Winforms Applications there is no file - the code for generating the form is within the unit file.
Win Forms Applications
The structure of your units within Win Forms Applications is quite different from those in VCL Forms Applications. The differences start in the uses-clause, where .NET components (namespaces) are referenced. All .NET components start with the namespace System.* whereas namespaces of the Borland VCL components begin with Borland.*.
uses
System.Drawing, System.Collections, System.ComponentModel,
System.Windows.Forms, System.Data;
Additionally there are code segments that are under the control of the form designer, because all information for the design is within the unit (no more in an extra .pas file).
In the type definition:
type
TWinForm = class(System.Windows.Forms.Form)
{$REGION 'Designer controlled code:'}
strict private
Components: System.ComponentModel.Container;
procedure InitializeComponent;
{$ENDREGION}
strict protected
procedure Dispose(Disposing: Boolean); override;
private
{ Private-Deklarationen }
public
constructor Create;
end;
The procedure InitializeComponent holds all information about the visual design of the form. This procedure is called in the constructor of the unit.
procedure TWinForm.InitializeComponent;
begin
Self.Components := System.ComponentModel.Container.Create;
Self.Size := System.Drawing.Size.Create(300, 300);
Self.Text := 'WinForm';
end;
constructor TWinForm.Create;
begin
inherited Create;
InitializeComponent;
//
// TODO: Add own constructor code after the call of InitializeComponent.
//
end;
The .NET framework "renamed" the destructor to Dispose, and there is also code added automatically by the designer to this procedure.
procedure TWinForm.Dispose(Disposing: Boolean);
begin
if Disposing then
begin
if Components <> nil then
Components.Dispose();
end;
inherited Dispose(Disposing);
end;
Examples for differences between VCL Forms Applications and Win Forms Applications
In Win Forms Applications you might have different components from these in VCL Forms Applications. But the differences are quite small because the designer of the .NET framework components and the designer of the VCL components is the same person. Learning how to use the .NET framework merely means learning how to use the components and their properties and events - and that’s not
very difficult for a Delphi programmer.
E.g. difference of TButton (VCL component) and Button (.NET component)
Button1: TButton;
Button1.Caption := 'Hello World';
Button1: System.Windows.Forms.Button;
Button1.Text := 'Hello World';
An other benefit of Win Forms Applications is that you can have multipe event handlers for one event. The default (designed) event handler is created in the procedure InitializeComponents e.g.:
Self.Button1.Location := System.Drawing.Point.Create(104, 72);
{ ... }
Include(Self.Button1.Click, Self.Button1_Click);
But you can add even more components by calling the Include procedure like:
Include(Button1.Click, Self.MyButtonClick);
The same procedure (Include) is available in VCL Forms Applications, too, but the event handler called with the last Include call is always executed.
General Differences
Migrating from Delphi to the .NET Framework means to lose control over the object lifecycle (the .NET Framework has a garbage collector) and the code execution (you create intermediate language which is compiled at runtime with the JIT). But on the other hand you get new benefits/new features from the .NET Framework itself and you work with the latest Windows technology (the next Windows OS - Longhorn - will be totally based on .NET) - and all Delphi programmers build Windows applications. In my opinion these benefits only take effect if you build Win Forms Application.
Theoretically with VCL Forms Applications it is possible to move existing Delphi applications to the .NET Framework. I don't think that this will work for most applications, for I don't think that there are many applications built with the common Delphi VCL components
Looking at Win Forms components the .NET Framework shows its details. To develop such applications you have to learn using the framework - which is easy for a Delphi programmer - and a few new techniques (e.g. multiple event handlers). You keep your favorite programming language and additional Borland components (microsoft independent data providers e.g. for Interbase or DB2).
Additionally Borland integrated UML support for designing classes named ECO (BoldSoft under Delphi 7). The components for ECO give you the possibility to treat your objects like relational data - e.g. displaying properties of all instances of a class in a grid.
Conclusion
To answer the heading question of this article "What's left of Delphi in Delphi 8?" we must look differently at VCL Applications and Win Forms Applications. With VCL Applications Borland tried to make the move from traditional Windows programming to .NET programming as easy as possible for the developer in providing many known and common components, techniques and structures. So, looking at VCL Forms Applications, I would say that Delphi 8 has much in common with Delphi - on the surface.
Whats left overall is the programming language (object Pascal) and its whole structure, a "microsoft-independent" product and Borland’s aim of building and providing a wide range of components (like ECO, Component One or providers for other databases).
What's new in Delphi 8 and what do developers have to consider when migrating from Delphi 7 (or lower) to Delphi 8 (.NET)?
Answer:
The IDE itself differs a lot from previous Delphi versions and looks a bit like Visual Studio .NET. Although the menus have not changed and all "old" shortcuts work, the Object Inspector, the Project Manager and the Component Palette and even the design of the Help did change a lot.
There are two possibilities to create applications with Delphi 8 - VCL Forms Applications and Win Forms Applications. Both create IML (intermediate language code) and are .NET Applications, which means that they can only be deployed on a computer with the .NET framework installed.
VCL Forms Applications
A VCL Forms Applications have - at a first glance - most in common with a normal Delphi application. You will find known VCL components and the structure of your units is very familiar. Borland tries to hide the .NET framework from the programmer as far as it is possible.
The first (visual) point you come across is the References-Node in the Project Manager. It contains the dependencies of your application from existing .NET components. In an empty project they are by default: System.Data.dll, System.dll,
System.Drawing.dll and System.XML.dll. As soon as you drag a component (e.g. a button) to your form the following references will be added to your project: Borland.Delphi.dll, Borland.Vcl.dll and Borland VclRtl.dll.
This is the first point you come in contact with the .NET framework and its dogma of namespaces. The unit concept of Delphi is represented by the namespace concept of the .NET framework. In Delphi each class is referenced by its file (unit) – in the .NET environment each class is identified by its namespace (a unique string divided by dots). Therefore the dlls are named e.g. Borland.Vcl.dll, which means that from now on dots are allowed in unit names: e.g. unit Borland.Vcl.Forms.
To be able to work with the "old" VCL components Borland moved them (partially) to the .NET framework, wich means that the VCL components are based on pure .NET components (e.g. TPersistent = System.MarshalByRefObject, TObject = System.Object or Exception = System.Exception) whenever it was possible - in all other cases they included Win32 API calls (unmanaged code in the eyes of .NET). But that also means that your VCL applications are platform dependent while pure .NET Applications are not (theoretically).
The next difference is that forms are no longer saved in a .dfm file. In VCL Applications the file has the extension .nfm, in Winforms Applications there is no file - the code for generating the form is within the unit file.
Win Forms Applications
The structure of your units within Win Forms Applications is quite different from those in VCL Forms Applications. The differences start in the uses-clause, where .NET components (namespaces) are referenced. All .NET components start with the namespace System.* whereas namespaces of the Borland VCL components begin with Borland.*.
uses
System.Drawing, System.Collections, System.ComponentModel,
System.Windows.Forms, System.Data;
Additionally there are code segments that are under the control of the form designer, because all information for the design is within the unit (no more in an extra .pas file).
In the type definition:
type
TWinForm = class(System.Windows.Forms.Form)
{$REGION 'Designer controlled code:'}
strict private
Components: System.ComponentModel.Container;
procedure InitializeComponent;
{$ENDREGION}
strict protected
procedure Dispose(Disposing: Boolean); override;
private
{ Private-Deklarationen }
public
constructor Create;
end;
The procedure InitializeComponent holds all information about the visual design of the form. This procedure is called in the constructor of the unit.
procedure TWinForm.InitializeComponent;
begin
Self.Components := System.ComponentModel.Container.Create;
Self.Size := System.Drawing.Size.Create(300, 300);
Self.Text := 'WinForm';
end;
constructor TWinForm.Create;
begin
inherited Create;
InitializeComponent;
//
// TODO: Add own constructor code after the call of InitializeComponent.
//
end;
The .NET framework "renamed" the destructor to Dispose, and there is also code added automatically by the designer to this procedure.
procedure TWinForm.Dispose(Disposing: Boolean);
begin
if Disposing then
begin
if Components <> nil then
Components.Dispose();
end;
inherited Dispose(Disposing);
end;
Examples for differences between VCL Forms Applications and Win Forms Applications
In Win Forms Applications you might have different components from these in VCL Forms Applications. But the differences are quite small because the designer of the .NET framework components and the designer of the VCL components is the same person. Learning how to use the .NET framework merely means learning how to use the components and their properties and events - and that’s not
very difficult for a Delphi programmer.
E.g. difference of TButton (VCL component) and Button (.NET component)
Button1: TButton;
Button1.Caption := 'Hello World';
Button1: System.Windows.Forms.Button;
Button1.Text := 'Hello World';
An other benefit of Win Forms Applications is that you can have multipe event handlers for one event. The default (designed) event handler is created in the procedure InitializeComponents e.g.:
Self.Button1.Location := System.Drawing.Point.Create(104, 72);
{ ... }
Include(Self.Button1.Click, Self.Button1_Click);
But you can add even more components by calling the Include procedure like:
Include(Button1.Click, Self.MyButtonClick);
The same procedure (Include) is available in VCL Forms Applications, too, but the event handler called with the last Include call is always executed.
General Differences
Migrating from Delphi to the .NET Framework means to lose control over the object lifecycle (the .NET Framework has a garbage collector) and the code execution (you create intermediate language which is compiled at runtime with the JIT). But on the other hand you get new benefits/new features from the .NET Framework itself and you work with the latest Windows technology (the next Windows OS - Longhorn - will be totally based on .NET) - and all Delphi programmers build Windows applications. In my opinion these benefits only take effect if you build Win Forms Application.
Theoretically with VCL Forms Applications it is possible to move existing Delphi applications to the .NET Framework. I don't think that this will work for most applications, for I don't think that there are many applications built with the common Delphi VCL components
Looking at Win Forms components the .NET Framework shows its details. To develop such applications you have to learn using the framework - which is easy for a Delphi programmer - and a few new techniques (e.g. multiple event handlers). You keep your favorite programming language and additional Borland components (microsoft independent data providers e.g. for Interbase or DB2).
Additionally Borland integrated UML support for designing classes named ECO (BoldSoft under Delphi 7). The components for ECO give you the possibility to treat your objects like relational data - e.g. displaying properties of all instances of a class in a grid.
Conclusion
To answer the heading question of this article "What's left of Delphi in Delphi 8?" we must look differently at VCL Applications and Win Forms Applications. With VCL Applications Borland tried to make the move from traditional Windows programming to .NET programming as easy as possible for the developer in providing many known and common components, techniques and structures. So, looking at VCL Forms Applications, I would say that Delphi 8 has much in common with Delphi - on the surface.
Whats left overall is the programming language (object Pascal) and its whole structure, a "microsoft-independent" product and Borland’s aim of building and providing a wide range of components (like ECO, Component One or providers for other databases).
2010. június 14., hétfő
...delete a file permanently?
Problem/Question/Abstract:
delete a file permanently?
Answer:
If you want to get rid of a file normally you just delete it.
But someone else can undelete it if the file hasn't been wiped correctly.
For security purposes, to insure that certain files are permanently
gone, the WipeFile procedure writes over the data in the file with
random characters and then erases it.
Wenn man eine Datei nicht mehr braucht, l�scht man sie einfach.
Aber jemand anders kann die Datei wieder herstellen, wenn sie
nicht "richtig" gel�scht wurde.
Aus Sicherheitsgr�nden, um sicherzustellen, dass eine Datei permanent
gel�scht wird, �berschreibt die WipeFile Prozedur eine Datei mit
Zufalls-Zeichen und l�scht sie anschliessend.
}
procedure WipeFile(FileName: string);
var
buffer: array [0..4095] of Byte;
max, n: LongInt;
i: Integer;
fs: TFileStream;
procedure RandomizeBuffer;
var
i: Integer;
begin
for i := Low(buffer) to High(buffer) do
buffer[i] := Random(256);
end;
begin
fs := TFilestream.Create(FileName, fmOpenReadWrite or fmShareExclusive);
try
for i := 1 to 3 do
begin
RandomizeBuffer;
max := fs.Size;
fs.Position := 0;
while max > 0 do
begin
if max > SizeOf(buffer) then
n := SizeOf(buffer)
else
n := max;
fs.Write(Buffer, n);
max := max - n;
end;
FlushFileBuffers(fs.Handle);
end;
finally
fs.Free;
end;
Deletefile(FileName);
end;
delete a file permanently?
Answer:
If you want to get rid of a file normally you just delete it.
But someone else can undelete it if the file hasn't been wiped correctly.
For security purposes, to insure that certain files are permanently
gone, the WipeFile procedure writes over the data in the file with
random characters and then erases it.
Wenn man eine Datei nicht mehr braucht, l�scht man sie einfach.
Aber jemand anders kann die Datei wieder herstellen, wenn sie
nicht "richtig" gel�scht wurde.
Aus Sicherheitsgr�nden, um sicherzustellen, dass eine Datei permanent
gel�scht wird, �berschreibt die WipeFile Prozedur eine Datei mit
Zufalls-Zeichen und l�scht sie anschliessend.
}
procedure WipeFile(FileName: string);
var
buffer: array [0..4095] of Byte;
max, n: LongInt;
i: Integer;
fs: TFileStream;
procedure RandomizeBuffer;
var
i: Integer;
begin
for i := Low(buffer) to High(buffer) do
buffer[i] := Random(256);
end;
begin
fs := TFilestream.Create(FileName, fmOpenReadWrite or fmShareExclusive);
try
for i := 1 to 3 do
begin
RandomizeBuffer;
max := fs.Size;
fs.Position := 0;
while max > 0 do
begin
if max > SizeOf(buffer) then
n := SizeOf(buffer)
else
n := max;
fs.Write(Buffer, n);
max := max - n;
end;
FlushFileBuffers(fs.Handle);
end;
finally
fs.Free;
end;
Deletefile(FileName);
end;
2010. június 13., vasárnap
Split and freeze rows and columns in Excel
Problem/Question/Abstract:
How to split and freeze rows and columns in Excel
Answer:
{ ... }
ExcelApplication1.Connect;
ExcelApplication1.Workbooks.Add(Null, 0);
ExcelApplication1.ActiveWindow.SplitColumn := 5;
ExcelApplication1.ActiveWindow.SplitRow := 10;
ExcelApplication1.ActiveWindow.FreezePanes := True;
{ ...}
2010. június 12., szombat
How to create a TRichEdit with a tiled background
Problem/Question/Abstract:
Does anyone know how to use a tiled picture as the background for a TRichEdit control?
Answer:
For a standard TRichEdit there seems to be no way to make it transparent or paint its background with a tiled bitmap. But there is a workaround if you're using the Win2000 operating system. There you can make your control transparent by setting the WS_EX_LAYERED constant to the window extended style and then calling the SetLayeredWindowAttributes Win API function.
The example listed below is a TRichEdit control with a DrawStyle property. Depending on its value, the control will have a transparent background or will draw itself with an alpha transparency.
{ ... }
type
TDrawStyle = (ds_Transparent, ds_NotDistinctly, dsNormal);
MyTransparentRichEdit = class(TRichEdit)
protected
FDrawStyle: TDrawStyle;
procedure CreateParams(var Params: TCreateParams); override;
procedure CreateWnd; override;
procedure SetDrawStyle(AValue: TDrawStyle);
public
constructor Create(AOwner: TComponent); override;
published
property DrawStyle: TDrawStyle read FDrawStyle write SetDrawStyle;
end;
{ ... }
constructor MyTransparentRichEdit.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
FDrawStyle := dsNormal;
end;
procedure MyTransparentRichEdit.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
if not (csDesigning in ComponentState) then
begin
Params.Style := Params.Style or WS_POPUP;
Params.ExStyle := Params.ExStyle + WS_EX_LAYERED;
end;
end;
procedure MyTransparentRichEdit.CreateWnd;
var
XPoint: TPoint;
begin
if not (csDesigning in ComponentState) then
begin
XPoint := TWinControl(Owner).ClientToScreen(POINT(Left, Top));
Left := XPoint.X;
Top := XPoint.Y;
end;
inherited CreateWnd;
case FDrawStyle of
ds_Transparent:
SetLayeredWindowAttributes(Handle, ColorToRGB(Color), 255, LWA_COLORKEY);
ds_NotDistinctly:
SetLayeredWindowAttributes(Handle, 0, 150, LWA_ALPHA);
end;
end;
procedure MyTransparentRichEdit.SetDrawStyle(AValue: TDrawStyle);
begin
if FDrawStyle <> AValue then
begin
FDrawStyle := AValue;
RecreateWnd;
end;
end;
2010. június 11., péntek
How to determine if a TListBox has a scrollbar
Problem/Question/Abstract:
I'm trying to ensure that a TListBox is wide enough to hold the items in it. However, I see no method for determining if a scroll bar exists, and if so how wide it is.
Answer:
procedure TForm1.FormShow(Sender: TObject);
var
LBStyle: Longint;
begin
LBStyle := GetWindowLong(ListBox1.Handle, GWL_STYLE);
if (LBStyle and WS_VSCROLL) <> 0 then
ShowMessage(' ScrollBar visible, its width is ' + IntToStr(GetSystemMetrics(SM_CYVSCROLL))
+ ' pixels ')
else
ShowMessage('ScrollBar not visible');
end;
2010. június 10., csütörtök
Adjust the column width in a TStringGrid to fit the widest text in a cell
Problem/Question/Abstract:
In the column of a TStringGrid, how do I assign as ColWidth the widest text inside its corresponding cells?
Answer:
You measure the text using the TStringGrid's canvas:
procedure SetGridColumnWidths(Grid: TStringGrid; const Columns: array of Integer);
const
DEFBORDER = 8;
var
max, temp, i, n: Integer;
begin
with Grid do
begin
Canvas.Font := Font;
for n := Low(Columns) to High(Columns) do
begin
max := 0;
for i := 0 to RowCount - 1 do
begin
temp := Canvas.TextWidth(Cells[Columns[n], i]) + DEFBORDER;
if temp > max then
max := temp;
end;
if max > 0 then
ColWidths[Columns[n]] := max;
end;
end;
end;
Use this like:
SetGridColumnWidths(stringgrid1, [1, 4]);
This would adjust the widths of columns 1 and 4 to fit the contents.
2010. június 9., szerda
How to load HTML pages from a resource file into a TWebBrowser
Problem/Question/Abstract:
How I can save HTML pages, including *.jpg and *.gif images, into a resource file? Finally all of this stuff will be inside one compiled application. The canvas for my HTML pages will be a TWebBrowser.
Answer:
MY_HTMLFILE.RC contents: MY_HTMLFILE 23 "my_htmlfile.html"
Compiled using BRCC32 to MY_HTMLFILE.RES.
Do not ask me what the 23 resource identifier is, I got it from the MS web site.
To load into a TWebBrowser from a resource:
procedure TForm1.Button1Click(Sender: TObject);
var
Flags, TargetFrameName, PostData, Headers: OleVariant;
begin
WebBrowser1.Navigate('res://' + Application.ExeName + '/MY_HTMLFILE', Flags,
TargetFrameName, PostData, Headers);
end;
2010. június 8., kedd
Highlight an entire row in a TStringGrid
Problem/Question/Abstract:
Using Delphi 4 and a TStringGrid component: How do I highlight the entire row in the grid where the cursor is currently? In other words, if the user clicks their mouse cursor on row 3, I want the entire row 3 to be highlighted pale yellow. When they then move the cursor to row 6, I want row 3 to revert back to white, and then have the entire row 6 turn pale yellow. Please note that I must be able to also edit the contents of any cell in the highlighted row.
Answer:
You can achieve this fairly easily with a combination of handlers for the OnSelectCell and OndrawCell events of the grid:
type
TGridCracker = class(TStringGrid);
{required to access protected method InvalidateRow}
procedure TForm1.StringGrid1SelectCell(Sender: TObject; ACol, ARow: Integer;
var CanSelect: Boolean);
begin
with TGridCracker(Sender as TStringGrid) do
begin
InvalidateRow(Row);
InvalidateRow(aRow);
end;
end;
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
var
grid: TStringGrid;
begin
if gdFixed in State then
Exit;
grid := Sender as TStringGrid;
if grid.Row = aRow then
begin
with Grid.Canvas.Brush do
begin
Color := $C0FFFF; {pale yellow}
Style := bsSolid;
end;
grid.Canvas.FillRect(Rect);
grid.Canvas.Font.Color := clBlack;
grid.Canvas.TextRect(Rect, Rect.Left + 2, Rect.Top + 2, grid.Cells[acol, arow]);
Grid.Canvas.Brush := grid.Brush;
end;
end;
2010. június 7., hétfő
Check if the mouse is over a tab of a TTabControl
Problem/Question/Abstract:
I try to determine if the mouse is just over a tab of a TTabControl using GetHitTestInfoAt(). This returns htOnItem if the mouse is over the tab and if it's not. How do I have to approach this issue?
Answer:
Probably, the problem is in the routine which calls GetHitTestInfoAt, since I've tried to check this method and all seemed to work fine. I was calling it in the form's and tabcontrols's OnMouseMove events:
procedure TForm1.TabControl1MouseMove(Sender: TObject; Shift: TShiftState; X, Y:
Integer);
var
XHitTests: THitTests;
begin
XHitTests := TabControl1.GetHitTestInfoAt(X, Y);
if htOnItem in XHitTests then
ShowMessage('OnItem');
end;
Also, you can try to call Win API macro TabCtrl_HitTest directly, in order to determine which tab, if any, is over the cursor. Here's an example:
procedure TForm1.TabControl1MouseMove(Sender: TObject; Shift: TShiftState; X, Y:
Integer);
var
XHitTestInfo: TTCHitTestInfo;
XIndex: integer;
begin
XHitTestInfo.pt := POINT(X, Y);
XIndex := TabCtrl_HitTest(TabControl1.Handle, @XHitTestInfo);
if XHitTestInfo.flags in [TCHT_ONITEM] then
ShowMessage('OnItem ' + TabControl1.Tabs[XIndex])
else if XHitTestInfo.flags in [TCHT_ONITEMICON] then
ShowMessage('OnIcon ' + TabControl1.Tabs[XIndex])
else if XHitTestInfo.flags in [TCHT_ONITEMLABEL] then
ShowMessage('OnLabel ' + TabControl1.Tabs[XIndex]);
end;
2010. június 6., vasárnap
Adding an AVI in your EXE File
Problem/Question/Abstract:
Adding an AVI in your EXE File
Answer:
In Notepad type or some other simple text editor type:
MyAvi AVI "some.avi"
or
100 AVI "some.avi"
depending on how you want to reference the identifier. You will want to know whether it is referenced by a resource name or a resource ID when you write the code to play the AVI.
Save the file with a .RC extension
You will be using the Animate Component to play the file, therefore the same rules apply, like no sound can be with the AVI.
Use Borland's Resource Compiler: BRCC32.EXE to convert the file to a .RES file. At the dos prompt type the following:
brcc32 myfile.rc
This is some code to play an animation using the Resource Name:
Animate.ResHandle := 0;
Animate.ResName := 'MyAvi';
Animate.Active := True;
To stop an animation, call the Stop method.
Place the following code to add your resource file into your executable.
{$R MYFILE.RES}
A sample file is listed below of how this would work correctly:
unit AviResU;
interface
uses
Forms, ComCtrls, StdCtrls, Classes, Controls;
type
TForm1 = class(TForm)
PlayBtn: TButton;
Animate: TAnimate;
StopBtn: TButton;
procedure PlayBtnClick(Sender: TObject);
procedure StopBtnClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
{$R AVIRESRC.RES}
procedure TForm1.PlayBtnClick(Sender: TObject);
begin
Animate.ResHandle := 0;
Animate.ResName := 'TurboGuy';
Animate.Active := True;
PlayBtn.Enabled := False;
StopBtn.Enabled := True;
end;
procedure TForm1.StopBtnClick(Sender: TObject);
begin
Animate.Stop;
PlayBtn.Enabled := True;
StopBtn.Enabled := False;
end;
end.
2010. június 5., szombat
How to change the font size when printing a TRichEdit
Problem/Question/Abstract:
I am using a TRichEdit in my application that shows text in a 10 point character size. I would like to print it using a size of 13. How can I change the printing size?
Answer:
If the whole contents of the TRichEdit use the same font size the simplest method would be to simply assign 13 to richedit.font.size, print the bugger and revert the size to 10. One could do this with a hidden TRichEdit control that contains a copy of the text, if the user should not be aware of what is going on.
This method will break as soon as the rich edit control contains formatted text in several font sizes. In this case one can scale the printer canvas by using a custom mapping mode. Unfortunately this means one has to do the printing manually, since the mapping mode can only be set after a BeginDoc and richedit.print will then fail. Here is an example that will print a TRichEdit 1.3 times the original size. It assumes all text will fit onto the first page. If several pages need to be printed the scaling of the printer canvas needs to be redone after each NewPage, since that resets the printer canvas to the default mapping mode!
procedure TForm1.Button1Click(Sender: TObject);
var
r: TRect;
richedit_outputarea: TRect;
printresX, printresY: Integer;
fmtRange: TFormatRange;
begin
printer.begindoc;
try
r := Rect(1000 div 13, 1000 div 13, Round((Printer.PageWidth - 100) / 1.3),
Round((Printer.Pageheight - 100) / 1.3));
SetMapMode(printer.canvas.handle, MM_ANISOTROPIC);
SetWindowExtEx(printer.canvas.handle, GetDeviceCaps(printer.canvas.handle,
LOGPIXELSX), GetDeviceCaps(printer.canvas.handle, LOGPIXELSY), nil);
SetViewportExtEx(printer.canvas.handle, Round(GetDeviceCaps(printer.canvas.handle,
LOGPIXELSX) * 1.3), Round(GetDeviceCaps(printer.canvas.handle,
LOGPIXELSY) * 1.3), nil);
with Printer.Canvas do
begin
printresX := Round(GetDeviceCaps(handle, LOGPIXELSX) / 1.3);
printresY := Round(GetDeviceCaps(handle, LOGPIXELSY) / 1.3);
{Define a rectangle for the rich edit text. The height is set to the maximum.
But we need to convert from device units to twips, 1 twip = 1/1440 inch or 1/20
point.}
richedit_outputarea := Rect(r.left * 1440 div printresX, r.top * 1440 div
printresY, r.right * 1440 div printresX, r.bottom * 1440 div printresY);
{Tell rich edit to format its text to the printer. First set up data record
for message:}
fmtRange.hDC := Handle; {printer handle}
fmtRange.hdcTarget := Handle; {ditto}
fmtRange.rc := richedit_outputarea;
fmtRange.rcPage := Rect(0, 0, Printer.PageWidth * 14400 div 13 div printresX,
Printer.PageHeight * 14400 div 13 div printresY);
fmtRange.chrg.cpMin := 0;
fmtRange.chrg.cpMax := richedit1.GetTextLen - 1;
{Format the text}
richedit1.Perform(EM_FORMATRANGE, 1, Longint(@fmtRange));
{Free cached information}
richedit1.Perform(EM_FORMATRANGE, 0, 0);
end;
finally
printer.enddoc;
end;
end;
The richedit1.perform( EM_FORMATRANGE call returns the index of the last character that could be fitted into the passed fmtrange.rc, + 1. So if multiple pages are required one repeats with fmtrange.chrg.cpMin set to this value, until all characters have been printed. Note that the rich edit control strips blanks and linebreaks off the end of the text so the number of characters to output may be smaller than richedit.gettextLen .
2010. június 4., péntek
How to detect a mouse click in a polygon region
Problem/Question/Abstract:
I create several regions using CreatePolygonRgn function, passing an array of several points (ex. 4). After that, under some condition, I have to test if the user has clicked inside that region using PtInRegion. Now there are some problems: Sometimes CreatePolygonRgn returns 0 (no region created). Why is that? Under any circumstances I can not get any hits when passing points to PtInRegion.
Answer:
Here is a sample using a dynamic TPoint array:
procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton;
Shift: TShiftState; X, Y: Integer);
type
PPointArray = ^TPointArray;
TPointArray = array[0..MaxInt div SizeOf(TPoint) - 1] of TPoint;
var
Rgn: HRGN;
P: PPointArray;
begin
GetMem(P, SizeOf(TPoint) * 3);
P[0] := Point(0, 0);
P[1] := Point(50, 100);
P[2] := Point(100, 50);
Rgn := CreatePolygonRgn(P[0], 3, WINDING);
Canvas.Brush.Color := clRed;
FillRgn(Canvas.Handle, Rgn, Canvas.Brush.Handle);
if PtInRegion(Rgn, X, Y) then
Beep;
DeleteObject(Rgn);
FreeMem(P);
end;
2010. június 3., csütörtök
More lines in a hint
Problem/Question/Abstract:
More lines in a hint
Answer:
If you want to display more than a one line in the hint of a component, for example of Button1, set it's property ShowHint to true. In the Object Inspector, don't put anything in Button1's hint property. In the FormCreate event handler of the form that contains Button1, add this line:
Button1.Hint := 'First line' + Chr(13) + 'Second line';
2010. június 2., szerda
How to jump to the contents page of a help file
Problem/Question/Abstract:
How to jump to the contents page of a help file
Answer:
Application.HelpCommand(HELP_CONTENTS, 0);
2010. június 1., kedd
How to paint a border around text in a TRichEdit
Problem/Question/Abstract:
How to paint a border around text in a TRichEdit
Answer:
procedure TForm1.Button1Click(Sender: TObject);
var
r: TRect;
begin
richedit1.perform(EM_GETRECT, 0, lparam(@r));
Inflaterect(r, -5, -5);
richedit1.perform(EM_SETRECT, 0, lparam(@r));
end;
Feliratkozás:
Bejegyzések (Atom)