2007. február 28., szerda
How to set the master volume
Problem/Question/Abstract:
How can I set the master volume? I don't want to use an external file like a DLL.
Answer:
Solve 1:
The "Mixer" parameter of SetMasterVolume has to be either a mixer device ID in the range 0..mixerGetNumDevs-1 or a mixer handle returned by a call to mixerOpen().
interface
uses
SysUtils, Windows, MMSystem;
procedure SetMasterVolume(Mixer: hMixerObj; Value: Word);
implementation
function GetMasterVolumeControl(Mixer: hMixerObj; var Control: TMixerControl): MMResult;
{Returns True on success}
var
Line: TMixerLine;
Controls: TMixerLineControls;
begin
ZeroMemory(@Line, SizeOf(Line));
Line.cbStruct := SizeOf(Line);
Line.dwComponentType := MIXERLINE_COMPONENTTYPE_DST_SPEAKERS;
Result := mixerGetLineInfo(Mixer, @Line, MIXER_GETLINEINFOF_COMPONENTTYPE);
if Result = MMSYSERR_NOERROR then
begin
ZeroMemory(@Controls, SizeOf(Controls));
Controls.cbStruct := SizeOf(Controls);
Controls.dwLineID := Line.dwLineID;
Controls.cControls := 1;
Controls.dwControlType := MIXERCONTROL_CONTROLTYPE_VOLUME;
Controls.cbmxctrl := SizeOf(Control);
Controls.pamxctrl := @Control;
Result := mixerGetLineControls(Mixer, @Controls, MIXER_GETLINECONTROLSF_ONEBYTYPE);
end;
end;
procedure SetMasterVolume(Mixer: hMixerObj; Value: Word);
var
MasterVolume: TMixerControl;
Details: TMixerControlDetails;
UnsignedDetails: TMixerControlDetailsUnsigned;
Code: MMResult;
begin
Code := GetMasterVolumeControl(Mixer, MasterVolume);
if Code = MMSYSERR_NOERROR then
begin
with Details do
begin
cbStruct := SizeOf(Details);
dwControlID := MasterVolume.dwControlID;
cChannels := 1; {set all channels}
cMultipleItems := 0;
cbDetails := SizeOf(UnsignedDetails);
paDetails := @UnsignedDetails;
end;
UnsignedDetails.dwValue := Value;
Code := mixerSetControlDetails(Mixer, @Details, MIXER_SETCONTROLDETAILSF_VALUE);
end;
if Code <> MMSYSERR_NOERROR then
raise Exception.CreateFmt('SetMasterVolume failure, ' + 'multimedia system error #%d', [Code]);
end;
Solve 2:
uses
MMSystem;
function GetVolumeControl(aMixer: HMixer; componentType, ctrlType: Longint;
var mxc: TMixerControl): Boolean;
var
mxl: TMixerLine;
mxlc: TMixerLineControls;
rc: Longint;
begin
Result := FALSE;
FillChar(mxl, SizeOf(TMixerLine), 0);
mxl.cbStruct := SizeOf(TMixerLine);
mxl.dwComponentType := componentType;
{Obtain a line corresponding to the component type}
rc := mixerGetLineInfo(aMixer, @mxl, MIXER_GETLINEINFOF_COMPONENTTYPE);
if rc = MMSYSERR_NOERROR then
begin
mxlc.cbStruct := SizeOf(TMixerLineControls);
mxlc.dwLineID := mxl.dwLineID;
mxlc.dwControlType := ctrlType;
mxlc.cControls := 1;
mxlc.cbmxctrl := SizeOf(TMixerLine);
mxlc.pamxctrl := @mxc;
mxlc.pamxctrl^.cbStruct := SizeOf(TMixerControl);
mixerGetLineControls(aMixer, @mxlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);
rc := mixerGetLineControls(aMixer, @mxlc, MIXER_GETLINECONTROLSF_ONEBYTYPE);
Result := rc = MMSYSERR_NOERROR;
end;
end;
function SetVolumeControl(aMixer: HMixer; mxc: TMixerControl; volume:
LongInt): Boolean;
var
mxcd: TMixerControlDetails;
vol: TMixerControlDetails_Unsigned;
rc: MMRESULT;
begin
FillChar(mxcd, SizeOf(mxcd), 0);
mxcd.dwControlID := mxc.dwControlID;
mxcd.cbStruct := SizeOf(TMixerControlDetails);
mxcd.cbDetails := SizeOf(TMixerControlDetails_Unsigned);
mxcd.paDetails := @vol;
mxcd.cChannels := 1;
vol.dwValue := volume;
rc := mixerSetControlDetails(aMixer, @mxcd, MIXER_SETCONTROLDETAILSF_VALUE);
Result := rc = MMSYSERR_NOERROR;
end;
function InitMixer: HMixer;
var
Err: MMRESULT;
begin
Err := mixerOpen(@Result, 0, 0, 0, 0);
if Err <> MMSYSERR_NOERROR then
Result := 0;
end;
Usage example:
procedure SetMasterVolumeToZero;
var
MyMixerHandle: HMixer;
MyVolCtrl: TMixerControl;
begin
MyMixerHandle := InitMixer;
if MyMixerHandle <> 0 then
try
FillChar(MyVolCtrl, SizeOf(MyVolCtrl), 0);
if GetVolumeControl(MyMixerHandle, MIXERLINE_COMPONENTTYPE_DST_SPEAKERS,
MIXERCONTROL_CONTROLTYPE_VOLUME, MyVolCtrl) then
begin
{The last parameter (0) here is the volume level}
if SetVolumeControl(MyMixer, MyVolCtrl, 0) then
ShowMessage('Volume should now be set to zero');
end;
finally
mixerClose(MyMixer);
end;
end;
2007. február 27., kedd
Calculate a TColor between two other TColors
Problem/Question/Abstract:
I needed a function that calculates a TColor-Value between two others from an Extended. This Extended should be limited from two other Extended Variables.
Answer:
Solve 1:
I needed a function that calculates a TColor-Value between two others from an Extended. This Extended should be limited from two other Extended Variables. All Values of "Pointvalue" less then "von" will return "Startcolor" and all Pointvalues greater than "Bis" gives EndColor back. Ist written with some Inline- Assemblercode. I think maybee its could be usefull for somebody else.
function GetColorBetween(StartColor, EndColor: TColor; Pointvalue, Von, Bis:
Extended): TColor;
var
F: Extended;
r1, r2, r3, g1, g2, g3, b1, b2, b3: Byte;
function CalcColorBytes(fb1, fb2: Byte): Byte;
begin
result := fb1;
if fb1 < fb2 then
Result := FB1 + Trunc(F * (fb2 - fb1));
if fb1 > fb2 then
Result := FB1 - Trunc(F * (fb1 - fb2));
end;
begin
if Pointvalue <= Von then
begin
result := StartColor;
exit;
end;
if Pointvalue >= Bis then
begin
result := EndColor;
exit;
end;
F := (Pointvalue - von) / (Bis - Von);
asm
mov EAX, Startcolor
cmp EAX, EndColor
je @@exit
mov r1, AL
shr EAX,8
mov g1, AL
shr Eax,8
mov b1, AL
mov Eax, Endcolor
mov r2, AL
shr EAX,8
mov g2, AL
shr EAX,8
mov b2, AL
push ebp
mov al, r1
mov dl, r2
call CalcColorBytes
pop ecx
push ebp
Mov r3, al
mov dL, g2
mov al, g1
call CalcColorBytes
pop ecx
push ebp
mov g3, Al
mov dL, B2
mov Al, B1
call CalcColorBytes
pop ecx
mov b3, al
XOR EAX,EAX
mov AL, B3
SHL EAX,8
mov AL, G3
SHL EAX,8
mov AL, R3
@@Exit:
mov @result, eax
end;
end;
Solve 2:
//------------------------------------------------------------------------------
// Function for getting mixed color from two given colors, with a relative
// distance from two colors determined by Position value inside
// MinPosition..MaxPosition range
// Author: Dmitri Papichev (c) 2001
// License type: Freeware
//------------------------------------------------------------------------------
function GetMixedColor(const StartColor,
EndColor: TColor;
const MinPosition,
Position,
MaxPosition: integer): TColor;
var
Fraction: double;
R, G, B,
R0, G0, B0,
R1, G1, B1: byte;
begin
{process Position out of range situation}
if (MaxPosition < MinPosition) then
begin
raise Exception.Create
('GetMixedColor: MaxPosition is less then MinPosition');
end; {if}
{if Position is outside MinPosition..MaxPosition range, the closest boundary
is effectively substituted through the adjustment of Fraction}
Fraction :=
Min(1, Max(0, (Position - MinPosition) / (MaxPosition - MinPosition)));
{extract the intensity values}
R0 := GetRValue(StartColor);
G0 := GetGValue(StartColor);
B0 := GetBValue(StartColor);
R1 := GetRValue(EndColor);
G1 := GetGValue(EndColor);
B1 := GetBValue(EndColor);
{calculate the resulting intensity values}
R := R0 + Round((R1 - R0) * Fraction);
G := G0 + Round((G1 - G0) * Fraction);
B := B0 + Round((B1 - B0) * Fraction);
{combine intensities in a resulting color}
Result := RGB(R, G, B);
end; {--GetMixedColor--}
2007. február 26., hétfő
Read the current code page of system
Problem/Question/Abstract:
How to read the code page of system?
Answer:
Sometimes in run-time you must detect the current values of code page.
To detect the code page of Windows operation system you must call the GetACP function from Windows API.
This function will return the value:
874 Thai
932 Japan
936 Chinese (PRC, Singapore)
949 Korean
950 Chinese (Taiwan, Hong Kong)
1200 Unicode (BMP of ISO 10646)
1250 Windows 3.1 Eastern European
1251 Windows 3.1 Cyrillic
1252 Windows 3.1 Latin 1 (US, Western Europe)
1253 Windows 3.1 Greek
1254 Windows 3.1 Turkish
1255 Hebrew
1256 Arabic
1257 Baltic
If you needs to read the code page of "DOS" sessions, you must call the GetOEMCP function from Windows API.
This function will return the value:
437 MS-DOS United States
708 Arabic (ASMO 708)
709 Arabic (ASMO 449+, BCON V4)
710 Arabic (Transparent Arabic)
720 Arabic (Transparent ASMO)
737 Greek (formerly 437G)
775 Baltic
850 MS-DOS Multilingual (Latin I)
852 MS-DOS Slavic (Latin II)
855 IBM Cyrillic (primarily Russian)
857 IBM Turkish
860 MS-DOS Portuguese
861 MS-DOS Icelandic
862 Hebrew
863 MS-DOS Canadian-French
864 Arabic
865 MS-DOS Nordic
866 MS-DOS Russian (former USSR)
869 IBM Modern Greek
874 Thai
932 Japan
936 Chinese (PRC, Singapore)
949 Korean
950 Chinese (Taiwan, Hong Kong)
1361 Korean (Johab)
Also you can check the valids of code page. For example,
if IsValidCodePage(866) then
ShowMessage('Correct MS-DOS russian code page')
2007. február 25., vasárnap
Create a thumbnail from a JPEG image
Problem/Question/Abstract:
Once I was trying to resize a jpeg image and made some Internet search. Believe or not, I couldn't find clear answers to my question, but it's very easy to do.
Answer:
The code below will reduce width and height of a chosen .jpg image.
Go to "File / New / Console Application" and paste this code. Set the SizePct (a const on the code below, but can be a variable on your program) to fit your needs. If you want a new image with 30% of the original width and height set this to 30.
All I do is load the JPEG on a TJPEGImage, create a bitmap and .StretchDraw the JPEG on the bitmap. Then I copy the bitmap to a TJPEGImage using the .Assign method, and, finally, save it.
program Project1;
{$APPTYPE CONSOLE}
uses
Classes, Windows, SysUtils, Dialogs, JPEG, Graphics;
const
SizePct: integer = 50; { The new image will have 50% of the original }
var
OpenDlg: TOpenDialog;
SaveDlg: TSaveDialog;
oJPG: TJPEGImage;
oBmp: TBitmap;
begin
OpenDlg := TOpenDialog.Create(nil);
SaveDlg := TSaveDialog.Create(nil);
if (OpenDlg.Execute) then
begin
try
begin
oJPG := TJPEGImage.Create;
oJPG.LoadFromFile(OpenDlg.FileName);
end
except
MessageBox(
0,
PChar('Error while trying to open ' +
OpenDlg.FileName +
'.'),
PChar('Error'),
MB_OK or MB_ICONERROR
);
exit;
end;
oBmp := TBitmap.Create;
oBmp.Width := Round(oJPG.Width * SizePct / 100);
oBmp.Height := Round(oJPG.Height * SizePct / 100);
oBmp.Canvas.StretchDraw(
Rect(0, 0, oBmp.Width - 1, oBmp.Height - 1),
oJPG
);
oJPG.Assign(oBmp);
oJPG.Compress;
if (SaveDlg.Execute) then
begin
oJPG.SaveToFile(SaveDlg.FileName);
end;
oBmp.Free;
oJPG.Free;
end;
OpenDlg.Free;
SaveDlg.Free;
end.
2007. február 24., szombat
How to run an application in systray mode
Problem/Question/Abstract:
How to run an application in systray mode
Answer:
Solve 1:
Nothing special. It's just a normal application hiding all of its forms and displaying an icon in the systray. The shell takes care of displaying the icon. Just send a message with all info to the shell. Here is an example:
var
Nid: TNOTIFYICONDATA;
prodecure ShowTrayIcon();
begin
nid.cbSize := sizeof(TNOTIFYICONDATA);
nid.Wnd := Form1.Handle;
nid.uID := 1;
nid.uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
nid.uCallbackMessage := WM_MYMESSAGE; {or make it Nil if you don't need it}
nid.hIcon := LoadIcon(0, IDI_EXCLAMATION); {replace this by your icon}
lstrcpy(nid.szTip, 'This is my hint');
Shell_NotifyIcon(NIM_ADD, @nid);
end;
Only have Win32 code for the callback:
function WndProc(hwnd: HWND; msg: integer; wParam: WPARAM;
lParam: LPARAM): LRESULT; stdcall;
begin
case msg of
WM_MYMESSAGE:
begin
case (LOWORD(lParam)) of
WM_LBUTTONDOWN:
begin
MessageBox(hwnd, 'You pressed the left mouse button', 'Caption',
MB_YESNO or MB_SETFOREGROUND or MB_SYSTEMMODAL);
end;
WM_RBUTTONDOWN:
begin
PostQuitMessage(0);
result := 0;
end
else
begin
result := 0;
end;
end;
result := 1; {true}
end;
WM_CLOSE:
begin
PostQuitMessage(0);
result := 0;
{Exit;}
end;
WM_DESTROY:
begin
PostQuitMessage(0);
result := 0;
{Exit;}
end;
end;
result := DefWindowProc(hwnd, msg, wParam, lParam);
end;
Solve 2:
Try this and don't forget the TImageList holding your icon:
unit Main;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
Shellapi, ImgList, VersionMonitor;
type
TForm1 = class(TForm)
ImageList1: TImageList;
procedure FormCreate(Sender: TObject);
procedure FormDestroy(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: Boolean);
private
{ Private Declarations }
Data: PNotifyIconData;
wm_notifyicon: Cardinal;
notifyHandle: THandle;
procedure MyOnClose(var Message: TMessage); message WM_CLOSE;
public
{ Public Declarations }
procedure NotifyIconEvnt(var Param: TMessage);
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.FormCreate(Sender: TObject);
var
aIcon: TIcon;
begin
wm_notifyicon := RegisterWindowMessage('wm_notifyicon');
notifyHandle := AllocateHWnd(NotifyIconEvnt);
aIcon := TIcon.Create;
ImageList1.GetIcon(0, aIcon);
new(Data);
Data.cbSize := sizeof(TNotifyIconData);
Data.Wnd := notifyHandle;
Data.uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
Data.uCallbackMessage := WM_NOTIFYICON;
Data.hIcon := aIcon.handle;
StrCopy(Data.szTip, 'Tooltip hint');
Shell_NotifyIcon(NIM_ADD, Data);
SetWindowLong(Application.Handle, GWL_EXSTYLE, WS_EX_TOOLWINDOW);
Form1.Visible := False;
end;
procedure TForm1.FormDestroy(Sender: TObject);
begin
Shell_NotifyIcon(NIM_DELETE, Data);
DeallocateHWnd(notifyHandle);
dispose(Data);
end;
procedure TForm1.MyOnClose(var Message: TMessage);
begin
Beep;
Close;
end;
procedure TForm1.NotifyIconEvnt(var Param: TMessage);
begin
case Param.LParam of
WM_LBUTTONDOWN:
begin
Form1.Visible := True;
end;
WM_RBUTTONDOWN:
begin
Form1.Visible := False;
end;
WM_CLOSE:
begin
DeallocateHWnd(notifyHandle);
Close;
end;
end;
end;
procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
DeallocateHWnd(notifyHandle);
CanClose := True;
end;
end.
2007. február 23., péntek
How to get the image size of a JPG, GIF and PNG image file
Problem/Question/Abstract:
How to get the image size of a JPG, GIF and PNG image file
Answer:
Solve 1:
This set of functions shows how to extract the dimensions (width and height) of a JPG, GIF and PNG file. This code was done quite a while back and while it works fine for my purposes, it may be not handle some of the newer stuff like progressive JPEGs and such. Experimentation is highly recommened.
unit ImgSize;
interface
uses Classes;
procedure GetJPGSize(const sFile: string; var wWidth, wHeight: word);
procedure GetPNGSize(const sFile: string; var wWidth, wHeight: word);
procedure GetGIFSize(const sGIFFile: string; var wWidth, wHeight: word);
implementation
uses SysUtils;
function ReadMWord(f: TFileStream): word;
type
TMotorolaWord = record
case byte of
0: (Value: word);
1: (Byte1, Byte2: byte);
end;
var
MW: TMotorolaWord;
begin
{It would probably be better to just read these two bytes in normally and
then do a small ASM routine to swap them. But we aren't talking about
reading entire files, so I doubt the performance gain would be worth the trouble.}
f.Read(MW.Byte2, SizeOf(Byte));
f.Read(MW.Byte1, SizeOf(Byte));
Result := MW.Value;
end;
procedure GetJPGSize(const sFile: string; var wWidth, wHeight: word);
const
ValidSig: array[0..1] of byte = ($FF, $D8);
Parameterless = [$01, $D0, $D1, $D2, $D3, $D4, $D5, $D6, $D7];
var
Sig: array[0..1] of byte;
f: TFileStream;
x: integer;
Seg: byte;
Dummy: array[0..15] of byte;
Len: word;
ReadLen: LongInt;
begin
FillChar(Sig, SizeOf(Sig), #0);
f := TFileStream.Create(sFile, fmOpenRead);
try
ReadLen := f.Read(Sig[0], SizeOf(Sig));
for x := Low(Sig) to High(Sig) do
if Sig[x] <> ValidSig[x] then
ReadLen := 0;
if ReadLen > 0 then
begin
ReadLen := f.Read(Seg, 1);
while (Seg = $FF) and (ReadLen > 0) do
begin
ReadLen := f.Read(Seg, 1);
if Seg <> $FF then
begin
if (Seg = $C0) or (Seg = $C1) then
begin
ReadLen := f.Read(Dummy[0], 3); { don't need these bytes }
wHeight := ReadMWord(f);
wWidth := ReadMWord(f);
end
else
begin
if not (Seg in Parameterless) then
begin
Len := ReadMWord(f);
f.Seek(Len - 2, 1);
f.Read(Seg, 1);
end
else
Seg := $FF; { Fake it to keep looping. }
end;
end;
end;
end;
finally
f.Free;
end;
end;
procedure GetPNGSize(const sFile: string; var wWidth, wHeight: word);
type
TPNGSig = array[0..7] of byte;
const
ValidSig: TPNGSig = (137, 80, 78, 71, 13, 10, 26, 10);
var
Sig: TPNGSig;
f: tFileStream;
x: integer;
begin
FillChar(Sig, SizeOf(Sig), #0);
f := TFileStream.Create(sFile, fmOpenRead);
try
f.Read(Sig[0], SizeOf(Sig));
for x := Low(Sig) to High(Sig) do
if Sig[x] <> ValidSig[x] then
exit;
f.Seek(18, 0);
wWidth := ReadMWord(f);
f.Seek(22, 0);
wHeight := ReadMWord(f);
finally
f.Free;
end;
end;
procedure GetGIFSize(const sGIFFile: string; var wWidth, wHeight: word);
type
TGIFHeader = record
Sig: array[0..5] of char;
ScreenWidth, ScreenHeight: word;
Flags, Background, Aspect: byte;
end;
TGIFImageBlock = record
Left, Top, Width, Height: word;
Flags: byte;
end;
var
f: file;
Header: TGifHeader;
ImageBlock: TGifImageBlock;
nResult: integer;
x: integer;
c: char;
DimensionsFound: boolean;
begin
wWidth := 0;
wHeight := 0;
if sGifFile = '' then
exit;
{$I-}
FileMode := 0; { read-only }
AssignFile(f, sGifFile);
reset(f, 1);
if IOResult <> 0 then
{Could not open file}
exit;
{Read header and ensure valid file}
BlockRead(f, Header, SizeOf(TGifHeader), nResult);
if (nResult <> SizeOf(TGifHeader)) or (IOResult <> 0) or (StrLComp('GIF', Header.Sig, 3) <> 0) then
begin
{Image file invalid}
close(f);
exit;
end;
{Skip color map, if there is one}
if (Header.Flags and $80) > 0 then
begin
x := 3 * (1 shl ((Header.Flags and 7) + 1));
Seek(f, x);
if IOResult <> 0 then
begin
{ Color map thrashed }
close(f);
exit;
end;
end;
DimensionsFound := False;
FillChar(ImageBlock, SizeOf(TGIFImageBlock), #0);
{ Step through blocks }
BlockRead(f, c, 1, nResult);
while (not EOF(f)) and (not DimensionsFound) do
begin
case c of
',': { Found image }
begin
BlockRead(f, ImageBlock, SizeOf(TGIFImageBlock), nResult);
if nResult <> SizeOf(TGIFImageBlock) then
begin
{ Invalid image block encountered }
close(f);
exit;
end;
wWidth := ImageBlock.Width;
wHeight := ImageBlock.Height;
DimensionsFound := True;
end;
',': { Skip }
begin
{ NOP }
end;
{ nothing else, just ignore }
end;
BlockRead(f, c, 1, nResult);
end;
close(f);
{$I+}
end;
end.
Solve 2:
Getting the size of a *.jpg and *.gif image:
{resourcestring
SInvalidImage = 'Image is not valid';}
type
TImageType = (itUnknown, itJPG, itGIF);
function GetImageType(Image: PByte): TImageType;
var
pImage: PChar;
begin
pImage := PChar(Image);
Result := itUnknown;
if StrLComp(pImage, 'GIF', 3) = 0 then
begin
Result := itGIF;
end
else if (pImage[0] = #$FF) and (pImage[1] = #$D8) then
begin
Result := itJPG;
end;
end;
procedure GetImageBounds(Image: PByte; Size: Integer; var Width: Cardinal; var Height: Cardinal);
const
SizeSegments = [#$C0, #$C1, #$C2];
var
pImage: PChar;
ImageType: TImageType;
cSegmentType: Char;
nSegmentSize: Word;
nPos: Integer;
bFound: Boolean;
begin
ImageType := GetImageType(Image);
pImage := PChar(Image);
case ImageType of
itJPG:
begin
nPos := 2;
bFound := False;
while not bFound and (nPos < Size) do
begin
if pImage[nPos] <> #$FF then
begin
EInvalidGraphic.Create(SInvalidImage);
end;
Inc(nPos);
if nPos >= Size then
begin
raise EInvalidGraphic.Create(SInvalidImage);
end;
cSegmentType := pImage[nPos];
bFound := cSegmentType in SizeSegments;
if not bFound then
begin
Inc(nPos);
if not (cSegmentType in [#$01, #$D0..#$D7]) then
begin
if nPos >= Size - 1 then
begin
raise EInvalidGraphic.Create(SInvalidImage);
end;
nSegmentSize := MakeWord(Byte(pImage[nPos + 1]), Byte(pImage[nPos]));
Inc(nPos, nSegmentSize);
end;
end;
end;
if not bFound then
begin
raise EInvalidGraphic.Create(SInvalidImage);
end;
Inc(nPos, 4);
if nPos >= Size - 1 then
begin
raise EInvalidGraphic.Create(SInvalidImage);
end;
Height := MakeWord(Byte(pImage[nPos + 1]), Byte(pImage[nPos]));
Inc(nPos, 2);
if nPos >= Size - 1 then
begin
raise EInvalidGraphic.Create(SInvalidImage);
end;
Width := MakeWord(Byte(pImage[nPos + 1]), Byte(pImage[nPos]));
end;
itGIF:
begin
nPos := 6;
if nPos >= Size - 1 then
begin
raise EInvalidGraphic.Create(SInvalidImage);
end;
Width := MakeWord(Byte(pImage[nPos]), Byte(pImage[nPos + 1]));
nPos := 8;
if nPos >= Size - 1 then
begin
raise EInvalidGraphic.Create(SInvalidImage);
end;
Height := MakeWord(Byte(pImage[nPos]), Byte(pImage[nPos + 1]));
end
else
begin
raise EInvalidGraphic.Create(SInvalidImage);
end;
end;
end;
Solve 3:
This is a customization of Solve 1:
function GoodFileRead(fhdl: THandle; buffer: Pointer; readsize: DWord): Boolean;
var
numread: DWord;
retval: Boolean;
begin
retval := ReadFile(fhdl, buffer^, readsize, numread, nil);
result := retval and (readsize = numread);
end;
function ReadMWord(fh: HFile; var value: Word): Boolean;
type
TMotorolaWord = record
case byte of
0: (Value: word);
1: (Byte1, Byte2: byte);
end;
var
MW: TMotorolaWord;
numread: DWord;
begin
{ It would probably be better to just read these two bytes in normally and then
do a small ASM routine to swap them. But we aren't talking about reading entire files,
so I doubt the performance gain would be worth the trouble.}
Result := False;
if ReadFile(fh, MW.Byte2, SizeOf(Byte), numread, nil) then
if ReadFile(fh, MW.Byte1, SizeOf(Byte), numread, nil) then
Result := True;
Value := MW.Value;
end;
function ImageType(Fname: string): Smallint;
var
ImgExt: string;
Itype: Smallint;
begin
ImgExt := UpperCase(ExtractFileExt(Fname));
if ImgExt = '.BMP' then
Itype := 1
else if (ImgExt = '.JPEG') or (ImgExt = '.JPG') then
Itype := 2
else
Itype := 0;
Result := Itype;
end;
function FetchBitmapHeader(PictFileName: string; var wd, ht: Word): Boolean;
{similar routine is in "BitmapRegion" routine}
label
ErrExit;
const
ValidSig: array[0..1] of byte = ($FF, $D8);
Parameterless = [$01, $D0, $D1, $D2, $D3, $D4, $D5, $D6, $D7];
BmpSig = $4D42;
var
{Err : Boolean;}
fh: HFile;
{tof : TOFSTRUCT;}
bf: TBITMAPFILEHEADER;
bh: TBITMAPINFOHEADER;
{JpgImg : TJPEGImage;}
Itype: Smallint;
Sig: array[0..1] of byte;
x: integer;
Seg: byte;
Dummy: array[0..15] of byte;
skipLen: word;
OkBmp, Readgood: Boolean;
begin
{Open the file and get a handle to it's BITMAPINFO}
OkBmp := False;
Itype := ImageType(PictFileName);
fh := CreateFile(PChar(PictFileName), GENERIC_READ, FILE_SHARE_READ, nil,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0);
if (fh = INVALID_HANDLE_VALUE) then
goto ErrExit;
if Itype = 1 then
begin
{read the BITMAPFILEHEADER}
if not GoodFileRead(fh, @bf, sizeof(bf)) then
goto ErrExit;
if (bf.bfType <> BmpSig) then {'BM'}
goto ErrExit;
if not GoodFileRead(fh, @bh, sizeof(bh)) then
goto ErrExit;
{for now, don't even deal with CORE headers}
if (bh.biSize = sizeof(TBITMAPCOREHEADER)) then
goto ErrExit;
wd := bh.biWidth;
ht := bh.biheight;
OkBmp := True;
end
else if (Itype = 2) then
begin
FillChar(Sig, SizeOf(Sig), #0);
if not GoodFileRead(fh, @Sig[0], sizeof(Sig)) then
goto ErrExit;
for x := Low(Sig) to High(Sig) do
if Sig[x] <> ValidSig[x] then
goto ErrExit;
Readgood := GoodFileRead(fh, @Seg, sizeof(Seg));
while (Seg = $FF) and Readgood do
begin
Readgood := GoodFileRead(fh, @Seg, sizeof(Seg));
if Seg <> $FF then
begin
if (Seg = $C0) or (Seg = $C1) or (Seg = $C2) then
begin
Readgood := GoodFileRead(fh, @Dummy[0], 3); {don't need these bytes}
if ReadMWord(fh, ht) and ReadMWord(fh, wd) then
OkBmp := True;
end
else
begin
if not (Seg in Parameterless) then
begin
ReadMWord(fh, skipLen);
SetFilePointer(fh, skipLen - 2, nil, FILE_CURRENT);
GoodFileRead(fh, @Seg, sizeof(Seg));
end
else
Seg := $FF; {Fake it to keep looping}
end;
end;
end;
end;
ErrExit: CloseHandle(fh);
Result := OkBmp;
end;
2007. február 22., csütörtök
Antialiased line drawer
Problem/Question/Abstract:
How do I draw smooth lines in my apps like photoshop?
Answer:
procedure AALine(x1, y1, x2, y2: single; color: tcolor; canvas: tcanvas);
function CrossFadeColor(FromColor, ToColor: TColor; Rate: Single): TColor;
var
r, g, b: byte;
begin
r := Round(GetRValue(FromColor) * Rate + GetRValue(ToColor) * (1 - Rate));
g := Round(GetGValue(FromColor) * Rate + GetGValue(ToColor) * (1 - Rate));
b := Round(GetBValue(FromColor) * Rate + GetBValue(ToColor) * (1 - Rate));
Result := RGB(r, g, b);
end;
procedure hpixel(x: single; y: integer);
var
FadeRate: single;
begin
FadeRate := x - trunc(x);
with canvas do
begin
pixels[trunc(x), y] := CrossFadeColor(Color, Pixels[Trunc(x), y], 1 - FadeRate);
pixels[trunc(x) + 1, y] := CrossFadeColor(Color, Pixels[Trunc(x) + 1, y],
FadeRate);
end;
end;
procedure vpixel(x: integer; y: single);
var
FadeRate: single;
begin
FadeRate := y - trunc(y);
with canvas do
begin
pixels[x, trunc(y)] := CrossFadeColor(Color, Pixels[x, Trunc(y)], 1 - FadeRate);
pixels[x, trunc(y) + 1] := CrossFadeColor(Color, Pixels[x, Trunc(y) + 1],
FadeRate);
end;
end;
var
i: integer;
ly, lx, currentx, currenty, deltax, deltay, l, skipl: single;
begin
if (x1 <> x2) or (y1 <> y2) then
begin
currentx := x1;
currenty := y1;
lx := abs(x2 - x1);
ly := abs(y2 - y1);
if lx > ly then
begin
l := trunc(lx);
deltay := (y2 - y1) / l;
if x1 > x2 then
begin
deltax := -1;
skipl := (currentx - trunc(currentx));
end
else
begin
deltax := 1;
skipl := 1 - (currentx - trunc(currentx));
end;
end
else
begin
l := trunc(ly);
deltax := (x2 - x1) / l;
if y1 > y2 then
begin
deltay := -1;
skipl := (currenty - trunc(currenty));
end
else
begin
deltay := 1;
skipl := 1 - (currenty - trunc(currenty));
end;
end;
currentx := currentx + deltax * skipl;
currenty := currenty + deltay * skipl; {}
for i := 1 to trunc(l) do
begin
if lx > ly then
vpixel(trunc(currentx), currenty)
else
hpixel(currentx, trunc(currenty));
currentx := currentx + deltax;
currenty := currenty + deltay;
end;
end;
end;
2007. február 21., szerda
Make TextOut with 3d-Effect or hollow Text
Problem/Question/Abstract:
How to make TextOut with 3d-Effect or hollow Text
Answer:
Make a new Application and take this Proc bellow for the OnPaint-Event of the Form. The TextOutput will look like written with a kaligraf.If You replace the for loop in the proc with a single call of textout you can use this code to write "hollow" text. Try it with different Pen-Styles too!
procedure TForm1.FormPaint(Sender: TObject);
var
HFnt: HFONT;
Fontname, Txt: PChar;
sze: Size;
c: Integer;
byt: Byte;
begin
Fontname := 'Arial';
txt := 'Mediakueche';
HFnt := CreateFont(90, 60, 0, 0, FW_BOLD, 0, 0, 0, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
PROOF_QUALITY, DEFAULT_PITCH + FF_DONTCARE, Fontname);
SelectObject(Canvas.Handle, hfnt);
SetBkMode(Canvas.Handle, TRANSPARENT);
GetTextExtentPoint32(Canvas.Handle, txt, length(txt), sze);
BeginPath(Canvas.Handle);
c := 1;
for c := 0 to 4 do
begin
TextOut(Canvas.Handle, 5 + c, 10 + c, Txt, length(Txt));
end;
EndPath(Canvas.Handle);
// Canvas.pen.Style := psDot;
StrokePath(Canvas.Handle);
SetBkMode(Canvas.Handle, OPAQUE);
DeleteObject(SelectObject(Canvas.Handle, GetStockObject(WHITE_BRUSH)));
SelectObject(Canvas.Handle, GetStockObject(SYSTEM_FONT));
DeleteObject(HFnt);
end;
2007. február 20., kedd
Very simple connection to an Access 2000 database using ADO
Problem/Question/Abstract:
How can I connect my Application to an Access 2000 Database
Answer:
You have an access 2000 Database with a few table and are not very familiar with ADO. You want to, at least, connect you database to your application (For example one of your friend don't have an access viewer and you want him to be able to read you Tip and Trick DataBase).
Here how it work.
First put a TADOConnection Component on your form. Open the object inspector and search for the connection string property. Click the 3 dot icon (...). You should see a windows with the following title bar:
Form1.ADOConnection1 Connection String
As it's our first connection ever we gonna use a connection string as
connection Source. Click the second radio button. Don't type anything in the textbox, just click the build button to automate the process.
Another windows appear named Data Link Properties. There 4 sections called Provider, Connection, Advanced ans All. Go in provider.
As I don't want to go in detail and let you make a quick connection I recommend you chose the latest Microsoft Jet OLE Provider. On my version it's 4.0, if you don't have updated yet your sustem it will be 3.51. Click Next.
You are now in the connection section. Click the 3 dot (...) to access your database with the open dialog. Ok following you are not a DataBase expert and don't share it with other users you don't need any login name / password, so delete the Admin default username.
We don't need to edit the Advanced and All section. Leaves that blank and click ok, click ok another time.
Back to the form.
Open the object inspector. First set the LoginPrompt to false. Got to the connected property, make it true. You are connected!
Wanna be sure? Yes, all was done non visual. We will put some visual components on the form but first we need to complete the non visual part.
Add a TADODataSet to the form. Go to the Connection propery, click down and choose ADOConnection1. In CommandType click cmdTableDirect, we don't need any fancy thing, let's get straight to the point. In the command text property click down and select the main table of you app (I assume for this article that you have only one table). In the active property click true.
The rest go a lot like BDE application. Go to the DataAccess category and put a TDataSource component on the form. Put it's DataSet property to ADODataSet1.
We're ready for a visual component. Now put a TDBGrid on the form.
In the DataSource property choose DataSource1.
That's it your connected!
2007. február 19., hétfő
How to change the TCheckBox state without assigning an OnClick event handler
Problem/Question/Abstract:
I was wondering if there was any way to change the state of the TCheckBox control without setting off the OnClick Event Handler. If certain other properties are incorrect, I want void the state the event was set to by the Click, without setting of the event handler again.
Answer:
procedure TForm1.Button1Click(Sender: TObject);
begin
if Button1.Tag = 0 then
begin
SendMessage(CheckBox1.handle, BM_SETCHECK, BST_CHECKED, 0);
Button1.Tag := 1;
end
else
begin
SendMessage(CheckBox1.handle, BM_SETCHECK, BST_UNCHECKED, 0);
Button1.Tag := 0;
end;
end;
procedure TForm1.CheckBox1Click(Sender: TObject);
begin
Showmessage('clicked');
end;
2007. február 18., vasárnap
Copying Files in Delphi
Problem/Question/Abstract:
How do I copy a file in Delphi?
Answer:
Reminiscing on Days Gone By...
Back in the old DOS days, we took for granted copying a file from one place to another with the copy command. But with Windows, everything changed. We now use File Manager or Explorer to copy files from one place to another, which is a huge improvement over typing in the fully qualified path for both source and destination files.
But at the programming level, performing the copying of one file to another is not as apparent as one would think. In fact, there are no native Delphi calls for copying a file whatsoever. So what do you do if you want to copy a file? You have to write the routine yourself.
Interestingly enough, there is a pretty good example of copying a file that is in the FMXUTILS.PAS file in the Delphi\Demos\Doc\Filmanex directory that will perform a file copy using native Delphi file-related commands. While this method works just fine, I decided to go another route; that is, to use a file stream to copy a file from one place to another. Streams are interesting animals. They're used internally in Delphi to read and write components, forms and data, and they're pretty handy. Unfortunately, they aren't well-documented so they can be a bit tricky to use. I went through a lot of trial and error to get them to work, and referenced several sources outside of the online help (which is just about the only place you'll find anything on streams in Delphi) before I got a handle on streams. But once I figured them out, they became what I use for reading and writing files almost exclusively.
There's Almost Always More Than One Way of Doing Things...
Once you've programmed for a while, you realize that it's possible to solve a particular problem in a variety of ways; which way is valid is dependent upon your knowledge and experience (one way may be more optimized than another) or, at times, even the situation will dictate that one methodology is better suited for a task than another.
For instance, with file copying, there are times you just want to copy a file in the background using a quick and dirty method, and you don't care if the user knows what's going on at all. But there are other times, such as when file utilities are part of an interface, when you want the user to be aware of the copying progress.
What I'm going to show you here are two ways to perform file copying: one quick and dirty; the other, a more snazzy, graphical way of copying a file, though it uses a few more resources and is a bit slower.
Quick and Dirty Copying
Traditionally, copying a file involves using a loop to move a series of blocks from one file into a temporary buffer, then copying the contents of the buffer into another file. Let's look at the CopyFile function found in the FMXUTILS.PAS:
{=============================================================================
CopyFile procedure found in the FMXUTILS.PAS file in Delphi\Demos\Doc\Filmanex
This is an example of copying a file using a buffer.
=============================================================================}
procedure CopyFile(const FileName, DestName: TFileName);
var
CopyBuffer: Pointer; { buffer for copying }
TimeStamp, BytesCopied: Longint;
Source, Dest: Integer; { handles }
Destination: TFileName; { holder for expanded destination name }
const
ChunkSize: Longint = 8192; { copy in 8K chunks }
begin
Destination := ExpandFileName(DestName); { expand the destination path }
if HasAttr(Destination, faDirectory) then { if destination is a directory... }
Destination := Destination + '\' + ExtractFileName(FileName);
{ ...clone file name }
TimeStamp := FileAge(FileName); { get source's time stamp }
GetMem(CopyBuffer, ChunkSize); { allocate the buffer }
try
Source := FileOpen(FileName, fmShareDenyWrite); { open source file }
if Source < 0 then
raise EFOpenError.Create(FmtLoadStr(SFOpenError, [FileName]));
try
Dest := FileCreate(Destination); { create output file; overwrite existing }
if Dest < 0 then
raise EFCreateError.Create(FmtLoadStr(SFCreateError, [Destination]));
try
repeat
BytesCopied := FileRead(Source, CopyBuffer^, ChunkSize); { read chunk }
if BytesCopied > 0 then { if we read anything... }
FileWrite(Dest, CopyBuffer^, BytesCopied); { ...write chunk }
until BytesCopied < ChunkSize; { until we run out of chunks }
finally
FileClose(Dest); { close the destination file }
end;
finally
FileClose(Source); { close the source file }
end;
finally
FreeMem(CopyBuffer, ChunkSize); { free the buffer }
end;
end;
But Delphi implements a method of TStream called CopyFrom that allows you to copy the entire contents of one stream into another in one fell swoop. Here's an implementation of copying a file using the CopyFrom method:
{=============================================================
Quick and dirty copying using the CopyFrom method of TStream.
=============================================================}
procedure FileCopy(const FSrc, FDst: string);
var
sStream,
dStream: TFileStream;
begin
sStream := TFileStream.Create(FSrc, fmOpenRead);
try
dStream := TFileStream.Create(FDst, fmCreate);
try
{Forget about block reads and writes, just copy
the whole darn thing.}
dStream.CopyFrom(sStream, 0);
finally
dStream.Free;
end;
finally
sStream.Free;
end;
end;
The declaration of the CopyFrom method is as follows:
function CopyFrom(Source: TStream; Count: LongInt): LongInt;
Source is the TStream you're going to copy from, and Count is the number of bytes to copy from the stream. If Count is zero (0), the entire contents of the source stream is copied over. This makes for a quick one-liner copying.
Notice that in both the examples above, all the functionality is enclosed in nested try..finally blocks. This is extremely important because just in case something goes wrong, all resources and pointers that are created are freed. You don't want to have stray pointers or unreleased memory in your system, so providing at least this level of exception handling is key to ensuring that you don't.
A Sexier File Copy
If you write robust user interfaces, practically everything that you do involves interacting with the user by providing visual cues to let the user know what's going on. File copying is one of those types of operations that when performed within the context of a user interface must provide some status as to the progress of the copy operation. Therefore, a quick and dirty copy like the one I just described above won't do. What we need then is something with a bit more pizazz.
In order to get status, we need to copy the file in chunks. That way, as we copy each chunk from one file to another, we can let the user know how far we've proceeded. What this implies is that we need two pieces. The first is the unit that performs the copying; the other a status window used for notification. For me, the best way to get both pieces to work in concert was to build a custom component which encapsulates the file copy operation and uses another unit to perform the notification.
The notification unit is just a simple form with a TGauge and a TButton placed on it. The unit code is as follows:
unit copyprg;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
StdCtrls, Gauges;
type
TFileProg = class(TForm)
Gauge1: TGauge;
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ Private declarations }
fCancel: Boolean;
public
property CancelIt: Boolean read fCancel;
end;
var
FileProg: TFileProg;
implementation
{$R *.DFM}
procedure TFileProg.Button1Click(Sender: TObject);
begin
fCancel := True;
end;
procedure TFileProg.FormCreate(Sender: TObject);
begin
fCancel := False;
end;
end.
Nothing odd here. I simply added a custom property to the form called CancelIt, which is a simple Boolean flag used to cancel the copying operation midstream should the user desire to do so. The real work happens in the custom component itself. Let's look at its code, then discuss it:
unit FileCopy;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;
type
TFileCopy = class(TComponent)
private
FSource,
FDest: string;
procedure DoCopyFile(const SrcFile, DstFile: string);
public
procedure CopyFile; virtual;
published
property FileSource: string read FSource write FSource;
property FileDestination: string read FDest write FDest;
end;
procedure Register;
implementation
uses copyprg;
procedure TFileCopy.CopyFile;
begin
DoCopyFile(FileSource, FileDestination);
end;
procedure TFileCopy.DoCopyFile(const SrcFile, DstFile: string);
const
bufSize = 16384; {Use a 16K buffer. You can use whatever size suits you, though.}
var
sStream,
dStream: TFileStream;
pBuf: Pointer;
cnt: Integer;
prgFrm: TFileProg;
totCnt,
X,
strmSize: LongInt;
begin
totCnt := 0;
{Open up the Source File to read it}
sStream := TFileStream.Create(SrcFile, fmOpenRead or fmShareDenyWrite);
{Create the copying progress form and set property values}
prgFrm := TFileProg.Create(Application);
with prgFrm.Gauge1 do
begin
MinValue := 0;
MaxValue := 100;
Progress := 0;
end;
prgFrm.Show;
{Get the size of the entire stream to use for the progress gauge. Note
we have to call FileSeek first because it will place the pointer
at the end of the file when we get the file first return value.}
strmSize := sStream.size;
try
{ Create the destination file. If it already exists,
overwrite it. }
dStream := TFileStream.Create(DstFile, fmCreate or fmShareExclusive);
try
GetMem(pBuf, bufSize);
try
{Read and write first bufSize bytes from source into the buffer
If the file size is smaller than the default buffer size, then
all the user will see is a quick flash of the progress form.}
cnt := sStream.Read(pBuf^, bufSize);
cnt := dStream.Write(pBuf^, cnt);
totCnt := totCnt + cnt;
{Loop the process of reading and writing}
while (cnt > 0) do
begin
{Let things in the background proceed while loop is processing}
Application.ProcessMessages;
{Read bufSize bytes from source into the buffer}
cnt := sStream.Read(pBuf^, bufSize);
{Now write those bytes into destination}
cnt := dStream.Write(pBuf^, cnt);
{Increment totCnt for progress and do arithmetic to update the
gauge}
totcnt := totcnt + cnt;
if not prgFrm.CancelIt then
with prgFrm.Gauge1 do
begin
Progress := Round((totCnt / strmSize) * 100);
Update;
end
else
Break; {If user presses cancel button, then break out of loop}
{which will make program go to finally blocks}
end;
finally
FreeMem(pBuf, bufSize);
end;
finally
dStream.Free;
if prgFrm.CancelIt then {If copying was cancelled, delete the destination file}
DeleteFile(DstFile); {after stream has been freed, which will close the file.}
end;
finally
sStream.Free;
prgFrm.Close;
end;
end;
procedure Register;
begin
{You can change the palette entry to something of your choice}
RegisterComponents('BD', [TFileCopy]);
end;
end.
Like the CopyFile routine in FMXUTILS.PAS, the concept behind copying for this component is the same: Grab a chunk of the source file, then dump it into the destination file. Repeat this process until all possible data has been copied over. Notice that I used a TFileStream once again. But this time, I didn't copy the entire file over in one fell swoop. That would've defeated the whole purpose of providing user status.
I've commented the code extensively, so I won't go into real detail here. I'll leave it up to you to study the code to learn about what's going on in it.
Notice the method declaration for CopyFile is declared as a virtual method. I've done this on purpose so that this class can be used a template class for specialized copy operations. The CopyFile method is actually rather trivial at this level -- all it does is call the DoCopyFile method and pass the FileSource and FileDestination property values.
However, it is the only public interface for actually performing the copying operation. This is an important point for all you component designers out there. Providing limited method visibility ensures that the core features of your components remain intact. Remember, you want other users of your component to see only what is absolutely necessary.
How is this useful? It allows you to have a bit of control over how the hierarchy develops. By hiding the basic functionality from descendant classes, you can ensure that the basic functionality of your class is retained throughout the inheritance tree. Granted, users can completely override the behavior of the CopyFile method, but that doesn't mean that the original capability will be lost. It will still be there, just not implemented.
Obviously, the meat of the work is performed by the DoCopyFile method. Study the code to see what happens from point to point. Note that I used a Pointer for the buffer. You can use just about any type as a buffer, but a pointer makes sense because its a simple 4-byte value. If you are copying a text file and want to treat the pointer like a string, you can cast it as a PChar, so long as you append a #0 byte to the end of the buffer. Neat stuff, huh?
A Little Note About TFileStream
TFileStream is not a direct assignment of TStream. In fact, it's a descendant of THandleStream which, when created, fills a property value called Handle which is the handle to an external file. TFileStream inherits the Handle property. The significance of this is really neat: File operations that take a handle as input can be applied to a TFileStream. That has interesting implications in that you can do file operations on a TFileStream object before you write it to another place. Try experimenting with this.
Okay, we've come a long way. And no, I haven't delved into the depths of Stream classes. That's probably best left to another article or series of articles. In any case, play around with the TCopyFile class. It could prove to be a useful addition to your applications.
2007. február 17., szombat
How can I close a MessageBox()
Problem/Question/Abstract:
How can I close a MessageBox()
Answer:
You can use a thread to achieve that:
unit MsgThread;
interface
uses
Windows, Messages, SysUtils, Classes, Controls, Forms, StdCtrls, ExtCtrls;
type
TMboxThread = class(TThread)
private
{ private declarations }
protected
procedure Execute; override;
public
constructor Create;
end;
type
TFrmMsgThread = class(TForm)
BtnClose: TButton;
Edit1: TEdit;
Edit2: TEdit;
Timer1: TTimer;
procedure BtnCloseClick(Sender: TObject);
procedure Timer1Timer(Sender: TObject);
private
FFirst: boolean;
FMboxThread: TMBoxThread;
FWinHandle: HWnd;
public
{ public declarations }
end;
var
FrmMsgThread: TFrmMsgThread;
implementation
{$R *.DFM}
{ TMboxThread }
constructor TMboxThread.Create;
begin
FreeOnTerminate := True;
inherited Create(False);
end;
procedure TMboxThread.Execute;
begin
{ Place thread code here }
MessageBox(Application.Handle, 'Text', 'Caption',
MB_APPLMODAL + MB_SETFOREGROUND);
end;
{ TForm1 }
procedure TFrmMsgThread.BtnCloseClick(Sender: TObject);
begin
FMBoxThread := TMBoxThread.Create;
FFirst := true;
Timer1.Enabled := true;
end;
procedure TFrmMsgThread.Timer1Timer(Sender: TObject);
begin
Timer1.Enabled := false;
if FFirst then
begin
FWinHandle := GetForegroundWindow;
FFirst := false;
Timer1.Enabled := true;
end
else
SendMessage(FWinHandle, WM_CLOSE, 0, 0);
end;
end.
2007. február 16., péntek
How to display text from a TRichEdit on the canvas of a TGraphicControl
Problem/Question/Abstract:
How to display text from a TRichEdit on the canvas of a TGraphicControl
Answer:
If you just want to view the text you can make a component derived from TGraphicControl. The following code shows how to put text from a TRichEdit control on the canvas of the TGraphicControl.
{rtf is a property of the TRICHLABEL of the type TRichEdit }
procedure TRICHLABEL.Paint;
var
myFormatRange: TFormatRange;
myCharRange: TCharRange;
myRect: TRect;
OldMap: Integer;
LastChar: Integer;
rc: TRect;
hDC, hDCTarget: THandle;
begin
if not Assigned(rtf) then
exit;
FillChar(myFormatRange, sizeof(TFormatRange), 0);
myCharRange.cpMin := 0;
myCharRange.cpMax := -1;
canvas.brush.color := BackColor;
if FTransparent then
Canvas.brush.style := bsClear
else
begin
Canvas.brush.style := bsSolid;
Canvas.FillRect(ClientRect);
end;
with myFormatRange do
begin
hDC := canvas.handle;
hDCTarget := canvas.handle;
rc := Rect(0, ptOrigin.y * 15, Width * 15, ptOrigin.y * 15 + Height * 15);
chrg.cpMin := 0;
chrg.cpMax := -1;
end;
LastChar := SendMessage(rtf.handle, EM_FORMATRANGE, 1, LPARAM(@myFormatRange));
myRect := Rect(0, 0, width * 15, height * 15);
SendMessage(rtf.handle, EM_DISPLAYBAND, 0, LPARAM(@myRect));
LastChar := SendMessage(rtf.handle, EM_FORMATRANGE, 0, LPARAM(nil));
end;
2007. február 15., csütörtök
Creating weird shaped forms
Problem/Question/Abstract:
Is it possible to create forms with shapes other than the standard rectangular shape in Windows?
Answer:
Sometimes it's just not enough to write applications that have the same boring rectangular forms over and over again. Sometimes you need a change. How about an elliptical form? Or maybe even a triangular form? Sound intriguing? It's not that hard to do.
New in Win32 is something called a region. The Win32 API Programmer's Reference defines a region as follows:
...a rectangle, polygon or ellipse (or a combination of two or more of these shapes) that can be filled, painted, inverted, framed and used to perform hit testing (testing for the cursor location).
From the definition, the most notable thing about a region is that it can be manipulated in a variety of ways. For our purposes we want to define a region to create a specific shape.
I should point out that a region can be defined for just about any TWinControl descendant (not just forms), meaning you can apply a region to a TPanel or even a TEdit (though I strongly recommend against it). But to alter the shape of a TWinControl descendant, all you need to provide is a handle and employ some handy-dandy shape change functions.
To get a control to change its shape, follow this two-step process:
Define the boundaries of the region that represent a particular shape.
Apply the boundaries you've defined to a window.
This is pretty simple. However, it's very important to refer to the help file, and to have the source at hand. I wouldn't be able to accomplish many of my projects, let alone write many of the articles I write here, without those two resources at my disposal. Especially with the Windows API calls, having access to the Window.PAS file is essential so I know what to pass into the functions. Remember, the WinAPI calls are really wrapper calls into the appropriate Windows DLLs, and of course, the help file is essential to getting background information on the topic you're interested in.
With respect to this article, look up the SetWindowRgn topic in Win32 Developer's Help, and have it handy while you're putting together your program. Pay particular attention to the Group hyperlink because it will give you a run-down of all the procedures related to the region topic. Let's move on!
Defining a Region's Boundary
The first step to creating a form of a different shape is to define the shape itself. For our discussion, we'll use three WinAPI calls:
CreateEllipticRgn
This function will create an elliptically-shaped region.
CreateRoundRectRgn
This will create a rectangular region with rounded corners.
CreatePolygonRgn
This will create just about any multi-sided shape, as long as the lines form a closed solid.
These functions return a HRGN type, which will then be used by a function called SetWindowRgn whose sole purpose in life it is to set the parameters defined by a particular region variable. I've encapsulated these functions in methods that are part of a demonstration form. The functions are coded as follows:
{===========================================================================
Notice that all the functions are used in an assignment
operation to a variable called rgn. This is a
private var that I declared for the form. The private var is
accessible to all functions; I did this so that I could change the shape of
the form or a control on the form, and use the same region.
===========================================================================}
procedure TForm1.DrawEllipticRegion(wnd: HWND; rect: TRect);
begin
rgn := CreateEllipticRgn(rect.left, rect.top, rect.right, rect.bottom);
SetWindowRgn(wnd, rgn, TRUE);
end;
procedure TForm1.DrawRndRectRegion(wnd: HWND; rect: TRect);
begin
rgn := CreateRoundRectRgn(rect.left, rect.top, rect.right, rect.bottom, 30, 30);
SetWindowRgn(wnd, rgn, TRUE);
end;
procedure TForm1.DrawPolygonRegion(wnd: HWND; rect: TRect; NumPoints: Integer;
DoStarShape: Boolean);
const
RadConvert = PI / 180;
Degrees = 360;
MaxLines = 100;
var
x, y,
xCenter,
yCenter,
radius,
pts,
I: Integer;
angle,
rotation: Extended;
arPts: array[0..MaxLines] of TPoint;
begin
xCenter := (rect.Right - rect.Left) div 2;
yCenter := (rect.Bottom - rect.Top) div 2;
if DoStarShape then
begin
rotation := Degrees / (2 * NumPoints);
pts := 2 * NumPoints;
end
else
begin
rotation := Degrees / NumPoints; //get number of degrees to turn per point
pts := NumPoints
end;
radius := yCenter;
{This loop defines the Cartesian points of the shape. Notice
I've added 90 degrees to the rotation angle. This is so that shapes will
stand up; otherwise they'll lie on their sides. I had to
brush up on my trigonometry to accomplish this (forgot all those sin and cos
thingies. Many thanks to Terry Smithwick and David Ullrich for their
assistance on CompuServe!}
for I := 0 to pts - 1 do
begin
if DoStarShape then
if (I mod 2) = 0 then //which means that
radius := Round(radius / 2)
else
radius := yCenter;
angle := ((I * rotation) + 90) * RadConvert;
x := xCenter + Round(cos(angle) * radius);
y := yCenter - Round(sin(angle) * radius);
arPts[I].X := x;
arPts[I].Y := y;
end;
rgn := CreatePolygonRgn(arPts, pts, WINDING);
SetWindowRgn(wnd, rgn, TRUE);
end;
The first two functions are pretty simple, just two-liners. All that's needed to create the appropriate shapes is a handle and a TRect structure. For forms, that structure would be taken from the ClientRect property; for other controls, use the BoundsRect property.
The DrawPolygonRegion method, however, is much more complex. This is due in part to the fact that CreatePolygonRgn requires the vertices of the corners of the polygon to be passed as an array of TPoints, and partly because I wanted to draw equilateral polygons based off points rotated around a common center point. For that I had to use some trigonometry.
I wanted to not only draw polygon regions, but stars as well. Using rotational trig allowed me to do it. The way the function works if the DrawStarShape parameter is set to True is that for every even value of I in the loop, the radius of the circle is set to half its length, and to maintain the number of points of the polygon I want to draw, I double the number of points to accomodate the contraction of the radius.
At the very end of each function is a call to SetWindowRgn. This function takes as parameters a window handle, a rgn var, and a Boolean value that specifies whether the window should be re-drawn. In all cases, if you want to see the shape you've made, this must be always be set to True.
Below is the listing for the entire source code of my test form. On the form I've dropped four TButtons (one for each of the shapes: ellipse, round rectangle, polygon and star); a TPanel to demonstrate the ability to set regions for TWinControl descendants other than TForm; and a SpinEdit used in conjunction with the Polygon and Star region buttons to define the number of points that'll be defining the shape. Here's the code:
unit regmain;
interface
uses
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
ExtCtrls, StdCtrls, Spin;
type
TForm1 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
SpinEdit1: TSpinEdit;
Button4: TButton;
Panel1: TPanel;
Edit1: TEdit;
procedure DrawRndRectRegion(wnd: HWND; rect: TRect);
procedure DrawEllipticRegion(wnd: HWND; rect: TRect);
procedure DrawPolygonRegion(wnd: HWND; rect: TRect; NumPoints: Integer;
DoStarShape: Boolean);
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
private
{ Private declarations }
rgn: HRGN;
rect: TRect;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.DFM}
procedure TForm1.DrawRndRectRegion(wnd: HWND; rect: TRect);
begin
rgn := CreateRoundRectRgn(rect.left, rect.top, rect.right, rect.bottom, 30, 30);
SetWindowRgn(wnd, rgn, TRUE);
end;
procedure TForm1.DrawEllipticRegion(wnd: HWND; rect: TRect);
begin
rgn := CreateEllipticRgn(rect.left, rect.top, rect.right, rect.bottom);
SetWindowRgn(wnd, rgn, TRUE);
end;
procedure TForm1.DrawPolygonRegion(wnd: HWND; rect: TRect; NumPoints: Integer;
DoStarShape: Boolean);
const
RadConvert = PI / 180;
Degrees = 360;
MaxLines = 100;
var
x, y,
xCenter,
yCenter,
radius,
pts,
I: Integer;
angle,
rotation: Extended;
arPts: array[0..MaxLines] of TPoint;
begin
xCenter := (rect.Right - rect.Left) div 2;
yCenter := (rect.Bottom - rect.Top) div 2;
if DoStarShape then
begin
rotation := Degrees / (2 * NumPoints);
pts := 2 * NumPoints;
end
else
begin
rotation := Degrees / NumPoints; //get number of degrees to turn per point
pts := NumPoints
end;
radius := yCenter;
{This loop defines the Cartesian points of the shape. Again,
I've added 90 degrees to the rotation angle so the shapes will
stand up rather than lie on their sides. Thanks again to Terry Smithwick and
David Ullrich for their trig help on CompuServe.}
for I := 0 to pts - 1 do
begin
if DoStarShape then
if (I mod 2) = 0 then //which means that
radius := Round(radius / 2)
else
radius := yCenter;
angle := ((I * rotation) + 90) * RadConvert;
x := xCenter + Round(cos(angle) * radius);
y := yCenter - Round(sin(angle) * radius);
arPts[I].X := x;
arPts[I].Y := y;
end;
rgn := CreatePolygonRgn(arPts, pts, WINDING);
SetWindowRgn(wnd, rgn, TRUE);
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
DrawEllipticRegion(Form1.Handle, Form1.ClientRect);
end;
procedure TForm1.Button2Click(Sender: TObject);
begin
DrawPolygonRegion(Panel1.Handle, Panel1.BoundsRect, SpinEdit1.Value, False);
end;
procedure TForm1.Button3Click(Sender: TObject);
begin
DrawRndRectRegion(Form1.Handle, Form1.ClientRect);
end;
procedure TForm1.Button4Click(Sender: TObject);
begin
DrawPolygonRegion(Panel1.Handle, Panel1.BoundsRect, SpinEdit1.Value, True);
end;
end.
As you can see, defining and setting regions is pretty easy. Look in the help file for in-depth discussions. If you belong to the MS Developer's Network, the library CDs discuss this topic comprehensively.
2007. február 14., szerda
Change all hyperlinks in a Winword document
Problem/Question/Abstract:
How to change all hyperlinks in a Winword document
Answer:
{ ... }
Doc := Word.ActiveDocument;
for x := 1 to Doc.Hyperlinks.Count do
begin
Doc.Hyperlinks.Item(x).Address;
end;
{ ... }
2007. február 13., kedd
How to read the properties of movie files
Problem/Question/Abstract:
Does anybody know how to read properties of movie files (avi, mpeg, asf, ..). I would like to get as much information about files as possible: length, resolution, audio and video codecs, copyright and so on. In other words: Information that is displayed after right-clicking file and selecting "properties".
Answer:
Below is some code to get some of the data. To use the DirectDraw/ DirectShow calls you need either the older DSHOW.PAS (DX6) or more current DirectShow.pas header conversion from the Project JEDI web site:
type
TDSMediaInfo = record
SurfaceDesc: TDDSurfaceDesc;
Pitch: integer;
PixelFormat: TPixelFormat;
MediaLength: Int64;
AvgTimePerFrame: Int64;
FrameCount: integer;
Width: integer;
Height: integer;
FileSize: Int64;
end;
function GetHugeFileSize(const FileName: string): int64;
var
FileHandle: hFile;
begin
FileHandle := FileOpen(FileName, fmOpenRead or fmShareDenyNone);
try
LARGE_INTEGER(Result).LowPart := GetFileSize(FileHandle, @LARGE_INTEGER(Result).HighPart);
if LARGE_INTEGER(Result).LowPart = $FFFFFFFF then
Win32Check(GetLastError = NO_ERROR);
finally
FileClose(FileHandle);
end;
end;
function GetMediaInfo(FileName: WideString): TDSMediaInfo;
var
DirectDraw: IDirectDraw;
AMStream: IAMMultiMediaStream;
MMStream: IMultiMediaStream;
PrimaryVidStream: IMediaStream;
DDStream: IDirectDrawMediaStream;
GraphBuilder: IGraphBuilder;
MediaSeeking: IMediaSeeking;
TimeStart, TimeStop: Int64;
DesiredSurface: TDDSurfaceDesc;
DDSurface: IDirectDrawSurface;
begin
if FileName = '' then
raise Exception.Create('No File Name Specified');
OleCheck(DirectDrawCreate(nil, DirectDraw, nil));
DirectDraw.SetCooperativeLevel(GetDesktopWindow(), DDSCL_NORMAL);
Result.FileSize := GetHugeFileSize(FileName);
AMStream := IAMMultiMediaStream(CreateComObject(CLSID_AMMultiMediaStream));
OleCheck(AMStream.Initialize(STREAMTYPE_READ, AMMSF_NOGRAPHTHREAD, nil));
OleCheck(AMStream.AddMediaStream(DirectDraw, MSPID_PrimaryVideo, 0, IMediaStream(nil^)));
OleCheck(AMStream.OpenFile(PWideChar(FileName), AMMSF_NOCLOCK));
AMStream.GetFilterGraph(GraphBuilder);
MediaSeeking := GraphBuilder as IMediaSeeking;
MediaSeeking.GetDuration(Result.MediaLength);
MMStream := AMStream as IMultiMediaStream;
OleCheck(MMStream.GetMediaStream(MSPID_PrimaryVideo, PrimaryVidStream));
DDStream := PrimaryVidStream as IDirectDrawMediaStream;
DDStream.GetTimePerFrame(Result.AvgTimePerFrame);
{Result.FrameCount := Result.MediaLength div Result.AvgTimePerFrame;}
{ TODO : Test for better accuracy }
Result.FrameCount := Round(Result.MediaLength / Result.AvgTimePerFrame);
Result.MediaLength := Result.FrameCount * Result.AvgTimePerFrame;
ZeroMemory(@DesiredSurface, SizeOf(DesiredSurface));
DesiredSurface.dwSize := Sizeof(DesiredSurface);
OleCheck(DDStream.GetFormat(TDDSurfaceDesc(nil^), IDirectDrawPalette(nil^),
DesiredSurface, DWord(nil^)));
Result.SurfaceDesc := DesiredSurface;
DesiredSurface.ddsCaps.dwCaps := DesiredSurface.ddsCaps.dwCaps or
DDSCAPS_OFFSCREENPLAIN or DDSCAPS_SYSTEMMEMORY;
DesiredSurface.dwFlags := DesiredSurface.dwFlags or DDSD_CAPS or DDSD_PIXELFORMAT;
{Create a surface here to get vital statistics}
OleCheck(DirectDraw.CreateSurface(DesiredSurface, DDSurface, nil));
OleCheck(DDSurface.GetSurfaceDesc(DesiredSurface));
Result.Pitch := DesiredSurface.lPitch;
if DesiredSurface.ddpfPixelFormat.dwRGBBitCount = 24 then
Result.PixelFormat := pf24bit
else if DesiredSurface.ddpfPixelFormat.dwRGBBitCount = 32 then
Result.PixelFormat := pf32bit;
Result.Width := DesiredSurface.dwWidth;
Result.Height := DesiredSurface.dwHeight;
end;
2007. február 12., hétfő
List of all ALIASES pointing to a SQL server
Problem/Question/Abstract:
For a little tool, I recently needed to get a list of all aliases which point to a SQL db. (I did not want to see those Paradox files).
Answer:
I came up with the following procedure, which I call like this:
GetAliases(ComboBox1.Items)
procedure GetAliases(const AList: TStrings);
var
i: Integer;
Desc: DBDesc;
Buff: array[0..254] of char;
begin
// list all BDE aliases
Session.GetAliasNames(AList);
for i := AList.Count - 1 downto 0 do
begin
StrPCopy(Buff, AList[i]);
Check(DbiGetDatabaseDesc(Buff, @Desc));
// no Paradox, please
if StrPas(Desc.szDBType) = 'STANDARD' then
AList.Delete(i)
end
end;
2007. február 11., vasárnap
How to change the client area of a TListBox
Problem/Question/Abstract:
I have created my own listbox control as a descendant of TListBox. What I want to be able to do is to change the client area of the listbox so that I can draw a label above the list box area. I can change the client rect by overriding the CreateWnd method like this:
procedure TMyListBox.CreateWnd;
begin
inherited CreateWnd;
ClientHeight := Height - 20;
end;
But I can't move the client rect down (ie change the origin). Using ClientOrigin.X := 20 does not work as the ClientOrigin property is read only. I'm thinking maybe I need to override the CreateParams method to do this. Any ideas?
Answer:
You have to respond to the WM_NCCALCSIZE message:
procedure TREDCustomListBox.WMNCCalcSize(var Msg: TWMNCCALCSIZE);
begin
inherited;
Inc(MSG.CalcSize_Params^.rgrc[0].Top, FHeader.Height);
end;
That is a the method within my own listbox which does exactly what you want to do. Notice how I'm incrementing the client area by the height of the header (which I implemented as a separate class so I can do the same thing in other controls). It was a fun exercise.
Another clue: You have to paint the header in WM_NCPAINT
2007. február 10., szombat
How to generate a temporary file name
Problem/Question/Abstract:
I am trying to find a function that will generate a temporary filename. I know that there is the GetTempFilename function, but I don't have any examples on how to use it.
Answer:
Solve 1:
procedure TForm1.Button1Click(Sender: TObject);
var
TempFile: array[0..MAX_PATH - 1] of Char;
TempPath: array[0..MAX_PATH - 1] of Char;
begin
GetTempPath(MAX_PATH, TempPath);
if GetTempFileName(TempPath, PChar('abc'), 0, TempFile) = 0 then
raise Exception.Create('GetTempFileName API failed. ' +
SysErrorMessage(GetLastError));
ShowMessage(TempFile);
end;
Note that this would actually create the temp file in the windows temp folder. Check online help for GetTempFileName, uUnique parameter for details.
Solve 2:
function MyGetTempFile(const APrefix: string): string;
var
MyBuffer, MyFileName: array[0..MAX_PATH] of char;
begin
FillChar(MyBuffer, MAX_PATH, 0');
FillChar(MyFileName, MAX_PATH, 0);
GetTempPath(SizeOf(MyBuffer), MyBuffer);
GetTempFileName(MyBuffer, APrefix, 0, MyFileName);
Result := MyFileName;
end;
const
MyPrefix: string = 'abc';
MyTempFile := MyGetTempFile(MyPrefix);
Solve 3:
Pass in the path and filename you want for the first parameter and your extension as the second. If you want the file to always be myfile1.tmp rather than myfile.tmp leave the last parameter, otherwise set it to false. E.g. to create a file like c:\Tempdir\MyTempFile2000.tmp
sNewFileName := CreateNewFileName('C:\TempDir\MyTempFile', '.tmp');
function CreateNewFileName(BaseFileName: string; Ext: string;
AlwaysUseNumber: Boolean = True): string;
var
DocIndex: Integer;
FileName: string;
FileNameFound: Boolean;
begin
DocIndex := 1;
Filenamefound := False;
{if number not required and basefilename doesn't exist, use that.}
if not (AlwaysUseNumber) and (not (fileexists(BaseFilename + ext))) then
begin
Filename := BaseFilename + ext;
FilenameFound := true;
end;
while not (FileNameFound) do
begin
filename := BaseFilename + inttostr(DocIndex) + Ext;
if fileexists(filename) then
inc(DocIndex)
else
FileNameFound := true;
end;
Result := filename;
end;
I simply checks if the file exists and returns the first that doesn't.
2007. február 9., péntek
How to disable the scrollbars in a TWebBrowser
Problem/Question/Abstract:
How to disable the scrollbars in a TWebBrowser
Answer:
Try this. It also uses 2 speedbuttons to scroll the page.
procedure TForm1.FormShow(Sender: TObject);
begin
{MUST navigate first}
{site I created for my baseball league}
WB.Navigate('http://www.austinmetrobaseball.com');
end;
procedure TForm1.WBDocumentComplete(Sender: TObject; const pDisp: IDispatch;
var URL: OleVariant);
begin
{turn off scrollbars}
while WB.ReadyState <> READYSTATE_COMPLETE do
Application.ProcessMessages;
WB.OleObject.document.body.style.overflowX := 'hidden';
WB.OleObject.document.body.style.overflowY := 'hidden';
end;
procedure TForm1.sbUPClick(Sender: TObject);
begin
{scrollup 100 pixels}
WB.OleObject.document.parentWindow.scrollBy(0, -100);
end;
procedure TForm1.sbDNClick(Sender: TObject);
begin
{scrolldown 100 pixels}
WB.OleObject.document.parentWindow.scrollBy(0, 100);
end;
2007. február 8., csütörtök
How to copy rich text from a TRichEdit to the clipboard
Problem/Question/Abstract:
I have used the TRichEdit component to generate some rich text which I am now holding in a byte array. How can I paste it to the clipboard so that it can be copied into MS Word?
Answer:
You have to copy it to the clipboard with a specific format. The richedit unit defines a string constant CF_RTF (very unfortunate name!). You feed that to RegisterClipboardFormat to obtain a format identifier which you can then use with Clipboard.SetAshandle.
If you write the data to a memorystream you can use the following procedure to copy the streams content to the clipboard. Use the format identifier you obtained from CF_RTF as first parameter.
procedure CopyStreamToClipboard(fmt: Cardinal; S: TStream);
var
hMem: THandle;
pMem: Pointer;
begin
{Rewind stream position to start}
S.Position := 0;
{Allocate a global memory block the size of the stream data}
hMem := GlobalAlloc(GHND or GMEM_DDESHARE, S.Size);
if hMem <> 0 then
begin
{Succeeded, lock the memory handle to get a pointer to the memory}
pMem := GlobalLock(hMem);
if pMem <> nil then
begin
{Succeeded, now read the stream contents into the memory the pointer points at}
try
S.Read(pMem^, S.Size);
{Rewind stream again, caller may be confused if the stream position is
left at the end}
S.Position := 0;
finally
{Unlock the memory block}
GlobalUnlock(hMem);
end;
{Open clipboard and put the block into it. The way the Delphi clipboard
object is written this will clear the clipboard first. Make sure the
clipboard is closed even in case of an exception. If left open it would
become unusable for other apps.}
Clipboard.Open;
try
Clipboard.SetAsHandle(fmt, hMem);
finally
Clipboard.Close;
end;
end
else
begin
{Could not lock the memory block, so free it again and raise an out of
memory exception}
GlobalFree(hMem);
OutOfMemoryError;
end;
end
else
{Failed to allocate the memory block, raise exception}
OutOfMemoryError;
end;
2007. február 7., szerda
Files Bigger than 2 gig
Problem/Question/Abstract:
Searching for files and get no problems when the size is greater than 2 gig
Answer:
The FindFirstFile / FindNextFile / FindClose APIs are used for searching for various files.
When using these APIs it is important to remember that failing to close a Find can result in files or directories not being available for some operations (such as deletes). This is because these APIs open a handle to the objects being searched, and the operating system won't allow you to do certain things to an object as long as an active handle to that object exists.
Also, the data structure used by these APIs contains string data, which is terminated by null characters.
Example:
procedure TForm1.Button1Click(Sender: TObject);
var
Handle: THandle;
s: string;
FD: WIN32_FIND_DATA;
begin
s := 'c:\*.*';
Handle := FindFirstFile(pchar(s), fd);
if Handle <> INVALID_HANDLE_VALUE then
begin
Memo1.Lines.Add(fd.cFileName);
while FindNextFile(handle, fd) = True do
Memo1.Lines.Add(fd.cFileName);
end;
Windows.FindClose(Handle);
end;
Finds the first file matching the wild file specifier. '*' can be used to match 0 or more characters, '?' for a single character. If there are no wild specifiers in the string the function acts as a query for an explicit single file. The function works for both files and folders.
The WIN32_FIND_DATA contains full information about the first matched file.
dwFileAttributes File attributes (see CreateFile)
ftCreationTime Time file created (see GetFileTime)
ftLastAccessTime Time file last accessed
ftLastWriteTime Time file last written to
nFileSizeHigh Most significant 32bits of file size(see GetFileSize)
nFileSizeLow Least significant 32bits of file size
dwReserved0 O.S. specific data
dwReserved1 O.S. specific data
cFileName File name with extension within the folder
cAlternateFileName Alternate shortened (8.3) form of name iff cFileName is not a valid MSDOS name
The returned handle is passed to FindNextFile to get at the next matching file or folder. When the scan is completed FindClose must be used to close the handle.
More Info:
WIN32_FIND_DATA = record
dwFileAttributes: DWORD;
ftCreationTime: TFileTime;
ftLastAccessTime: TFileTime;
ftLastWriteTime: TFileTime;
nFileSizeHigh: DWORD;
nFileSizeLow: DWORD;
dwReserved0: DWORD;
dwReserved1: DWORD;
cFileName: array[0..MAX_PATH - 1] of AnsiChar;
cAlternateFileName: array[0..13] of AnsiChar;
end;
FILE_ATTRIBUTE_ARCHIVE
The file is an archive file. Applications use this value to mark files for backup or removal.
FILE_ATTRIBUTE_COMPRESSED
The file or directory is compressed. For a file, this means that all of the data in the file is compressed. For a directory, this means that compression is the default for newly created files and subdirectories.
FILE_ATTRIBUTE_DIRECTORY
The file is a directory.
FILE_ATTRIBUTE_HIDDEN
The file is hidden. It is not included in an ordinary directory listing.
FILE_ATTRIBUTE_NORMAL
The file has no other attributes set. This value is valid only if used alone.
FILE_ATTRIBUTE_OFFLINE
The data of the file is not immediately available. Indicates that the file data has been physically moved to offline storage.
FILE_ATTRIBUTE_READONLY
The file is read-only. Applications can read the file but cannot write to it or delete it.
FILE_ATTRIBUTE_SYSTEM
The file is part of the operating system or is used exclusively by it.
FILE_ATTRIBUTE_TEMPORARY
The file is being used for temporary storage. Applications should write to the file only if absolutely necessary. Most of the file's data remains in memory without being flushed to the media because the file will soon be deleted.
2007. február 6., kedd
How to set the item index in an alpha sorted TComboBox when searching incrementally
Problem/Question/Abstract:
I have some alpha sorted items in a combo box (style csDropDown). When the user types in text, the combo box incrementally searches but it does not set the itemindex property. Is there a way of making it do this?
Answer:
You could use the OnChange handler to perform a CB_FINDSTRING with the current edit text, then set the itemindex to the found item. But that is disruptive to typing, if the found item is not the one the user wants he has no way to change it, since each change triggers OnChange, which again finds the item. So you have to invest considerably more effort into this. Attach these handlers to the OnKeyPress and the OnChange event of the combobox, that seems to work fairly well.
procedure TForm1.ComboBox1Change(Sender: TObject);
var
oldpos: Integer;
item: Integer;
begin
with sender as TComboBox do
begin
oldpos := selstart;
item := Perform(CB_FINDSTRING, -1, lparam(Pchar(text)));
if item >= 0 then
begin
onchange := nil;
text := items[item];
selstart := oldpos;
sellength := gettextlen - selstart;
onchange := combobox1change;
end;
end;
end;
procedure TForm1.ComboBox1KeyPress(Sender: TObject; var Key: Char);
var
oldlen: Integer;
begin
if key = #8 then
with sender as TComboBox do
begin
oldlen := sellength;
if selstart > 0 then
begin
selstart := selstart - 1;
sellength := oldlen + 1;
end;
end;
end;
This works with Win2000, but on a Win98 machine, the ItemIndex is getting set incorrectly after the first search. To make it work under both Win2000 and Win98, you could do something like this:
procedure TMainForm.ComboBoxKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
var
s: string;
begin
if key = VK_RETURN then
begin
s := TComboBox(Sender).Text;
{The search doesn't work in Win 98 with DroppedDown set to true}
TComboBox(Sender).DroppedDown := false;
TComboBox(Sender).Text := s;
end;
end;
2007. február 5., hétfő
Disable the transparent part of a TSpeedButton from clicking
Problem/Question/Abstract:
How to disable the transparent part of a TSpeedButton from clicking
Answer:
procedure TMFSpeedButton.MouseDown(Button: TMouseButton; Shift: TShiftState; X, Y:
Integer);
var
ScreenDC: HDC;
Transp, Bits: Boolean;
begin
inherited MouseDown(Button, Shift, X, Y);
if (Button = mbLeft) and Enabled then
begin
Bits := False;
Transp := False;
ScreenDC := GetDC(0);
try
{Transparent color is color of form background. Test for True Color 24bit
or more, because on lower color depth the color is blended, so it works only
on true color for some colors. If it is for example clBlack, it works
on everything}
Bits := GetDeviceCaps(ScreenDC, BITSPIXEL) >= 24;
{test for desired color}
Transp := GetPixel(ScreenDC, Mouse.CursorPos.x, Mouse.CursorPos.Y) = $0094ADBD;
finally
ReleaseDC(0, ScreenDC);
end;
{leave procedure if test for transp. color was successful}
if Transp and Bits then
Exit;
if not FDown then
begin
FState := bsDown;
Invalidate;
end;
FDragging := True;
end;
end;
2007. február 4., vasárnap
Starting Delphi without a project
Problem/Question/Abstract:
Starting Delphi without a project
Answer:
Does it disturb you that the Delphi IDE starts up with the a 'noname project'?
There is help. Run Delphi with passing the -np switch. That will open it without a project. You could put this parameter in your shortcut that you use to start up the IDE.
\Delphi5\bin\Delphi32.exe -np
2007. február 3., szombat
How to display the item text of a TListBox in the hint window of the listbox
Problem/Question/Abstract:
I would like to have my hints read the same as the listbox item that the mouse is pointing to. How can I do that?
Answer:
Solve 1:
You could use the listboxes OnMOuseMove event together with the ItemAtPos method.
procedure TForm1.ListBox1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
item: Integer;
begin
with Sender as TListbox do
begin
item := itemAtpos(Point(x, y), true);
if item >= 0 then
begin
if hint <> items[item] then
begin
hint := items[item];
application.cancelhint;
end;
end;
end;
end;
Solve 2:
You can use the OnMouseMove event and trap which item is under the cursor.
procedure TForm1.ListBox1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
APoint: TPoint;
Index: Integer;
begin
APoint.X := X;
APoint.Y := Y;
Index := ListBox1.ItemAtPos(APoint, True);
if Index >= 0 then
begin
ListBox1.Hint := ListBox1.Items.Strings[Index];
end;
end;
If you want to Hint to change when the mouse moves after the hint is originally shown, you might want to do something like this:
Set the ShowHint property of the list box to False.
procedure TForm1.ListBox1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
var
APoint: TPoint;
Index: Integer;
HW: THintWindow;
Rec: TRect;
sHint: string;
begin
APoint.X := X;
APoint.Y := Y;
Index := ListBox1.ItemAtPos(APoint, True);
if Index >= 0 then
begin
HW := THintWindow.Create(nil);
try
GetCursorPos(APoint);
sHint := ListBox1.Items.Strings[Index];
Rec.Top := APoint.Y + 20;
Rec.Left := APoint.X;
Rec.Right := Rec.Left + HW.Canvas.TextWidth(sHint) + 6;
Rec.Bottom := Rec.Top + HW.Canvas.TextHeight(sHint) + 4;
HW.ActivateHint(Rec, sHint);
HW.Refresh;
Sleep(1000);
HW.ReleaseHandle;
finally
HW.Free;
end;
end;
end;
2007. február 2., péntek
How to get the icon of a window for which you know the handle
Problem/Question/Abstract:
How to get the icon of a window for which you know the handle
Answer:
Solve 1:
function GetWindowIcon(Wnd: HWND): TIcon;
{Wnd: Handle to window whose icon you want
Returns: TIcon instance holding the window's icon}
begin
{Create a TIcon instance to hold the icon information}
Result := TIcon.Create;
{As the Win32 API help states, getclasslong will return the handle to the window's icon. Assign that value to the TIcon's Handle property as described in the VCL help.}
Result.Handle := GetClassLong(Wnd, GCL_HICON);
end;
Additional note:
In my opinion, memory should be allocated and freed on the same level (if possible). So I would recommend changing the above code to this:
procedure GetWindowIcon(Wnd: HWND; Icon: TIcon);
begin
if not Assigned(Icon) then
raise Exception.Create('Create instance of Icon ' + 'before calling GetWindowIcon')
Icon.Handle := GetClassLong(Wnd, GCL_HICON);
end;
Solve 2:
Use GetClassLong to obtain the icon handle and then use CopyIcon to create a new icon from this icon handle. Assuming the Delphi help is open for testing, following are examples:
{Returns true if icon is retrieved and copied to AIcon succesfully}
function CopyIconFromWindowHandle(AHandle: THandle; AIcon: TIcon): boolean;
var
hWindowIcon: THandle; {HICON}
tmpIcon: TIcon; {temporary TIcon}
begin
Result := true;
if (not (AHandle > 0)) or (AIcon = nil) then
begin
Result := false;
exit;
end;
hWindowIcon := GetClassLong(AHandle, GCL_HICON);
if hWindowIcon = 0 then
begin
Result := false;
exit;
end;
tmpIcon := TIcon.Create;
try
{exactly the same icon is copied}
tmpIcon.Handle := CopyIcon(hWindowIcon);
if tmpIcon.Handle = 0 then
begin
Result := false;
exit;
end;
{AIcon is changing}
try
AIcon.Assign(tmpIcon);
except
Result := false;
raise;
end
finally
tmpIcon.Free;
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
var
wHandle: THandle;
TestIcon: TIcon;
begin
wHandle := FindWindow(nil, pchar('Delphi Help'));
if not (wHandle > 0) then
exit;
TestIcon := TIcon.Create;
try
TestIcon.Handle := CopyIcon(Application.Icon.Handle);
Canvas.Draw(0, 0, TestIcon);
if CopyIconFromWindowHandle(wHandle, TestIcon) = true then
Canvas.Draw(0, 80, TestIcon);
finally
TestIcon.Free;
end;
end;
2007. február 1., csütörtök
Find out which language version of Word is installed
Problem/Question/Abstract:
How can I get the language of an Office installation? I need to add a new menu item, but the captions are Office language dependent (File - English, Archivo - Spanish, etc. )
Answer:
{ ... }
MsWord := CreateOleObject('Word.Basic');
try
{Return Application Info. This call is the same for English and
French Microsoft Word.}
Lang := MsWord.AppInfo(Integer(16));
except
try
{For German Microsoft Word the procedure name is translated}
Lang := MsWord.AnwInfo(Integer(16));
except
try
{For Swedish Microsoft Word the procedure name is translated}
Lang := MsWord.PrgmInfo(Integer(16));
except
try
{For Dutch Microsoft Word the procedure name is translated}
Lang := MsWord.ToepasInfo(Integer(16));
except
{If this procedure does not exist there is a different translation
of Microsoft Word}
ShowMessage('Microsoft Word version is not German, French, Dutch, Swedish
or English.');
Exit;
end;
end;
end;
end;
ShowMessage(Lang);
{ ... }
Feliratkozás:
Bejegyzések (Atom)