Problem/Question/Abstract:
How can I adapt DateTime values for different SQL-Server formats?
Answer:
// Torry's Delphi Tips
// Author Sven Sohnel
// Listed 27.10.2002
{
Wenn man mit verschiedensprachigen (MS-)SQL-Servern arbeitet,
hat man ab und an das Problem, Datumswerte in ein f�r den
jeweiligen Server verst�ndliches Format umzuwandeln.
}
{
If you work with different (MS-)SQL-Server, you have sometimes the
problem what the date value is in the correct format.
}
function TForm1.GetSQLDateTimeFormat(UDL: string): string;
begin
Screen.Cursor := crSQLWait;
if ADOConnection1.Connected then
ADOConnection1.Close;
ADOConnection1.ConnectionString := 'FILE NAME=' + UDL;
ADOQuery1.SQL.Clear;
ADOQuery1.SQL.Add('sp_helplanguage @@LANGUAGE');
Application.ProcessMessages;
try
try
ADOQuery1.Open;
except
on E: Exception do
MessageBox(Handle,
PChar('Die Abfrage konnte nicht ge�ffnet werden:' + #13#10 + #13#10 +
E.Message),
PChar('Fehler!'), 16);
end;
if (ADOQuery1.Active) and (ADOQuery1.RecordCount > 0) then
Result := ADOQuery1.FieldByName('dateformat').AsString;
finally
Screen.Cursor := crDefault;
end;
end;
function DateTimeToSQLDateTimeString(Data: TDateTime; Format: string;
OnlyDate: Boolean = True): string;
var
y, m, d, h, mm, s, ms: Word;
begin
DecodeDate(Data, y, m, d);
DecodeTime(Data, h, mm, s, ms);
if Format = 'dmy' then
Result := IntToStr(d) + '-' + IntToStr(m) + '-' + IntToStr(y)
else if Format = 'ymd' then
Result := IntToStr(y) + '-' + IntToStr(m) + '-' + IntToStr(d)
else if Format = 'ydm' then
Result := IntToStr(y) + '-' + IntToStr(d) + '-' + IntToStr(m)
else if Format = 'myd' then
Result := IntToStr(m) + '-' + IntToStr(y) + '-' + IntToStr(d)
else if Format = 'dym' then
Result := IntToStr(d) + '-' + IntToStr(y) + '-' + IntToStr(m)
else
Result := IntToStr(m) + '-' + IntToStr(d) + '-' + IntToStr(y); //mdy: ; //US
if not OnlyDate then
Result := Result + ' ' + IntToStr(h) + ':' + IntToStr(mm) + ':' + IntToStr(s);
end;
//Example:
//Beispiel:
procedure ConvertSQLDateTime;
begin
ShowMessage(DateTimeToSQLDateTimeString(now, GetSQLLanguage('C:\DBEngl.udl')));
end;
2006. december 31., vasárnap
2006. december 30., szombat
Patch binary files
Problem/Question/Abstract:
How to patch binary files?
Answer:
Replaces a string in a file with new string.
procedure TForm1.Button1Click(Sender: TObject);
var
f: file;
l: Longint;
FileName, oldstring, newstring, s: string;
begin
oldstring := 'old string';
newstring := 'new string';
FileName := 'c:\YourFileName.xyz';
s := oldstring;
AssignFile(f, FileName);
Reset(f, 1);
for l := 0 to FileSize(f) - Length(oldstring) - 1 do
begin
Application.ProcessMessages;
Seek(f, l);
BlockRead(f, oldstring[1], Length(oldstring));
if oldstring = s then
begin
Seek(f, l);
BlockWrite(f, newstring[1], Length(newstring));
ShowMessage('String successfully replaced!');
end;
Application.ProcessMessages;
end;
CloseFile(f);
end;
How to patch binary files?
Answer:
Replaces a string in a file with new string.
procedure TForm1.Button1Click(Sender: TObject);
var
f: file;
l: Longint;
FileName, oldstring, newstring, s: string;
begin
oldstring := 'old string';
newstring := 'new string';
FileName := 'c:\YourFileName.xyz';
s := oldstring;
AssignFile(f, FileName);
Reset(f, 1);
for l := 0 to FileSize(f) - Length(oldstring) - 1 do
begin
Application.ProcessMessages;
Seek(f, l);
BlockRead(f, oldstring[1], Length(oldstring));
if oldstring = s then
begin
Seek(f, l);
BlockWrite(f, newstring[1], Length(newstring));
ShowMessage('String successfully replaced!');
end;
Application.ProcessMessages;
end;
CloseFile(f);
end;
2006. december 29., péntek
Put the monitor into standby mode
Problem/Question/Abstract:
How to put the monitor into standby mode
Answer:
To put the screen into standby mode, use the SendMessage command to send a system command to the API. Send it the appplication handle, message type and the message values. Here we send SC_MonitorPower command type with the value of 1 to turn on screen standy mode. PostMessage can be used with the same parameters. The only difference is that the message is put into a queue. SendMessage causes direct action.
SendMessage(Application.Handle, wm_SysCommand, SC_MonitorPower, 1);
To manually turn the screen back on, send the same command but with the value of -1. Otherwise standby mode will be turned off by the usual actions associated with screen revival.
SendMessage(Application.Handle, wm_SysCommand, SC_MonitorPower, -1);
How to put the monitor into standby mode
Answer:
To put the screen into standby mode, use the SendMessage command to send a system command to the API. Send it the appplication handle, message type and the message values. Here we send SC_MonitorPower command type with the value of 1 to turn on screen standy mode. PostMessage can be used with the same parameters. The only difference is that the message is put into a queue. SendMessage causes direct action.
SendMessage(Application.Handle, wm_SysCommand, SC_MonitorPower, 1);
To manually turn the screen back on, send the same command but with the value of -1. Otherwise standby mode will be turned off by the usual actions associated with screen revival.
SendMessage(Application.Handle, wm_SysCommand, SC_MonitorPower, -1);
2006. december 28., csütörtök
How can I read a BlobStream with TADOQuery from an AccessDB?
Problem/Question/Abstract:
How can I read a BlobStream with TADOQuery from an AccessDB?
Answer:
// Torry's Delphi Tips - Database
// Author Peppa
// Listed 14.05.2003
function GetBlobStream(Query: TADOQuery): TMemoryStream;
begin
result := TMemoryStream.Create;
// You must connect to AccessDB first.
// See: Query.Connection, TADOConection or Query.ConnectString
// Send SQL command
Query.Active := False;
Query.SQL.Clear;
// data is my row and email the table
Query.SQL.Append('SELECT data FROM email WHERE id=1');
Query.Active := True;
Result.LoadFromStream(Query.CreateBlobStream(Query.FieldByName('Data'), bmRead));
end;
How can I read a BlobStream with TADOQuery from an AccessDB?
Answer:
// Torry's Delphi Tips - Database
// Author Peppa
// Listed 14.05.2003
function GetBlobStream(Query: TADOQuery): TMemoryStream;
begin
result := TMemoryStream.Create;
// You must connect to AccessDB first.
// See: Query.Connection, TADOConection or Query.ConnectString
// Send SQL command
Query.Active := False;
Query.SQL.Clear;
// data is my row and email the table
Query.SQL.Append('SELECT data FROM email WHERE id=1');
Query.Active := True;
Result.LoadFromStream(Query.CreateBlobStream(Query.FieldByName('Data'), bmRead));
end;
2006. december 27., szerda
Using the Math Unit
Problem/Question/Abstract:
I've heard of the Math unit that's included with the Developer level version of Delphi. How do I use it?
Answer:
An Unsung Hero?
If you haven't heard of the Math unit, you're not alone. It's one of those units that's kind of buried in the myriad directories under the Delphi directory. Also, it's only included in the Delphi 2.0 Developer version and above. For those of you who have Delphi 2.0 Developer, you can find the source code file in the \\Borland\Delphi 2.0\Source\RTL\SYS directory.
This unit is one of those things I've heard a lot of people mention in the past but unfortunately, I haven't seen many examples of using it. I'm not sure whether it's because developers don't use it much or don't know about it. In any case, it's a shame because what the Math unit has to offer could be helpful to people needing to use mathematical functions in their code.
Not only are they helpful by their mere existence, but several of the member functions are written in Assembly language that is optimized for the Pentium FPU, so they're incredibly fast and efficient. For example, the procedure SinCos, which is one of the procedures written in Assembly, will produce the Sin and Cos simultaneously of an any angle faster than if you called the Sin and Cos functions individually. I've seen it at work and it's amazing.
The Math unit includes several categories of mathematical functions you can incorporate into your code. These include:
Trigonometric, Hyperbolic and Angle Conversion Functions
Logorithmic Functions
Exponential Functions
Some Miscellaneous Computing Functions
Several Statistical Functions
The Standard Set of Quattro Pro Financial Functions
Mind you, not all of the functions are coded in Assembly, but the mere fact that they've already been written means you don't have to, so that's a real time saver. Below are the function prototypes for the unit, so you can see what's in the file:
{-----------------------------------------------------------------------
Copyright (c) Borland International.
Most of the following trig and log routines map directly to Intel 80387 FPU
floating point machine instructions. Input domains, output ranges, and
error handling are determined largely by the FPU hardware.
Routines coded in assembler favor the Pentium FPU pipeline architecture.
-----------------------------------------------------------------------}
{ Trigonometric functions }
function ArcCos(X: Extended): Extended; { IN: |X| <= 1 OUT: [0..PI] radians }
function ArcSin(X: Extended): Extended; { IN: |X| <= 1 OUT: [-PI/2..PI/2] radians }
{ ArcTan2 calculates ArcTan(Y/X), and returns an angle in the correct quadrant.
IN: |Y| < 2^64, |X| < 2^64, X <> 0 OUT: [-PI..PI] radians }
function ArcTan2(Y, X: Extended): Extended;
{ SinCos is 2x faster than calling Sin and Cos separately for the same angle }
procedure SinCos(Theta: Extended; var Sin, Cos: Extended) register;
function Tan(X: Extended): Extended;
function Cotan(X: Extended): Extended; { 1 / tan(X), X <> 0 }
function Hypot(X, Y: Extended): Extended; { Sqrt(X**2 + Y**2) }
{ Angle unit conversion routines }
function DegToRad(Degrees: Extended): Extended; { Radians := Degrees * PI / 180}
function RadToDeg(Radians: Extended): Extended; { Degrees := Radians * 180 / PI }
function GradToRad(Grads: Extended): Extended; { Radians := Grads * PI / 200 }
function RadToGrad(Radians: Extended): Extended; { Grads := Radians * 200 / PI }
function CycleToRad(Cycles: Extended): Extended; { Radians := Cycles * 2PI }
function RadToCycle(Radians: Extended): Extended;{ Cycles := Radians / 2PI }
{ Hyperbolic functions and inverses }
function Cosh(X: Extended): Extended;
function Sinh(X: Extended): Extended;
function Tanh(X: Extended): Extended;
function ArcCosh(X: Extended): Extended; { IN: X >= 1 }
function ArcSinh(X: Extended): Extended;
function ArcTanh(X: Extended): Extended; { IN: |X| <= 1 }
{ Logorithmic functions }
function LnXP1(X: Extended): Extended; { Ln(X + 1), accurate for X near zero }
function Log10(X: Extended): Extended; { Log base 10 of X}
function Log2(X: Extended): Extended; { Log base 2 of X }
function LogN(Base, X: Extended): Extended; { Log base N of X }
{ Exponential functions }
{ IntPower: Raise base to an integral power. Fast. }
function IntPower(Base: Extended; Exponent: Integer): Extended register;
{ Power: Raise base to any power.
For fractional exponents, or exponents > MaxInt, base must be > 0. }
function Power(Base, Exponent: Extended): Extended;
{ Miscellaneous Routines }
{ Frexp: Separates the mantissa and exponent of X. }
procedure Frexp(X: Extended; var Mantissa: Extended; var Exponent: Integer) register;
{ Ldexp: returns X*2**P }
function Ldexp(X: Extended; P: Integer): Extended register;
{ Ceil: Smallest integer >= X, |X| < MaxInt }
function Ceil(X: Extended):Integer;
{ Floor: Largest integer <= X, |X| < MaxInt }
function Floor(X: Extended): Integer;
{ Poly: Evaluates a uniform polynomial of one variable at value X.
The coefficients are ordered in increasing powers of X:
Coefficients[0] + Coefficients[1]*X + ... + Coefficients[N]*(X**N) }
function Poly(X: Extended; const Coefficients: array of Double): Extended;
{-----------------------------------------------------------------------
Statistical functions.
Common commercial spreadsheet macro names for these statistical and
financial functions are given in the comments preceding each function.
-----------------------------------------------------------------------}
{ Mean: Arithmetic average of values. (AVG): SUM / N }
function Mean(const Data: array of Double): Extended;
{ Sum: Sum of values. (SUM) }
function Sum(const Data: array of Double): Extended register;
function SumOfSquares(const Data: array of Double): Extended;
procedure SumsAndSquares(const Data: array of Double;
var Sum, SumOfSquares: Extended) register;
{ MinValue: Returns the smallest signed value in the data array (MIN) }
function MinValue(const Data: array of Double): Double;
{ MaxValue: Returns the largest signed value in the data array (MAX) }
function MaxValue(const Data: array of Double): Double;
{ Standard Deviation (STD): Sqrt(Variance). aka Sample Standard Deviation }
function StdDev(const Data: array of Double): Extended;
{ MeanAndStdDev calculates Mean and StdDev in one pass, which is 2x faster than
calculating them separately. Less accurate when the mean is very large
(> 10e7) or the variance is very small. }
procedure MeanAndStdDev(const Data: array of Double; var Mean, StdDev: Extended);
{ Population Standard Deviation (STDP): Sqrt(PopnVariance).
Used in some business and financial calculations. }
function PopnStdDev(const Data: array of Double): Extended;
{ Variance (VARS): TotalVariance / (N-1). aka Sample Variance }
function Variance(const Data: array of Double): Extended;
{ Population Variance (VAR or VARP): TotalVariance/ N }
function PopnVariance(const Data: array of Double): Extended;
{ Total Variance: SUM(i=1,N)[(X(i) - Mean)**2] }
function TotalVariance(const Data: array of Double): Extended;
{ Norm: The Euclidean L2-norm. Sqrt(SumOfSquares) }
function Norm(const Data: array of Double): Extended;
{ MomentSkewKurtosis: Calculates the core factors of statistical analysis:
the first four moments plus the coefficients of skewness and kurtosis.
M1 is the Mean. M2 is the Variance.
Skew reflects symmetry of distribution: M3 / (M2**(3/2))
Kurtosis reflects flatness of distribution: M4 / Sqr(M2) }
procedure MomentSkewKurtosis(const Data: array of Double;
var M1, M2, M3, M4, Skew, Kurtosis: Extended);
{ RandG produces random numbers with Gaussian distribution about the mean.
Useful for simulating data with sampling errors. }
function RandG(Mean, StdDev: Extended): Extended;
{-----------------------------------------------------------------------
Financial functions. Standard set from Quattro Pro.
Parameter conventions:
From the point of view of A, amounts received by A are positive and
amounts disbursed by A are negative (e.g. a borrower's loan repayments
are regarded by the borrower as negative).
Interest rates are per payment period. 11% annual percentage rate on a
loan with 12 payments per year would be (11 / 100) / 12 = 0.00916667
-----------------------------------------------------------------------}
type
TPaymentTime = (ptEndOfPeriod, ptStartOfPeriod);
{ Double Declining Balance (DDB) }
function DoubleDecliningBalance(Cost, Salvage: Extended;
Life, Period: Integer): Extended;
{ Future Value (FVAL) }
function FutureValue(Rate: Extended; NPeriods: Integer; Payment, PresentValue:
Extended; PaymentTime: TPaymentTime): Extended;
{ Interest Payment (IPAYMT) }
function InterestPayment(Rate: Extended; Period, NPeriods: Integer; PresentValue,
FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
{ Interest Rate (IRATE) }
function InterestRate(NPeriods: Integer;
Payment, PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
{ Internal Rate of Return. (IRR) Needs array of cash flows. }
function InternalRateOfReturn(Guess: Extended;
const CashFlows: array of Double): Extended;
{ Number of Periods (NPER) }
function NumberOfPeriods(Rate, Payment, PresentValue, FutureValue: Extended;
PaymentTime: TPaymentTime): Extended;
{ Net Present Value. (NPV) Needs array of cash flows. }
function NetPresentValue(Rate: Extended; const CashFlows: array of Double;
PaymentTime: TPaymentTime): Extended;
{ Payment (PAYMT) }
function Payment(Rate: Extended; NPeriods: Integer;
PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
{ Period Payment (PPAYMT) }
function PeriodPayment(Rate: Extended; Period, NPeriods: Integer;
PresentValue, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
{ Present Value (PVAL) }
function PresentValue(Rate: Extended; NPeriods: Integer;
Payment, FutureValue: Extended; PaymentTime: TPaymentTime): Extended;
{ Straight Line depreciation (SLN) }
function SLNDepreciation(Cost, Salvage: Extended; Life: Integer): Extended;
{ Sum-of-Years-Digits depreciation (SYD) }
function SYDDepreciation(Cost, Salvage: Extended; Life, Period: Integer): Extended;
Clearing Things Up: Usage
Whew! That's a lot of information to digest! I listed it here to impress upon you just how much there is. For those of you creating financial applications, the financial functions will come in handy (I sure wish I had these functions available when I was writing financial software).
Listing the functions is one thing - actually using them is another. As you can see, most of the input parameters require an Extended numeric type, which is a 10-byte number ranging from 3.4 * 10e-4932 to 1.1 * 10e4932 in scientific notation. In other words, you can have incredibly huge numbers as input values.
Take a moment to look at the statistical functions. Notice anything odd about almost all of the functions' input parameters? Most of them take a constant open array of double! This implies you can pass any size array as an input parameter, but it must be passed as a constant array, which means that you have to pass the array in the form of (1, 2, 3, 4, 5, 6 ..). That's not so difficult with small known sets of numbers; just hard code them in. But arrays in Pascal are typically of the variable type, where you define a finite number of elements, then fill in the element values. This poses a bit of a problem. Fortunately, there's a solution.
Buried in the System unit is a function called Slice: function Slice(var A: array; Count: Integer): array; Essentially, what Slice does is take a certain number of elements from an array, starting at the beginning, and passes the slice of the array as an open array parameter, fooling the compiler into thinking a constant array is being passed. This means that you can pass the entire array or smaller subset. In fact, Slice can only be used within the context of being passed as an open array parameter. Using it outside of this context will create a compiler error. What a convenient function! So, we can define a variable type array as we're used to in Delphi, fill it up, put it into Slice, which is then used in one of the functions. For example: MyExtendedNumber := Mean(Slice(MyArray, NumElementsPassed));
At this point, you're probably thinking this is pretty incredible stuff. But there's one thing that still bothers me about it: The place where the statistical functions would be most useful is on columnar calculations on tables. Unfortunately, you never know how many records are in a table until runtime. Granted, depending upon the amount of RAM in your system, you could make an incredibly large array of let's say 100K elements, fill it up to the record count of your table, then apply Slice to grab only those you need. However, that's pretty inefficient. Also, in my immediate experience, many of my tables have well over 100K records, which means I'd have to hard code an even greater upper limit. But there will also be tables that have far fewer records than 100K - more like 10K. So the idea then is to strike a balance. No thanks!
Doing the DynArray Thing
So where am I if I can't accept defining a huge array, or making some sort of size compromise? I guess I need to create a variable sized array whose size can be defined at runtime.
Wait a minute! You're not supposed to be able to do that in Delphi!
You can, but it takes some pointers to be able to pull it off. For an in-depth discussion of the technique, I'm going to point you to an article on the enquiry site entitled Runtime Resizeable Arrays, which will show you how to create an array that has an element count you don't know about until runtime. I highly suggest reading the article before continuing, if you're not familiar with the technique.
What gives you the ability to create a dynamic array is a function like the following (this in the article):
type
TResizeArr = array[0..0] of string;
PResizeArr = ^TResizeArr;
...
{============================================================================
Procedure which defines the dynarray. Note that the THandle and Pointer to
the array are passed by reference. This is so that they may defined outside
the scope of this proc.
============================================================================}
procedure DefineDynArray(var h: THandle; {Handle to mem pointer}
NumElements: LongInt; {Number of items in array}
var PArr: PResizeArr); {Pointer to array struct}
begin
{Allocate Windows Global Heap memory}
h := GlobalAlloc(GMEM_FIXED, NumElements * sizeof(TResizeArr));
PArr := GlobalLock(h);
end;
Note that you can just as easily replace the array of String with an array of Double. In any case, the gist of what the procedure does is allocate memory for the number of elements that you want to have in your array (TResizeArr), then locks that area of the heap and assigns it to an array pointer (PResizeArr). To load values into the array, you de-reference the pointer as follows: MyPointerArray^[0] := 1234.1234;
To pass the entire array into the Mean function as above, all we have to do is de-reference the entire array as follows: MyExtendedNumber := Mean(Slice(MyPointerArray^, NumElementsPassed));
Putting It All Together
I mentioned above that the best place to employ the statistical functions is in performing statistics on columnar data in a table. The unit code below provides a simple example of loading a DynArray with columnar data, then performing the Mean function on the loaded array. Note the table that I used had about 80K records in it.
unit parrmain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
DB, DBTables, ComCtrls, Math, StdCtrls, ds_stats;
type
TResizeArr = array[0..0] of Double;
PResizeArr = ^TResizeArr;
TForm1 = class(TForm)
StatusBar1: TStatusBar;
Button1: TButton;
Table1: TTable;
procedure Button1Click(Sender: TObject);
private
{ Private declarations }
PA: PResizeArr;
Hndl: THandle;
procedure DefineDynArray(var H: THandle;
NumElem: LongInt;
var PArr: PResizeArr);
procedure FillArray;
procedure SaveScreen;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.DefineDynArray(var H: THandle;
NumElem: LongInt;
var PArr: PResizeArr);
begin
H := GlobalAlloc(GMEM_FIXED, NumElem * SizeOf(TResizeArr));
PArr := GlobalLock(H);
end;
procedure TForm1.LoadArray;
var
tbl: TTable;
recs: LongInt;
begin
tbl := TTable.Create(Application); //Create a TTable instance
with tbl do
begin //and set properties
Active := False;
DatabaseName := 'Primary';
TableName := 'Test';
TableType := ttParadox;
Open;
recs := RecordCount; //Get number of records in table
DefineDynArray(Hndl, recs, PA); //Now, define our dynarray
recs := 0; //Reset recs for reuse
StatusBar1.SimpleText := 'Now filling array';
while not EOF do
begin
Application.ProcessMessages; //allow background processing
try
PA^[recs] := FieldByName('Charge').AsFloat;
StatusBar1.SimpleText := 'Grabbed value of: ' + FloatToStr(PA^[recs]);
StatusBar1.Invalidate;
Inc(recs);
Next;
except
GlobalUnlock(Hndl);
GlobalFree(Hndl);
Exit;
end;
end;
//Pop up a message to show what was calculated.
ShowMessage(FloatToStr(Mean(Slice(PA^, RecordCount))));
//pass the array using Slice
GlobalUnlock(Hndl); //Unlock and Free memory and TTable instance.
GlobalFree(Hndl);
tbl.Free;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
LoadArray;
end;
end.
You could get pretty complex with this by creating a component that encapsulates the statistical functions and grabs data off a table. Using the principles of the code above, it shouldn't be too hard to do. Follow the code; better yet, try it out, supply your own values, and see what you come up with.
We covered a lot of ground here. I wasn't happy to tell you merely about the Math unit and all the wonderful routines it contains; I wanted to show you a way to employ a major portion of it in as flexible a way as possible.
In my opinion, it's not enough just to know about something in programming; you have to know how to use it. With the material I've presented, you should be able to employ the functions of the Math unit in very little time.
2006. december 26., kedd
Create and free a window handle inside a TThread object
Problem/Question/Abstract:
I have a (separate) thread that spends most of its time waiting for (synchronisation) events, and when an event occurs it performs some processing which involves user-specific (Delphi) event handling. In these event handlers I would now like to be able to create objects that uses Windows messages, such as TTimers or some other own-developed components we have, and that these objects should be able to generate (Delphi) events as they normally would (although executed in my separate thread, of course).
Answer:
I usually use messages within threads and works fine, I attach 2 procedures that allows you to create/ free a window handle inside a TThread object and to use the standard Delphi way to handle messages. The function are:
CreateWindowHandle(AObject: TObject): integer;
FreeWindowHandle(AHandle: integer);
You should use them in the following way:
{ ... }
type
TMyThread = class(TThread)
private
hwnd: integer;
protected
procedure handler1(var message: TMessage); message WM_USER; {or any other message}
end;
constructor Create { ... }
begin
{ ... }
hwnd := CreateWindowHandle(Self);
{ ... }
end;
destructor Destroy { ... }
begin
{ ... }
freewindowhandle(hwnd);
{ ... }
end;
var
ObjectWindowClass: TWndClass = (style: 0; lpfnWndProc: @windows.DefWindowProc;
cbClsExtra: 0; cbWndExtra: 0; hInstance: 0; hIcon: 0;
hCursor: 0; hbrBackground: 0; lpszMenuName: nil;
lpszClassName: 'ObjectWindowClass@wbuwbvubvy');
function ObjectWindowProc(HWnd, Msg, wParam, lParam: integer): integer; stdcall;
var
m: TMessage;
begin
m.Msg := uint(msg);
m.wParam := wParam;
m.lParam := lParam;
TObject(GetWindowLong(hwnd, GWL_USERDATA)).Dispatch(m);
result := m.Result;
end;
function CreateWindowHandle(AObject: TObject): integer;
var
TempClass: TWndClass;
ClReg: Boolean;
hwnd: integer;
begin
{register the window class (if not already registered) }
ObjectWindowClass.hInstance := HInstance;
ClReg := GetClassInfo(HInstance, ObjectWindowClass.lpszClassName, TempClass);
if (not ClReg) or (TempClass.lpfnWndProc <> @windows.DefWindowProc) then
begin
if ClReg then
Windows.UnregisterClass(ObjectWindowClass.lpszClassName, HInstance);
Windows.RegisterClass(ObjectWindowClass);
end;
{create the window}
HWnd := CreateWindow(ObjectWindowClass.lpszClassName, '', 0, 0, 0, 0, 0, 0, 0,
HInstance, nil);
{subclass the window}
SetWindowLong(HWnd, GWL_USERDATA, integer(AObject));
SetWindowLong(HWnd, GWL_WNDPROC, integer(@ObjectWindowProc));
Result := HWnd;
end;
procedure FreeWindowHandle(AHandle: integer);
begin
SetWindowLong(AHandle, GWL_WNDPROC, integer(@windows.DefWindowProc));
DestroyWindow(AHandle);
end;
2006. december 25., hétfő
Display the multimedia properties from the control panel
Problem/Question/Abstract:
Display the multimedia properties from the control panel
Answer:
Displaying the multimedia properties dialog from the control panel is very easy with the following code; the secret is to know to call rundll32.
begin
// Audio
WinExec('rundll32 shell32.dll,Control_RunDLL MMSys.cpl,,0', SW_SHOW);
// Video
WinExec('rundll32 shell32.dll,Control_RunDLL MMSys.cpl,,1', SW_SHOW);
// Midi
WinExec('rundll32 shell32.dll,Control_RunDLL MMSys.cpl,,2', SW_SHOW);
// CD Music
WinExec('rundll32 shell32.dll,Control_RunDLL MMSys.cpl,,3', SW_SHOW);
end
2006. december 24., vasárnap
Move components on a form during runtime
Problem/Question/Abstract:
Move components on a form during runtime
Answer:
Moves misc. components on a form, e.g. TButton, TLabel, etc. Put the call in the components OnMouseDown-Eventhandler;
Example: MoveObject(Sender, X, Y);
uses
Windows, Messages;
procedure MoveObject(Sender: TObject; X, Y: Integer);
const
SC_DragMove = $F012;
begin
ReleaseCapture;
(Sender as TControl).Perform(WM_SysCommand, SC_DragMove, 0);
end;
2006. december 23., szombat
Debugger skips code
Problem/Question/Abstract:
I trace the instructions in the debugger and find that it suddenly skips code I've written - it moves through it, but doesn't take action.
Answer:
I also had this problem with the skipped code. I removed all of the temp type files, just kept the DFM and PAS files + DPR,did a rebuild and it seemed to be ok. It seems to get out of step with itself sometimes.
2006. december 22., péntek
Today's date on database server
Problem/Question/Abstract:
Today's date on database server
Answer:
This is another case where ORACLE and InterBase differ.
// make the SQL dependent on type of DBMS
if AppLibrary.Database.DriverName = 'ORACLE' then
SQL.Add('and entry_date < SYSDATE')
else
SQL.Add('and entry_date < "TODAY"');
end;
2006. december 21., csütörtök
How to use all the capabilities of windows fonts?
Problem/Question/Abstract:
TFont doesn't give you all the capabilities of Windows fonts when you need to draw some text.
This is because the mechanism TFont uses differs from the one Windows have.
Answer:
TFont is a descent of TGraphicsObject. This is the abstract base class for objects which encapsulate a system graphics object: TBrush, TFont, and TPen.
Internally TFont uses the TFontData record to keep track of all changes to our font.
While TFontData structure is like this:
TFontData = record
Handle: HFont;
Height: Integer;
Pitch: TFontPitch;
Style: TFontStylesBase;
Charset: TFontCharset;
Name: TFontDataName;
end;
windows LogFont structure defines the following attributes of a font:
tagLOGFONTA = packed record
lfHeight: Longint;
lfWidth: Longint;
lfEscapement: Longint;
lfOrientation: Longint;
lfWeight: Longint;
lfItalic: Byte;
lfUnderline: Byte;
lfStrikeOut: Byte;
lfCharSet: Byte;
lfOutPrecision: Byte;
lfClipPrecision: Byte;
lfQuality: Byte;
lfPitchAndFamily: Byte;
lfFaceName: array[0..LF_FACESIZE - 1] of AnsiChar;
end;
TLogFontA = tagLOGFONTA;
TLogFont = TLogFontA;
you can already see the difference between both. Anyway, while trying to simplify the process Delphi team forgot to add to TFontData : lfEscapment, lfOrientation (with the two we can rotate a font) and lfQuality (this one give us the possibility of drawing an antialised font).
When you call TextOut, internally when the draw text routine gets the handle for the font to be used with the windows API ExtTextOut, TFont maps the TFont structure to windows logfont structure (much like the way I did the DrawText routine forward).
In TFont.GetHandle we see the following:
(...split...)
lfEscapement := 0; { only straight fonts }
lfOrientation := 0; { no rotation }
(...split...)
lfQuality := DEFAULT_QUALITY;
(...split...)
so... they didn't want to code just a little more :)
I thougth about changing (or even extending) TFont class to have these two properties available, anyway (if someone is interested I could do that) I'll just present here a routine that can draw some text (rotated and with more quality).
procedure DrawText(ACanvas: TCanvas; AAngle: Integer; AQuality: byte; X, Y: Integer;
AText: string);
var
lf: TLogFont;
begin
with ACanvas do
begin
GetObject(Font.Handle, SizeOf(lf), @lf);
with lf do
begin
lfQuality := AQuality;
lfOrientation := AAngle * 10;
lfEscapement := lfOrientation;
end;
Font.Handle := CreateFontIndirect(lf);
TextOut(X, Y, AText);
end;
end;
AQuality can be :
DEFAULT_QUALITY
Appearance of the font does not matter.
DRAFT_QUALITY
Appearance of the font is less important than when PROOF_QUALITY is used. For GDI raster fonts, scaling is enabled, which means that more font sizes are available, but the quality may be lower. Bold, italic, underline, and strikeout fonts are synthesized if necessary.
PROOF_QUALITY
Character quality of the font is more important than exact matching of the logical-font attributes. For GDI raster fonts, scaling is disabled and the font closest in size is chosen. Although the chosen font size may not be mapped
exactly when PROOF_QUALITY is used, the quality of the font is high and there is no distortion of appearance. Bold, italic, underline, and strikeout fonts are synthesized if necessary.
NONANTIALIASED_QUALITY
ANTIALIASED_QUALITY
Note: Remember that the 3 attributes just work with True Type Fonts.
2006. december 20., szerda
How to get unique ItemIndexes across multiple TRadioGroups
Problem/Question/Abstract:
Is there a clean, simple solution to this problem: You have three RadioGroups: RgA, RgB, and RgC. Each has the same three items (numbered 0, 1, and 2 - actual string values are irrelevant here). Initially, RgA.ItemIndex = 0, RgB.ItemIndex = 1, and RgC.ItemIndex =2. The problem is to ensure that each selection (ItemIndex) is unique across all the RadioGroups. If the user clicks RgA and changes its index to 2, RgC's ItemIndex must change to 0 (the unused value). You'll always have one RadioGroup with an ItemIndex of 0, one with 1, and one with 2.
Answer:
Here is a possible approach:
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Menus, StdCtrls, ExtCtrls;
type
TForm1 = class(TForm)
RadioGroup1: TRadioGroup;
RadioGroup2: TRadioGroup;
RadioGroup3: TRadioGroup;
procedure FormCreate(Sender: TObject);
procedure AllRadiogroupsClick(Sender: TObject);
private
{ Private declarations }
FRGroups: array[1..3] of TRadioGroup;
FRGroupItemIndices: array[1..3] of 0..2;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
var
i: Integer;
begin
FRGroups[1] := RadioGroup1;
FRGroups[2] := RadioGroup2;
FRGroups[3] := RadioGroup3;
for i := 1 to 3 do
begin
FRGroupItemIndices[i] := FRGroups[i].ItemIndex;
FRGroups[i].Tag := i;
end;
{ assumes indices have been set up correctly at design time! }
end;
procedure TForm1.AllRadiogroupsClick(Sender: TObject);
var
oldvalue: Integer;
swapWith: Integer;
thisGroup: TRadioGroup;
function FindOldValue(value: Integer): Integer;
var
i: integer;
begin
result := 0;
for i := 1 to 3 do
if FRGroupItemIndices[i] = value then
begin
result := i;
break;
end;
if result = 0 then
raise exception.create('Error in FindOldValue');
end;
begin
{Tag property of radiogroup stores index for arrays}
{Find old value of the group that changed}
thisGroup := Sender as TRadioGroup;
oldvalue := FRGroupItemIndices[thisGroup.Tag];
if oldvalue = thisGroup.ItemIndex then
Exit;
{Find the index of the group that currently has the value this group changed to}
swapWith := FindOldValue(thisGroup.ItemIndex);
{Change the array values}
FRGroupItemIndices[thisGroup.Tag] := thisGroup.ItemIndex;
FRGroupItemIndices[swapWith] := Oldvalue;
{Change the Itemindex of the other group. Disconnect handler while doing so}
with FRGroups[swapWith] do
begin
OnClick := nil;
ItemIndex := oldValue;
OnClick := AllRadioGroupsClick;
end;
end;
end.
2006. december 19., kedd
How to create a non-rectangular TShape that accepts mouse clicks only in the shape's region itself
Problem/Question/Abstract:
How to create a non-rectangular TShape that accepts mouse clicks only in the shape's region itself
Answer:
This will show you how to do a TShape that has mouse clicks only in the shape itself and not it's surrounding box.
unit NoMouseShape;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls;
type
TNoMouseShape = class(TShape)
private
{ Private declarations }
protected
{ Protected declarations }
procedure CMHitTest(var Message: TCMHitTest); message CM_HITTEST;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
published
{ Published declarations }
function ValidPoint(pt: TPoint): Boolean;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('EXS', [TNoMouseShape]);
end;
constructor TNoMouseShape.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
Shape := stCircle;
end;
function TNoMouseShape.ValidPoint(pt: TPoint): Boolean;
var
i, j: Integer;
begin
Result := False;
for i := 0 to Width do
for j := 0 to Height do
if (Self.Canvas.Pixels[pt.x, pt.y] = clWhite) or (Self.Canvas.Pixels[pt.x, pt.y] = clBlack) then
Result := True;
end;
procedure TNoMouseShape.CMHitTest(var Message: TCMHitTest);
begin
if ValidPoint(SmallPointToPoint(Message.Pos)) then
Message.Result := HTCLIENT {Handle the message}
else
Message.Result := HTNOWHERE; {pass on to parent}
end;
end.
Now, if you want a real non-rectangular area, using SetWindowRgn would be the way to go. You do not have to derive from TWinControl, drive it from TCustomControl as TCustomControl provides a canvas and handles paint messages. Then you will need to re-write the ValidPoint function to use the PtInRegion API function.
2006. december 18., hétfő
Select an area with similar color
Problem/Question/Abstract:
In my application, I want to select an area with similar color. This tool should work like Photoshop's Magic Wizard Tool. But how can I determine if 2 pixels are similar if they don't have the same RGB value?
Answer:
A "quick" solution is to compare the RGB components separately - if none of the red, green or blue components differ by more than the threshold amount then you can assume that the colours are "similar". There are more optically correct methods of comparing colours but this might be good enough for your purposes.
function ColoursAreSimilar(Col1, Col2: TRGBQuad; Threshold: Integer): Boolean;
begin
Result := (Abs(Col1.rgbRed - Col2.rgbRed) <= Threshold) and
(Abs(Col1.rgbGreen - Col2.rgbGreen) <= Threshold) and
(Abs(Col1.rgbBlue - Col2.rgbBlue) <= Threshold);
end;
2006. december 17., vasárnap
How to check for duplicates in a dynamic array of elements containing random integers
Problem/Question/Abstract:
Say I have a dynamic array of five elements containing random integers. I want to be able to check for duplicate numbers among the 5 elements and if found, call a random number generator function until all the numbers are different.
Answer:
Solve 1:
This strategy goes through the array one element at a time, making sure it is unique. All we have to do is make sure it is unique from all the preceding elements, and by the time you get to the end of the array, it is all unique.
procedure ForceUnique;
var
i, j: integer;
begin
for i := 2 to 5 do {the first element is unique by definition}
begin
j := 1;
while (j < i) do
begin
if MyArray[j] = MyArray[i] then
begin
MyArray[i] := Random(MyRange);
j := 1; {start over with a new number}
end
else
j := j + 1;
end;
end;
end;
Watch out for potential infinite loop problem if the range of random numbers is less than the size
of the array.
Solve 2:
How about filling the arrays with ascending order of number and then shuffling it randomly afterwards. This is the quickest and surest way to achieve the same effect.
What I am afraid is a situation like this. Let us say you have an array of 32767 small integers. Let us say you want to fill the individual elements with Random values ranging from 0 to 32767. Using random number generator, the probability that all of the elements will be unique is zero. Now, let us say, you sort the random list first, then it is easy to find duplicates, and let us say for each duplicate you try to generate a random number which is not in the array and hopefully replace those duplicates. In this situation, your program will take forever to complete.
If you only have 5 elements, a bubble-type comparison would suffice, if you have more than a hundred elements, you need to sort your array first and do a binary search to find duplicates. If your random number ranges from 0 to MaxInt, this has a greater chance of successfully completing than with a smaller random number range.
Here's the slowest but easy to understand working code for bubble-wise comparisons. Assume iArray is your dynamic array 1 based.
{declare these first:
i, j, k, iRand, iCurr: integer;
iAlreadyExists: boolean;}
{ ... }
for i := 1 to 5 do
begin
icurr := iArray[i];
for j := i + 1 to 5 do
begin
if icurr = iArray[j] then
begin
repeat
irand := Random(MaxInt);
iAlreadyExists := False;
for k := 1 to 5 do
begin
if irand = iArray[k] then
begin
iAlreadyExists := True;
break;
end;
end;
until
not iAlreadyExists;
iArray[i] := irand;
break;
end;
end;
end;
{ ... }
2006. december 16., szombat
Capturing all of the Output from a Console application (32 bit)
Problem/Question/Abstract:
Use the function GetDosOutput in your application to capture all the output from a DOS application (this version only supports 32-bit console applications for how to do this with 16 bit see the update to this article "Capturing all of the Output from a Console application (16 bit)").
Code recieved from Mike Lischke (Team JEDI) in response to a question I asked on the borland winapi newsgroup. It came from his app "Compiler Generator" (www.lischke-online.de/DCG.html) and then was converted to the GetDosOutput function by me. You can contact me at johnwlong@characterlink.net.
Answer:
unit consoleoutput;
interface
uses
Controls, Windows, SysUtils, Forms;
function GetDosOutput(const CommandLine: string): string;
implementation
function GetDosOutput(const CommandLine: string): string;
var
SA: TSecurityAttributes;
SI: TStartupInfo;
PI: TProcessInformation;
StdOutPipeRead, StdOutPipeWrite: THandle;
WasOK: Boolean;
Buffer: array[0..255] of Char;
BytesRead: Cardinal;
WorkDir, Line: string;
begin
Application.ProcessMessages;
with SA do
begin
nLength := SizeOf(SA);
bInheritHandle := True;
lpSecurityDescriptor := nil;
end;
// create pipe for standard output redirection
CreatePipe(StdOutPipeRead, // read handle
StdOutPipeWrite, // write handle
@SA, // security attributes
0 // number of bytes reserved for pipe - 0
default
);
try
// Make child process use StdOutPipeWrite as standard out,
// and make sure it does not show on screen.
with SI do
begin
FillChar(SI, SizeOf(SI), 0);
cb := SizeOf(SI);
dwFlags := STARTF_USESHOWWINDOW or STARTF_USESTDHANDLES;
wShowWindow := SW_HIDE;
hStdInput := GetStdHandle(STD_INPUT_HANDLE); // don't redirect std
input
hStdOutput := StdOutPipeWrite;
hStdError := StdOutPipeWrite;
end;
// launch the command line compiler
WorkDir := ExtractFilePath(CommandLine);
WasOK := CreateProcess(nil, PChar(CommandLine), nil, nil, True, 0, nil,
PChar(WorkDir), SI, PI);
// Now that the handle has been inherited, close write to be safe.
// We don't want to read or write to it accidentally.
CloseHandle(StdOutPipeWrite);
// if process could be created then handle its output
if not WasOK then
raise Exception.Create('Could not execute command line!')
else
try
// get all output until dos app finishes
Line := '';
repeat
// read block of characters (might contain carriage returns and line feeds)
WasOK := ReadFile(StdOutPipeRead, Buffer, 255, BytesRead, nil);
// has anything been read?
if BytesRead > 0 then
begin
// finish buffer to PChar
Buffer[BytesRead] := #0;
// combine the buffer with the rest of the last run
Line := Line + Buffer;
end;
until not WasOK or (BytesRead = 0);
// wait for console app to finish (should be already at this point)
WaitForSingleObject(PI.hProcess, INFINITE);
finally
// Close all remaining handles
CloseHandle(PI.hThread);
CloseHandle(PI.hProcess);
end;
finally
result := Line;
CloseHandle(StdOutPipeRead);
end;
end;
end.
2006. december 15., péntek
Retrieving names, addresses, e-mails from MS Outlook
Problem/Question/Abstract:
So how to read a collection of Contacts which are exist in MS Outlook?
Answer:
As you know I like MS Outlook because there a lot of possibilities and OLE automation of Outlook allow to solve possible any task.
Today I want continue a serie of tips for MS Outlook.
So how to read a collection of Contacts which are exist in MS Outlook?
This task is very popular. For example, you want to develop a sample tool which will notify you about birthday for someone or you want to send messages to "mailing list". So you want to naviagte thru list of defined contacts and process any item.
Below you'll find a sample code:
const
olFolderContacts = $0000000A;
var
outlook, NameSpace, Contacts, Contact: OleVariant;
i: Integer;
begin
outlook := CreateOleObject('Outlook.Application');
NameSpace := outlook.GetNameSpace('MAPI');
Contacts := NameSpace.GetDefaultFolder(olFolderContacts);
for i := 1 to Contacts.Items.Count do
begin
Contact := Contacts.Items.Item(i);
{now you can read any property of contact. For example, full name
and email address}
ShowMessage(Contact.FullName + ' <' + Contact.Email1Address + '>');
end;
Outlook := UnAssigned;
end;
if you need a birthday, you can retrieve it as DateToStr(Contact.Birthday)
Any contact item have a lot of properties. See a list (alphabet):
Birthday
Business2TelephoneNumber
BusinessAddress
BusinessAddressCity
BusinessAddressCountry
BusinessAddressPostalCode
BusinessAddressPostOfficeBox
BusinessAddressState
BusinessAddressStreet
BusinessFaxNumber
BusinessHomePage
BusinessTelephoneNumber
CompanyAndFullName
CompanyMainTelephoneNumber
CompanyName
ComputerNetworkName
Department
Email1Address
Email1AddressType
Email1DisplayName
Email2Address
Email2AddressType
Email2DisplayName
Email3Address
Email3AddressType
Email3DisplayName
FirstName
FTPSite
FullName
FullNameAndCompany
GovernmentIDNumber
Hobby
Home2TelephoneNumber
HomeAddress
HomeAddressCity
HomeAddressCountry
HomeAddressPostalCode
HomeAddressPostOfficeBox
HomeAddressState
HomeAddressStree
HomeFaxNumber
HomeTelephoneNumber
Initials
ISDNNumber
JobTitle
Language
LastName
LastNameAndFirstName
MailingAddress
MailingAddressCity
MailingAddressCountry
MailingAddressPostalCode
MailingAddressPostOfficeBox
MailingAddressState
MailingAddressStreet
MiddleName
NickName
OfficeLocation
OrganizationalIDNumber
PersonalHomePage
PrimaryTelephoneNumber
Profession
Suffix
Title
WebPage
2006. december 14., csütörtök
Customize Toolbars at runtime
Problem/Question/Abstract:
Professional applications like Word or Excel let the user design the toolbars and let create new toolbars at runtime. This is't usually done with a little customize dialog and drag & drop functions.
Answer:
Professional applications like Word or Excel let the user design the toolbars and let create new toolbars at runtime. This ist usually done with a little customize dialog and drag & drop functions. This sample shows you, how to get this functionality simple into your program.
First of all, you must make some decissions to your program design. All functions, that can be put on a toolbar must defined in an Actionlist, because we need a list of functions that can be asigned to the toolbuttons. To minimize this work an Actionlist is the right thing, because wie can walk through the functions, have an automatic integration of the images for the toolbuttons via the imagelist, centralized shortcuts and have unique names for the functions to save and restore it.
The next thing is, that you should save and restore the customized toolbars to an inifile or the registry. This is done by the TFiletoolbar component included with this sample. Note, that you must assign the Actionlist to TFiletoolbars to get the save/restore work.
The customize dialog is built with formstyle fsStayOnTop, so the window is on top of our application. The form is displayed with the Show methode, because we must interact in the mainform for doing Drag & Drop.
For further details look at the TFiletoolbar documentation and to the source of the demo project. In the source you find detailed comments to anything that ist neccesary for get it working in your application.
Download the whole sample project including TFiletoolbar component for saving and restoring Toolbars.
http://www.helli.de/DelphiCorner/dc_tips/dc_tip4/dc_tip4.html
TToolbar97 component pack by Jordan Russel, Version 1.75 or higher available from http://www.jordanr.dhs.org/ (Freeware for non commercial use) is needed.
Example of Drag&Drop functions from Sample project:
//---------------------------------------------------------------
// Drag & Drop of Toolbar Buttons
// These functions are plugged to the OnDragOver, OnDragDrop and
// OnDragEnd property of any toolbar and any button on it.
// By user defined buttons and toolbars this is done automatic,
// by predefined toolbars you must plug in the functions in
// Objectinspector
//---------------------------------------------------------------
procedure TForm1.ToolbarDragOver(Sender, Source: TObject; X,
Y: Integer; State: TDragState; var Accept: Boolean);
// This function must be plugged to the OnDragOver property of
// any toolbars and toolbuttons
//
// Check if Dropping is allowed
begin
if Source is TListView then
// Docking allowed only from CustomizeDialog Command Listview
Accept := (Source as TListView).Name = CustomizeDialog.Commands.Name;
end;
procedure TForm1.ToolbarDragDrop(Sender, Source: TObject; X, Y: Integer);
// This function must be plugged to the OnDragDrop property of
// any toolbars and toolbuttons
//
// Handler for TToolbutton97 objects for dropping from CustomizeDialog
var
t: TListView;
act: TAction;
ti: TListItem;
tb, tb2: TToolbarButton97;
ftb: TFileToolbar;
oi: Integer;
begin
if Source is TListView then
begin // Is it from the Listview?
t := Source as TListView;
if Assigned(t) then
begin
// Get the selected item, it holds the desired action
ti := t.Selected;
if Assigned(ti) then
begin
// The action is stored in the data property of the item
act := ti.Data;
if Assigned(act) then
begin
// create a toolbutton on the fly
tb := TToolbarButton97.Create(self);
if Assigned(tb) then
begin
tb.Images := ImageList1; // Assign the Imagelist
tb.DisplayMode := dmGlyphOnly; // display only the image
// mode is turned to dmManual when leaving the
// CustomizeDialog but to handle Drag&Drop, it must be
// dmAutomatic by now
tb.DragMode := dmAutomatic;
tb.Action := act; // the desired Action
// Assign the handlers
tb.OnDragDrop := ToolbarDragDrop;
tb.OnDragOver := ToolbarDragOver;
tb.OnEndDrag := ToolbarEndDrag;
// Hints like parent
tb.ParentShowHint := True;
// Look for the place to add the button on the toolbar
ftb := nil;
oi := x div tb.Width;
// Convert Position from pixel to number of buttons
if Sender is TFileToolbar then
begin
// dropped direct to the toolbar?
ftb := Sender as TFileToolbar;
end
else if Sender is TToolbarButton97 then
begin
// placed on an other button?
tb2 := Sender as TToolbarButton97;
if Assigned(tb2) then
begin
// Get parent and Orderindex of the button
ftb := tb2.Parent as TFileToolbar;
oi := ftb.OrderIndex[tb2];
end;
end;
if Assigned(ftb) then
begin // We have a parent...
// generate a unique name for the button
tb.Name := 'tbB' + CustomizeDialog.DateStamp;
// Insert the button on the toolbar
ftb.InsertControl(tb);
ftb.OrderIndex[tb] := oi; // and set the Orderindex
end;
end;
end;
end;
end;
end;
end;
procedure TForm1.ToolbarEndDrag(Sender, Target: TObject; X, Y: Integer);
// This function must be plugged to the OnEndDrag property of any
// toolbutton. The toolbars must not have this, because you cant
// throw them out of the program...
//
// Handler for TToolbarbutton97 objects to throw 'em out
// of the Toolbar
var
tb: TToolbarButton97;
ftb: TFileToolbar;
begin
if not Assigned(Target) then
begin
// No target > so throw the button away
tb := Sender as TToolbarButton97;
if Assigned(tb) then
begin
ftb := tb.Parent as TFileToolbar;
// Delete the button
if Assigned(ftb) then
ftb.RemoveControl(tb);
end;
end;
end;
2006. december 13., szerda
How can I find out the Internet Explorer Version
Problem/Question/Abstract:
How can I find out the Internet Explorer version?
Answer:
uses
Registry;
function GetIEVersion(Key: string): string;
var
Reg: TRegistry;
begin
Reg := TRegistry.Create;
try
Reg.RootKey := HKEY_LOCAL_MACHINE;
Reg.OpenKey('Software\Microsoft\Internet Explorer', False);
try
Result := Reg.ReadString(Key);
except
Result := '';
end;
Reg.CloseKey;
finally
Reg.Free;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
ShowMessage('IE-Version: ' + GetIEVersion('Version')[1] + '.' +
GetIEVersion('Version')[3]);
ShowMessage('IE-Version: ' + GetIEVersion('Version'));
// <major version>.<minor version>.<build number>.<sub-build number>
end;
2006. december 12., kedd
How to colour specific cells in a TDBGrid
Problem/Question/Abstract:
Do you know any way to make a row in a DBGrid have a different color. I would like the color to be based on a value in the row. Like a status flag that says if a an account is over 90 days due, show the row in red.
Answer:
Respond to an OnDrawDataCell event. Here's an example that uses the demo COUNTRY table and draws the text of each row in red where the population is less than a certain value.
{ ... }
begin
if Table1.FieldByName('Population').AsFloat < 10000000 then
DBGrid1.Canvas.Font.Color := clRed;
DBGrid1.DefaultDrawDataCell(Rect, Field, State);
end;
{ ... }
2006. december 11., hétfő
How to synchronize a TTreeView with a TMemo
Problem/Question/Abstract:
I'm trying to synchronize different nodes in a TTreeView with different text displayed in a TMemo control.
Answer:
Lets assume you want to store the memo text into TStringList instances and store the stringlist reference into the node.data property. The first order of the day is to create the stringlists. You do that when you create the nodes, that is the logical place to do it. So after you have added a node you do a
node.Data := TStringlist.Create;
The stringlist is initially empty, of course. So if you have some data to load into the node you can load it into the stringlist with a statement like
TStringlist(node.data).Text := SomeStringValue;
The act of moving the text to the memo and back is now a simple assignment, no stringlists get created for that since we already have them. The main problem is to get aware when the user moves to a new node, so the old (perhaps edited) node can be updated from the memo. This seems to be another problem that is giving you grief. The solution is to track the *last* selected node, the one whos content is currently in the memo. Add a field
FLastNode: TTreeNode;
to your form (private section). This field starts out as Nil (no node active).
procedure TForm1.tv_eg5Change(Sender: TObject; Node: TTreeNode);
begin
if Node.Selected and (FLastNode <> Node) then
begin
if Assigned(FLastNode) then
TStringlist(FLastNode.Data).Assign(memo1.lines);
Memo1.Lines.Assign(TStringlist(Node.Data));
FLastNode := Node;
end;
end;
procedure TForm1.Memo1Exit(Sender: TObject);
begin
if assigned(FLastnode) then
TStringlist(FLastNode.Data).Assign(memo1.lines);
end;
You have to check whether the memos Onexit event happens before or after the treeviews OnChange event if you have focus on the memo and click on an item in the treeview. If it happens after the OnChange event the Onexit handler needs to be modified to look like
procedure TForm1.Memo1Exit(Sender: TObject);
begin
if assigned(FLastnode) and not treeview1.focused then
TStringlist(FLastNode.Data).Assign(memo1.lines);
end;
Otherwise you will assign the memos content just back from the node the OnChange handler just loaded it from.
A final thing you have to do is free the stringlist items attached to the treeview before the form is destroyed. If you don't use drag & dock with the treeview or the form it is on you could use the OnDeletion event of the treeview for this, doing a
TObject(node.data).Free;
in the handler. If drag & dock is involved this is not a good idea since the event fires each time the treeview or its parent changes from undocked to docked status and back. In this case the forms OnCloseQuery or OnClose event may be suitable. There you iterate over the treeviews nodes and do the free on each as above.
2006. december 10., vasárnap
Copy/delete a BDE table
Problem/Question/Abstract:
Copy/delete a BDE table
Answer:
Here is an example of a routine that I use for copying and deleting tables. It uses DB, DBTables, DbiProcs,DbiErrs, and DbiTypes.
You simply provide the directory to copy from, the source table name, the directory to copy to, and the destination table name, and the BDE will copy the entire table, indexes and all to the new file.
The delete function takes the path to delete from and the name of the table to delete, the BDE takes care of deleting all associated files (indexes, etc.).
These procedures have been pulled off a form of mine, and I've edited them to remove some dependencies that existed with that form. They should now be completely stand-alone.
procedure TConvertForm.CopyTable(FromDir, SrcTblName, ToDir, DestTblName: string);
var
DBHandle: HDBIDB;
ResultCode: DBIResult;
Src, Dest, Err: array[0..255] of Char;
SrcTbl, DestTbl: TTable;
begin
SrcTbl := TTable.Create(Application);
DestTbl := TTable.Create(Application);
try
SrcTbl.DatabaseName := FromDir;
SrcTbl.TableName := SrcTblName;
SrcTbl.Open;
DBHandle := SrcTbl.DBHandle;
SrcTbl.Close;
ResultCode := DbiCopyTable(DBHandle, false,
StrPCopy(Src, FromDir + '\' + SrcTblName), nil,
StrPCopy(Dest, ToDir + '\' + DestTblName));
if ResultCode <> DBIERR_NONE then
begin
DbiGetErrorString(ResultCode, Err);
raise EDatabaseError.Create('While copying ' +
FromDir + '\' + SrcTblName + ' to ' +
ToDir + '\' + DestTblName + ', the '
+ ' database engine generated the error '''
+ StrPas(Err) + '''');
end;
finally
SrcTbl.Free;
DestTbl.Free;
end;
end;
procedure TConvertForm.DeleteTable(Dir, TblName: string);
var
DBHandle: HDBIDB;
ResultCode: DBIResult;
tbl, Err: array[0..255] of Char;
SrcTbl, DestTbl: TTable;
begin
SrcTbl := TTable.Create(Application);
try
SrcTbl.DatabaseName := Dir;
SrcTbl.TableName := TblName;
SrcTbl.Open;
DBHandle := SrcTbl.DBHandle;
SrcTbl.Close;
ResultCode := DbiDeleteTable(DBHandle,
StrPCopy(Tbl, Dir + '\' + TblName), nil);
if ResultCode <> DBIERR_NONE then
begin
DbiGetErrorString(ResultCode, Err);
raise EDatabaseError.Create('While deleting ' +
Dir + '\' + TblName + ', the database ' +
'engine generated the error ''' + StrPas(Err) + '''');
end;
finally
SrcTbl.Free;
end;
end;
2006. december 9., szombat
How to access menuitems like an array and how to write just one onclick procedure for all of them
Problem/Question/Abstract:
How to access menuitems like an array and how to write just one onclick procedure for all of them?
Answer:
// Suppose you have an application with a TMainMenu.
// Under the MenuItem, with caption "File" of the MainMenu, you can
// choose the "normal" things, like:
// "Open", "New", "Close", Save", "Print", "Printer Setup", "Exit" and
// so on.
// At the end of the "File" menu there is a recent file list with all
// the files in it which you recently opened with this application.
// (Just like in Word.) Suppose the names of these MenuItems are:
//
// FileLastFile1 : TMenuItem;
// FileLastFile2 : TMenuItem;
// FileLastFile3 : TMenuItem;
// FileLastFile4 : TMenuItem;
// FileLastFile5 : TMenuItem;
// FileLastFile6 : TMenuItem;
// FileLastFile7 : TMenuItem;
// FileLastFile8 : TMenuItem;
// FileLastFile9 : TMenuItem;
//
// When the application starts, it opens a config file, where the drive,
// path and filenames of the recent file list are read. You can set the
// captions of the MenuItems with the following procedure:
//----------------------------------------------------------------------
procedure ReadConfigFile;
var
// It is better to make this a global TMenuItem array so you only
// have to assign once.
aMenuItem: array[1..9] of TMenuItem
f: TextFile;
I: Integer;
S: string;
begin
// If this array is global, you only have to assign once. F.i. in the
// procedure OnFormCreate. After that you can always access the
// MenuItems like an array, with: aMenuItem[Index].
aMenuItem[1] := FileLastFile1;
aMenuItem[2] := FileLastFile2;
aMenuItem[3] := FileLastFile3;
aMenuItem[4] := FileLastFile4;
aMenuItem[5] := FileLastFile5;
aMenuItem[6] := FileLastFile6;
aMenuItem[7] := FileLastFile7;
aMenuItem[8] := FileLastFile8;
aMenuItem[9] := FileLastFile9;
// Now the MenuItems are in an TMenuItem array, we can get easy access
// to the individual MenuItems. Like this:
for I := 1 to 9 do
aMenuItem[I].Caption := ''; // Make the caption empty.
// Open the config file with the recent file list. Example:
AssignFile(f, 'c:\my_ini_file.ini');
Reset(f);
// Read out the recent file names and put them in a global string array.
// Example:
for I := 1 to 9 do
if not Eof(f) then
ReadLn(f, aRecentfile[I]);
System.CloseFile(f);
//
// Of course you can also do it the "windows" way with:
// MyIniFile := TIniFile.Create('c:\my_ini_file.ini');
// For I := 1 to 9 do
// aRecentFile[I] := MyIniFile.ReadString('Section','RecentFile' +
// Chr(I + Ord('0')), '');
// MyIniFile.Free;
//
// If the content of the IniFile looks like this:
//
// [Section]
// RecentFile1=c:\firstfile.bmp
// RecentFile2=c:\secondfile.txt
// ...
//
// Now you can assign the filenames to the caption of the TMenuItems.
// Example:
for I := 1 to 9 do
aMenuItem[I].Caption := ExtractFileName(aRecentFile[I]);
end;
//----------------------------------------------------------------------
// You can also point ALL the OnClick events of the TMenuItems to just
// one procedure. F.i. the procedure below.
// The procedure finds out on which MenuItem is clicked, and opens the
// right file from the array with the recent filenames.
//----------------------------------------------------------------------
procedure TMainForm.FileLastFileClick(Sender: TObject);
var
sFilename: string;
I: Integer;
sS: string;
iErr: Integer;
begin
// Assumes that the global string array aRecentFile[1..9] holds the
// filenames of the recent file list, read during startup of the app.
// Find out which one is clicked.
// Get the name of the sender: FileLastFile1..FileLastFile9
sS := (Sender as TComponent).Name;
// Get the number at the end of the name of the sender, so you know
// which one is clicked. Example:
Val(sS[13], I, iErr);
if iErr <> 0 then
begin
// sS[13] is an illegal character.
Exit;
end;
// Now the variable "I" holds the number of the MenuItem, which was
// clicked on.
// Get the filename from the array of filenames and do something with
// it. Example:
sFileName := aRecentFile[I];
if sFileName <> '' then
begin
if FileExists(sFileName) then
begin
// Open the file
// Do your stuff
end
else
begin
// Something wrong! The file cannot be found.
// Send a message. Example:
// ShowMessage('Cannot find the requested file: ' + sFilename + '.');
// Or grayout this MenuItem in the "File" menu. Example:
// (Sender as TComponent).Enabled := False;
// Or hide this MenuItem in the "File" menu. Example:
// (Sender as TComponent).Hide;
end;
end;
end;
//----------------------------------------------------------------------
2006. december 8., péntek
How to get the IP address for a given URL string and the URL string for a given IP address
Problem/Question/Abstract:
How to get the IP address for a given URL string and the URL string for a given IP address
Answer:
Maybe these functions will help you:
uses
Winsock, { etc. }
function IpAddrToHostName(const sIP: string): string;
var
I: Integer;
P: PHostEnt;
begin
Result := '';
I := inet_addr(PChar(sIP));
if I <> u_long(INADDR_NONE) then
begin
P := GetHostByAddr(@I, SizeOf(Integer), PF_INET);
if P <> nil then
Result := P^.h_name;
end
else
Result := 'Invalid IP address';
end;
function HostNameToIpAddr(const sHost: string): string;
var
P: PHostEnt;
Ia: TInAddr;
begin
Ia.S_addr := 0;
P := gethostbyname(PChar(sHost));
if P <> nil then
with Ia.S_un_b, P^ do
begin
s_b1 := h_addr_list^[0];
s_b2 := h_addr_list^[1];
s_b3 := h_addr_list^[2];
s_b4 := h_addr_list^[3];
end;
Result := inet_ntoa(ia);
end;
2006. december 7., csütörtök
How to play sound from a resource file (2)
Problem/Question/Abstract:
How do I play an audio waveform from a memory buffer without writing to disk?
Answer:
procedure TFrmWaves.PlayTheSound(AName: PChar);
var
resInfo: THandle;
wavHdl: THandle;
wavPtr: Pointer;
begin
case RgrMethod.ItemIndex of
0:
begin
resInfo := FindResource(HInstance, AName, RES_TYPE);
if resInfo <> 0 then
begin
Label1.Caption := StrPas(AName);
wavHdl := LoadResource(HInstance, resInfo);
wavPtr := LockResource(wavHdl);
sndPlaySound(wavPtr, SND_ASYNC or SND_MEMORY);
if (wavHdl <> 0) then
begin
UnlockResource(wavHdl);
FreeResource(wavHdl);
end;
end
else
Label1.Caption := StrPas(AName) + ': Not found';
end;
1: PlaySound(AName, HInstance, SND_RESOURCE);
end;
end;
2006. december 6., szerda
How to do case insensitive search with SQL
Problem/Question/Abstract:
Does anyone know how to do a SELECT WHERE statement where it covers all cases. Like, when a user types in 'new york' to search a city field, it finds all occurrences of the string, like 'NEW YORK', 'New York', 'new york', 'NeW yOrk', etc..
Answer:
SELECT * FROM MyTable
WHERE UPPER(City) = 'NEW YORK'
Using UPPER makes it impossible to use an index which may hurt performance.
2006. december 5., kedd
How to drag and drop TPanels within a scrollbox to rearrange the order
Problem/Question/Abstract:
All I want to do is to allow the user to drag/ drop panels within a scrollbox to rearrange the order. They are initially created in alpha order by caption anf pointers stored in a stringlist. When the user moves one it is moved in the stringlist, the scrollbox is cleared (note: the panels cannot be freed because they contain info) and re-parented in stringlist sequence. If you do more than one move, the resulting sequence of the panels in the scrollbox seems completely random, and certainly bears no resemblance to the sequence in the stringlist! I have tried doing this by index up from and downto zero; neither works.
Answer:
All your trouble comes from using the alTop style for the panels. Simply set it to alNone and size and position the panels in code, using their SetBounds method.
procedure TForm1.FormCreate(Sender: TObject);
var
iCount: integer;
APanel: TPanel;
y: Integer;
begin
AStrList := TStringList.Create;
y := 0;
for iCount := 0 to 4 do
begin
APanel := TPanel.Create(Self);
with APanel do
begin
Name := 'P' + IntToStr(iCount);
Align := alNone;
OnMouseDown := PanelMouseDown;
OnDragOver := PanelDragOver;
OnDragDrop := PanelDragDrop;
SetBounds(0, y, scrollbox1.clientwidth, height);
Inc(y, height);
end;
AStrList.AddObject(APanel.Caption, APanel);
end;
for iCount := 0 to (AStrList.Count - 1) do
TPanel(AStrList.Objects[iCount]).Parent := ScrollBox1;
end;
procedure TForm1.PanelDragDrop(Sender, Source: TObject; X, Y: Integer);
var
iFrom, iTo, iCount: integer;
begin
iFrom := AStrList.IndexOfObject(TPanel(Sender));
iTo := AStrList.IndexOfObject(TPanel(Source));
AStrList.Move(iFrom, iTo);
y := 0;
for iCount := 0 to (AStrList.Count - 1) do
with TPanel(AStrList.Objects[iCount]) do
begin
Top := y;
Inc(y, Height);
end;
end;
2006. december 4., hétfő
Moving Controls over the form
Problem/Question/Abstract:
How is it possiple to move an control over the form ?
Answer:
It's very simple - so don't try to write hundreds of lines to solve this problem
Just take a look at this small source-snip
All what we need to do is to override the dynamic MouseDown method of the TControl-BaseClass and fire an WM_SysCommand event with the magicKey $F012.
I hope this article is helpful for you
{-----------------------------------------------------------------------------
hEaDRoOm Development
29.10.2002
Unit Name: HREdit
Author: Benjamin Benjamin Wittfoth
Purpose:
History:
-----------------------------------------------------------------------------}
unit HREdit;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
THREdit = class(TEdit)
private
fDragable: Boolean;
protected
procedure MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
override;
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Dragable: Boolean read fDragable write fDragable;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('HEADROOM DEVELOPMENT', [THREdit]);
end;
{ THREdit }
constructor THREdit.Create(AOwner: TComponent);
begin
inherited Create(AOwner);
end;
destructor THREdit.Destroy;
begin
inherited;
end;
procedure THREdit.MouseDown(Button: TMouseButton; Shift: TShiftState; X,
Y: Integer);
const
SC_DragMove = $F012; // important key !!
begin
inherited;
if assigned(onMouseDown) then
OnMouseDown(self, Button, Shift, x, y);
if fDragable then
begin
ReleaseCapture;
(self as TControl).perform(WM_SysCommand, SC_DragMove, 0); // this is the key !
end;
end;
end.
If you put then next line after ReleaseCapture; the object will even come to the front before moving it and not just after.
(self as TControl).BringToFront;
Cordinates of control. Simply use an Timer on your form - i know this is not elegant, but it works.
procedure TForm1.TimerTimer(Sender: TObject);
begin
Edit1.text := inttostr(Edit1.Left) + '/' + inttostr(Edit1.top);
end;
2006. december 3., vasárnap
InterBase: Error msg about file 'SAA.AAA'
Problem/Question/Abstract:
InterBase: Error msg about file 'SAA.AAA'
Answer:
The 'SAA.AAA' file is a temporary file used in processing a query.
A failure involving this file generally means that InterBase has run out of disk space processing the query.
(Remember that this will be a temporary file on the server, so the server is out of disk space, not your local machine!)
2006. december 2., szombat
How to open a registry key for reading data without requesting write access
Problem/Question/Abstract:
How can an application open a registry key for reading data without requesting write access? TRegistry seems to open keys always for ReadWrite, which fails on WindowsNT if the user has no write permission on that key (which is the default for HKEY_LOCAL_MACHINE if the user is no Administrator). I want to write user independant registry data into HKEY_LOCAL_MACHINE during installation (which is supposed to be the standard according to the WIN API Help) and have the program, which is normally not run by an administrator, to read these data on startup.
Since everything in TRegistry is static - as always when I want to inherit from a VCL anchestor - I cannot simply write a descendant of TRegistry that overrides the OpenKey and GetKey methods. Do I have to patch the source or to copy and modify the whole TRegistry code or am I missing something obvious?
Answer:
Alternatively you can directly use the Win32API calls:
{Local. Read the registry for the given key}
function GetKeyValue(const key: string): string;
var
hkey: THandle;
buf: array[0..255] of Char;
n: Longint;
begin
Result := '';
if regOpenKeyEx(HKEY_CLASSES_ROOT, @key[1], 0, KEY_READ, hKey) = ERROR_SUCCESS then
begin
n := 255;
if regQueryValue(hKey, nil, buf, n) = ERROR_SUCCESS then
begin
Result := StrPas(buf);
end;
RegCloseKey(hkey);
end
else
Result := '';
end;
{Local. Look through the Registry looking for the descriptions of the given extension.}
function GetDescription(Extension: string): string;
var
intermediate: string;
begin
{Get intermediate value}
intermediate := GetKeyValue(Extension);
if intermediate <> '' then
begin
{Look up definition for the full description}
Result := GetKeyValue(intermediate);
end
else
Result := '';
end;
{Local. Read the registry for the description of the given file extension.}
function getExtensionDescription(const extension: string): string;
var
description: string;
begin
{Try to get the description from the registry}
description := GetDescription(extension);
{Make sure we have a description to present to the user}
if description = '' then
description := extension + ' file';
{Return the description to the caller}
Result := description;
end;
2006. december 1., péntek
How to programmatically shift focus to the next control
Problem/Question/Abstract:
How to programmatically shift focus to the next control
Answer:
To shift focus from one control to the next in the tab order without using the tab key, set the KeyPreview property of the parent form to True and drop the following into the OnKeyPress of the first control (and every control you want to "exit" with the [programmer specified] key).
if Key = chr(XXX) then
SendMessage(form1.Handle, WM_NEXTDLGCTL, 0, 0);
XXX = the ASCII code of a Key (e.g. ENTER = 13)
2006. november 30., csütörtök
Create a TStringGrid with one column of runtime created buttons in it
Problem/Question/Abstract:
I want the following: A StringGrid with 1 column of buttons in it. The number of rows in the grid is not known at design time, so the buttons are created at runtime.
Answer:
TSpeedButton will work and you won't have to worry about the TabStop. The problem with using the Rect that comes in as a param, it doesn't hit all the cells in the column. So what you end up with is buttons displaying in the wrong cells. If it doesn't matter, then you're ok. But if it does, then you'll need to update the entire column for all the visible cells. Here's what I came up with:
{ ... }
var
HelpButtons: array of TSpeedButton;
procedure Form1.CreateTheButtons;
var
i: Integer;
begin
SetLength(HelpButtons, ParamGrid.RowCount - 1);
for i := 0 to ParamGrid.RowCount - 2 do
begin
HelpButtons[i] := TSpeedButton.Create(Self);
HelpButtons[i].Visible := False;
HelpButtons[i].Parent := ParamGrid;
HelpButtons[i].Caption := IntToStr(i) + ' ?';
HelpButtons[i].Width := 34;
HelpButtons[i].Height := 18;
HelpButtons[i].Tag := i;
HelpButtons[i].OnClick := ParamGridButtonClick;
end;
{Force the buttons to show}
ParamGrid.Refresh;
end;
procedure TForm1.ParamGridDrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
procedure UpdateTheColumn;
var
i: Integer;
R: TRect;
begin
for i := ParamGrid.TopRow to (ParamGrid.VisibleRowCount + ParamGrid.TopRow) do
begin
if i >= ParamGrid.RowCount then
Break;
R := ParamGrid.CellRect(2, i);
HelpButtons[i - 1].Top := R.Top;
HelpButtons[i - 1].Left := R.Left;
if not HelpButtons[i - 1].Visible then
HelpButtons[i - 1].Visible := True;
end;
end;
begin
if Length(HelpButtons) = 0 then
Exit;
if not FRefresh then
Exit;
if ((ACol = 2) and (ARow > 0)) then
begin
UpdateTheColumn;
end;
end;
procedure TForm1.ParamGridButtonClick(Sender: TObject);
begin
ShowMessage('Click ' + Sender.ClassName + ' ' + IntToStr(TControl(Sender).Tag));
end;
2006. november 29., szerda
Incremental Searches with a TListbox
Problem/Question/Abstract:
How can I create a form that has a list box that I can perform an incremental search on?
Answer:
There are a couple of ways to do this. One's hard and slow, the other easy and fast (we're going to take the easy and fast option).
For those of you who aren't familiar with incremental searching with list boxes, the concept is simple: A user types part of a string into an edit box, then the list box automatically selects one of its items that most closely matches the value typed by the user. For example of this, open up any topic search dialog in a Windows Help file. If you type into the edit box, the list will scroll to the value that most closely matches what you type.
Why is creating a capability like this essential? Because it's tedious to scroll through a list that has lots of items. Imagine if a list contained hundreds of unsorted items. To get to the value you're looking for would take a long time if you only had the capability of scrolling through the list using the vertical scroll bar. But if you knew at least part of the value you're trying to find, entering it into an edit box and getting the item you want immediately is a much more attractive solution.
Let's delve into what you have to do make this work. First, here's the unit code for a sample form I produced:
unit uinclist;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TForm1 = class(TForm)
ListBox1: TListBox;
Edit1: TEdit;
procedure FormCreate(Sender: TObject);
procedure Edit1Change(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
{This is a test string to load into the list box at runtime}
const
ListStrings = 'United States'#13'Guatemala'#13'Mexico'#13 +
'El Salvador'#13'Costa Rica'#13'Yucatan'#13 +
'China'#13'Japan'#13'Thailand'#13'Switzerland'#13 +
'Germany'#13'Lichtenstein'#13'Jamaica'#13'Greece' +
'Turkey'#13'Ireland'#13'United Kingdom'#13'Scotland' +
'Canada'#13'Uruguay'#13'Paraguay'#13'Cuba'#13 +
'Spain'#13'Italy'#13'France'#13'Portugal'#13'New Zealand'#13 +
'Austria'#13'Australia'#13'Philippines'#13'Korea'#13 +
'Malaysia'#13'Tibet'#13'Nepal'#13'India'#13'Sri Lanka'#13 +
'Pakistan'#13 + 'Saudi Arabia'#13'United Arab Emerates'#13'Iran'#13 +
'Ukraine'#13'Belarus'#13 +
'Chechen'#13'Yugoslavia'#13'Czechoslovakia'#13'Slovina'#13'Kazakhstan'#13 +
'Egypt'#13'Morocco'#13'Macedonia'#13'Cyprus'#13'Finland'#13 +
'Norway'#13'Sweden'#13'Denmark'#13'Netherlands'#13'Lithuania'#13;
begin
ListBox1.Items.SetText(ListStrings);
end;
procedure TForm1.Edit1Change(Sender: TObject);
var
S: array[0..255] of Char;
begin
StrPCopy(S, Edit1.Text);
with ListBox1 do
ItemIndex := Perform(LB_SELECTSTRING, 0, LongInt(@S));
end;
end.
Form1 has two controls: a TEdit and a TListBox. Notice that during FormCreate, I loaded up the value of the list box with the huge string of countries. This was only for testing purposes. How you load up your list is up to you. Now, the trick to making the incremental search is in the OnChange event of Edit1. I've used the Windows message LB_SELECTSTRING to perform the string selection for me. Let's talk about the message.
LB_SELECTSTRING is one of the members of the WinAPI list box message family (all preceeded by LB_) that manipulates all aspects of a list box object in Windows. The message takes two parameters: wParam, the index from which the search should start; and lParam, the address of the null-terminated string to search on. Since WinAPI calls require null-terminated strings, use either a PChar or an Array of Char to pass string values. It's more advantageous to use a an Array of Char if you know a string value won't exceed a certain length. You don't have to manually allocate and de-allocate memory with an Array of Char, as opposed to a PChar that requires you to use GetMem or New and FreeMem to allocate and de-allocate memory.
In any case, to convert a Pascal string to a null-terminated string, just use StrPCopy to copy the contents of the Pascal string into the null-terminated string. Once that's done, all we have to do is pass the address of the null- terminated string into the wParam parameter of LB_SELECTSTRING, and that's done by using the @ symbol.
When we use Perform to execute the LB_SELECTSTRING message, the message will return the item index of the matching list item. Then all that's left to do is assign the ItemIndex property of the list box to the return value of the message. The net result is that the list box will scroll to and select the list element that was found.
There are several list box messages you can perform in Delphi. If you bring up the help system and do a topic search, enter LB_ in the edit box, and peruse the list of messages.
Delphi Expert Eddie Shipman adds the following useful information:
This procedure can be applied to TComboBox by changing to this code:
procedure TForm1.ComboBox1Change(Sender: TObject);
var
S: array[0..255] of Char;
begin
StrPCopy(S, TComboBox(Sender).Text);
with ComboBox1 do
ItemIndex := Perform(CB_SELECTSTRING, 0, LongInt(@S));
end;
2006. november 28., kedd
FoxPro limits
Problem/Question/Abstract:
FoxPro limits
Answer:
Table and Index Files
Max. # of records per table
1 billion*
Max. # of chars per record
65,000
Max. # of fields per record
255
Max. # of open DBFs
225
Max. # of chars per field
254
Max. # of chars per index key (IDX)
100
Max. # of chars per index key (CDX)
240
Max. # of open index files per table
unlimited**
Max. # of open index files in all work areas
unlimited**
* The actual file size (in bytes) cannot exceed 2 gigabytes for single-user or exclusively opened multi-user tables. Shared tables with no indexes or .IDX indexes cannot exceed 1 gigabyte. Shared tables with structural .CDX indexes cannot exceed 2 gigabytes.
** Limited by memory. In FoxPro for MS-DOS and FoxPro for Windows, also limited by available MS-DOS file handles. Each .CDX file uses only 1 file handle. The number of MS-DOS file handles is determined by the CONFIG.SYS FILES parameter.
Field Characteristics
Max. size of character fields
254
Max. size of numeric fields
20
Max. # of chars in field names
10
Digits of precision in numeric computations
16
2006. november 27., hétfő
How to create tables in Word
Problem/Question/Abstract:
Is is possible to create a table in Word via OLE Automation and to specify the value of each cell?
Answer:
Yes. If Doc is a TWordDocument, for example:
{ ... }
var
Tbl: Table;
R: Range;
Direction: OleVariant;
{ ... }
Direction := wdCollapseEnd;
R := Doc.Range;
R.Collapse(Direction);
Tbl := Doc.Tables.Add(R, 2, 4, EmptyParam, EmptyParam);
Tbl.Cell(1, 1).Range.Text := 'Row 1, Col 1';
Tbl.Cell(1, 2).Range.Text := 'Row 1, Col 2';
But doing things with individual table cells in Word is extremely slow. If you can, it's better to enter the data as (for example) comma-separated values and convert it into a table only as the last step. Here's an example:
{ ... }
const
Line1 = 'January,February,March';
Line2 = '31,28,31';
Line3 = '31,59,90';
var
R: Range;
Direction, Separator, Format: OleVariant;
{ ... }
R := Word.Selection.Range;
Direction := wdCollapseEnd;
R.Collapse(Direction);
R.InsertAfter(Line1);
R.InsertParagraphAfter;
R.InsertAfter(Line2);
R.InsertParagraphAfter;
R.InsertAfter(Line3);
R.InsertParagraphAfter;
Separator := ',';
Format := wdTableFormatGrid1;
R.ConvertToTable(Separator, EmptyParam, EmptyParam, EmptyParam, Format, EmptyParam,
EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam, EmptyParam,
EmptyParam, EmptyParam);
{ ... }
2006. november 26., vasárnap
How to calculate the minimum distance between two polygons
Problem/Question/Abstract:
Do you know how to calculate the minimum distance between two polygons if they don't overlap?
Answer:
Well, this is rather complex. I start with the basics (don't know if you already know). I use UPPER letters for vectors and lower letters for numbers. Well, and sorry that I repeat some things, I wrote the text and realized I forgot something you have to know for a later step, so I went back and added it, I didn't write this text line by line. If something isn't clear to you, I recommend to make some small drawings, I had to do many, too.
A plane is defined by (X - P) * N = 0 where P is a vector to any point in your plane and N is the normal vector of your plane. Sometimes another definition is used, which is easier to gain if you have the corners of a polygon: X = P + a * A + b * B (a, b are any real numbers). If you know 3 Points X1, X2, X3 of the plane (3 corners of the polygon), you can get A and B by A = X2 - X1 (subtract each component of the vector from the same component of the other vector) and B = X3 - X1 (and you can use P = X1).
Unfortunately this definition is not good to calculate distances, so you have to get N out of A and B. A * N must be 0 and B * N must be 0 (which means a1 * n1 + a2 * n2 + a3 * n3 = 0 and b1 * n1 + b2 * n2 + b3 * n3 = 0). Sorry I cannot remember how to do this, but you have 2 equations with three unknown variables, so you can choose one of them as you want (just be careful with 0 and not 0), the only difference is that the resulting N differs in its length.
A line is defined by X = P + v * V where P is any one point of your line and V is the line's direction (like A and B of the plane). Again if you know two points X1 and X2 of your line you get V by V = X2 - X1(and you can use P = X1).
The length of a vector V = (v1, v2, v3) is length = sqrt(sqr(v1) + sqr(v2) + sqr(v3)) (just 3-dimensional Pythagoras).
You add two vectors A = (a1, a2, a3) and B = (b1, b2, b3) like that: A + B = (a1 + b1, a2 + b2, a3 + b3) (which is a vector again).
You multiply two vectors A = (a1, a2, a3) and B = (b1, b2, b3) like that: A * B = (a1 * b1 + a2 *b2 + a3 * b3) (which is a NUMBER).
Use following formula to get the distance between any one point X and a plane: dist = 1 / n * (X - P) *N where X is a vector to the point you want to examine and n is the length of N (you don't need 1/n if N has alread length of 1). If X is (x1, x2, x3) this is
dist = 1 / n * ((x1 - p1) * n1 + (x2 - p2) * n2 + (x3 - p3) * n3)
n = sqrt(sqr(n1) + sqr(n2) + sqr(n3))
Now the distance between two polygons isn't that simple, because there are many different cases (and a polygon is more than just a simple plane, even if it's size is smaller).
What you also need is to calculate the distance between two lines. At first you need a plane, that is parallel to both lines and includes one of the lines:
P(plane) = P(line1)
A(plane) = V(line1)
B(plane) = V(line2)
where A and B are two vectors in the plane (N * A = 0 and N * B = 0), V is a vector in your line (for polygons, you can take V = X2 - X1 where X2 and X1 are two corners). Now calculate the distance between this plane and any one point of line2 using the formula above (any point because ALL points have the same distance to a plane that's parallel to the line - nice trick, isn't it?
The last thing we need is not only the minimum distance between two lines, but the points of the lines, that have minimum distance. You can do this (for the point of line1 M1 with minimum distance to line2) by calculating a plane again with
P(secondplane) = P(line2)
A(secondplane) = V(line2)
B(secondplane) = N(plane) <-- the plane we calculated above
The second plane includes line2 and the point of line1 with the minimum distance to line2. To get this point of minimum distance, set P(line1) + v * V(line1) = P(secondplane) + a * A(secondplane) + b * B(secondplane). Solve this, you should get the v and when you set this v into X = P + v * V of line1 you have the point X (=M1) of minimum distance.
The bad news: To get M2, you have to repeat this for line2. Another way would be to take the distance between the lines (I call it d) and do following: M2 = M1 + d * 1 / n * N(plane) (or M2 = M1 - d * 1 / n * N(plane), depends on the direction of N). The distance between two points X1 and X2 equals the length of the vector X2 - X1.
Okay, these were the basics. Now the different cases, you have to cope with:
1.) The planes of both polygons are parallel (N1 = x * N2):
Transform both polygons the following way: X' = X - P for each corner of the polygon (where P is any point in your plane). The new polygons should now be in the same plane. Test whether both polygons overlap (is not as simple as it sounds, to be honest I don't know how to do that).
1a) They overlap:
The minimum distance is the distance of the two planes (take any one point of one plane and use the formula above to get the distance to the other plane).
1b) They do not overlap:
Use 2) to calculate the minimum distance
2) The planes are not parallel (or case 1b):
Calculate the minimum distance of one line of one polygon and one line of the other polygon. Calculate the points of minimum distance from the lines. The edges of the polygons do not have infinite length (the lines do have), so check whether the points of minimum distance are within the polygons (I'd better say: within the edges of the polygons).
2a) Both points are within the polygons:
Store the minimum distance from the lines.
2b) One point or both points are not within the polygon:
Take the corner(s) of the polygon(s) within the line(s) you checked next to the point(s) of minimum distance. Calculate the distance between these points and store it. Now repeat this for each pair of lines (if you have 2 triangles you get 9 combinations (3 times 3). When you are ready compare all the minimum distances and take the smallest one.
Okay, this is quite much to do (realtime? difficult. perhaps if you don't have many polygons) and there are several problems (a vector (x1, x2, x3) and a vector (x1, x2, 0) may have to be treated different, for example when you try to get N out of A and B). If you really need the minimum distance, try it, but perhaps you find an easier way, that is not that exact (take the distance between the center of each polygon would be least exact, but very much easier).
I want to add, that I don't know of other solutions, perhaps there are better ones, and that I don't know if everything I told you is right, I haven't tested it, everything is just theoretically.
Feliratkozás:
Bejegyzések (Atom)