2005. március 31., csütörtök
Change fonts between columns in a TStringGrid
Problem/Question/Abstract:
How to change fonts between columns in a TStringGrid
Answer:
You must write the text to the canvas after setting the font. Use Canvas.TextRect for this:
procedure TForm1.StringGrid1DrawCell(Sender: TObject; ACol, ARow: Integer;
Rect: TRect; State: TGridDrawState);
begin
with Sender as TStringGrid do
begin
case aCol of
0: canvas.font.name := 'Courier New';
1..5: canvas.font.name := 'Arial';
end;
Canvas.TextRect(Rect, Rect.Left + 2, Rect.Top + 2, Cells[ACol, ARow]);
end;
end;
2005. március 30., szerda
How to create unique numbers for a primary index field
Problem/Question/Abstract:
Using D4, Paradox 7 and a peer-to-peer network on Win95/ 98, I am currently thinking about the problems of using AutoInc fields as primary indexes to avoid key violations. On balance, I feel that it is probably best to avoid potential problems by choosing an alternative primary index system. But what are the alternatives? Using a DateTime field as the unique primary index or use a number that is incremented in code?
Answer:
If you need a unique number for a primary key create a single-field-single-record table to hold the last used value and call the following function when you need a new number.
function dgGetUniqueNumber(LastNumberTbl: TTable): LongInt;
{Gets the next value from a one field one record table which stores the last used
value in its first field. The parameter LastNumberTbl is the table that contains the last used number.}
const
ntMaxTries = 100;
var
I, WaitCount, Tries: Integer;
RecordLocked: Boolean;
ErrorMsg: string;
begin
Result := 0;
Tries := 0;
with LastNumberTbl do
begin
{Make sure the table contains a record. If not, add one and set the first field to zero.}
if RecordCount = 0 then
begin
Insert;
Fields[0].AsInteger := 0;
Post;
end;
{Try to put the table that holds the last used number into edit mode. If calling Edit
raises an exception wait a random period and try again.}
Randomize;
while Tries < ntMaxTries do
try
Inc(Tries);
Edit;
Break;
except
on E: EDBEngineError do
{The call to Edit failed because the record could not be locked.}
begin
{See if the lock failed because the record is locked by another user.}
RecordLocked := False;
for I := 0 to Pred(E.ErrorCount) do
if E.Errors[I].ErrorCode = 10241 then
RecordLocked := True;
if RecordLocked then
begin
{Wait for a random period and try again.}
WaitCount := Random(20);
for I := 1 to WaitCount do
Application.ProcessMessages;
Continue;
end
else
begin
{The record lock failed for some reason other than another user has the
record locked. Display the BDE error stack and exit.}
ErrorMsg := '';
for I := 0 to Pred(E.ErrorCount) do
ErrorMsg := ErrorMsg + E.Errors[I].Message + ' (' + IntToStr(E.Errors[I].ErrorCode) + '). ';
MessageDlg(ErrorMsg, mtError, [mbOK], 0);
Exit;
end;
end;
end;
if State = dsEdit then
begin
Result := Fields[0].AsInteger + 1;
Fields[0].AsInteger := Result;
Post;
end
else
{If the record could not be locked after the specified number of tries raise an exception.}
raise Exception.Create('Cannot get next unique number. (dgGetUniqueNumber)');
end;
end;
2005. március 29., kedd
Determine the processor speed in MHz
Problem/Question/Abstract:
Determine the processor speed in MHz
Answer:
Here is a handy routine which will return an estimated core processor speed (CPU speed) of your PC. Read the comment to see how to use it.
function GetCpuSpeed: Comp;
{ function to return the CPU clock speed only. }
{ Usage: MessageDlg(Format('%.1f MHz', [GetCpuSpeed]), mtConfirmation, [mbOk], 0); }
var
t: DWORD;
mhi, mlo, nhi, nlo: DWORD;
t0, t1, chi, clo, shr32: Comp;
begin
shr32 := 65536;
shr32 := shr32 * 65536;
t := GetTickCount;
while t = GetTickCount do
begin
end;
asm
DB 0FH
DB 031H
mov mhi,edx
mov mlo,eax
end;
while GetTickCount < (t + 1000) do
begin
end;
asm
DB 0FH
DB 031H
mov nhi,edx
mov nlo,eax
end;
chi := mhi;
if mhi < 0 then
chi := chi + shr32;
clo := mlo;
if mlo < 0 then
clo := clo + shr32;
t0 := chi * shr32 + clo;
chi := nhi;
if nhi < 0 then
chi := chi + shr32;
clo := nlo;
if nlo < 0 then
clo := clo + shr32;
t1 := chi * shr32 + clo;
Result := (t1 - t0) / 1E6;
end;
2005. március 28., hétfő
How to get the free system resources
Problem/Question/Abstract:
How to get the free system resources
Answer:
unit Sysresources;
interface
uses
Windows, Sysutils;
const
GFSR_SYSTEMRESOURCES = 0;
GFSR_GDIRESOURCES = 1;
GFSR_USERRESOURCES = 2;
function GetSystemResources(typ: Word): Integer;
implementation
var
hDll: HMODULE;
pProc: function(typ: word): Integer; stdcall;
function GetSystemResources(typ: word): Integer;
begin
result := pProc(typ);
end;
function InternalGetSystemresources(typ: Word): Integer; stdcall;
begin
result := -1;
end;
initialization
pProc := InternalGetSystemresources;
if Win32Platform <> VER_PLATFORM_WIN32_NT then
begin
hdll := LoadLibrary('rsrc32.dll');
if hdll <> 0 then
begin
@pProc := getProcAddress(hdll, '_MyGetFreeSystemResources32@4');
if @pProc = nil then
pProc := InternalGetSystemresources;
end;
end;
finalization
if hDLL <> 0 then
FreeLibrary(hdll);
end.
2005. március 27., vasárnap
How to control the MIDI speaker output volume
Problem/Question/Abstract:
How can I control the MIDI speaker output volume? If that's not directly possible: how can I programmatically open the volume control?
Answer:
First you need to ID the device
tmpreg := TRegistry.Create;
tmpreg.RootKey := HKEY_CURRENT_USER;
tmpreg.OpenKey('Software\Microsoft\Windows\CurrentVersion\Multimedia\MIDIMap', false);
{ ... }
if tmpreg.ValueExists('CurrentInstrument') then
begin
MidiOutPutDev := tmpreg.ReadString('CurrentInstrument');
end;
tmpreg.destroy;
{ ... }
Then get a handle of the device
amt := MidiOutGetNumDevs;
MidiOutputDevid := -1;
for t := 1 to amt do
begin
MidiOutGetDevCaps(t - 1, @Midicap, Sizeof(Midicap));
if Strpas(@MidiCap.szPName) = MidiOutPutDev then
begin
MidiOutputDevid := t - 1;
end;
end;
Then set the volume either master or seperate
procedure SetVolumeMidi(RVolume, LVolume: Cardinal);
begin
midiOutSetVolume(MidiOutputDevid, (RVolume * 256 * 256) + LVolume);
end;
procedure SetMVolumeWave(Volume: Cardinal);
var
pl, pr: Cardinal;
begin
pr := (WRPan * Volume) div 100;
pl := (WLPan * Volume) div 100;
waveOutSetVolume(WaveOutputDevid, (pr * 256 * 256) + pl);
end;
Include mmsystem in your Uses clause
2005. március 26., szombat
How to convert the mouse coordinates into a line and character offset in a TRichEdit
Problem/Question/Abstract:
I am currently trapping the OnMouseMove event, however have run into significant problems converting the mouse coordinates into a line and character offset in a rich edit.
Answer:
procedure TForm1.RichEdit1MouseMove(Sender: TObject; Shift: TShiftState; X, Y:
Integer);
var
Point: TPoint;
Value: LongInt;
LineNumber: Integer;
LinePos: Integer;
Line: string;
begin
{Get absolute position of character beneath mouse}
Point.x := X;
Point.y := Y;
Value := RichEdit1.Perform(EM_CHARFROMPOS, 0, LParam(@Point));
if Value >= 0 then
begin
{Get line number}
LineNumber := RichEdit1.Perform(EM_LINEFROMCHAR, Value, 0);
{Get line position}
LinePos := Value - RichEdit1.Perform(EM_LINEINDEX, LineNumber, 0);
{Get line}
Line := RichEdit1.Lines[LineNumber];
Label1.Caption := Format('Line: %d Column: %d: %s', [LineNumber, LinePos, Line]);
end
else
begin
Label1.Caption := EmptyStr;
end;
end;
This only works for RichEdits.
2005. március 25., péntek
How to check if a control is partially covered by another window
Problem/Question/Abstract:
Is there a way that I can know if there is a 'Stay On Top' form owned by another application partially covering my control?
Answer:
You would have to iterate over all windows above yours in Z-order and check for each window you find if it has the WS_EX_TOPMOST exstyle set and is visible. If it has, you have to get its window rectangle (GetWindowRect) and test if that overlaps your window. Example:
procedure TForm1.Button1Click(Sender: TObject);
function IsTopmost(wnd: HWND): Boolean;
begin
Result := (GetWindowLong(wnd, GWL_EXSTYLE) and WS_EX_TOPMOST) <> 0;
end;
procedure logWindowInfo(wnd: HWND);
const
visString: array[Boolean] of string = ('not ', '');
var
buffer: array[0..256] of Char;
r: TRect;
begin
if wnd = 0 then
exit;
GetClassname(wnd, buffer, sizeof(buffer));
memo1.lines.add(format(' Window of class %s ', [buffer]));
Windows.getWindowrect(wnd, r);
memo1.lines.add(format(' at (%d,%d):(%d,%d)', [r.left, r.top, r.right,
r.bottom]));
memo1.lines.add(format(' Window is %svisible',
[visString[IsWindowVisible(wnd)]]));
memo1.lines.add(format(' Window is %stopmost', [visString[IsTopmost(wnd)]]));
end;
var
wnd: HWND;
begin
memo1.clear;
wnd := handle;
repeat
wnd := GetNextWindow(wnd, GW_HWNDPREV);
LogWindowInfo(wnd);
until
wnd = 0;
memo1.lines.add('End log');
end;
An easier approach would be to make your own window topmost while it is active.
2005. március 24., csütörtök
How to do greyscale dithering in Delphi
Problem/Question/Abstract:
How to do greyscale dithering in Delphi
Answer:
procedure Greyscale(dib8, dib24: TFastDIB; Colors: Byte);
type
TDiv3 = array[0..767] of Byte;
TScale = array[0..255] of Byte;
TLineErrors = array[-1.. - 1] of DWord;
PDiv3 = ^TDiv3;
PScale = ^TScale;
PLineErrors = ^TLineErrors;
var
x, y, i, Ln, Nxt: Integer;
pc: PFColor;
pb: PByte;
Lines: array[0..1] of PLineErrors;
Div3: PDiv3;
Scale: PScale;
pti: PDWord;
dir: ShortInt;
begin
dib8.FillColors(0, Colors, tfBlack, tfWhite);
New(Div3);
pb := Pointer(Div3);
for i := 0 to 255 do
begin
pb^ := i;
Inc(pb);
pb^ := i;
Inc(pb);
pb^ := i;
Inc(pb);
end;
New(Scale);
pb := Pointer(Scale);
x := (Colors shl 16) shr 8;
y := x;
for i := 0 to 255 do
begin
pb^ := y shr 16;
Inc(y, x);
Inc(pb);
end;
GetMem(Lines[0], 24 * (dib24.Width + 2));
GetMem(Lines[1], 24 * (dib24.Width + 2));
pc := PFColor(dib24.Bits);
for x := 0 to dib24.Width - 1 do
begin
Lines[0, x] := Div3[pc.r + pc.g + pc.b] * 16;
Inc(pc);
end;
pc := Ptr(Integer(pc) + dib24.Gap);
dir := 1;
for y := 1 to dib24.Height do
begin
Nxt := y mod 2;
Ln := 1 - Nxt;
if y < dib24.Height then
begin
for x := 0 to dib24.Width - 1 do
begin
Lines[Nxt, x] := Div3[pc.r + pc.g + pc.b] * 16;
Inc(pc);
end;
pc := Ptr(Integer(pc) + dib24.Gap);
end;
x := 0;
if dir = -1 then
x := dib24.Width - 1;
pti := @Lines[Ln, x];
pb := @dib8.Pixels8[y - 1, x];
while ((x > -1) and (x < dib24.Width)) do
begin
pti^ := pti^ div 16;
if pti^ > 255 then
pti^ := 255
else if pti^ < 0 then
pti^ := 0;
pb^ := Scale[pti^];
i := pti^ - dib8.Colors[pb^].r;
if i <> 0 then
begin
Inc(Lines[Ln, x + dir], i * 7);
Inc(Lines[Nxt, x - dir], i * 3);
Inc(Lines[Nxt, x], i * 5);
Inc(Lines[Nxt, x + dir], i);
end;
Inc(pb, dir);
Inc(pti, dir);
Inc(x, dir);
end;
Inc(pb, dib8.Gap);
dir := -dir;
end;
Dispose(Lines[0]);
Dispose(Lines[1]);
Dispose(Scale);
Dispose(Div3);
end;
2005. március 23., szerda
How to check if a drive is ready
Problem/Question/Abstract:
How to check whether there is a floppy or CD inside the drives?
Answer:
function DiskInDrive(const Drive: char): Boolean;
var
DrvNum: byte;
EMode: Word;
begin
result := false;
DrvNum := ord(Drive);
if DrvNum >= ord('a') then
dec(DrvNum, $20);
EMode := SetErrorMode(SEM_FAILCRITICALERRORS);
try
if DiskSize(DrvNum - $40) <> -1 then
result := true
else
messagebeep(0);
finally
SetErrorMode(EMode);
end;
end;
2005. március 22., kedd
How to display the record number in the indicator rectangle of a TDBGrid
Problem/Question/Abstract:
How to display the record number in the indicator rectangle of a TDBGrid
Answer:
Solve 1:
You can show a record number (in case the dataset supports one) in the indicator's rectangle (check if your grid has a dgIndicator in its Options):
{ ... }
TMyDBGrid = class(TDBGrid)
protected
procedure DrawCell(ACol: Integer; ARow: Integer; ARect: TRect;
AState: TGridDrawState); override;
procedure SetColumnAttributes; override;
end;
{ ... }
procedure TMyDBGrid.DrawCell(ACol: Integer; ARow: Integer; ARect: TRect;
AState: TGridDrawState);
var
XInt: integer;
begin
inherited DrawCell(ACol, ARow, ARect, AState);
if (ACol = 0) and (dgIndicator in Options) and Assigned(DataLink.DataSet)
and (DataLink.DataSet.Active) then
begin
if dgTitles in Options then
if ARow = 0 then
exit
else
dec(ARow);
Canvas.FillRect(ARect);
DataLink.ActiveRecord := ARow;
XInt := DataLink.DataSet.RecNo;
Canvas.TextOut(ARect.Left, ARect.Top, intToStr(XInt));
end;
end;
procedure TMyDBGrid.SetColumnAttributes;
begin
inherited SetColumnAttributes;
if (dgIndicator in Options) then
ColWidths[0] := 20;
end;
This code worked fine for Paradox tables with BDE datasets and for Interbase tables with InterBase Express's TIBTable.
Solve 2:
Drop a TDBGrid on a form. Add all the required columns through the columns editor. Set the fieldname and title caption. Add an extra column and set it right at the top of the columns list, so that this will appear as the first column to display the record number. Don't set a field name for this column. Set any title caption like 'Row No'. Also make sure that the extra added column for displaying the row number is read-only.
procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect;
DataCol: Integer; Column: TColumn; State: TGridDrawState);
begin
if DataCol = 0 then
begin
if table1.State = dsInsert then
begin
if Table1.RecNo < 0 then
DBGrid1.Canvas.TextOut(rect.Left + 2, rect.Top + 3,
IntTostr(Table1.recordcount + 2))
else
DBGrid1.Canvas.TextOut(rect.Left + 2, rect.Top + 3, IntTostr(Table1.RecNo));
end
else
DBGrid1.Canvas.TextOut(rect.Left + 2, rect.Top + 3, IntTostr(Table1.RecNo));
end;
end;
procedure TForm1.DBGrid1ColEnter(Sender: TObject);
begin
if DBGrid1.SelectedIndex = 0 then
DBGrid1.SelectedIndex := 1;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
DBGrid1.SelectedIndex := 1;
end;
2005. március 21., hétfő
Understanding what files are and choosing a Delphi file type - part 2
Problem/Question/Abstract:
What is a File? How are they stored? What format is best for my project? - The second part of a series by Philip Rayment
Answer:
Which file type should I use?
By now I hope you understand that, in theory, you can use any Delphi file type to read and write any file (although using a TextFile to read a file that is not text generally won't work very well). You could, for example, use an untyped file or a file of char, to read and write ASCII text. You could, in theory, use an untyped file to write an executable program file.
In practice, of course, particular file types work better with some files than others. If your file contains just strings of text, TextFile is the obvious choice. Similarly, if your file contains data of a single type (including records), a typed file is the way to go. However, there are always going to be those occasions when you want a file to contain data of various types. For example, a Windows resource file might contain some icons, some bitmaps, and some cursors.
Let us have a look at examples of how to save some employee data with each of the three file types. We will include a file version number at the start of the file. Unless you are sure that your file format will never change, a file version number is a good idea so that your program will know what format the data is in. The Untyped and Text files will also include a value indicating the number of records. This is not essential but assists with reading in the information.
The Typed file does not need this as the number of records can be easily calculated by dividing the size of the file by the size of the type. There is of course more than one way to write and read files; the examples below are not necessarily the only ways. In each of these examples let's assume the following declarations:
type
��PersonRecord� = �packed�record
��ChistianName: �string[15];
����Surname: ������string[15];
����Address1: �����string[30];
����Address2: �����string[30];
����Town: ���������string[15];
����Postcode: �����word;
�� {zip code for Americans}
����Birthdate: ����Tdate;
����YearsService: �byte
����ID: �����������word;
��End;
��� {PersonRecord} {this record is 114 bytes long}
var
People: array�of�PersonRecord;
const
���LatestFileVersion� = �3;
We will use two records with the following values so that we can work out the file sizes for each method:
Record 1
Record 2
ChistianName
Fred
Josephine
Surname
Smith
Black-Forest
Address1
13 Railway Crescent
Flat 16
Address2
144 Carrington Highway
Town:
Smallville
Williamstown East
Postcode
9053
8405
Birthdate
29-2-1952
25-12-1970
YearsService
15
3
ID
14587
34423
In the code examples, the figures in square brackets are the bytes written by each statement for each of the two records.
Example 1: Untyped File
procedure�WriteFile(filename:string);
var
��fil:�file;
��i:�integer;
��num:�word;�����{allows up to 65535 records}
const��ver:����byte�=�LatestFileVersion;
���procedure�WriteString(s:ShortString);
���begin���{WriteString}
�����BlockWrite(fil,s,succ(length(s));
���end;���{WriteString}
begin���{WriteFile}
���assignFile(fil,filename);�rewrite(fil,1); {Create the file}
���BlockWrite(fil,ver,sizeof(ver)); [1]{Write the file version}
���num:=length(People);
���BlockWrite(fil,num,sizeof(num)); [2]{Write the number
of records}
���for�i:=0�to�high(people)�do
�������with�people[i]�do�begin{write the data}
���������WriteString(ChristianName); [5,9]
���������WriteString(Surname); [6,13]
���������WriteString(Address1); [20,8]
���������WriteString(Address2); [1,23]
���������WriteString(Town); [11,18]
���������BlockWrite(fil,Postcode,sizeof(Postcode));[2,2]
���������BlockWrite(fil,Birthdate,sizeof(Birthdate));[8,8]
���������BlockWrite(fil,YearsService,sizeof(YearsService)); [1,1]
���������BlockWrite(fil,ID,sizeof(ID)); [2,2]
�������end;���{with}
���CloseFile(fil);
end;���{WriteFile}
procedure�ReadFile(filename:string);
var����fil:����file;
�������i:������integer;
�������num:����word;�����{allows up to 65535 records}
ver:����byte;
���function�ReadString:ShortString;
���begin���{ReadString}
�����BlockRead(fil,result,1); {Read the length of the string}
�����BlockRead(fil,s[1],length(s)); {Read the string itself}
���end;���{ReadString}
begin���{ReadFile}
���assignFile(fil,filename);�reset(fil,1); {Open the file}
���BlockRead(fil,ver,sizeof(ver)); {Read the file version}
���BlockRead(fil,num,sizeof(num)); {Read the number of records}
���SetLength(People,num);
���for�i:=0�to�high(people)�do
�������with�people[i]�do�begin��{Read the data}
���������ChristianName:=ReadString;
���������Surname:=ReadString;
���������Address1:=ReadString;
���������Address2:=ReadString;
���������Town:=ReadString;
���������BlockRead(fil,Postcode,sizeof(Postcode));
���������BlockRead(fil,Birthdate,sizeof(Birthdate));
���������BlockRead(fil,YearsService,sizeof(YearsService));
���������BlockRead(fil,ID,sizeof(ID));
�������end;���{with}
���CloseFile(fil);
end;���{WriteFile}
Analysis
The total file size is 143 bytes, the smallest of our examples, but the most complex to write. We had to use a temporary variable (ver) as the BlockWrite statement requires variables, not constants. If we later need to increase the maximum length of a surname, for example, changing the record declaration is all that is required.
Example 2: File of Record
procedure�WriteFile(filename:string);
var
��fil:����file�of�PersonRecord;
��i:������integer;
��rec:����PersonRecord
begin���{WriteFile}
���assignFile(fil,filename);�rewrite(fil); {Create file}
���fillchar(rec,sizeof(rec),0); {clear fields (not necessary)}
���rec.postcode:=LatestFileVersion; {any suitable numeric field would do}
���Write(fil,rec); [114] {write a record containing the
file version}
���for�i:=0�to�high(people)�do
�Write(fil,People[i]); [114,114] {Write the data}
���CloseFile(fil);
end;���{WriteFile}
procedure�ReadFile(filename:string);
var�fil:����file�of�PersonRecord;
i:�integer;
��rec:�PersonRecord
��ver:�byte;
begin���{ReadFile}
��assignFile(fil,filename);�reset(fil);��{Open the file}
��Read(fil,rec);��{Read a record containing the file version...}
��ver:=rec.postcode;��{... and extract the file version from it}
��SetLength(people,pred(filesize(fil)�div�sizeof(rec));���{calculate number of records.}
��for�i:=0�to�high(people)�do�Read(fil,People[i]);��{Read the data}
��CloseFile(fil);
end;���{ReadFile}
Analysis
The total file size is 342 bytes, by far the largest of our examples, but also the easiest to write. The space is in the unused parts of the strings, which were designed to hold the largest likely names and addresses, plus in the additional record we used at the start just to hold the file version. If we decide later that we need to allow longer strings, we not only need to change the record definition, but also all the files already written this way . Thus while it is the easiest to write, it is probably the hardest to change.
2005. március 20., vasárnap
How to clip the client area of a form using regions
Problem/Question/Abstract:
I'm trying to produce a form that has transparent areas in it (no border or title with odd shape edges/ holes). I've successfully done this; the problem I'm having is the refreshing of the transparent areas. I have an idea form the conceptual point of what to do, but was hoping some could let me know if this sounds like it would work, and any technical information on how to do it.
I want to pass the WM_PAINT message that my form gets on to the window(s) underneath my form, so that those windows refresh themselves. Then, only after the window(s) beneath my form finish refreshing, I want to refresh my form (act on the WM_PAINT message in a normal manner).
Answer:
While the method you are attempting to use could work, it's much easier to use SetWindowRgn().
This API function will associate an HRGN with your window. This region will be used to determine the area your window is allowed to paint in:
procedure CutOutClient(pForm: TForm);
var
rgnCenter: HRGN;
rcWindow: TRect;
rcClient: TRect;
begin
GetWindowRect(pForm.Handle, rcWindow);
Windows.GetClientRect(pForm.Handle, rcClient);
MapWindowPoints(pForm.Handle, HWND_DESKTOP, rcClient, 2);
OffsetRect(rcClient, -rcWindow.Left, -rcWindow.Top);
rgnCenter := CreateRectRgnIndirect(rcClient);
try
SetWindowRgn(pForm.Handle, rgnCenter, IsWindowVisible(pForm.Handle));
finally
DeleteObject(rgnCenter);
end;
end;
This procedure should clip the client area from your form. To extend this, you simply need to create a different region. See the CreateEllipticRgn, CreatePolygonRgn, CreateRectRgn, and CombineRgn (as well as a few others).
2005. március 19., szombat
How to print the content of a TRichEdit centered on a page
Problem/Question/Abstract:
I have a TDBRichEdit component in D4 and I would like to allow the user to print the selected record centered on a page.
Answer:
It boils down to measuring the text height required for a given width of the printout. This can be done using the EM_FORMATRANGE message, which can also be used to print the formatted text. Here is an example that you can use as a starting point. It measures the text to be able to frame it on the page, you can use the calculated height to vertically center the text by adding to the top border. Printing rich edit contents using EM_FORMATRANGE and EM_DISPLAYBAND:
procedure TForm1.Button2Click(Sender: TObject);
var
printarea: Trect;
x, y: Integer;
richedit_outputarea: TRect;
printresX, printresY: Integer;
fmtRange: TFormatRange;
begin
Printer.beginDoc;
try
with Printer.Canvas do
begin
printresX := GetDeviceCaps(handle, LOGPIXELSX);
printresY := GetDeviceCaps(handle, LOGPIXELSY);
Font.Name := 'Arial';
Font.Size := 14;
Font.Style := [fsBold];
{1 inch left margin / 1.5 inch top
margin / 1 inch right margin / 1.5 inch bottom margin}
printarea := Rect(printresX, printresY * 3 div 2, Printer.PageWidth - printresX,
Printer.PageHeight - printresY * 3 div 2);
x := printarea.left;
y := printarea.top;
TextOut(x, y, 'A TRichEdit print example');
y := y + TextHeight('Ag');
Moveto(x, y);
Pen.Width := printresY div 72; {1 point}
Pen.Style := psSolid;
Pen.Color := clBlack;
LineTo(printarea.Right, y);
Inc(y, printresY * 5 div 72);
{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((printarea.left + 2) * 1440 div printresX, y * 1440
div printresY,
(printarea.right - 4) * 1440 div printresX, (printarea.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; {printer handle}
fmtRange.rc := richedit_outputarea;
fmtRange.rcPage := Rect(0, 0, Printer.PageWidth * 1440 div printresX,
Printer.PageHeight * 1440 div printresY);
fmtRange.chrg.cpMin := 0;
fmtRange.chrg.cpMax := richedit1.GetTextLen - 1;
{First measure the text, to find out how high the format rectangle will be.
The call sets fmtrange.rc.bottom to the actual height required,
if all characters in the selected
range will fit into a smaller rectangle,}
richedit1.Perform(EM_FORMATRANGE, 0, Longint(@fmtRange));
{Draw a rectangle around the format rectangle}
Pen.Width := printresY div 144; {0.5 points}
Brush.Style := bsClear;
Rectangle(printarea.Left, y - 2, printarea.right, fmtrange.rc.bottom * printresY
div 1440 + 2);
{Now render the text}
richedit1.Perform(EM_FORMATRANGE, 1, Longint(@fmtRange));
{and print it}
richedit1.Perform(EM_DISPLAYBAND, 0, Longint(@fmtRange.rc));
y := fmtrange.rc.bottom * printresY div 1440 + printresY * 5 div 72;
{Free cached information}
richedit1.Perform(EM_FORMATRANGE, 0, 0);
TextOut(x, y, 'End of example.');
end;
finally
Printer.EndDoc;
end;
end;
2005. március 18., péntek
How to retrieve the text of a single-line edit control
Problem/Question/Abstract:
How to retrieve the text of a single-line edit control
Answer:
Solve 1:
{ ... }
var
FNEText: array[0..127] of Char;
begin
SendMessage(Edit1.Handle, WM_GETTEXT, Sizeof(FNEText), Integer(@FNEText));
{ ... }
Solve 2:
{ ... }
var
buffer: array[0..$10000] of Char;
len: Integer;
begin
buffer[0] := #0;
len := SendMessage(hFocusWin, WM_GETTEXTLENGTH, 0, 0);
if len > 0 then
SendMessage(hFocusWin, WM_GETTEXT, len + 1, LPARAM(@buffer));
memo1.SetTextBuf(buffer);
{ ... }
2005. március 17., csütörtök
Reading a Field's Value into a TStrings Property
Problem/Question/Abstract:
Reading a Field's Value into a TStrings Property
Answer:
Any programming environment is not without its faults, and Delphi is no exception to this. And while I consider myself to be one of the biggest fans of Delphi, there are still things that are either missing or are so poorly implemented in it, that they make me want to pull my hair out! Of those "things" there are two components that make me rankle: The TDBLookupListBox and the TDBComboBox. On the surface, these components have the potential to be incredibly useful. Load values from a field from one table so they can be used in another. Unfortunately, most people, including myself, have had only marginal success with them. It's not because they don't work, it's just that I feel they're poorly implemented.
Typically, property names should give a good indication of what a property represents. For instance, it's very clear in DataSet components that DatabaseName actually means a database name. Unfortunately in the case of the DBLookup components, the property names are a bit misleading, and it makes using these components a bit unwieldy. For instance, both components have the properties, Field and DataField. If you didn't know any better, you'd think that Field is the lookup field and DataField is the field into which the lookup value is applied. Actually, the converse is true. Furthermore, while the DBLookup components offer incredible flexibility by allowing you specify different display fields in place of the actual data field that will be used for inserting the value, providing these introduce a bit of complexity that while useful, is poorly implemented by, yet again, confusing property names.
Don't get me wrong here. I actually use these components quite a bit becasue I understand how they work and have had a lot of practice using them various applications. But there are some applications where I don't really need lookup and insert capabilities, only lookup capabilities. After all, the DBLookup components are for data entry, and not all applications are data-entry applications. For instance, many of my applications are specifically geared towards data retrieval. But for ease of use, I employ a lot of list boxes and combo boxes based on lookup table data to aid in the selection criteria process. When I'm ready to execute a retrieval, I'm not interested in grabbing field values from a table, all I want to do is get the entered value in the edit boxes or the selected or checked item(s) in a list or combo directly.
So in these cases, I employ a simple list load mechanism that reads data from a table's field and inserts the values into some sort of TStrings property. Mind you, it doesn't have the flexibility of a DBLookup component, but its mere simplicity makes it a much more attractive alternative when doing pure reference types of applications. That said, you'll probably kick me for taking so long to lead into the code, which happens to be moronically simple.
Below are two procedures that I use to load TStrings types of properties. The first employs a TTable to get the values, the second employes a TQuery. I'll discuss the particulars following the code.
// ======================================================================
// This procedure will load a list box with values taken from a specific
// field in a TTable.
// ======================================================================
procedure DBLoadListTbl(dbSource, {database name}
tblSource, {table name}
fldName: string; {field name to load from}
const LBox: TStrings); {List Box on Form}
var
SourceTbl: TTable;
begin
SourceTbl := TTable.Create(Application); {Create an instance of sourceTbl}
with SourceTbl do
begin
Active := False;
DatabaseName := dbSource;
TableName := tblSource;
try
Open;
First;
while not EOF do
begin
LBox.Add(SourceTbl.FieldByName(fldName).AsString);
Next;
end;
finally
Free;
end;
end;
end;
// =======================================================================
// This is a variant on the procedure above. Instead, it uses a TQuery
// =======================================================================
procedure DBLoadListQry(tblSource, {table name}
fldName: string; {field name to load from}
const List: TStrings); {Any TStrings}
var
qry: TQuery;
begin
qry := TQuery.Create(nil);
with qry do
begin
Active := False;
DatabaseName := ExtractFilePath(tblSource);
SQL.Add('SELECT DISTINCT d."' + fldName + '" ');
SQL.Add('FROM "' + tblSource + '" d');
try
Open;
while not EOF do
begin
List.Add(FieldByName(fldName).AsString);
Next;
end;
finally
Free;
end;
end;
end;
Now you might be wondering why in the world I have two procedures that perform almost identical tasks. The reason for this is that with the DBLoadListTbl procedure, there is a complete disregard for duplicate value checking. Simply put, the first procedure has the potential to include duplicate values. The second procedure, DBLoadListQry, on the other hand, employs a SELECT DISTINCT query to remove duplicates. I know, it could be argued that I could probably combine the two procedures into a single one that does duplicate checking, but why bother? While it would probably be much more elegant to do something like that, sometimes just sheer simplicity makes for a much more attractive path to follow. So rather than create a procedure that has a bunch of duplication checking logic, I employ two procedures: One that allows duplicates, another that disallows duplicates. Both of these calls are quick, painless, and don't require a lot thought to implement. And in today's world of short deadlines, I'll take the most simple road over the more complex, elegant solution any day.
2005. március 16., szerda
Oracle and master-detail queries
Problem/Question/Abstract:
Oracle and master-detail queries
Answer:
Just the other day I was writing an application that had master-detail queries, using the TQuery component. The queries were made against an Oracle 8 database using BDE and Delphi 3, but the problem was also present on Oracle 7 and in different oracle set ups. The picture was the following:
QUERY1 (master):
SELECT
A.NAME, A.CODE, B.AREA
FROM
CLIENTS A, AREAS B
WHERE
(A.AREACODE = B.CODE)
Note: CODE is CHAR(10)
QUERY2 (set as detail of QUERY1):
SELECT
A.ORDERNUM, A.DATE, A.VALUE
FROM
ORDERS A
WHERE
(A.CLIENTCODE = :CODE)
Note: CLIENTCODE is CHAR(10)
The problem is that even 'though all the clients with a valid AREACODE would be listed, no order for that client would be listed, even if there were data in the ORDERS table for all the customers. The QUERY2 dataset was always empty (yes, it was Active).
FIXED QUERY2 (set as detail of QUERY1):
SELECT
A.ORDERNUM, A.DATE, A.VALUE
FROM
ORDERS A
WHERE
(RTRIM(A.CLIENTCODE) = RTRIM(:CODE))
This fixed once and for all the problem, and all the orders for each customer were correctly returned by the query. It seemed that when the parameter was passed, it's data type was changed, or some padding was added to the field. Anyway, trimming both fields and comparing only the data part worked.
2005. március 15., kedd
How to assign a TImage to a TBitmap at runtime
Problem/Question/Abstract:
Is it possible to assign a TImage.picture (a JPEG image) to a different TBitmap (created at runtime)? I want to copy the content of a TImage to another bitmap, but it seems to only works for .bmp files.
Answer:
I've done this without any problems. One thing to make sure is that the JPEG unit is in the uses clause. If I understand you correctly you want to do something like:
{ ... }
b := TBitmap.Create;
try
b.Assign(Image1.Picture.Graphic);
Image2.Picture.Graphic := b;
finally
b.Free;
end;
Where the Image1 graphic is a TJPEGImage. This code works for me as long as the JPEG is in
the uses clause.
2005. március 14., hétfő
How to check if a folder contains subfolders
Problem/Question/Abstract:
How to check if a folder contains subfolders
Answer:
function HasSubDirs(dir: string): boolean;
var
sr: TSearchRec;
begin
result := false;
dir := IncludeTrailingBackslash(dir);
if FindFirst(dir + '*.*', faAnyfile, sr) = 0 then
begin
repeat
result := (sr.attr and faDirectory <> 0) and (sr.name <> '.') and (sr.name <>
'..');
until
result or (FindNext(sr) <> 0);
FindClose(sr);
end;
end;
2005. március 13., vasárnap
TCollection The Class for Master Detail Relations
Problem/Question/Abstract:
Getting A master Detail Relation in a component on a way it's easely streamed.
Using a TStream Class Decendant
Answer:
The collection Class is one of my favorit when it comes to storing multiple Data with one component its even posible to include the Collection in its own item making it useful for recursion (The Collection Can have a item withs can hold a other collection.
If you want the standard Editor for Collections u have to use a TOwnedCollection i think its ijn the unit Classes from delphi 4 but if u have D3 u need to Make that class first like this
TOwnedCollection = class(TCollection)
private
FOwner: TPersistent;
protected
function GetOwner: TPersistent; override;
public
// Fil in the AOwner in The Fowner proeprty on the Create Constructor .
constructor Create(AOwner: TPersistent; ItemClass: TCollectionItemClass);
end;
Heres a Example Of a collection That does that
unit Unit1;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TMyCollection = class(TOwnedCollection)
end;
TMyCollectionItem = class(TCollectionItem)
private
FANummer: Integer;
FAString: string;
FMoreCollections: TMyCollection;
procedure SetANummer(const Value: Integer);
procedure SetAString(const Value: string);
procedure SetMoreCollections(const Value: TMyCollection);
public
constructor Create(Collection: TCollection); override;
destructor Destroy; override;
published
property AString: string read FAString write SetAString;
property ANummer: Integer read FANummer write SetANummer;
property MoreCollections: TMyCollection read FMoreCollections write
SetMoreCollections;
end;
TCollectionWrapper = class(TComponent)
private
FCollection: TMyCollection;
procedure SetCollection(const Value: TMyCollection);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property Collection: TMyCollection read FCollection write SetCollection;
end;
implementation
{ TMyCollectionItem }
constructor TMyCollectionItem.Create(Collection: TCollection);
begin
inherited;
FMoreCollections := TMyCollection.Create(self, TMyCollectionItem);
end;
destructor TMyCollectionItem.Destroy;
begin
FMoreCollections.free;
inherited;
end;
procedure TMyCollectionItem.SetANummer(const Value: Integer);
begin
FANummer := Value;
end;
procedure TMyCollectionItem.SetAString(const Value: string);
begin
FAString := Value;
end;
procedure TMyCollectionItem.SetMoreCollections(const Value: TMyCollection);
begin
FMoreCollections := Value;
end;
{ TCollectionWrapper }
constructor TCollectionWrapper.Create(AOwner: TComponent);
begin
inherited;
FCollection := TMyCollection.Create(self, TMyCollectionItem);
end;
destructor TCollectionWrapper.Destroy;
begin
FCollection.free;
inherited;
end;
procedure TCollectionWrapper.SetCollection(const Value: TMyCollection);
begin
FCollection := Value;
end;
end.
This is how you could addres it Run time
procedure TForm1.Button1Click(Sender: TObject);
var
ACollection: TCollectionWrapper;
begin
ACollection := TCollectionWrapper.Create(Self);
try
// Default add gives u a TCollectionItem So u need to cast it
with TMyCollectionItem(ACollection.Collection.Add()) do
begin
AString := 'Hallo';
ANummer := 5;
MoreCollections.add;
end;
finally
ACollection.Free;
end;
end;
If you register this component u will be able to use the default Collection Editor Design time
Component Download: http://www.xs4all.nl/~suusie/Pieter/Programs/CollectionComponent.zip
2005. március 12., szombat
Find the intersection of two polylines
Problem/Question/Abstract:
How to find the intersection of two polylines
Answer:
Solve 1:
You have to intersect each polygon segment set which has a collision of their overlapping rectangles defined by the start and end point of each segment except neigboring segments. That means m-1 * n-1 segments are possible. To make a fast overlapping (collision) set I use a xy hash tree based on quadtree decomposition of the segments. Here is the code for the line intersection:
{XYIntersect Container Intersection for 2 dimensional segments
XII/2001 TriplexWare; Written by A.Weidauer
Abstract: Representation for 2-dimensional segment intersections
Author: Alexander Weidauer (alex.weidauer@huckfinn.de)
Created: December 2001
Lastmod: December 2001
The Unit delivers a 2-dimensional segment intersection for several objects
represented by basic data types for I/O}
unit UXYIntersect;
interface
uses
UConst; {Basic datatype definitions. See further down the page.)
{The function checks a possible segmentation of two segments. The first is defined by
the coordinate set S1( P1(x1, y1): P2(x2, y2)) and the second is
defined S2( P3(x3, y3): P4(x4, y4)) where P1, P2, P3, P4 be the points.
OutX and OutY represent the intersection coordinates and are only valid if
the function turns back the value TRUE. If the segments are paralell the flag
is set to TRUE and if the the segmets are paralell and overlapping eachother
then OutX, OutY keeping the heavy point of the 4 coordinates sets.
In this case you have to check the intervall borders.
The solution of the intersection is NOT a point, it is a SEGMENT again.}
function Isec(x1, y1, x2, y2, x3, y3, x4, y4: TDouble; var OutX, OutY: TDouble;
var ParallelFlag: TBoolean): TBoolean;
implementation
function Isec(x1, y1, x2, y2, x3, y3, x4, y4: TDouble; var OutX, OutY: TDouble;
var ParallelFlag: TBoolean): TBoolean;
var
delta, rmu, l1, l2, l3: TDouble;
begin
OutY := 0;
OutX := 0;
ParallelFlag := False;
x2 := x2 - x1;
y2 := y2 - y1;
x4 := x4 - x3;
y4 := y4 - y3;
delta := x2 * y4 - y2 * x4;
{First case segments are paralell !}
if abs(delta) < 1 E - 8 then
begin
ParallelFlag := False;
x2 := x2 + x1;
x4 := x4 + x3;
y2 := y2 + y1;
y4 := y4 + y3;
l1 := sqrt((x2 - x1) * (x2 - x1) + (y2 - y1) * (y2 - y1));
l2 := sqrt((x3 - x1) * (x3 - x1) + (y3 - y1) * (y3 - y1));
l3 := sqrt((x3 - x2) * (x3 - x2) + (y3 - y2) * (y3 - y2));
if (l1 = l2 - l3) or (l1 = l2 + l3) then
begin
Result := true;
Parallelflag := true;
OutX := (x1 + x2 + x3 + x4) / 4;
OutY := (y1 + y2 + y3 + y4) / 4;
end
else
Result := False;
Exit;
end;
{End of parallel case}
rmu := ((x3 - x1) * y4 - (y3 - y1) * x4) / delta;
if (rmu > 1) or (rmu < 0) then
begin
isec := False;
Exit;
end;
OutX := x1 + RMU * x2;
Outy := y1 + RMU * y2;
x2 := x2 + x1;
x4 := x4 + x3;
y2 := y2 + y1;
y4 := y4 + y3;
if x1 > x2 then
SwapDouble(x1, x2);
if y1 > y2 then
SwapDouble(y1, y2);
if x3 > x4 then
SwapDouble(x3, x4);
if y3 > y4 then
SwapDouble(y3, y4);
{Rangecheck of the solution}
if (outx > x2) or (outx < x1) or (outx > x4) or (outx < x3) or (outy > y2)
or (outy < y1) or (outy > y4) or (outy < y3) then
begin
Result := False;
exit;
end;
Result := True;
end;
end.
{Basic Datatype and file extention definitions
XII/2001TriplaxWare; Written by A.Weidauer
Abstract: Basic Datatype and file extention definitions
Author: Alexander Weidauer (alex.weidauer@huckfinn.de)
Created: December 2001
Lastmod: December 2001
The Unit delivers basic data types for I/O and their file extentions.}
unit UConst;
interface
const
{registry destination set}
cStorageName = 'Software\tsWB';
{Maximal read buffer size for blocked bufferd reading}
cMaxReadBuffer = 32000;
{Maximal write buffer size for blocked bufferd writing}
cMaxWriteBuffer = 32000;
type
{Type encapsulation Boolean}
TBoolean = Boolean;
{Type encapsulation String}
TString = string;
{Type encapsulation Byte 8 Bit}
TByte = Byte;
{Type encapsulation Word 16 Bit}
TWord = Word;
{Type encapsulation LongWord 32 Bit}
TLongWord = LongWord;
{Type encapsulation SmallInt signed 8 Bit}
TInt08 = SmallInt;
{Type encapsulation Integer signed 16 Bit}
TInt16 = ShortInt;
{Type encapsulation Integer signed 32 Bit}
TInt32 = LongInt;
{Type encapsulation Integer signed 32 Bit as common integer}
TInteger = TInt32;
{Type encapsulation Integer signed 64 Bit}
TInt64 = Int64;
{Type encapsulation Single 4 Byte}
TSingle = Single;
{Type encapsulation Real 6 Byte}
TReal = Real48;
{Type encapsulation Double 8 Byte}
TDouble = Double;
{Type encapsulation Double 10 Byte}
TExtended = Extended;
{Swap data if a > b for String}
procedure SwapString(var a, b: TString);
{Swap data if a > b for TByte}
procedure SwapByte(var a, b: TByte);
{Swap data if a > b for TWord}
procedure SwapWord(var a, b: TWord);
{Swap data if a > b for TLongWord}
procedure SwapLongWord(var a, b: TLongWord);
{Swap data if a > b for TInt08}
procedure SwapInt08(var a, b: TInt08);
{Swap data if a > b for TInt16}
procedure SwapInt16(var a, b: TInt16);
{Swap data if a > b for TInt32}
procedure SwapInt32(var a, b: TInt32);
{Swap data if a > b for TInt64}
procedure SwapInt64(var a, b: TInt64);
{Swap data if a > b for single}
procedure SwapSingle(var a, b: TSingle);
{Swap data if a > b for double}
procedure SwapDouble(var a, b: TDouble);
{Swap data if a > b for Extended}
procedure SwapExtended(var a, b: TExtended);
implementation
procedure SwapString(var a, b: TString);
var
r: TString;
begin
r := a;
a := b;
b := r;
end;
procedure SwapByte(var a, b: TByte);
var
r: TByte;
begin
r := a;
a := b;
b := r;
end;
procedure SwapWord(var a, b: TWord);
var
r: TWord;
begin
r := a;
a := b;
b := r;
end;
procedure SwapLongWord(var a, b: TLongWord);
var
r: TLongWord;
begin
r := a;
a := b;
b := r;
end;
procedure SwapInt08(var a, b: TInt08);
var
r: TInt08;
begin
r := a;
a := b;
b := r;
end;
procedure SwapInt16(var a, b: TInt16);
var
r: TInt16;
begin
r := a;
a := b;
b := r;
end;
procedure SwapInt32(var a, b: TInt32);
var
r: TInt32;
begin
r := a;
a := b;
b := r;
end;
procedure SwapInt64(var a, b: TInt64);
var
r: TInt64;
begin
r := a;
a := b;
b := r;
end;
procedure SwapSingle(var a, b: TSingle);
var
r: TSingle;
begin
r := a;
a := b;
b := r;
end;
procedure SwapDouble(var a, b: TDouble);
var
r: TDouble;
begin
r := a;
a := b;
b := r;
end;
procedure SwapExtended(var a, b: TExtended);
var
r: TExtended;
begin
r := a;
a := b;
b := r;
end;
end.
Solve 2:
This function will return the list of points found on a line from (x1,y1) to (x2,y2).
The procedure will calculate the points in the direction the line is drawn. For the line (x1,y1)------ --(x2,y2) or the line (x2,y2)-------(x1,y1) the first point in the list is always (x1,y1) and the last point in the list is always (x2, y2).
Points are calculated along the axis with the most change so that as many points as possible are created for the line.
// The point object
TPointFill = class
X: Integer;
Y: Integer;
end;
// ----------------------------------------------------------------------------
// GetLinePoints
// ----------------------------------------------------------------------------
function GetLinePoints(X1, Y1, X2, Y2: Integer): TList;
var
ChangeInX, ChangeInY, i, MinX, MinY, MaxX, MaxY, LineLength: Integer;
ChangingX: Boolean;
Point: TPointFill;
ReturnList, ReversedList: TList;
begin
ReturnList := TList.Create;
ReversedList := TList.Create;
// Get the change in the X axis and the Max & Min X values
if X1 > X2 then
begin
ChangeInX := X1 - X2;
MaxX := X1;
MinX := X2;
end
else
begin
ChangeInX := X2 - X1;
MaxX := X2;
MinX := X1;
end;
// Get the change in the Y axis and the Max & Min Y values
if Y1 > Y2 then
begin
ChangeInY := Y1 - Y2;
MaxY := Y1;
MinY := Y2;
end
else
begin
ChangeInY := Y2 - Y1;
MaxY := Y2;
MinY := Y1;
end;
// Find out which axis has the greatest change
if ChangeInX > ChangeInY then
begin
LineLength := ChangeInX;
ChangingX := True;
end
else
begin
LineLength := ChangeInY;
ChangingX := false;
end;
// If the x's match then the line changes only on the Y axis
if X1 = X2 then
begin
// Loop thru the points on the list, lowest to highest.
for i := MinY to MaxY do
begin
Point := TPointFill.Create;
Point.X := X1;
Point.Y := i;
ReturnList.Add(Point);
end;
// If the point was started on the right and went to the left then
reverse the list.
if Y1 > Y2 then
begin
ReversedList := ReversePointOrder(ReturnList);
ReturnList := ReversedList;
end;
end
// If the x's match then the line changes only on the Y axis
else if Y1 = Y2 then
begin
// Loop thru the points on the list, lowest to highest.
for i := MinX to MaxX do
begin
Point := TPointFill.Create;
Point.X := i;
Point.Y := Y1;
ReturnList.Add(Point);
end;
// If the point was started on the bottom and went to the top then reverse the list.
if X1 > X2 then
begin
ReversedList := ReversePointOrder(ReturnList);
ReturnList := ReversedList;
end;
end
// The line is on an angle
else
begin
// Add the first point to the list.
Point := TPointFill.Create;
Point.X := X1;
Point.Y := Y1;
ReturnList.Add(Point);
// Loop thru the longest axis
for i := 1 to (LineLength - 1) do
begin
Point := TPointFill.Create;
// If we are moving on the x axis then get the related Y point.
if ChangingX then
begin
Point.y := Round((ChangeInY * i) / ChangeInX);
Point.x := i;
end
// otherwise we are moving on the y axis so get the related X point.
else
begin
Point.y := i;
Point.x := Round((ChangeInX * i) / ChangeInY);
end;
// if y1 is smaller than y2 then we are moving in a Top to Bottom direction.
// we need to add y1 to get the next y value.
if Y1 < Y2 then
Point.y := Point.Y + Y1
// otherwise we are moving in a Bottom to Top direction.
// we need to subtract y1 to get the next y value.
else
Point.Y := Y1 - Point.Y;
// if X1 is smaller than X2 then we are moving in a Left to Right direction
// we need to add x1 to get the next x value
if X1 < X2 then
Point.X := Point.X + X1
// otherwise we are moving in a Right to Left direction
// we need to subtract x1 to get the next x value.
else
Point.X := X1 - Point.X;
ReturnList.Add(Point);
end;
// Add the second point to the list.
Point := TPointFill.Create;
Point.X := X2;
Point.Y := Y2;
ReturnList.Add(Point);
end;
Result := ReturnList;
end;
// ----------------------------------------------------------------------------
// ReversePointOrder
// ----------------------------------------------------------------------------
function ReversePointOrder(LinePointList: TList): TList;
var
i: integer;
NewPointList: TList;
CurrentPointFill: TPointFill;
begin
NewPointList := TList.Create;
i := LinePointList.Count - 1;
while i > -1 do
begin
CurrentPointFill := TPointFill(LinePointList.Items[i]);
NewPointList.Add(CurrentPointFill);
dec(i);
end;
Result := NewPointList;
end;
2005. március 11., péntek
Create caption for TWinControl components
Problem/Question/Abstract:
In microsoft access I can see the Listbox there contains a window caption. how can I create my own components win a caption ?
Answer:
We must not forget that this code will work only in a TWinControl components.
Well, first of all we must declear the procedure of CreateParams in the public section...
Then we go to work !!!
Now you must add this line in the publised area if you wish to add some text to the caption:
property Caption;
Now for the code part:
unit ListboxTest;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls;
type
TListboxTest = class(TListbox)
private
{ Private declarations }
protected
procedure CreateParams(var Params: TCreateParams); override;
public
{ Public declarations }
published
{ Published declarations }
property Caption stored True;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [TListboxTest]);
end;
procedure TListboxTest.CreateParams(var Params: TCreateParams);
begin
inherited CreateParams(Params);
with Params do
begin
Style := Style or WS_CAPTION;
end;
end;
end.
Now we have a caption for our ListBox... And the funny part is that i read while back that VB users payied mony for this kind of OCX component... :)
2005. március 10., csütörtök
How to get a list of all registered typelibs
Problem/Question/Abstract:
Does anybody know how to get a list of all registered typelibs (like the list in the typelib import window in Delphi)? I found a place in the registry (HKCR\TypeLib\...) but a lot of libs are listed more then one time (up to 4 or 5 times) in that place. Do I have to grab all libs from this place? I have found no API (like EnumTypeLibs) that does this.
Answer:
procedure EnumTypeLibs(TypeLibNames: TStringList);
var
f: TRegistry;
keyNames, keyVersions, keyInfos: TStringList;
keyName, keyVersion, keyInfo, tlName: string;
i, j, k: Integer;
begin
TypeLibNames.Clear;
{TypeLibNames.Sorted := True;}
keyNames := nil;
keyVersions := nil;
keyInfos := nil;
f := TRegistry.Create;
try
keyNames := TStringList.Create;
keyVersions := TStringList.Create;
keyInfos := TStringList.Create;
f.RootKey := HKEY_CLASSES_ROOT;
if not f.OpenKey('TypeLib', False) then
raise Exception.Create('TRegistry.Open');
f.GetKeyNames(keyNames);
f.CloseKey;
for i := 0 to keyNames.Count - 1 do
begin
keyName := keyNames.Strings[i];
if not f.OpenKey(Format('TypeLib\%s', [keyName]), False) then
Continue;
f.GetKeyNames(keyVersions);
f.CloseKey;
for j := 0 to keyVersions.Count - 1 do
begin
keyVersion := keyVersions.Strings[j];
if not f.OpenKey(Format('TypeLib\%s\%s', [keyName, keyVersion]), False) then
Continue;
tlName := f.ReadString('');
f.GetKeyNames(keyInfos);
f.CloseKey;
for k := 0 to keyInfos.Count - 1 do
begin
keyInfo := keyInfos.Strings[k];
{$B-}
if (keyInfo = '') or (keyInfo[1] < '0') or (keyInfo[1] > '9') then
Continue;
if not f.OpenKey(Format('TypeLib\%s\%s\%s\win32', [keyName, keyVersion,
keyInfo]), False) then
Continue;
f.CloseKey;
TypeLibNames.Add(Format('%s ver.%s', [tlName, keyVersion]));
end;
end;
end;
finally
f.Free;
keyNames.Free;
keyVersions.Free;
keyInfos.Free;
end;
end;
2005. március 9., szerda
Paint a complete TTreeView on a canvas
Problem/Question/Abstract:
How to paint a complete TTreeView on a canvas
Answer:
I recently implemented a procedure to paint a TTreeView component to a canvas, including the images, state images and so on, and not only the visible nodes, but also those that do not fit in the client area.
unit TreePaint;
interface
uses
Windows, Graphics, ComCtrls;
procedure TreeViewPaintTo(ATreeView: TTreeView; FullExpand: Boolean;
ACanvas: TCanvas; X, Y: Integer);
implementation
procedure TreeViewPaintTo(ATreeView: TTreeView; FullExpand: Boolean;
ACanvas: TCanvas; X, Y: Integer);
var
OffsetX, OffsetY: Integer;
procedure DrawButton(X, Y: Integer; Expanded: Boolean);
var
R: TRect;
begin
ACanvas.Pen.Color := clGray;
ACanvas.Pen.Style := psSolid;
ACanvas.Rectangle(X - 5, Y - 5, X + 4, Y + 4);
ACanvas.Pixels[X + 1, Y - 1] := clBlack;
ACanvas.Pixels[X, Y - 1] := clBlack;
ACanvas.Pixels[X - 1, Y - 1] := clBlack;
ACanvas.Pixels[X - 2, Y - 1] := clBlack;
ACanvas.Pixels[X - 3, Y - 1] := clBlack;
if (not Expanded) then
begin
ACanvas.Pixels[X - 1, Y + 1] := clBlack;
ACanvas.Pixels[X - 1, Y] := clBlack;
ACanvas.Pixels[X - 1, Y - 1] := clBlack;
ACanvas.Pixels[X - 1, Y - 2] := clBlack;
ACanvas.Pixels[X - 1, Y - 3] := clBlack;
end;
end;
procedure DrawHorizLine(X, Y: Integer; HasButton: Boolean);
begin
if (HasButton) then
X := X + 5;
ACanvas.Pixels[X, Y] := clGray;
ACanvas.Pixels[X + 2, Y] := clGray;
ACanvas.Pixels[X + 4, Y] := clGray;
end;
procedure DrawVertLine(X, Y0, Y1: Integer; HasButton: Boolean);
begin
if (HasButton) then
Y0 := Y0 + 5;
while (Y0 <= Y1) do
begin
ACanvas.Pixels[X, Y0] := clGray;
inc(Y0, 2);
end;
end;
procedure TreeNodePaintTo(ATreeNode: TTreeNode; ACanvas: TCanvas);
var
FirstNode: Boolean;
CurNode: TTreeNode;
NewX, NewY, CurX, CurY, StateY, ImageY: Integer;
begin
CurNode := ATreeNode;
FirstNode := True;
while (CurNode <> nil) do
begin
if (not (CurNode.IsVisible or FullExpand)) then
Exit;
{Compute Start X and Y}
NewX := X + (CurNode.Level * OffsetX);
NewY := Y + (OffsetY div 2);
{Line to sibling node}
if (ATreeView.ShowLines) then
begin
if (not FirstNode) then
begin
if (ATreeView.ShowRoot or (CurNode.Level > 0)) then
DrawVertLine(NewX - 1, CurY - (OffsetY div 2) + 1, NewY, True);
end
else
begin
FirstNode := False;
{Line to parent node}
if (CurNode.Parent <> nil) then
DrawVertLine(NewX - 1, Y - (OffsetY div 2) + 1, NewY, True)
end;
end;
{Update Sibling offsets}
CurX := NewX;
CurY := NewY;
if (ATreeView.ShowRoot or (CurNode.Level > 0)) then
begin
if (ATreeView.ShowButtons) then
begin
{Draw the button}
if (CurNode.HasChildren) then
begin
DrawButton(NewX, NewY, FullExpand or CurNode.Expanded);
CurY := CurY + 9;
end;
if (ATreeView.ShowLines) then
DrawHorizLine(NewX, NewY, CurNode.HasChildren);
end
else if (ATreeView.ShowLines) then
DrawHorizLine(NewX, NewY, False);
end;
{Update X Offset}
NewX := NewX + 9;
{State Image}
if (Assigned(ATreeView.StateImages)) then
begin
{Draw the State Image}
StateY := Y + ((OffsetY - ATreeView.StateImages.Height) div 2);
ATreeView.StateImages.Draw(ACanvas, NewX, StateY, CurNode.StateIndex);
{Update X Offset}
NewX := NewX + ATreeView.StateImages.Width;
end;
{Image}
if (Assigned(ATreeView.Images)) then
begin
{Draw the Image}
ImageY := Y + ((OffsetY - ATreeView.Images.Height) div 2);
ATreeView.Images.Draw(ACanvas, NewX, ImageY, CurNode.ImageIndex);
{Update X Offset}
NewX := NewX + ATreeView.Images.Width;
end;
ACanvas.TextOut(NewX, Y, CurNode.Text);
{Update Y Offset}
Y := Y + OffsetY;
{Paint Child Nodes}
if (CurNode.GetFirstChild <> nil) then
TreeNodePaintTo(CurNode.GetFirstChild, ACanvas);
{Paint sibling nodes}
CurNode := CurNode.GetNextSibling;
end;
end;
begin
{Compute Offsets}
OffsetX := 19;
OffsetY := 5 * ACanvas.TextHeight('|') div 4;
if (Assigned(ATreeView.StateImages)) and (ATreeView.StateImages.Height > OffsetY)
then
OffsetY := ATreeView.StateImages.Height;
if (Assigned(ATreeView.Images)) and (ATreeView.Images.Height > OffsetY) then
OffsetY := ATreeView.Images.Height;
if (ATreeView.ShowRoot) then
X := X + 10;
TreeNodePaintTo(ATreeView.Items.GetFirstNode, ACanvas);
end;
end.
2005. március 8., kedd
Filter Table,Query with Exception Handling
Problem/Question/Abstract:
This demonstrates how to filter a table with exception handling and also demonstrates how the overload directive can be used
Answer:
function FilterTable(Data: TQuery; Filter: string): string; overload;
function ExecuteSQL(Data: TQuery; F: TStrings): string;
function ExecuteSQL(Data: TQuery; F: TStrings): string;
var
TSQL: TStrings;
begin
try
TSQL := TStringList.Create;
TSQL.Assign(Data.SQL);
try
Result := Data.Bookmark;
Data.Active := False;
Data.SQL.Assign(F);
Data.Active := True;
except
on EDBEngineError do
begin
Data.SQL.Assign(TSQL);
TSQL.Free;
TSQL := nil;
Data.Active := True;
end;
end; //try except
finally
if TSQL <> nil then
TSQL.Free;
end;
end;
function FilterTable(Data: TTable; Filter: string): string;
begin
try
Result := Data.Bookmark;
Data.Active := False;
Data.Filtered := True;
Data.FilterOptions := [foCaseInsensitive];
Data.Filter := Filter;
Data.Active := True;
except
on EDatabaseError do
begin
Data.Filter := '';
Data.Active := True;
end;
end; //try except
end;
A few routines that can be used to clean up code.
2005. március 7., hétfő
How to open the Windows screen mode dialog
Problem/Question/Abstract:
Is there a way to open the default Windows dialog for screen settings (screen resolution, colors etc.) by a Delphi application?
Answer:
uses
ShellAPI;
{ ... }
ShellExecute(HInstance, nil, PCHAR('rundll32.exe'), PCHAR('shell32.dll,
Control_RunDLL desk.cpl, , 3') { 3 is the tab index }, NIL, 1);
2005. március 6., vasárnap
How to save components to a file or stream
Problem/Question/Abstract:
I have a component TZzz (descends from TComponent) and I want to implement some saving/ restoring capabilities for it. Here's my point. I want to place a TZzz component in a form and export this object to a file. Later, I want import that file to another TZzz object in another form (only for copying the properties from one object to another). Any ideas?
Answer:
Here is a component I wrote which I use often. Simply derive from TPersistentComponent and you can then stream it in and out either directly to a stream or to a file as well. You will have to implement the FindMethod method yourself.
unit Unit1;
interface
uses
Classes, Sysutils;
const
cFileDoesNotExist = 'File Does Not Exist %0s';
cDefaultBufSize = 4096;
type
TPersistComponent = class(TComponent)
private
FStreamLoad: Boolean;
protected
property StreamLoad: Boolean read FSTreamLoad;
procedure FindMethod(Reader: TReader; const MethodName: string; var Address:
Pointer;
var Error: Boolean); virtual;
public
procedure LoadFromFile(const FileName: string; const Init: Boolean = False);
virtual;
procedure SaveToFile(const FileName: string); virtual;
procedure LoadFromStream(Stream: TStream); virtual;
procedure SaveToStream(Stream: TStream); virtual;
end;
implementation
procedure TPersistComponent.FindMethod(Reader: TReader; const MethodName: string;
var Address: Pointer; var Error: Boolean);
begin
Error := False;
end;
procedure TPersistComponent.LoadFromFile(const FileName: string; const Init: Boolean =
False);
var
FS: TFileStream;
begin
if FileExists(Filename) then
begin
FS := TFileStream.Create(FileName, fmOpenRead or fmShareDenyWrite);
try
LoadFromStream(FS);
finally
FS.Free;
end;
end
else
raise Exception.CreateFmt(cFileDoesNotExist, [FileName]);
end;
procedure TPersistComponent.LoadFromStream(Stream: TStream);
var
Reader: TReader;
begin
Reader := TReader.Create(Stream, cDefaultBufSize);
try
{Reader.OnFindMethod := FindMethod;}
FStreamLoad := True;
Reader.OnFindMethod := FindMethod;
Reader.BeginReferences;
Reader.Root := Owner;
Reader.ReadComponent(Self);
Reader.EndReferences;
Loaded;
finally
FStreamLoad := False;
Reader.Free;
end;
end;
procedure TPersistComponent.SaveToFile(const FileName: string);
var
FS: TFileStream;
begin
FS := TFileStream.Create(FileName, fmCreate);
try
SaveToStream(FS);
finally
FS.Free;
end;
end;
procedure TPersistComponent.SaveToStream(Stream: TStream);
var
Writer: TWriter;
begin
Writer := TWriter.Create(Stream, cDefaultBufSize);
try
Writer.Root := Owner;
Writer.WriteComponent(Self);
finally
Writer.Free;
end;
end;
end.
2005. március 5., szombat
How to get a list of all data-aware controls linked to a given TDataSource
Problem/Question/Abstract:
Is there a way to get a list of data-aware controls linked to a given TDataSource component? I need a solution that will work without knowing ahead of time what form to look in for the controls.
Answer:
Try something like the following. This code will scan all components on all forms and determine if the component has a DataSource property. If it does, the value of the DataSource property is assigned to the variable ThisDataSource.
for I := 0 to Screen.FormCount - 1 do
if Screen.Forms[I] is TCustomForm then
with Screen.Forms[I] as TCustomForm do
for J := 0 to ComponentCount - 1 do
if IsPublishedProp(Components[J], 'DataSource') then
begin
ThisDataSource := GetObjectProp(Components[J], 'DataSource') as TDataSource;
if ThisDataSource = SomeOtherDataSource then
{...}
end;
2005. március 4., péntek
How to disable and reenable the Windows start button
Problem/Question/Abstract:
How can I disable the Windows start button and prevent the user from accessing it by clicking on it or by pressing [CTRL] + [ESC] ?
Answer:
Solve 1:
To disable the Start button:
var
h: hwnd;
begin
h := FindWindowEx(FindWindow('Shell_TrayWnd', nil), 0, 'Button', nil);
EnableWindow(h, false);
end;
But the user can still access the start menu by pressing [CTL] + [ESC] or the windows key. Even hiding the Start button doesn't work. But hiding the Start button and using the SetParent function seems to work:
var
h: hwnd;
begin
h := FindWindowEx(FindWindow('Shell_TrayWnd', nil), 0, 'Button', nil)
ShowWindow(h, 0);
Windows.SetParent(h, 0);
end;
To enable the Start button again:
var
h: hwnd;
TaskWindow: hwnd;
begin
h := FindWindowEx(GetDesktopWindow, 0, 'Button', nil);
TaskWindow := FindWindow('Shell_TrayWnd', nil);
Windows.SetParent(h, TaskWindow);
ShowWindow(h, 1);
end;
Furthermore, you could create your own Start button and "replace" it with your own.
var
b: TButton; {or another button type that can hold a bitmap}
h, Window: hwnd;
begin
Window := FindWindow('Shell_TrayWnd', nil);
b := TButton.Create(nil);
b.ParentWindow := Window;
b.Caption := 'Start';
b.Width := 60;
b.font.style := [fsbold];
end;
Solve 2:
procedure TForm1.Button1Click(Sender: TObject);
var
Rgn: hRgn;
begin
{Hide the start button}
Rgn := CreateRectRgn(0, 0, 0, 0);
SetWindowRgn(FindWindowEx(FindWindow('Shell_TrayWnd', nil), 0, 'Button', nil), Rgn,
true);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
{Turn the start button back on}
SetWindowRgn(FindWindowEx(FindWindow('Shell_TrayWnd', nil), 0, 'Button', nil), 0,
true);
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
{Disable the start button}
EnableWindow(FindWindowEx(FindWindow('Shell_TrayWnd', nil), 0, 'Button', nil),
false);
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
{Enable the start button}
EnableWindow(FindWindowEx(FindWindow('Shell_TrayWnd', nil), 0, 'Button', nil),
true);
end;
2005. március 3., csütörtök
Draw text alongside a curve
Problem/Question/Abstract:
How to draw text alongside a curve
Answer:
You need a free rotatable text and the exact measurement of fonts. I use the ATM metrix, but you can also try textwidth or textheight. I use this code under Windows NT to draw symbols along a polygon line.
procedure TDrawer.fOutSymbolXYW(w: Double; x, y: Integer; S: string);
var
nx, ny, xw, xh: Integer;
O, T: TXForm;
sc, cHg, fHg: Double;
begin
nx := 0;
ny := 0;
cHg := fFontMetrix.fY2 - fFontMetrix.fY1; {I use Adobe one}
fHg := TextHeight(S);
if fHG = 0 then
Exit;
Sc := cHg / fHg;
xw := OperateFont(Textwidth(S));
xh := OperateFont(SymbolHeight(S));
case TextJust of
tjCenterTop:
begin
nx := -xw div 2;
ny := 0;
end;
tjCenterBottom:
begin
nx := -xw div 2;
ny := -xh;
end;
tjCenterCenter:
begin
nx := -xw div 2;
ny := -xh div 2;
end;
tjLeftTop:
begin
nx := 0;
ny := 0;
end;
tjLeftCenter:
begin
nx := 0;
ny := -xh div 2;
end;
tjLeftBottom:
begin
nx := 0;
ny := -xh;
end;
tjRightCenter:
begin
nx := -xw;
ny := -xh div 2;
end;
tjRightTop:
begin
nx := -xw;
ny := 0;
end;
tjRightBottom:
begin
nx := -xw;
ny := -xh;
end;
end;
SetGraphicsMode(TheDraw.Handle, GM_Advanced);
T.eM11 := 1 * Cos(w / 360 * Pi * 2);
T.eM22 := 1 * Cos(w / 360 * Pi * 2);
T.eM12 := 1 * Sin(w / 360 * Pi * 2);
T.eM21 := 1 * -Sin(w / 360 * Pi * 2);
T.eDX := X;
T.eDY := Y;
GetWorldTransform(TheDraw.Handle, O);
ModifyWorldTransform(TheDraw.Handle, T, MWT_LEFTMULTIPLY);
{ TheDraw.Pen.Style := psClear;
TheDraw.Rectangle(nx - 1, ny - 1, nx + xw + 3, ny + xh + 2); }
TheDraw.TextOut(nx + OperateFont(FFontMetrix.fX1 / SC), ny -
OperateFont(TextHeight(S)
- SymbolHeight(S) + FFontMetrix.fY1 / sc), S);
{ SetPen(0, 200, 0, 0. 25, psSolid);
TheDraw.Ellipse(nx - 1, ny - 1, nx + 1, ny + 1); }
T.eM11 := 1;
T.eM22 := 1;
T.eM12 := 0;
T.eM21 := 0;
T.eDX := 0;
T.eDY := 0;
SetWorldTransform(TheDraw.Handle, O);
end;
procedure TDrawer.SymbolLine(Poly: TXYPointList; Distance: Double; Offset: Double;
StartAngle: Double; R, G, B: Byte; Lib: string; CharSet: Byte; Size: Double; Style:
TFontStyles;
Sign: Char);
var
i, Segment: Integer;
PosX, PosY, TargetLength, CurrentLength: Double;
{P, pxy: TXYpoint;}
s, c: Double;
Angle: Double;
{Locates the angle of symbol at one linepoint}
procedure LANGLE(j: Integer; var s, c: Double);
var
x1, x2, y1, y2, l: Double;
begin
x1 := Poly.Points[j].x;
x2 := Poly.Points[j - 1].x;
y1 := Poly.Points[j].y;
y2 := Poly.Points[j - 1].y;
l := sqrt(sqr(x2 - x1) + sqr(y2 - y1));
s := (x1 - x2) / l;
c := (y2 - y1) / l;
end;
{Llocates the angle of symbol between to lines linepoints}
procedure SLANGLE(j: Integer; var s, c: Double);
var
x1, x2, y1, y2, l: Double;
begin
x1 := Poly.Points[j - 1].x;
x2 := Poly.Points[j + 1].x;
y1 := Poly.Points[j - 1].y;
y2 := Poly.Points[j + 1].y;
l := sqrt(sqr(x2 - x1) + sqr(y2 - y1));
s := (x1 - x2) / l;
c := (y2 - y1) / l;
end;
function Place(L: Double; var x, y: Double; var Pl: Double; var Index: integer;
var s, c: Double): Boolean;
var
x1, x2, y1, y2: Real;
l1, l2: Real;
j: Integer;
begin
Place := False;
if L < 0 then
Exit;
j := index;
while (l >= Pl) and (j < Poly.MaxPoint) do
begin
inc(j);
x1 := Poly.Points[j - 1].x;
x2 := Poly.Points[j].x;
y1 := Poly.Points[j - 1].y;
y2 := Poly.Points[j].y;
pl := pl + sqrt(sqr(x2 - x1) + sqr(y2 - y1));
end;
if not (l < Pl) and (j >= Poly.MaxPoint) then
Exit;
if (l = pl) then
begin
X := Poly.Points[j].X;
Y := Poly.Points[j].Y;
if j = Poly.MaxPoint then
LAngle(j, s, c)
else if j = 1 then
LAngle(2, s, c)
else
SLAngle(j, s, c);
Exit;
end;
x1 := Poly.Points[j - 1].x;
x2 := Poly.Points[j].x;
y1 := Poly.Points[j - 1].y;
y2 := Poly.Points[j].y;
l1 := sqrt(sqr(x2 - x1) + sqr(y2 - y1));
if j < 3 then
l2 := l
else
l2 := l - (Pl - l1);
x := l2 / l1 * (x2 - x1) + Poly.Points[j - 1].x;
y := l2 / l1 * (y2 - y1) + Poly.Points[j - 1].y;
if j <> index then
LANGLE(j, s, c);
index := j;
Place := True;
end;
begin
SetSymbols(R, G, B, LIB, Charset, Size, Style);
if Distance = 0 then
Distance := 1;
CurrentLength := 0;
TargetLength := Poly.PolyLength;
Segment := 1;
i := -1;
if (Poly.MaxPoint < 2) or (Poly.PolyLength < Distance + Offset) then
Exit;
repeat
Inc(i);
if Place(Offset + Distance * i, PosX, PosY, CurrentLength, Segment, S, C) then
begin
Angle := ArcTan2(S, C) / 2 / Pi * 360;
OutSymbolXYW(Angle + StartAngle, PosX, PosY, Sign);
end;
until
Offset + Distance * i >= TargetLength;
end;
2005. március 2., szerda
Rotate an ellipse
Problem/Question/Abstract:
How to draw a rotated Ellipse?
Answer:
I wrote a procedure "CentralRotatedEllipse" to rotate an ellipse. It works exactly enougth for simple graphics. The Ellipse is maked with two connected beziercurves. Rotatingpoint of the Ellipse is its centralpoint. The Parameter canvas for the Destinationcanvas, coordinates like "common" Ellipse and in alpha the rotatingangle. The function "Rotate2DPoint" you have to put in your code too, its called by the CentralRotatedEllipse-Procedure.
And dont forget uses Math!
function Rotate2DPoint(P, Fix: TPoint; alpha: double): TPoint;
var
sinus, cosinus: Extended;
begin
SinCos(alpha, sinus, cosinus);
P.x := P.x - Fix.x;
P.y := P.y - Fix.y;
result.x := Round(p.x * cosinus + p.y * sinus) + fix.x;
result.y := Round(-p.x * sinus + p.y * cosinus) + Fix.y;
end;
procedure CentralRotatedEllipse(Canvas: TCanvas; x1, y1, x2, y2: Integer; alpha:
Extended);
var
PointList: array[0..6] of TPoint;
f: TPoint;
dk: Integer;
begin
dk := Round(0.654 * Abs(y2 - y1));
f.x := x1 + (x2 - x1) div 2;
f.y := (y1 + (y2 - y1) div 2) - 1;
PointList[0] := Rotate2DPoint(Point(x1, f.y), f, Alpha); // Startpoint
PointList[1] := Rotate2DPoint(Point(x1, f.y - dk), f, Alpha);
//Controlpoint of Startpoint first part
PointList[2] := Rotate2DPoint(Point(x2 - 1, f.y - dk), f, Alpha);
//Controlpoint of secondpoint first part
PointList[3] := Rotate2DPoint(Point(x2 - 1, f.y), f, Alpha);
// Firstpoint of secondpart
PointList[4] := Rotate2DPoint(Point(x2 - 1, f.y + dk), f, Alpha);
// Controllpoint of secondpart firstpoint
PointList[5] := Rotate2DPoint(Point(x1, f.y + dk), f, Alpha);
// Conrollpoint of secondpart endpoint
PointList[6] := PointList[0]; // Endpoint of
// Back to the startpoint
PolyBezier(canvas.handle, Pointlist[0], 7);
end;
Example:
CentralRotatedEllipse(Canvas, 100, 100, 150, 300, DegToRad(45));
CentralRotatedEllipse(Canvas, 100, 100, 150, 300, DegToRad(90));
Angle always should be in Rad.
2005. március 1., kedd
Making Any-Shaped Form ( The Hard-Code )
Problem/Question/Abstract:
Wel, this is a hard-coded application, ONLY for people interested in knowing more, it describes another way of doing starnge shaped forms !!
Answer:
Well, Declare these 2 sentences to your PROTECTED declaration
procedure EvEraseBkgnd(var M: tMessage); message WM_ERASEBKGND;
procedure EvNcHitTest(var M: tMessage); message WM_NCHITTEST;
Then Of Course, add them in the body code !!
procedure tForm1.EvEraseBkgnd(var M: tMessage);
begin
{ No Erase Window Background.... }
M.Result := 1;
end;
procedure tForm1.EvNcHitTest(var M: tMessage);
begin
inherited;
{ If Hit in Client Area then simulate hit in Caption Area }
if M.Result = HTCLIENT then
M.Result := HTCAPTION;
end;
make the following OnFormPaint Procedure..
procedure TForm1.FormPaint(Sender: TObject);
var
Buffer: tBitmap;
begin
Buffer := tBitmap.create;
Buffer.LoadFromResourceName(hinstance, 'FORM');
Bitblt(Canvas.handle, 0, 0,
Buffer.width, Buffer.height,
Buffer.canvas.handle, 0, 0,
SrcCopy);
Buffer.free;
end;
And On Your Form, Put Any Buttons Or TEdits Or Anything You Want To Add, Try Your Form, I think it is working just as it used to work all the time, that is true, this is not the secret, the big part is here
OnFormCreate Procedure Needs To Be Added :-))
procedure TForm1.FormCreate(Sender: TObject);
var
Region1: array of tPoint;
Region1hrgn: hRgn;
begin
SetLength(Region1, 59);
Region1[0].X := 12;
Region1[0].Y := 6;
Region1[1].X := 484;
Region1[1].Y := 6;
Region1[2].X := 484;
Region1[2].Y := 7;
Region1[3].X := 486;
Region1[3].Y := 7;
Region1[4].X := 486;
Region1[4].Y := 8;
Region1[5].X := 487;
Region1[5].Y := 8;
Region1[6].X := 487;
Region1[6].Y := 9;
Region1[7].X := 488;
Region1[7].Y := 9;
Region1[8].X := 488;
Region1[8].Y := 10;
Region1[9].X := 489;
Region1[9].Y := 10;
Region1[10].X := 489;
Region1[10].Y := 12;
Region1[11].X := 490;
Region1[11].Y := 12;
Region1[12].X := 490;
Region1[12].Y := 285;
Region1[13].X := 489;
Region1[13].Y := 285;
Region1[14].X := 489;
Region1[14].Y := 287;
Region1[15].X := 488;
Region1[15].Y := 287;
Region1[16].X := 488;
Region1[16].Y := 288;
Region1[17].X := 487;
Region1[17].Y := 288;
Region1[18].X := 487;
Region1[18].Y := 289;
Region1[19].X := 486;
Region1[19].Y := 289;
Region1[20].X := 486;
Region1[20].Y := 290;
Region1[21].X := 484;
Region1[21].Y := 290;
Region1[22].X := 484;
Region1[22].Y := 291;
Region1[23].X := 101;
Region1[23].Y := 291;
Region1[24].X := 100;
Region1[24].Y := 290;
Region1[25].X := 99;
Region1[25].Y := 290;
Region1[26].X := 98;
Region1[26].Y := 289;
Region1[27].X := 97;
Region1[27].Y := 288;
Region1[28].X := 96;
Region1[28].Y := 287;
Region1[29].X := 95;
Region1[29].Y := 286;
Region1[30].X := 95;
Region1[30].Y := 284;
Region1[31].X := 94;
Region1[31].Y := 283;
Region1[32].X := 94;
Region1[32].Y := 200;
Region1[33].X := 93;
Region1[33].Y := 199;
Region1[34].X := 93;
Region1[34].Y := 198;
Region1[35].X := 92;
Region1[35].Y := 197;
Region1[36].X := 91;
Region1[36].Y := 196;
Region1[37].X := 90;
Region1[37].Y := 195;
Region1[38].X := 89;
Region1[38].Y := 194;
Region1[39].X := 88;
Region1[39].Y := 194;
Region1[40].X := 87;
Region1[40].Y := 193;
Region1[41].X := 14;
Region1[41].Y := 193;
Region1[42].X := 13;
Region1[42].Y := 192;
Region1[43].X := 12;
Region1[43].Y := 192;
Region1[44].X := 11;
Region1[44].Y := 191;
Region1[45].X := 10;
Region1[45].Y := 190;
Region1[46].X := 9;
Region1[46].Y := 189;
Region1[47].X := 8;
Region1[47].Y := 188;
Region1[48].X := 8;
Region1[48].Y := 187;
Region1[49].X := 7;
Region1[49].Y := 186;
Region1[50].X := 7;
Region1[50].Y := 184;
Region1[51].X := 6;
Region1[51].Y := 183;
Region1[52].X := 6;
Region1[52].Y := 12;
Region1[53].X := 7;
Region1[53].Y := 11;
Region1[54].X := 7;
Region1[54].Y := 10;
Region1[55].X := 8;
Region1[55].Y := 9;
Region1[56].X := 9;
Region1[56].Y := 8;
Region1[57].X := 10;
Region1[57].Y := 7;
Region1[58].X := 11;
Region1[58].Y := 7;
Region1hrgn := CreatePolygonRgn(Region1[0], 59, 2);
SetWindowRgn(Handle, Region1hrgn, True);
end;
Feliratkozás:
Bejegyzések (Atom)