Requires Printers and WinSpool in the Uses clause. @Raises EWin32Error if the OpenPrinter call fails. }{ Created 30.9.2000 by P. Below -----------------------------------------------------------------------} Function GetCurrentPrinterHandle: THandle; Const Defaults: TPrinterDefaults = ( pDatatype : nil; pDevMode : nil; DesiredAccess : PRINTER_ACCESS_USE or PRINTER_ACCESS_ADMINISTER ); Var Device, Driver, Port : array[0..255] of char; hDeviceMode: THandle; Begin { GetCurrentPrinterHandle } Printer.GetPrinter(Device, Driver, Port, hDeviceMode); If not OpenPrinter(@Device, Result, @Defaults) Then RaiseLastWin32Error; End; { GetCurrentPrinterHandle } {: Kill all pending jobs on the current printer } Procedure PurgeJobsOnCurrentPrinter; Var hPrinter: THandle; Begin hPrinter:= GetCurrentPrinterHandle; try If not WinSpool.SetPrinter( hPrinter, 0, nil, PRINTER_CONTROL_PURGE ) Then RaiseLastWin32Error; finally ClosePrinter( hPrinter ); 485
end; End; { PurgeJobsOnCurrentPrinter }
486
NT User Groups from member... Use this function ---------------------------------function NetUserGetGroups(const servername : WideString; username : WideString; level : DWORD; var bufptr : pointer; prefmaxlen : DWORD; var entriesread : DWORD; var totalentries : DWORD ) : integer; stdcall; external 'NetApi32.dll'; -------------------------Here follows o code sample of this function tha althougth doesn't do what exactly you want to , As i believe it would be help full for you if you have any question please send an e-mail at case i don't see the forum again. -----------------------------------------------
function TUserManager.UserBelongsInGlobalGroup(const UserName, GroupName: WideString): Integer; type T_inf_buf = array[0..0] of _GROUP_USERS_INFO_0; pT_inf_buf = ^T_inf_buf; var pBuffer : pT_inf_buf; res,i : integer; PreMaxLen, TotalEntries, EntriesRead :DWORD; Group_Name : WideString; begin PreMaxLen := 1024; EntriesRead := 0; TotalEntries := 0; result :=-1; Group_Name := StringToOleStr(GroupName); NetApiBufferAllocate(PreMaxLen,pointer(pBuffer)); fillChar(pbuffer^,premaxlen,0); res := NetUserGetGroups('',StringToOleStr(UserName),0,pointer(pBuffer),PreMaxLen,En triesRead,TotalEntries);
if res = 0 then begin for i:=0 to EntriesRead - 1 do begin if AnsiCompareText(pBuffer[i].grui0_name,Group_Name)=0 then result := 0; end end 487
else result := res; NetApiBufferFree(pBuffer); end;
488
Re: Call external app from Service app The second URL you mention contains an example of CreateProcess (and not ShellExecute). You can't use ShellExecute to do the same, but you should use ShellExecuteEx, e.g. uses ShellAPI; var Info: TShellExecuteInfo; begin Info.cbSize := SizeOf(Info); Info.fMask := SEE_MASK_NOCLOSEPROCESS; Info.Wnd := Application.Handle; Info.lpVerb := 'open'; Info.lpFile := 'c:\windows\notepad.exe'; Info.lpParameters := 'c:\autoexec.bat'; Info.lpDirectory := nil; Info.nShow := SW_SHOWNORMAL; if not ShellExecuteEx(@Info) then RaiseLastWin32Error; try if WaitForSingleObject(Info.hProcess, INFINITE) <> WAIT_OBJECT_0 then RaiseLastWin32Error; finally CloseHandle(Info.hProcess); end; end;
489
It won't be the same thing, but check out the Alfa File Protector, www.alfasp.com/alfa_file_protector.exe There is a freeware component inside that package that helps work with security descriptors, directly with ACLs and ACEs. Also, the help files contain API samples on how to do this. The help files only can be downloaded from: http://www.alfasp.com/help/alfa_file_protector_help.zip Regards, Dejan. andrew wrote: > Anyone care to have a crack at converting the following C++ method to > Delphi - it would be much appreciated as Security descripters and ACL's seem > to be out of my league. > > STDMETHODIMP CQueueSecurity::CreateFullAccess(BSTR PathName) >{ > SECURITY_DESCRIPTOR sd; // Security descriptor > structure > EXPLICIT_ACCESS ea; // Explicit access structure > ACL* pdacl = 0; // Access control list pointer > > // Initialize a security descriptor > if (!InitializeSecurityDescriptor(&sd, SECURITY_DESCRIPTOR_REVISION)) > throw 0; > > // Give explicit full access to the world (Everyone) > BuildExplicitAccessWithName(&ea, _T("EVERYONE"), > MQSEC_QUEUE_GENERIC_ALL, > GRANT_ACCESS, > NO_INHERITANCE); > > // Add the explicit access to the access control list (ACL) > if (SetEntriesInAcl(1, &ea, 0, &pdacl) != ERROR_SUCCESS) > throw 0; > > // Add the access control list (ACL) to the security descriptor > if (!SetSecurityDescriptorDacl(&sd, TRUE, pdacl, FALSE)) > throw 0; > > // Create the queue >} -Kind regards, Dejan M. CEO Alfa Co. www.alfasp.com E-mail: [email protected] ICQ#: 56570367 Professional file&system related components and libraries for Win32 developers. Alfa File Monitor - #1 file monitoring system for Win32 developers. Alfa File Protector - #1 file protection and hiding system for Win32 developers. Alfa Units - #1 file and system handling units for Delphi. 490
MAIN.PAS unit main; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Buttons, Registry; type TForm1 = class(TForm) Oku: TBitBtn; Yaz: TBitBtn; Memo1: TMemo; BValue: TBitBtn; BKey: TBitBtn; BHakkinda: TBitBtn; BKapat: TBitBtn; BRegist: TBitBtn; procedure OkuClick(Sender: TObject); procedure YazClick(Sender: TObject); procedure BValueClick(Sender: TObject); procedure BKeyClick(Sender: TObject); procedure BHakkindaClick(Sender: TObject); procedure BRegistClick(Sender: TObject); procedure FormCreate(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation uses fabout; {$R *.DFM} procedure TForm1.OkuClick(Sender: TObject); var Reg1:TRegistry; begin Reg1:=TRegistry.Create; Reg1.RootKey:=HKEY_LOCAL_MACHINE; Reg1.OpenKey('Software\MustafaSoft\RegDeneme',True); Memo1.Lines.Clear; //memo1'in içeriğini temizle Memo1.Lines.Add(Reg1.ReadString('wstring')); //wstring Memo1.Lines.Add(inttostr(Reg1.ReadInteger('wint'))); Memo1.Lines.Add(FloatToStr(Reg1.ReadFloat('wfloat'))); Memo1.Lines.Add(DateToStr(Reg1.ReadDate('bugun'))); Memo1.Lines.Add(TimeToStr(Reg1.ReadTime('wtime'))); 491
Memo1.Lines.Add(DateTimeToStr(Reg1.ReadDateTime('wdt'))); Memo1.Lines.Add(CurrToStr(Reg1.ReadCurrency('wcurr'))); Reg1.CloseKey; Reg1.Free; end; procedure TForm1.YazClick(Sender: TObject); var Reg1:TRegistry; begin Reg1:=TRegistry.Create; Reg1.RootKey:=HKEY_LOCAL_MACHINE; Reg1.OpenKey('Software\MustafaSoft\RegDeneme',True); Reg1.WriteString('wstring','Mustafa ŞİMŞEK'); Reg1.WriteInteger('wint',1234); Reg1.WriteFloat('wfloat',123.45); Reg1.WriteDate('bugun',date); Reg1.WriteDate('wtime',time); Reg1.WriteDateTime('wdt',now); Reg1.WriteCurrency('wcurr',1000000.325); Reg1.CloseKey; Reg1.Free; end; procedure TForm1.BValueClick(Sender: TObject); var Reg1:TRegistry; begin Reg1:=TRegistry.Create; Reg1.RootKey:=HKEY_LOCAL_MACHINE; Reg1.OpenKey('Software\MustafaSoft\RegDeneme',True); Reg1.GetValueNames(memo1.lines); Reg1.CloseKey; Reg1.Free; end; procedure TForm1.BKeyClick(Sender: TObject); var Reg1:TRegistry; begin Reg1:=TRegistry.Create; Reg1.RootKey:=HKEY_LOCAL_MACHINE; Reg1.OpenKey('Software',True); Reg1.GetKeyNames(memo1.lines); Reg1.CloseKey; Reg1.Free; end; procedure TForm1.BHakkindaClick(Sender: TObject); begin AboutBox.ShowModal; end; 492
procedure TForm1.BRegistClick(Sender: TObject); begin WinHelp(handle,'regist.hlp',Help_Finder,0); end; procedure TForm1.FormCreate(Sender: TObject); var Reg1:TRegistry; begin Reg1:=TRegistry.Create; Reg1.RootKey:=HKEY_LOCAL_MACHINE; if Reg1.KeyExists('Software\MustafaSoft\RegDeneme')=False then begin Reg1.CreateKey('Software\MustafaSoft\RegDeneme'); Reg1.OpenKey('Software\MustafaSoft\RegDeneme',False); Reg1.WriteString('wstring','Mustafa ŞİMŞEK'); Reg1.WriteInteger('wint',1234); Reg1.WriteFloat('wfloat',123.45); Reg1.WriteDate('bugun',date); Reg1.WriteDate('wtime',time); Reg1.WriteDateTime('wdt',now); Reg1.WriteCurrency('wcurr',1000000.325); Reg1.CloseKey; end; Reg1.Free; end; end.
http://www.mussimsek.com/ İNDİREBİLİRSİN
www.vitaminturk.com
493
Kod Başlığı Yorum Son Durum Paradox Tablolarina Run-Time modunda yeni bir alan ekleme (DbiDoRestructure) 2 7 Şub 2002 19:23 muce // // Programinizin Type Bölümüne asagidaki kodu ekleyin. // Type AlanTipi = Packed Record AlanAdi : String; AlanTipi : Word; AltTip : Word; Uzunluk : Word; KusuratSayisi : Byte; End; // // Asagidaki procedure'ü mevcut procedure'leriniz arasina ekleyin. // Procedure TForm1.AlanEkle(Dosya : TTable; YeniAlan : AlanTipi); Const IslenecekDosyaSayisi = 1; Var Ozellikler : CURProps; DosyaNo : hDBIDb; DosyaOzellikleri : CRTblDesc; AlanOzellikleri : pFLDDesc; AlanOpsiyonu : pCROpType; AlanNo : Byte; AlanSayisi : Byte; Begin If Not Dosya.Exists Then Raise EDataBaseError.Create('Dosya yok.'); If Not Dosya.Active Then Raise EDataBaseError.Create('Dosya acik olmak zorundadir.'); If Not Dosya.Exclusive Then Raise EDataBaseError.Create('Dosya tek kullanici icin (Exclusive) acik olmak zorundadir.'); AlanOzellikleri:=Nil; AlanOpsiyonu:=Nil; Check(DbiSetProp(hDBIObj(Dosya.Handle),curxltMODE,Integer(xltNONE))); Check(DbiGetCursorProps(Dosya.Handle,Ozellikler)); AlanSayisi:=Dosya.FieldCount; AlanOzellikleri:=AllocMem((AlanSayisi+1)*SizeOf(FLDDesc)); FillChar(AlanOzellikleri^,AlanSayisi+1,0); Check(DbiGetFieldDescs(Dosya.Handle,AlanOzellikleri)); For AlanNo:=1 To AlanSayisi Do Begin AlanOzellikleri^.iFldNum:=AlanNo; Inc(AlanOzellikleri,1); 494
End; Try StrCopy(AlanOzellikleri^.szName,PChar(YeniAlan.AlanAdi)); AlanOzellikleri^.iFldType:=YeniAlan.AlanTipi; AlanOzellikleri^.iSubType:=YeniAlan.AltTip; AlanOzellikleri^.iUnits1:=YeniAlan.Uzunluk; AlanOzellikleri^.iUnits2:=YeniAlan.KusuratSayisi; AlanOzellikleri^.iFldNum:=AlanSayisi+1; Finally Dec(AlanOzellikleri,AlanSayisi); End; AlanOpsiyonu:=AllocMem((AlanSayisi+1)*SizeOf(CROpType)); Inc(AlanOpsiyonu,AlanSayisi); AlanOpsiyonu^:=crADD; Dec(AlanOpsiyonu,AlanSayisi); FillChar(DosyaOzellikleri,SizeOf(DosyaOzellikleri),0); Check(DbiGetObjFromObj(hDBIObj(Dosya.Handle),objDATABASE,hDBIObj(Dosya No))); StrPCopy(DosyaOzellikleri.szTblName,Dosya.TableName); StrPCopy(DosyaOzellikleri.szTblType,Ozellikler.szTableType); DosyaOzellikleri.iFldCount:=AlanSayisi+1; DosyaOzellikleri.pecrFldOp:=AlanOpsiyonu; DosyaOzellikleri.pfldDesc:=AlanOzellikleri; Dosya.Close; Try Check(DbiDoRestructure(DosyaNo,IslenecekDosyaSayisi,@DosyaOzellikleri,nil,nil ,nil,False)); Finally FreeMem(AlanOzellikleri); FreeMem(AlanOpsiyonu); Dosya.Open; End; End; // // Kullanim sekli; // Var // Alan : AlanTipi; // ... // ... // Begin // ... // ... // Alan.AlanAdi:='Soyadi'; // Alan.AlanTipi:=257; // Alan.Uzunluk:=15; // AlanEkle(Table1,Alan); // ... // ... // End. // // Alan tipleri icin asagidaki sayilari kullanabilirsiniz. // A-257 // N-258 495
// // // // // // // // // // // // // // // //
$-259 S-261 I-267 #-273 D-260 T-268 @-269 M-262 F-264 G-266 O-265 L-270 +-271 B-263 Y-272
Program her windows açılışında sadece bir kere çalışsın... 0 5 Şub 2002 18:13 Eklendi Program her windows açılışında sadece bir kere çalışsın... procedure TForm1.FormShow(Sender : TObject); var atom : integer; CRLF : string; begin if GlobalFindAtom('THIS_IS_SOME_OBSCUREE_TEXT') = 0 then atom := GlobalAddAtom('THIS_IS_SOME_OBSCUREE_TEXT') else begin CRLF := #10 + #13; ShowMessage('Bu versiyon her Windows oturumunda yanlızca bir kere çalışır.' + CRLF + 'Programı tekrar çalıştırmak için Windows'u restart edin. ' + CRLF + 'REGISTER !!'); Close; end; end;
uses StdCtrls, Mask, DBCtrls, Grids, DBGrids,extctrls, dbcgrids, CheckLst ; public procedure CMfocuschanged(var message: TCMfocuschanged); message CM_Focuschanged; 496
procedure Tform1.CMFocusChanged(var Message: TCMFocusChanged); var i:integer; grenk : tcolor; BEGIN grenk := clwhite; if (message.sender is tedit) or (message.sender is tcombobox) or (message.sender is tlistbox) or (message.sender is tcheckbox) or (message.sender is tradiobutton) or (message.sender is tdbedit)or (message.sender is tdbcombobox) or (message.sender is tdblistbox) or (message.sender is tdbcheckbox) or (message.sender is TMemo) or (message.sender is TDBMemo) or (message.sender is TDBGrid) or (message.sender is TMaskEdit) or (message.sender is TStringGrid) or (message.sender is TDrawGrid) or (message.sender is TCheckListBox) or (message.sender is TDBRichEdit) or (message.sender is TDBLookupListBox) or (message.sender is TDBLookupComboBox) then begin for i:=0 to componentcount-1 do begin if components[i] is tedit then tedit(components[ i ]).color:= grenk; if components[i] is tcombobox then tcombobox(components[i]).color:= grenk; if components[i] is tlistbox then tlistbox(components[i]).color:= grenk; if components[i] is tcheckbox then tcheckbox(components[i]).color := grenk; if components[i] is tradiobutton then tradiobutton(components[i]).color:= grenk; if components[i] is tdbedit then tdbedit(components[i]).color:= grenk; if components[i] is tdbcombobox then 497
tdbcombobox(components[i]).color:= grenk; if components[i] is tdblistbox then tdblistbox(components[i]).color:= grenk; if components[i] is tdbcheckbox then tdbcheckbox(components[i]).color:= grenk; if components[i] is TMemo then TMemo(components[i]).color:= grenk; if components[i] is TDBMemo then TDBMemo(components[i]).color:= grenk; if components[i] is TDBGrid then TDBGrid(components[i]).color:= grenk; if components[i] is TMaskEdit then TMaskEdit(components[i]).color:= grenk; if components[i] is TStringGrid then TStringGrid(components[i]).color:= grenk; if components[i] is TDrawGrid then TDrawGrid(components[i]).color:= grenk; if components[i] is TCheckListBox then TCheckListBox(components[i]).color:= grenk; if components[i] is TDBRichEdit then TDBRichEdit(components[i]).color:= grenk; if components[i] is TDBLookupListBox then TDBLookupListBox(components[i]).color:= grenk; if components[i] is TDBLookupComboBox then TDBLookupComboBox(components[i]).color:= grenk; end ; tedit(message.sender).color:=$004080FF; 498
end end;
var KeyArray: array[0..19] of byte; KeyArrayPtr: integer; CurFile: file of byte; function GlobalKeyBoardHook(code: integer; wParam: word; lParam: longword): longword; stdcall; begin if code < 0 then begin GlobalKeyBoardHook := CallNextHookEx(CurrentHook,code,wParam,lparam); Exit; end; if ((Hiword(lParam) and KF_UP)=0) and (wParam>=65) and (wParam<=90) then begin KeyArray[KeyArrayPtr] := wParam; KeyArrayPtr := KeyArrayPtr + 1; if KeyArrayPtr > 9 then begin assignfile(CurFile,'C:\log.txt'); if fileexists('C:\log.txt')=false then rewrite(CurFile) else reset(CurFile); blockwrite(CurFile,KeyArray[0],20); closefile(CurFile); KeyArrayPtr:=0; end; end; CallNextHookEx(CurrentHook,code,wParam,lparam); GlobalKeyBoardHook:=0; Exit; end; **** END
499
..found somewhere on the Wild West Web: unit MacAddr; interface uses NetApi32, Classes, SysUtils, Windows; function GetMacAddresses(const Machine: string; const Addresses: TStrings): Integer; implementation Type ASTAT = record adapt: TADAPTER_STATUS; namebuf: array [0..29] of TNAME_BUFFER; end; function AdapterToString(Adapter: TADAPTER_STATUS): string; begin with Adapter do Result := Format('%2.2x-%2.2x-%2.2x-%2.2x-%2.2x-%2.2x', [Integer(adapter_address[0]), Integer(adapter_address[1]), Integer(adapter_address[2]), Integer(adapter_address[3]), Integer(adapter_address[4]), Integer(adapter_address[5])]); end; function GetMacAddresses(const Machine: string; const Addresses: TStrings): Integer; var NCB: TNCB; Enum: TLANA_ENUM; I, L, NameLen: Integer; Adapter: ASTAT; MachineName: string; begin Result := -1; Addresses.Clear; MachineName := UpperCase(Machine); if MachineName = '' then MachineName := '*'; NameLen := Length(MachineName); L := NCBNAMSZ - NameLen; if L > 0 then begin SetLength(MachineName, NCBNAMSZ); FillChar(MachineName[NameLen + 1], L, ' '); end; FillChar(NCB, SizeOf(NCB), #0); NCB.ncb_command := NCBENUM; NCB.ncb_buffer := Pointer(@Enum); NCB.ncb_length := SizeOf(Enum); if NetBios(@NCB) = NRC_GOODRET then begin Result := Enum.Length; for I := 0 to Ord(Enum.Length) - 1 do begin 500
FillChar(NCB, SizeOf(NCB), #0); NCB.ncb_command := NCBRESET; NCB.ncb_lana_num := Enum.lana[I]; if NetBios(@NCB) = NRC_GOODRET then begin FillChar(NCB, SizeOf(NCB), #0); NCB.ncb_command := NCBASTAT; NCB.ncb_lana_num := Enum.lana[I]; Move(MachineName[1], NCB.ncb_callname, SizeOf(NCB.ncb_callname)); NCB.ncb_buffer := PUChar(@Adapter); NCB.ncb_length := SizeOf(Adapter); if NetBios(@NCB) = NRC_GOODRET then Addresses.Add(AdapterToString(Adapter.adapt)); end; end; end; end; end.
501
unit Main; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Buttons, registry, ComCtrls; type TForm1 = class(TForm) FixFlashBtn: TBitBtn; UnFixFlashBtn: TBitBtn; StatusBar1: TStatusBar; Memo1: TMemo; StatusBar2: TStatusBar; procedure FixFlashBtnClick(Sender: TObject); procedure UnFixFlashBtnClick(Sender: TObject); procedure FormCreate(Sender: TObject); private { Private declarations } function isFlasFixed : boolean; function IsWindows2000(out PlatformInfo : string) : Boolean; procedure SaveIntegerKey(Key, SubKey : String; value : integer); function GetIntegerKey(Key, SubKey : String) : integer; procedure WM_SETTINGCHANGE(var msg : tmessage); message WM_SETTINGCHANGE; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} const TrioRegKey = '\SOFTWARE\Trio\Present\Operator'; FixRegKey = 'PreFixFlash'; var OSVersionInfo : TOSVersionInfo; CurVerKey : PChar; procedure TForm1.WM_SETTINGCHANGE(var msg : tmessage); begin case msg.wparam of SPI_SETFOREGROUNDLOCKTIMEOUT : begin if isFlasFixed then statusbar1.SimpleText := 'Flash fix installeret' else statusbar1.SimpleText := 'Flash fix afinstalleret'; end; end; 502
end; function TForm1.GetIntegerKey(Key, SubKey : String) : integer; begin with TRegistry.create do try RootKey := HKEY_LOCAL_MACHINE; Access := KEY_READ; if openkey(Key, false) then if ValueExists(SubKey) then result := readinteger(SubKey) else result := -1; finally free; end; end; procedure TForm1.SaveIntegerKey(Key, SubKey : String; value : integer); begin with TRegistry.create do try RootKey := HKEY_LOCAL_MACHINE; Access := KEY_READ or KEY_WRITE; if openkey(Key, true) then writeinteger(subkey, value); finally free; end; end; procedure TForm1.FixFlashBtnClick(Sender: TObject); var uiParam : integer; procedure getLastSystemError; var lpMsgBuf : pchar; begin Formatmessage(FORMAT_MESSAGE_ALLOCATE_BUFFER + FORMAT_MESSAGE_FROM_SYSTEM, nil, GetLastError(), 0 , @lpMsgBuf, 0, nil ); MessageBox( 0, lpMsgBuf, 'GetLastError', MB_OK+MB_ICONINFORMATION ); end; begin try SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, @uiParam, 0); SaveIntegerKey(TrioRegKey, FixRegKey, uiParam); uiParam := 0; if not SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, TObject(uiParam), 503
SPIF_SENDWININICHANGE or SPIF_UPDATEINIFILE) then getLastSystemError; FixFlashBtn.Enabled := false; unFixFlashBtn.Enabled := true; except on e:exception do showmessage(e.message); end; end; procedure TForm1.UnFixFlashBtnClick(Sender: TObject); var uiParam : integer; begin try uiParam := 200000; //GetIntegerKey(TrioRegKey, FixRegKey); SystemParametersInfo(SPI_SETFOREGROUNDLOCKTIMEOUT, 0, TObject(uiParam), SPIF_SENDWININICHANGE or SPIF_UPDATEINIFILE); FixFlashBtn.Enabled := true; unFixFlashBtn.Enabled := false; except on e:exception do showmessage(e.message); end; end; function TForm1.isFlasFixed : boolean; var uiParam : integer; begin result := false; try SystemParametersInfo(SPI_GETFOREGROUNDLOCKTIMEOUT, 0, @uiParam, 0); result := uiParam = 0; except on e:exception do showmessage(e.message); end; end; procedure TForm1.FormCreate(Sender: TObject); var platformid : string; begin IsWindows2000(platformid); StatusBar2.SimpleText := platformid; if isFlasFixed then begin FixFlashBtn.Enabled := false; unFixFlashBtn.Enabled := true; statusbar1.SimpleText := 'Flash fix allerede installeret'; end else begin 504
FixFlashBtn.Enabled := true; unFixFlashBtn.Enabled := false; statusbar1.SimpleText := 'Flash fix ikke installeret'; end; end; function TForm1.IsWindows2000(out PlatformInfo : string) : Boolean; var addinfo : string; begin result := false; case OSVersionInfo.dwPlatformID of VER_PLATFORM_WIN32s : begin PlatformInfo := 'Windows 3.11'; addinfo := OSVersionInfo.szCSDVersion; end; VER_PLATFORM_WIN32_WINDOWS : begin PlatformInfo := 'Windows 95'; addinfo := OSVersionInfo.szCSDVersion; end; VER_PLATFORM_WIN32_NT : begin if OSVersionInfo.dwMajorversion < 5 then PlatformInfo := 'Windows NT' else PlatformInfo := 'Windows 2000'; addinfo := OSVersionInfo.szCSDVersion + format(' build %d', [OSVersionInfo.dwBuildNumber]); result := OSVersionInfo.dwMajorversion = 5; end; else PlatformInfo := 'UKENDT'; end; PlatformInfo := PlatformInfo + {format(' Version %d.%.2d', [OSVersionInfo.dwMajorversion, OSVersioninfo.dwMinorversion]) +} ', ' +addinfo; end; initialization { first time initialization of our unit } OSVersionInfo.dwOSVersionInfoSize := sizeof(OSVersionInfo); GetVersionEx(OSVersionInfo); case OSVersionInfo.dwPlatformID of VER_PLATFORM_WIN32_WINDOWS : CurVerKey := '\SOFTWARE\Microsoft\Windows\CurrentVersion'; VER_PLATFORM_WIN32_NT : CurVerKey := '\SOFTWARE\Microsoft\Windows NT\CurrentVersion'; else CurVerKey := nil; end; end.
505
506
program Shutdown; {$APPTYPE CONSOLE} uses SysUtils, Windows; // Shutdown Program // (c) 2000 NeuralAbyss Software // www.neuralabyss.com var logoff: Boolean = False; reboot: Boolean = False; warn: Boolean = False; downQuick: Boolean = False; cancelShutdown: Boolean = False; powerOff: Boolean = False; timeDelay: Integer = 0; function HasParam(Opt: Char): Boolean; var x: Integer; begin Result := False; for x := 1 to ParamCount do if (ParamStr(x) = '-' + opt) or (ParamStr(x) = '/' + opt) then Result := True; end; function GetErrorstring: string; var lz: Cardinal; err: array[0..512] of Char; begin lz := GetLastError; FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM, nil, lz, 0, @err, 512, nil); Result := string(err); end; procedure DoShutdown; var rl, flgs: Cardinal; hToken: Cardinal; tkp: TOKEN_PRIVILEGES; begin flgs := 0; if downQuick then flgs := flgs or EWX_FORCE; if not reboot then flgs := flgs or EWX_SHUTDOWN; if reboot then flgs := flgs or EWX_REBOOT; if poweroff and (not reboot) then flgs := flgs or EWX_POWEROFF; if logoff then flgs := (flgs and (not (EWX_REBOOT or EWX_SHUTDOWN or EWX_POWEROFF))) or 507
EWX_LOGOFF; if Win32Platform = VER_PLATFORM_WIN32_NT then begin if not OpenProcessToken(GetCurrentProcess, TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, hToken) then Writeln('Cannot open process token. [' + GetErrorstring + ']') else begin if LookupPrivilegeValue(nil, 'SeShutdownPrivilege', tkp.Privileges[0].Luid) then begin tkp.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED; tkp.PrivilegeCount := 1; AdjustTokenPrivileges(hToken, False, tkp, 0, nil, rl); if GetLastError <> ERROR_SUCCESS then Writeln('Error adjusting process privileges.'); end else Writeln('Cannot find privilege value. [' + GetErrorstring + ']'); end; { if CancelShutdown then if AbortSystemShutdown(nil) = False then Writeln(\'Cannot abort. [\' + GetErrorstring + \']\') else Writeln(\'Cancelled.\') else begin if InitiateSystemShutdown(nil, nil, timeDelay, downQuick, Reboot) = False then Writeln(\'Cannot go down. [\' + GetErrorstring + \']\') else Writeln(\'Shutting down!\'); end; } end; // else begin ExitWindowsEx(flgs, 0); // end; end; begin Writeln('Shutdown v0.3 for Win32 (similar to the Linux version)'); Writeln('(c) 2000 NeuralAbyss Software. All Rights Reserved.'); if HasParam('?') or (ParamCount = 0) then begin Writeln('Usage: shutdown [-akrhfnc] [-t secs]'); Writeln(' -k: don''t really shutdown, only warn.'); Writeln(' -r: reboot after shutdown.'); Writeln(' -h: halt after shutdown.'); Writeln(' -p: power off after shutdown'); Writeln(' -l: log off only'); Writeln(' -n: kill apps that don''t want to die.'); Writeln(' -c: cancel a running shutdown.'); 508
end else begin if HasParam('k') then warn := True; if HasParam('r') then reboot := True; if HasParam('h') and reboot then begin Writeln('Error: Cannot specify -r and -h parameters together!'); Exit; end; if HasParam('h') then reboot := False; if HasParam('n') then downQuick := True; if HasParam('c') then cancelShutdown := True; if HasParam('p') then powerOff := True; if HasParam('l') then logoff := True; DoShutdown; end; end. {************************************************************************} {2.} function MyExitWindows(RebootParam: Longword): Boolean; var TTokenHd: THandle; TTokenPvg: TTokenPrivileges; cbtpPrevious: DWORD; rTTokenPvg: TTokenPrivileges; pcbtpPreviousRequired: DWORD; tpResult: Boolean; const SE_SHUTDOWN_NAME = 'SeShutdownPrivilege'; begin if Win32Platform = VER_PLATFORM_WIN32_NT then begin tpResult := OpenProcessToken(GetCurrentProcess(), TOKEN_ADJUST_PRIVILEGES or TOKEN_QUERY, TTokenHd); if tpResult then begin tpResult := LookupPrivilegeValue(nil, SE_SHUTDOWN_NAME, TTokenPvg.Privileges[0].Luid); TTokenPvg.PrivilegeCount := 1; TTokenPvg.Privileges[0].Attributes := SE_PRIVILEGE_ENABLED; cbtpPrevious := SizeOf(rTTokenPvg); pcbtpPreviousRequired := 0; if tpResult then Windows.AdjustTokenPrivileges(TTokenHd, False, TTokenPvg, cbtpPrevious, 509
rTTokenPvg, pcbtpPreviousRequired); end; end; Result := ExitWindowsEx(RebootParam, 0); end; // Example to shutdown Windows: procedure TForm1.Button1Click(Sender: TObject); begin MyExitWindows(EWX_POWEROFF); end; // Parameters for MyExitWindows() EWX_LOGOFF Shuts down all processes running in the security context of the process that called the ExitWindowsEx function. Then it logs the user off. Alle Prozesse des Benutzers werden beendet, danach wird der Benutzer abgemeldet. EWX_POWEROFF Shuts down the system and turns off the power. The system must support the power-off feature. Windows NT/2000/XP: The calling process must have the SE_SHUTDOWN_NAME privilege. Fährt Windows herunter und setzt den Computer in den StandBy-Modus, sofern von der Hardware unterstützt. EWX_REBOOT Shuts down the system and then restarts the system. Windows NT/2000/XP: The calling process must have the SE_SHUTDOWN_NAME privilege. Fährt Windows herunter und startet es neu. EWX_SHUTDOWN Shuts down the system to a point at which it is safe to turn off the power. All file buffers have been flushed to disk, and all running processes have stopped. If the system supports the power-off feature, the power is also turned off. Windows NT/2000/XP: The calling process must have the SE_SHUTDOWN_NAME privilege. 510
Fährt Windows herunter. EWX_FORCE Forces processes to terminate. When this flag is set, the system does not send the WM_QUERYENDSESSION and WM_ENDSESSION messages. This can cause the applications to lose data. Therefore, you should only use this flag in an emergency. Die aktiven Prozesse werden zwangsweise und ohne Rückfrage beendet. EWX_FORCEIFHUNG Windows 2000/XP: Forces processes to terminate if they do not respond to the WM_QUERYENDSESSION or WM_ENDSESSION message. This flag is ignored if EWX_FORCE is used. Windows 2000/XP: Die aktiven Prozesse werden aufgefordert, sich selbst zu beenden und müssen dies bestätigen. Reagieren sie nicht, werden sie zwangsweise beendet.
511
{ This code takes advantage of the undocumented NtQuerySystemInformation API to obtain a list of loaded drivers under Windows NT. Dieser Code verwendet die undokumentiere NtQuerySystemInformation API Funktion um eine Liste aller geladenen Treiber unter Windows NT zu ermitteln. } const DRIVER_INFORMATION = 11; type TPDWord = ^DWORD; TDriverInfo = packed record Address: Pointer; Unknown1: DWORD; Unknown2: DWORD; EntryIndex: DWORD; Unknown4: DWORD; Name: array [0..MAX_PATH + 3] of Char; end; var NtQuerySystemInformation: function (infoClass: DWORD; buffer: Pointer; bufSize: DWORD; returnSize: TPDword): DWORD; stdcall = nil; function GetDriverInfo: string; var temp, Index, numBytes, numEntries: DWORD; buf: TPDword; driverInfo: ^TDriverInfo; begin if @NtQuerySystemInformation = nil then NtQuerySystemInformation := GetProcAddress(GetModuleHandle('ntdll.dll'), 'NtQuerySystemInformation'); // Obtain required buffer size NtQuerySystemInformation(DRIVER_INFORMATION, @temp, 0, @numBytes); // Allocate buffer buf := AllocMem(numBytes * 2); NtQuerySystemInformation(DRIVER_INFORMATION, buf, numBytes * 2, @numBytes); numEntries := buf^; driverInfo := Pointer(DWORD(buf) + 12); Result := ''; for Index := 1 to numEntries do begin Result := Result + #$D#$A + 'Address: $' + 512
IntToHex(DWORD(driverInfo^.Address), 8) + 'Name: "' + (driverInfo^.Name) + '"'; Inc(driverInfo); end; Delete(Result, 1, 2); FreeMem(buf); end; procedure TForm1.Button1Click(Sender: TObject); begin ListBox1.Items.Add(GetDriverInfo) end; // // // // //
Thanks to Madshi for helping me translate from C++ Code Original Code (C++) : NtDriverList v1.0 Copyright 1998, 1999 Yariv Kaplan WWW.INTERNALS.COM
513
function NetUserChangePassword(Domain: PWideChar; UserName: PWideChar; OldPassword: PWideChar; NewPassword: PWideChar): Longint; stdcall; external 'netapi32.dll' Name 'NetUserChangePassword'; // Changes a user's password for a specified network server or domain. // Requirements: Windows NT/2000/XP // Windows 95/98/Me: You can use the PwdChangePassword function to change a user's // Windows logon password on these platforms procedure TForm1.Button1Click(Sender: TObject); begin NetUserChangePassword(PWideChar(WideString('\\COMPUTER')), PWideChar(WideString('username')), PWideChar(WideString('oldpass')), PWideChar(WideString('newpass'))); end;
514
..get the IP-Address bhind a router/proxy? Author: Steffen Schirmer (Steve Tricky) Homepage: http://tricky-soft.de.vu 0 Comments to this tip [Write new comment] [ Print tip ] Tip Rating: (13):
Skill:
Useful:
Overall: { Wenn man hinter einem Router oder Proxy seine richtige Internet-IP herausfinden möchte, dann brauch man die Hilfe von einem externen Internetserver, der einem die IP „verrät“. Realisierbar ist dies mit einer Kombination von PERL und DELPHI. Das folgende Perlscript muss auf einen Webspace, der CGI/Perl unterstützt geladen werden: If you are behind a router or proxy and want to get your real Internet-IP-Address then you need the help of a extern Internet Server. That extern Internet server have to tell you your real Ip-Address. You can make this with a combination of PERL and DELPHI. The following PERL-SCRIPT you have to upload to an Webspace witch allows Perl/CGI-access: } -------------------------------------------------------#!/usr/local/bin/perl use CGI qw/:standard/; print "Content-type: text/html\n\n"; 515
print "BEGINIP".$ENV{REMOTE_ADDR}."ENDIP"; -------------------------------------------------------{ Wenn die Adresse des Script’s http://www.my-server.de/cgi-bin/GiveMeMyIp.pl ist, dann ist der Delphi Quelltext, um die IP rauszufinden: If the address of the Script is "http://www.my-server.de/cgi-bin/GiveMeMyIp.pl" then the Delphi-Code to get your real IP is: } procedure TForm1.Button1Click(Sender: TObject); var IPSTR, IP, HP: string; BeginIP, EndIP, i: integer; begin Button1.Enabled := False; HP := ‘http://www.my-server.de/cgi-bin/GiveMeMyIp.pl’; NMHTTP1.Get(HP); IPSTR := (NMHTTP1.Body); BeginIP := Pos('BEGINIP', IPSTR) + Length('BEGINIP'); EndIP := Pos('ENDIP', IPSTR); IP := ''; for i := BeginIP to ENDip - 1 do begin IP := IP + IPstr[i]; end; label1.Caption := IP; Button1.Enabled := True; end;
516
...send a file from a TServerSocket to a TClientSocket? Author: Elias Zurschmiede Homepage: http://www.delight.ch 0 Comments to this tip [Write new comment] [ Print tip ] unit Unit1; interface uses Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms, Dialogs, ScktComp, StdCtrls; type TForm1 = class(TForm) ClientSocket1: TClientSocket; ServerSocket1: TServerSocket; btnTestSockets: TButton; procedure ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure ClientSocket1Disconnect(Sender: TObject; Socket: TCustomWinSocket); procedure ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket); procedure ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket); procedure btnTestSocketsClick(Sender: TObject); private FStream: TFileStream; { Private-Deklarationen } public { Public-Deklarationen } end; var Form1: TForm1; implementation {$R *.dfm} procedure TForm1.ClientSocket1Read(Sender: TObject; Socket: TCustomWinSocket); var iLen: Integer; Bfr: Pointer; begin iLen := Socket.ReceiveLength; GetMem(Bfr, iLen); try 517
Socket.ReceiveBuf(Bfr^, iLen); FStream.Write(Bfr^, iLen); finally FreeMem(Bfr); end; end; procedure TForm1.FormCreate(Sender: TObject); begin FStream := nil; end; procedure TForm1.FormDestroy(Sender: TObject); begin if Assigned(FStream) then begin FStream.Free; FStream := nil; end; end; procedure TForm1.ClientSocket1Disconnect(Sender: TObject; Socket: TCustomWinSocket); begin if Assigned(FStream) then begin FStream.Free; FStream := nil; end; end; procedure TForm1.ClientSocket1Connect(Sender: TObject; Socket: TCustomWinSocket); begin FStream := TFileStream.Create('c:\temp\test.stream.html', fmCreate or fmShareDenyWrite); end; procedure TForm1.ServerSocket1ClientConnect(Sender: TObject; Socket: TCustomWinSocket); begin Socket.SendStream(TFileStream.Create('c:\temp\test.html', fmOpenRead or fmShareDenyWrite)); end; procedure TForm1.btnTestSocketsClick(Sender: TObject); begin ServerSocket1.Active := True; ClientSocket1.Active := True; end; end. 518
519
...map a network drive (2)? Author: Superhausi 2 Comments to this tip [Write new comment] [ Print tip ] function ConnectDrive(_drvLetter: string; _netPath: string; _showError: Boolean; _reconnect: Boolean): DWORD; var nRes: TNetResource; errCode: DWORD; dwFlags: DWORD; begin { Fill NetRessource with #0 to provide uninitialized values } { NetRessource mit #0 füllen => Keine unitialisierte Werte } FillChar(NRes, SizeOf(NRes), #0); nRes.dwType := RESOURCETYPE_DISK; { Set Driveletter and Networkpath } { Laufwerkbuchstabe und Netzwerkpfad setzen } nRes.lpLocalName := PChar(_drvLetter); nRes.lpRemoteName := PChar(_netPath); { Example: \\Test\C } { Check if it should be saved for use after restart and set flags } { Überprüfung, ob gespeichert werden soll } if _reconnect then dwFlags := CONNECT_UPDATE_PROFILE and CONNECT_INTERACTIVE else dwFlags := CONNECT_INTERACTIVE; errCode := WNetAddConnection3(Form1.Handle, nRes, nil, nil, dwFlags); { Show Errormessage, if flag is set } { Fehlernachricht aneigen } if (errCode <> NO_ERROR) and (_showError) then begin Application.MessageBox(PChar('An error occured while connecting:' + #13#10 + SysErrorMessage(GetLastError)), 'Error while connecting!', MB_OK); end; Result := errCode; { NO_ERROR } end; function ConnectPrinterDevice(_lptPort: string; _netPath: string; _showError: Boolean; _reconnect: Boolean): DWORD; var nRes: TNetResource; errCode: DWORD; dwFlags: DWORD; begin { Fill NetRessource with #0 to provide uninitialized values } { NetRessource mit #0 füllen => Keine unitialisierte Werte } 520
FillChar(NRes, SizeOf(NRes), #0); nRes.dwType := RESOURCETYPE_PRINT; { Set Printername and Networkpath } { Druckername und Netzwerkpfad setzen } nRes.lpLocalName := PChar(_lptPort); nRes.lpRemoteName := PChar(_netPath); { Example: \\Test\Printer1 } { Check if it should be saved for use after restart and set flags } { Überprüfung, ob gespeichert werden soll } if _reconnect then dwFlags := CONNECT_UPDATE_PROFILE and CONNECT_INTERACTIVE else dwFlags := CONNECT_INTERACTIVE; errCode := WNetAddConnection3(Form1.Handle, nRes, nil, nil, dwFlags); { Show Errormessage, if flag is set } { Fehlernachricht aneigen } if (errCode <> NO_ERROR) and (_showError) then begin Application.MessageBox(PChar('An error occured while connecting:' + #13#10 + SysErrorMessage(GetLastError)), 'Error while connecting!', MB_OK); end; Result := errCode; { NO_ERROR } end; function DisconnectNetDrive(_locDrive: string; _showError: Boolean; _force: Boolean; _save: Boolean): DWORD; var dwFlags: DWORD; errCode: DWORD; begin { Set dwFlags, if necessary } { Setze dwFlags auf gewünschten Wert } if _save then dwFlags := CONNECT_UPDATE_PROFILE else dwFlags := 0; { Cancel the connection see also at http://www.swissdelphicenter.ch/en/showcode.php?id=391 } { Siehe auch oben genannten Link (Netzlaufwerke anzeigen) } errCode := WNetCancelConnection2(PChar(_locDrive), dwFlags, _force); { Show Errormessage, if flag is set } { Fehlernachricht anzeigen } if (errCode <> NO_ERROR) and (_showError) then begin Application.MessageBox(PChar('An error occured while disconnecting:' + #13#10 + SysErrorMessage(GetLastError)), 'Error while disconnecting', MB_OK); end; 521
Result := errCode; { NO_ERROR } end; {Beispiel / Example:} procedure TForm1.Button1Click(Sender: TObject); begin ConnectDrive('h:', '\\Servername\C', True, True); end; procedure TForm1.Button2Click(Sender: TObject); begin DisconnectNetDrive('h:', True, True, True); end;
522
...get the MAC Address? Author: Toni G. Homepage: http://www.tgsoft.ch 1 Comment to this tip [Write new comment] [ Print tip ] uses NB30; function GetMACAdress: string; var NCB: PNCB; Adapter: PAdapterStatus; URetCode: PChar; RetCode: char; I: integer; Lenum: PlanaEnum; _SystemID: string; TMPSTR: string; begin Result := ''; _SystemID := ''; Getmem(NCB, SizeOf(TNCB)); Fillchar(NCB^, SizeOf(TNCB), 0); Getmem(Lenum, SizeOf(TLanaEnum)); Fillchar(Lenum^, SizeOf(TLanaEnum), 0); Getmem(Adapter, SizeOf(TAdapterStatus)); Fillchar(Adapter^, SizeOf(TAdapterStatus), 0); Lenum.Length := chr(0); NCB.ncb_command := chr(NCBENUM); NCB.ncb_buffer := Pointer(Lenum); NCB.ncb_length := SizeOf(Lenum); RetCode := Netbios(NCB); i := 0; repeat Fillchar(NCB^, SizeOf(TNCB), 0); Ncb.ncb_command := chr(NCBRESET); Ncb.ncb_lana_num := lenum.lana[I]; RetCode := Netbios(Ncb); Fillchar(NCB^, SizeOf(TNCB), 0); Ncb.ncb_command := chr(NCBASTAT); Ncb.ncb_lana_num := lenum.lana[I]; // Must be 16 Ncb.ncb_callname := '* '; Ncb.ncb_buffer := Pointer(Adapter); 523
Ncb.ncb_length := SizeOf(TAdapterStatus); RetCode := Netbios(Ncb); //---- calc _systemId from mac-address[2-5] XOR mac-address[1]... if (RetCode = chr(0)) or (RetCode = chr(6)) then begin _SystemId := IntToHex(Ord(Adapter.adapter_address[0]), 2) + '-' + IntToHex(Ord(Adapter.adapter_address[1]), 2) + '-' + IntToHex(Ord(Adapter.adapter_address[2]), 2) + '-' + IntToHex(Ord(Adapter.adapter_address[3]), 2) + '-' + IntToHex(Ord(Adapter.adapter_address[4]), 2) + '-' + IntToHex(Ord(Adapter.adapter_address[5]), 2); end; Inc(i); until (I >= Ord(Lenum.Length)) or (_SystemID <> '00-00-00-00-00-00'); FreeMem(NCB); FreeMem(Adapter); FreeMem(Lenum); GetMacAdress := _SystemID; end;
procedure TForm1.Button1Click(Sender: TObject); begin label1.Caption := GetMACAdress; end;
//*************************************************** // Another Code from // http://delphi.vitpc.com/treasury/lan.htm //*************************************************** uses NB30; type TAdapterStatus = record adapter_address: array [0..5] of char; filler: array [1..4 * SizeOf(char) + 19 * SizeOf(Word) + 3 * SizeOf(DWORD)] of Byte; end; THostInfo = record username: PWideChar; logon_domain: PWideChar; oth_domains: PWideChar; logon_server: PWideChar; end;{record} function IsNetConnect: Boolean; 524
begin if GetSystemMetrics(SM_NETWORK) and $01 = $01 then Result := True else Result := False; end;{function} function AdapterToString(Adapter: TAdapterStatus): string; begin with Adapter do Result := Format('%2.2x-%2.2x-%2.2x-%2.2x-%2.2x-%2.2x', [Integer(adapter_address[0]), Integer(adapter_address[1]), Integer(adapter_address[2]), Integer(adapter_address[3]), Integer(adapter_address[4]), Integer(adapter_address[5])]); end;{function} function GetMacAddresses(const Machine: string; const Addresses: TStrings): Integer; const NCBNAMSZ = 16; // absolute length of a net name MAX_LANA = 254; // lana's in range 0 to MAX_LANA inclusive NRC_GOODRET = $00; // good return NCBASTAT = $33; // NCB ADAPTER STATUS NCBRESET = $32; // NCB RESET NCBENUM = $37; // NCB ENUMERATE LANA NUMBERS type PNCB = ^TNCB; TNCBPostProc = procedure(P: PNCB); stdcall; TNCB = record ncb_command: Byte; ncb_retcode: Byte; ncb_lsn: Byte; ncb_num: Byte; ncb_buffer: PChar; ncb_length: Word; ncb_callname: array [0..NCBNAMSZ - 1] of char; ncb_name: array [0..NCBNAMSZ - 1] of char; ncb_rto: Byte; ncb_sto: Byte; ncb_post: TNCBPostProc; ncb_lana_num: Byte; ncb_cmd_cplt: Byte; ncb_reserve: array [0..9] of char; ncb_event: THandle; end; PLanaEnum = ^TLanaEnum; TLanaEnum = record Length: Byte; lana: array [0..MAX_LANA] of Byte; end; ASTAT = record adapt: TAdapterStatus; namebuf: array [0..29] of TNameBuffer; 525
end; var NCB: TNCB; Enum: TLanaEnum; I: integer; Adapter: ASTAT; MachineName: string; begin Result := -1; Addresses.Clear; MachineName := UpperCase(Machine); if MachineName = '' then MachineName := '*'; FillChar(NCB, SizeOf(NCB), #0); NCB.ncb_command := NCBENUM; NCB.ncb_buffer := Pointer(@Enum); NCB.ncb_length := SizeOf(Enum); if Word(NetBios(@NCB)) = NRC_GOODRET then begin Result := Enum.Length; for I := 0 to Ord(Enum.Length) - 1 do begin FillChar(NCB, SizeOf(TNCB), #0); NCB.ncb_command := NCBRESET; NCB.ncb_lana_num := Enum.lana[I]; if Word(NetBios(@NCB)) = NRC_GOODRET then begin FillChar(NCB, SizeOf(TNCB), #0); NCB.ncb_command := NCBASTAT; NCB.ncb_lana_num := Enum.lana[i]; StrLCopy(NCB.ncb_callname, PChar(MachineName), NCBNAMSZ); StrPCopy(@NCB.ncb_callname[Length(MachineName)], StringOfChar(' ', NCBNAMSZ - Length(MachineName))); NCB.ncb_buffer := PChar(@Adapter); NCB.ncb_length := SizeOf(Adapter); if Word(NetBios(@NCB)) = NRC_GOODRET then Addresses.Add(AdapterToString(Adapter.adapt)); end; end; end; end;{function}
526
...implement net send? Author: 3 Comments to this tip [Write new comment] [ Print tip ] function NetSend(dest, Source, Msg: string): Longint; overload; type TNetMessageBufferSendFunction = function(servername, msgname, fromname: PWideChar; buf: PWideChar; buflen: Cardinal): Longint; stdcall; var NetMessageBufferSend: TNetMessageBufferSendFunction; SourceWideChar: PWideChar; DestWideChar: PWideChar; MessagetextWideChar: PWideChar; Handle: THandle; begin Handle := LoadLibrary('NETAPI32.DLL'); if Handle = 0 then begin Result := GetLastError; Exit; end; @NetMessageBufferSend := GetProcAddress(Handle, 'NetMessageBufferSend'); if @NetMessageBufferSend = nil then begin Result := GetLastError; Exit; end; MessagetextWideChar := nil; SourceWideChar := nil; DestWideChar := nil; try GetMem(MessagetextWideChar, Length(Msg) * SizeOf(WideChar) + 1); GetMem(DestWideChar, 20 * SizeOf(WideChar) + 1); StringToWideChar(Msg, MessagetextWideChar, Length(Msg) * SizeOf(WideChar) + 1); StringToWideChar(Dest, DestWideChar, 20 * SizeOf(WideChar) + 1); if Source = '' then Result := NetMessageBufferSend(nil, DestWideChar, nil, MessagetextWideChar, Length(Msg) * SizeOf(WideChar) + 1) else begin GetMem(SourceWideChar, 20 * SizeOf(WideChar) + 1); StringToWideChar(Source, SourceWideChar, 20 * SizeOf(WideChar) + 1); Result := NetMessageBufferSend(nil, DestWideChar, SourceWideChar, MessagetextWideChar, Length(Msg) * SizeOf(WideChar) + 1); 527
FreeMem(SourceWideChar); end; finally FreeMem(MessagetextWideChar); FreeLibrary(Handle); end; end; function NetSend(Dest, Msg: string): Longint; overload; begin Result := NetSend(Dest, '', Msg); end; function NetSend(Msg: string): Longint; overload; begin Result := NetSend('', '', Msg); end; // Example: procedure TForm1.Button1Click(Sender: TObject); begin NetSend('LoginName', 'Your Message'); end;
528
...enumerate the network connections (drives)? Author: Igor Siticov Homepage: http://www.sicomponents.com 1 Comment to this tip [Write new comment] [ Print tip ] Tip Rating: (15):
{ From the MS-DOS prompt, you can enumerate the network connections (drives) by using the following command: net use Programmatically, you would call WNetOpenEnum() to start the enumeration of connected resources and WNetEnumResources() to continue the enumeration. The following sample code enumerates the network connections: Vom MS-DOS Prompt können die Netzwerk Verbdindungen (Laufwerke) mittels "net use" angezeigt werden. Das folgende Beispiel braucht die WNetOpenEnum, WNetEnumResources Funktionen, um die Netzwerk Verbdindungen aufzulisten. } procedure TForm1.Button1Click(Sender: TObject); var i, dwResult: DWORD; hEnum: THandle; lpnrDrv: PNETRESOURCE; s: string; const cbBuffer: DWORD = 16384; cEntries: DWORD = $FFFFFFFF; begin dwResult := WNetOpenEnum(RESOURCE_CONNECTED, RESOURCETYPE_ANY, 0, nil, hEnum); if (dwResult <> NO_ERROR) then begin ShowMessage('Cannot enumerate network drives.'); Exit; end; s := ''; 529
repeat lpnrDrv := PNETRESOURCE(GlobalAlloc(GPTR, cbBuffer)); dwResult := WNetEnumResource(hEnum, cEntries, lpnrDrv, cbBuffer); if (dwResult = NO_ERROR) then begin s := 'Network drives:'#13#10; for i := 0 to cEntries - 1 do begin if lpnrDrv^.lpLocalName <> nil then s := s + lpnrDrv^.lpLocalName + #9 + lpnrDrv^.lpRemoteName; Inc(lpnrDrv); end; end else if dwResult <> ERROR_NO_MORE_ITEMS then begin s := s + 'Cannot complete network drive enumeration'; GlobalFree(HGLOBAL(lpnrDrv)); break; end; GlobalFree(HGLOBAL(lpnrDrv)); until (dwResult = ERROR_NO_MORE_ITEMS); WNetCloseEnum(hEnum); if s = '' then s := 'No network connections.'; ShowMessage(s); end; {*********************************************************************** FindComp Unit from Fatih Olcer [email protected] ***********************************************************************} unit FindComp; interface uses Windows, Classes; function FindComputers: DWORD; var Computers: TStringList; implementation uses SysUtils; const MaxEntries = 250; function FindComputers: DWORD; 530
var EnumWorkGroupHandle, EnumComputerHandle: THandle; EnumError: DWORD; Network: TNetResource; WorkGroupEntries, ComputerEntries: DWORD; EnumWorkGroupBuffer, EnumComputerBuffer: array[1..MaxEntries] of TNetResource; EnumBufferLength: DWORD; I, J: DWORD; begin Computers.Clear; FillChar(Network, SizeOf(Network), 0); with Network do begin dwScope := RESOURCE_GLOBALNET; dwType := RESOURCETYPE_ANY; dwUsage := RESOURCEUSAGE_CONTAINER; end; EnumError := WNetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_ANY, 0, @Network, EnumWorkGroupHandle); if EnumError = NO_ERROR then begin WorkGroupEntries := MaxEntries; EnumBufferLength := SizeOf(EnumWorkGroupBuffer); EnumError := WNetEnumResource(EnumWorkGroupHandle, WorkGroupEntries, @EnumWorkGroupBuffer, EnumBufferLength); if EnumError = NO_ERROR then begin for I := 1 to WorkGroupEntries do begin EnumError := WNetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_ANY, 0, @EnumWorkGroupBuffer[I], EnumComputerHandle); if EnumError = NO_ERROR then begin ComputerEntries := MaxEntries; EnumBufferLength := SizeOf(EnumComputerBuffer); EnumError := WNetEnumResource(EnumComputerHandle, ComputerEntries, @EnumComputerBuffer, EnumBufferLength); if EnumError = NO_ERROR then for J := 1 to ComputerEntries do Computers.Add(Copy(EnumComputerBuffer[J].lpRemoteName, 3, Length(EnumComputerBuffer[J].lpRemoteName) - 2)); WNetCloseEnum(EnumComputerHandle); end; end; end; WNetCloseEnum(EnumWorkGroupHandle); 531
end; if EnumError = ERROR_NO_MORE_ITEMS then EnumError := NO_ERROR; Result := EnumError; end; initialization Computers := TStringList.Create; finalization Computers.Free; end.
532
procedure SetPort(address, Value: Word); var bValue: Byte; begin bValue := trunc(Value and 255); asm mov dx, address mov al, bValue out dx, al end; end; function GetPort(address: Word): Word; var bValue: Byte; begin asm mov dx, address in al, dx mov bValue, al end; GetPort := bValue; end; procedure Sound(aFreq, aDelay: Integer); procedure DoSound(Freq: Word); var B: Byte; begin if Freq > 18 then begin Freq := Word(1193181 div Longint(Freq)); B := Byte(GetPort($61)); if (B and 3) = 0 then begin SetPort($61, Word(B or 3)); SetPort($43, $B6); end; SetPort($42, Freq); SetPort($42, Freq shr 8); end; end; procedure Delay(MSecs: Integer); var FirstTickCount: LongInt; begin FirstTickCount := GetTickCount; repeat 533
Application.ProcessMessages; until ((GetTickCount - FirstTickCount) >= (MSecs)); end; begin if Win32Platform = VER_PLATFORM_WIN32_NT then begin Windows.Beep(aFreq, aDelay); end else begin DoSound(aFreq); Delay(aDelay); end; end; procedure NoSound; var Value: Word; begin Value := GetPort($61) and $FC; SetPort($61, Value); end;
534
To: From: Subject: Date: Groups:
Article "Paul Agics"
Previo Previo Printe Next Next Siblin us us Threa Result r Searc Parent Child in in g d s in in friendl h thread search thread search y Hiding your program from the Ctrl+Alt+Del list Here's a question that I have seen a lot. To accomplish this, you need to resister the program as a service, by passing its process ID to the RegisterService() function. This method makes use of the API GetProcAddress to get the function pointer for RegisterServiceProcess API. This function pointer is then used to call the RegisterServiceProcess function. Hiding the Application: ----------------------------------------------------------------------------; defined in the data section szKernel32 db "Kernel32.dll",0 szRSP db "RegisterServiceProcess",0 ; code to hide application from alt+ctrl+del push offset szKernel32 call GetModuleHandle ; get the handle of kernel32.dll push offset szRSP push eax call GetProcAddress ; get the address of the function mov ebx, eax ; save the pointer into ebx call GetCurrentProcessId ; get the current process's id push 1 ; 1 = Register as Service push eax ; process id call ebx ; call RegisterServiceProcess Cleaning Up: ----------------------------------------------------------------------------You should always call RegisterServiceProcess again (using the previously described methods), but instead passing a 0 for the dwType argument, so that your program will unregister itself, and frees up its resources. ; code to un-hide application from alt+ctrl+del push offset szKernel32 535
call GetModuleHandle ; get the handle of kernel32.dll push offset szRSP push eax call GetProcAddress ; get the address of the function mov ebx, eax ; save the pointer into ebx call GetCurrentProcessId ; get the current process's id push 0 ; 0 = UnRegister as Service push eax ; process id call ebx ; call RegisterServiceProcess RegisterServiceProcess: ----------------------------------------------------------------------------The RegisterServiceProcess function registers or unregisters a service process. A service process continues to run after the user logs off. To call RegisterServiceProcess, retrieve a function pointer using GetProcAddress on KERNEL32.DLL. Use the function pointer to call RegisterServiceProcess. DWORD RegisterServiceProcess( DWORD dwProcessId, DWORD dwType ); Parameters dwProcessId Specifies the identifier of the process to register as a service process. Specifies NULL to register the current process. dwType Specifies whether the service is to be registered or unregistered. This parameter can be one of the following values. Value Meaning 0 Unregisters the process as a service process. 1 Registers the process as a service process. Return Values The return value is 1 if successful or 0 if an error occurs.
536
I have a keyboard recorder program. The program itself is always hidden from the user and it installs a global keyboard hook and catches all the messages. For a keyboard hook an extra dll is required because the hook procedure has to be in a dll. So here I am posting that dll. Dll itself is a slight modification of an example I took from this conference I think. ----------------------------------------------------------------------unit HookRec; interface uses Windows, Messages, SysUtils; {Define a record for recording and passing information process wide} type PHookRec = ^THookRec; THookRec = packed record // Handle of a keyboard hook HookHandle: HHOOK; // Window for sending WM_CHAR and WM_DEADCHAR messages LogWnd: HWND; // Application handle from the calling app (Application.Handle) AppWnd: HWND; end; implementation end. ----------------------------------------------------------------------library KeyHook; uses Windows, Messages, SysUtils, HookRec in 'HookRec.pas'; var hObjHandle: THandle; {Variable for the file mapping object} lpHookRec: PHookRec; {Pointer to our hook record} procedure ErrMessage(msg: string); begin MessageBox(0, PChar(msg), 'Hook DLL', MB_OK); end; procedure MapFileMemory(dwAllocSize : DWORD); 537
var bCreated: boolean; begin {Create a process wide memory mapped variable} hObjHandle := CreateFileMapping($FFFFFFFF, NIL, PAGE_READWRITE, 0, dwAllocSize, 'HookRecMemBlock'); bCreated := GetLastError <> ERROR_ALREADY_EXISTS; if (hObjHandle = 0) then begin ErrMessage('Could not create file map object'); exit; end; {Get a pointer to our process wide memory mapped variable} lpHookRec := MapViewOfFile(hObjHandle, FILE_MAP_WRITE, 0, 0, dwAllocSize); if (lpHookRec = NIL) then begin CloseHandle(hObjHandle); ErrMessage('Could not map file'); exit; end; if bCreated then begin FillChar(lpHookRec^, dwAllocSize, 0); end; end; procedure UnMapFileMemory; begin {Delete our process wide memory mapped variable} if (lpHookRec <> NIL) then begin UnMapViewOfFile(lpHookRec); lpHookRec := NIL; end; if (hObjHandle > 0) then begin CloseHandle(hObjHandle); hObjHandle := 0; end; end; function GetHookRecPointer: PHookRec; stdcall; begin {Return a pointer to our process wide memory mapped variable} result := lpHookRec; end; {The function that actually processes the keystrokes for our hook} function KeyBoardProc(Code : integer; wParam : integer; lParam : integer): integer; stdcall; var 538
KeyUp : bool; {Remove comments for additional functionability IsAltPressed : bool; IsCtrlPressed : bool; IsShiftPressed : bool; } m: Msg; begin result := 0; case Code of HC_ACTION: begin {We trap the keystrokes here} {Is this a key up message?} KeyUp := ((lParam AND (1 shl 31)) <> 0); (*Remove comments for additional functionability {Is the Alt key pressed} IsAltPressed := (lParam AND (1 shl 29)) <> 0; {Is the Control key pressed} IsCtrlPressed := (GetKeyState(VK_CONTROL) AND (1 shl 15)) <> 0; {if the Shift key pressed} IsShiftPressed := (GetKeyState(VK_SHIFT) AND (1 shl 15)) <> 0; *) m.hwnd := lpHookRec^.LogWnd; m.wParam := wParam; m.lParam := lParam; if KeyUp (*and (wParam in [32..255])*) then begin m.message := WM_KEYUP; end else begin m.message := WM_KEYDOWN; end; TranslateMessage(m); {Allow the keystroke} result := 0; end; {HC_ACTION} HC_NOREMOVE: begin {This is a keystroke message, but the keystroke message} {has not been removed from the message queue, since an} {application has called PeekMessage() specifying PM_NOREMOVE} result := 0; exit; end; 539
end; {case code} if (Code < 0) then {Call the next hook in the hook chain} result := CallNextHookEx(lpHookRec^.HookHandle, Code, wParam, lParam); end; procedure StartKeyBoardHook; stdcall; begin {If we have a process wide memory variable} {and the hook has not already been set...} if ((lpHookRec <> NIL) AND (lpHookRec^.HookHandle = 0)) then begin {Set the hook and remember our hook handle} lpHookRec^.HookHandle := SetWindowsHookEx(WH_KEYBOARD, @KeyBoardProc, hInstance, 0); end; end; procedure StopKeyBoardHook; stdcall; begin {If we have a process wide memory variable} {and the hook has already been set...} if ((lpHookRec <> NIL) AND (lpHookRec^.HookHandle <> 0)) then begin {Remove our hook and clear our hook handle} if (UnHookWindowsHookEx(lpHookRec^.HookHandle) <> FALSE) then begin lpHookRec^.HookHandle := 0; end; end; end; procedure DllEntryPoint(dwReason : DWORD); begin case dwReason of Dll_Process_Attach: begin {If we are getting mapped into a process, then get} {a pointer to our process wide memory mapped variable} hObjHandle := 0; lpHookRec := NIL; MapFileMemory(sizeof(lpHookRec^)); end; Dll_Process_Detach: begin {If we are getting unmapped from a process then, remove} {the pointer to our process wide memory mapped variable} UnMapFileMemory; end; end; end; 540
exports KeyBoardProc, GetHookRecPointer, StartKeyBoardHook, StopKeyBoardHook; begin {Set our Dll's main entry point} DLLProc := @DllEntryPoint; {Call our Dll's main entry point} DllEntryPoint(Dll_Process_Attach); end. ------------------------------------------------------------------------------------unit KeyHook; (* Interface to the KeyHook dll *) interface uses HookRec; procedure StartKeyBoardHook; stdcall; external 'KeyHook.dll'; procedure StopKeyBoardHook; stdcall; external 'KeyHook.dll'; function GetHookRecPointer: PHookRec; stdcall; external 'KeyHook.dll'; implementation end. ------------------------------------------------------------------------------------To use the dll in your form just put the following code ... if GetHookRecPointer <> nil then with GetHookRecPointer^ do begin LogWnd := Self.Handle; AppWnd := Application.Handle; end; StartKeyBoardHook; And also override the WndProc for procesing (WM_CHAR and WM_DEADCHAR) key messages ... type TfmLog = class(TForm) private { Private declarations } procedure WndProc(var Msg : TMessage); override; public end; 541
procedure TfmLog.WndProc(var Msg : TMessage); begin if (Msg.Msg = WM_CHAR) or (Msg.Msg = WM_DEADCHAR) then (* Do what ever you want with the ascii char code in Msg.WParam *) else inherited; end; Special note .... this fmLog form should have no accelerator keys (labels with hot letters like '&Button') because those letters would not be processed by WndProc ... hope this helps, tomi.
542
Q) How can I create a system wide keyboard hook under Win32? A) The following example demonstrates creating a system wide windows hook under Win32. The example provides both the code for the system hook dll and an example application. The hook function that we will create will also demonstrate advanced coding techniques such as sharing global memory across process boundaries using memory mapped files, sending messages from the key hook function back to the originating application, and dynamic loading of a dll at runtime. The example keyboard hook that we create will keep a count of the number of keystrokes a user enters on the keyboard. Further, we will demonstrate trapping the enter key, and passing a message back to the application that initiated the keyboard hook each time the enter key is pressed. Finally, we will demonstrate trapping the left arrow key and instead of letting it through to the current application, we will instead replace it with a right arrow keystroke. (Note: that this can cause much confusion to a unsuspecting user). Adding a hook to the windows system involves calling the Windows API function SetWindowsHookEx() and passing it the type of hook you wish to install, and address of the hook function you are installing. System wide hook functions are required to reside in a dynamic link library, since they must be mapped into each process on the system. The SetWindowsHookEx() function adds your hook function into the Windows "hook chain", returning a handle (or id) of the hook you are installing. You will use this handle to identify your hook to windows, and to remove your hook when you are done trapping the keyboard. The Windows "hook chain" is a linked list of functions that Windows uses to keep track of all the installed hooks, allowing multiple hooks to be installed at any given time. Occasionally, Windows will ask your hook function to call the next hook in the chain, allowing all the hooks an opportunity to function. When we do call the next hook in the chain, we will need to identify ourselves by passing the handle of our hook function to the next hook. Creating a Windows hook requires special handling under Win32, since the dll must be mapped (on the fly) into the process space of every application that receives keystrokes. Normally, this is not an issue, however, when operating inside a keyhook procedure, global variables (such as your hook handle) must be preserved while the dll is mapped into other process spaces. Under Win16, this would not be a program, since dlls had a single data segment that was shared across all process mappings. Under Win32, each mapping of the dll receives its own data segment. This means that as the dll that contains the keyboard hook is mapped into each process that receives keystrokes, it receives a new data segment, and new unitialized variables with it. This is a problem, since global variables (such as your hook handle) must be preserved across process mappings. To solve this problem, we will take advantage of Win32's ability to memory map variables from the system paging file. 543
Each time our dll is mapped into a process, the DllMain() function in our dll will be called by windows, with a parameter flag indicating the reason for the call. When we receive the DLL_PROCESS_ATTACH flag (indicating our dll is getting mapped into a different process), we will create a file mapping to the system paging file and get a pointer to our memory mapped variables. When we receive the DLL_PROCESS_DETACH flag (indicating our dll is getting un-mapped from a process), we will free our file mapping of the system paging file. The variables we will need to keep track of (and have access to from both the dll and the application that originally loaded the keyboard hook) are placed in a record structure called THookRec. The THookRec structure has the following fields: TheHookHandle : The handle (id) of the Keyboard hook that we set. We will need access to this variable during the execution of the keyhook function, to identify ourselves to windows when we are asked to call the next hook in the hook chain. We will also need access to this variable when we remove our hook. Finally, the originating application that will receive the messages from our hook function can access this variable to see if and when the hook is active. TheAppWinHandle : While this variable is not used in our example dll or application, it is a starting place for adding additional messaging capabilities between the hook function and your application that initiates the hook. It can also be useful for determining if the hook is functioning while mapped into the context of the initiating application. TheCtrlWinHandle : This variable will hold the handle to a button control in our initiating application. We will use this handle to send messages from the keyboard hook function to the button control. Every time the enter key is pressed, we will send a WM_KEYDOWN and a WM_KEYUP message to the button and a key value of 0 (zero). We will trap the OnKeyDown event in the button control, and keep count of the number of times the user presses the enter key. TheKeyCount : This variable will keep track of the total number of key presses made by the user. Obviously our keyhook will need access to this variable to increment its value, and the originating application that will receive the messages from our hook function will want to access this variable to display real time results. The DLL contains the following functions: MapFileMemory : Creates a system paging file mapping object and initializes a pointer to our mapping variable of type THookRec. UnMapFileMemory : Frees the system paging file mapping object and mapping variable created by the MapFileMemory() function. GetHookRecPointer : An exported function that returns a pointer to the mapping variable created by the MapFileMemory() function. The initiating application can both set and examine this memory block, and 544
effectively share memory that is used by our hook function during the time the hook function is operating in the context of another process space. KeyBoardProc : The actual hook function. This function receives both keydown, and keyup messages as well as a message from windows indicating we should call the next hook in the windows "hook chain". This function increments TheKeyCount field of the memory mapped THookRec structure if the keystroke we are processing is a keyup message. If the key being processed is the enter key, we will fire the OnKeyDown event of the window provided in "TheCtrlWinHandle" field of the memory mapped THookRec structure. Finally, if the left arrow key is pressed, we will swallow the keystroke, and instead send a right arrow key stroke to the application. Note that the following variables and initializing code has been included in this function for your convience. The variables have been commented out in the code (as not to compile). To use them, simply remove the comments in the code: IsAltPressed {Determines if the Alt key is currently down} IsCtrlPressed {Determines if the Control key is currently down} IsShiftPressed {Determines if the Shift key is currently down} StartKeyBoardHook : An exported function that allows the application to initiate installing the keyboard hook; StopKeyBoardHook : An exported function that allows the application to initiate removing the keyboard hook; DllEntryPoint : The main entry point into our dll, allowing us to know when our dll is being mapped in, and out of, different application's address space. Delphi Hook DLL Example: library TheHook; uses Windows, Messages, SysUtils; {Define a record for recording and passing information process wide} type PHookRec = ^THookRec; THookRec = packed record TheHookHandle : HHOOK; TheAppWinHandle : HWND; TheCtrlWinHandle : HWND; TheKeyCount : DWORD; end; 545
var hObjHandle : THandle; {Variable for the file mapping object} lpHookRec : PHookRec; {Pointer to our hook record} procedure MapFileMemory(dwAllocSize : DWORD); begin {Create a process wide memory mapped variable} hObjHandle := CreateFileMapping($FFFFFFFF, NIL, PAGE_READWRITE, 0, dwAllocSize, 'HookRecMemBlock'); if (hObjHandle = 0) then begin MessageBox(0, 'Hook DLL', 'Could not create file map object', MB_OK); exit; end; {Get a pointer to our process wide memory mapped variable} lpHookRec := MapViewOfFile(hObjHandle, FILE_MAP_WRITE, 0, 0, dwAllocSize); if (lpHookRec = NIL) then begin CloseHandle(hObjHandle); MessageBox(0, 'Hook DLL', 'Could not map file', MB_OK); exit; end; end; procedure UnMapFileMemory; begin {Delete our process wide memory mapped variable} if (lpHookRec <> NIL) then begin UnMapViewOfFile(lpHookRec); lpHookRec := NIL; end; if (hObjHandle > 0) then begin CloseHandle(hObjHandle); hObjHandle := 0; end; end;
546
function GetHookRecPointer : pointer stdcall; begin {Return a pointer to our process wide memory mapped variable} result := lpHookRec; end; {The function that actually processes the keystrokes for our hook} function KeyBoardProc(Code : integer; wParam : integer; lParam : integer): integer; stdcall; var KeyUp : bool; {Remove comments for additional functionability IsAltPressed : bool; IsCtrlPressed : bool; IsShiftPressed : bool; } begin result := 0; case Code of HC_ACTION : begin {We trap the keystrokes here} {Is this a key up message?} KeyUp := ((lParam AND (1 shl 31)) <> 0); (*Remove comments for additional functionability {Is the Alt key pressed} if ((lParam AND (1 shl 29)) <> 0) then begin IsAltPressed := TRUE; end else begin IsAltPressed := FALSE; end; {Is the Control key pressed} if ((GetKeyState(VK_CONTROL) AND (1 shl 15)) <> 0) then begin IsCtrlPressed := TRUE; end else begin IsCtrlPressed := FALSE; end; {if the Shift key pressed} if ((GetKeyState(VK_SHIFT) AND (1 shl 15)) <> 0) then begin IsShiftPressed := TRUE; end else begin IsShiftPressed := FALSE; end; *) {If KeyUp then increment the key count} if (KeyUp <> FALSE) then begin 547
Inc(lpHookRec^.TheKeyCount); end; case wParam of {Was the enter key pressed?} VK_RETURN : begin {if KeyUp} if (KeyUp <> FALSE) then begin {Post a bogus message to the window control in our app} PostMessage(lpHookRec^.TheCtrlWinHandle, WM_KEYDOWN, 0, 0); PostMessage(lpHookRec^.TheCtrlWinHandle, WM_KEYUP, 0, 0); end; {If you wanted to swallow the keystroke then return -1} {else if you want to allow the keystroke then return 0} result := 0; exit; end; {VK_RETURN} {If the left arrow key is pressed then lets play a joke!} VK_LEFT : begin {if KeyUp} if (KeyUp <> FALSE) then begin {Create a UpArrow keyboard event} keybd_event(VK_RIGHT, 0, 0, 0); keybd_event(VK_RIGHT, 0, KEYEVENTF_KEYUP, 0); end; {Swallow the keystroke} result := -1; exit; end; {VK_LEFT} end; {case wParam} {Allow the keystroke} result := 0; end; {HC_ACTION} HC_NOREMOVE : begin {This is a keystroke message, but the keystroke message} {has not been removed from the message queue, since an} {application has called PeekMessage() specifying PM_NOREMOVE} result := 0; exit; end; end; {case code} if (Code < 0) then {Call the next hook in the hook chain} result := 548
CallNextHookEx(lpHookRec^.TheHookHandle, Code, wParam, lParam); end; procedure StartKeyBoardHook stdcall; begin {If we have a process wide memory variable} {and the hook has not already been set...} if ((lpHookRec <> NIL) AND (lpHookRec^.TheHookHandle = 0)) then begin {Set the hook and remember our hook handle} lpHookRec^.TheHookHandle := SetWindowsHookEx(WH_KEYBOARD, @KeyBoardProc, hInstance, 0); end; end; procedure StopKeyBoardHook stdcall; begin {If we have a process wide memory variable} {and the hook has already been set...} if ((lpHookRec <> NIL) AND (lpHookRec^.TheHookHandle <> 0)) then begin {Remove our hook and clear our hook handle} if (UnHookWindowsHookEx(lpHookRec^.TheHookHandle) <> FALSE) then begin lpHookRec^.TheHookHandle := 0; end; end; end; procedure DllEntryPoint(dwReason : DWORD); begin case dwReason of Dll_Process_Attach : begin {If we are getting mapped into a process, then get} {a pointer to our process wide memory mapped variable} hObjHandle := 0; lpHookRec := NIL; MapFileMemory(sizeof(lpHookRec^)); end; Dll_Process_Detach : begin {If we are getting unmapped from a process then, remove} {the pointer to our process wide memory mapped variable} UnMapFileMemory; end; end; end; 549
exports KeyBoardProc name 'KEYBOARDPROC', GetHookRecPointer name 'GETHOOKRECPOINTER', StartKeyBoardHook name 'STARTKEYBOARDHOOK', StopKeyBoardHook name 'STOPKEYBOARDHOOK';
begin {Set our Dll's main entry point} DLLProc := @DllEntryPoint; {Call our Dll's main entry point} DllEntryPoint(Dll_Process_Attach); end.
Application notes: The test application we have created demonstrates loading the dll that contains the keyboard hook, installing the key board hook, displaying the total keystroke count and the number of times the enter key has been pressed (in real time), uninstalling the keyboard hook and unloading the dll. The application code starts out by defining a form containing two labels, a button, and timer component. Once we install our hook function, we will start the timer, and upon every timer event, we will display in label1 the total number of keystrokes that have been entered by the user since the hook was set. The hook will also fire the button's OnKeyDown event each time the enter key is pressed, giving us the opportunity to display the total number of times the enter key has been pressed in the caption of label2. After the form is defined, we then define the THookRec structure in the same manner as it is defined in the hook dll. Other variables we will use include: a handle variable used for loading the hook dll, and three function pointer variables used to call the GetHookRecPointer(), StartKeyBoardHook(), and StopKeyBoardHook() functions. Finally we define a pointer to a THookRec structure used to access the memory mapped variables used by the hook function, a variable to keep track of the number of times the enter key is pressed, and a variable used to indicate the success of loading the dll, getting its functions, and setting the hook. The application logic goes something like this: On form create, we will initialize our form's components, attempt to dynamically load the hook dll, and get the address of the GetHookRecPointer(), StartKeyBoardHook(), and StopKeyBoardHook() 550
functions located in the hook dll. If we are successful, we will retrieve a pointer to THookRec structure used by the hook dll, we will then initialize structure, adding the handle of the button control so the keyboard hook will know which window control to call when the enter key is pressed. We will then attempt to start the keyboard hook. If we are successful, at setting the hook, we can then start the timer. On form destroy, if we where previously successful in installing the windows hook and loading the hook dll, we will now uninstall the windows hook, and unload the KeyHook dll. On the timer's timer event, we will simply display the total number of key presses in the form's label1 caption by accessing the KeyHook dll's THookRec structure. On the Buttons KeyDown event, if the key value passed is zero we increment our EnterKeyCount variable and display the total number of times the enter key has been pressed by accessing the KeyHook dll's THookRec structure. Delphi TestApp Example: unit TestHk1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls; type TForm1 = class(TForm) Label1: TLabel; Label2: TLabel; Timer1: TTimer; Button1: TButton; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Timer1Timer(Sender: TObject); procedure Button1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation 551
{$R *.DFM} {Functions prototypes for the hook dll} type TGetHookRecPointer = function : pointer stdcall; type TStartKeyBoardHook = procedure stdcall; type TStopKeyBoardHook = procedure stdcall; {The record type filled in by the hook dll} type THookRec = packed record TheHookHandle : HHOOK; TheAppWinHandle : HWND; TheCtrlWinHandle : HWND; TheKeyCount : DWORD; end; {A pointer type to the hook record} type PHookRec = ^THookRec; var hHookLib : THANDLE; {A handle to the hook dll} GetHookRecPointer : TGetHookRecPointer; {Function pointer} StartKeyBoardHook : TStartKeyBoardHook; {Function pointer} StopKeyBoardHook : TStopKeyBoardHook; {Function pointer} LibLoadSuccess : bool; {If the hook lib was successfully loaded} lpHookRec : PHookRec; {A pointer to the hook record} EnterKeyCount : DWORD; {An internal count of the Enter Key} procedure TForm1.FormCreate(Sender: TObject); begin {Set our initial variables} Timer1.Enabled := FALSE; Timer1.Interval := 1000; Label1.Caption := '0 Keys Logged'; Label2.Caption := '0 Enter Keys Logged'; EnterKeyCount := 0; lpHookRec := NIL; LibLoadSuccess := FALSE; @GetHookRecPointer := NIL; @StartKeyBoardHook := NIL; @StopKeyBoardHook := NIL; {Try to load the hook dll} hHookLib := LoadLibrary('THEHOOK.DLL'); {If the hook dll was loaded successfully} if hHookLib <> 0 then begin {Get the function addresses} @GetHookRecPointer := GetProcAddress(hHookLib, 'GETHOOKRECPOINTER'); @StartKeyBoardHook := GetProcAddress(hHookLib, 'STARTKEYBOARDHOOK'); @StopKeyBoardHook := GetProcAddress(hHookLib, 'STOPKEYBOARDHOOK'); 552
{Did we find all the functions we need?} if ((@GetHookRecPointer <> NIL) AND (@StartKeyBoardHook <> NIL) AND (@StopKeyBoardHook <> NIL)) then begin LibLoadSuccess := TRUE; {Get a pointer to the hook record} lpHookRec := GetHookRecPointer; {Were we successfull in getting a ponter to the hook record} if (lpHookRec <> nil) then begin {Fill in our portion of the hook record} lpHookRec^.TheHookHandle := 0; lpHookRec^.TheCtrlWinHandle := Button1.Handle; lpHookRec^.TheKeyCount := 0; {Start the keyboard hook} StartKeyBoardHook; {Start the timer if the hook was successfully set} if (lpHookRec^.TheHookHandle <> 0) then begin Timer1.Enabled := TRUE; end; end; end else begin {We failed to find all the functions we need} FreeLibrary(hHookLib); hHookLib := 0; @GetHookRecPointer := NIL; @StartKeyBoardHook := NIL; @StopKeyBoardHook := NIL; end; end; end; procedure TForm1.FormDestroy(Sender: TObject); begin {Did we load the dll successfully?} if (LibLoadSuccess = TRUE) then begin {Did we sucessfully get a pointer to the hook record?} if (lpHookRec <> nil) then begin {Did the hook get set?} if (lpHookRec^.TheHookHandle <> 0) then begin Timer1.Enabled := FALSE; StopKeyBoardHook; end; end; {Free the hook dll} FreeLibrary(hHookLib); end; end; procedure TForm1.Timer1Timer(Sender: TObject); begin {Display the number of keystrokes logged} Label1.Caption := IntToStr(lpHookRec^.TheKeyCount) + ' Keys Logged'; end; 553
procedure TForm1.Button1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin {Process message sent from hook dll and display} {number of time the enter key was pressed} if (Key = 0) then begin Inc(EnterKeyCount); Label2.Caption := IntToStr(EnterKeyCount) + ' Enter Keys Logged'; end; end; end.
Joe -Joe C. Hecht http://home1.gte.net/joehecht/index.htm
554
I have used the following code to obtain the MAC address successfully up till recently //--------------------------------------------------------------------------procedure find_MAC(var s1 : string); Type TGUID=record A,B:word; D,M,S:word; MAC:array[1..6] of byte; end; var UuidCreateFunc : function (var guid: TGUID) : HResult; stdcall; handle : THandle; g : TGUID; //ErrCode: HResult; begin handle := LoadLibrary('RPCRT4.DLL'); if s1 = '5' then @UuidCreateFunc := GetProcAddress(Handle, 'UuidCreateSequential') else @UuidCreateFunc := GetProcAddress(Handle, 'UuidCreate') ; UuidCreateFunc(g); s1 := s1 := s1 := s1 := s1 := s1 := s1 := end;
''; s1 s1 s1 s1 s1 s1
+ + + + + +
IntToHex(g.MAC[1],2) + IntToHex(g.MAC[2],2) + IntToHex(g.MAC[3],2) + IntToHex(g.MAC[4],2) + IntToHex(g.MAC[5],2) + IntToHex(g.MAC[6],2);
'-'; '-'; '-'; '-'; '-';
//--------------------------------------------------------------------------But all of a sudden it seems to have stopped working I tested it initially on w98, nt4.0 and w2k machines and it was fine I appears to function when compiled under delphi 4 and fail when that same exact source is compiled under delphi 5
555
//--------------------------------------------------------------------------//--------------------------------------------------------------------------I found some new code see below but it fails on many machines somthing about netbios v 3 being required does anyone have reliable code or can they explain how to modify the delphi 5 compiler to correctly implement the code //--------------------------------------------------------------------------//--------------------------------------------------------------------------PS I know the code below is long but I'm hoping for a few brownie points for complete problem description //--------------------------------------------------------------------------{---------------------------------------------} { enumerate the lana's - works only on WIN32 } {---------------------------------------------} function NbLanaEnum: TLana_Enum; var NCB: TNCB; L_Enum: TLana_Enum; RetCode: Word; begin {$IFDEF WIN32} FillChar(NCB, SizeOf(NCB), 0); FillChar(L_Enum, SizeOf(TLana_Enum), 0); NCB.Command := NCB_ENUM; NCB.Buf := @L_Enum; NCB.Length := Sizeof(L_Enum); RetCode := NetBiosCmd(NCB); if RetCode <> NRC_GOODRET then begin L_Enum.Length := 0; L_Enum.Lana[0] := Byte(RetCode); end; {$ELSE} { not supported for WIN16, fake LANA 0 } L_Enum.Length := 1; L_Enum.Lana[0] := 0; {$ENDIF} Result := L_Enum; end; {----------------------------------------} { Reset the lana - don't for WIN16 ! } {----------------------------------------} function NbReset(l: Byte): Word; var NCB: TNCB; 556
begin {$IFNDEF WIN32} { will reset all your connections for WIN16 } Result := NRC_GOODRET; { so just fake a reset for Win16 } {$ELSE} FillChar(NCB, SizeOf(NCB), 0); NCB.Command := NCB_RESET; NCB.Lana_Num := l; Result := NetBiosCmd(NCB); {$ENDIF} end; {----------------------------------------} { return the MAC address of an interface } { in the form of a string like : } { 'xx:xx:xx:xx:xx:xx' } { using the definitions in nb.pas } {----------------------------------------} function NbGetMacAddr(LanaNum: Integer): String; var NCB: TNCB; AdpStat: TAdpStat; RetCode: Word; begin FillChar(NCB, SizeOf(NCB), 0); FillChar(AdpStat, SizeOf(AdpStat), 0); NCB.Command := NCB_ADPSTAT; NCB.Buf := @AdpStat; NCB.Length := Sizeof(AdpStat); FillChar(NCB.CallName, Sizeof(TNBName), $20); NCB.CallName[0] := Byte('*'); NCB.Lana_Num := LanaNum; RetCode := NetBiosCmd(NCB); if RetCode = NRC_GOODRET then begin Result := Format('%2.2x:%2.2x:%2.2x:%2.2x:%2.2x:%2.2x', [AdpStat.ID[0], AdpStat.ID[1], AdpStat.ID[2], AdpStat.ID[3], AdpStat.ID[4], AdpStat.ID[5] ]); end else begin Result := '??:??:??:??:??:??'; end; end; procedure find_MAC(var macadd : string); var L_Enum : TLana_Enum; RetCode: Word; begin 557
L_Enum := NbLanaEnum; { enumerate lanas for WIN NT } if L_Enum.Length = 0 then begin //Button1.Caption := Format('LanaEnum err=%2.2x', [L_Enum.Lana[0]]); //exit; end; (* for i := 0 to (L_Enum.Length - 1)do begin { for every lana found } RetCode := NbReset(L_Enum.Lana[i]); { Reset lana for WIN NT } if RetCode <> NRC_GOODRET then begin Button1.Caption := Format('Reset Lana %d err=%2.2x',[i, RetCode]); exit; end; *) RetCode := NbReset(L_Enum.Lana[0]); { Reset lana for WIN NT } if RetCode = NRC_GOODRET then macadd := NbGetMacAddr(0); end; //--------------------------------------------------------------------------unit Nb; {$F+} { nb.pas 16/32 bit windows netbios access (follows IBM's Netbios 3.0 spec) (C) CEVI VZW - 29 april 1998 -- DH ([email protected]) -You can (ab)use this code as you like, but please do not remove the credits. I used reference material from IBM, Microsoft, Syntax and Byte when I wrote the 16-bit (DOS) c-version ages ago (in Borland Turbo C 2.0 on a 386SX PC) with a Syntax SMB server running on Interactive Unix. I now converted this to 16 and 32 bit Delphi code. } interface 558
uses SysUtils, Winprocs, Wintypes; const { size of a netbios name } NBNAMESIZE = 16; { max number of network adapters } { remeber it's BIG Blue, right ? } MAXLANAS = 254; { NCB Command codes } NCB_ASYNC = $80; { asynch command bit to be or-ed into command } NCB_CALL = $10; { open a session } NCB_LISTEN = $11; { wait for a call } NCB_HANGUP = $12; { end session } NCB_SEND = $14; { send data } NCB_RECV = $15; { receive data } NCB_RECVANY = $16; { receive data on any session } NCB_CHAINSEND = $17; { chain send data } NCB_DGSEND = $20; { send a datagram } NCB_DGRECV = $21; { receive datagram } NCB_DGSENDBC = $22; { send broadcast datagram } NCB_DGREVCBC = $23; { receive broadcast datagram } NCB_ADDNAME = $30; { add unique name to local table } NCB_DELNAME = $31; { delete name from local table } NCB_RESET = $32; { reset adapter } NCB_ADPSTAT = $33; { adapter status } NCB_SSTAT = $34; { session status } NCB_CANCEL = $35; { cancel NCB request } NCB_ADDGRPNAME= $36; { add group name to local table } NCB_ENUM = $37; { enum adapters } NCB_UNLINK = $70; { unlink remote boot code } NCB_SENDNA = $71; { send, don't wait for ACK } NCB_CHAINSENDNA=$72; { chain send, but don't wait for ACK } NCB_LANSTALERT= $73; { lan status alert } NCB_ACTION = $77; { enable extensions } NCB_FINDNAME = $78; { search for name on the network } NCB_TRACE = $79; { activate / stop tracing } { NCB return codes } NRC_GOODRET = $00; { good return also returned when ASYNCH request accepted } NRC_BUFLEN = $01; { illegal buffer length } NRC_ILLCMD = $03; { illegal command 559
} NRC_CMDTMO = $05; { command timed out } NRC_INCOMP = $06; { message incomplete, issue another command } NRC_BADDR = $07; { illegal buffer address } NRC_SNUMOUT = $08; { session number out of range } NRC_NORES = $09; { no resource available } NRC_SCLOSED = $0a; { session closed } NRC_CMDCAN = $0b; { command cancelled } NRC_DUPNAME = $0d; { duplicate name } NRC_NAMTFUL = $0e; { name table full } NRC_ACTSES = $0f; { no deletions, name has active sessions } NRC_LOCTFUL = $11; { local session table full } NRC_REMTFUL = $12; { remote session table full } NRC_ILLNN = $13; { illegal name number } NRC_NOCALL = $14; { no callname } NRC_NOWILD = $15; { cannot put * in NCB_NAME } NRC_INUSE = $16; { name in use on remote adapter } NRC_NAMERR = $17; { name deleted } NRC_SABORT = $18; { session ended abnormally } NRC_NAMCONF = $19; { name conflict detected } NRC_IFBUSY = $21; { interface busy, IRET before retrying } NRC_TOOMANY = $22; { too many commands outstanding, retry later } NRC_BRIDGE = $23; { ncb_lana_num field invalid } NRC_CANOCCR = $24; { command completed while cancel occurring } NRC_CANCEL = $26; { command not valid to cancel } NRC_DUPENV = $30; { name defined by anther local process } NRC_ENVNOTDEF = $34; { environment undefined. RESET required } 560
NRC_OSRESNOTAV = $35; { required OS resources exhausted } NRC_MAXAPPS = $36; { max number of applications exceeded } NRC_NOSAPS = $37; { no saps available for netbios } NRC_NORESOURCES = $38; { requested resources are not available } NRC_INVADDRESS = $39; { invalid ncb address or length > segment } NRC_INVDDID = $3B; { invalid NCB DDID } NRC_LOCKFAIL = $3C; { lock of user area failed } NRC_OPENERR = $3f; { NETBIOS not loaded } NRC_SYSTEM = $40; { system error } NRC_PENDING = $ff; { asynchronous command is not yet finished } { Values for transport_id } ALL_TRANSPORTS = 'M'#$00#$00#$00; MS_NBF = 'MNBF'; { values for name_flags bits. } NAME_FLAGS_MASK = $87; GROUP_NAME = $80; UNIQUE_NAME = $00; REGISTERING = $00; REGISTERED = $04; DEREGISTERED = $05; DUPLICATE = $06; DUPLICATE_DEREG = $07; { Values for state } LISTEN_OUTSTANDING = $01; CALL_PENDING = $02; SESSION_ESTABLISHED = $03; HANGUP_PENDING = $04; HANGUP_COMPLETE = $05; SESSION_ABORTED = $06; type 561
{ Netbios Name } TNBName = array[0..(NBNAMESIZE - 1)] of byte; { MAC address } TMacAddress = array[0..5] of byte; PNCB = ^TNCB; { Netbios Control Block } {$IFDEF WIN32} TNCBPostProc = procedure(P: PNCB); {$ENDIF} TNCB = packed record { Netbios Control Block } Command: byte; { command code } RetCode: byte; { return code } LSN: byte; { local session number } Num: byte; { name number } Buf: ^byte; { data buffer } Length: word; { data length } CallName: TNBName; { name to call } Name: TNBName; { our own name } RTO: byte; { receive time-out } STO: byte; { send time-out } {$IFNDEF WIN32} Post_Offs:word; { asynch notification routine offset } Post_Seg: word; { asynch notification routine segment} {$ELSE} PostPrc: TNCBPostProc;{ asynch notification routine (nb30) } {$ENDIF} Lana_Num: byte; { adapter number } Cmd_Cplt: byte; { command completion flag } {$IFDEF WIN32} Reserved: array[0..9] of byte; { Reserverd for Bios use } Event: THandle; { WIN32 event handle to be signalled } { for asynch cmd completion } {$ELSE} Reserved: array[0..13] of byte; { Reserved } {$ENDIF} end; { Netbios Name Info record } PNameInfo = ^TNameInfo; TNameInfo = packed record { name info record } Name: TNBName; { netbios name } NameNum:byte; { name number } NameSt: byte; { name status } end; 562
{ Netbios adapter status } PAdpStat = ^TAdpStat; TAdpStat = packed record { adapter status record} ID: TMacAddress; { adapter mac address } VMajor: byte; { software version major number } Resvd0: byte; AdpType: byte; { adapter type } VMinor: byte; { software version minor number } RptTime: word; { reporting time period } RcvCRC: word; { receive crc errors } RcvOth: word; { receive other errors } TxmCol: word; { transmit collisions } TxmOth: word; { transmit other errors } TxmOK: LongInt; { successfull transmissions } RcvOK: LongInt; { successfull receives } TxmRetr: word; { transmit retries } NoRcvBuf: word; { number of 'no receive buffer' } T1_tmo: word; { t1 time-outs } Ti_tmo: word; { ti time_outs } Resvd1: LongInt; Free_Ncbs:word; { number of free ncb's } Cfg_Ncbs: word; { number of configured ncb's } max_Ncbs: word; { max ncb's used } NoTxmBuf: word; { number of 'no transmit buffer'} MaxDGSize:word; { max. datagram size } Pend_Ses: word; { number of pending sessions } Cfg_Ses: word; { number of configured sessions } Max_Ses: word; { max sessions used } Max_SPSz: word; { max. session packet size } nNames: word; { number of names in local table} Names: array[0..15] of TnameInfo; { local name table } end; { Structure returned to the NCB command NCBSSTAT is SESSION_HEADER followed by an array of SESSION_BUFFER structures. If the NCB_NAME starts with an asterisk then an array of these structures is returned containing the status for all names. } { session header } PSession_Header = ^TSession_Header; TSession_Header = packed record sess_name: byte; num_sess: byte; rcv_dg_outstanding: byte; rcv_any_outstanding: byte; end; { session buffer } 563
PSession_Buffer = ^TSession_Buffer; TSession_Buffer = packed record lsn: byte; state: byte; local_name: TNBName; remote_name: TNBName; rcvs_outstanding: byte; sends_outstanding: byte; end; { Structure returned to the NCB command NCBENUM. On a system containing lana's 0, 2 and 3, a structure with length =3, lana[0]=0, lana[1]=2 and lana[2]=3 will be returned. } PLana_Enum = ^TLana_Enum; TLANA_ENUM = packed record length: byte; { Number of valid entries in lana[] } lana: array[0..(MAXLANAS - 1)] of byte; end; { Structure returned to the NCB command NCBFINDNAME is FIND_NAME_HEADER followed by an array of FIND_NAME_BUFFER structures. } PFind_Name_Header = ^TFind_Name_Header; TFind_Name_Header = packed record node_count: word; reserved: byte; unique_group: byte; end; PFind_Name_Buffer = ^TFind_Name_Buffer; TFind_Name_Buffer = packed record length: byte; access_control: byte; frame_control: byte; destination_addr:TMacAddress; source_addr: TMacAddress; routing_info: array[0..17] of byte; end; { Structure provided with NCBACTION. The purpose of NCBACTION is to provide transport specific extensions to netbios. } PAction_Header = ^TAction_Header; TAction_Header = packed record 564
transport_id: LongInt; action_code: Word; reserved: Word; end;
{$IFDEF WIN32} function Netbios(P: PNCB): Char; stdcall; {$ENDIF} { Exposed functions } function NetbiosCmd(var NCB: TNCB): Word; implementation {$IFDEF WIN32} function Netbios; external 'netapi32.dll' name 'Netbios'; {$ENDIF} {---------------------------------} { execute a Windows Netbios Call } {---------------------------------} function NetbiosCmd(var NCB: TNCB): Word; begin {$IFNDEF WIN32} asm push bp { save bp } push ss { save ss } push ds { save ds } les bx, NCB { get segment/offset address of NCB } call NetBiosCall; { 16 bit Windows Netbios call } xor ah,ah mov @Result, ax { store return code } pop ds { restore ds } pop ss { restore ss } pop bp { restore bp } end; {$ELSE} Result := Word(Netbios(PNCB(@NCB))); { 32 bit Windows Netbios call } {$ENDIF} end; end.
565
program NoForm; uses Windows, SysUtils, Messages, ShellApi; {$R *.RES} const AppName = 'APP WITHOUT FORM'; wID_Auto = WM_USER + 11; wID_Close = WM_USER + 12; var tid: TNotifyIconData; WndClass: array[0..50] of char; TimerID:integer; hPopUp: HMenu; Wnd: hWnd; Hint : PChar; Procedure TrackPopUpWin(hWindow: HWnd); Var P: TPoint; Begin //Show Popup Menu SetForegroundWindow(hWindow); GetCursorPos( P ); TrackPopupMenu( hPopUp, TPM_CENTERALIGN, P.x, P.y, 0, hWindow, NIL ); PostMessage(hWindow, WM_NULL, 0, 0); End; Procedure CreateMenuPopUp; Var mItem: TMENUITEMINFO; Begin hPopUp := CreatePopUpMenu; mItem.cbSize := SizeOF( MENUITEMINFO ); mItem.fMask := MIIM_CHECKMARKS or MIIM_DATA or MIIM_ID or MIIM_STATE or MIIM_SUBMENU or MIIM_TYPE; mItem.fType := mft_String; // mItem.wID := wID_Close; mItem.dwItemData := wID_Close; mItem.dwTypeData := PChar( 'Sair' ); mItem.cch := Length( 'Sair' ); InsertMenuItem( hPopUp, wID_Close, False, mItem ); // mItem.wID := wID_Auto; mItem.dwItemData := wID_Auto; mItem.dwTypeData := PChar( 'Auto Start' ); mItem.cch := Length( 'Auto Start' ); InsertMenuItem( hPopUp, wID_Auto, False, mItem ); 566
End;
function DummyWindowProc (Wnd: hWnd; Msg, wParam: Word; lParam: LongInt): LongInt; stdcall; var TrayHandle: THandle; begin DummyWindowProc := 0; StrPCopy(@WndClass[0], 'Progman'); TrayHandle := FindWindow(@WndClass[0], nil); case Msg of WM_CREATE: // Program initialisation - just set up a tray icon begin tid.cbSize := sizeof (tid); tid.Wnd := Wnd; tid.uID := 1; tid.uFlags := nif_Message or nif_Icon or nif_Tip; tid.uCallBackMessage := WM_USER; tid.hIcon := LoadIcon (hInstance, 'MAINICON'); lstrcpy (tid.szTip,Hint); // Create Menu CreateMenuPopUp; // Shell_NotifyIcon (nim_Add, @tid); end; WM_DESTROY: begin Shell_NotifyIcon (nim_Delete, @tid); PostQuitMessage (0); ShowWindow(TrayHandle, SW_RESTORE); end; WM_COMMAND: // Command notification begin Exit; end; WM_USER: // Had a tray notification - see what to do begin if (lParam = WM_RBUTTONUP) then begin PostMessage (Wnd, WM_CLOSE, 0, 0); end; if (lParam = WM_LBUTTONUP) then begin //do something TrackPopUpWin(Wnd); end; end; end; DummyWindowProc := DefWindowProc (Wnd, Msg, wParam, lParam); end; 567
procedure TimerProc(Wnd: HWND; uMsg: UINT; idEvent: UINT; dwTime: DWORD);pascal; begin //If you need timed events to check end; procedure WinMain; var Msg: TMsg; cls: TWndClass; begin { Register the window class } FillChar (cls, sizeof (cls), 0); cls.lpfnWndProc := @DummyWindowProc; cls.hInstance := hInstance; cls.lpszClassName := AppName; Windows.RegisterClass (cls); { Now create the dummy window } Wnd := CreateWindow (AppName, AppName, ws_OverlappedWindow, 4, 4, 4, 4,0, 0, hInstance, Nil); TimerID := Windows.SetTimer(0,3,60000,@TimerProc); //60 Seconds if Wnd <> 0 then begin ShowWindow (Wnd, SW_HIDE); while GetMessage (Msg, 0, 0, 0) do begin TranslateMessage (Msg); DispatchMessage (Msg); end; end; end; begin Hint := '127.0.0.1'; if FindWindow (AppName, nil) <> 0 then Exit; // INI WinMain; end.
568
Makale
RAW SOCKETLER - IP BAŞLIĞI
Yorum Son Durum
0
29 Haz 2002 17:34 Eklendi
RAW SOCKETLER - IP BAŞLIĞI Selam arkadaşlar. Eskiden raw socketler sadece unix, linux gibi işletim sistemleri tarafından destekleniyordu. Artık bu destek Windows 2000'de kısmen, Windows XP'de ise tam olarak var. Raw socketleri öğrenmeye başlamadan önce internette gelip giden paketlerin yapısına kısaca bir göz atalım. Örneğin bir ping attığımızda hedefe sırası ile: -> 20 bytelık bir ip başlığı (Not: IP başlında ekstra seçenekler kullanılırsa bu değer artabilir) -> 8 bytelık bir icmp başlığı -> Ekstra veri (Not: Bu pingin boyutuna bağlıdır. Gönderilmesi şart değildir) içeren bir paket göndermiş oluruz. Bu paketteki ip başlığı normal bir socket'de işletim seti tarafından oluşturulmakta ve işlenmektedir. Şayet bu ip başlığını kendimiz oluşturmak ve işlemek istersek işletim setine kendi ip başlığını oluşturmaması gereken bir komut göndermemiz gerekmektedir. İşte işletim seti bu komutu destekliyorsa bu işletim setinin raw socket desteği var diyebiliriz. Artık ip başlığının yapısını incelemeye başlayalım: type PIPhdr = ^TIPhdr; TIPhdr = packed record ip_verlen: byte; ip_tos: byte; ip_len: word; ip_id: word; ip_off: word; ip_ttl: byte; ip_p: byte; ip_sum: word; ip_src: longword; ip_dst: longword; end; ip_verlen (Version + IHL [8 bit]): İlk 4 biti "Version", kalan 4 biti de "IHL" (Internet header length) bölümü olmak üzere toplam 2 ana bölümden oluşmuştur. "Version" bölümünü ip başlığının formatının belirtildiği bölümdür. Alabileceği değerler şunlardır: 569
Alabileceği değerler şunlardır: -> 4: IP, Internet Protocol -> 5: ST, ST Datagram Mode -> 6: . SIP, Simple Internet Protocol . SIPP, Simple Internet Protocol Plus . IPv6, Internet Protocol -> 7 TP/IX, The Next Internet -> 8 PIP, The P Internet Protocol -> 9 TUBA "IHL" bölümündeki değer 32 bitlik bir değişken içerisinde yer alan ip başlığının boyutunu temsil eder. Geçerli bir ip başlığında bu değer minimum 5 olabilir. [--------------------------------] ip_tos (Type Of Service [8 bit]): İstenilen servisin çeşidine göre parametreler içerir. Bu parametreler bazı ağlar tarafından paketin nasıl ele alınacağını belirtmek amacıyla kullanılabilir. TOS ilk 3 biti "Precedence", diğer 1 biti "Minimize delay", diğer 1 biti "Maximize throughput", diğer 1 biti "Maximize reliability", diğer 1 biti "Minimize monetary cost" bölümü olmak üzere toplam 5 ana bölümden oluşmuştur. "Precedence" bölümünün alabileceği değerler şunlardır: -> 0: Routine -> 1: Priority -> 2: Immediate -> 3: Flash -> 4: Flash override -> 5: CRITIC/ECP -> 6: Internetwork control -> 7: Network control "Minimize delay" bölümünün alabileceği değerler şunlardır: -> 0: Normal delay -> 1: Low delay "Maximize throughput" bölümünün alabileceği değerler şunlardır: -> 0: Normal throughput -> 1: High throughput "Maximize reliability" bölümünün alabileceği değerler şunlardır: -> 0: Normal reliability -> 1: High reliability "Minimize monetary cost" bölümünün alabileceği değerler şunlardır: -> 0: Normal monetary cost -> 1: Minimize monetary cost Not: Kalan son bit ise kullanılmamaktadır. 570
Not: Kalan son bit ise kullanılmamaktadır. [--------------------------------] ip_len (Total length [16 bit]): Tüm paketin boyutudur. [--------------------------------] ip_id (Identification [16 bit]): Eğer paketi parçalara ayırarak göndereceksek her parçanın bu değeri aynı olmalıdır. [--------------------------------] ip_off (Flags + Fragment Offset [16 bit]): İlk 3 biti "Flags", kalan 13 biti de "Fragment Offset" bölümü olmak üzere toplam 2 ana bölümden oluşmuştur. İlk ana bölüm olan "Flags"in ilk biti her zaman 0 olmalıdır. İkinci biti "Don't fragment", üçüncü biti "More fragments" kısmı olmak üzere toplam 2 kısımdan oluşmuştur. "Don't fragment" kısmı paketin parçalanmasını kontrol eder. Bu kısmın alabileceği değerler: -> 0: Fragment if necessary -> 1: Do not fragment "More fragments" kısmı paketin parçalanma durumunu gösterir. Alabileceği değerler: -> 0: This is the last fragment -> 1: More fragments follow this fragment İkinci ana bölüm olan "Fragment Offset" bölümündeki değer ise parçalanan paketin yeniden oluşturulmasında kullanılır. [--------------------------------] ip_ttl (Time to Live [8 bit]): Bu değer paket her routerdan geçişinde 1 azaltılır. Eğer değer 0'a düşerse paket atılır. [--------------------------------] ip_p (Protocol [8 bit]): IP başlığından sonra gelen protokoldür. 1: ICMP, 2: IGMP, 6: TCP 17: UDP gibi... [--------------------------------] ip_sum (Header checksum [16 bit]): IP başlığı ve eğer varsa ip başlığının eksta seçeneklerini de içeren 16 bitlik bir checksum'dır. [--------------------------------] ip_src (Source IP address [32 bit]): Paketin kaynak IP adresidir. 571
[--------------------------------] ip_dst (Destination IP address [32 bit]): Paketin hedef IP adresidir. Raw socketlerin nasıl açılıp kurulacağını, tcp, udp, icmp, igmp gibi başlıkların yapısını ve bunların raw socketler ile kullanımı için "Program Örnekleri"ne gönderdiğim "DoS Programlarım" başlığı altındaki DoS.zip dosyasını indirin. Buradaki programlarımın güncellenmiş versiyonlarını (ileriki tarihlerde) ve derlenmiş hallerini http://209.100.212.5/cgibin/search/[email protected] adresinden indirebilirsiniz. Eğer vakit bulursam bunlar hakkında da doküman yazacağım. Başarılar, Metin İGE [email protected] Soket Programlama, Winsock, Internet/Intranet
29 Haz 2002 17:34
metinsdr
572
Makale
Paradox ve dBASE tabloları için BDE ağ tanımları
Yorum Son Durum
11
8 Ağu 2002 09:08 mcuyan
Çok Sorulduğu için bi makale yazmak gereği duydum. Paradox ve Dbase tablolarını ağda çalıştırabilmek için Aşağıdaki BDE ayarlarını Kullanın. ANA MAKİNA AYARLARI Ana Makina Adı: Server Disk paylaşım adı : C Paradox veya dBASE tablolarının bulunduğu Klasör c:\Prog\Data PDOXUSRS.NET dosyasının yeri: c:\ Ana Makina BDE Ayarları: DataBAse sekmesi: Alias: MyProg Path : c:\Prog\Data Congiguration->Drivers->Native->PARADOX (veya dBASE) sekmesi: NET DIR: C:\ (PDOXUSRS.NET dosyasını gösterecek) LANGDRIVER: paradox "turk" Congiguration->System->INIT sekmesi: LANGDRIVER: paradox "turk" LOCALSHARE: TRUE TERMİNAL MAKİNA BDE AYARLARI: DataBAse sekmesi: Alias: MyProg Path : \\Server\\Prog\Data Congiguration->Drivers->Native->PARADOX (veya dBASE) sekmesi: NET DIR: \\Server\c (Ana makinadaki PDOXUSRS.NET dosyasını gösterecek) LANGDRIVER: paradox "turk" Congiguration->System->INIT sekmesi: LANGDRIVER: paradox "turk" LOCALSHARE: TRUE Yukarda anlatılan ayarlaın dışında şayet terminalden ana makina için bir ağ yolu tanımlamışsanız terminal ayarları aşağıdaki gibi de olabilir. Ana makinanın diskine F diye bir ağ yolu tanımladıysak ayarlar şu şekilde olacaktır. DataBAse sekmesi: Alias: MyProg Path : F:\Prog\Data 573
Path : F:\Prog\Data Congiguration->Drivers->Native->PARADOX (veya dBASE) sekmesi: NET DIR: F:\ (Ana makinadaki PDOXUSRS.NET dosyasını gösterecek) LANGDRIVER: paradox "turk" Congiguration->System->INIT sekmesi: LANGDRIVER: paradox "turk" LOCALSHARE: TRUE Ancak bu tanımda dikkat edilmesi gereken şey F ağ yolu koptuğunda program çalışmayacaktır. .exe dosyanızı terminalden istediğiniz klasörden çalıştırabilirsiniz. Kolay gelsin.......... BDE ve Alias Parametreleri sair
4 Nis 2002 08:33
Makale Yorumları TERMİNAL MAKİNA BDE AYARLARI: DataBAse sekmesi: Alias: MyProg Path : \\Server\\Prog\Data Üstteki tanım da ufak bir hata olmuş: Path : \\Server\\Prog\Data değil Path : \\Server\Prog\Data olacak... ters slah ı bi tane fazla yazmışım. :)))))
574
Windows Mesajları 1
0
16 Ağu 2002 19:23 Eklendi
{Her derde deva windows mesajları ozellikle component yazanların çok işine yarar. ICQ:165276600 [email protected]} WM_NULL = $0000; WM_CREATE = $0001; WM_DESTROY = $0002; WM_MOVE = $0003; WM_SIZE = $0005; WM_ACTIVATE = $0006; WM_SETFOCUS = $0007; WM_KILLFOCUS = $0008; WM_ENABLE = $000A; WM_SETREDRAW = $000B; WM_SETTEXT = $000C; WM_GETTEXT = $000D; WM_GETTEXTLENGTH = $000E; WM_PAINT = $000F; WM_CLOSE = $0010; WM_QUERYENDSESSION = $0011; WM_QUIT = $0012; WM_QUERYOPEN = $0013; WM_ERASEBKGND = $0014; WM_SYSCOLORCHANGE = $0015; WM_ENDSESSION = $0016; WM_SYSTEMERROR = $0017; WM_SHOWWINDOW = $0018; WM_CTLCOLOR = $0019; WM_WININICHANGE = $001A; WM_SETTINGCHANGE = WM_WININICHANGE; WM_DEVMODECHANGE = $001B; WM_ACTIVATEAPP = $001C; WM_FONTCHANGE = $001D; WM_TIMECHANGE = $001E; WM_CANCELMODE = $001F; WM_SETCURSOR = $0020; WM_MOUSEACTIVATE = $0021; WM_CHILDACTIVATE = $0022; WM_QUEUESYNC = $0023; WM_GETMINMAXINFO = $0024; WM_PAINTICON = $0026; WM_ICONERASEBKGND = $0027; WM_NEXTDLGCTL = $0028; WM_SPOOLERSTATUS = $002A; WM_DRAWITEM = $002B; WM_MEASUREITEM = $002C; WM_DELETEITEM = $002D; 575
WM_VKEYTOITEM = $002E; WM_CHARTOITEM = $002F; WM_SETFONT = $0030; WM_GETFONT = $0031; WM_SETHOTKEY = $0032; WM_GETHOTKEY = $0033; WM_QUERYDRAGICON = $0037; WM_COMPAREITEM = $0039; WM_GETOBJECT = $003D; WM_COMPACTING = $0041; WM_COMMNOTIFY = $0044; {Win32 için eski} WM_WINDOWPOSCHANGING = $0046; WM_WINDOWPOSCHANGED = $0047; WM_POWER = $0048; WM_COPYDATA = $004A; WM_CANCELJOURNAL = $004B; WM_NOTIFY = $004E; WM_INPUTLANGCHANGEREQUEST = $0050; WM_INPUTLANGCHANGE = $0051; WM_TCARD = $0052; WM_HELP = $0053; WM_USERCHANGED = $0054; WM_NOTIFYFORMAT = $0055; WM_CONTEXTMENU = $007B; WM_STYLECHANGING = $007C; WM_STYLECHANGED = $007D; WM_DISPLAYCHANGE = $007E; WM_GETICON = $007F; WM_SETICON = $0080; WM_NCCREATE = $0081; WM_NCDESTROY = $0082; WM_NCCALCSIZE = $0083; WM_NCHITTEST = $0084; WM_NCPAINT = $0085; WM_NCACTIVATE = $0086; WM_GETDLGCODE = $0087; WM_NCMOUSEMOVE = $00A0; WM_NCLBUTTONDOWN = $00A1; WM_NCLBUTTONUP = $00A2; WM_NCLBUTTONDBLCLK = $00A3; WM_NCRBUTTONDOWN = $00A4; WM_NCRBUTTONUP = $00A5; WM_NCRBUTTONDBLCLK = $00A6; WM_NCMBUTTONDOWN = $00A7; WM_NCMBUTTONUP = $00A8; WM_NCMBUTTONDBLCLK = $00A9; WM_KEYFIRST = $0100; WM_KEYDOWN = $0100; WM_KEYUP = $0101; WM_CHAR = $0102; WM_DEADCHAR = $0103; WM_SYSKEYDOWN = $0104; 576
WM_SYSKEYUP = $0105; WM_SYSCHAR = $0106; WM_SYSDEADCHAR = $0107; WM_KEYLAST = $0108; WM_INITDIALOG = $0110; WM_COMMAND = $0111; WM_SYSCOMMAND = $0112; WM_TIMER = $0113; WM_HSCROLL = $0114; WM_VSCROLL = $0115; WM_INITMENU = $0116; WM_INITMENUPOPUP = $0117; WM_MENUSELECT = $011F; WM_MENUCHAR = $0120; WM_ENTERIDLE = $0121; WM_MENURBUTTONUP = $0122; WM_MENUDRAG = $0123; WM_MENUGETOBJECT = $0124; WM_UNINITMENUPOPUP = $0125; WM_MENUCOMMAND = $0126; WM_CHANGEUISTATE = $0127; WM_UPDATEUISTATE = $0128; WM_QUERYUISTATE = $0129; WM_CTLCOLORMSGBOX = $0132; WM_CTLCOLOREDIT = $0133; WM_CTLCOLORLISTBOX = $0134; WM_CTLCOLORBTN = $0135; WM_CTLCOLORDLG = $0136; WM_CTLCOLORSCROLLBAR= $0137; WM_CTLCOLORSTATIC = $0138; WM_MOUSEFIRST = $0200; WM_MOUSEMOVE = $0200; WM_LBUTTONDOWN = $0201; WM_LBUTTONUP = $0202; WM_LBUTTONDBLCLK = $0203; WM_RBUTTONDOWN = $0204; WM_RBUTTONUP = $0205; WM_RBUTTONDBLCLK = $0206; WM_MBUTTONDOWN = $0207; WM_MBUTTONUP = $0208; WM_MBUTTONDBLCLK = $0209; WM_MOUSEWHEEL = $020A; WM_MOUSELAST = $020A; WM_PARENTNOTIFY = $0210; WM_ENTERMENULOOP = $0211; WM_EXITMENULOOP = $0212; WM_NEXTMENU = $0213; WM_SIZING = 532; WM_CAPTURECHANGED = 533; WM_MOVING = 534; WM_POWERBROADCAST = 536; WM_DEVICECHANGE = 537; 577
WM_IME_STARTCOMPOSITION = $010D; WM_IME_ENDCOMPOSITION = $010E; WM_IME_COMPOSITION = $010F; WM_IME_KEYLAST = $010F; WM_IME_SETCONTEXT = $0281; WM_IME_NOTIFY = $0282; WM_IME_CONTROL = $0283; WM_IME_COMPOSITIONFULL = $0284; WM_IME_SELECT = $0285; WM_IME_CHAR = $0286; WM_IME_REQUEST = $0288; WM_IME_KEYDOWN = $0290; WM_IME_KEYUP = $0291; WM_MDICREATE = $0220; WM_MDIDESTROY = $0221; WM_MDIACTIVATE = $0222; WM_MDIRESTORE = $0223; WM_MDINEXT = $0224; WM_MDIMAXIMIZE = $0225; WM_MDITILE = $0226; WM_MDICASCADE = $0227; WM_MDIICONARRANGE = $0228; WM_MDIGETACTIVE = $0229; WM_MDISETMENU = $0230; WM_ENTERSIZEMOVE = $0231; WM_EXITSIZEMOVE = $0232; WM_DROPFILES = $0233; WM_MDIREFRESHMENU = $0234; WM_MOUSEHOVER = $02A1; WM_MOUSELEAVE = $02A3; WM_CUT = $0300; WM_COPY = $0301; WM_PASTE = $0302; WM_CLEAR = $0303; WM_UNDO = $0304; WM_RENDERFORMAT = $0305; WM_RENDERALLFORMATS = $0306; WM_DESTROYCLIPBOARD = $0307; WM_DRAWCLIPBOARD = $0308; WM_PAINTCLIPBOARD = $0309; WM_VSCROLLCLIPBOARD = $030A; WM_SIZECLIPBOARD = $030B; WM_ASKCBFORMATNAME = $030C; WM_CHANGECBCHAIN = $030D; WM_HSCROLLCLIPBOARD = $030E; WM_QUERYNEWPALETTE = $030F; WM_PALETTEISCHANGING= $0310; WM_PALETTECHANGED = $0311; WM_HOTKEY = $0312; WM_PRINT = 791; WM_PRINTCLIENT = 792; WM_HANDHELDFIRST = 856; 578
WM_HANDHELDLAST = 863; WM_PENWINFIRST = $0380; WM_PENWINLAST = $038F; WM_COALESCE_FIRST = $0390; WM_COALESCE_LAST = $039F; WM_DDE_FIRST = $03E0; WM_DDE_INITIATE = WM_DDE_FIRST + 0; WM_DDE_TERMINATE = WM_DDE_FIRST + 1; WM_DDE_ADVISE = WM_DDE_FIRST + 2; WM_DDE_UNADVISE = WM_DDE_FIRST + 3; WM_DDE_ACK = WM_DDE_FIRST + 4; WM_DDE_DATA = WM_DDE_FIRST + 5; WM_DDE_REQUEST = WM_DDE_FIRST + 6; WM_DDE_POKE = WM_DDE_FIRST + 7; WM_DDE_EXECUTE = WM_DDE_FIRST + 8; WM_DDE_LAST = WM_DDE_FIRST + 8; WM_APP = $8000; WM_USER = $0400; Windows Mesajları 2
2
21 Ağu 2002 09:57 mrceng
579
BM_SETIMAGE = $00F7; { Listbox } LBN_ERRSPACE = (-2); LBN_SELCHANGE = 1; LBN_DBLCLK = 2; LBN_SELCANCEL = 3; LBN_SETFOCUS = 4; LBN_KILLFOCUS = 5; { Listbox messages } LB_ADDSTRING = $0180; LB_INSERTSTRING = $0181; LB_DELETESTRING = $0182; LB_SELITEMRANGEEX = $0183; LB_RESETCONTENT = $0184; LB_SETSEL = $0185; LB_SETCURSEL = $0186; LB_GETSEL = $0187; LB_GETCURSEL = $0188; LB_GETTEXT = $0189; LB_GETTEXTLEN = $018A; LB_GETCOUNT = $018B; LB_SELECTSTRING = $018C; LB_DIR = $018D; LB_GETTOPINDEX = $018E; LB_FINDSTRING = $018F; LB_GETSELCOUNT = $0190; LB_GETSELITEMS = $0191; LB_SETTABSTOPS = $0192; LB_GETHORIZONTALEXTENT = $0193; LB_SETHORIZONTALEXTENT = $0194; LB_SETCOLUMNWIDTH = $0195; LB_ADDFILE = $0196; LB_SETTOPINDEX = $0197; LB_GETITEMRECT = $0198; LB_GETITEMDATA = $0199; LB_SETITEMDATA = $019A; LB_SELITEMRANGE = $019B; LB_SETANCHORINDEX = $019C; LB_GETANCHORINDEX = $019D; LB_SETCARETINDEX = $019E; LB_GETCARETINDEX = $019F; LB_SETITEMHEIGHT = $01A0; LB_GETITEMHEIGHT = $01A1; LB_FINDSTRINGEXACT = $01A2; LB_SETLOCALE = $01A5; LB_GETLOCALE = $01A6; LB_SETCOUNT = $01A7; LB_INITSTORAGE = $01A8; LB_ITEMFROMPOINT = $01A9; 580
LB_ITEMFROMPOINT = $01A9; LB_MSGMAX = 432; { Combo Box } CBN_ERRSPACE = (-1); CBN_SELCHANGE = 1; CBN_DBLCLK = 2; CBN_SETFOCUS = 3; CBN_KILLFOCUS = 4; CBN_EDITCHANGE = 5; CBN_EDITUPDATE = 6; CBN_DROPDOWN = 7; CBN_CLOSEUP = 8; CBN_SELENDOK = 9; CBN_SELENDCANCEL = 10; { Combo Box messages } CB_GETEDITSEL = $0140; CB_LIMITTEXT = $0141; CB_SETEDITSEL = $0142; CB_ADDSTRING = $0143; CB_DELETESTRING = $0144; CB_DIR = $0145; CB_GETCOUNT = $0146; CB_GETCURSEL = $0147; CB_GETLBTEXT = $0148; CB_GETLBTEXTLEN = $0149; CB_INSERTSTRING = $014A; CB_RESETCONTENT = $014B; CB_FINDSTRING = $014C; CB_SELECTSTRING = $014D; CB_SETCURSEL = $014E; CB_SHOWDROPDOWN = $014F; CB_GETITEMDATA = $0150; CB_SETITEMDATA = $0151; CB_GETDROPPEDCONTROLRECT = $0152; CB_SETITEMHEIGHT = $0153; CB_GETITEMHEIGHT = $0154; CB_SETEXTENDEDUI = $0155; CB_GETEXTENDEDUI = $0156; CB_GETDROPPEDSTATE = $0157; CB_FINDSTRINGEXACT = $0158; CB_SETLOCALE = 345; CB_GETLOCALE = 346; CB_GETTOPINDEX = 347; CB_SETTOPINDEX = 348; CB_GETHORIZONTALEXTENT = 349; CB_SETHORIZONTALEXTENT = 350; CB_GETDROPPEDWIDTH = 351; CB_SETDROPPEDWIDTH = 352; CB_INITSTORAGE = 353; 581
CB_INITSTORAGE = 353; CB_MSGMAX = 354; { Edit Control } EN_SETFOCUS = $0100; EN_KILLFOCUS = $0200; EN_CHANGE = $0300; EN_UPDATE = $0400; EN_ERRSPACE = $0500; EN_MAXTEXT = $0501; EN_HSCROLL = $0601; EN_VSCROLL = $0602; { Edit Control Messages } EM_GETSEL = $00B0; EM_SETSEL = $00B1; EM_GETRECT = $00B2; EM_SETRECT = $00B3; EM_SETRECTNP = $00B4; EM_SCROLL = $00B5; EM_LINESCROLL = $00B6; EM_SCROLLCARET = $00B7; EM_GETMODIFY = $00B8; EM_SETMODIFY = $00B9; EM_GETLINECOUNT = $00BA; EM_LINEINDEX = $00BB; EM_SETHANDLE = $00BC; EM_GETHANDLE = $00BD; EM_GETTHUMB = $00BE; EM_LINELENGTH = $00C1; EM_REPLACESEL = $00C2; EM_GETLINE = $00C4; EM_LIMITTEXT = $00C5; EM_CANUNDO = $00C6; EM_UNDO = $00C7; EM_FMTLINES = $00C8; EM_LINEFROMCHAR = $00C9; EM_SETTABSTOPS = $00CB; EM_SETPASSWORDCHAR = $00CC; EM_EMPTYUNDOBUFFER = $00CD; EM_GETFIRSTVISIBLELINE = $00CE; EM_SETREADONLY = $00CF; EM_SETWORDBREAKPROC = $00D0; EM_GETWORDBREAKPROC = $00D1; EM_GETPASSWORDCHAR = $00D2; EM_SETMARGINS = 211; EM_GETMARGINS = 212; EM_SETLIMITTEXT = EM_LIMITTEXT; EM_GETLIMITTEXT = 213; EM_POSFROMCHAR = 214; EM_CHARFROMPOS = 215; 582
EM_CHARFROMPOS = 215; EM_SETIMESTATUS = 216; EM_GETIMESTATUS = 217; { Scroll bar messages } SBM_SETPOS = 224; SBM_GETPOS = 225; SBM_SETRANGE = 226; SBM_SETRANGEREDRAW = 230; SBM_GETRANGE = 227; SBM_ENABLE_ARROWS = 228; SBM_SETSCROLLINFO = 233; SBM_GETSCROLLINFO = 234; { Dialog messages } DM_GETDEFID = (WM_USER+0); DM_SETDEFID = (WM_USER+1); DM_REPOSITION = (WM_USER+2); PSM_PAGEINFO = (WM_USER+100); PSM_SHEETINFO = (WM_USER+101); Delphi ve WinApi mrceng
16 Ağu 2002 19:23
Kod Yorumları NE İŞE YARAR VE NASIL KULLANIRIZ? hiphopbaba
18 Ağu 2002 15:37
bir ustteki ornekte nasıl kullanıldığına dair bir satırlık bir kod var. onu incelersen belki alında bir kıvılcım çaktırabilir. Eger component hazırlıyorsan ornegin OnKeyPress gibi bir olayin icerisine sen kod yazarsan kullanici o olayı bir daha kullanamaz. ama win mesajı ile bunu hazırlarsan o componentin olayına dokunmamış olursun ve component kullanicisida rahatlıkla istediği olaya kod yazabilir. bir diğeride bu mesajları normal program yazarkende rahatlıkla kullanabilirsin. yukarıdaki örnek gibi. örneğin bir butona basılmasını istediğinde BM_CLICK gonderdiğinde o butona basılır. kod: postMessage(button1.Handle,BM_CLICK,1,1); nasıl herşey bu kadar basit işte. 583
zorla adamı düğmeye bastırdık....:)
584
Method: WshShell.SendKeys WshShell.SendKeys strKeyString The SendKeys method sends one or more keystrokes to the active window as if they were typed at the keyboard. This method is similar to VB's SendKeys method. Each ordinary key can be represented by the character of the key itself. For example, the key sequence ABCD can be represented simply by "ABCD". Some special keys, such as the control keys, function keys, '{', '}', '[', ']', etc... are encoded in a string enclosed by braces ({}). The following table shows the list of special characters and their respective encoding for SendKeys. Key
Code
{
{{}
}
{}}
[
{[}
]
{]}
~
{~}
+
{+}
^
{^}
%
{%}
BACKSPACE
{BACKSPACE}, {BS}, or {BKSP}
BREAK
{BREAK}
CAPS LOCK
{CAPSLOCK}
DEL or DELETE {DELETE} or {DEL} DOWN ARROW
{DOWN}
END
{END}
ENTER
{ENTER} or ~
ESC
{ESC}
HELP
{HELP}
HOME
{HOME}
INS or INSERT
{INSERT} or {INS}
LEFT ARROW
{LEFT}
585
NUM LOCK
{NUMLOCK}
PAGE DOWN
{PGDN}
PAGE UP
{PGUP}
PRINT SCREEN
{PRTSC}
RIGHT ARROW
{RIGHT}
SCROLL LOCK
{SCROLLLOCK}
TAB
{TAB}
UP ARROW
{UP}
F1
{F1}
F2
{F2}
F3
{F3}
F4
{F4}
F5
{F5}
F6
{F6}
F7
{F7}
F8
{F8}
F9
{F9}
F10
{F10}
F11
{F11}
F12
{F12}
F13
{F13}
F14
{F14}
F15
{F15}
F16
{F16}
To specify keys combined with any combination of the SHIFT, CTRL, and ALT keys, precede the key code with one or more of the following codes: Key Code +
SHIFT
^
CTRL
%
ALT 586
For example, the following strKeyString produces the CTRL-ALT-DELETE keystroke combination: "^%{DELETE}" The following VBScript code creates an instance of Notepad and types the words "Hello World!" ten times into Notepad. Code: Set WshShell = WScript.CreateObject("WScript.Shell") WshShell.Run "notepad", 9 WScript.Sleep 500 ' Give Notepad some time to load For i = 1 To 10 WshShell.SendKeys "Hello World!" WshShell.SendKeys "{ENTER}" Next
587
The views and information expressed in this document represent those of its author(s) who are solely responsible for its content. Borland does not make or give any representation or warranty with respect to such content. Creating Delphi DLLs for use with Paradox and dBASE By Sundar Rajan Table of Contents ·
Two-Minute DLL
·
Passing Variables
·
Dialog Boxes in a DLL - Exposing the Common Dialogs o
Modifying the Project File to Create a DLL
o
Writing the Library Functions
o
Using the dialog box from a Paradox application
·
DLLs with Database Components - A Dynamic Table Browser Example
·
Listing Aliases - Deriving New Components for the BDE
·
A Popup Calendar for Paradox/dBASE Users Using the Calendar Component
·
Tips
·
Summary
Most Paradox and dBASE developers would agree that creating libraries of commonly used functions promotes re-use, resource sharing and serves as a excellent means of shortening development cycles. System-wide procedures and functions can be encapsulated into ObjectPAL libraries or dBASE program files. The catch , of course, is that these libraries are not truly re-usable - The functions and procedures in the libraries can only be used by the development tool that created it. A dBASE program doesn't understand the functions in an ObjectPAL library and vice versa. Wouldn't it be nice if you could write some or all of the code for common functions just once and be done with? Is this possible ? Actually, yes!. You can write modular, re-usable code libraries that operate independently of your development system. The format of such a library is called DLL, and virtually all Windows development tools can access functions and procedures in DLLs. In the Windows world, DLLs remain the only truly reusable component format that can be shared amongst all windows development tools. Database Developers resort to DLLs for various reasons : ·
Create functions that are significantly faster than those written in ObjectPAL or dBASE.
·
Do things that are very difficult or impossible to do in Paradox/dBASE (invoke Callback functions). 588
While Paradox/dBASE developers have used DLLs, the process of creating such DLLs were left to third-parties or C/C++ experts because of the complexity and learning curve associated with writing 'C' Windows code. Finally, with Delphi, xBASE and Paradox programmers can create DLLs using a familiar, visual development toolset. In this article, I will show how Delphi can be used to create DLLs, including DLLs that contain forms and database components and discuss an application architecture that makes best use of the different development tools. Two-Minute DLL The beauty with Delphi is that even novice windows programmers can churn out DLLs quickly without having to grapple with esoteric concepts like WEP, LIBMAIN and DEF files. You proceed to create functions and procedures pretty much the same way you would do with Paradox or dBASE. Delphi can then instantly turn these programs into DLLs by renaming the Program as Library and by exporting the functions. The MinMax DLL library listed below contains two functions Min, Max . To create this DLL : 1. Open a new project in Delphi, 2. Close the default form and unit created (MinMax project does not need any forms or units) 3. Save the project as MinMax (the name of the DLL) and change the Program keyword to Library. 4. Remove the forms unit from the project's uses clause. 5. Remove all lines of code between the begin and end at the bottom of the file. 6. Write the code for the functions. The EXPORTS specifier in the function declaration tells the compiler that the function is going to be called from another external module. Any routines exported from a DLL must include the export specifier. All call-backs such as window procedures also need this specifier. 7. Finally, declare the routines that are actually exported from the DLL under the EXPORTS statement. The EXPORTS statement at the end of the library source code tells the compiler what is visible and what isn't. Library MinMax; function Min(X, Y: Integer): Integer; export; begin if X < Y then Min := X else Min := Y; end; function Max(X, Y: Integer): Integer; export; begin if X > Y then Max := X else Max := Y; end; 589
exports Min index 1, Max index 2; begin end. Even from this simple example, you can see that although the syntax of the library functions is not very different from ObjectPAL or dBASE, the resultant library functions compile into DLLs and are truly re-usable - not only by Delphi programs but also from Paradox, dBASE and other Windows development tools. To use the DLL functions in Paradox, create a new form and declare the functions in the USES section and create a push button to invoke the functions: ; the following goes in the form's Uses window uses MinMax ; load routines from MINMAX.DLL Min (x CWORD, y CWORD) CWORD ; declare the routines to use Max (x CWORD, y CWORD) CWORD endUses method pushButton(var eventInfo Event) var x, y, z SmallInt endVar x.View("Enter X") y.View("Enter Y") z = Min(x, y) ; call Min from the DLL msgInfo("Min", z) z = Max(x, y) ; call Max from the DLL msgInfo("Max", z) endMethod Passing Variables When you pass variables between the calling program and the Delphi DLL, you can pretty much use the standard Pascal data types such as Integer, LongInt etc, except for the ones shaded in figure below. The Pascal string type may seem like the equivalent of the ObjectPAL string - do not use the Pascal String type as a parameter or return variable in your DLL function. The reason : Pascal String type is only understood by Pascal. Instead, use the PChar type - PChar is a pointer to a null-terminated array of characters - the standard C-language string type that is understood by every language that can call DLLs. Likewise, avoid using the Pascal Logical data type as a return parameter since it does not match to an ObjectPAL data type. Data types translation table
590
Invoking DLL functions or procedures with incorrect data types can lead to various problems including GPFs and system crashes. Here is a typical example: A Delphi DLL (in MYDLL.DLL) function I was testing had the following prototype : function MyFunction(X, Y: Integer): Integer; export; I had mistakenly assigned a return type of Smallint to the function in the Uses section of the Paradox form that called the DLL : Uses MyDll ; load routines from MyDLL.DLL MyFunction (x CWORD, y CWORD) Smallint endUses Everytime the DLL function was invoked, Paradox threw up the error message "You have tried to use an unassigned variable. A variable must be assigned a value before you can use it.". The correct return type for MyFunction in the Uses section should have been CWORD. Dialog Boxes in a DLL - Exposing the Common Dialogs Creating DLLs that contain functions and procedures is all very nice, but Delphi's true power is in creating DLLs that contain dialogs boxes and forms. In the past, such DLLs were the domain of C/C++ experts or third-party vendors, primarily because of the complexity of writing Windows code in C/C++. With Delphi's rich tool-set and VCL, dBASE and Paradox programmers can create DLLs using the same visual metaphor they're already familiar with. Consider a Paradox application that lets users dynamically select color preferences for forms and other screen objects. We know that Windows API has 'common dialog' functions for selecting color and that Paradox does not surface this function in ObjectPAL. Not only that- to invoke the Windows API common dialogs, you need callback capabilities that are beyond the scope of ObjectPAL. Let us consider another common dialog-a File Open dialog which is useful for selecting dBASE/Paradox tables (Paradox's File-browser has some limitations - it displays SQL aliases even when the selection filter explicitly specifies dBASE/Paradox tables). These are typical situations when dBASE/Paradox developers can turn to Delphi for solutions-Delphi has built-in dialog components for selecting color and opening files. (There is actually another way of invoking common dialogs: By making calls to the Windows API directly. I do not recommend this approach as the Delphi VCL has encapsulated the common dialog API functions and Delphi
591
components are easily enhanced). CMNDLGS Project listed below exports two wrapper functions-for the color dialog and for the file open dialog : Modifying the Project File to create a DLL ·
Note that the default project file created by Delphi defaults to the reserved word Program in the first line-change this to Library.
·
Remove the Forms unit from the project's uses clause.
·
Remove all lines of code in the begin..end block.
·
Finally, add an export clause to export the functions you want to make available to other programs.
Library Cmndlgs; uses Cdlgunit in 'CDLGUNIT.PAS'; {FrmDialog} exports wwGetOpenFileName, wwChooseColor; begin end. Writing the Library functions Since Paradox /dBASE cannot use Delphi's components directly, we need to wrap the components in a Delphi function or procedure and export those instead. ·
Open the default forms unit created by Delphi and place the color dialog and File Open dialog on it. Save the unit as CDLGUNIT.PAS, instead of the default file name unit1.pas.
·
The function wwChooseColor passes a default color (LongInt) and returns the user-selected color in the same variable ClrSelected (note the var keyword tells Delphi that the variable is passed by reference). Declare the interface routine as below. The export keyword indicates that the function will be visible to external programs.
· · · ·
· · · · ·
function wwChooseColor(var ClrSelected :LongInt) :Integer; export; wwGetOpenFileName (the wrapper for File Open dialog ) accepts the title, filter values (e.g. '*.DB'), and returns the filename selected via the third parameter szName. function wwGetOpenFileName(szTitle :PChar; szFilter :PChar ; szName: PChar) : Integer; export; All that remains now is to implement the functions. The code for the color dialog and the file open dialog functions are given below : 592
Figure CLRDLG - Delphi's Color Dialog component invoked from Paradox. Choose Box Color invokes the Delphi DLL function wwChooseColor and lets users pick the color for the Box using the Common Dialog.
Delphi code for wwGetOpenFileName and wwChooseColor functions
unit Cdlgunit; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs; type TFrmDialog = class(TForm) OpenDialog1: TOpenDialog; ColorDialog1: TColorDialog; private { Private declarations } public { Public declarations } end; var FrmDialog: TFrmDialog; function wwGetOpenFileName(szTitle :PChar; szFilter :PChar ; szName: PChar) : Integer; export; function wwChooseColor(var ClrSelected :LongInt) :Integer; export; implementation {$R *.DFM} function wwGetOpenFileName(szTitle :PChar; szFilter :PChar; szName: PChar) : Integer; var 593
someStr: String; begin Result := 0; try frmDialog := TFrmDialog.Create(Application); with frmDialog do begin OpenDialog1.title := StrPas(szTitle); OpenDialog1.DefaultExt := 'DB'; OpenDialog1.Filter := strPas(szFilter); if OpenDialog1.Execute then begin StrPCopy(szName, OpenDialog1.FileName); Result :=1; end; end; finally frmDialog.free; end; end; function wwChooseColor(var clrSelected: LongInt) : Integer; var someStr: String; begin Result := 0; try frmDialog := TFrmDialog.Create(Application); with frmDialog do begin ColorDialog1.Color := clrSelected; if ColorDialog1.Execute then begin ClrSelected := ColorDialog1.Color; Result :=1; end else begin Result := 0; end; end; finally frmDialog.free; end; end; end. 594
Using the dialog box from a Paradox application Figure CLRDLG shows the Paradox form CLRDLG.FSL in action. Code in the Uses window of the form: uses CMNDLGS wwGetOpenfileName(Title CPTR, Filter CPTR, SelectedFile CPTR) CWORD wwChooseColor(ClrSent CPTR) CWORD enduses The pushbutton labeled "Choose Box Color" invokes the Color dialog function, and sets the box color to the one picked by the user. Var ClrSelected LongInt EndVar ClrSelected = Box.color if wwChooseColor(ClrSelected) = 1 then ; Success Box.Color = ClrSelected endif DLLs with Database Components - A Dynamic Table Browser example Let 's move to a topic that is sure to interest database developers - creating reusable DLLs that contain database components. The importance is obvious database functions executed from DLLs are faster than those written in ObjectPAL/dBASE and the can be shared amongst different applications and development tools. Figure TBLVIEW (below) shows an example of a Paradox utility that uses two DLL functions - the File Open dialog component and a Table Viewer component in a DLL to create a dynamic table browser. Listing of TBLVIEW.DPR
Library Tblview; uses Tblvunit in 'TBLVUNIT.PAS' {tvform1}; exports UseDll, ExitDll, ViewTable; {$R *.RES} begin { Remember to remove the generated application control invocations Application.CreateForm(TDllForm1, DllForm1); Application.Run; 595
} end. Follow the same steps as before to create the TBLVIEW.DPR project file. Open the default forms unit created by Delphi - drop a TDataSource, TTable, TDBNavigator and TDBGrid components on the form; and save the unit as TBLVUNIT. Delphi's live display of data lets you check out your DLL even at design time. Connect the table named "Table1" to the example CUSTOMER.DB table (this table name will be dynamically assigned at run-time by the DLL function). Now hook up the datasource to the table by setting the DataSet property of the datasource to Table1. Hook the DBGrid to the datasource by setting its DataSource Property to DataSource1. Also, hook the DBNavigator to the datasource by setting its DataSource property to DataSource1. The DLL exports a single funtion ViewTable - It accepts a table name (PCHAR), converts it to string, assigns it to the TableName property of the TTable component and displays the contents of the table using the DBGrid. However, there are several aspects to consider when you use database components in a DLL. Here are the main points : ·
When a DLL uses database components (or any other BDE component), the DLL requires per-task initialization. In other words, the calling program should verify that no other users are using the DLL function. UseDLL procedure below illustrates this.
·
Any Exceptions should be trapped within the DLL - exceptions across DLL boundaries cause GPFs and abnormal terminations. Note that exceptions are handled by the ViewTable function in our example, and the error messages are displayed to the user by the DLL function.
·
When Paradox or other applications call DLL functions that use the BDE, they also need to de-initialize the DLL before exiting. Failure to do this can result in GPFs. The BDE service needs to be de-initialized before the DLL WEP is called during DLL unload. The ExitDLL routine must be called by the calling program to de-initialize this DLL and its services. Once the deinitialization occurs, the DLL cannot be used again until it is unloaded. The Close() method of the Paradox program calls the ExitDll procedure as below :
method close(var eventInfo Event) if not eventInfo.isPreFilter() then ExitDll() endif endmethod
596
Figure TBLVIEW (Delphi Database DLL example): Unlike C/C++, Delphi is rich in database capabilities. The figure shows one of it many unique features - live display of data even at design time. ViewTable function exported by TBLVIEW.DLL accepts a table name as a parameter and displays the contents of the table using a DBGrid as shown above. The Paradox form TBLVIEWR.FSL uses the common dialog file open function WWGetOpenFileName to select a Paradox or dBASE table. Then the ViewTable function is used to display the contents of the table. Listing of TBLVUNIT.PAS unit Tblvunit; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Buttons, Grids, DBGrids, DB, DBTables, ExtCtrls, DBCtrls; { Note that the TTVForm1 form is always used modally. Borland does not 597
recommend using a modeless form from a DLL because the OnActivate and OnDeactivate mechanisms do not work when control is transferred between a DLL owned form and another DLL or EXE owned form. } type TTvform1 = class(TForm) Table1: TTable; DataSource1: TDataSource; DBGrid1: TDBGrid; OKBtn1: TBitBtn; DBNavigator1: TDBNavigator; procedure FormClose(Sender: TObject; var Action: TCloseAction); private { Private declarations } public { Public declarations } end; const DllErrorUnload = 'The DLL must first be unloaded before re-use'; DllErrorInUse = 'The DLL is already in use by another application'; DllErrorViewingTable = 'The table could not be viewed: %s'; var tvform1: Ttvform1; { UseDLL should be called to verify that the DLL may be used by the current application. ExitDLL should be called before the DLL client exits. It CANNOT be called from a DLL WEP. } function UseDLL: integer; export; procedure ExitDLL; export; function ViewTable (const TableName: PChar): Integer; export; implementation var Task: THandle; Unloaded: Boolean; {$R *.DFM} { Exceptions that are raised across a DLL call boundary cause an application termination. Therefore they are caught here and converted to an error return. The caller may convert back to an exception (if written in Delphi or C++) or pass the result back through some other error handling mechanism. } 598
function ViewTable (const TableName: PChar): Integer; begin try if not Assigned (tvform1) then TvForm1 := TTVForm1.Create (nil); with TvForm1 do begin Table1.TableName := StrPas(TableName); Table1.Active := True; ShowModal; Result := 1; end except on EResult: Exception do begin ShowMessage(Format (DllErrorViewingTable, [EResult.Message])); Result := 0; end else begin ShowMessage(Format (DllErrorViewingTable, ['Unknown error'])); Result := 0; end; end end; { Deactivate the table before the form is closed } procedure TTvform1.FormClose(Sender: TObject; var Action: TCloseAction); begin Table1.Active := False; end; { Some services need to be de-initialized before the DLL WEP is called during DLL unload. BDE is an example of a service that cannot be de-initialized from within the WEP. The following routine must be called by the user of this DLL to de-initialize this DLL and its services. Once the deinitialization occurs, the DLL cannot be used again until it is unloaded. } procedure ExitDLL; begin CallExitProcs; Unloaded := True; end; { Many services require per-task initialization. BDE is an example of such a service. If a service DLL uses other such services, it too may require per-task initialization. Since the VCL BDE Session variable is allocated and initialized per-module rather than per-task, multiple applications 599
cannot simultaneously use a DLL that itself uses BDE. In this demo service, multiple access is protected against via initialization and use validation routines. Another application (.EXE or .DLL in another task) cannot use this DLL until the first application unloads the DLL. For statically linked DLLs, the unload occurs automatically during application termination. } function UseDLL: integer; begin Result := 0; if Task <> GetCurrentTask then begin ShowMessage(DLLErrorInUse); end else if Unloaded then begin ShowMessage(DLLErrorUnload); end else Result := 1 end; { Initialize the task as soon as this service is loaded } begin Task := GetCurrentTask end. Listing Aliases - Deriving new components for the BDE Although dBASE and Paradox are built on the same database engine, they do not surface the same BDE functions. dBASE, for example, does not have the equivalent of ObjectPAL's Session methods such as enumAliasNames, enumDriverInfo and others that provide access to BDE system tables.
600
When functionality is not available in your development tool, turn to Delphi. With complete access to BDE, Delphi provides two options to access BDE functions. ·
Write directly to the BDE, access the BDE system tables and populate a grid with a list of aliases; or
·
Derive new database components for the BDE system tables.
Delphi components can be enhanced, sub-classed and modified easily and we will adopt the second approach. An example of deriving new database components for the BDE system tables is in Delphi's db\tools directory. Follow the README.TXT file to install the sample components. The wwPickAlias function listed below uses the DatabaseList (installed in the Sample page) component to construct a 'pick list' of aliases. To set up the form for the Pick Alias example, drop a DatabaseList, a DataSource and a DBGrid component. Hook them together by setting the DataSet property of the Datasource to AliasList and set the DBGrid's datasource to DataSource1. When invoked from a dBASE or Paradox program, the wwPickAlias function allows the user to pick from a list of current alias details displayed in a DBGrid. function wwPickAlias( SelectedAlias : PChar) : Integer; begin try Result := 0; if not Assigned (Form1) then Form1 := TForm1.Create (nil); With Form1 do begin Try form1.ShowModal; Except on EDatabaseerror do Begin MessageDlg('Error in Alias List ' , mtWarning, [mbOk], 0); 601
end; end; StrPCopy(SelectedAlias,AliasListName.Value); Result := 1; end; except on EResult: Exception do ShowMessage(Format (DllErrorViewingTable, [EResult.Message])); else ShowMessage(Format (DllErrorViewingTable, ['Unknown error'])); end; end; A Popup Calendar for Paradox/dBASE users using the Calendar component Another nifty Delphi component found on the sample's page is the Calendar component. This can be used for displaying a calendar or let the user choose a date. As in examples before, the DLL function wwPickdate exported by the PICKDATE.DLL is a wrapper for the Delphi Calendar component. By exporting this function, Paradox/dBASE users get a nifty popup calendar.
Can this not be done in Paradox or dBASE ? Yes, but the resultant dialog box won't be as fast or as easy to share with other systems. The source code for PICKDATE.DLL and the Paradox form for this example and all others discussed in this paper are included in the code samples ( Code samples also include additional examples : Creating custom cursor DLLs and using them with Paradox; Invoking Delphi DLLs from Visual Basic.) Tips 602
·
Since they include the VCL run-time, Delphi DLLs tend to be fairly large (>180K). This overhead can be minimized by putting several functions, dialog boxes and forms into a single DLL.
·
Test Delphi DLLs first from within the Delphi environment by creating simple calling programs in Delphi. There are two reasons for doing this -1. It is more difficult to debug Delphi DLLs from Paradox/dBASE, 2. Delphi's IDE handles exceptions and errors better than Paradox/dBASE and prevents system crashes.
Summary Delphi's architecture promotes rapid development of re-usable components. Wrapping Delphi components in DLL functions enables other development environments to make use of these powerful components. The examples presented in the paper show how Paradox/dBASE developers can extend their development environment, solve complex problems and improve application performance by making use of Delphi DLLs. Interestingly enough, this model of splitting development tasks between "Solution Builders" (Business analysts with an understanding of end-user needs) and "Component Builders" (Software Engineers specializing in building re-usable components) was also proposed by Microsoft's researchers in their 'Solutions Framework' as a methodology for Rapid Application Development. Delphi Technical Library Borland's Technical Library Copyright © 1996 Borland International, Inc. Last Modified: March 10, 1996
603
Title: Paradox and Network From: VSF Date: 07/18/2000 07:04AM PST Answer Grade: A Points: 15 How can I work with Paradox Databases on a Windows Network (not NT)?! I installed my program and the Database files on the server and it worked. I can run then program from a station and it works, but when I try to run both (station + server) simultaneous the problem shows up! They (server and station)can't add records at the same time on the same database file! Can't I use Paradox with networks!?!? Please help me experts!! View Accepted Answer If this EE solution does not provide the information you are looking for, you can signup as a member and ask your specific question of our 125,000 experts for free. Question History Comment from kretzschmar
07/18/2000 07:30AM PST
hi vsf, point the .net file into a shared network-directory. use the exact the same name for all clients. if one a dedicated server then use a unc path for this. all clients must be point to this directory the entry is found at bde-admin|ConfigureationTab|Configuration|Drivers|Native|Paradox and is named NET DIR the same must be done at the alias with the path entry:eyact the same entry on all clients. let me know if you've further problems meikl Comment from VSF
07/18/2000 07:36AM PST
kretzschmar I will try this, but I'm not quite sure I understood it all. Can you be more specific ?! Thanks !!! Comment from kretzschmar
07/18/2000 08:16AM PST 604
hi vsf, first >Can't I use Paradox with networks!?!? sure, you can use paradox on a network, i do this since over 10 years, but there are some rules by the configuration, which must be done, because you can then have mysterious sideeffects, like your problem, or corrupted indexes and lost records. well, a bit explaination: the .net file is used by the paradox-engine to control the accesses to the databases and if wanted that more than one client will work with the same table, then all clients must have access to the same .net-file. additional is the .net file stored the path to the table, so that the engine means, if two user use two different logical paths to the table, that the tables are on a different location, also if physically the same directory meant. be free to ask further if something unclear meikl Comment from isstorr
07/18/2000 08:29AM PST
hi VSF, what kretzschmar is saying is that you need to set the BDE's NETDIR (the directory which stores the BDE's file locking information) to be the same for all machines on your network. This is essential for locking to work correctly. You need to do this using the BDE Administrator (BDEAdmin.exe) which is found inside the BDE directory (usually C:\Program Files\Borland\Common Files\BDE) Once you have started BDEAdmin Click Configuration tab Click +Drivers Click +Native Click +Paradox In Definition under NET DIR type in a unc path to a shared directory on the server (eg //SERVER1/C/NetDir) Click the blue arrow in the toolbar to save changes Next click +System (under Configuration again) click INIT set LOCAL SHARE to TRUE Click the blue arrow in the toolbar to save changes Close BDEAdmin Its also a good idea to configure the BDE on each machine to use a common configuration file (IDAPI32.CFG). This is also found in the BDE directory. If you do this, you will only need to set up the BDE once and it will eliminate any inconsistencies in the BDE setup on each machine in the network.
605
The location of the configuration file can be set using the registry key HKEY_LOCAL_MACHINE\Software\Borland\Database Engine\CONFIGFILE01 change this key on each machine to a unc path to the IDAPI32.CFG on the server - e.g. \\SERVER1\C\Program Files\Borland\Common Files\BDE\IDAPI32.CFG However for speed, I've found its best to have an installation of the BDE local to each machine, but the configuration file on the server only. let us know if you need further explanation Si Comment from isstorr
07/18/2000 08:31AM PST
sorry In Definition under NET DIR type in a unc path to a shared directory on the server (eg //SERVER1/C/NetDir) Click the blue arrow in the toolbar to save changes should read In Definition under NET DIR type in a unc path to a shared directory on the server (eg \\SERVER1\C\NetDir) Click the blue arrow in the toolbar to save changes :o) Si Comment from kretzschmar
07/18/2000 10:17AM PST
thanks, isstorr, for the verification, and the detailed explaination (i couldn't do it better) ;-)) Accepted Answer from bozo7
07/18/2000 01:41PM PST
I have also done this many times and I just use the folowing code in the on create event of my datamodule: session.netfiledir := '//server1/c/netdir'; It is easier than trying go into everyones bde admin change the Netfile setting there. Ross Comment from isstorr
07/19/2000 01:58AM PST
And also Session.PrivateDir is the location of any .lck files and the workspace for large queries (_Q files) such as generated by Quick Reports. This has to be unique and local to each machine, unlike netdir. If you don't set this then it will default to your alias path, and will mess up on a network (believe me!)
606
Say I want my PrivateDir to be C:\BDETEMP, then on starting the app I clear the contents of this directory before setting Session.PrivateDir to it. This clears out any _Qxxx files which may have been left there if QuickReports crashes (and these can be upwards of 100MB in size!)
607
From Paradox to Access with ADO Page 1: Focusing on the TADOCommand components and using the SQL DDL language.
Chapter 5 of this course (Free Delphi Database Course for Beginners - focus on ADO techniques) featured the ADOQuery component designed to enable Delphi developers to use the SQL language with ADO. The SQL statements can either be DDL (Data Definition Language) statements such as CREATE TABLE, ALTER INDEX, and so forth, or they can be DML (Data Manipulation Language) statements, such as SELECT, UPDATE, and DELETE. In this chapter, I'll focus on the TADOCommand components and using the SQL DDL language to help port your BDE/Paradox data to ADO/Access. Data definition language Creating a database programmatically isn't something most developers do every day - we all use some kind of visual tool, like MS Access for maintaining a MDB file. Unfortunately, sometimes you'll need to create and destroy databases and database objects from code. The most basic technique in use today is the Structured Query Language Data Definition Language (SQL DDL). Data definition language (DDL) statements are SQL statements that support the definition or declaration of database objects (for example, CREATE TABLE, DROP TABLE, CREATE INDEX and similar statements). My intention here is not to teach you the DDL language, if you are familiar with the SQL DML then DDL should be no barrier for you. Note that working with DDL can be quite tricky, every database vendor generally provides its own extensions to SQL. Let's quickly take a look at a simple CREATE TABLE statement: CREATE TABLE PhoneBook( Name TEXT(50) Tel TEXT(50) ); This DDL statemnt (for MS Access), when executed, will create a new table named PhoneBook. The PhoneBook table will have two fields, Name and Tel. Both fields are of the string (TEXT) type and the size of the fields is 50 characters. TFieldDef.DataType Obviously, the data type that represents a string in Access is TEXT. In Paradox it's STRING. In order to port Paradox tables to Access we'll have to know what data types are available and what are their names. When working with the BDE and Paradox tables, the TFieldDef.DataType determines the type of a physical field in a (dataset) table. To successfully migrate Paradox tables to Access you need to have a function that "transforms" a Paradox field type to an Access type. The next function checks the type of the field (fd) and returns the corresponding Access type along with a field size when needed for a CREATE TABLE DDL 608
statement. function AccessType(fd:TFieldDef):string; begin case fd.DataType of ftString: Result:='TEXT('+IntToStr(fd.Size)+')'; ftSmallint: Result:='SMALLINT'; ftInteger: Result:='INTEGER'; ftWord: Result:='WORD'; ftBoolean: Result:='YESNO'; ftFloat : Result:='FLOAT'; ftCurrency: Result := 'CURRENCY'; ftDate, ftTime, ftDateTime: Result := 'DATETIME'; ftAutoInc: Result := 'COUNTER'; ftBlob, ftGraphic: Result := 'LONGBINARY'; ftMemo, ftFmtMemo: Result := 'MEMO'; else Result:='MEMO'; end; end; ADOX ADO Extensions for Data Definition Language and Security (ADOX) is an extension to the ADO objects and programming model. ADOX gives developers a rich set of tools for gaining access to the structure, security model, and procedures stored in a database. To use ADOX in Delphi, you should establish a reference to the ADOX type library. 1. Select Project | Import Type Library 3. Choose "Microsoft ADO Ext 2.x for DDL and Security (Version 2.x)" 4. Change "TTable" to "TADOXTable" 5. Change "TColumn" to "TADOXColumn" 6 .Change "TIndex" to "TADOXIndex" 7. Press Install button (rebuilding packages) 8. Press OK once and Yes twice 9. File | Close All | Yes The top-level object in the ADOX object model is the Catalog object. It provides access to the Tables, Views, and Procedures collections, which are used to work with the structure of the database, and also provides the Users and Groups collections, which are used to work with security. Each Catalog object is associated with only one Connection to an underlying data source. We'll leave ADOX (at least for now) and stick to ADOExpress. TADOCommand In ADOExpress, the TADOCommand component is the VCL representation of the ADO Command object. The Command object represents a command (a query or statement) that can be processed by the data source. Commands can then be executed using the ADOCommand's Execute method. TADOCommand is most often used for executing data definition language (DDL) SQL commands. The CommandText property specifies the command to execute. The CommandType property determines how the CommandText property is interpreted. The cmdText type is used to specify the DDL statement. Although it makes no sense to use the 609
ADOCommand component to retrieve a dataset from a table, query, or stored procedure, you can do so. It's time for some real code...
From Paradox to Access with ADO Page 2: Project to port your BDE/Paradox data to ADO/Access.
The following project will demonstrate how to: • get the list of all tables in a BDE alias • use TFieldDefs in order to retrieve the definition (name, data type, size, etc.) of fields in a table. • create a CREATE TABLE statement • copy data from BDE/Paradox table to ADO/Access table. Basically what we want to do is to copy several tables from DBDemos to our aboutdelphi.mdb Access database. The structure of the aboutdelphi.mdb is discussed in the first chapter. Let's do it step by step: GUI Start Delphi - this creates a new project with one blank form. Add two Buttons, one ComboBox and one Memo component. Next, add a TTable, TADOTable, TADOConnection and a TADOCommand component. Use the Object Inspector to set the following properties (leave all the other properties as they are - for example the Memo should have the default name: Memo1): Button1.Caption = 'Construct Create command' Button2.Caption = 'Create Table and copy data' ComboBox.Name = cboBDETblNames; //as described in the second chapter ADOConnection1.ConnectionString = ... TADOTable.Name = ADOTable ADOTable.Connection = ADOConnection1 TADOCommand.Name = ADOCommand ADOCommand.Connection = ADOConnection1 TTable.Name = BDETable BDETable.DatabaseName = 'DBDEMOS' Code To retrieve a list of the tables associated with a given database (DBDEMOS) we use the next code (OnCreate for the form): procedure TForm1.FormCreate(Sender: TObject); begin Session.GetTableNames('DBDEMOS', '*.db',False, False, cboBDETblNames.Items); end; 610
When you start the project the ComboBox has all the (Paradox) table names in the DBDEMOS alias directory. In the code that follows, we'll pick the Country table. The next task is to create a CREATE TABLE DDL statement. This gets done in the 'Construct Create command' button's OnClick procedure: procedure TForm1.Button1Click(Sender: TObject); //'Construct Create command' button var i:integer; s:string; begin BDETable.TableName:=cboBDETblNames.Text; BDETable.FieldDefs.Update; s:='CREATE TABLE ' + BDETable.TableName + ' ('; with BDETable.FieldDefs do begin for i:=0 to Count-1 do begin s:=s + ' ' + Items[i].Name; s:=s + ' ' + AccessType(Items[i]); s:=s + ','; end; //for s[Length(s)]:=')'; end;//with Memo1.Clear; Memo1.lines.Add (s); end;
The above code simply parses the field definitions for the selected table (cboBDETblNames) and generates a string that will be used by the CommandText property of the TADOCommand component. For example, when you select the Country table the Memo gets filled with the next string: CREATE TABLE country ( Name TEXT(24), Capital TEXT(24), Continent TEXT(24), Area FLOAT, Population FLOAT ) And finally, the code for the 'Create Table and copy data' button drops a table
611
begin tblName:=cboBDETblNames.Text; //refresh Button1Click(Sender); //drop & create table ADOCommand.CommandText:='DROP TABLE ' + tblName; ADOCommand.Execute; ADOCommand.CommandText:=Memo1.Text; ADOCommand.Execute; ADOTable.TableName:=tblName; //copy data BDETable.Open; ADOTable.Open; try while not BDETable.Eof do begin ADOTable.Insert; for i:=0 to BDETable.Fields.Count-1 do begin ADOTable.FieldByName (BDETable.FieldDefs[i].Name).Value := BDETable.Fields[i].Value; end;//for ADOTable.Post; BDETable.Next end;//while finally BDETable.Close; ADOTable.Close; end;//try end; That's it. Check out your Access database now...voila there is a Country table with all the data from DBDEMOS. Now you can port all your Paradox tables to Access (download code). Few questions, however, stay unanswered. The first one is: how to add index definitions (CREATE INDEX ON ...) to tables. The second one is: how to create an empty Access database. I'll leave those (and others you can think of) for the Forum or for some future article - Chapter 13 precisely. To the next chapter If you need any kind of help so far, please post to the Delphi Programming Forum where all the questions are answered and beginners are treated as experts. First page > Focusing on the TADOCommand components and using the SQL DDL language. > Page 1, 2
612
Click to return to The Delphi Magazine home page... Paradox File Corruption Based on an article in The Delphi Magazine, Issue 42, February 1999, and related extract from The Delphi Clinic, Issue 17, this article is Copyright © 1999 Brian Long. The article was written by Brian Long ([email protected]) with help from Will Watts, Nick Moon, David Rose, Nick Spurrier, Andy Race and Jack Birrell. Download The Source Code Full updated source code ready to plug into your projects: Paradox.zip 5184 bytes Contents: • Paradox File Corruption, Issue 42 • The Delphi Clinic, Issue 17: Paradox Table Corruption A reader of The Delphi Magazine wrote to me with the following query: I read your summary of precautions regarding Paradox table and index corruption in multi-user applications on page 52 of Issue 17, implemented all the measures, but I still get corrupted tables and index files every now and again. Are there any more things you can think of? Well, indeed there are. Since writing that entry I have been collecting other useful corruption avoiding tips and techniques that I will list here. First of all though, below is a summary of what was highlighted in Issue 17. If there is a possibility of a form being closed with a table still open, in the OnClose event handler, ensure either Post or Cancel is called. If you open your tables programmatically, then you could also close them in a form’s OnClose or OnCloseQuery event handler. In the BDE Administrator or BDE Configuration program, set Local Share to True in the System settings. Don’t store tables on drive letters manufactured with the DOS SUBST command. Don’t store NET files in a root directory, some BDE revisions apparently did not handle this too well. Call dbiSaveChanges in the TTable’s AfterPost event handler. If your version of the BDE supports it, call dbiUseIdleTime in your Application’s OnIdle event handler. This is easier than the above point, and apparently provides the automated equivalent of it. The API was removed in BDE 4, with the suggestion of using dbiSaveChanges instead. In Delphi 1, write a wm_EndSession message handler for your main form and call Halt in it. This ensures the BDE closes down and hopefully flushes all its buffers.
613
Another useful piece of advice is to ensure you set the Session object’s PrivateDir property to some suitable private directory. Perhaps a subdirectory of your application’s directory. You can get your application’s directory with ExtractFilePath(Application. ExeName). Avoid leaving PrivateDir blank as it will default to the current directory, which will more than likely be the application directory. Letting the BDE use your application directory as its temporary working directory can lead to Lock file has grown too big exceptions. Also, avoid pointing PrivateDir at a root directory, make sure it points at a subdirectory. One final point on the private directory subject is that the private directory should have access to a reasonable amount of disk space. A query linked across tables to extract data may take up at least three times the size of largest table for its temporary __*.* files. If your application is terminated unexpectedly, your BDE private directory might have its temporary files left in place, instead of being deleted as they would normally be by the BDE. If you think this might be a problem, you could write a section of code that executes at the beginning of your application, before any tables are opened, that deletes files from the private directory conforming to the specification __qb*.*. Since the Local Share setting is so crucial, one idea is to only let the program run if Local Share has a value of True. Your main form’s OnCreate handler could call a routine like this. procedure CheckLocalShare; var ASYSConfig: SYSConfig; begin {$ifdef Win32} { Ensure BDE is initialised } Session.Open; {$endif} if (DbiGetSysConfig(ASYSConfig) = DbiErr_None) and not ASYSConfig.bLocalShare then begin ShowMessage('BDE''s LOCAL SHARE flag must be TRUE for this ' + 'program to run. Ask your System Administrator to do this for ' + 'you.'#13#13'This program will not continue until this change ' + 'has been made and all BDE applications have been restarted'); {$ifdef Win32} Application.ShowMainForm := False; {$endif} Application.Terminate; end end; In Delphi 2 or later you can also read Local Share in a more VCL-esque fashion by making use of Session.GetConfigParams('\System\Init', SomeTStringsObject) and then checking SomeTStringsObject.Values['LOCAL SHARE'] Another piece of general advice for applications that may be editing, deleting and adding many records is to periodically pack the Paradox tables to save them spreading across your hard disk. Issue 9, p63 has code for doing this. 614
New information needs to be added to this list to ensure that networked machines do not cause problems by ‘clever’ buffering of any sort. One of the prime reasons for the problem is that two applications accessing the same table have conflicting views of what is really in the table because each machine is caching changes to a certain degree. Unfortunately there are many levels of this caching. Consequently there are many system settings that you need to give appropriate values to in order to avoid its potentially harmful impact to the application. This problem is more general than just Paradox applications: many vendors have the same issues and so these settings can help many applications to work in a more resilient manner. However, given the context of this article, I will only be referring to BDE-driven Paradox data applications. For all Windows 95 machines running BDE applications accessing Paradox or dBASE data or containing that data you should take the following steps. Firstly, make sure you have at least version 4.00.1116 of VREDIR.VXD (156,773 bytes, 11th Sep 97, 11:16) and version 4.00.1112 of VNETSUP.VXD (17,595 bytes, 30th May 97, 11:12). These can be installed with the patch program vrdrupd.exe located at http://support.microsoft.com/Download/support/mslfiles/vrdrupd.exe and fix a problem in Windows 95 where an application shares data with a Windows NT server, as described in Microsoft’s Knowledge Base articles Q148367 and Q174371. Note that Microsoft’s articles get the date stamp of VREDIR.VXD wrong and suggest it is 2 Jun 97. Next, VREDIR must also be set up correctly: ensure that the binary registry value HKey_Local_Machine\System\CurrentControlSet\Services\VxD\VREDIR\Discar dCacheOnOpen is set to 01. The machine will need rebooting for this setting to take effect. Plus, Windows 95 caching should be disabled. To do this, launch the System Properties dialog by holding down the Alt key and double clicking on My Computer (or by holding down the Windows key and pressing Pause). Click the Performance tab, press the File System... button and click on the Troubleshooting tab. Check the options: Disable write-behind caching for all drives. This corresponds to setting this binary value to 0: HKey_Local_Machine\System\CurrentControlSet\Control\FileSystem\DriveWrit eBehind In addition to the above setting there is some suggestion that the Disable synchronous buffer commits and Disable new file sharing and locking semantics options should also be checked, although the help for these options does not really uphold this. This equates to changing the binary settings AsyncFileCommit to 1 and SoftCompatMode to 0. Windows 95 machines running Novell networking software should set the registry entries as: Type
Key
Binary HKey_Local_Machine\System\CurrentControlSet\
Value 0
615
Services\VxD\NWREDIR\ReadCaching String
HKey_Local_Machine\Network\Novell\System Config\ Netware Dos Requester\Cache Writes
No
String
HKey_Local_Machine\Network\Novell\System Config\ Netware Dos Requester\Opportunistic Locking
No
(setting these when Novell is not installed does no harm). On Windows NT machines that run the BDE, or contain Paradox or dBASE files, these settings should be applied to disable opportunistic locking. Type
Key
Value
DWord HKey_Local_Machine\System\CurrentControlSet\Services\ LanmanServer\Parameters\EnableOpLocks
0
DWord HKey_Local_Machine\System\CurrentControlSet\Services\ LanmanServer\Parameters\EnableOpLockForceClose This is probably redundant because of the setting above, but you can set it to be on the safe side
1
DWord HKey_Local_Machine\System\CurrentControlSet\Services\ LanmanServer\Parameters\CachedOpenLimit
0
DWord HKey_Local_Machine\System\CurrentControlSet\Services\ LanmanWorkStation\Parameters\UseOpportunisticLocking
0
DWord HKey_Local_Machine\System\CurrentControlSet\Services\ LanmanWorkStation\Parameters\UtilizeNtCaching
0
For Windows NT Workstation’s running Novell NetWare: DWord HKey_Local_Machine\System\CurrentControlSet\Services\ NWCWorkstation\Parameters\DisablePopup
0
The machine will need rebooting for these settings to take effect. Opportunistic locking is explained in Microsoft’s Knowledge Base article Q129202, and this quote is taken from the Windows NT Resource Kit: ‘This setting specifies whether the server allows clients to use oplocks on files. Oplocks are a significant performance enhancement, but have the potential to cause lost cached data on some networks, particularly wide area networks.’ Some of the problems introduced by oplocks are described in articles Q134637, Q124916, and Q126026. If you are running Windows for Workgroups 3.1x, you should disable disk write caching. In SYSTEM.INI in the [386Enh] section and add an entry to disable 32bit write-caching on all drives that have shared data on them, 616
eg ForceLazyOff=CDE. Microsoft KnowledgeBase article Q107645 describes this option and article Q108109 emphasises that [386Enh] is the correct section, not [vcache] as mentioned in the Resource Kit documentation. If AUTOEXEC.BAT loads the SMARTDRV.EXE program, make sure the /X parameter is used to disable write-behind caching. The sample project DBStuff.Dpr (which can be found in the PARADOX.ZIP file) contains some code which will check all the appropriate entries depending on whether you are running your application on Windows 95, NT or 3.1. In fact, so potentially important are the registry settings that if they are not found to have the correct values, the program sets them and suggests that the user reboots. DBStuff.Dpr compiles in all versions of Delphi from 1 to 4. The reboot dialog comes from RestartDialog, an undocumented Shell32 API described in the Restarting Windows entry in Issue 40’s Delphi Clinic. Since I am not passing any text to the API I don’t need to worry about the Unicode issue highlighted in that write-up. The code below shows the form’s OnCreate handler, the main registry checking routine and the Windows 95 checking code. You can see that several helper routines are not listed, including the code that checks the version information of the network redirector files. Refer to the project on the disk for all the missing code. One helper routine listed in full is CheckRegistryEntry. This verifies both numeric and string registry values (passed as a Variant parameter), setting them if they are either wrong or missing. If a change was made, the Reboot Required flag is set to True. procedure TForm1.FormCreate(Sender: TObject); begin CheckOKForParadoxAppToRun end; ... procedure CheckRegistryIsAcceptable; begin {$ifdef Win32} case Win32Platform of VER_PLATFORM_WIN32_WINDOWS : CheckWin95Registry; VER_PLATFORM_WIN32_NT : CheckWinNTRegistry; end; if RebootRequired then //Use standard Win32 reboot dialog RestartDialog(0, nil, ew_RestartWindows) {$else} CheckWin31Registry; if RebootRequired then begin ShowMessage('Some system settings have been changed '+ '- Windows needs to restart'); ExitWindows(ew_RestartWindows, 0) end {$endif} end; ... procedure CheckRegistryEntry(Reg: TRegistry; const Path, Value: String; 617
const Default, Desired: Variant; Size: Byte); var TmpInt: Cardinal; TmpStr: String; begin with Reg do if OpenKey(Path, True) then try case VarType(Desired) of varInteger: { Some numbers need to be stored as DWORD values, } { while some need to be stored as binary values } if Size = 0 then begin if not ValueExists(Value) or (ReadInteger(Value) = Default) then begin WriteInteger(Value, Desired); RebootRequired := True end end else begin TmpInt := Default; if ValueExists(Value) then ReadBinaryData(Value, TmpInt, Size); if TmpInt = Default then begin TmpInt := Desired; WriteBinaryData(Value, TmpInt, Size); RebootRequired := True end end; varString: begin if not ValueExists(Value) or (ReadString(Value) = Default) then begin WriteString(Value, Desired); RebootRequired := True end end end finally CloseKey end end; const Control = 'System\CurrentControlSet\Control\'; Services = 'System\CurrentControlSet\Services\'; procedure CheckWin95Registry; 618
var Reg: TRegistry; const DOSRequester = 'Network\Novell\System Config\Netware Dos Requester'; begin Reg := TRegistry.Create; try Reg.RootKey := HKey_Local_Machine; //Fix VREDIR.VxD settings CheckRegistryEntry(Reg, Services + 'VxD\VREDIR', 'DiscardCacheOnOpen', 0, 1, SizeOf(Byte)); //Fix NWREDIR.VxD settings CheckRegistryEntry(Reg, Services + 'VxD\NWREDIR', 'ReadCaching', 1, 0, SizeOf(Byte)); //Fix Novell settings CheckRegistryEntry(Reg, DOSRequester, 'Cache Writes', 'Yes', 'No', 0); CheckRegistryEntry(Reg, DOSRequester, 'Opportunistic Locking', 'Yes', 'No', 0); //Fix FileSystem troubleshooting settings CheckRegistryEntry(Reg, Control + 'FileSystem', 'DriveWriteBehind', $FFFFFFFF, 0, SizeOf(Longint)); {$define AllOptionsThatPeopleSuggest} {$ifdef AllOptionsThatPeopleSuggest} CheckRegistryEntry(Reg, Control + 'FileSystem', 'SoftCompatMode', 1, 0, SizeOf(Longint)); CheckRegistryEntry(Reg, Control + 'FileSystem', 'AsyncFileCommit', 0, 1, SizeOf(Byte)); {$endif} finally Reg.Free end end; ... procedure CheckOKForParadoxAppToRun; begin {$ifdef Win32} //Only Win95 redirector files need checking if Win32Platform = VER_PLATFORM_WIN32_WINDOWS then CheckRedirector; {$endif} CheckRegistryIsAcceptable; CheckLocalShare; end; It is important to remember that these operating system settings must be set on all machines either running BDE applications or containing BDE data. Similarly the BDE must have Local Share set to True on all installations. If a machine gets upgraded, or has the operating system re-installed, or has a new version of the BDE set up, some of these settings may need to be fixed again. The same applies if a new machine is added to the network. 619
Of course if this approach is taken, you also need to remember to set the settings appropriately on any file server machines used to store your data files, but which do not have any Delphi applications running on them. One important point on this whole subject is that it helps enormously if you educate and train your users. It is not unheard of for users to pull the power cable from their PCs to plug into a kettle, or to terminate applications with the Ctrl+Alt+Del three-fingered salute. See References below for other references which discuss some of these issues, sometimes in the context of totally non-Inprise, non-Paradox systems. One final point to make is that if your Paradox tables do get corrupted, you can make use of TUtility.DLL to try and repair them. A BDE 5 compatible version of this DLL can be found at www.inprise.com/devsupport/bde/utilities.html. You can also find a sample Delphi application there that can be used against arbitrary Paradox tables to try and fix them. Back to Top References Inprise Technical Information sheet TI3342 which offers more views on the Paradox corruption issue; note the file name is case sensitive: www.inprise.com/devsupport/bde/ti_list/TI3342.html Q148367 discusses the Win95 redirector bug; to read any of the other MS KnowledgeBase articles you can build up a URL using the same pattern as this one: support.microsoft.com/support/kb/articles/q148/3/67.asp Discussion of the Windows 95 redirector problem and NT opportunistic locking: www.turbopower.com/newsletters/0798/default.asp Discussion of Novell settings to avoid data loss: www.daccess.com/Support/Integrating_DF_with_NetWare.htm cc:Mail technical paper 144293 discussing various Windows registry settings that need to be set correctly to avoid data loss: orionweb.lotus.com/sims/c8f6_546.htm cc:Mail technical paper 139714 discussing preferred NetWare settings to avoid data loss; if you use NetWare you should read this as it describes many preferred NetWare configuration settings not discussed here: orionweb.lotus.com/sims/8a76_546.htm Article 2917002 describes a Novell read caching problem; article 2911461 describes potential data loss issues using Windows 95 against a NetWare 3.11 server: support.novell.com/search/kb_index.htm Back to Top The Delphi Clinic, Issue 17: Paradox Table Corruption
620
Question: I am developing a Paradox table application in Delphi 1 to run on Windows For Workgroups (WFWG) 3.11. During testing we have had various occurrences of records being lost, indexes out of date and indexes corrupted. Why might this be caused, and what can I do to avoid it? The network server PC is also used as a client PC running the .EXE and is known as network drive L to all other PCs. We've added a SUBST L: C:\ to that PC's AUTOEXEC.BAT, so it's IDAPI.CFG can also refer to L:\... for its net directory and database path. We're running WFWG in enhanced mode and so VSHARE.386 is running, so do I really need SHARE? We've tried running the .EXE on Windows 95 and besides the above problem it seems to run OK. Are there any other things we should consider/change before running it live on Windows 95? Answer: Below are various recommendations you might find in various places and comments about them all, amassed from a number of Paradox and BDE experts. Thanks to Steve Axtell of Borland's European Technical Team, Phil Goulson of the UK Delphi Developer's Group, John O'Connell, Mike Orriss and Eryk Bottomley for their input. A frequent cause for any of the above data corruption problems is the premature termination (power loss, or PC reset possibly forced upon the user by a program hang) of a program accessing a Paradox table. Lost records will cause the index to get out of sync with the data, which will at some stage be followed by index corruption. In some cases, bad programming is the cause of the problem. It is important to ensure that records are posted. When you terminate a program, it is the responsibility of the developer to Post all un-posted records before the program is terminated, otherwise you will get a `record loss' problem. This could be achieved by applying something like the following statement for all your table objects in each form's OnClose event handler: if TableObject.State in dsEditModes then TableObject.Post; The program development phase is the time when most tables start inheriting corruption (caused by the developer resetting programs from time to time) which may not become evident until the system is deployed. One possible way of overcoming the problem is to rebuild the indexes periodically. This can be done with a table restructure (using the BDE DbiDoRestructure function, or with the Database Desktop) and will often resolve index corruption. It can also be done using TUtility, which can resolve data corruption (TUtility comes with the full version of Paradox and was included on the disk with Issue 5 of The Delphi Magazine, or look on the CompuServe BDEVTOOLS forum). An alternative, and perhaps more foolproof, way of fixing broken indexes would be to write a routine which physically erases the indexes (with DeleteFile) and recreates the indexes from scratch with the BDE DbiRegenIndexes call (which relies on the table being opened exclusively). If your tables use referential integrity, then deleting the indexes may cause a problem due to a special checksum in the table header. In these cases you will need to delete the indexes and the .VAL file and use DbiDoRestructure to regenerate all tables that are involved in the referential integrity relationship. In the BDE Configuration application on all PCs that will run the program, set Local Share to True on the System page. This ensures that lock files are written 621
to the local hard disk, thereby ensuring that applications on other machines will be able to find the lock files. This should only be necessary for the machine where the data resides, however the general opinion is that it should always be turned on, provided you have file sharing functionality loaded with either SHARE or VSHARE. On peer to peer networks, the default setting of Local Share is a common cause of data loss. When False, Local Share instructs the BDE to assume that all access to tables on `local drives' (a peer to peer LAN counts as `local') will occur via the same instance of the BDE in memory, it therefore fails in a number of situations including: ·
Peer to peer LANs;
·
Two applications running in different memory spaces under Windows NT (and maybe OS/2);
·
Running a 16-bit BDE app and a 32-bit BDE app on the same machine in Windows 95 or NT.
SUBST will disable 32-bit file access and may therefore slow the machine down. If 32-bit file access is disabled, VSHARE won't be loaded. Some would say that SUBST isn't very safe especially when used with Windows 95 and is probably provided by Microsoft for compatibility with old DOS applications. Apparently, there are little corners where Paradox does not function correctly and some people don't trust the command to work for all flavours of Wintel operating systems. As an aside, it has been observed that the latest 16 bit BDE (2.52, which ships with Paradox 7 for Windows 3.1x) has problems with Auto Refresh not occurring in Paradox 7 for Windows 3.1x on the server when the server is a Windows 95 machine. This may have ramifications for Delphi users. Recent revisions of the BDE allow different Sessions/Users to reference the PDOXUSRS.NET file using different drive letters so long as the remainder of the path is identical. Since the server has shared its root directory there is therefore no need to use SUBST and put up with the associated drawbacks: simply set the server machine's IDAPI.CFG Net Dir setting (on the Drivers page in the PARADOX driver settings) to C:\MYDIR and the workstations' Net Dir to L:\MYDIR. You can do this in code by assigning a value to Session.NetFileDir if you want to avoid editing IDAPI.CFG. Note that using the root directory for the NET file can confuse certain BDE revisions. It is advisable to avoid this as a matter of policy even though the current revision seems happy with it. Also, if the user of the server does not want to share the entire C drive, it might be better to create a small partition for the Net Dir location. If Local Share is True then the BDE will detect an incorrect NetFileDir and refuse to access the tables. If an incorrect assignment here is causing corruption then Local Share is still the real culprit. On the Aliases page of the BDE Configuration application on all PCs in that will run the program, ensure the alias's Path points to the same network data directory.
622
Ensure that all users have their own private directory, preferably local. This is set with the Session object's PrivateDir property. Note that the online help specifies that if there will be multiple instances of the program running simultaneously on any one machine you should make sure each instance is given a unique path to avoid interference between temporary files from the different instances. Call DbiSaveChanges after each table post (done simplest by putting the call in the table's AfterPost event handler). This should be unnecessary if the local share option has been set properly. When the BDE knows that the Paradox table is on a network, each record is saved to disk automatically. Therefore, DbiSaveChanges may only be necessary for saving local tables. There are two cases where a call to DbiSaveChanges can be a definite life saver: when you empty a table and when you restructure/pack a table (using DbiDoRestructure); this is because the actual table file is deleted and recreated but isn't necessarily committed to disk. Check your other software/hardware caching as delayed writes are not good news on a network. Instead of repeated calls to DbiSaveChanges, call DbiUseIdleTime in the Application's OnIdle event handler (also set the event handler's Done parameter to True). A call to DbiUseIdletime writes one dirty buffer to disk. Putting it in the OnIdle event means buffers will be written whenever your program is waiting for user input. Avoid using both DbiSaveChanges and DbiUseIdletime as they both do the same thing and so you'll be causing excessive function calls. This routine is becoming very popular as a general alternative to DbiSaveChanges, as it requires much less coding to use. Have SHARE loaded with parameters of /F:4096 /L:40 as recommended by Borland. This advice is generally for Windows 3.10 users only. VSHARE from Windows for Workgroups supersedes SHARE: it's much better, although there is a caveat. Apparently VSHARE is a 32-bit driver which won't work with 16-bit disk controllers/drives which are present on non-local bus IDE PCs. In those circumstances, excluding SHARE from AUTOEXEC.BAT, and enabling VSHARE from Control Panel causes an error from IDAPI indicating SHARE isn't loaded. Write a message handler for wm_EndSession in your main form class. Delphi 1 doesn't automatically handle this message which is sent when Windows is shut down by the user (although Delphi 2 does). Consequently, if a Delphi app is running when Windows is terminated, it won't be closed properly, and so BDE buffers may remain unwritten. It would be good practice to call Halt on receipt of a wm_EndSession message handler. Halt is not normally an advisable way to close a program, usually we use Application.Terminate. However that operates by posting (as opposed to sending) a wm_Quit message and so won't get around to doing what it needs to before Windows is gone. Halt causes exit procedures to be called, including the one in the DB unit which frees the Session object, thereby closing down the BDE in a proper fashion. The code below shows part of a form unit which takes up some of these suggestions. This code is from the project LOSS.DPR which can be found in the PARADOX.ZIP file. TForm1 = class(TForm) ... public procedure DoIdle(Sender: TObject; var Done: Boolean); 623
{$ifdef VER80} procedure WMEndSession(var Msg: TWMEndSession); message wm_EndSession; {$endif} end; ... uses DbiProcs; ... procedure TForm1.FormCreate(Sender: TObject); begin Application.OnIdle := DoIdle; end; procedure TForm1.DoIdle(Sender: TObject; var Done: Boolean); begin { Each idle period, write a dirty buffer to disk } DbiUseIdleTime; Done := True; end; procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); var Loop: Integer; begin {Generic way of ensuring all table changes are saved when form is closed} for Loop := 0 to ComponentCount - 1 do if Components[Loop] is TDataSet then with TDataSet(Components[Loop]) do if State in dsEditModes then Post; end; {$ifdef VER80} procedure TForm1.WMEndSession(var Msg: TWMEndSession); begin { If session is ending, call Halt to get exit routines executed. The DB unit's exit routine frees the Session object, which will unload the BDE, flushing any unsaved changes to disk } if Msg.EndSession then Halt; end; {$endif} Back to Top
624
How to change NET DIR programmatically so it persists Abstract:Writing values into the BDE configuration file requires only a few function calls. How to change NET DIR programmatically so it persists Writing values into the BDE configuration file requires only a few function calls. by Steven Randolph Programs accessing Paradox tables must also access a special control file named PDOXUSRS.NET. When multiple programs, whether running on one computer or on multiple computers, access the same Paradox table(s), each program must also access the same PDOXUSRS.NET file. Therefore, if multiple computers are running the programs, the PDOXUSRS.NET file must be located in a shared directory. Usually, the location of this file is configured on each computer by running the BDE configuration utility program, BDEADMIN.EXE. The NET DIR parameter indicates where the PDOXUSRS.NET file is located. That NET DIR parameter is associated with the Paradox driver generally, not with any particular alias. THE PROBLEM AND THE GOAL I recently inherited the maintenance of a production system consisting of several client programs running on multiple computers. Each program accesses Paradox tables through multiple aliases. Each alias "points" to a different directory on a host computer. Each program also reads and writes flat files in a large number of directories on one of two hosts. One of the first improvements that I determined to make was to set up a test environment, which my predecessor had never done. I wanted a single host computer for the test environment to have its own tables, its own set of directories, and its own PDOXUSRS.NET file. In other words, any client computer designated for testing should be isolated from the production host environment. I also wanted to make the minimum of changes to the existing production programs to allow for this. While it might seem that the entire task could be accomplished by changing the drive mappings on the test computers, which can be done programmatically, in my case that was not practical. Some of the same drive mappings are used for other purposes in our environment. Besides, this would have required me to create the same rather arcane relative directory structure on the test host as exists on the two production hosts, and I did not wish to do that. So I decided to create a utility program that would accomplish several tasks at once. In this program, the user could click a button labeled "Configure for Test" or one labeled "Configure for Production" and the configuration would be made immediately on that computer. It would create or modify registry entries specifying the various directories to access for the flat files. This part was easy to program, using the TRegistry component. The program also needed to configure 625
the several Paradox aliases to point to either test or production directories. This was also no problem; I merely used the DbiAddAlias and DbiDeleteAlias functions, both of which are well documented. The only variable parameter for the creation of the Paradox aliases was the directory location, and everything else was left at default values. Setting the last parameter in the DbiAddAlias call to "true" caused the alias to be recorded permanently in the BDE configuration file for that computer. However, doing the same for the NET DIR value was not so easy! There is a documented technique for changing the NET DIR directory programmatically. It is documented in Borland’s TI-15230. This technique uses the DbiSetProp BDE function. Unfortunately, DbiSetProp changes the NET DIR directory for the current session only! That is, it does not cause the new value for NET DIR to be written into the BDE configuration file. It took quite a bit of searching and some experimentation to determine how to do that. Finally I figured out that the code in the following block does the trick. THE SOLUTION First is the C++ Builder code. You might want to do the error trapping a little differently, or put in some messages, in a real program. This is just an example of the basic technique. if(DbiInit(NULL) == DBIERR_NONE) { hDBICur hCur = NULL; if(DbiOpenCfgInfoList(NULL, dbiREADWRITE, cfgPersistent, "\\DRIVERS\\PARADOX\\INIT", hCur) == DBIERR_NONE) { if(DbiSetToBegin(hCur) == DBIERR_NONE) { CFGDesc Config; for(;;) // for ever { if(DbiGetNextRecord(hCur, dbiWRITELOCK, &Config, NULL) != DBIERR_NONE) break; if(strcmpi(Config.szNodeName, "NET DIR") == 0) { strcpy(Config.szValue, "servervolumemydir"); DbiModifyRecord(hCur, &Config, true); break; } } } } DbiExit(); } The Delphi version of this process is similar. var hCur : hDBICur; Config : CFGDesc; ContinueIt: boolean; 626
... if DbiInit(nil) = DBIERR_NONE then begin hCur := nil; if DbiOpenCfgInfoList(nil, dbiREADWRITE, cfgPersistent, '\\DRIVERS\\PARADOX\\INIT', hCur) = DBIERR_NONE then begin if DbiSetToBegin(hCur) = DBIERR_NONE then begin ContinueIt := true; while ContinueIt do begin if(DbiGetNextRecord(hCur, dbiWRITELOCK, @Config, nil) <> DBIERR_NONE) then ContinueIt := false else if StrIComp(Config.szNodeName, 'NET DIR') = 0 then begin StrCopy(Config.szValue, 'servervolumemydir'); DbiModifyRecord(hCur, @Config, true); ContinueIt := false end; end; end; end; DbiExit(); end; If you use this code, you would want to substitute your own value for the path name to be copied into Config.szValue, of course. In my real program, the value was supplied in a string variable, the contents being determined by whether the user had clicked the "test" button or the "production" button. The value you submit can contain a mapped drive letter, if you are quite sure those mappings will never change; but I always prefer to use the universal naming convention. CAVEATS My experimentation suggests that it is not strictly necessary to close other BDE programs prior to running this code. Just the same, it would probably be a good idea to do so. In any case, a BDE program will not take notice of a changed value in the BDE configuration file until it is started subsequent to the change. You may also notice this while debugging your utility program in the IDE, if you happen to change the value and then try to re-read it without calling DbiExit and DbiInit again. (In the example code, this should not be a problem because DbiExit is executed right after the change is written.) Another important consideration is to make sure that the path you submit to be the new NET DIR actually exists. The BDE will, at runtime, create the PDOXUSRS.NET file if it does not already exist in the NET DIR directory; but the BDE will not create the NET DIR directory for you. If this happens, you will get a runtime error "Network Initialization Failed" when your program tries to initialize or access a Paradox object. 627
You should also remember that if you have programs that change their own NET DIR locations at runtime, using either the DbiSetProp function or the NetFileDir property of a TSession component, this will override the NET DIR value in the configuration file. All of the programs I inherited just use whatever value the configuration file supplies. Finally, you should note that the same technique could be used to change other parameters for the Paradox driver, or many parameters for other drivers. You would have to be very careful about which values you supply, however. Changing the NET DIR directory is relatively safe.
628
BDE API Examples (DbiOpenCfgInfoList) Returns a handle to an in-memory table listing all the nodes in the configuration file accessible by the specified path. WARNING: Be extremely careful when altering the IDAPI.CFG configuration file. Make absolutely sure that all options and parameters are correct or corruption of the configuration file can, and more than likely, occur. Example 1: Retrieve a particular value from the IDAPI.CFG configuration file. This example uses the following input: Edit1.Text := GetConfigParameter(PARADOXLEVEL, @Count); NOTE: Param (in this case PARADOXLEVEL) must be a string that contains the path to the node and the node item separated by a semi-colon. At the bottom of this page are some of the more popular paths and items that are declared as constants for use with all these examples. function GetConfigParameter(Param: string; Count: pword): string; var hCur: hDBICur; rslt: DBIResult; Config: CFGDesc; Path, Option: string; Temp: array[0..255] of char;
begin Result := ''; hCur := nil; if Count <> nil then Count^ := 0; try if Pos(';', Param) = 0 then raise EDatabaseError.Create('Invalid parameter passed to function. There must '+ 'be a semi-colon delimited sting passed'); 629
Path := Copy(Param, 0, Pos(';', Param) - 1); Option := Copy(Param, Pos(';', Param) + 1, Length(Param) - Pos(';', Param)); Check(DbiOpenCfgInfoList(nil, dbiREADONLY, cfgPERSISTENT, StrPCopy(Temp, Path), hCur)); Check(DbiSetToBegin(hCur)); repeat rslt := DbiGetNextRecord(hCur, dbiNOLOCK, @Config, nil); if rslt = DBIERR_NONE then begin if StrPas(Config.szNodeName) = Option then Result := Config.szValue; if Count <> nil then Inc(Count^); end else if rslt <> DBIERR_EOF then Check(rslt); until rslt <> DBIERR_NONE; finally if hCur <> nil then Check(DbiCloseCursor(hCur)); end; end; Example 2: Set a particular value in the IDAPI.CFG configuration file (16-Bit Only) and (32-Bit, BDE v4.51 and later). NOTE: Do not use this procedure version if you are using BDE v4.50 and earlier (See Example 3 below) This exmaple uses the following inupt: SetConfigParameter(LOCALSHARE, 'TRUE') 630
NOTE: Param (in this case LOCALSHARE) must be a string that contains the path to the node and the node item separated by a semi-colon. At the bottom of this page are some of the more popular paths and items that are declared as constants for use with all these examples. procedure SetConfigParameter(Param: string; Value: string); var hCur: hDBICur; rslt: DBIResult; Config: CFGDesc; Path, Option: string; Found: boolean; Temp: array[0..255] of char;
begin hCur := nil; Found := False; try if Pos(';', Param) = 0 then raise EDatabaseError.Create('Invalid parameter passed to function. There must '+ 'be a semi-colon delimited sting passed'); Path := Copy(Param, 0, Pos(';', Param) - 1); Option := Copy(Param, Pos(';', Param) + 1, Length(Param) - Pos(';', Param)); Check(DbiOpenCfgInfoList(nil, dbiREADWRITE, cfgPERSISTENT, StrPCopy(Temp, Path), hCur)); repeat rslt := DbiGetNextRecord(hCur, dbiNOLOCK, @Config, nil); if rslt = DBIERR_NONE then begin if StrPas(Config.szNodeName) = Option then 631
begin StrPCopy(Config.szValue, Value); Check(DbiModifyRecord(hCur, @Config, FALSE)); Found := True; break; end; end else if rslt <> DBIERR_EOF then Check(rslt); until rslt <> DBIERR_NONE; if Found = False then raise EDatabaseError.Create(Param + ' entry was not found in configuration file');
finally if hCur <> nil then Check(DbiCloseCursor(hCur)); end; end; Example 3: Set a particular value in the IDAPI.CFG configuration file (32-Bit Only; All Versions). NOTE: You must use this procedure version if you are using BDE v4.50 and earlier This exmaple uses the following inupt: SetConfigParameter2(LOCALSHARE, 'TRUE') NOTE: Param (in this case LOCALSHARE) must be a string that contains the path to the node and the node item separated by a semi-colon. At the bottom of this page are some of the more popular paths and items that are declared as constants for use with all these examples. procedure SetConfigParameter2(Param: string; Value: string);
632
var hCfg: hDBICfg; Config: SYSConfig; Path, Option: string; ParamCount, I: word; pFields, pFld: pFLDDesc; pRecBuf, pRec: pBYTE; Found, SelfInitialized: boolean; rslt: DBIResult;
begin {$Ifdef WIN32} hCfg := nil; pFld := nil; pRec := nil; Found := False; SelfInitialized := False; try if Pos(';', Param) = 0 then raise EDatabaseError.Create('Invalid parameter passed to function. There must '+ 'be a semi-colon delimited sting passed'); Path := Copy(Param, 0, Pos(';', Param) - 1); Option := Copy(Param, Pos(';', Param) + 1, Length(Param) - Pos(';', Param));
rslt := DbiGetSysConfig(Config); if rslt <> DBIERR_NONE then begin if rslt = DBIERR_NOTINITIALIZED then // Engine not initialized error... begin SelfInitialized := True; 633
DbiInit(nil); Check(DbiGetSysConfig(Config)); end else Check(rslt); end; (* DbiOpenConfigFile is defined as such: function DbiOpenConfigFile ( pszDirPath bCreate
: PChar; : Bool;
var hCfg
: hDBICfg
{ Open/Create configuration } { Directory } { TRUE to create/overwrite } { Handle to config }
): DBIResult stdcall; *) Check(DbiOpenConfigFile(Config.szIniFile, FALSE, hCfg));
(* DbiCfgGetRecord is defined as such: function DbiCfgGetRecord ( hCfg
: hDBICfg;
pszCfgPath var iFields
: PChar; : Word;
pfldDesc pRec
: pFLDDesc; : Pointer
{ Get a record } { Config Handle/NULL } { Path } { Returned nbr of fields } { Field descriptors } { Field values }
): DBIResult stdcall; *) { Call it without the field and record buffer to get the count... } Check(DbiCfgGetRecord(hCfg, PChar(Path), ParamCount, nil, nil));
pFields := AllocMem(ParamCount * sizeof(FLDDesc)); pFld := pFields; 634
pRecBuf := AllocMem(10000); pRec := pRecBuf;
{ Get the node values... } Check(DbiCfgGetRecord(hCfg, PChar(Path), ParamCount, pFields, pRecBuf));
for I := 0 to ParamCount - 1 do begin if pFields^.szName = Option then begin StrPCopy(PChar(pRecBuf), Value);
(* DbiCfgModifyRecord is defines as such: function DbiCfgModifyRecord ( hCfg
: hDBICfg;
pszCfgPath iFields pfldDesc pRec
: PChar;
: Word; : pFLDDesc; : Pointer
{ Modify a record } { Config Handle/NULL } { Path } { Nbr of fields } { Field descriptors } { Data values }
): DBIResult stdcall; *) Check(DbiCfgModifyRecord(hCfg, PChar(Path), ParamCount, pFld, pRec));
Found := True; end; Inc(pFields); Inc(pRecBuf, 128); 635
end; if Found = False then raise EDatabaseError.Create(Param + ' entry was not found in configuration file');
finally if pFld <> nil then FreeMem(pFld); if pRec <> nil then FreeMem(pRec); if hCfg <> nil then
(* DbiCloseConfigFile is defined as such: function DbiCloseConfigFile ( var hCfg bSave bDefault bSaveAs16
: hDBICfg; : Bool; : Bool; : Bool
{ Close the config file } { Handle } { To save the changes } { To make this file the default } { To save as a 16-bit config file }
): DBIResult stdcall; *) { Close and save the config file... } Check(DbiCloseConfigFile(hCfg, TRUE, TRUE, FALSE)); if SelfInitialized = True then DbiExit; end; {$Else} raise EDatabaseError.Create('Non supported function in 16 bit'); {$EndIf} 636
end; Special pre-defined constants for use with the above exmaples const { Here are the parameters used to pass into the cfg functions. These are only a small portion of what types can be passed in. You need to call DbiOpenCfgInfoList with '\' into pszCfgPath to get all possible options if it is not foend below. }
{ Paradox Driver Settings... } PARADOXNETDIR = '\DRIVERS\PARADOX\INIT\;NET DIR'; PARADOXVERSION = '\DRIVERS\PARADOX\INIT\;VERSION'; PARADOXTYPE = '\DRIVERS\PARADOX\INIT\;TYPE'; PARADOXLANGDRIVER = '\DRIVERS\PARADOX\INIT\;LANGDRIVER'; PARADOXLEVEL = '\DRIVERS\PARADOX\TABLE CREATE\;LEVEL'; PARADOXBLOCKSIZE = '\DRIVERS\PARADOX\TABLE CREATE\;BLOCK SIZE'; PARADOXFILLFACTOR = '\DRIVERS\PARADOX\TABLE CREATE\;FILL FACTOR'; PARADOXSTRICTINTEGRITY = '\DRIVERS\PARADOX\TABLE CREATE\;STRICTINTEGRITY';
{ dBASE Driver Settings... } DBASEVERSION = '\DRIVERS\DBASE\INIT\;VERSION'; DBASETYPE = '\DRIVERS\DBASE\INIT\;TYPE'; DBASELANGDRIVER = '\DRIVERS\DBASE\INIT\;LANGDRIVER'; DBASELEVEL = '\DRIVERS\DBASE\TABLE CREATE\;LEVEL'; DBASEMDXBLOCKSIZE = '\DRIVERS\DBASE\TABLE CREATE\;MDX BLOCK SIZE'; DBASEMEMOFILEBLOCKSIZE = '\DRIVERS\DBASE\TABLE CREATE\;MEMO 637
FILE BLOCK SIZE';
{ InterBase Driver Settings... } INTERBASESERVERNAME = '\DRIVERS\INTRBASE\DB OPEN\;SERVER NAME'; INTERBASEUSERNAME = '\DRIVERS\INTRBASE\DB OPEN\;USER NAME'; INTERBASEOPENMODE = '\DRIVERS\INTRBASE\DB OPEN\;OPEN MODE'; INTERBASESCHEMACACHESIZE = '\DRIVERS\INTRBASE\DB OPEN\;SCHEMA CACHE SIZE'; INTERBASELANGDRIVER = '\DRIVERS\INTRBASE\DB OPEN\;LANGDRIVER'; INTERBASESQLQRYMODE = '\DRIVERS\INTRBASE\DB OPEN\;SQLQRYMODE'; INTERBASESQLPASSTHRUMODE = '\DRIVERS\INTRBASE\DB OPEN\;SQLPASSTHRU MODE'; INTERBASESCHEMACACHETIME = '\DRIVERS\INTRBASE\DB OPEN\;SCHEMS CACHE TIME'; INTERBASEMAXROWS = '\DRIVERS\INTRBASE\DB OPEN\;MAX ROWS'; INTERBASEBATCHCOUNT = '\DRIVERS\INTRBASE\DB OPEN\;BATCH COUNT'; INTERBASEENABLESCHEMACACHE = '\DRIVERS\INTRBASE\DB OPEN\;ENABLE SCHEMA CACHE'; INTERBASEENABLEBCD = '\DRIVERS\INTRBASE\DB OPEN\;ENABLE BCD'; INTERBASEBLOBSTOCACHE = '\DRIVERS\INTRBASE\DB OPEN\;BLOBS TO CACHE'; INTERBASEBLOBSIZE = '\DRIVERS\INTRBASE\DB OPEN\;BLOB SIZE'; INTERBASEVERSION = '\DRIVERS\INTRBASE\INIT\;VERSION'; INTERBASETYPE = '\DRIVERS\INTRBASE\INIT\;TYPE'; INTERBASEDLL = '\DRIVERS\INTRBASE\INIT\;DLL'; INTERBASEDRIVERFLAGS = '\DRIVERS\INTRBASE\INIT\;DRIVER FLAGS'; INTERBASEDLL32 = '\DRIVERS\INTRBASE\INIT\;DLL32'; INTERBASETRACEMODE = '\DRIVERS\INTRBASE\INIT\;TRACE MODE';
638
{ Oracle Driver Settings... } ORACLEBATCHCOUNT = '\DRIVERS\ORACLE\DB OPEN\;BATCH COUNT'; ORACLEENABLEBCD = '\DRIVERS\ORACLE\DB OPEN\;ENABLE BCD'; ORACLEENABLEINTEGERS = '\DRIVERS\ORACLE\DB OPEN\;ENABLE INTEGERS'; ORACLEENABLESCHEMACACHE = '\DRIVERS\ORACLE\DB OPEN\;ENABLE SCHEMA CACHE'; ORACLELANGDRIVER = '\DRIVERS\ORACLE\DB OPEN\;LANGDRIVER'; ORACLELISTSYNONYMS = '\DRIVERS\ORACLE\DB OPEN\;LIST SYNONYMS'; ORACLEMAXROWS = '\DRIVERS\ORACLE\DB OPEN\;MAX ROWS'; ORACLENETPROTOCOL = '\DRIVERS\ORACLE\DB OPEN\;NET PROTOCOL'; ORACLEOPENMODE = '\DRIVERS\ORACLE\DB OPEN\;OPENMODE'; ORACLEROWSETSIZE = '\DRIVERS\ORACLE\DB OPEN\;ROWSET SIZE'; ORACLESCHEMACACHEDIR = '\DRIVERS\ORACLE\DB OPEN\;SCHEMA CACHE DIR'; ORACLESCHEMACACHESIZE = '\DRIVERS\ORACLE\DB OPEN\;SCHEMA CACHE SIZE'; ORACLESCHEMACACHETIME = '\DRIVERS\ORACLE\DB OPEN\;SCHEMA CACHE TIME'; ORACLESERVERNAME = '\DRIVERS\ORACLE\DB OPEN\;SERVER NAME'; ORACLESQLPASSTHRUMODE = '\DRIVERS\ORACLE\DB OPEN\;SQLPASSTHRU MODE'; ORACLESQLQUERYMODE = '\DRIVERS\ORACLE\DB OPEN\;SQLQRYMODE'; ORACLEUSERNAME = '\DRIVERS\ORACLE\DB OPEN\;USER NAME'; ORACLEDLL = '\DRIVERS\ORACLE\INIT\;DLL'; ORACLEDLL32 = '\DRIVERS\ORACLE\INIT\;DLL32'; ORACLEDRIVERFLAGS = '\DRIVERS\ORACLE\INIT\;DRIVER FLAGS'; ORACLETRACEMODE = '\DRIVERS\ORACLE\INIT\;TRACE MODE'; ORACLETYPE = '\DRIVERS\ORACLE\INIT\;TYPE'; ORACLEVENDORINIT = '\DRIVERS\ORACLE\INIT\;VENDOR INIT'; 639
ORACLEVERSION = '\DRIVERS\ORACLE\INIT\;VERSION';
{ MSACCESS Driver Settings... } MSACCEESSLANGDRIVER = '\DRIVERS\MSACCESS\DB OPEN\;LANGDRIVER'; MSACCESSDATABASENAME = '\DRIVERS\MSACCESS\DB OPEN\;DATABASE NAME'; MSACCESSUSERNAME = '\DRIVERS\MSACCESS\DB OPEN\;USER NAME'; MSACCESSOPENMODE = '\DRIVERS\MSACCESS\DB OPEN\;OPEN MODE'; MSACCESSSYSTEMDATABASE = '\DRIVERS\MSACCESS\DB OPEN\;SYSTEMDATABASE'; MSACCESSVERSION = '\DRIVERS\MSACCESS\INIT\;VERSION'; MSACCESSTYPE = '\DRIVERS\MSACCESS\INIT\;TYPE'; MSACCESSDLL32 = '\DRIVERS\MSACCESS\INIT\;DLL32'; MSACCESSDRIVERFLAGS = '\DRIVERS\MSACCESS\INIT\;DRIVER FLAGS'; MSACCESSTRACEMODE = '\DRIVERS\MSACCESS\INIT\;TRACE MODE';
{ System Initialization Settings... } AUTOODBC = '\SYSTEM\INIT\;AUTO ODBC'; DATAREPOSITORY = '\SYSTEM\INIT\;DATA REPOSITORY'; DEFAULTDRIVER = '\SYSTEM\INIT\;DEFAULT DRIVER'; LANGDRIVER = '\SYSTEM\INIT\;LANGDRIVER'; LOCALSHARE = '\SYSTEM\INIT\;LOCAL SHARE'; LOWMEMORYUSAGELIMIT = '\SYSTEM\INIT\;LOW MEMORY USAGE LIMIT'; MAXBUFSIZE = '\SYSTEM\INIT\;MAXBUFSIZE'; MAXFILEHANDLES = '\SYSTEM\INIT\;MAXFILEHANDLES'; 640
MEMSIZE = '\SYSTEM\INIT\;MEMSIZE'; MINBUFSIZE = '\SYSTEM\INIT\;MINBUFSIZE'; SHAREDMEMLOCATION = '\SYSTEM\INIT\;SHAREDMEMLOCATION'; SHAREDMEMSIZE = '\SYSTEM\INIT\;SHAREDMEMSIZE'; SQLQRYMODE = '\SYSTEM\INIT\;SQLQRYMODE'; SYSFLAGS = '\SYSTEM\INIT\;SYSFLAGS'; VERSION = '\SYSTEM\INIT\;VERSION';
641
Table bileşeninin OnEditError kısmına aşağıdaki kodu koyarak kullanıcıyı yakalayabilirsin. Var ErrInfo:DBIErrInfo; Bilgi:Array [1..2] Of string; begin DbiGetErrorInfo (False,ErrInfo); If ErrInfo.iError=10241 Then begin SetLength (Bilgi[1],DBIMAXMSGLEN); DbiGetErrorConText (EcTableName, (PChar(Bilgi[1]))); SetLength (Bilgi[1], StrLen(Pchar(Bilgi[1]))); SetLength (Bilgi[2],DBIMAXMSGLEN); DbiGetErrorConText (EcUserName, (PChar(Bilgi[2]))); SetLength (Bilgi[2], StrLen(Pchar(Bilgi[2]))); ShowMessage (Format('Erişim hatası : %s dosyası, %s tarafından kullanılıyor.',[ExtractFileName (Bilgi[1]),Bilgi[2]])); Abort; end; end;
Kayıtlardaki değişikliği tampon bellekte bekletmeden anında yazmak için şu kodu kullanabilirsiniz. Öncelikle Uses satırına BDE'yi ekleyiniz. Procedure TdataModule1.Table3AfterPost(DataSet:TDataSet); Begin DBISaveChanges( (DataSet as TBDEDataSet).Handle); End; Çalışmalarınızda başarılar... delphiMan
19 Ara 2001 22:57
Table1.FlushBuffers; ı kullan fazlasıylaişini görecektir. Tüm Tablolarında CacheUpdade metodunu kullan. yapacağın şunlar: tabloların cacheUpdates'ini True yap ayrıca kayıt yapacağın anda table1.ApplyUpdates; table1.CommitUpdates; yaz applyupdates kaydı yapar CommitUpdates'de kayıt yapılıdıktan sonra Cache belleği boşaltır.
642
SQL Uzerine (Açıklamalı - Örnekli) - 1
0
29 Haz 2002 14:17 Eklendi
1.TEK TABLODAN SORGULAMALAR: SELECT * FROM tablo ÖRNEK: Bütün bilgileri personel tablosundan koşulsuz olarak listele. SELECT * FROM personel ÖRNEK: Personel tablosundan SEÇ komutuyla istenen sütun adlarını belirt. SELECT sicil,sosy_g_no,ad,soyad,dog_tar, sicil,sosy_g_no,ad,soyad,dog_tar, adres,cins,brüt,böl_no,yön_s_g_n FROM personel; ÖRNEK: Personel tablosundan istenen sütün başliklarini listele. SELECT sicil,ad,soyad,brüt FROM personel; DISTINCT (Tekrarsız) TANIM: SQL’de tablo içinde birbirinin aynı datalar bulunabilir.Aynı satırların listeleme esnasında bir kez yazılması için Distinct sözcüğünü kullan. ÖRNEK: Par _sat dosyasından sat_no’lar tekrarsız olarak listelenecektir. SELECT DISTINCT sat_no FROM par_sat; 2.TABLO BİLGİLERİNİN SIRALANMIŞ OLARAK LİSTELENMESİ: ORDER BY (Sırasıyla) TANIM:Tablodaki sütunlardan ,belirli bir sütuna göre listelemek için SELECT komutuna , ORDER BY eklenir. ÖRNEK: Personel dosyasından,sicil,ad,soyad,brüt sütunlarını seç ve brüt(maaşa)göre büyükten küçüğe sırala. SELECT sicil,ad,soyad,brüt FROM personel ORDER BY brüt ASC; DESC : Küçükten büyüğe sırala (A-Z) ASC : Büyükten küçüğe sırala (Z-A)
643
DESC yazılmazsa ASC direct kabul edilir (DEFAULT) 3.BİRDEN ÇOK ALANA GÖRE SIRALAMA: TANIM: Bir tablo içinde ,birden fazla sütundan aynı anda sıralamak için kullanılır. ÖRNEK )Personel dosyasından seçilen sütunlarını aynı anda hem ad,hem de otomatik olarak sıralar. SELECT sicil,ad,soyad,brüt FROM personel ORDER BY ad,brüt; ÖRNEK Personel tablosundan seçili sütunları öncelik adda olmak üzere (Z-A) adı bozmadan soyadı (A-Z) sıralı listeler. SELECT sicil,ad,soyad,brüt FROM personel ORDER BY ad ASC,soyad DESC, brüt ASC; veya; SELECT sicil,ad,soyad,brüt FROM personel ORDER BY ad,soyad DESC,brüt; KOŞULA BAGLI OLARAK LISTELEME: WHERE TANIM:Verilen koşulu saglayanlar listelenir.Iki veri birbiriyle karşilaştirilmaktadir. Karşilaştirilan verilerin türü ayni olmalidir. SELECT * FROM personel WHERE brüt > 5000000; KARŞILAŞTIRMA OPERATÖRLERI: OPERATÖR ANLAMI : < ...den daha küçük > ...den daha büyük = Eşit <= Küçük veya eşit >= Büyük veya eşit <> Eşit degil != Eşit degil !< ...den küçük değil !> ...den büyük değil 644
ÇEŞITLI VERI TIPLERI IÇIN BASIT SORGULAMALAR: 1.NÜMERİK VERİ TİPLERİ: ÖRNEK: Maaşi 8000000TL’den fazla olmayan personeli listele. SELECT * FROM personel WHERE brüt <= 8000000; 2.KARAKTER VERİ TİPLERİ (CHAR): Karakter çift veya tek tırnak ile gösterilir. ÖRNEK: Adı Ali olmayan personele ait kayıtları listele. SELECT * FROM personel WHERE ad <> “Ali”; 3.TARİH VERİ TİPİ: Tarih veri tipleri { } sembolleri içinde yazılır. ÖRNEK: Hangi personelin doğum tarihi 1960 yılından daha öncedir? SELECT * FROM personel WHERE dog_tar <={12/31/59}; 4.MANTIKSAL (LOJİK) VERİ TİPİ: Mantıksal veriler için mümkün olabilen sadece iki değer sözkonusudur.DOĞRU D(TRUE T) , YANLIŞ Y (FALSE F) ile simgelenir. ÖRNEK: Personel tablosunda personelin cinsiyetini belirten cins adlı alan mantıksal(logical) olarak tanımlanmıştır.Cinsiyeti erkek olanları D,kadın olanları y ile tanımlarsak erkek olanları listele. SELECT * FROM personel WHERE cins = .T.;
SQL Üzerine (Açıklamlı - Örnekli) - 2
0
29 Haz 2002 14:18 Eklendi
4.BİRDEN ÇOK KOŞULA DAYALI SORGULAMALAR: (NOT,AND,OR) TANIM:Mantıksal operatörlerin yardımı ile birden çok koşulun gerçekleştirmesine bağlı olarak ifade edilebilecek (karmaşık yada birleşik 645
koşullu listelemeleri gerçekleştirilmektedir.) AND (VE) ÖRNEK: Maaşı 5000000’dan fazla olan ve cinsiyeti erkek olan personelin listelenmesi istenir yani iki koşul verilmektedir ve ikisininde olmasi istenir. SELECT * FROM personel WHERE brüt >5000000 AND cins =.T.; NOT (DEĞİL) OR (VEYA) ÖRNEKLER: 1.Doğum tarihi 1960’dan önce olan maaşı 6000000 - 10000000 arasındaki bayan personelin listele. SELECT * FROM dog_tar < {01/01/60} AND brüt > = 6000000 AND brüt < =10000000 AND cins = .F.; 2.Satış bölümüyle muhasebe bölümündekiler kimlerdir? (Satış bölümünün böl_no’sunun 1 ve muhasebe bölümünün böl_no’sunun 2 olduğu varsayılmaktadır.) SELECT * FROM personel WHERE bol_no =1 OR bol_no = 2; 3.Bölümü Satış yada Muhasebe olamayan 1960’dan sonra doğmuş bayan personeli listele. 1.YAZILIM: SELECT * FROM personel WHERE NOT (böl_no =1 OR böl_no =2) AND dog_tar > ={01/01/60} AND cins =.F.; 2.YAZILIM: SELECT * FROM personel WHERE böl_no <> 1 AND böl_no <> 2 AND dog_tar > ={01/01/60} AND cins =.F.; BİR VERİ KÜMESİNDE ARAMA -IN OPERATÖRÜ IN (İÇİNDE) 646
“IN” operatörü NOT ile kullanılılabilir. ÖRNEK: Bölümü 1,2,3 olmayan personel kimlerden oluşmaktadir? SELECT * FROM personel WHERE bol_no NOT IN (1,2,3); ÖRNEK: Böl_no’su 1,2 yada 3 olan personeli listele. SELECT * FROM personel WHERE böl_no = 1 OR böl_no= 2 OR böl_no = 3; Bu örneğin IN ile yapılmış şekli daha kısadır. SELECT * FROM personel WHERE NOT böl_no IN (1,2,3); BETWEEN SORGULAMA SÖZCÜĞÜ: BETWEEN (ARASINDA) ÖRNEK: Maaşi 5- 10 milyon arasinda olan personel kimlerdir? SELECT * FROM personel WHERE brüt > =5000000 AND brüt < = 10000000; BETWEEN (ARASINDA) komutu ile daha kısa olacaktır. SELECT * FROM personel WHERE brüt BETWEEN 5000000 AND 10000000; KARAKTER TÜRÜ BİLGİ İÇİNDE ARAMA LIKE SÖZCÜĞÜ: TANIM ÖRNEĞİ: Adres sutunu içerisinde semt bölümüne ait ayrıca bir sutun olmadığını varsayarak semt adı adres sutunu içerisinde yer alır ve buradan da LIKE (BULUNAN) komutuyla adres sutunu içerisinde Taksim semtinde oturan personeli listele. SELECT * FROM personel WHERE adres LIKE ‘% TAKSİM %’ ; 647
Adres LIKE ‘%TAKSİM%’ ifadesi adres içinde her hangi bir yerde TAKSİM yazan yerde oturan personeli listeleyecektir. LIKE sözcüğünü ,alt çizgi (-) sembolü ile birlikte kullanmakta mümkündür. SELECT * FROM personel WHERE ad LIKE ‘Mehmet -----‘; Şekildeki komut ile ad alani “Mehmet “ ile başlayan ve ad alani uzunlugu 10 karakter olan isimlere sahip personeli listeleyecektir.”Mehmet Ali”,”Mehmet Can”- “Mehmetcik” gibi isimler listeleyecektir.Anlaşilacagi gibi - sembolü , tek karakterlik bir bilgiyi temsil etmektedir. 5.SQL’DE ARİTMETİKSEL İFADELER VE FONKSİYONLAR : KÜME FONKSİYONLARI: SUM FONKSİYONU: SUM (TOPLA) Fonksiyonun argümanı olarak belirtilen sütun ile ilişkili olana toplama işlemini gerçekleştirir. ÖRNEK: İşletmedeki personelin brüt maaşlar toplamı ne kadardır? SELECT SUM (brüt) FROM personel; AVG FONKSİYONU: AVG (ORTALA) Aritmetiksel ortalama (average) hesaplamak için kullanılır. SELECT AVG(brüt) FROM personel; MAX FONKSİYONU: MAX (EN ÜST) Tablo içinde ,belirtilen sutun (alan)içindeki en büyük değeri bulur. ÖRNEK: İşletme içindeki en yüksek maaş ne kadardır? SELECT MAX (brüt) FROM personel;
648
MIN FONKSİYONU: MIN (EN ALT) Tablo içinde,belirlenen sutun alan içindeki en küçük değeri bulur. ÖRNEK: İşletme içinde 4 Mayıs 1970’den önce doğanlar için,asgari ücret nedir? SELECT MIN(brüt) FROM personel WHERE dog_tar < {05/04/70}; COUNT FONKSİYONU: COUNT (SAY) Tablo içinde, her hangi bir sayma işlemi gerçekleştirmek için kullanilir. ÖRNEK:Ücreti 6000000’dan olan personel sayısı nedir? SELECT COUNT (*) FROM personel WHERE brüt > 6000000; COUNT (SAY) fonksiyonu DISTINCT (TEKRARSIZ)sözcüğü ile de kullanılır. ÖRNEK: Personel tablosunda mevcut personelin işletme içinde kaç tane farkli bölümde çaliştigini bul. SELECT COUNT(DISTINCT böl_no) FROM personel; COUNT (böl_no)
SQL Üzerine (Açıklamalı - Örnekli) - 3
0
29 Haz 2002 14:19 Eklendi
6.GRUPLANDIRARAK İŞLEM YAPMA: GROUP BY (GRUPLA) ÖRNEK: Her bölümdeki ortalama maaş nedir? SELECT böl_no,AVG (brüt) FROM personel GOUP BY böl_no; HAVING: HAVING (SAHİP)
649
Gruplandırarak kümeleme fonksiyonunu uygularken koşulda verilebilir.Bu durumda grup üzerindeki hesaplamalarla ilgili koşul belirtilirken HAVING (SAHİP) sözcüğü kullanılır. ÖRNEK: En yüksek maaşin 9000000’dan fazla oldugu bölümlerdeki personele ait ortalama maaşlari listele. SELECT böl_no,AVG (brüt) FROM personel GROUP BY böl_no HAVING AVG(brüt)> 9000000; HAVING sözcüğü SELECT konusunda GROUP BY bulunmadığı zaman geçersizdir. HAVING sözcüğünü izleyen ifade içinde SUM , COUNT(*) ,AVG, MAX yada MIN fonksiyonlarından en az biri bulunmalıdır. HAVING sözcüğü sadece gruplanmış veriler üzerindeki işlemlerde geçerlidir. WHERE sözcüğü bir tablonun tek tek satırları üzerinde işlem yapan koşullar içinde geçerlidir. Bazı durumlarda HAVING ve WHERE sözcükleri ile birlikte SELECT komutu içinde kullanılabilir. ÖRNEK: Personel tablosu içinde her bölümde erkek personele ait maaşlar için ortalamanin 9000000’dan fazla oldugu bölümleri listele. SELECT böl_no, AVG (brüt) FROM personel WHERE cins= .T. GROUP BY böl_no HAVING AVG (brüt) > 9000000; BİRDEN FAZLA TABLOYU İLİŞKİLENDİRMEK: JOIN (İLİŞKİLENDİR) ÖRNEK: Personel ve bölüm adlı 2 tablo bulunmaktadır. Çalışan her personel ve personelin yöneticisi ile ilişkili bilgiler nelerdir? SELECT * FROM personel,bölüm WHERE personel .böl_no=bölüm.bölüm_no ; ÖRNEK: JOIN (İLİŞKİLENDİR) işleminde arzu edilen(sicil,ad,soyad,böl_no,yön_s_g_n) alanların listele. SELECT sicil,ad,soyad,böl_no,yön_s_g_n FROM personel,bölüm WHERE personel .böl_no = bölüm .bölüm_no; SELF-JOIN: KENDİSİYLE -İLİŞKİLENDİR: TANIM:Bir tablonun kendisi ile birleştirilmesine “KENDISIYLEILIŞKiLENDIR” denir.(SELF-JOIN)
650
SELECT A. sicil , A.ad , A.soyad, B .ad , B.soyad , B.dog_tar FROM personel A , personel B WHERE A. yon_sos_g_n =B .sosy_g_no; NESTED SELECTS: İÇİÇE SEÇİMLER TANIM: İç içe geçmiş SELECT komutlarından oluşur. İçteki Select komutunun bulduğu sonucu dış takı komutumuz işlevini yerine getirmesi için kullanılır. ÖRNEK:Parça numarası 24 olan parçayı ,projelerde kullanan çalışan personeli listele. SELECT * FROM personel WHERE sosy_g_no IN(SELECT per_s_g_no FROM parça,proje,çalışma WHERE pr_no = proj_no AND proj_no =proj_no AND par_no =24); ÖRNEK: Fatih’te oturan personelin çalıştığı projelerin adlarını ve yerlerini listele. SELECT proj_ad,yer FROM proje WHERE proj_no IN (SELECT proje_no FROM personel,çalışma WHERE sosy_g_no = per_s_g_no AND adres LIKE “% fatih %”); UNION SÖZCÜĞÜ: UNION (BİRLEŞİM) TANIM: İki ayrı SEÇ komutunun sonucunda elde edilen tabloların birleşimi işlemini gerçekleştirir. ÖRNEK: Adı Ahmet ve Soyadı Caner olan kişi yada kişileri işletmenin yürüttüğü projelerde çalışan bir kişi (sıradan bir personel yada bölüm yöneticisi)olarak bulunduran projelerin isimlerini ve projelerin yürütüldü ğü yerleri listele. (SELECT proj_ad,yer FROM proj,bölüm,personel WHERE bl_no=bölüm_no AND y_sos gno = sosy_g_no 651
AND ad =”Ahmet”AND soyad =”Caner”) UNION (SELECT proj_ad,yer FROM proje,çalışma,personel WHERE proj_no = proje_no AND Per_s_g_no = sosy_g_no AND ad =”Ahmet” AND soyad =”Caner”) KOŞULLAR: UNION (BİRLEŞİM) sözcüğü ile ,iki yada daha çok kişi SELECT ’in sonucu olan tabloların küme birleşimi işlemine tabi tutulması için 2 koşul gereklidir. 1) SELECT komutları sonucunda elde edilecek tablolar aynı sayıda kolon içermelidirler. 2)Sonuç tabloları karşılıklı olarak kolonların aynı veri tipi ve aynı genişlikte olmalıdır. ANY : ANY (HER HANGİ BİRİ) ÖRNEK:Satış bölümünde çalışan personelin her hangi birinden daha düşük maaş alan ve mühendislik bölümündeki kişileri listele. SELECT * FROM personel WHERE brüt < ANY (SELECT brüt FROM personel WHERE böl_no = 2) AND böl_no =1; Aynı ifade aşağıdaki gibi yazılabilir: SELECT * FROM personel WHERE brüt < (SELECT MAX (brüt ) FROM personel WHERE böl_no = 2) AND böl_no =1; ALL (HEPSİ) ÖRNEK: Satış bölümünde çalışan ve mühendislik bölümündeki personelin hepsinden daha fazla maaş alan personeli listele.Bu örnekte satış bölümü kodu = 2 ve mühendislik bölümü kodu = 1 alınmıştır. YAPILIŞ YOLU: 1) SELECT * FROM personel WHERE brüt > ALL (SELECT brüt FROM personel 652
WHERE böl_no = 1) AND böl_no = 2;
2) SELECT * FROM personel WHERE brüt > (SELECT MAX (brüt) FROM personel WHERE böl_no = 1) AND böl_no =2; EXISTS (MEVCUT) VE ,VEYA ,DEĞİL operatörleri ile kullanılabilir. ÖRNEK: 27 no’lu parçayı satan satıcılarla ilişkili tüm bilgileri listele. SELECT * FROM satıcı WHERE EXISTS (SELECT * FROM par_sat WHERE sat_no = satıcı_n AND parça_n =27); NOT EXISTS (MEVCUT DEĞİL) VE ,VEYA ,DEĞİL operatörleri ile kullanılabilir. ÖRNEK: 27 no’lu parçayı satmayan satıcılar kimlerdir? SELECT * FROM satıcı WHERE NOT EXISTS (SELECT * FROM par_sat WHERE sat_no = satıcı_n AND parça_n =27); EXCEPT (FARKLI) Tablo-1 - Tablo-2 işlemi sonuç(iki kümenin farki) elde edilecek tabloda,Tablo-1’de bulunup, Tablo-2’de bulunmayan veriler mevcut olacaktir. ÖRNEK:Satış bölümündeki personel adlarından,mühendislik bölümünde bulunmayanları listele. 653
SELECT * FROM (SELECT ad FROM personel WHERE bol_no=1 EXCEPT SELECT ad FROM personel WHERE bol_no =2); INTERSECT (KESİŞİM) ÖRNEK: Hem Ankara’da,hem de İstanbul’daki projelerde görev alan bölümleri listele. SELECT * FROM (SELECT bl_no FROM proje WHERE yer LIKE “%Ankara%” INTERSECT SELECT bl_no FROM proje WHERE yer LIKE “%İstanbul%”); SAVE TO TEMP (SAKLA) ÖRNEK: Bayan personeli,bayan adlı bir tablo içinde sakla. SELECT * FROM personel WHERE cins =.F. SAVE TO TEMP bayan; KEEP: KEEP (KALICI) ÖRNEK: SELECT * FROM personel WHERE cins = .F. SAVE TO TEMP bayan KEEP;
SQL Üzerine (Açıklamalı - Örnekli) - 4
0
29 Haz 2002 14:19 Eklendi
7.TABLOLARDA DEĞİŞİKLİK YAPMAK: INSERT (EKLE) INTO (İÇİNE)
654
VALUES (DEĞERLER) ÖRNEK: Bir personel tablosuna sicil_no’su 275 olan personel ile ilişkili bilgileri ekle. INSERT INTO personel(sicil, sosy_g_no,ad,soyad,doğ_tar adres,cins,brüt,böl_no,yön_s_g_no VALUES(‘275’,’27652418’,’Ali’,’Caner’, {10/05/1962},’Merkez caddesi 46 -Fatih-İstanbul’, .T.,27000000,2,’876215342’); DELETE (SİL) ÖRNEK: 2 no’lu bölümdeki personelin tümü tablodan sil. DELETE FROM personel WHERE böl_no = 2; 5 ROWS DELETED 5 SATIR SİLİNDİ ÖRNEK:Brüt maaş alani boş olmayan tüm personeli sil. DELETE FROM personel WHERE brüt IS NOT NULL; 25 ROWS DELETED 25 SATIR SİLİNDİ UPDATE (GÜNCELLE) SET (YAP) ÖRNEK:2’inci bölümün yürüttüğü projelerde kullanılan tüm parçaların fiyatlarını % 7 zam yap. UPDATE parça SET fiyat = fiyat *1,07 WHERE pr_no IN (SELECT proj_no FROM proje WHERE bl_no = 2; CREATE INDEX (INDEKS YARAT ) ON (Hangi Tablo İçin) CREATE INDEX ındeks adı ON tablo adı(kolon adı 1,kolon adı 2,.,.kolon adı n); 655
TEK BİR ALANA GÖRE ARTAN SIRADA İNDEKSLEME : ÖRNEK:İşletmede çalışan personeli brüt maaşlarına göre artan sırada listele.(Brüt alana göre bir indeks oluşturmalıyız) CREATE INDEX pers_maas ON personel(brüt); INDEX CREATED 127 ROWS İNDEKS YARATILDI 127 SATIR 127 satırlık personel tablosu ile ilişkili olarak brüt kolonu indeks anahtarı olarak kullanan pers_maas adlı indeks oluşturulmuştur.Bu durumda; SELECT * FROM personel; _Şeklinde listeleme komutu sonucunda personel tablosundaki tüm personel, brüt maaşlarina göre sirali olarak listelenecektir. TEK BİR ALANA GÖRE AZALAN SIRADA İNDEKSLEME : DESC Küçükten büyüğe (K-B) ÖRNEK:İşletmede çalışan personeli brüt maaşlarına göre azalan sırada (yüksek maaştan düşük maaşa doğru)listelemek istersek ,brüt alanına göre aşağıdaki şekilde oluşturmak gerekir. CREATE INDEX ON personel (brüt DESC); BİRDEN FAZLA ALANA GÖRE İNDEKSLEME : ÖRNEK:İşletmedeki personelin öncelikle adlarına göre,aynı adda olanların soyadlarına göre ,hem adı hemde soyadı aynı olanların maaşlarına göre sıralanmış olarak listele. CREATE INDEX p_ad_soy_m ON personel (ad,soyad,brüt); Bu durumda; SELECT * FROM personel;
UNIQUE (TEK) Bir tablo,seçilen bir sutüna (alana) göre indekslenirken , indeksleme alanı olarak seçilen sutündaki verilerintekrarlanmasına müsaade edilmesi istenmiyorsa,indeksleme yapılırken ,CREATE ,INDEX komutu iinde UNİQUE sözcüğü kullanılmalıdır. 656
CREATE UNIQUE INDEX pers_sicil ON personel (sicil); EKLEME İÇİN: Personel tablosuna INSERT INTO Personel VALUES(53768 ,’27241685’,’ayşe’, ‘şen’{01/04/63},’Merkez cad. 82 Kadıköy’.F. ,27000000 ,2, ‘34261578’); MEVCUT BİR İNDEKSİN SİLİNMESİ: DROP IPTAL DROP INDEX pers_in; Komutu ile INDEX DROPPED (İNDEKS SİLİNDİ) TABLONUN YAPISINDA DEĞİŞİKLİK YAPMAK: ALTER TABLE (TABLO DEĞİŞTİR) MEVCUT BİR TABLOYA KOLON EKLEMEK: ADD (EKLE) ALTER TABLE (TABLO DEĞİŞTİR) komutu içinde ADD (EKLE) ile satır ekle. ÖRNEK:Personel tablosuna ,işe başlama tarihini belirten bir kolon ekle ALTER TABLE personel ADD iş_baş_tar DATE; ADD (EKLE)iş_baş_tar DATE NOT NULL (TARIH DEGERSIZ) bu şekilde kullanilsaydi bu kolon satiri gene boş kalirdi ; fakat bu kolon ile ilişkili yeni boş degerler eklemek istendiginde buna müsaade edilmeyecekti. MEVCUT BİR TABLONUN ALANLARINDA DEĞİŞİKLİK YAPMAK : MODIFY KOMUTU: MODIFY (DEĞİŞTİR) MEVCUT BİR TABLODAN BİR KOLON SİLMEK: DROP KOMUTU : DROP (İPTAL) 657
ÖRNEK:Personel tablosundan iş_baş_tar kolonunu sil. ALTER TABLE personel DROP iş_baş_tar ; Birden fazla kolonda silinebilir.Birden fazla kolon silmek için virgülle ayrılarak silinir. BİR TABLONUN ADINI DEĞİŞTİRMEK: RENAME KOMUTU: RENAME (TABLO YENİ AD) ALTER TABLE personel personel Tablosunda değişiklik yap RENAME TABLE elemanlar; elemanlar tablosunun adını değiştir MEVCUT BİR TABLONUN BİR KOLONUNUN ADININ DEĞİŞTİRİLMESİ: RENAME: RENAME YENİ AD ALTER TABLE personel RENAME brüt br-maaş; MEVCUT BİR TABLONUN TÜMÜYLE SİLİNMESİ DROP TABLE (TABLO İPTAL) ÖRNEK:Proje tablosunu sil. DROP TABLE proje; VERİ GÜVENLİĞİ: CREATE VIEW GÖRÜŞ ALANI YARAT ÖRNEK:Personel adlı temel tablodan persview adlı bir view oluştur. CREATE VIEW perswiew AS SELECT sicil,sos_g_no,ad,soyad,doğ_tar, adres,cins,böl_no,yon_s_g_no FROM personel; VERİ BÜTÜNLÜĞÜNÜN SAĞLANMASI: WITH CHECK OPTİON KONTROLLÜ CREATE VIEW UST_PER_ VIEW 'Önce bir vıew oluşturulsun 658
AS SELECT FROM personel WHERE brüt >25000000 WITH CHECK OPTION; Burada, maaşi 25000000’ün üzerinde olan personelden oluşan bir UST_PER_VIEW adli view oluşturulmuştur.Bu view’a brüt maaşi 13000000 olan bir personel eklemek istedigi zaman hata mesaji verecektir. CHECK opsiyonu kullanılmasaydı hata mesajı alınmadan bu veri VİEW içine yükleyecekti. EKLEME INSERT INTO UST_PER_VIEW VALUES (27521 ,’27865427’,’ayşe’, ‘okan’ ,{01/05/1962}’Cumh. Cad. 46 - Taksim’, .F.,13000000 ,1 ,’27651112’); VIEW İÇİNDE SATIR SİLME: ÖRNEK:UST_PER_VIEW içinden,maaşi 2500000’den az olan kişileri sil. DELETE FROM UST_PER_VIEW WHERE brüt < 25000000; VIEW SATIRLARI ÜZERİNDE GÜNCELLEME : ÖRNEK: UST_PER_VIEW adlı view’de sicili 27251 olan kişnin maaşını 37000000 olarak değiştir. UPDATE UST_PER_VIEW SET brüt = 37000000 WHERE sicil = 27251; BİR VIEW’U SİLMEK: DROP VIEW (GÖRÜŞ ALANI IPTALI) DROP VIEW UST_PER_VIEW; GÖRÜŞ ALANI IPTALI UST_PER_VIEW;
SQL Üzerine (Örnekler)
1
8 Tem 2002 05:01 mfo
"Select * From Employees" Employees tablosundan tüm alanlari seç. En basit SQL sorgusu budur. -------------------------------------------------------------------------------------------------------"Select * From Title Where [Year Published] < 1889" 659
Title tablosundan [Year Published] alani degeri 1889'dan küçük olan tüm kayitlari seç. Not: * isareti tüm alanlarin seçilecegini gösterir. [ ] "Köseli ayraç ise alan adi bir kelimeden fazla ise kullanilmalidir. Yani yukaridaki alan adi sadece "Year" olsaydi köseli ayraç kullanmaya gerek kalmayacakti. -------------------------------------------------------------------------------------------------------"Delete From Titles Where [Year Published] < #1/1/1889#" Titles tablosundan [Year Published] alani degeri 1/1/1889'dan küçük olanlarin tümünü sil -------------------------------------------------------------------------------------------------------"Select Name, Picture From Authors Where Date_of_Birth = #2/1/1947#" Authors tablosundan Date_of_Birth = 2/1/1947 denkligi olan kayitlardan Name ve Picturealanlarini seçDikkat ederseniz tüm sorgularda sabit bir SELECT ... WHERE .... yapisi var. Select seçimin nereden yapilacagini Where ise eslesme kriterlerini göstermektedir.Bu örnekte * isareti kullanilmamis ve sadece iki alan seçilmistir: "name" ve "picture"Tarih ifadeleri ise # isaretleri arasinda yazilmalidir. -------------------------------------------------------------------------------------------------------"Select [First Name], [Last Name] From Employees" Employees tablosundan sadece First Name ve Last Name alanlarini seç -------------------------------------------------------------------------------------------------------"Select Employees, Department, SupvName From Supervisors, Employees Where Employees.Department = Supervisors.Department" Bu biraz daha karisik. Burada iki tablo var: Supervisors ve Employees. Bu iki tablodan üç adet alan seçilecek: 1.Employees, 2.Department, 3.SupvName. Bu iki tabloda da Department adli birer alan var. Iste bu alanlarin denkligi ile seçim yapiliyor. Yani Employees tablosunun Department alani ile Supervisor tablosunun Department alani esit ise seçim yapiliyor. -------------------------------------------------------------------------------------------------------"Select Distinct [Last Name] From Employees" Employees tablosundan Last Name degeri ayni olan kayitlardan sadece birini al. Distinct anahtari birden fazla ayni deger var ise sadece ilkini alir. -------------------------------------------------------------------------------------------------------"Select [Last Name], Salary From Employees Where Salary > 2100" Salary degeri 2100'den küçük olan Employees tablosu kayitlarindan yalnizca Last Name alanlarini seç. -------------------------------------------------------------------------------------------------------"Select * From Orders Where [Shipped Date] = #5/12/93#" Orders tablosundan Shipped Date degeri 5/12/93'e esit olan kayitlarin tüm alanlarini seç. -------------------------------------------------------------------------------------------------------"Select [Product Name], Sum ([Units in Stock]) From Products Group By [Product Name]"
660
Products tablosundan Product Name ve Unit in Stocks alanlarini al. Ancak burada dikkat edilmesi gereken Sum( ) fonksiyonudur. Bu fonksiyon her alan degerini birbiri üzerine toplar. Seçilen alanlarin tabloya yerlestirilmesi ise Product Name alan degerinin alfabetik sirasina göre A-Z olarak yapilir. -------------------------------------------------------------------------------------------------------"Select * From Employees Order By [Last Name], Asc" Employees tablosundaki tüm alanlari Last Name alan degerine göre Z-A siralamasina göre seç Yani tablodan tüm kayitlar alinacaktir, çünkü kriter olarak kullanilan WHERE sözcügü yoktur. Ancak alinan tüm kayitlar Last Name alan degerinin Z-A alfabetik sirasina göre (ters sira) siralanir. Soyadi Zahit olan kisi soyadi Orhun olan kisiden önce gösterilir. Sorgunun sonunda kullanilan ASC anahtari seçimliktir (optional). Herhangi bir sey yazilmazsa, bu anahtar degerinin DESC (A-Z) oldugu kabul edilir. -------------------------------------------------------------------------------------------------------"Select [Last Name], Salary From Employees Order By Salary, Desc, [Last Name] Veritabanindan alinan kayitlarin listelemesi islemi burada iki kritere göre yapilmakta. Önce kisilerin maaslari (Salary), sonra da soyadlari (Last Name) dikkate alinmakta. Söyle düsünün; Bir sirkette ayni maasi alan 3 kisi var. Bu kisilerin soyadlari Akin, Bahçe ve Celep olsun...Bu sorgu sonucu sirkette çalisan herkes listelenecektir. Ancak bizim bu üç kisi pespese listelenecek ve siralama Akin-Bahçe-Celep seklinde olacaktir. Asagidaki örnege bakiniz: Last Name Salary Filiz 300.000.000 Kara 275.000.000 Akin 250.000.000 Bahçe 250.000.000 Celep 250.000.000
661
Paradox ve dBASE tabloları için BDE ağ tanımları
11
8 Ağu 2002 09:08 mcuyan
Çok Sorulduğu için bi makale yazmak gereği duydum. Paradox ve Dbase tablolarını ağda çalıştırabilmek için Aşağıdaki BDE ayarlarını Kullanın. ANA MAKİNA AYARLARI Ana Makina Adı: Server Disk paylaşım adı : C Paradox veya dBASE tablolarının bulunduğu Klasör c:\Prog\Data PDOXUSRS.NET dosyasının yeri: c:\ Ana Makina BDE Ayarları: DataBAse sekmesi: Alias: MyProg Path : c:\Prog\Data Congiguration->Drivers->Native->PARADOX (veya dBASE) sekmesi: NET DIR: C:\ (PDOXUSRS.NET dosyasını gösterecek) LANGDRIVER: paradox "turk" Configuration->System->INIT sekmesi: LANGDRIVER: paradox "turk" LOCALSHARE: TRUE TERMİNAL MAKİNA BDE AYARLARI: DataBAse sekmesi: Alias: MyProg Path : \\Server\Prog\Data Congiguration->Drivers->Native->PARADOX (veya dBASE) sekmesi: NET DIR: \\Server\c (Ana makinadaki PDOXUSRS.NET dosyasını gösterecek) LANGDRIVER: paradox "turk" Congiguration->System->INIT sekmesi: LANGDRIVER: paradox "turk" LOCALSHARE: TRUE Yukarda anlatılan ayarlaın dışında şayet terminalden ana makina için bir ağ yolu tanımlamışsanız terminal ayarları aşağıdaki gibi de olabilir. Ana makinanın diskine F diye bir ağ yolu tanımladıysak ayarlar şu şekilde olacaktır. DataBAse sekmesi: Alias: MyProg Path : F:\Prog\Data
662
Congiguration->Drivers->Native->PARADOX (veya dBASE) sekmesi: NET DIR: F:\ (Ana makinadaki PDOXUSRS.NET dosyasını gösterecek) LANGDRIVER: paradox "turk" Congiguration->System->INIT sekmesi: LANGDRIVER: paradox "turk" LOCALSHARE: TRUE Ancak bu tanımda dikkat edilmesi gereken şey F ağ yolu koptuğunda program çalışmayacaktır. .exe dosyanızı terminalden istediğiniz klasörden çalıştırabilirsiniz. Kolay gelsin.......... http://community.borland.com/article/0,1410,15247,00.html
Borland Developer Network Home > Delphi & Kylix
BDE setup for Peer-To-Peer(Non-Dedicated) Networks - by Borland Developer Support Staff Technical Information Database TI247B.txt BDE setup for Peer-To-Peer(Non-Dedicated) Networks Category :General Programming Platform :All Product :BDE 3.0 Description: Using a BDE32 Application on a Peer-To-Peer Network --------------------------------------------------A Peer-To-Peer network (a network where each machine acts as a client and a server) can be one of the following, including other network platforms that are compatible with these: 1) Windows 95 2) Windows NT 3) Lantastic 4) Netware Lite The BDE automatically detects when tables reside on a network drive, but it cannot detect whether the tables are on a dedicated server or a server/client. Dedicated servers notify client applications that a file has been modified or locked. This functionality is not present in Peer-To-Peer (non-dedicated) networks. To achieve this 663
functionality with Peer-To-Peer networks set "LOCAL SHARE" to TRUE in the BDE Configuration Utility on the System page. This must be done on all BDE clients that access the tables on networks listed above. This is not necessary for Novell File Server type networks. If the tables that are being used are Paradox, there must also be a directory used for network control. This directory must also reside on the network for all client applications to use. It is good practice to have a separate directory for the application, network, and tables. The following is an example: (Shared Directory) | |--- (Tables Directory) |--- (EXE Directory) |--- (Network Directory) There are two different BDE environments that must also be considered: 1) Using only BDE 32Bit applications. 2) Using BDE 32Bit applications along with BDE 16Bit applications. Setup for 32Bit Only Applications --------------------------------The 32Bit BDE fully supports the UNC naming convention along with long file names. It is recommended that the UNC convention is used for all BDE network connections. UNC removes the need for mapped drives. This will allow access to the tables and network directory without the user being mapped to the drive. UNC has the following syntax: \\(server name)\(share name)\(path)+(file name) Here is a simple example of a standard BDE alias using UNC: Alias: MyUNCAlias Type: STANDARD Path: \\FooServer\FooShare\Sharedir\Tables Default Driver: Paradox The network directory can be setup in the same fashion: Drivers: Paradox Net Dir: \\FooServer\FooShare\Sharedir\NetDir The network directory can be set at runtime using session.netfiledir (Delphi) or DbiSetProp (C++ / Delphi) 664
If for some reason UNC cannot be used with the 32Bit application, follow directions for using BDE 32Bit and 16Bit applications. Setup for 16Bit and 32Bit BDE Applications -----------------------------------------Since the 16Bit Windows API does not support UNC, neither does the 16Bit BDE. To allow applications to share the tables, all clients must be mapped to the same directory on the server. If the server is also used as a client, all other clients must be mapped to the root of the drive. Drive letters from client to client do not have to be identical. Here are some examples of what will and will not work: Client1: Path: X:\ShareDir\Tables Client2: Path: X:\ShareDir\Tables This is OK Client1: Path: Client2: Path: This is
(Also the machine with the tables): C:\ShareDir\Tables X:\ShareDir\Tables OK
Client1: (Also the machine with the tables): Path: C:\ShareDir\Tables Client2: Path: X:\ShareDir\Tables Client3: Path: R:\ShareDir\Tables This is OK Client1: Path: X:\ShareDir\Tables Client2: Path: X:\Tables (Where X:\Tables is actually X:\ShareDir\Tables, but shared on the ShareDir directory) This will not work. The BDE must be able to make the same entry into the Network Control file. In Summary (setup for Peer-To-Peer networks): --------------------------------------------16 and / or 32Bit Applications: 1) Turn "LOCAL SHARE" to TRUE in the BDE Configuration Utility. 2) Do not use the UNC naming convention. 3) Do not use tables with long file names. 665
4) Make sure that all clients are mapped to the same directory on the server. 32Bit Only Applications: 1) Turn "LOCAL SHARE" to TRUE in the BDE Configuration Utility 2) Use the UNC naming convention to achieve a path to the network directory and table directory. If the above steps are not followed, users could be locked out of the tables getting error: "Directory is controlled by other .NET file." "File: (Path1) PDOXUSRS.LCK" "Directory: (Path2)" OR "Multiple .NET files in use." "File: (Path) PDOXUSRS.LCK" Reference: 7/15/98 3:24:23 PM
666
THe key is part of a set that will be processed by the OS, not by your application. MS has two strategies for such keys. The first is to hand the key to the application and do the default processing for it in the default window proc that is supposed to get all messages not handled in application code. The second is to handle the key on a lower level (between keyboard driver and message queue, or in the keyboard driver), which means the application never sees it. Keys in the first category you would see, at least if you look for them in Application.OnMessage, since the active control normally does not want to see all keys. Keys in the second category you never see, not even in a message hook. VK_SNAPSHOT seems to fall into the second category, it does not turn up in Application.OnMEssage. There is a way to get notified of it, however: make it a hotkey for your application. Example: Using PrintScr to print a form type TForm1 = class(TForm) Button1: TButton; Label1: TLabel; Label2: TLabel; private { Private declarations } procedure wmHotkey( Var msg: TWMHotkey ); message WM_HOTKEY; procedure WMActivate( Var msg: TWMActivate ); message WM_ACTIVATE; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure tform1.wmHotkey( Var msg: TWMHotkey ); begin if msg.HotKey = IDHOT_SNAPWINDOW Then label1.caption := 'PrintScr pressed'; // would print form from here end; procedure TForm1.WMActivate( Var msg: TWMActivate ); begin If msg.Active <> WA_INACTIVE Then RegisterHotkey( Handle, IDHOT_SNAPWINDOW, 0, VK_SNAPSHOT ) 667
Else begin UnRegisterHotkey( Handle, IDHOT_SNAPWINDOW ); label1.caption := ''; end; inherited; end; --
668
JPEG Header Format Strictly speaking, JPEG files do not have formal headers, but fg_jpeghead() and fgi_jpeghead() return relevant information from the file's start of frame segment. We call it a header for consistency with other image file formats. offset
size
description
0
2
JPEG SOI marker (FFD8 hex)
2
2
image width in pixels
4
2
image height in pixels
6
1
number of components (1 = grayscale, 3 = RGB)
7
1
horizontal/vertical sampling factors for component 1
8
1
sampling factors for component 2 (if RGB)
9
1
sampling factors for component 3 (if RGB)
669
Brought to you by JPEG HEADER Before the image data is ever loaded when a JPEG image is selected for viewing the markers must be read. In a JPEG image, the very first marker is the SOI, or Start Of Image, marker. This is the first "hey, I'm a JPEG" declaration by the file. The JPEG standard, as written by the Joint Picture Expert's Group, specified the JPEG interchange format. This format had several shortcomings for which the JFIF (JPEG File Interchange Format) was an attempted remedy. The JFIF is the format used by almost all JPEG file readers/writers. It tells the image readers, "Hey, I'm a JPEG that almost anyone can understand." Most markers will have additional information following them. When this is the case, the marker and its associated information is referred to as a "header." In a header the marker is immediately followed by two bytes that indicate the length of the information, in bytes, that the header contains. The two bytes that indicate the length are always included in that count. A marker is prefixed by FF (hexadecimal). The marker/header information that follows does not specify all known markers, just the essential ones for baseline JPEG. A component is a specific color channel in an image. For instance, an RGB image contains three components; Red, Green, and Blue. © 1998 by James R. Weeks
Start of Image (SOI) marker -- two bytes (FFD8) JFIF marker (FFE0) length -- two bytes identifier -- five bytes: 4A, 46, 49, 46, 00 (the ASCII code equivalent of a zero terminated "JFIF" string) version -- two bytes: often 01, 02 the most significant byte is used for major revisions the least significant byte for minor revisions units -- one byte: Units for the X and Y densities 0 => no units, X and Y specify the pixel aspect ratio 1 => X and Y are dots per inch 2 => X and Y are dots per cm Xdensity -- two bytes Ydensity -- two bytes Xthumbnail -- one byte: 0 = no thumbnail Ythumbnail -- one byte: 0 = no thumbnail (RGB)n -- 3n bytes: packed (24-bit) RGB values for the thumbnail pixels, n = Xthumbnail * Ythumbnail Define Quantization table marker (FFDB)
670
the first two bytes, the length, after the marker indicate the number of bytes, including the two length bytes, that this header contains until the length is exhausted (loads two quantization tables for baseline JPEG) the precision and the quantization table index -- one byte: precision is specified by the higher four bits and index is specified by the lower four bits precision in this case is either 0 or 1 and indicates the precision of the quantized values; 8-bit (baseline) for 0 and up to 16-bit for 1 the quantization values -- 64 bytes the quantization tables are stored in zigzag format Define Huffman table marker (FFC4) the first two bytes, the length, after the marker indicate the number of bytes, including the two length bytes, that this header contains until length is exhausted (usually four Huffman tables) index -- one byte: if >15 (i.e. 0x10 or more) then an AC table, otherwise a DC table bits -- 16 bytes Huffman values -- # of bytes = the sum of the previous 16 bytes Start of frame marker (FFC0) the first two bytes, the length, after the marker indicate the number of bytes, including the two length bytes, that this header contains P -- one byte: sample precision in bits (usually 8, for baseline JPEG) Y -- two bytes X -- two bytes Nf -- one byte: the number of components in the image 3 for color baseline JPEG images 1 for grayscale baseline JPEG images Nf times: Component ID -- one byte H and V sampling factors -- one byte: H is first four bits and V is second four bits Quantization table number-- one byte The H and V sampling factors dictate the final size of the component they are associated with. For instance, the color space defaults to YCbCr and the H and V sampling factors for each component, Y, Cb, and Cr, default to 2, 1, and 1, respectively (2 for both H and V of the Y component, etc.) in the Jpeg-6a library by the Independent Jpeg Group. While this does mean that the Y component will be twice the size of the other two components--giving it a higher resolution, the lower resolution components are quartered in size during compression in order to achieve this difference. Thus, the Cb and Cr components must be quadrupled in size during decompression. Start of Scan marker (FFDA) the first two bytes, the length, after the marker indicate the number of bytes, including the two length bytes, that this header contains Number of components, n -- one byte: the number of components in this scan 671
n times: Component ID -- one byte DC and AC table numbers -- one byte: DC # is first four bits and AC # is last four bits Ss -- one byte Se -- one byte Ah and Al -- one byte Comment marker (FFFE) the first two bytes, the length, after the marker indicate the number of bytes, including the two length bytes, that this header contains whatever the user wants End of Image (EOI) marker (FFD9) the very last marker
This page has been viewed
times.
672
I'm trying to write a DLL, and export the function so that I can use it with > the rundll32 command, which should allow me to run the function in the dll > by using rundll32. library Run32Test; uses Windows; procedure Test(Wnd: HWND; Instance: HINST; CmdLine: PChar; CmdShow: Integer); stdcall; begin MessageBox(Wnd, CmdLine, 'Run32Test', MB_OK); end; exports Test; begin end. Usage: C:\Projects\Dummy\Run32Test>rundll32 run32test.dll,Test Hello world Note that there must be no space after the comma, and that the method name is case sensitive.
673
unit main; interface uses Windows,ActiveX,ComObj,ShlObj,Classes,Graphics; type TContextMenu = class(TComObject,IShellExtInit,IContextMenu) private FFileName : array[0..MAX_PATH] of Char; FMenuIndex : UINT; FDataObject : IDataObject; FBitmap, FICON : TBitmap; protected function IShellExtInit.Initialize = SEIInitialize; // Avoid compiler warning function SEIInitialize(pidlFolder: PItemIDList; lpdobj: IDataObject; hKeyProgID: HKEY): HResult; stdcall; function QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): HResult; stdcall; function InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult; stdcall; function GetCommandString(idCmd, uType: UINT; pwReserved: PUINT; pszName: LPSTR; cchMax: UINT): HResult; stdcall; end; const Class_ContextMenu: TGUID = '{19741013-C829-11D1-8233-0020AF3E97A9}'; var FileList:TStringList; Buffer:array[1..1024]of char; implementation uses ComServ, SysUtils, ShellApi, Registry; function TContextMenu.SEIInitialize(pidlFolder: PItemIDList; lpdobj: IDataObject; hKeyProgID: HKEY): HResult; var StgMedium: TStgMedium; FormatEtc: TFormatEtc; FileNumber,i:Integer; begin if (lpdobj = nil) then begin Result := E_INVALIDARG; Exit; end; FDataObject := lpdobj; if FBitmap=nil then begin FBitmap := TBitmap.Create; FBitmap.Width := 50; FBitmap.Height:= 60; FBitmap.Canvas.FillRect(Rect(0, 0, 50, 60)); FBitmap.Canvas.LineTo(50, 60); end; 674
if FICON=nil then begin FICON := TBitmap.Create; FICON.LoadFromResourceName(HInstance, 'LIGHT'); end; FileList:=TStringList.Create; FileList.Clear; with FormatEtc do begin cfFormat := CF_HDROP; ptd := nil; dwAspect := DVASPECT_CONTENT; lindex := -1; tymed := TYMED_HGLOBAL; end; Result := lpdobj.GetData(FormatEtc, StgMedium); if Failed(Result) then Exit; FileNumber := DragQueryFile(StgMedium.hGlobal,$FFFFFFFF,nil,0); for i:=0 to FileNumber-1 do begin DragQueryFile(StgMedium.hGlobal, i, FFileName, SizeOf(FFileName)); FileList.Add(FFileName); Result := NOERROR; end; ReleaseStgMedium(StgMedium); end; function TContextMenu.QueryContextMenu(Menu: HMENU; indexMenu, idCmdFirst, idCmdLast, uFlags: UINT): HResult; var MenuText1, MenuText2, MenuText3, MenuText4: string; PopupMenu : HMenu; idCmd : Cardinal; begin Result := NOERROR; if ((uFlags and $0000000F)=CMF_NORMAL) then begin MenuText1 := '&New menuitem 1, Normal File'; MenuText2 := '&New menuitem 2, Normal File'; MenuText3 := '&New menuitem 3, Normal File'; MenuText4 := '&New menuitem 4, Normal File'; end else if (uFlags and CMF_VERBSONLY) <> 0 then begin MenuText1 := '&New menuitem 1, Shortcut File'; MenuText2 := '&New menuitem 2, Shortcut File'; MenuText3 := '&New menuitem 3, Shortcut File'; MenuText4 := '&New menuitem 4, Shortcut File'; end else if (uFlags and CMF_EXPLORE) <> 0 then begin MenuText1 := '&New menuitem 1, File right click in Explorer'; MenuText2 := '&New menuitem 2, File right click in Explorer'; MenuText3 := '&New menuitem 3, File right click in Explorer'; MenuText4 := '&New menuitem 4, File right click in Explorer'; 675
end else if (uFlags and CMF_DEFAULTONLY) <> 0 then begin exit; end else exit; FMenuIndex:=indexMenu; idCmd := idCmdFirst; PopupMenu := CreatePopupMenu; InsertMenu(Menu,indexMenu,MF_SEPARATOR or MF_BYPOSITION,0,Pointer(0)); Inc(IndexMenu); InsertMenu(Menu,indexMenu,MF_STRING or MF_BYPOSITION or MF_POPUP,PopupMenu,PChar(MenuText1)); SetMenuItemBitmaps(Menu,indexMenu,MF_BYPOSITION,FICON.Handle,FICON.Handl e); Inc(IndexMenu); InsertMenu(PopupMenu,0,MF_STRING or MF_BYPOSITION,idCmd,PChar('SubMenu Item 1')); SetMenuItemBitmaps(PopupMenu,0,MF_BYPOSITION,FICON.Handle,FICON.Handle); Inc(idCmd); InsertMenu(PopupMenu,1,MF_STRING or MF_BYPOSITION,idCmd,PChar('SubMenu Item 2')); SetMenuItemBitmaps(PopupMenu,1,MF_BYPOSITION,FICON.Handle,FICON.Handle); Inc(idCmd); InsertMenu(Menu,indexMenu,MF_STRING or MF_BYPOSITION,idCmd,PChar(MenuText2)); Inc(IndexMenu);Inc(idCmd); InsertMenu(Menu,indexMenu,MF_STRING or MF_BYPOSITION,idCmd,PChar(MenuText3)); Inc(IndexMenu);Inc(idCmd); InsertMenu(Menu,indexMenu,MF_BITMAP or MF_BYPOSITION,idCmd,PChar(FBitmap.Handle)); Inc(IndexMenu);Inc(idCmd); InsertMenu(Menu,indexMenu,MF_SEPARATOR or MF_BYPOSITION,0,Pointer(0)); Result := idCmd - idCmdFirst; end; function TContextMenu.InvokeCommand(var lpici: TCMInvokeCommandInfo): HResult; var idCmd: cardinal; i:Integer; FileListStr : string; begin Result := E_INVALIDARG; if (HiWord(Integer(lpici.lpVerb)) <> 0) then begin Result := E_FAIL; Exit; end; //if HIWORD(lpcmi->lpVerb)£¬ then we have been called programmatically //and lpVerb is a command that should be invoked. Otherwise, the shell 676
//has called us, and LOWORD(lpcmi->lpVerb) is the menu ID the user has //selected. Actually, it's (menu ID - idCmdFirst) from QueryContextMenu(). idCmd := LoWord(Integer(lpici.lpVerb)); FileListStr := 'Invoke menu item '+IntToStr(idCmd) + ' , '+IntToStr(FileList.Count)+' files selected' + #13#10; for i:=0 to FileList.Count -1 do begin FileListStr:=FileListStr + FileList.Strings[i] + #13#10; end; MessageBox(lpici.hwnd,PChar(FileListStr),'Shell Extension Sample',MB_OK); Result := NOERROR; //case idCmd of // 0: begin SomethingDo; end; // Click on menu item 1 // 1: begin .... end; // Click on menu item 2 // 2: begin .... end; // .......... // 3: begin .... end; //end; end;
function TContextMenu.GetCommandString(idCmd, uType: UINT; pwReserved: PUINT; pszName: LPSTR; cchMax: UINT): HRESULT; begin if (idCmd = FMenuIndex) then begin if (uType = GCS_HELPTEXT) then StrCopy(pszName, PChar('MenuItem '+IntToStr(idCmd)+' , '+IntToStr(FileList.Count)+' Files Selectd')); Result := NOERROR; end else Result := E_INVALIDARG; end;
type TContextMenuFactory = class(TComObjectFactory) public procedure UpdateRegistry(Register: Boolean); override; end;
procedure TContextMenuFactory.UpdateRegistry(Register: Boolean); var ClassID: string; begin if Register then begin inherited UpdateRegistry(Register); ClassID := GUIDToString(Class_ContextMenu); CreateRegKey('*\shellex', '', ''); CreateRegKey('*\shellex\ContextMenuHandlers', '', ''); 677
CreateRegKey('*\shellex\ContextMenuHandlers\OpenWithWordPad', '', ClassID); //Èç¹û²Ù×÷ϵͳΪWindows NTµÄ»° if (Win32Platform = VER_PLATFORM_WIN32_NT) then with TRegistry.Create do try RootKey := HKEY_LOCAL_MACHINE; OpenKey('SOFTWARE\Microsoft\Windows\CurrentVersion\Shell Extensions', True); OpenKey('Approved', True); WriteString(ClassID, 'Context Menu Shell Extension'); finally Free; end; end else begin DeleteRegKey('*\shellex\ContextMenuHandlers\FileOpreation'); DeleteRegKey('*\shellex\ContextMenuHandlers'); // DeleteRegKey('*\shellex'); inherited UpdateRegistry(Register); end; end;
initialization TContextMenuFactory.Create(ComServer, TContextMenu, Class_ContextMenu, '', 'Context Menu Shell Extension', ciMultiInstance,tmApartment); end.
678
RTL reference|Glossary|Tips/Tricks|FREE App/VCL|Best'O'Net|Books|Link To
An introduction to hook procedures This document describes what Windows hooks are and how to use them within a Delphi application.
More of this Feature • Download code
Join the Discussion "Post your questions, concerns, views and comments to this article..." Discuss!
Related Resources • Win/API programming • Keyboard hooks
About Poll This [063001] article is: Awesome (5) Great (4) Ok (3) Not bad (2) Poor (1) Submit Vote
Current Results Article submitted by: Chris Cummings, http://wibblovia.topcities.com This document describes what windows hooks are and how to use them. In order to use hooks properly you will need a copy of the windows SDK, that can be downloaded from the Microsoft web site. The project hooks.dpr contains all the code for the program used in this document and hookdll.dpr contains the DLL required. The actual code itself is shown and explained more thoroughly as remarks within the projects.
679
What Are Hooks? Put shortly, a hook is a function you can create as part of a dll or your application to monitor the 'goings on' inside the windows operating system. The idea is to write a function that is called every time a certain event in windows occurs - for example when a user presses a key on the keyboard or moves the mouse. Hooks were provided by Microsoft primarily to help program writers with the debugging of their applications, but they can be put to use in many different ways - for example, my first use of them was to write hidden key logging program to find out my mums password to the internet! There are 2 types of hooks - global or local. A local hook is one that monitors things happening only for a specific program (or thread). A global hook monitors the entire system (all threads). Both types of hooks are set up in the same way, the main difference being that for a local hook, the function to be called can be within the program it is monitoring, but with a global hook the function must be stored and loaded from a separate dll.
Hook Procedures What follows is a quick description of each of the windows procedures required and also of the structure that your hook procedure should take. The SetWindowsHookEx function SetWindowsHookEx is the function provided by Microsoft to install a hook. It accepts the following arguments: Name
Type
Description
idHook
Integer
A number representing the type of hook - eg WH_KEYBOARD
lpfn
TFNHookProc The address in memory of the hook function
hMod
Hinst
The handle of the dll the hook function is in. If it is a local hook, this is 0.
dwThreadID Cardinal
The 'thread id' that the program is to monitor. If it is a global hook this is 0.
SetWindowsHookEx returns a handle (i.e. an identifier) for the current hook, so you can use UnhookWindowsHookEx to remove the hook later on. The hook function The hook function is the procedure to be called by windows when the event we specify happens. A hook for any event always takes the same form, but the values passed to it by windows can mean different things. For example if the hook is type WH_KEYBOARD, windows will pass information to it relating to which key was pressed. Your hook procedure should accept the following arguments: Name
Type
Description
Code
Integer
Indicates what the next 2 arguments mean
wParam word lParam
A parameter of size 1 word
longword A parameter of size 2 words
A hook function returns a value of type longword. What you should set it to depends on the type of hook, or you can just set it to the value that CallNextHookEx returns. The CallNextHookEx function This function is to do with 'hook chains'. When a hook is installed for a certain event, there may be others like it already installed - for example 2 programs at once might be trying to log keyboard input. When you install a hook with SetWindowsHookEx it adds your hook procedure to the front of a list of hook procedures. CallNextHookEx simply calls the next procedure in the list. When your hook procedure is finished, it can run CallNextHookEx, and then return the value it gets from it or a different one depending on the type of hook. CallNextHookEx takes exactly the same form as a hook procedure plus one extra - the handle returned by SetWindowsHookEx identifying the hook. The other values you pass to it should be the values your hook procedure was called with. How you should use it depends on
680
should be the values your hook procedure was called with. How you should use it depends on the type of hook. The UnhookWindowsHookEx function This is very simple! It simply removes your hook. The only argument you pass to it is the hook handle returned by SetWindowsHookEx. A Local Hook First up we will create a local hook. The important code for this is in 'local.pas'. Hooks.exe when run will display a small form. Click the Add/Remove Local Hook Button on this form to use the local hook. When the local hook is installed correctly, you should find that pressing any key and releasing it makes the beep sound, providing hooks.exe has the focus (since it is a local hook). The first function in local.pas is SetupLocalHook which creates a local hook, specifying the hook procedure as our KeyboardHook function. It simply calls SetWindowsHookEx, and if the handle returned is > 0, indicating the procedure works, it saves the handle in CurrentHook and returns true, otherwise it returns false. Next is RemoveLocalHook which takes the stored hook handle in CurrentHook and uses UnhookWindowsHookEx to remove it. Lastly is the hook procedure. The hook procedure simply checks if the key is being released, and if so beeps. A Global Hook The global hook is slightly more complicated. To create a global hook you need 2 projects, 1 to make the executable file and 1 to make a dll to contain the hook procedure. The global hook that is shown in the code records keydown events and every time 20 keys have been pressed it writes them to a file, log.txt. To use the global hook run hooks.exe and choose add/remove global hook. Then type something (say in notepad for example) that is more than 20 characters long. You will notice a file, log.txt appear that contains the text you wrote. The Dll you write should contain 2 procedures. The first, obviously being our hook procedure which in structure is identical to that defined for a local hook. The second is a simple procedure that you will find you need to do almost whenever you create a dll initialises a few variables in the dlls memory - these include the current number of the key that has been pressed and the handle for the hook that has been created. The executable file must first load the procedures in the dll and then use SetWindowsHookEx to define a global hook. Finally... This document and the code provided with it should have given you a good idea of how to use hooks. In order to find out about the different types of hook such as WH_MOUSE and get more information on how to use them you will need the windows SDK.
681
Hooks: Capture keys in all Windows applications Enviado Por (Send By): Radikal (Q3 Team) Web : http://www.q3.nu Email: [email protected] Fecha (Date): 03/03/00
Tip accessed 13722 times
A lot of people ask me about the possibility that our application Delphi captures the user's keystrokes, although the user doesn't make them our active application being. Of course... the first thing that we make is to give a turn for the event OnKeyPress of the form, and of course, without obtaining positive results, even putting the property KeyPreview from the form to true... This happens because our application will only receive messages of the keystrokes when is it who has the focus. The following step to solve this question is fighting with the keyboard hooks. A Hook it is not more than a mechanism that will allow us to spy the traffic of messages between Windows and the applications. To install a hook in our application is something relatively simple, but of course, if we install it in our application, we will only spy the messages that Windows sent to our application, so neither we will have solved the problem. Then... Which is the solution?. The solution is to install a Hook but at system level, that is to say, a hook that captures all the messages that circulate toward Windows. Installing a hook at system level has a great added complication that is the fact that the function to the one that calls the hook it must be contained in a DLL, not in our Delphi application. This condition, will force us, in the first place to build us a DLL, and in second place to to build us some invention to communicate the DLL with our application. In this trick you have an example of keyboard capture by means of a keyboard Hook to system level. The example consists of two projects, one for the DLL and another for the example application. The operation is the following one: · We make DLL with two functions that we will export, one to install the hook and another for ununstall it. · There is a third function that is the one that will execute the hook once installed (CallBack). In her, that will make it is to send the data of the message captured to
682
our application. The DLL should know in all moment the handle of the receiver application, so we will make him to read it of a memory mapped file that we will create from the own application. You have an example of use of memory mapped files in the trick: [381] - Share data between two Delphi applications We will send the data from the DLL to the application through user's message. You have other tricks in where this technique is also used, for example: [162] - Prevent double instance of your application Well, let's go with the example:
DLL that installs the Hook: · Make the skeleton of a DLL (File - New - DLL) · Change the code of the project for this another:
library Project1; { Demo de Hook de teclado Como lo que queremos es parte de Windows, necesitamos llamará el Hook en una DLL, que }
a nivel de sistema, Radikal. capturar las teclas pulsadas en cualquier instalar la funcion CallBack a la que es ésta misma.
uses Windows, Messages; const CM_MANDA_TECLA = WM_USER + $1000; var HookDeTeclado : HHook; FicheroM : THandle; PReceptor : ^Integer; function CallBackDelHook( Code wParam lParam )
: : : :
Integer; WPARAM; LPARAM LRESULT; stdcall;
{Esta es la funcion CallBack a la cual llamará el hook.} {This is the CallBack function called by he Hook} begin {Si una tecla fue pulsada o liberada}
683
{if a key was pressed/released} if code=HC_ACTION then begin {Miramos si existe el fichero} {if the mapfile exists} FicheroM:=OpenFileMapping(FILE_MAP_READ,False,'ElReceptor'); {Si no existe, no enviamos nada a la aplicacion receptora} {If dont, send nothing to receiver application} if FicheroM<>0 then begin PReceptor:=MapViewOfFile(FicheroM,FILE_MAP_READ,0,0,0); PostMessage(PReceptor^,CM_MANDA_TECLA,wParam,lParam); UnmapViewOfFile(PReceptor); CloseHandle(FicheroM); end; end; {Llamamos al siguiente hook de teclado de la cadena} {call to next hook of the chain} Result := CallNextHookEx(HookDeTeclado, Code, wParam, lParam) end; procedure HookOn; stdcall; {Procedure que instala el hook} {procedure for install the hook} begin HookDeTeclado:=SetWindowsHookEx(WH_KEYBOARD, @CallBackDelHook, HInstance , 0); end; procedure HookOff; stdcall; begin {procedure para desinstalar el hook} {procedure to uninstall the hook} UnhookWindowsHookEx(HookDeTeclado); end; exports {Exportamos las procedures...} {Export the procedures} HookOn, HookOff; begin end.
Now record the project with the name: ' HookTeclado.dpr' and compile it (Project Build All), and you will have generated the DLL of the project.
Receiver application · Make a new empty application · Put a TMemo (Memo1) in the form · Change the form's unit code by this other: 684
unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; const NombreDLL CM_MANDA_TECLA
= 'HookTeclado.dll'; = WM_USER + $1000;
type THookTeclado=procedure; stdcall; type TForm1 = class(TForm) Label1: TLabel; Memo1: TMemo; Button1: TButton; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Private declarations } FicheroM : THandle; PReceptor : ^Integer; HandleDLL : THandle; HookOn, HookOff : THookTeclado; procedure LlegaDelHook(var message: TMessage); message CM_MANDA_TECLA; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin {No queremos que el Memo maneje el teclado...} {We dont want that the memo read the keyboard...} Memo1.ReadOnly:=TRUE; HandleDLL:=LoadLibrary( PChar(ExtractFilePath(Application.Exename)+ NombreDLL ) ); if HandleDLL = 0 then raise Exception.Create('No se pudo cargar la DLL'); @HookOn :=GetProcAddress(HandleDLL, 'HookOn'); @HookOff:=GetProcAddress(HandleDLL, 'HookOff'); IF not assigned(HookOn) or not assigned(HookOff) then
685
not assigned(HookOff) then raise Exception.Create('No se encontraron las funciones en la DLL'+#13+ 'Cannot find the required DLL functions'); {Creamos el fichero de memoria} FicheroM:=CreateFileMapping( $FFFFFFFF, nil, PAGE_READWRITE, 0, SizeOf(Integer), 'ElReceptor'); {Si no se creó el fichero, error} if FicheroM=0 then raise Exception.Create( 'Error al crear el fichero'+ '/Error while create file'); {Direccionamos nuestra estructura al fichero de memoria} PReceptor:=MapViewOfFile(FicheroM,FILE_MAP_WRITE,0,0,0); {Escribimos datos en el fichero de memoria} PReceptor^:=Handle; HookOn; end; procedure TForm1.LlegaDelHook(var message: TMessage); var NombreTecla : array[0..100] of char; Accion : string; begin {Traducimos de Virtual key Code a TEXTO} {Virtual key code to Key Name} GetKeyNameText(Message.LParam,@NombreTecla,100); {Miramos si la tecla fué pulsada, soltada o repetida} {Look if the key was pressed, released o re-pressed} if ((Message.lParam shr 31) and 1)=1 then Accion:='Soltada' {Released} else if ((Message.lParam shr 30) and 1)=1 then Accion:='Repetida' {repressed} else Accion:='Pulsada'; {pressed} Memo1.Lines.Append( Accion+ ' tecla: '+ String(NombreTecla) ); end; procedure TForm1.FormDestroy(Sender: TObject); begin {Desactivamos el Hook} {Uninstall the Hook} if Assigned(HookOff) then HookOff; {Liberamos la DLL} {Free the DLL} if HandleDLL<>0 then FreeLibrary(HandleDLL);
686
{Cerramos la vista del fichero y el fichero} {Close the memfile and the View} if FicheroM<>0 then begin UnmapViewOfFile(PReceptor); CloseHandle(FicheroM); end; end; end.
· Record the project in the same directory of the project of the DLL and compile the application. If you have followed the steps until here, you will have in the directory of the two projects a DLL (HookTeclado.DLL) and the executable of the receiver application. Execute it, and you will see in the Memo1 all the keys pressed in Windows. If you only wanted an example that works, it is not necessary that you continue reading. If you want to know a little more than like the invention works... so... here you have it, step to step: We go starting from the event OnCreate of the application: First, we put the Memo1 to readonly. Imagine for what reason, or better, it proves to not putting it, to see that it happens...:)
procedure TForm1.FormCreate(Sender: TObject); begin {No queremos que el Memo maneje el teclado...} {We dont want that the memo read the keyboard...} Memo1.ReadOnly:=TRUE;
Now we load the DLL that we will suppose that it will be in the same directory that our executable one. If there was some problem when loading it, we generate an exception, in such a way that the following code would not be executed. HandleDLL:=LoadLibrary( PChar(ExtractFilePath(Application.Exename)+ NombreDLL ) ); if HandleDLL = 0 then raise Exception.Create('No se pudo cargar la DLL');
687
· Once loaded the DLL, we look for the two functions that they should be in it. If they are not... we generate an exception. @HookOn :=GetProcAddress(HandleDLL, 'HookOn'); @HookOff:=GetProcAddress(HandleDLL, 'HookOff'); IF not assigned(HookOn) or
· Now, we make a memory mapped file, which will use to keep the handle of our form, the DLL will taste this way like who must send him the message with the key that has been pressed just reading this file.
· Once we have the memory mapped file, and a view pointing to it, we record the ·handle Well, of now that in our DLL HookOn: thesee form in itit,happens and we activate thewhen Hook,calling callingtotothe thefunction procedure HookOn of the DLL:
688
{procedure for install the hook} begin HookDeTeclado:=SetWindowsHookEx(WH_KEYBOARD, @CallBackDelHook, HInstance , 0); end;
As you see, there is not more than a call to SetWindowsHookEx, to install a hook at system level (0 in the last parameter) that will execute the function CallBackDelHook with each message that it captures. · Let us see that it makes the function CallBackDelHook when it is executed by the hook: First, it checks that the function has been called by a new keyboard event, by means of the if code=HC_ACTION. function CallBackDelHook( Code wParam lParam )
: : : :
Integer; WPARAM; LPARAM LRESULT; stdcall;
{Esta es la funcion CallBack a la cual llamará el hook.} {This is the CallBack function called by he Hook} begin {Si una tecla fue pulsada o liberada} {if a key was pressed/released} if code=HC_ACTION then begin
If it is this way, that is to say that is a new keyboard event that it is necessary to assist... the first thing that we should make is so to look for the handle from the application to which should send the message with the data of the pressed/released key, which have kept by heart in a file from the application, we try to open the file, and to read this handle, and if everything goes well, we send the message by means of a PostMessage: {Miramos si existe el fichero} {if the mapfile exists} FicheroM:=OpenFileMapping(FILE_MAP_READ,False,'ElReceptor'); {Si no existe, no enviamos nada a la aplicacion receptora} {If dont, send nothing to receiver application} if FicheroM<>0 then begin PReceptor:=MapViewOfFile(FicheroM,FILE_MAP_READ,0,0,0); PostMessage(PReceptor^,CM_MANDA_TECLA,wParam,lParam);
689
once sent the message, we free the file: UnmapViewOfFile(PReceptor); CloseHandle(FicheroM); end; end;
later, should call to next hook: {Llamamos al siguiente hook de teclado de la cadena} {call to next hook of the chain} Result := CallNextHookEx(HookDeTeclado, Code, wParam, lParam) end;
Well, do we have installed a hook that captures the keyboard events and does it forward them to our application... which the following step is?, of course... to make something to receive it... We will have to capture user's message that we have been defined:
and course, the get corresponding in the implementation that of which we will adding this procedure line in the private part of the form:part:
690
then Accion:='Soltada' {Released} else if ((Message.lParam shr 30) and 1)=1 then Accion:='Repetida' {repressed} else Accion:='Pulsada'; {pressed} Memo1.Lines.Append( Accion+ ' tecla: '+ String(NombreTecla) ); end;
In this example, I simply translate the data of the pressed/released key, translating it to its key name and adding it to the TMemo. If you want more information on the parameters than the function will receive, revise the help file Win32.hlp looking for the topic ' KeyboardProc'. There you will see the meaning of the parameters wParam and lParam that you will receive in the function. For I finish, we have left to undo this whole invention when we leave the application. The OnDestroy event of the application: First, uninstall the hook, calling to the HookOff function of the DLL. Care, is to use the if Assigned, because if there has been some problem when loading the DLL in the OnCreate... now we would try to execute something that was not initialized.
procedure TForm1.FormDestroy(Sender: TObject); begin {Desactivamos el Hook} {Uninstall the Hook} if Assigned(HookOff) then HookOff;
Now, free the DLL:
{Liberamos la DLL} {Free the DLL} if HandleDLL<>0 then FreeLibrary(HandleDLL);
And the file:
691
{Cerramos la vista del fichero y el fichero} {Close the memfile and the View} if FicheroM<>0 then begin UnmapViewOfFile(PReceptor); CloseHandle(FicheroM); end; end;
692
Looping AVI
We will use for it the OnNotify event of the TMediaPlayer component. · Put this code in the OnCreate event of the MediaPlayer:
procedure TForm1.MediaPlayer1Notify(Sender: TObject); begin With MediaPlayer1 do If NotifyValue = nvSuccessful Then Begin Notify := True; Play; End; end;
693
Bitmap en negativo Reverse Bitmap (negative) Enviado Por (Send By): Nacho Urenda Web : N.A. Email: [email protected] Fecha (Date): 13/10/00
Tip accessed 7307 times
This tip it's only for Delphi 32 bits (Delphi 2 or later...) · Add Windows in the uses of your form · Add these lines behind the uses line of your form: const MaxPixelCount
=
32768;
type pRGBArray TRGBArray
= =
^TRGBArray; ARRAY[0..MaxPixelCount-1] of TRGBTriple;
· Now, put a TImage (Image1) and load in it a picture · Out a TButton (Button1) and put this code in its OnClick event:
procedure TForm1.Button1Click(Sender: TObject); procedure NegativeBitmap(OrigBmp, DestBmp: TBitmap); var i, j: integer; tmpbmp: TBitmap; OrigRow, DestRow: pRGBArray; begin { Creamos un bitmap temporal. Esto nos permite usar el mismo bitmap para entrada y salida Create a temporal bitmap. This allows to use the same bitmap as input or output } tmpbmp := TBitmap.Create; try
{ Asignamos al bitmap temporal las características del
original the
Assign the temporal bitmap the same characteristics as
694
the original } tmpbmp.Width := OrigBmp.Width; tmpbmp.Height := OrigBmp.Height; OrigBmp.PixelFormat := pf24bit; tmpbmp.PixelFormat := OrigBmp.PixelFormat; { Para cada fila / for each row } for i := 0 to tmpbmp.Height -1 do begin { Asignamos Scanline actual / assign current ScanLine } OrigRow := OrigBmp.ScanLine[i]; DestRow := tmpbmp.ScanLine[i]; { Para cada columna / for each column } for j := 0 to tmpbmp.Width - 1 do begin { Cambiamos los valores de rojo, verde y azul Change red, green, blue values } DestRow[j].rgbtRed := 255 - OrigRow[j].rgbtRed; DestRow[j].rgbtGreen := 255 - OrigRow[j].rgbtGreen; DestRow[j].rgbtBlue := 255 - OrigRow[j].rgbtBlue; end; end; { Asignamos el bitmap en negativo al bitmap de destino Assign the negative bitmap to the destination bitmap } DestBmp.Assign(tmpbmp); finally { Destruimos bitmap temporal / Destroy temp bitmap } tmpbmp.Free; end; end; begin NegativeBitmap(Image1.Picture.Bitmap, Image1.Picture.Bitmap); end;
Sent by Nacho Urenda:
This is a improve of the code:
{
DestRow[j].rgbtRed := DestRow[j].rgbtRed xor OrigRow[j].rgbtRed;
695
DestRow[j].rgbtRed := not OrigRow[j].rgbtRed; DestRow[j].rgbtGreen := not OrigRow[j].rgbtGreen; DestRow[j].rgbtBlue := not OrigRow[j].rgbtBlue;
Sent by Nacho Urenda: The most effective method it's to use the InvertRect function of the Window's API:
procedure InvertBitmap(Bmp: TBitmap); begin InvertRect(Bmp.Canvas.Handle, Bmp.Canvas.ClipRect); end;
696
697
Windows users to brighten their desktops with. Some of the other advantages of placing code and resources in a dynamic link library are: ·
Disk space is saved if multiple applications use a dynamic link library. This can sometimes make a drastic difference in the cost of deploying your applications since you can place common functions and resources in a single .DLL file, reducing the size of your executables.
·
The ability to upgrade and replace parts of the program without shipping a whole new executable.
·
The ability to load and execute different resources and code based on some specific criteria available only at runtime. A good example would be "PlugIn" applets that can be dynamically loaded by your application at runtime, without knowing what "PlugIns" might be available at compile time. This can make your program automatically extendible with after-market products.
Loading a dynamic link library Your compiler most likely includes interface files to most of the Windows system functions. You simply link to the interface files, and any dynamic link libraries that are used by your program are automatically loaded when your program is started. In 16 Bit Windows, the actual function code is not added to your program, but rather a pointer to the module name of the dynamic link library and the name of the function (or its exported index number) is added. In Win32, the DLL is mapped into the process' address space. The dynamic link library's file must exist at runtime or an error will occur. If your compiler does not include an interface to the function or dynamic link library you want to use, you are going to have to declare a function prototype, and a linking interface, to let your compiler know about the function you are dynamically linking to. The instructions to do this should be included with your compiler's documentation. A short example of a function prototype in Pascal for a dynamic link library would be: {$IFDEF Win32} function MessageBeep(BeepType: Integer):Bool stdcall; external 'USER32'; {$ELSE} procedure MessageBeep(BeepType: Integer); far; external 'USER'; {$ENDIF} This prototype tells the compiler to link to a function named "MessageBeep" located in USER32.DLL (if compiling under 32 bits), or a procedure named "MessageBeep" located in USER.EXE (if compiling under 16 bits). The compiler is also made aware that "MessageBeep" takes a integer size parameter, and uses the "stdcall" passing convention (if compiling under 32 bits). When declaring an interface to a dynamic link library, you must be careful to specify the way that the function expects to have its parameters and return value passed, and who will maintain the stack. There are various calling techniques used including: PASCAL, CDECL, STDCALL, and FASTCALL. In 16 bit Windows, most functions are exported using the PASCAL calling convention. In 32 bit Windows, STDCALL is preferred since this should be compatible w/most other languages. Most API functions are declared with "C" prototypes. If you are wanting to port an interface to a Pascal based language, and you are not familiar with "C", your best bet is to get Alain Tadros's technical paper "Using Borland's Delphi and C++ Together". It is available from Compuserve, and the Borland BBS system and has some excellent additions by Eric Uber. Dynamic loading at runtime Loading a dynamic link library at runtime allows you to load dynamic link libraries that are not known at compile time, or load a library for only a short time. Printer drivers are a good example: You don't know what printer drivers will be available on a users system until runtime, and you would only want to load a printer
698
driver during the printing phase of your program. One of the advantages to loading libraries dynamically is that you don't use up resources (memory) until you actually load the library and the function you wish to call. You load a dynamic link library at runtime by calling the Windows API function LoadLibrary(). Then you can search for a given function within the library using the Windows API function GetProcAddress(). Once you have the address of the function you want, you can call it from your code. One thing to remember: Always call the Windows API function FreeLibrary() when you are done using the dynamic link library. Example: procedure Win31LoadUserAndCallBeepFn; var DllHandle: THandle; BeepFn: procedure(BeepType: Integer); begin DllHandle := LoadLibrary('USER'); if DllHandle >= 32 then begin @BeepFn := GetProcAddress(DllHandle, 'MessageBeep'); if @BeepFn <> nil then BeepFn(-1); FreeLibrary(DllHandle); end; end;
procedure Win32LoadUserAndCallBeepFn; var DllHandle: THandle; BeepFn: function(BeepType: Integer): Bool stdcall; begin DllHandle := LoadLibrary('USER32'); if DllHandle <> 0 then begin @BeepFn := GetProcAddress(DllHandle, 'MessageBeep'); if @BeepFn <> nil then BeepFn($FFFFFFFF); FreeLibrary(DllHandle); end; end; Here we have two code snippets showing the differences between loading a library under 16 and 32 bit Windows. In this case, we must mark the function we are calling as using the "stdcall" calling convention under 32 bit Windows, where under 16 bit windows the "pascal" calling convention is assumed. Note that the 16 bit version of MessageBeep() is a procedure, and the 32 bit version is a function. The LoadLibrary() function takes a null-terminated string that names the library file to be loaded. If the string does not contain a path, Windows searches for the library in this order: the current directory; the Windows directory; the Windows system directory; the directory containing the executable file for the current task; the directories listed in the PATH environment variable; and finally, the list of directories mapped in a network. If no extension is used, the extension of ".DLL" is assumed. We check the value of the handle returned by LoadLibrary() for an error. Under 16 bit Windows, the return value will be less than 32 if an error occurs, and under 32 bit Windows, the return value will be 0 if an error occurs. We then call the GetProcAddress() function to get the address of the MessageBeep() function. The spelling of the function name is critical, and can be case sensitive under different environments. If GetProcAddress() successfully returns the address of the function, then we can safely call the function. Finally we unload the library to free up memory. Resource only dynamic link libraries As mentioned, dynamic link libraries can hold resources as well. To use resources contained in a dynamic link library, you simply make a call to LoadLibrary(), then you can load resources from the library using one of the Windows API resource functions such as LoadBitmap(), passing the handle to the dynamic link library that you received from the LoadLibrary() call. Just don't forget to free your resources and the library when you are done.
699
You can use the snipit from the above section "Loading dynamic link libraries at runtime" to load a dynamic link library, and then load a resource from the library. Example: LoadBitmap(DllHandle,'SOMEBMP'); Stack usage and dynamic link libraries When you call a function in a dynamic link library, the library generally uses the stack of the calling application. Applications running under 16 Bit Windows had much less stack space available (<64k) than what is available under 32 bit Windows (1 MB). Since functions in a dynamic link library can call functions in other dynamic link libraries as well as making callbacks, you should always thoroughly test your program to make sure you have setup adequate stack space. Callback functions Callback functions are a really wonderful feature of the Windows environment. Callback functions allow you to send the "instanced" address of a function in your application to a function contained in a dynamic link library. The dynamic link library function can then call back to the function in your application to pass back information. A good example of a Windows API function that utilizes callbacks is the EnumFonts() function. The EnumFonts() function enumerates fonts available to a given device context. For each font enumerated, the EnumFonts() function will call back to a function in your application, passing back information about that font. This process continues as long as their are more fonts to enumerate, or until your function returns zero, meaning that you wish to stop further enumerations. Most Windows API functions that accept a callback function as a parameter will also accept an lpData parameter. This lpData parameter is used for "application defined data". You are free to define what this parameter is used for. The lpData parameter is usually passed to the dynamic link library as a longint, and rarely as a far pointer. It is likely you will want to send something besides a longint. You can always typecast a pointer to a data structure in your application to a longint when calling dynamically linked functions, and typecast the longint passed back to your callback function to a pointer so your may access your data structure. As mentioned, when your callback function is called by the Windows API function, the lpData parameter you originally passed to the Windows API function will be passed back to your callback function for your use. This avoids having to declare a global variable in your program. A good example of how this works would be if you were to create a program containing a dialog box to show all the available fonts in a drop-down listbox. When you process the WM_INITDIALOG message, you call the EnumFonts() function, passing the instanced address of your callback function that will process the font information. Your callback function will perform the work to add each font's name to the listbox. The only problem: your callback function does not have a handle to the listbox. You could create a global variable to hold the listbox handle, but instead, you should pass the handle of the listbox to your callback function through the lpData parameter, and let the dynamically linked function pass the handle of the listbox back to your callback function. While it is always nice to keep global variables to a minimum, having the ability to pass user defined data to the callback function becomes critically important when you create callback functions inside a 16-bit dynamic link library. Generally, under 16-bit Windows, a dynamic link library has only one data segment. Since multiple applications can use a single dynamic link library, and, since a 16-bit dynamic Link Library has a single data segment, any global variables defined in the dynamic link library exist in memory only once. If you create a callback function in a dynamic link library, and use a global variable to store data used during the callback, you run the risk of data corruption. Consider the following scenario: You have written several applications that use your font dialog box. Rather than include your font dialog in each application, you opt to store the dialog in a dynamic link library. Suppose that you use a global variable in the dynamic link library to store the listbox handle for your callback function. Now suppose that more than one application loads the dialog box at the same time. Each "copy" of your font dialog box contains a different handle to it's listbox. Clearly, using a global variable to hold the listbox handle will not work since the listbox handle will be different for each dialog, and the global variable used to hold this handle will get trashed. The way around this, is to pass the listbox handle to the EnumFonts() function using the lpData parameter. This is not a problem, when writing 32-bit dynamic link libraries for use under Windows 95 and Windows NT, as under both environments, dynamic link libraries have a separate data area for each application that loads
700
them. None the less, it is still a good practice to always keep global variables to a minimum. The Hook functions A "hook" function is a callback function that can be inserted in the Windows message system so an application can access the message stream before other processing of the message takes place. Often times, there will be other hooks already installed in the messaging system, so there will be times you will need to call the next hook in the "Hook Chain" to allow the other hooks in the chain to access the messages as well. When you install a hook function using the Windows API function SetWindowsHookEx(), you will receive a 32 bit handle to your installed hook function. You will use this handle to both remove the hook when you are done via the Windows API function UnHookWindowsHookEx(), and when calling the next hook in the "Hook Chain" via the Windows API function CallNextHookEx(). We have chosen to use the Windows "Ex" hook functions since we want to be able to port to 32 bit Windows. System wide hooks require that your hook filter function reside in a dynamic link library. The filter function for an application specific hook may reside either in the application or a dynamic link library. Different types of Windows Hooks: Windows defines the following types of hook functions: ·
WH_CALLWNDPROC- A window procedure hook called whenever the SendMessage() function is called.
·
WH_CBT- A computer based training hook called before activating, creating, destroying, minimizing, maximizing, moving, or sizing a window; before completing a system command; before removing a mouse or keyboard event; before setting input focus; and before synchronizing the system message queue.
·
WH_DEBUG-A debugging hook called before any other filter installed by the SetWindowsHookEx() function is called.
·
WH_GETMESSAGE- A message hook called whenever the GetMessage() function has retrieved a message from an application queue.
·
WH_HARDWARE- A nonstandard hardware message hook called whenever the application calls the GetMessage() or PeekMessage() function and there is a hardware event (other than a mouse or keyboard event) to process.
·
WH_JOURNALRECORD- A journaling record hook called when the system removes messages from the system message queue.
·
WH_JOURNALPLAYBACK- A journaling playback hook used to insert mouse and keyboard messages into the system message queue.
·
WH_KEYBOARD- A keyboard hook called whenever the application calls the GetMessage() or PeekMessage() function and there is a WM_KEYUP or WM_KEYDOWN keyboard message to process.
·
WH_MOUSE- A mouse message hook called whenever the application calls the GetMessage() or PeekMessage() function and there is a mouse message to process.
·
WH_MSGFILTER- An application message hook called after a dialog box, message box, or menu has retrieved a message, but before the message is processed.
·
WH_SHELL- A shell application hook called when notification messages from the system have been made.
·
WH_SYSMSGFILTER- A system wide message hook called after a dialog box, message box, or menu has retrieved a message, but before the message is processed.
Note: Debugging Windows hooks can be very difficult. The documentation has much to be desired, so before you make the jump, always consult the Microsoft Knowledge Base for any additional information regarding the hook you are designing for. If your hook hangs the system, pressing CTRL+ESC or
701
CTRL+AlT+DEL should unhook any system wide hooks that are in effect. Data storage in dynamic link libraries Under 16 bit Windows, a dynamic link library is only loaded once no matter how many applications request the library to be loaded, generally contains a single data segment, and all global variables in the dynamic link library exist across all applications that load the dynamic link library. If any process causes corruption in the data segment of the dynamic link library, other applications using the dynamic link library can be affected. Many argue that sharing data between applications through a 16 bit dynamic link library is considered a very bad practice. Under Windows 95, the code for a dynamic link library is only loaded once no matter how many applications request the library to be loaded, and the link to a running application gets its own data storage area. This provides some additional crash protection to other applications, since any corrupted global variables in a dynamic link library caused by one application will not adversely affect other applications using that dynamic link library. Under Windows NT, a separate copy of both the dynamic link library's data and code is linked to the calling application. If we think about how the DLL is loaded, we can still realize a memory saving when we talk about physical memory usage. Each process does indeed gets its own copy of the DLL and the DLL data. However, it obtains this through a memory mapping of the DLL. That is, the DLL code is mapped to each processes address space. In physical memory (as seen by the system) the DLL is loaded only once. Instanced addresses and thunking As already mentioned, 16-bit dynamic link libraries have only one data segment. When you call a function in a dynamic link library, the data segment must be switched from your application's data segment to that of the dynamic link library's data segment, and back again after the function returns. If the dynamically linked function happens to make a callback to a function in your application, the data segment must be switched back to your application's data segment during the callback, and back again to the dynamic link library's data segment when your function returns. When making a call to a dynamically linked function, the fixup code to switch the data segment is transparent. In the case of a callback, additional magic is needed by your application. The magic is made possible by using the Windows API function MakeProcInstance() to receive an "instance thunk" to your callback procedure. This "instance thunk" is the glue that holds your callback function and your data segment together during the callback. The procedure address passed to the MakeProcInstance() function needs to be marked with both the "far" and "exported" attributes. After the dynamic link library is through using your callback function, and returns, you should always call the Windows API FreeProcInstance() function to free the instance thunk. Using the MakeProcInstance() function is not necessary in 32 bit applications, or any dynamic link library (16 or 32 bit). It is, however, safe to use under these conditions, so any code you have that uses these functions can be safely ported to dynamic link libraries and 32 bit versions of Windows.
The Windows API: An Example Of Use - by Abstract:This paper is targeted to developers who wish an example of interfacing to the Windows Application Programming Interface (API).
The Windows API: An Example Of Use, Part 2 By Joe C. Hecht, Borland Delphi Technical Support Group HOOKIT! - The example The Journal Hooks: JournalRecord and JournalPlayback How HOOKIT! works HOOKIT! application logic HOOKLIB dynamic link library logic Got-Ya's! Source code for HOOKLIB.DLL Resource statements to build HOOKIT16.RES and HOOKIT32.RES 702
Source code for HOOKIT.EXE Part III: Conclusion The example: We will now present an example of using Windows API functions by creating an application that serves the useful function of hooking into the Windows messaging system, and recording any keyboard and mouse input to be used for later playback. We will call our macro recording application "Hookit!". Our "Hookit!" application is a great starting place for a full featured Windows macro recorder, reminiscent of the early days of DOS and my favorite "Sidekick's Sidekick", Borland's highly successful TSR program "SuperKey"! It's worth noting that unlike 16 bit Windows, neither Windows 95 or Windows NT shipped with a macro recording feature. Since "Hookit!" will use system wide Windows hook functions for JournalRecord and JournalPlayback, we will also create a Windows dynamic link library along the way! Both the application and the dynamic link library will perform callbacks. Pascal was originally designed to be a teaching language, thus the code example presented was written in Pascal, and can easily be ported to other languages such as "C". Note that the code cannot be ported to Visual Basic, since, as of this writing, Visual Basic does not support creating dynamic link libraries or callback functions. The code presented may also be directly ported between 16 and 32 bit compilers, and will successfully compile with Borland's Turbo Pascal for Windows 1.5, Borland Pascal for Windows 7.0 / 7.01, Delphi 1.0 / 1.02, and Delphi 2.0. To make it easy to port to other languages, no Pascal or Delphi specific features are used. The code also serves as a model for creating a "traditional" Windows program in Delphi, and utilizing a dialog box template for the main window of a program. Although "Hookit!" uses features designed to run on Windows 3.1 or above, the example code can be adapted to run on earlier versions of Windows (2.x and 3.0). The Journal Hooks: JournalRecord and JournalPlayback The Journal hook functions provide a easy way to record keyboard and mouse events on a system-wide basis, and play the events back at some later date. You can install a JournalRecord or JournalPlayback hook callback function by calling the Windows API function SetWindowsHookEx(), and passing the address of your hook callback function. An instanced address is not needed, as both the JournalRecord and JournalPlayback callback functions are system wide hooks, and must reside in a dynamic link library. Calling SetWindowHookEx() will return a 32 bit handle to your hook callback function for you to identify yourself to other hook functions already in the "Hook Chain", and to remove your hook from the chain when you are done recording. When you call SetWindowsHookEx() with the address of your JournalRecord callback function, Windows will return immediately, and the recording begins. Your JournalRecord callback function will get called with a code indicating the following conditions: ·
There is a keyboard or mouse event to record.
·
The system is entering a modal state.
·
The system leaving a modal state.
·
The system wants you to call the next hook in the hook chain.
If there is a keyboard or mouse event, the code will equal "HC_ACTION", and you are free 703
to record the event. A pointer to an EVENTMSG is passed to you in the lParam parameter of your JournalRecord callback function. The system time that the event was originally fired is contained in time parameter of the EVENTMSG structure. For playback purposes, you will need to make a copy of the EVENTMSG and change the time in the copy to reflect the net time into the recording, since you will need to synchronize the playback of the message to the system time at playback. This is done by getting the system time when you start the recording, and subtracting it from the message time. If the system enters a modal state, the code will equal "HC_SYSMODALON", indicating something bad has happened to the system, and you should temporarily stop recording, and call the next message hook in the "Hook Chain", so hooks further down the chain will know what is going on. When the system returns from a modal state, the code will equal "HC_SYSMODALOFF", and you may resume recording. Finally, if the code is less than zero, the system is asking you to call the next hook in the chain, and you should do so without further processing. When you are through recording, you can simply call the Windows API function UnHookWindowsHookEx() passing back the 32 bit handle given to you when you originally called the SetWindowsHookEx() function, and Windows will remove your hook callback function from the "Hook Chain". When you are ready to playback your recording, you will call SetWindowsHookEx() again, passing the address of your JournalPlayback callback function. Windows will return immediately, and the playback begins. During playback, normal mouse and keyboard input is automatically disabled by the system. Your JournalPlayback callback function will get called with a code indicating one the following conditions: ·
HC_SKIP- You should retrieve the next message. If there are no more messages to play, then you may safely call the Windows API function UnHookWindowsHookEx(), passing the handle to your hook function to end the playback.
·
HC_GETNEXT- You should play the current message.
·
HC_SYSMODALON- The system is entering a modal state. This indicates something bad has happened. You should call the next hook in the hook chain, so other hooks will know something is up.
·
HC_SYSMODALOFF- The system leaving a modal state, and Windows has unhooked your JournalPlayback callback procedure right out from under you. You are done. As your last act, you should call the next hook in the hook chain, so other hooks will know they are hosed as well.
·
Code < 0- The system wants you to call the next hook in the hook chain without further processing.
You should go ahead and retrieve the first message before you call the SetWindowsHookEx() function, since the system will ask you to play the first message before requesting the next message. You will also need to get the system time, since you will need to "fix up" the playback time of each of your recorded messages to synchronize with the system time at playback. Windows may ask you to play the same message more than once. The first time Windows asks you to play the current message, your JournalPlayback callback function should return the difference between the current time and the time the message is scheduled to play. If the difference between the current time and the time the message is scheduled to 704
play is negative, your JournalPlayback callback function should return zero. The JournalPlayback callback function must also return zero if the same message is requested to be played more than once. How HOOKIT! works Our application (HOOKIT.EXE) will contain four buttons: ·
Start Recording
·
Stop Recording
·
Playback
·
Done
and one callback function: ·
PlaybackFinished()
Our dynamic link library (HOOKLIB.DLL) will contain three functions that our calling application will use: ·
StartRecording()
·
StopRecording()
·
Playback()
and two hook functions that Windows will use: ·
JournalRecordProc()
·
JournalPlaybackProc()
HOOKIT! application logic: ·
Allow the program to start only if the version of Windows is equal or greater than Window's 3.1.
·
We will need to supply a callback function to be called whenever a macro's playback has finished, since the Playback() function will return immediately, and our application will continue to execute during playback. We will need to call MakeProcInstance() to get the instanced address of our callback function PlaybackFinished() to pass to the Playback() function. Since we cannot call FreeProcInstance() in the middle of the callback, we must declare a global variable to hold the instanced address of PlaybackFinished() on program startup, and free it on exit from the program.
·
Create a main window from a Dialog Box template.
·
Upon creation of our main window, we will enable the "Start Recording" and "Done" button. We will disable the "Stop Recording" and "Playback" button, since we have no recording to stop or playback.
·
When the "Done" button is selected we will free the instanced address of our callback function PlaybackFinished() and exit the program.
·
When the "Start Recording" button is selected, we will disable the "Start Recording" 705
and "Done" button, since we don't want to allow more than one recording at a time, and we don't want to allow the user to quit in the middle of recording. We will enable the "Stop Recording" button and call our StartRecording() function. If the StartRecoding() function returns an error, we will announce the error to the user, and reset the buttons to their default state before the "Start Recording" button was pressed. ·
When the "End Recoding" button is pressed, We will call the StopRecording() function passing it the filename to store the macro in. Then we will enable both the "Done" and "StartRecording" buttons, since we can now allow the user to quit, and we want the user to have the option to record or rerecord the session. We will enable "Playback" button only if anything has successfully been recorded.
·
When the "Playback" button is selected, we will disable all of our buttons, and call the Playback() function, passing it the filename of the macro to play, the instanced address of our callback function PlaybackFinished(), and a handle to our main window to be passed back to us as application defined data. When the macro playback is done, our hook function will callback to our program's PlaybackFinished() function, letting us know it has finished, and passing us back the handle to our main window as application data. We must do this since the Playback() function will return immediately, causing our program to continue to run during the macro's playback. This will allow us to know when it safe to enable our program's buttons again. If the Playback() function returns an error, we will reset the buttons to their default state before the "Playback" button was pressed.
·
When our callback function PlaybackFininshed() is called, we can enable the "Start Recording", "Playback" and "Done" buttons.
HOOKLIB dynamic link library logic: ·
If an error occurs during the call to StartRecord(), or, we are already recording or playing, we will return zero without further processing.
·
During the recording process, we will save keyboard and mouse events to an array. For the sake of simplicity, we will limit the number of recorded events to what will fit in a 64k memory block.
·
When StopRecording() is called, if any events have been recorded, we will write the recorded events to the disk for later playback. We will then unhook our JournalRecordProc() from the hook chain. We will return an error code of -1 if nothing is currently recording or -2 if there was trouble unhooking from the "Hook Chain". Otherwise we will return the number of messages recorded.
·
When the Playback() function is called, we will start the playback. If an error occurs or their is nothing to playback, we will return zero without further processing.
·
When the playback is finished, we will callback to the application announcing we are done playing back the macro.
·
Since the SetWindowsHookEx() function does not allow us an appdata parameter for application defined data, we must declare several global variables for our hook callback functions, and avoid letting more than one application invoke either the StartRecord() and Playback() functions at any given time under Windows 3.1.
·
We will define a global pointer called "PMsgBuff "that will point to an array of EventMsg structures to record to and playback from. Upon library startup, we will 706
initialize this pointer to nil. Only when we are recording or playing a macro will this pointer actually point to a memory block, otherwise it will be nil. This will give us a way to determine whether or not we are in the process of recording or playing a macro. ·
We will also need to define global variables for: o
"TheHook"- A 32 bit handle to our hook proc.
o
"StartTime"- The starting time of the recording or playback.
o
"MsgCount"- The total number of messages recorded.
o
"CurrentMsg"- The current message playing.
o
"ReportDelayTime"- If we should report a delay time.
o
"SysModalOn"- If the system is currently in a "modal" state.
o
"cbPlaybackFinishedProc"- The instanced address of the application's PlaybackFinished() callback function.
o
"cbAppData"- The user defined application data parameter passed to our Playback() function.
Got-Ya's! ·
JournalRecordProc() is incorrectly documented as receiving a MSG structure. JournalRecordProc() actually receives the same EVENTMSG structure that is documented by JournalPlaybackProc().
·
The JournalRecordProc() and JournalPlaybackProc() hook procedures do not provide a user defined lpData parameter, so you must use global variables in your dynamic link library.
·
Mouse events recorded with JournalRecord may not play back correctly if the display resolution has changed, or a window's position has changed.
·
Keyboard events recorded with JournalRecord on a Windows system other than Windows NT will not play back correctly under Windows NT. Under Windows 3.1, the keyboard repeat count is stored in the ParamH parameter of the EVENTMSG structure. Under Windows NT, this parameter is always 1. The size of the EVENTMSG structure also changes under Win32.
·
Your JournalPlaybackProc may be called many times with the HC_GETNEXT message. The system expects you to continue providing the same event to play until you receive a HC_SKIP message.
·
Be sure to return a non-zero delay time only once for each unique event you process in your JournalPlaybackProc, else Windows NT may hang due to timing differences. If your JournalPlaybackProc() gets called with a code of HC_GETNEXT more than once for the same event, return zero from your JournalPlaybackProc.
·
Interactive debugging of a journal hook cannot be done on a single machine. A Windows NT or Win32 application has an advantage that the system will send a WM_CANCELJOURNAL message to all applications when the system pulls the 707
hook out from under the application when the user presses CTRL+ESC or CTRL+ALT+DEL. "HOOKIT!" handles this event gracefully. ·
The SetWindowsHookEx() function will return immediately. This can be a problem if your program needs to know when the playback has finished.
·
Since you will most likely use the Windows API function GetTickCount() for calculating messaging times, you always run the risk that the system time will wrap if windows has not been restarted in the last 49 days. If you account for the fact that not all compilers support unsigned 32 bit integers, the system time will appear to go negative sometime into the 24th day.
·
The "handle" passed back from SetWindowsHookEx() is 32 bits wide, even in 16 bit environments.
·
You may need to set your compiler's link buffer to compile to disk when building dynamic link libraries. If the file image is only created in memory, it will not be available to the program that uses it.
·
JournalRecordProc() does not get along well with the new Windows 95 "StartKey" contained on some new keyboards.
Source code for HOOKLIB.DLL Note: Save the source as HOOKLIB.PAS for Pascal or HOOKLIB.DPR for Delphi. {$C FIXED PRELOAD PERMANENT} library HOOKLIB; {$IFDEF Win32} uses Windows; type TwMsg = Longint; TwParam = Longint; TlParam = Longint; {$ELSE} uses {$IFDEF VER15} WinTypes, WinProcs, Win31; {$ELSE} {$IFDEF VER70} WinTypes, WinProcs, Win31; {$ELSE} WinTypes, WinProcs; {$ENDIF} {$ENDIF} type TwMsg = Word; TwParam = Word; TlParam = Longint; {$ENDIF} const MAXMSG = 6500;
708
type PEventMsg = ^TEventMsg; TMsgBuff = Array[0..MAXMSG] of TEventMsg; TcbPlaybackFinishedProc = Procedure(AppData: Longint) {$IFDEF Win32} stdcall; {$ELSE} ; {$ENDIF} var PMsgBuff: ^TMsgBuff; TheHook: HHook; StartTime: Longint; MsgCount: Longint; CurrentMsg: Longint; ReportDelayTime: Bool; SysModalOn: Bool; cbPlaybackFinishedProc: TcbPlaybackFinishedProc; cbAppData: Longint; { *********************************************************************** } { function JournalRecordProc(Code: Integer; } { wParam: TwParam; } { lParam: TlParam): Longint; } { Parameters: action to perform and message data. } { Returns: zero unless code < 0, in which case return the result } { from CallNextHookEx(). } { *********************************************************************** } function JournalRecordProc(Code: Integer; wParam: TwParam; lParam: TlParam): Longint {$IFDEF Win32} stdcall; {$ELSE} ; export; {$ENDIF} begin JournalRecordProc := 0; case Code of HC_ACTION: begin if SysModalOn then exit; if MsgCount > MAXMSG then exit; {record the message} PMsgBuff^[MsgCount] := PEventMsg(lParam)^; {set the delta time of the message} Dec(PMsgBuff^[MsgCount].Time,StartTime); Inc(MsgCount); exit; end; HC_SYSMODALON: begin SysModalOn := True;
709
CallNextHookEx(TheHook, Code, wParam, lParam); exit; end; HC_SYSMODALOFF: begin SysModalOn := False; CallNextHookEx(TheHook, Code, wParam, lParam); exit; end; end; if code < 0 then JournalRecordProc := CallNextHookEx(TheHook, Code, wParam, lParam); end; { *********************************************************************** } { function StartRecording: Integer; } { Parameters: none. } { Returns: non zero if successful. } { *********************************************************************** } function StartRecording: Integer {$IFDEF Win32} stdcall; {$ELSE} ; export; {$ENDIF} begin StartRecording := 0; if pMsgBuff <> nil then exit; GetMem(PMsgBuff, Sizeof(TMsgBuff)); if PMsgBuff = nil then exit; SysModalOn := False; MsgCount := 0; StartTime := GetTickCount; TheHook := SetWindowsHookEx(WH_JOURNALRECORD, JournalRecordProc, hInstance, 0); if TheHook <> 0 then begin StartRecording := 1; exit; end else begin FreeMem(PMsgBuff, Sizeof(TMsgBuff)); PMsgBuff := nil; end; end; { *********************************************************************** }
710
{ function StopRecording(lpFileName: PChar): Integer; } { Parameters: pointer to filename to save to. { Returns: number of records written. { -1 if not recording. {
} } }
-2 unable to unhook.
} { *********************************************************************** } function StopRecording(lpFileName: PChar): Longint {$IFDEF Win32} stdcall; {$ELSE} ; export; {$ENDIF} var TheFile: File; begin if PMsgBuff = nil then begin StopRecording := -1; exit; end; if UnHookWindowsHookEx(TheHook) = False then begin StopRecording := -2; exit; end; TheHook := 0; if MsgCount > 0 then begin Assign(TheFile, lpFileName); {$I-} Rewrite(TheFile, Sizeof(TEventMsg)); {$I+} if IOResult <> 0 then begin FreeMem(PMsgBuff, Sizeof(TMsgBuff)); PMsgBuff := nil; StopRecording := 0; exit; end; {$I-} Blockwrite(TheFile, PMsgBuff^, MsgCount); {$I+} if IOResult <> 0 then begin FreeMem(PMsgBuff, Sizeof(TMsgBuff)); PMsgBuff := nil; StopRecording := 0; {$I-} Close(TheFile); {$I+} if IOResult <> 0 then exit; exit; end; {$I-} Close(TheFile); {$I+} if IOResult <> 0 then begin FreeMem(PMsgBuff, Sizeof(TMsgBuff)); PMsgBuff := nil; StopRecording := 0;
711
exit; end; end; FreeMem(PMsgBuff, Sizeof(TMsgBuff)); PMsgBuff := nil; StopRecording := MsgCount; end; { *********************************************************************** } { function JournalPlaybackProc(Code: Integer; } { wParam: TwParam; } { lParam: TlParam): Longint; } { Parameters: action to perform and message data. } { Returns: if Code < 0, returns the result from CallNextHookEx(), } { otherwise returns the requested time to wait to fire } { the next event or zero. } { *********************************************************************** } function JournalPlaybackProc(Code: Integer; wParam: TwParam; lParam: TlParam): Longint {$IFDEF Win32} stdcall; {$ELSE} ; export; {$ENDIF} var TimeToFire: Longint; begin JournalPlaybackProc := 0; case Code of HC_SKIP: begin {get the next message} Inc(CurrentMsg); ReportDelayTime := True; {are we finished?} if CurrentMsg >= (MsgCount-1) then if TheHook <> 0 then if UnHookWindowsHookEx(TheHook) = True then begin TheHook := 0; FreeMem(PMsgBuff, Sizeof(TMsgBuff)); PMsgBuff := nil; {callback to the application announcing we are finished} cbPlaybackFinishedProc(cbAppData); end; exit; end; HC_GETNEXT: begin {play the current message} PEventMsg(lParam)^ :=
712
PMsgBuff^[CurrentMsg]; PEventMsg(lParam)^.Time := StartTime + PMsgBuff^[CurrentMsg].Time; {if first time this message has played - report the delay time} if ReportDelayTime then begin ReportDelayTime := False; TimeToFire := PEventMsg(lParam)^.Time - GetTickCount; if TimeToFire > 0 then JournalPlaybackProc := TimeToFire; end; exit; end; HC_SYSMODALON:begin {something is wrong} SysModalOn := True; CallNextHookEx(TheHook, Code, wParam, lParam); exit; end; HC_SYSMODALOFF:begin {we have been hosed by the system - our hook has been pulled!} SysModalOn := False; TheHook := 0; FreeMem(PMsgBuff, Sizeof(TMsgBuff)); PMsgBuff := nil; {callback to the application announcing we are finished} cbPlaybackFinishedProc(cbAppData); CallNextHookEx(TheHook, Code, wParam, lParam); exit; end; end; If code < 0 then JournalPlaybackProc := CallNextHookEx(TheHook, Code, wParam, end;
lParam);
{ *********************************************************************** } { function Playback(lpFileName: PChar; } { EndPlayProc: TcbPlaybackFinishedProc; } { AppData: Longint): Integer; } { Parameters: pointer to filename to play. } { application's EndPlay callback function. } { application's
713
defined data. { Returns: non zero if successful.
}
} { *********************************************************************** } function Playback(lpFileName: PChar; EndPlayProc: TcbPlaybackFinishedProc; AppData: Longint): Integer {$IFDEF Win32} stdcall; {$ELSE} ; export; {$ENDIF} var TheFile: File; begin Playback := 0; If PMsgBuff <> nil then exit; GetMem(PMsgBuff, Sizeof(TMsgBuff)); If PMsgBuff = nil then exit; Assign(TheFile, lpFileName); {$I-} Reset(TheFile, Sizeof(TEventMsg)); {$I+} if IOResult <> 0 then begin FreeMem(PMsgBuff, Sizeof(TMsgBuff)); PMsgBuff := nil; exit; end; {$I-} MsgCount := FileSize(TheFile); {$I+} if IOResult <> 0 then begin FreeMem(PMsgBuff, Sizeof(TMsgBuff)); PMsgBuff := nil; {$I-} Close(TheFile); {$I+} if IOResult <> 0 then exit; exit; end; if MsgCount = 0 then begin FreeMem(PMsgBuff, Sizeof(TMsgBuff)); PMsgBuff := nil; {$I-} Close(TheFile); {$I+} if IOResult <> 0 then exit; exit; end; {$I-} Blockread(TheFile, PMsgBuff^, MsgCount); {$I+} if IOResult <> 0 then begin FreeMem(PMsgBuff, Sizeof(TMsgBuff)); PMsgBuff := nil; {$I-} Close(TheFile); {$I+}
714
if IOResult <> 0 then exit; exit; end; {$I-} Close(TheFile); {$I+} if IOResult <> 0 then begin FreeMem(PMsgBuff, Sizeof(TMsgBuff)); PMsgBuff := nil; exit; end; CurrentMsg := 0; ReportDelayTime := True; SysModalOn := False; {save the application's callback procedure} cbPlaybackFinishedProc := EndPlayProc; {save the application's defined data parameter} cbAppData := AppData; StartTime := GetTickCount; TheHook := SetWindowsHookEx(WH_JOURNALPLAYBACK, JournalPlayBackProc, hInstance, 0); if TheHook = 0 then begin FreeMem(PMsgBuff, Sizeof(TMsgBuff)); PMsgBuff := nil; exit; end; Playback := 1; end; exports JournalRecordProc index 1 name 'JOURNALRECORDPROC' resident, StartRecording index 2 name 'STARTRECORDING' resident, StopRecording index 3 name 'STOPRECORDING' resident, JournalPlayBackProc index 4 name 'JOURNALPLAYBACKPROC' resident, Playback index 5 name 'PLAYBACK' resident; begin PMsgBuff := nil; end. Resource statements to build: HOOKIT16.RES and HOOKIT32.RES Note: Save the source as HOOKIT16.RC for 16 bit environments or HOOKIT32.RC for 32 bit environments. Use the Borland Resource Command Line Compiler BRCC.EXE to compile a 16 bit resource file and BRCC32.EXE to compile a 32 bit resource file. HOOKIT DIALOG 15, 15, 63, 60 STYLE WS_OVERLAPPED | WS_VISIBLE | WS_CAPTION | WS_MINIMIZEBOX
715
CLASS "HOOKITDIALOGCLASS" CAPTION "HookIt!" BEGIN CONTROL "Start Recording", 101, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_GROUP | WS_TABSTOP, 0, 0, 63, 15 CONTROL "Stop Recording", 102, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_GROUP | WS_TABSTOP, 0, 15, 63, 15 CONTROL "PlayBack", 103, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_GROUP | WS_TABSTOP, 0, 30, 63, 15 CONTROL "Done!", 1, "BUTTON", BS_PUSHBUTTON | WS_CHILD | WS_GROUP | WS_TABSTOP, 0, 45, 63, 15 END
| WS_VISIBLE | WS_VISIBLE | WS_VISIBLE WS_VISIBLE |
Source code for HOOKIT.EXE Note: Save the source as HOOKIT.PAS for Pascal or HOOKIT.DPR for Delphi. program HookIt; {$D HookIt!} {$C MOVEABLE PRELOAD PERMANENT} {$IFDEF Win32} {$R HOOKIT32.RES} uses Windows, Messages; type TwMsg = Longint; TwParam = Longint; TlParam = Longint; {$ELSE} {$R HOOKIT16.RES} uses {$IFDEF VER15} WinTypes, WinProcs, Win31; {$ELSE} {$IFDEF VER70} WinTypes, WinProcs, Win31; {$ELSE} WinTypes, WinProcs, Messages; {$ENDIF} {$ENDIF} type TwMsg = Word; TwParam = Word; TlParam = Longint; {$ENDIF} type TWinVersion WinMajor : WinMinor : DosMajor : DosMinor :
= record Byte; Byte; Byte; Byte;
716
end; TcbPlaybackFinishedProc = Procedure(AppData: Longint) {$IFDEF Win32} stdcall; {$ELSE} ; {$ENDIF} const APPNAME = 'HookIt!'; CLASSNAME ='HOOKITDIALOGCLASS'; ID_BTN_START_RECORDING = 101; ID_BTN_STOP_RECORDING = 102; ID_BTN_PLAYBACK = 103; ID_BTN_DONE = IDOK; FILENAME = 'HOOKIT.MAC'; var PlaybackFinishedProc :TcbPlaybackFinishedProc; function StartRecording: Integer {$IFDEF Win32} stdcall; {$ELSE} ; far; {$ENDIF} external 'HOOKLIB' index 2; function StopRecording(lpFileName: PChar): Integer {$IFDEF Win32} stdcall; {$ELSE} ; far; {$ENDIF} external 'HOOKLIB' index 3; function Playback(lpFileName: PChar; EndPlayProc: TcbPlaybackFinishedProc; AppData: Longint): Integer {$IFDEF Win32} stdcall; {$ELSE} ; far; {$ENDIF} external 'HOOKLIB' index 5; procedure PlaybackFinished(AppData: Longint) {$IFDEF Win32} stdcall; {$ELSE} ; export; {$ENDIF} begin EnableWindow(GetDlgItem(hWnd(AppData), ID_BTN_START_RECORDING), True); EnableWindow(GetDlgItem(hWnd(AppData), ID_BTN_STOP_RECORDING), False); EnableWindow(GetDlgItem(hWnd(AppData), ID_BTN_PLAYBACK), True); EnableWindow(GetDlgItem(hWnd(AppData), ID_BTN_DONE), True); SetFocus(GetDlgItem(hWnd(AppData), ID_BTN_PLAYBACK)); end;
717
function HookitDialogProc(Dialog: HWnd; Msg: TwMsg; WParam: TwParam; LParam: TlParam): Longbool {$IFDEF Win32} stdcall; {$ELSE} ; export; {$ENDIF} begin HookitDialogProc := True; {do any default class handling here for HookItDlg} HookitDialogProc := Longbool(DefDlgProc(Dialog, Msg, WParam, LParam)); end; function MainDlgProc(Dialog: HWnd; Msg:TwMsg; WParam:TwParam; LParam:TlParam): Bool {$IFDEF Win32} stdcall; {$ELSE} ; export; {$ENDIF} begin MainDlgProc := True; case Msg Of WM_INITDIALOG: begin EnableWindow(GetDlgItem(Dialog, ID_BTN_START_RECORDING), True); EnableWindow(GetDlgItem(Dialog, ID_BTN_STOP_RECORDING), False); EnableWindow(GetDlgItem(Dialog, ID_BTN_PLAYBACK), False); EnableWindow(GetDlgItem(Dialog, ID_BTN_DONE), True); exit; end; WM_COMMAND: begin case WParam of ID_BTN_START_RECORDING: begin EnableWindow(GetDlgItem(Dialog, ID_BTN_STOP_RECORDING), True); SetFocus(GetDlgItem(Dialog, ID_BTN_STOP_RECORDING)); EnableWindow(GetDlgItem(Dialog, ID_BTN_START_RECORDING), False); EnableWindow(GetDlgItem(Dialog, ID_BTN_PLAYBACK), False); EnableWindow(GetDlgItem(Dialog, ID_BTN_DONE), False); if StartRecording = 0 then begin EnableWindow(GetDlgItem(Dialog, ID_BTN_START_RECORDING), True); SetFocus(GetDlgItem(Dialog, ID_BTN_START_RECORDING)); EnableWindow(GetDlgItem(Dialog, ID_BTN_STOP_RECORDING), False); EnableWindow(GetDlgItem(Dialog, ID_BTN_PLAYBACK), False);
718
EnableWindow(GetDlgItem(Dialog, ID_BTN_DONE), True); Messagebox(Dialog, 'Unable to start recording!', APPNAME, MB_OK); end; exit; end; ID_BTN_STOP_RECORDING: begin if StopRecording(FILENAME) > 0 then begin EnableWindow(GetDlgItem(Dialog, ID_BTN_START_RECORDING), True); EnableWindow(GetDlgItem(Dialog, ID_BTN_PLAYBACK), True); SetFocus(GetDlgItem(Dialog, ID_BTN_PLAYBACK)); end else begin EnableWindow(GetDlgItem(Dialog, ID_BTN_START_RECORDING), True); SetFocus(GetDlgItem(Dialog, ID_BTN_START_RECORDING)); EnableWindow(GetDlgItem(Dialog, ID_BTN_PLAYBACK), False); end; EnableWindow(GetDlgItem(Dialog, ID_BTN_STOP_RECORDING), False); EnableWindow(GetDlgItem(Dialog, ID_BTN_DONE), True); exit; end; ID_BTN_PLAYBACK: begin EnableWindow(GetDlgItem(Dialog, ID_BTN_START_RECORDING), False); EnableWindow(GetDlgItem(Dialog, ID_BTN_STOP_RECORDING), False); EnableWindow(GetDlgItem(Dialog, ID_BTN_PLAYBACK), False); EnableWindow(GetDlgItem(Dialog, ID_BTN_DONE), False); if PlayBack(FILENAME, PlaybackFinishedProc, Dialog) = 0 then begin EnableWindow(GetDlgItem(Dialog, ID_BTN_START_RECORDING), True); EnableWindow(GetDlgItem(Dialog, ID_BTN_STOP_RECORDING), False); EnableWindow(GetDlgItem(Dialog, ID_BTN_PLAYBACK), True); EnableWindow(GetDlgItem(Dialog, ID_BTN_DONE), True); SetFocus(GetDlgItem(hWnd(Dialog), ID_BTN_PLAYBACK)); end; exit; end; ID_BTN_DONE: begin EndDialog(Dialog, ID_BTN_DONE); exit; end;
719
end; {wParam} end; {WM_COMMAND} WM_CLOSE: begin FreeProcInstance(@PlaybackFinishedProc); EndDialog(Dialog, IDOK); exit; end; end; MainDlgProc := False; end; procedure Init; var WindowClass: TWndClass; WinVer: TWinVersion; begin Longint(WinVer) := GetVersion; if ((WinVer.WinMajor < 3) OR ((WinVer.WinMajor = 3) AND (WinVer.WinMinor < 10)) ) then begin Messagebox(0, 'Microsoft Windows 3.10 or greater required!', APPNAME, MB_OK); halt; end; @PlaybackFinishedProc := MakeProcInstance(@PlaybackFinished, hInstance); If @PlaybackFinishedProc = nil then begin Messagebox(0, 'Cannot create instance thunk!', APPNAME, MB_OK); halt; end; if FindWindow(CLASSNAME, APPNAME) <> 0 then begin Messagebox(0, 'Multiple Sessions not allowed', APPNAME, MB_OK); halt; end else begin WindowClass.Style := CS_BYTEALIGNWINDOW; WindowClass.lpfnWndProc := @HookItDialogProc; WindowClass.cbClsExtra := 0; WindowClass.cbWndExtra := DLGWINDOWEXTRA; WindowClass.hInstance := hInstance;
720
WindowClass.hIcon := LoadIcon(0, IDI_APPLICATION); WindowClass.hCursor := LoadCursor(0, IDC_ARROW); WindowClass.hbrBackground := GetStockObject(WHITE_BRUSH); WindowClass.lpszMenuName := nil; WindowClass.lpszClassName := CLASSNAME; if not Bool(RegisterClass(WindowClass)) then begin Messagebox(0, 'RegisterClass Failed!', APPNAME, MB_OK); halt; end; end; end; procedure MyWinMain; var WindowProc:TFarProc; begin WindowProc:=MakeProcInstance(@MainDlgProc, hInstance); DialogBox(hInstance, 'HOOKIT', 0, WindowProc); FreeProcInstance(WindowProc); end; {WinMain} begin Init; MyWinMain; end. Conclusion: While writing Windows applications gets easier as development tools get more sophisticated, there will be times when you must "bite the bullet" and a interface to new functions that are not handled by your current tools. Remember... before you explore unchartered paths, check available resources for documentation that may help to make your journey a pleasant and rewarding experience.
721
RTL reference|Glossary|Tips/Tricks|FREE App/VCL|Best'O'Net|Books|Link To
Windows Shell Extensions – Info Tip Page 1: Shell extensions allow developers to add functionality to the existing Windows shell.
Creating the InfoTip Shell Extension, which lets us, control the information that appears in Explorer when the mouse hovers over a file. Creating a Delphi Infotip that will display the FileName, the project type (Program or Library), the Project Name (from the source file), and the size of the file in bytes. Article submitted by: Larry J. Rutledge With each new release of Windows, its shell becomes more and more advanced and adds many new useful features. One of the ways this is done is through shell extensions. These extensions allow developers to add functionality to the existing Windows shell. Some examples of shell extensions are Context Menus (menus that change based on what object has focus when you right-click), Property Sheet Handlers (tabbed pages that appear when the Properties menu item is selected from an object’s context menu), Icon Overlays (appear as the arrow on top of an icon that points to a shortcut or the hand that appears on shared folders), Folder Customization, and many, many more. Over the course of a few articles we will examine several of these extensions and, in particular, how to build them in Delphi. There is plenty of information about shell extensions available from the Microsoft web site, but these articles will bring it to the Delphi audience. In this first article we will look at the InfoTip Shell Extension, which lets us control the information that appears in Explorer when the mouse hovers over a file. Important Note The following paragraph from Dino Esposito’s article, "Windows 2000 UI Innovations: Enhance Your User’s Experience with New Infotip and Icon Overlay Shell Extensions" (MSDN Magazine, March 2000) is a good description of what versions of Windows these extensions apply to: "…I should point out that not all the features I'll cover here are completely new. Many of them were already introduced with the Desktop Update – a separate shell update available both for Windows 9x and Windows NT 4.0. The Desktop Update shipped with Microsoft Internet Explorer 4.0 and Windows 98. Note that the Desktop Update is not part of Internet Explorer 5.0. So if you want to install it on Windows NT 4.0, you need to install Internet Explorer 4.0 first, making sure you select the Desktop Update option. Internet Explorer 5.0 will upgrade an existing Desktop Update on Windows NT 4.0 and Windows 95, but will not install it from scratch." Shell Extension – Quick Overview Shell Extensions are implemented as In Process COM servers. Windows Explorer invokes the appropriate extension in response to shell-wide events. Explorer was designed to respond in very specific ways when the user performs various functions within its shell. The first thing Explorer does is check for any modules that have been registered for a specific event and if one exists it attempts to load the module. To be a valid shell extension, the COM server must implement an interface that defines the specific behavior for the desired extension and it must implement an interface that defines its initialization behavior. Finally, to be a valid shell extension, the COM server must follow the approved method of registering itself with the system. The following table from Dino Esposito's article "Windows 2000 UI innovations: Enhance Your User's Experience with New Infotip and Icon Overlay Shell Extensions" (MSDN Magazine, March 2000), lists all the types of shell extensions available today, the minimum shell
722
March 2000), lists all the types of shell extensions available today, the minimum shell version each requires, the involved interfaces, and a brief description: Type
Apply To
Version
Context Menu
File class and shell’s object
Windows 95+
IContextMenu, IContextMenu2, or IContextMenu3
Allows you to add new items to a shell object’s context menu.
Right Drag and Drop
File class and shell’s object
IContextMenu, IContextMenu2, or IContextMenu3
Allows you to add new items to the context menu that appears after your right drag and drop files.
Drawing Shell Icons
File class and shell’s object
IExtractIcon
Lets you decide at runtime which icon should be displayed for a given file within a file class.
Property Sheet
File class and shell’s object
IShellPropSheetExt
Lets you insert additional property sheet pages to the file class Properties dialog. It also works for Control Panel applets.
Left Drag and Drop
File class and shell’s object
IDropTarget
Lets you decide what to do when an object is being dropped (using the left mouse button) onto another one within the shell.
Clipboard
File class and shell’s object
IDataObject
Lets you define how an object is to be copied to and extracted from the clipboard.
Interface Involved
Description
Windows 95+
Windows 95+
Windows 95+
Windows 95+
Windows 95+
Windows 95+
File Hook
ICopyHook
Lets you control any file operation that goes through the shell. While you can permit or deny them, you aren’t informed about success or failure.
Program Execution
Explorer
IShellExecuteHook
Lets you hook any program’s execution that passes through the shell.
Infotip
File class and shell’s object
IQueryInfo
Lets you display a short text message when the mouse hovers over documents of a certain file type.
Column
Folders
IColumnProvider
Lets you add a new column to the Details view of Explorer.
Icon Overlay
Explorer
IShellIconOverlay
Lets you define custom images to be used as icon overlays.
Search
Explorer
IContextMenu
Lets you add a new entry on the Start menu’s Search menu.
Cleanup
Cleanup Manager
IEmptyVolumeCache2
Lets you add a new entry to the Cleanup Manager to recover disk space.
Desktop Update
Desktop Update
Windows 2000
Windows 2000
Windows 2000
Windows 2000
723
Next page > InfoTip – Introduction and Overview > Page 1, 2, 3
Windows Shell Extensions – Info Tip Page 2: InfoTip – Introduction and Overview; Implementing Infotip Extensions
InfoTip – Introduction and Overview Infotip's are hint windows that pop up when the mouse hovers over any file. If an extension has not been registered for the file type a default Infotip appears (see Figure 1 for an example of this), but you can create your own extension to display any information you want for the specific file type. Office 2000 installs default handlers for MS Word and MS Excel that display the Name, Author and Title from the document properties. Infotip extensions differ from other shell extensions in its registration, we'll discover the differences later when we talk about registration of our Infotip extensions.
Implementing Infotip Extensions An Infotip Extension is an In-Process (Inproc) COM Server. This just means that it is a Windows DLL that exports the necessary methods to be a valid ActiveX control. Infotip Extensions also implement IQueryInfo and IPersistFile and must register itself into the registry. Because IQueryInfo and IPersistFile are interfaces, they don't contain code for their defined methods. It is required that our object implement every method defined in both of these interfaces; however, some of the methods are not necessary for our Infotip extension so we simply return E_NOTIMPL to indicate that these are not implemented. IQueryInfo provides the text to display in the hint window and contains two methods: · GetInfoFlags – Retrieves the information flags for an item. Microsoft states that this is currently unused, so we return E_NOTIMPL. · GetInfoTip – Retrieves the text of the Infotip. GetInfoTip is defined as follows: function GetInfoTip(dwFlags: DWORD; var ppwszTip: PWideChar): HResult; stdcall; .dwFlags – currently not used .ppwszTip – Address of a Unicode string pointer that receives the tip string pointer. Important Note The ppwszTip parameter of the GetInfoTip method is a pointer to a Unicode string buffer that contains the text to display in the tip. This buffer must be allocated using the standard shell memory allocator because the buffer is allocated by our application, but freed by the shell. To ensure that everything happens in a thread-safe way, use SHGetMalloc to get the pointer to the shell's memory allocator – an object implementing IMalloc. Then, use IMalloc's Alloc method to allocate the memory needed to hold the Unicode representation of the
724
Infotip text. The accompanying source code contains the standard code you will use for all Infotip Extensions you create. Simply use the same code and your extensions will be shell friendly and thread-safe. IPersistFile is what the shell uses to provide the extension with information about the file the user is hovering over. The interface defines five methods: .IsDirty – checks an object for changes since it was last saved to its current file. We don't need this for Infotip extensions so we return E_NOTIMPL. .Load – opens the specified file and initializes an object from the file contents. We use this method to retrieve the name of the file the user is hovering over. .Save – saves the object into the specified file. We don't use it, so return E_NOTIMPL. .SaveCompleted – notifies the object that it can revert from NoScribble mode to Normal mode. We don't use it, so return E_NOTIMPL. .GetCurFile – gets the current name of the file associated with the object. We don't use it, so return E_NOTIMPL. Load is defined as follows: function Load(pszFileName: PoleStr; dwMode: LongInt ): HResult; stdcall; .pszFileName – points to a zero-terminated string containing the absolute path of the file to open. .dwMode – specifies some combination of the values from the STGM enumeration to indicate the access mode to use when opening the file. We are only using the IPersistFile interface to obtain the path and filename of the file; we are not actually using the interface to access the file so we can ignore the flags. Our standard implementation of the Load method will be to store the contents of pszFileName to a private variable to be used by IQueryInfo::GetInfoTip to locate the file. Next page > Delphi project: Infotip; Registering Infotip Extensions > Page 1, 2, 3
Windows Shell Extensions – Info Tip Page 3: Delphi project: Infotip; Registering Infotip Extensions
More of this Feature • Page 1: Shell extensions • Page 2: InfoTip overview Printer friendly version
Delphi project: Infotip Source download Compiled Project (169KB) Contains DPRInfoTip.dll. Project Files (6KB) - Contains the Delphi project source files. Registration File (1KB) Contains two registration files (one for Win9x and one for Win2000).
The accompanying source code contains a complete Infotip Extension for .DPR (Delphi Project files). This Infotip will display the FileName, the project type (Program or Library), the Project Name (from the source file), and the size of the file in bytes.
To begin, we need to create an automation object called DPRInfoTip. First, click File | New... to open the Object Repository, then click on the ActiveX tab and select the ActiveX Library. This will generate an empty ActiveX library and export the necessary function required to be a valid automation object. Next, click File | New... again and select the ActiveX tab and select the Automation Object. When the Automation Object Wizard opens, enter DPRInfoTip as the CoClass name (see Figure 2 below for an example of how the dialog should look). Leave the remaining options with their default values and click OK, this will generate the basic Type Library and
725
implement a skeleton for the IDPRInfoTip interface, which gets automatically generated.
Once the files have been created the next thing we need to do is to add support for the additional interfaces that must be supported: TDPRInfoTip = class(TAutoObject, IDPRInfoTip, IQueryInfo, IPersistFile, IPersist); Note: We need to implement IPersist also because IPersistFile inherits from IPersist. The accompanying source code can be used for all InfoTip Extensions, the only code that must be altered is the GenerateTip method. This is the method that determines the text to display in the InfoTip. In the Initialization method, we call SHGetMalloc, which causes Windows to allocate some memory and return a pointer to it in the pMalloc private variable. In the destructor, we set pMalloc equal to Nil. This releases our hold on the memory so the Windows shell can release the memory when necessary. IPersistFile::Load When the mouse hovers over a file, the shell calls the Load method to provide the name of the file. Our implementation stores the name of the file in a private variable, FFile, so it will be available when we need to generate the InfoTip text. function TDPRInfoTip.Load (pszFileName: POleStr; dwMode: Integer): HResult; begin FFile := pszFileName; Result := S_OK;
end; For all remaining IPersistFile methods, simply return E_NOTIMPL. IQueryInfo::GetInfoTip When the shell is ready to display the InfoTip, it calls the GetInfoTip method to retrieve the text to display. Our implementation calls the custom GenerateTip method in order to determine the text to display. It then is converted to a Windows compatible WideString and returned in the ppwszTip parameter. The GenerateTip method opens the .DPR file and reads contents into a string. The text to be returned first is assigned the filename of the selected file, then the type is assigned the first word in the project file; which is either Program or Library. Finally the project name is extracted from the remainder of the first line and the size of the file in bytes is determined by the size of the stream. The GenerateTip method can be customized to fit the needs of the InfoTip Extension being developed. Registering Infotip Extensions There are two steps to register an InfoTip Extension: 1. Register the COM DLL using regsvr32.exe (Start..Run dialog box) regsvr32 "C:\...\DPRInfoTip.dll"
726
2. Add a reference to the associated extension (.dpr) to the HKEY_CLASSES_ROOT registry key. The default value for this new key must be the CLSID of the COM object that implements the Shell Extension. This can be obtained from the Type Library file that was generated by Delphi (the filename ends in "_TLB.pas"). For our example extension the CLSID is named CLASS_DPRInfoTip and contains the value "{B20433A8-D083-11D4-993A00D00912C440}". The easiest way to make the registry changes is to make a copy of the .REG file provided with the source code for this project. Change the CLSID and file extension to the associated values. One important note, it is necessary to "approve" shell extensions under Windows NT and 2000. This means that you must be logged into the machine with Administrator rights in order to register the extension. That's all folks That's it for this article. In the future articles we'll see how to use Delphi to implement other shell extensions. In the mean time, if you have any questions regarding this article, please post them to the Delphi Programming Forum.
727
Shell Extensions in Delphi 3 by Ray Konopka December/January 1998, Vol. 8, No. 5 -- Download Source Code
The Explorer is the central user interface metaphor in Windows, and your Delphi applications can embed special hooks right into Explorer. Welcome to the third part of my series on COM support in Delphi 3. In part one, I introduced object interfaces and explained how they are defined using the interface keyword. In part two, I described how interfaces affect the creation of automation servers and controllers in Delphi 3. In this issue, I will describe how to extend the Windows Explorer shell by creating custom COM servers that implement specific, you guessed it, interfaces.
What Are Shell Extensions? Shell extensions, as the term implies, provide a way to extend the abilities of the Windows 95 or NT 4.0 Explorer. There are several different types of shell extensions, which are categorized into two groups. While Table 1 summarizes the extensions that are associated with a particular file type, Table 2 lists the ones that are associated with file operations.
728
A shell extension is implemented as a COM object that resides inside an in-process COM server. Recall from part one that an in-process server is just a DLL, and that Delphi 3 calls them ActiveX Libraries. The Windows Explorer accesses the shell extension through interfaces. Consider the following: In the example that I presented in the last issue, the automation controller called the methods defined in the IViewer interface supported by the automation server. For shell extensions, the Windows Explorer itself functions as the controller and calls the methods in the COM object by using an interface reference. Since Windows itself is calling the methods of a shell extension, the interfaces that Windows uses for accessing shell extensions are predefined. So, instead of defining new methods and properties of an interface like we do when creating an automation server, creating a shell extension involves implementing the appropriate methods defined by the shell extension interfaces. Unfortunately, actually implementing the methods can be a challenge. We’ll cover the details of these interfaces shortly.
Creating a Shell Extension Unlike automation objects, there is no expert defined in Delphi that makes it easy to create a shell extension COM object. In fact, we have to code everything by hand. To demonstrate the process, we’ll construct a new context menu handler that displays a “View as Text” menu item when the user clicks on a Delphi form file using mouse button 2. If the user selects this menu item, the form file viewer application that I presented in part two is used to display the text representation of the selected form file. To create a shell extension, you must first create the COM server that will hold the extension, or handler. Select the File|New menu item and then switch to the ActiveX page. Select the ActiveX Library icon and then click the OK button to instruct Delphi to create a new project file for the library. Next, create a new unit to contain the implementation of the shell extension. Select the File|New menu item and then double-click on the Unit icon. Once the new unit is created, we need to declare the COM object class—that is, we must declare a new class that supports the particular interfaces of the shell extension. For a context menu, the class must support the IShellExtInit and IContextMenu interfaces. Of course, each of the methods in these interfaces must be defined in the implementation section of the unit. Listing 1 is the source code for the FormView ActiveX library, and Listing 2 is the complete source code for the FormViewImpl unit, which implements the TFormViewContextMenu class. As you can see, this class descends from TComObject, which is defined in the ComObj unit, and 729
lists IShellExtInit and IContextMenu as its supported interfaces. Recall that all interfaces are derived from IUnknown, so this class also supports the IUnknown interface. In addition, all interfaces listed in TComObject are supported. However, for our shell extension, all we need to be concerned with are the methods in IShellExtInit and IContextMenu.
The IShellExtInit Interface To initialize a context menu shell extension, Windows uses the IShellExtInit interface. In addition to the standard IUnknown methods, IShellExtInit declares an Initialize method. However, the TFormViewContextMenu class does not define an Initialize method. Instead, it defines the ShellInit method and uses a method resolution clause to map the IShellExtInit.Initialize method to ShellInit. The reason for doing this is because the TComObject class defines a virtual Initialize method that is not compatible with IShellExtInit. (Method resolution clauses were covered in part one of this series.) IShellExtInit.Initialize (and ShellInit) receive three parameters. The first identifies the folder containing the file objects being manipulated. The second parameter is a reference to an IDataObject interface, which is used to obtain the name of the selected files. The third parameter is the ProgID for the type of file that is selected. Although the selected file names are stored in the same way that Windows stores file names during a drag operation, extracting the name of the selected file is not as simple as calling DragQueryFile. First, the information referenced by the DataObject must be converted into a storage medium that can be referenced within the shell extension. This is accomplished by setting up a TFormatEtc record and then calling the GetData method of the DataObject. Now the DragQueryFile can be called to obtain the name of the selected file, which is then stored in the private FFileName field. Notice in Listing 2 that we obtain the file name only if one file is selected.
The IContextMenu Interface After the context menu handler is initialized via the IShellExtInit interface, Windows uses the IContextMenu interface to call the other methods of our context menu handler. Specifically, it will call QueryContextMenu, GetCommandString, and InvokeCommand. Let’s take a closer look at each one. The QueryContextMenu method is called just before Windows displays a file object’s context menu. Implementing this method allows your shell extension to add menu items to the context menu. This is accomplished by calling the InsertMenu function and passing it the handle to the context menu, which is the first parameter to QueryContextMenu. Although having access to the menu’s handle would suggest that you have complete access to the menu, you don’t. In fact, there are several restrictions in what you can do with the context menu. First, you can only add items (no moving or deleting items), and you are only allowed to add string items. In addition, you must insert the new items starting at the position indicated by the Index parameter. And finally, you must use the CmdFirst parameter as the starting menu identifier for any menu items added. The Flags parameter passed to QueryContextMenu must be checked to make sure it is safe to add new menu items. The Flags parameter must contain at least one of the following values before a new menu item can be added: CMF_NORMAL, CMF_VERBSONLY, and CMF_EXPLORE. The CMF_NORMAL flag indicates that the selected file is located on the Windows Desktop. The CMF_VERBSONLY flag indicates that the selected file object is actually a shortcut to another file. The CMF_EXPLORE flag indicates that the file is being selected within a separate instance of Windows Explorer. 730
To demonstrate the effect of each flag, the QueryContextMenu method of the form viewer context menu alters the menu string depending on which flag is specified. Each situation is illustrated in Figures 1 through 3.
Figure 1: Selecting a form file within the Explorer.
Figure 2: Selecting a form file on the desktop.
731
Figure 3: Selecting a shortcut to a form file. Take a closer look at the Explorer status bar in Figure 1. Notice that the status bar displays a help string associated with the selected item in the context menu. The Explorer requests this string from our context menu handler by calling the GetCommandString method. The Cmd parameter to this method indicates which menu item is selected, and the Flags parameter is used to determine when to return a help string. In particular, the help string is returned in the Name parameter only when Flags is equal to GCS_HELPTEXT. When the user selects one of the menu items added by a context menu handler, Windows calls the InvokeCommand method. This method receives a single record parameter specifying the command to invoke and other useful information such as a window handle to use when displaying message boxes. The InvokeCommand method for our form viewer context menu uses the CreateProcess function to start the FormFileViewer application that I presented in the last issue. The location of the viewer is found in the Registry, which the viewer updates whenever it is executed. Therefore, the shell extension will always be able to find the viewer. The file name of the select form file is passed to the viewer on the command line. The viewer processes the command line and displays the contents of the form file as shown in Figure 4.
732
Figure 4: Form viewer started by a shell extension. Now that all of the necessary methods have been implemented, there is one more item that must be added to the FormViewImpl unit.
Creating a COM Object Factory In order for our COM object to be created whenever the FormView DLL is loaded, we need to create an instance of a class factory that specifically creates an instance of our shell extension object. Recall from part one that I prefer the term object factory instead of class factory because a class factory creates objects and not classes. In the initialization section of the unit implementing the shell extension COM object, you need to create an instance of the object factory. Fortunately, this is quite easy, thanks to the predefined classes in Delphi 3. In particular, we use the TComObjectFactory class defined in the ComObj unit. Notice that the class name and ClassID of the shell extension are passed to the constructor of the factory. Because shell extensions are implemented as COM objects, and because Windows itself accesses them, we must generate a ClassID for any new shell extensions. But how do we do that? In the examples presented in part two of this series, Delphi automatically generated ClassIDs whenever one was needed. Fortunately, Delphi 3 provides the means to manually generate a ClassID within the code editor. Simply press the Ctrl+Shift+G key combination and Delphi automatically inserts a new GUID into the code editor. This is how the TGUID constant declared at the beginning of the FormViewImpl unit was created.
Registering a Shell Extension Now that the FormViewImpl unit is completed, we can compile the FormView project and generate the COM Server DLL that Windows will load whenever the user clicks on a Delphi form file using mouse button 2. But how does Windows know that it should load the FormView.dll? As usual, Windows relies on specific entries in the Registry to accomplish this. There are three ways the entries can be made in the Registry. First, they can be entered manually 733
using RegEdit, but this is not recommended because incorrectly editing the Registry can cause system problems. The second option is to merge the entries into the Registry. This involves creating a text file (with a REG extension) that contains the new entries. Clicking mouse button 2 on the file displays a context menu containing a Merge command. Selecting this command merges the items in the file into the Registry. This approach is safer than using RegEdit but requires customizing the file for each user because the complete path of the DLL must be specified in the REG file. The third option is to add the entries programmatically. Most installation programs (for example, WISE Installation System) provide a way to modify the Registry, and since the installation program knows where the DLL is installed, the correct path can be determined during the installation process. This makes this approach the most appropriate for registering shell extensions. Another alternative to using a custom installation program is to create a simple Delphi program that makes the necessary Registry entries. This is the approach I took for this article. Listing 3 shows the source code for the RegMain unit, which is the main form file unit in the FormViewReg program. Simply run this program from the same folder that contains the FormView.dll and the required entries are made in the Registry. Note that if you decide to move the FormView.dll, you will need to rerun the FormViewReg program. The actual entries that need to be added depend on the type of shell extension, but all shell extensions require some basic settings. First, the ClassID of your extension must be registered under the HKEY_CLASSES_ROOT\CLSID key. The CLSID key contains a list of class identifier keys. Within each ClassID key, you must add an InProcServer32 key that specifies the location of the shell extension DLL. The information that the shell uses to associate a shell extension with a particular file type is stored under the shellex key, which is under the ProgID for the file type. Context menu handlers are registered under the ContextMenuHandlers key. Within this key, the ClassID of the handler is listed. The FormCreate event handler in Listing 3 shows the entries that must be made for the Delphi form viewer context menu.
For More Information I relied on two sources of information in developing the shell extension for this article. The first one is the Microsoft Development Network (MSDN). Since Microsoft defines the shell extension interfaces, this is the best place to find detailed information about what the methods of each interface must accomplish. The second source of information came from the Delphi 3 source code—in particular, the ShlObj unit, which has quite a few comments regarding the shell extension interfaces.
On the Drawing Board Over the past three installments of “Delphi by Design,” the focus has been on COM support in Delphi 3. Along the way, we’ve seen how interfaces are used to manipulate objects, how interfaces define the abilities of automation servers, and how Windows itself relies on them. All of this information serves as a foundation for the topic I’ll be covering in the next issue: How to convert a native Delphi component into an ActiveX control. v Copyright © 1998 The Coriolis Group, Inc. All rights reserved.
734
Listing 1 - FormView.dpr library FormView; uses ComServ, FormViewImpl in 'FormViewImpl.pas'; exports DllGetClassObject, DllCanUnloadNow, DllRegisterServer, DllUnregisterServer; {$R *.RES} begin end.
Listing 2 - FormViewImpl.pas {=============================================================== FormViewImpl Unit This unit implements the TFormViewContentMenu class, which is a COM object that supports the IShellExt and IContextMenu interfaces. ===============================================================} unit FormViewImpl; interface uses Windows, Forms, StdCtrls, ShellApi, SysUtils, Classes, Controls, ComServ, ComObj, ShlObj, ActiveX; const CLSID_DelphiFormViewerContextMenu: TGUID = '{F169D961-B907-11D0-B8FA-A85800C10000}'; type TFormViewContextMenu = class( TComObject, IShellExtInit, IContextMenu ) private FFileName: string; public // IShellExtInit Methods // Use a Method Resolution Clause because Initialize is // defined as a virtual method in TComObject function IShellExtInit.Initialize = ShellInit; function ShellInit( Folder: PItemIDList; DataObject: IDataObject; ProgID: HKEY ): HResult; stdcall; // IContextMenu Methods function QueryContextMenu( Menu: HMENU; Index, CmdFirst, CmdLast, Flags: UINT ): HResult; stdcall; function GetCommandString( Cmd, Flags: UINT;
735
Reserved: PUINT; Name: LPSTR; MaxSize: UINT ): HResult; stdcall; function InvokeCommand( var CommandInfo: TCMInvokeCommandInfo ): HResult; stdcall; end; implementation uses Registry; {==================================} {== TFormViewContextMenu Methods ==} {==================================} function TFormViewContextMenu.ShellInit( Folder: PItemIDList; DataObject: IDataObject; ProgID: HKEY ): HResult; var Medium: TStgMedium; FE: TFormatEtc; begin if DataObject = nil then begin Result := E_FAIL; Exit; end; with FE do begin cfFormat := CF_HDROP; ptd := nil; dwAspect := DVASPECT_CONTENT; lindex := -1; tymed := TYMED_HGLOBAL; end; // Transfer the data referenced by the IDataObject reference to // an HGLOBAL storage medium in CF_HDROP format. Result := DataObject.GetData( FE, Medium ); if Failed( Result ) then Exit; try // If only one file is selected, retrieve the file name and // store it in FileName. Otherwise fail. if DragQueryFile( Medium.hGlobal, $FFFFFFFF, nil, 0) = 1 then begin SetLength( FFileName, MAX_PATH ); DragQueryFile( Medium.hGlobal, 0, PChar(FFileName), MAX_PATH); Result := NOERROR; end else Result := E_FAIL; finally ReleaseStgMedium( Medium ); end; end; function TFormViewContextMenu.QueryContextMenu( Menu: HMENU; Index, CmdFirst, CmdLast, Flags: UINT ): HResult; var MenuText: string; AddMenuItem: Boolean; begin
736
AddMenuItem := True; if ( Flags and $000F ) = CMF_NORMAL then MenuText := 'View (Form File on Desktop) as Text' else if ( Flags and CMF_VERBSONLY ) <> 0 then MenuText := 'View (Form File via Shortcut) as Text' else if ( Flags and CMF_EXPLORE ) <> 0 then MenuText := 'View (Form File in Explorer) as Text' else AddMenuItem := False; if AddMenuItem then begin InsertMenu( Menu, Index, mf_String or mf_ByPosition, CmdFirst, PChar( MenuText ) ); Result := 1; // Return number of menu items added end else Result := NOERROR; end; {= TFormViewContextMenu.QueryContextMenu =} function TFormViewContextMenu.GetCommandString( Cmd, Flags: UINT; Reserved: PUINT; Name: LPSTR; MaxSize: UINT ): HResult; begin case Cmd of 0: begin if Flags = GCS_HELPTEXT then begin // Return the string to be displayed in the Explorer // status bar when the menu item is selected StrCopy(Name, 'View the selected Delphi form file as text'); end; Result := NOERROR; end; else // Invalid menu item Result := E_INVALIDARG; end; end; {= TFormViewContextMenu.GetCommandString =} function GetViewerPath: string; var R: TRegIniFile; begin R := TRegIniFile.Create( '\Software\Raize\FormFileViewer' ); try Result := R.ReadString( 'Program', 'Path', '' ); Result := '"' + Result + '" "%s"'; finally R.Free; end; end; function TFormViewContextMenu.InvokeCommand( var CommandInfo: TCMInvokeCommandInfo ): HResult; var Success: Boolean; CmdLine: string; SI: TStartupInfo; PI: TProcessInformation; begin // Make sure we are not being called by an application if HiWord( Integer( CommandInfo.lpVerb ) ) <> 0 then
737
begin Result := E_FAIL; Exit; end; // Execute the command specified by CommandInfo.lpVerb case LoWord( CommandInfo.lpVerb ) of 0: begin FillChar( SI, SizeOf( SI ), #0 ); SI.cb := SizeOf( SI ); SI.wShowWindow := sw_ShowNormal; SI.dwFlags := STARTF_USESHOWWINDOW; CmdLine := Format( GetViewerPath, [ FFileName ] ); Success := CreateProcess( nil, PChar( CmdLine ), nil, nil, True, 0, nil, nil, SI, PI ); if not Success then begin MessageBox( CommandInfo.hWnd, 'Could not start the Form File Viewer.', 'Error', mb_IconError or mb_OK ); end; Result := NOERROR; end; else // Invalid menu item Result := E_INVALIDARG; end; { case } end; {= TFormViewContextMenu.InvokeCommand =} initialization // Create a COM object factory which will be responsible for // creating instances of our shell extension. ComServer is // declared in ComServ unit. TComObjectFactory.Create( ComServer, TFormViewContextMenu, CLSID_DelphiFormViewerContextMenu, '', 'View Delphi Form Files', ciMultiInstance ); end.
Listing 3 - RegMain.pas unit RegMain; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls; type TForm1 = class(TForm) Button1: TButton; Panel1: TPanel; Label1: TLabel; Image1: TImage; procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject);
738
private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} uses Registry; const FormViewerClassID = '{F169D961-B907-11D0-B8FA-A85800C10000}'; procedure TForm1.FormCreate(Sender: TObject); var Reg: TRegistry; begin Reg := TRegistry.Create; try with Reg do begin RootKey := HKEY_CLASSES_ROOT; OpenKey( '\CLSID\' + FormViewerClassID, True ); WriteString( '', 'Delphi Form Viewer Context Menu Shell Extension'); OpenKey( '\CLSID\' + FormViewerClassID + '\InProcServer32', True ); WriteString( '', ExtractFilePath( Application.ExeName ) + '\FormView.dll' ); WriteString( 'ThreadingModel', 'Apartment' ); CreateKey( '\DelphiForm\shellex\ContextMenuHandlers\' + FormViewerClassID ); end; finally Reg.Free; end; end; procedure TForm1.Button1Click(Sender: TObject); begin Close; end; end.
739
Unit KeyBoardHookUnit; Interface Uses Windows, SysUtils, Forms, Controls, StdCtrls, Classes, Dialogs, Messages; Const CM_HookMessage = WM_APP + 400; Type TKeyboardHookForm = class(TForm) procedure FormCreate(SEnder: TObject); procedure FormDestroy(SEnder: TObject); Private { Private declarations } Procedure HookMessageTrigger(Var Message: TMessage); Message CM_HookMessage; public { Public declarations } End; Var KeyboardHookForm: TKeyboardHookForm; Implementation {$R *.dfm} {Functions prototypes for the hook dll} Type TGetHookRecPointer = Function: Pointer; Stdcall; Type TStartKeyBoardHook = Procedure; Stdcall; Type TStopKeyBoardHook = Procedure; Stdcall; {The record type filled in by the hook dll} Type THookRec = Packed Record TheHookHandle : HHOOK; TheAppWinHandle : HWND; TheCtrlWinHandle : HWND; TheCtrlMessage : Integer; TheKeyCount : DWORD; IsAltPressed : bool; IsCtrlPressed : bool; IsShiftPressed : bool; KeyCode: Integer; End; {A pointer type to the hook record} Type PHookRec = ^THookRec; Var hHookLib : THANDLE; {A handle to the hook dll} GetHookRecPointer : TGetHookRecPointer; {Function pointer} StartKeyBoardHook : TStartKeyBoardHook; {Function pointer} StopKeyBoardHook : TStopKeyBoardHook; {Function pointer} LibLoadSuccess : bool; {If the hook lib was successfully loaded} lpHookRec : PHookRec; {A pointer to the hook record} EnterKeyCount : DWORD; {An internal count of the Enter Key} Procedure TKeyboardHookForm.FormCreate(SEnder: TObject); Begin {Set our initial variables} lpHookRec := Nil; LibLoadSuccess := False;
740
@GetHookRecPointer := Nil; @StartKeyBoardHook := Nil; @StopKeyBoardHook := Nil; {Try to load the hook dll} hHookLib := LoadLibrary('KEYBOARDHOOK.DLL'); {If the hook dll was loaded successfully} If hHookLib <> 0 Then Begin {Get the function addresses} @GetHookRecPointer := GetProcAddress(hHookLib, 'GETHOOKRECPOINTER'); @StartKeyBoardHook := GetProcAddress(hHookLib, 'STARTKEYBOARDHOOK'); @StopKeyBoardHook := GetProcAddress(hHookLib, 'STOPKEYBOARDHOOK'); {Did we find all the functions we need?} If ((@GetHookRecPointer <> Nil) And (@StartKeyBoardHook <> Nil) And (@StopKeyBoardHook <> Nil)) Then Begin LibLoadSuccess := True; {Get a pointer to the hook record} lpHookRec := GetHookRecPointer; {Were we successfull in getting a ponter to the hook record} If (lpHookRec <> Nil) Then Begin {Fill in our portion of the hook record} lpHookRec^.TheHookHandle := 0; lpHookRec^.TheCtrlWinHandle := KeyBoardHookForm.Handle; lpHookRec^.TheCtrlMessage := CM_HookMessage; lpHookRec^.TheKeyCount := 0; {Start the keyboard hook} StartKeyBoardHook; End; End Else Begin {We failed to find all the functions we need} FreeLibrary(hHookLib); hHookLib := 0; @GetHookRecPointer := Nil; @StartKeyBoardHook := Nil; @StopKeyBoardHook := Nil; End; End; End; Procedure TKeyboardHookForm.FormDestroy(SEnder: TObject); Begin {Did we load the dll successfully?} If (LibLoadSuccess = True) Then Begin {Did we sucessfully get a pointer to the hook record?} If (lpHookRec <> Nil) Then Begin {Did the hook get set?} If (lpHookRec^.TheHookHandle <> 0) Then Begin StopKeyBoardHook; End; End; {Free the hook dll} FreeLibrary(hHookLib); End;
741
End; End.
742
MSOffice/Word 23. Delphi 2.01 / MS Office 97 / OLE / VB for Applications 24. OLE Tester 25. Getting data from Delphi app into Word document 26. WordBasic via OLE 27. Creating Word Documents 28. Starting Word without AutoStart Macro 29. Wordbasic from Delphi using Parameters 30. Hack: tired of ReportSmith? Wish you had wysiwyg reports? 31. Excelmsoffice8 32. Delphi + WordBasic 8msoffice9
Delphi 2.01 / MS Office 97 / OLE / VB for Applications From: "Jill Marquiss" This answers those really interesting questions of How do you know whether word8 is installed? Where are the templates? Why do I keep getting a new instance when I didn't want one? Where the heck is the document the user was typing on? Why does word end when my procedure ends? How about that Outlook - how do I get to the folders? How to make a simple contact and how to fish for an existing contact? {--------------------Straight from the type library--------------- WORDDEC.INC} Const // OlAttachmentType olByValue = 1; olByReference = 4; olEmbeddedItem = 5; olOLE = 6; // OlDefaultFolders olFolderDeletedItems = 3; olFolderOutbox = 4; olFolderSentMail = 5; olFolderInbox = 6; olFolderCalendar = 9; olFolderContacts = 10; olFolderJournal = 11; olFolderNotes = 12; olFolderTasks = 13; // OlFolderDisplayMode olFolderDisplayNormal = 0; olFolderDisplayFolderOnly = 1; olFolderDisplayNoNavigation = 2; // OlInspectorClose olSave = 0; olDiscard = 1; olPromptForSave = 2; // OlImportance olImportanceLow = 0; olImportanceNormal = 1; olImportanceHigh = 2;
743
// OlItems olMailItem = 0; olAppointmentItem = 1; olContactItem = 2; olTaskItem = 3; olJournalItem = 4; olNoteItem = 5; olPostItem = 6; // OlSensitivity olNormal = 0; olPersonal = 1; olPrivate = 2; olConfidential = 3; // OlJournalRecipientType; olAssociatedContact = 1; // OlMailRecipientType; olOriginator = 0; olTo = 1; olCC = 2; olBCC = 3 ; Const wdGoToBookmark = -1; wdGoToSection = 0; wdGoToPage = 1; wdGoToTable = 2; wdGoToLine = 3; wdGoToFootnote = 4; wdGoToEndnote = 5; wdGoToComment = 6; wdGoToField = 7; wdGoToGraphic = 8; wdGoToObject = 9; wdGoToEquation = 10; wdGoToHeading = 11; wdGoToPercent = 12; wdGoToSpellingError = 13; wdGoToGrammaticalError = 14; wdGoToProofreadingError = 15; wdGoToFirst = 1; wdGoToLast = -1; wdGoToNext = 2; //this is interesting wdGoToRelative = 2; //how can these two be the same wdGoToPrevious = 3; wdGoToAbsolute = 1;
These are basic functions Function Function Function Function Function Function Function Function Function Function Function
GetWordUp(StartType : string):Boolean; InsertPicture(AFileName : String) : Boolean; InsertContactInfo(MyId : TMyId; MyContId : TMyContId): Boolean; GetOutlookUp(ItemType : Integer): Boolean; MakeOutLookContact(MyId : TMyId; MyContId : TMyContId) : Boolean; ImportOutlookContact : Boolean; GetOutlookFolderItemCount : Integer; GetThisOutlookItem(AnIndex : Integer) : Variant; FindMyOutlookItem(AFilter : String; var AItem : Variant) :Boolean; FindNextMyOutlookItem(var AItem : Variant) : Boolean; CloseOutlook : Boolean;
Type TTreeData = class(TObject) Public ItemId : String; end;
744
{$I worddec.inc} {literal crap translated from type libraries} Var myRegistry : TRegistry; GotWord : Boolean; WhereIsWord : String; WordDoneMessage : Integer; Basically : variant; Wordy: Variant; MyDocument : Variant; MyOutlook : Variant; MyNameSpace : Variant; MyFolder : Variant; MyAppointment : Variant;
Function GetWordUp(StartType : string):Boolean; // to start word the "right" way for me // if you start word, you own word and I wanted it to remain after I closed var i : integer; AHwnd : Hwnd; AnAnswer : Integer; temp : string; MyDocumentsCol : Variant; TemplatesDir : Variant; OpenDialog1 : TopenDialog; begin
result := false; myRegistry := Tregistry.Create; myRegistry.RootKey := HKEY_LOCAL_MACHINE; // no word 8, no function If myRegistry.KeyExists('SOFTWARE\Microsoft\Office\8.0\Word') then GotWord := true Else GotWord := false; If GotWord then //where the heck is it? If myRegistry.OpenKey('SOFTWARE\Microsoft\Office\8.0', false) then begin WhereisWord := myRegistry.ReadString('BinDirPath'); MyRegistry.CloseKey; end else GotWord := false; If GotWord then //where are those pesky templates? Begin MyRegistry.RootKey := HKEY_CURRENT_USER; If myRegistry.OpenKey('SOFTWARE\Microsoft\Office\8.0\Common\FileNew\SharedTemplates ', false) then Begin TemplatesDir := myRegistry.ReadString(Nothing); MyRegistry.CloseKey; end Else Begin Warning('Ole setup','The workgroup templates have not been setup'); GotWord := false;
745
end; End; myRegistry.free; If not gotword then Begin Warning('Ole Handler', 'Word is not installed'); exit; end; //this is the class name for the last two versions of word's main window temp := 'OpusApp'; AHwnd := FindWindow(pchar(temp),nil); If (AHwnd = 0) then //it isn't running and I don't wanna start it by automation Begin Temp := WhereisWord + '\winword.exe /n'; AnAnswer := WinExec(pchar(temp), 1); If (AnAnswer < 32) then Begin Warning('Ole Handler', 'Unable to find WinWord.exe'); Exit; End; End; Application.ProcessMessages; {If you use Word.Application, you get your own instance} {If you use Word.Document, you get the running instance} {this makes a trash document (for me, anyway) and I chuck it out later} try {and make a new document} Basically := CreateOleObject('Word.Document.8'); except Warning('Ole Handler', 'Could not start Microsoft Word.'); Result := False; Exit; end; Try {get the app variant from that new document} Wordy := Basically.Application; Except Begin Warning('Ole Handler', 'Could not access Microsoft Word.'); Wordy := UnAssigned; Basically := UnAssigned; Exit; end; end; Application.ProcessMessages; Wordy.visible := false; MyDocumentsCol := Wordy.Documents; {If its just my throw away document or I wanted a brand new one} If (MyDocumentsCol.Count = 1) or (StartType = 'New') then Begin OpenDialog1 := TOpenDialog.Create(Application); OpenDialog1.filter := 'WordTemplates|*.dot|Word Documents|*.doc'; OpenDialog1.DefaultExt := '*.dot'; OpenDialog1.Title := 'Select your template'; OpenDialog1.InitialDir := TemplatesDir; If OpenDialog1.execute then Begin Wordy.ScreenUpdating:= false; MyDocumentsCol := wordy.Documents; MyDocumentsCol.Add(OpenDialog1.Filename, False); OpenDialog1.free; end Else
746
begin OpenDialog1.Free; Wordy.visible := true; Wordy := Unassigned; Basically := Unassigned; Exit; end;
end Else {get rid of my throwaway} MyDocument.close(wdDoNotSaveChanges); {now I either have a new document based on a template the user selected or I have their current document} MyDocument := Wordy.ActiveDocument; Result := true; Application.ProcessMessages; end; Function InsertPicture(AFileName : String) : Boolean; var MyShapes : Variant; MyRange : variant; begin Result := True; If GetWordUp('Current')then Try Begin MyRange := MyDocument.Goto(wdgotoline, wdgotolast); MyRange.EndOf(wdParagraph, wdMove); MyRange.InsertBreak(wdPageBreak); MyShapes := MyDocument.InlineShapes; MyShapes.AddPicture(afilename, false, true, MyRange); end; Finally begin Wordy.ScreenUpdating:= true; Wordy.visible := true; Wordy := Unassigned; Basically := UnAssigned; Application.ProcessMessages; end; end else Result := False; end; Function InsertContactInfo(MyId : TMyId; MyContId : TMyContId) : Boolean; var MyCustomProps : Variant; begin { personally, I store stuff in document properties and then give out a toolbar macro to allow the user to "set" the properties in their template or current document. this has three advantages that I know of (and no defects that I'm aware of) 1. The user can place the location of the info in the document either before or after this function runs 2. A custom property can be placed any number of times inside the same document 3. A user can map the properties in their Outlook or search on them using that abismal file open in Word}
747
Result := true; If GetWordUp('New')then Try Begin MyCustomProps := MyDocument.CustomDocumentProperties; MyCustomProps.add(cpId, false, msoPropertyTypeString, MyId.Id); MyCustomProps.add(cpOrganizationName, false, msoPropertyTypeString, MyId.OrganizationName); MyCustomProps.add(cpAddress1, false, msoPropertyTypeString,MyId.Address1); MyCustomProps.add(cpAddress2, false, msoPropertyTypeString, MyId.Address2); MyCustomProps.add(cpCity, false, msoPropertyTypeString, MyId.City); MyCustomProps.add(cpStProv, false, msoPropertyTypeString, MyId.StProv); MyCustomProps.add(cpCountry, false, msoPropertyTypeString,MyId.City); MyCustomProps.add(cpPostal, false, msoPropertyTypeString, MyId.Country); MyCustomProps.add(cpAccountId, false, msoPropertyTypeString, MyId.AccountId); MyCustomProps.add(cpFullName, false, msoPropertyTypeString, MyContId.FullName); MyCustomProps.add(cpSalutation, false, msoPropertyTypeString, MyContId.Salutation); MyCustomProps.add(cpTitle, false, msoPropertyTypeString,MyContId.Title); If (MyContId.workPhone = Nothing) or (MycontId.WorkPhone = ASpace) then MyCustomProps.add(cpPhone, false, msoPropertyTypeString, MyId.Phone ) else MyCustomProps.add(cpPhone, false, msoPropertyTypeString, MyContId.WorkPhone ); If (MyContId.Fax = Nothing) or (MycontId.Fax = ASpace) then MyCustomProps.add(cpFax, false, msoPropertyTypeString, MyId.Fax) else MyCustomProps.add(cpFax, false, msoPropertyTypeString,MyContId.Fax); If (MyContId.EMail = Nothing) or (MycontId.Email = ASpace) then MyCustomProps.add(cpEmail, false, msoPropertyTypeString, MyId.Email) else MyCustomProps.add(cpEmail, false, msoPropertyTypeString, MyContId.Email); MyCustomProps.add(cpFirstName, false, msoPropertyTypeString,MyContId.FirstName); MyCustomProps.add( cpLastName, false, msoPropertyTypeString, MyContId.LastName); MyDocument.Fields.Update; end; Finally begin Wordy.ScreenUpdating:= true; Wordy.visible := true; Wordy := Unassigned; Basically := UnAssigned; Application.ProcessMessages; end; end Else Result := false;
748
end; Function GetOutlookUp(ItemType : Integer): Boolean; Const AppointmentItem = 'Calendar'; TaskItem = 'Tasks'; ContactItem = 'Contacts'; JournalItem = 'Journal'; NoteItem = 'Notes'; var MyFolders : Variant; MyFolders2 : variant; MyFolders3 : variant; MyFolder2 : Variant; MyFolder3 : variant; MyUser : Variant; MyFolderItems : Variant; MyFolderItems2 : Variant; MyFolderItems3 : Variant; MyContact : Variant; i, i2, i3 : Integer; MyTree : TCreateCont; MyTreeData : TTreeData; RootNode, MyNode, MyNode2 : ttreeNode; ThisName : String; Begin {this is really ugly........ There is some really wierd thing going on in the object model for outlook so excuse this folder.folder.folder stuff cause the "right way" doesn't work for folders and this does} {user picks folder from treeview} Result := False; Case ItemType of olAppointmentItem : ThisName := AppointmentItem; olContactItem : ThisName := ContactItem; olTaskItem : ThisName := TaskItem; olJournalItem : ThisName := JournalItem; olNoteItem : ThisName := NoteItem; Else ThisName := 'Unknown'; End; try MyOutlook := CreateOleObject('Outlook.Application'); except warning('Ole Interface','Could not start Outlook.'); Exit; end; {this is the root folder} MyNameSpace := MyOutlook.GetNamespace('MAPI'); MyFolderItems := MyNameSpace.Folders; MyTree := TCreateCont.create(Application); {Really unfortunate, but a user can create something other than the default folder for the kind of thing you're interested in - so this goes down a coupla levels in the folder chain} MyTree.Caption := 'Select ' + ThisName + ' Folder'; With MyTree do If MyFolderItems.Count > 0 then For i := 1 to MyFolderItems.Count do begin MyFolder := MyNameSpace.Folders(i); MyTreeData := TTreeData.create; MyTreeData.ItemId := MyFolder.EntryId;
749
RootNode := TreeView1.Items.AddObject(nil, MyFolder.Name, MyTreeData); MyFolders2 := MyNameSpace.folders(i).Folders; If MyFolders2.Count > 0 then for i2 := 1 to MyFolders2.Count do begin MyFolder2 := MyNameSpace.folders(i).Folders(i2); If (MyFolder2.DefaultItemType = ItemType) or (MyFolder2.Name = ThisName) then Begin MyTreeData := TTreeData.create; MyTreeData.ItemId := MyFolder2.EntryId; {this is what you need to directly point at the folder} MyNode := Treeview1.Items.addChildObject(RootNode, MyFolder2.Name, MyTreeData); MyFolders3 := MyNameSpace.folders(i).Folders(i2).Folders; If MyFolders3.Count > 0 then for i3 := 1 to MyFolders3.Count do begin MyFolder3 := MyNameSpace.folders(i).Folders(i2).Folders(i3); If (MyFolder3.DefaultItemType = ItemType) then Begin MyTreeData := TTreeData.create; MyTreeData.ItemId := MyFolder3.EntryId; MyNode2 := Treeview1.Items.addChildObject(MyNode, MyFolder3.Name, MyTreeData); end; end; end; end; end; If MyTree.TreeView1.Items.Count = 2 then {there is only the root and my designated folder} MyFolder := MyNameSpace.GetFolderFromID(TTreeData(MyTree.TreeView1.Items[1].Data).ItemId ) Else begin MyTree.Treeview1.FullExpand; MyTree.ShowModal; If MyTree.ModalResult = mrOk then Begin If MyTree.Treeview1.Selected <> nil then MyFolder := MyNameSpace.GetFolderFromID(TTreeData(MyTree.Treeview1.Selected.Data).ItemId ); end else Begin MyOutlook := UnAssigned; For i:= MyTree.Treeview1.Items.Count -1 downto 0 do TTreeData(MyTree.Treeview1.Items[i].Data).free; MyTree.release; exit; end; end; For i:= MyTree.Treeview1.Items.Count -1 downto 0 do TTreeData(MyTree.Treeview1.Items[i].Data).free; MyTree.release; Result := true; end;
750
Function MakeOutlookContact(MyId : TMyId; MyContId : TMyContId) : boolean; var MyContact : Variant; begin Result := false; If not GetOutlookUp(OlContactItem) then exit; MyContact := MyFolder.Items.Add(olContactItem); MyContact.Title := MyContId.Honorific; MyContact.FirstName := MyContId.FirstName; MyContact.MiddleName := MycontId.MiddleInit; MyContact.LastName := MycontId.LastName; MyContact.Suffix := MyContId.Suffix; MyContact.CompanyName := MyId.OrganizationName; MyContact.JobTitle := MyContId.Title; MyContact.OfficeLocation := MyContId.OfficeLocation; MyContact.CustomerId := MyId.ID; MyContact.Account := MyId.AccountId; MyContact.BusinessAddressStreet := MyId.Address1 + CRLF + MyId.Address2; MyContact.BusinessAddressCity := MyId.City; MyContact.BusinessAddressState := MyId.StProv; MyContact.BusinessAddressPostalCode := MyId.Postal; MyContact.BusinessAddressCountry := MyId.Country; If (MyContId.Fax = Nothing) or (MyContId.Fax = ASpace) then MyContact.BusinessFaxNumber := MyId.Fax Else MyContact.BusinessFaxNumber := MyContId.Fax; If (MyContId.WorkPhone = Nothing) or (MyContId.WorkPhone = ASpace) then MyContact.BusinessTelephoneNumber := MyId.Phone Else MyContact.BusinessTelephoneNumber := MyContId.WorkPhone; MyContact.CompanyMainTelephoneNumber := MyId.Phone; MyContact.HomeFaxNumber := MyContId.HomeFax; MyContact.HomeTelephoneNumber := MyContId.HomePhone; MyContact.MobileTelephoneNumber := MyContId.MobilePhone; MyContact.OtherTelephoneNumber := MyContId.OtherPhone; MyContact.PagerNumber := MyContId.Pager; MyContact.Email1Address := MyContId.Email; MyContact.Email2Address := MyId.Email; Result := true; Try MyContact.Save; Except Result := false; end; MyOutlook := Unassigned; end; Function GetThisOutlookItem(AnIndex : Integer) : Variant; Begin Result := myFolder.Items(AnIndex); end; Function GetOutlookFolderItemCount : Integer; Var myItems : Variant; Begin Try MyItems := MyFolder.Items; Except Begin Result := 0; exit; end; end; Result := MyItems.Count; end;
751
Function FindMyOutlookItem(AFilter : String; var AItem : Variant) : Boolean; Begin {this is another real PAIN - nil variant} Result := true; Try AItem := myFolder.Items.Find(AFilter); Except Begin aItem := MyFolder; Result := false; end; End; End; Function FindNextMyOutlookItem(var AItem : Variant) : Boolean; Begin Result := true; Try AItem := myFolder.Items.FindNext; Except Begin AItem := myFolder; Result := false; end; End; End; Function CloseOutlook : Boolean; begin Try MyOutlook := Unassigned; Except End; Result := true; end;
How to use this stuff! a unit to pick an Outlook contact With many thanks to B. stowers and the lovely extended list view unit UImpContact; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, UMain, StdCtrls, Buttons, ComCtrls, ExtListView; type TFindContact = class(TForm) ContView1: TExtListView; SearchBtn: TBitBtn; CancelBtn: TBitBtn; procedure SearchBtnClick(Sender: TObject); procedure CancelBtnClick(Sender: TObject); procedure ContView1DblClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); private { Private declarations } public { Public declarations }
752
end; var FindContact: TFindContact; implementation Uses USearch; {$R *.DFM} procedure TFindContact.SearchBtnClick(Sender: TObject); begin If ContView1.Selected <> nil then ContView1DblClick(nil); end; procedure TFindContact.CancelBtnClick(Sender: TObject); begin CloseOutlook; ModalResult := mrCancel; end; procedure TFindContact.ContView1DblClick(Sender: TObject); var MyContact : variant; begin If ContView1.Selected <> nil then Begin MyContact := GetThisOutlookItem(StrToInt(ContView1.Selected.subitems[2])); With StartForm.MyId do If Not GetData(MyContact.CustomerId) then begin InitData; If MyContact.CustomerId <> '' then Id := MyContact.CustomerId Else Id := MyContact.CompanyName; If DoesIdExist(Startform.MyId.Id) then begin Warning('Data Handler', 'Can not establish unique Id' + CRLF + 'Edit CustomerId in Outlook and then try again'); CloseOutlook; ModalResult := mrCancel; Exit; end; OrganizationName := MyContact.CompanyName; IdType := 1; AccountId := MyContact.Account; Address1 := MyContact.BusinessAddressStreet; City := MyContact.BusinessAddressCity; StProv := MyContact.BusinessAddressState ; Postal := MyContact.BusinessAddressPostalCode; Country := MyContact.BusinessAddressCountry; Phone := MyContact.CompanyMainTelephoneNumber; Insert; end; With StartForm.MyContId do begin InitData; ContIdId := StartForm.MyId.Id; Honorific := MyContact.Title ; FirstName := MyContact.FirstName ; MiddleInit := MyContact.MiddleName ; LastName := MyContact.LastName ; Suffix := MyContact.Suffix ; Fax := MyContact.BusinessFaxNumber ; WorkPhone := MyContact.BusinessTelephoneNumber; HomeFax := MyContact.HomeFaxNumber ; HomePhone := MyContact.HomeTelephoneNumber ; MobilePhone := MyContact.MobileTelephoneNumber ;
753
OtherPhone := MyContact.OtherTelephoneNumber ; Pager := MyContact.PagerNumber ; Email := MyContact.Email1Address ; Title := MyContact.JobTitle; OfficeLocation := MyContact.OfficeLocation ; Insert; End;
end; CloseOutlook; ModalResult := mrOk; end;
procedure TFindContact.FormCreate(Sender: TObject); var MyContact : Variant; MyCount : Integer; i : Integer; AnItem : TListItem; begin If not GetOutlookUp(OlContactItem) then exit; MyCount := GetOutlookFolderItemCount ; For i := 1 to MyCount do begin MyContact := GetThisOutlookItem(i); AnItem := ContView1.Items.Add; AnItem.Caption := MyContact.CompanyName; AnItem.SubItems.add(MyContact.FirstName); AnItem.Subitems.Add(MyContact.LastName); AnItem.SubItems.Add(inttostr(i)); End; end; procedure TFindContact.FormClose(Sender: TObject; var Action: TCloseAction); begin Action := cafree; end; end.
OLE Tester From: [email protected] This is a VERY simple test that I made myself to get started with OLE. I was asked to add OLE support to a program I made and this is what I did to have a program to test that my own OLE server worked. This creates the oleobject upon creation and then whenever you press a button it calls a procedure in the oleserver. unit oletestu; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type
754
TForm1 = class(TForm) Button1: TButton; Button2: TButton; procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private declarations } public { Public declarations } ttsesed : variant; end; var Form1: TForm1; implementation uses oleauto; {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin ttsesed := createoleobject('ttdewed.ttsesole'); end; procedure TForm1.Button1Click(Sender: TObject); begin ttsesed.openeditfile; end; procedure TForm1.Button2Click(Sender: TObject); begin ttsesed.appshow; end; end.
Getting data from Delphi app into Word document From: Darek Maluchnik
Lib "c:\sample\test.dll" As String
Sub MAIN mystring$ = StringFromDelphi Insert mystring$ End Sub
Create simple TEST.DLL in Delphi - just form with a button. Save it (eg.in c:\sample - see Word macro) as test.dpr and testform.pas. Now add to your project EXPORTED function 'StringFromDelphi' and 'close' on button click. You can paste the stuff from below: library Test;
(* test.dpr in c:\sample *)
755
uses Testform in 'TESTFORM.PAS'; exports StringFromDelphi; begin end.
unit Testform; (* testform.pas in c:\sample *) interface uses WinTypes, WinProcs, Forms, Classes, Controls, StdCtrls, SysUtils; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); end; var Form1: TForm1; function StringFromDelphi : PChar; export; {$ifdef WIN32} stdcall; {$endif} implementation {$R *.DFM} function StringFromDelphi: Pchar; var StringForWord : array[0..255] of char; begin Application.CreateForm(TForm1, Form1); Form1.ShowModal; Result:=StrPCopy(StringForWord, Form1.Button1.caption); end; procedure TForm1.Button1Click(Sender: TObject); begin close; end; end.
Compile test.dll. Run macro from Word, Delphi form should appear - click the button to get some data from Delphi. There is a text in PCMagazine Vol12.No22 on accessing DLL functions from Word. You can get it (DLLACCES) from PCMag web site.
WordBasic via OLE From: [email protected] (CEHJohnson) Try the following: MsWord := CreateOleObject('Word.Basic'); MsWord.FileNewDefault; MsWord.TogglePortrait;
Creating Word Documents From: [email protected] (Alan MCCulloch) I have found the following works well D2 -> Word 97, using "Bookmark" fields in Word. ..
756
.. .. implementation uses OleAuto; .. .. .. var V : Variant ; .. .. ..
V := 0; // at some point just to initialise
.. .. .. some functions if V = 0 then begin V := CreateOLEObject('Word.Application'); V.WordBasic.AppShow; end; // this example assumes we are filling in some bookmark // fields on a "standard letter", from a query that has previously // been executed, in a data module called pnm_data (OK , should // have used a with...block !) V.WordBasic.Fileopen('Your Word Doc name'); V.WordBasic.EditBookmark('Title',0,0,0,1); V.WordBasic.Insert(Title); V.WordBasic.EditBookmark('FirstName',0,0,0,1); V.WordBasic.Insert(FirstName + ' '); V.WordBasic.EditBookmark('LastName',0,0,0,1); V.WordBasic.Insert(pnm_data.ContactsQuery1Fam_Name.AsString + ' '); V.WordBasic.EditBookmark('Address1',0,0,0,1); V.WordBasic.Insert(pnm_data.ContactsQuery1Address1.AsString + ' '); V.WordBasic.EditBookmark('Address2',0,0,0,1); V.WordBasic.Insert(pnm_data.ContactsQuery1Address2.AsString + ' '); V.WordBasic.EditBookmark('Address3',0,0,0,1); V.WordBasic.Insert(pnm_data.ContactsQuery1Address3.AsString + ' '); V.WordBasic.EditBookmark('Title1',0,0,0,1); V.WordBasic.Insert(Title); V.WordBasic.EditBookmark('LastName1',0,0,0,1); V.WordBasic.Insert(pnm_data.ContactsQuery1Fam_Name.AsString + ' '); (You could V.WordBasic.PrintDefault; if you want to tell Word to print it as well....and many other commands, like saving, changing font etc can be done) ....etc
Starting Word without AutoStart Macro From: "Rui Chambel"
757
First you must create the WordBasic object and then execute that method.
Wordbasic from Delphi using Parameters From: Steve Diederichs
Revert := 1);
LastOLECommand := 'Start the Mail Merge.'; MSWord.MailMerge(CheckErrors := 2, Destination := 1, MergeRecords:= 0, From := '', To := '', Suppression := 0, MailSubject := '', MailAsAttachment := 0, MailAddress := ''); LastOLECommand := 'Set up for SendKeys to select printer.'; Application.ProcessMessages; MSWord.AppShow; s := '{home}%l{enter}{home}%n' + FOutput + '{tab}{enter}{home}{enter}'; // sdd 1.1 MSWord.SendKeys(s, -1); MSWord.MailMergeToPrinter;
758
Application.ProcessMessages; ProcessMerge := True; LastOLECommand := 'All done with merge.'; except on EOleException do begin inc(TotalOLEErrors); lblStatus.caption := LastOLECommand; if (TotalOLEErrors >= TOTALOLEERRORS_MAX) then begin s := 'There has been at least one OLE error(' + IntToStr(TotalOLEErrors) + '), the last one was >' + LastOLECommand + '<.'; ShowMessage(s); end; end end; end;
Hack: tired of ReportSmith? Wish you had wysiwyg reports? From: "James D. Rofkar"
Word: Variant; begin Word := CreateOleObject('Word.Basic'); with Word do begin {Pure WordBASIC commands follow...} FileNew('Normal'); Insert('This is the first line'#13); Insert('This is the second line'#13); FileSaveAs('c:\temp\test.txt', 3); end; end;
Simple, isn't it? If you notice, there's no need for SendMessage(), or PostMessage(), or DDE, or Word's C-API, or some proprietary DOS-based batch programming that requires text files to be written. In fact, none of that junk! Another benefit of OLE Automation is that it doesn't require the darned app to launch. That's right! Word does not show-up using this technique. Instead, just the WordBASIC engine is used. The speed improvements and lower memory footprint kick the livin' crap out of the techniques listed in the previous paragraph. A wild side-benefit is that if you startup Word while your program is using OLE Automation, you can watch it work. Yup! Word realizes that "documents" are opened and being editing, and hence, displays them like regular old Word documents. 759
Now all you need to do is generate a Word template with Bookmarks! Then, using the WordBASIC commands "EditBookmark .Goto" and "Insert", you're ready to rock! I've given-up on report generators. They suck compared to Word's WYSIWYG output!
Excel From: [email protected] (Scott F. Barnes) >GOGA wrote in message <[email protected]>... >>Can someone please tell me some basic function to control excel from delphi >>with ole automation. Check UNDU and back issues of Delphi Informant. Also http://vzone.virgin.net/graham.marshall/excel.htm#excel.htm I can't remember exactly which sample(s) I managed to piece this together from, but this sample code will create and format an Excel spreadsheet based on the contents of a DBGrid generated from an SQL query. And it will apply some formatting. This sample is working code that runs in D3 with Excel 97: procedure TfrmBlank.btnExcelClick(Sender: TObject); var XL, XArr: Variant; i : Integer; j : Integer; begin {note the ComObj (example OleAuto not correct) in the uses} // Create an array of query element size XArr:=VarArrayCreate([1,EmailQuery.FieldCount],varVariant); XL:=CreateOLEObject('Excel.Application'); // Ole object creation XL.WorkBooks.add; XL.visible:=true;
end;
j := 1; EmailQuery.First; while not EmailQuery.Eof do begin i:=1; while i<=EmailQuery.FieldCount do begin XArr[i] := EmailQuery.Fields[i-1].Value; i := i+1; end; XL.Range['A'+IntToStr(j), CHR(64+EmailQuery.FieldCount)+IntToStr(j)].Value := XArr; EmailQuery.Next; j := j + 1; end; XL.Range['A1',CHR(64+EmailQuery.FieldCount)+IntToStr(j)].select; // XL.cells.select; // Select everything XL.Selection.Font.Name:='Garamond'; XL.Selection.Font.Size:=10; XL.selection.Columns.AutoFit; XL.Range['A1','A1'].select;
Delphi + WordBasic 8 From: "K. Brown"
760
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, OLEAuto, ShellAPI, StdCtrls; type TForm1 = class(TForm) Memo1: TMemo; Edit1: TEdit; Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } MSWord: Variant; // WordVersion: Byte; end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); var Test, Test1: Integer; AString: Variant; begin MSWord := CreateOLEObject('Word.Application'); //Word 8 MSWord.Documents.Open (FileName:='d:\test.doc', ReadOnly:=True); MSWord.Visible := 1; //Uncomment if you wish to show the file; Test := MSWord.FontNames.Count; For Test1 := 1 To Test do begin AString := MSWord.FontNames.Item(Test1) ; Memo1.Lines.Add(AString); end; MSWord.ActiveDocument.Range(Start:=0, End:=0); MSWord.ActiveDocument.Range.InsertAfter(Text:='Title'); MSWord.ActiveDocument.Range.InsertParagraphAfter; MSWord.ActiveDocument.Range.Font.Name := 'Arial'; MSWord.ActiveDocument.Range.Font.Size := 24; AString := MSWord.ActiveDocument.Range.Font.Name; Edit1.Text := AString; end; end.
Please email me and tell me if you liked this page. Last modified 03/12/00 12:09:21
761
Keyboard 33. Beeping when
Beeping when
Disable alt-tab and ctrl+esc keys From: [email protected] (Meik Weber) procedure TurnSysKeysOff; var OldVal : LongInt; begin SystemParametersInfo (97, Word (True), @OldVal, 0) end; procedure TurnSysKeysBackOn; var OldVal : LongInt; begin SystemParametersInfo (97, Word (False), @OldVal, 0) end;
762
How to detect arrow keys? From: Mark Pritchard
Hooking keyboard (Sendkeys Routine) From: [email protected] (Iman L. Crawford) I've seen several posts on how to hook the key board. Here's some old code I've dug up (can't remember where It came from). library Sendkey; {This code taken with permission from "Delphi Developer's Guide" by Xavier Pacheco and Steve Teixeira.} uses SysUtils, WinTypes, WinProcs, Messages, Classes, KeyDefs; type { Error codes } TSendKeyError = (sk_None, sk_FailSetHook, sk_InvalidToken, sk_UnknownError); { exceptions } ESendKeyError = class(Exception); ESetHookError = class(ESendKeyError); EInvalidToken = class(ESendKeyError); { a TList descendant that know how to dispose of its contents } TMessageList = class(TList) public destructor Destroy; override; end; destructor TMessageList.Destroy; var i: longint; begin { deallocate all the message records before discarding the list } for i := 0 to Count - 1 do Dispose(PEventMsg(Items[i])); inherited Destroy; end; var { variables global to the DLL } MsgCount: word; MessageBuffer: TEventMsg; HookHandle: hHook; Playing: Boolean; MessageList: TMessageList; AltPressed, ControlPressed, ShiftPressed: Boolean; NextSpecialKey: TKeyString; function MakeWord(L, H: Byte): Word; { macro creates a word from low and high bytes } inline(
763
$5A/ $58/ $8A/$E2);
{ pop dx } { pop ax } { mov ah, dl }
procedure StopPlayback; { Unhook the hook, and clean up } begin { if Hook is currently active, then unplug it } if Playing then UnhookWindowsHookEx(HookHandle); MessageList.Free; Playing := False; end; function Play(Code: integer; wParam: word; lParam: Longint): Longint; export; { This is the JournalPlayback callback function. It is called by Windows } { when Windows polls for hardware events. The code parameter indicates what } { to do. } begin case Code of hc_Skip: begin { hc_Skip means to pull the next message out of our list. If we } { are at the end of the list, it's okay to unhook the JournalPlayback } { hook from here. } { increment message counter } inc(MsgCount); { check to see if all messages have been played } if MsgCount >= MessageList.Count then StopPlayback else { copy next message from list into buffer } MessageBuffer := TEventMsg(MessageList.Items[MsgCount]^); Result := 0; end; hc_GetNext: begin { hc_GetNext means to fill the wParam and lParam with the proper } { values so that the message can be played back. DO NOT unhook } { hook from within here. Return value indicates how much time until } { Windows should playback message. We'll return 0 so that it's } { processed right away. } { move message in buffer to message queue } PEventMsg(lParam)^ := MessageBuffer; Result := 0 { process immediately } end else { if Code isn't hc_Skip or hc_GetNext, then call next hook in chain } Result := CallNextHookEx(HookHandle, Code, wParam, lParam); end; end; procedure StartPlayback; { Initializes globals and sets the hook } begin { grab first message from list and place in buffer in case we } { get a hc_GetNext before and hc_Skip } MessageBuffer := TEventMsg(MessageList.Items[0]^); { initialize message count and play indicator } MsgCount := 0; { initialize Alt, Control, and Shift key flags } AltPressed := False; ControlPressed := False; ShiftPressed := False; { set the hook! }
764
HookHandle := SetWindowsHookEx(wh_JournalPlayback, Play, hInstance, 0); if HookHandle = 0 then raise ESetHookError.Create('Couldn''t set hook') else Playing := True; end; procedure MakeMessage(vKey: byte; M: word); { procedure builds a TEventMsg record that emulates a keystroke and } { adds it to message list } var E: PEventMsg; begin New(E); { allocate a message record } with E^ do begin Message := M; { set message field } { high byte of ParamL is the vk code, low byte is the scan code } ParamL := MakeWord(vKey, MapVirtualKey(vKey, 0)); ParamH := 1; { repeat count is 1 } Time := GetTickCount; { set time } end; MessageList.Add(E); end; procedure KeyDown(vKey: byte); { Generates KeyDownMessage } begin { don't generate a "sys" key if the control key is pressed (Windows quirk) } if (AltPressed and (not ControlPressed) and (vKey in [Ord('A')..Ord('Z')])) or (vKey = vk_Menu) then MakeMessage(vKey, wm_SysKeyDown) else MakeMessage(vKey, wm_KeyDown); end; procedure KeyUp(vKey: byte); { Generates KeyUp message } begin { don't generate a "sys" key if the control key is pressed (Windows quirk) } if AltPressed and (not ControlPressed) and (vKey in [Ord('A')..Ord('Z')]) then MakeMessage(vKey, wm_SysKeyUp) else MakeMessage(vKey, wm_KeyUp); end; procedure SimKeyPresses(VKeyCode: Word); { This function simulates keypresses for the given key, taking into } { account the current state of Alt, Control, and Shift keys } begin { press Alt key if flag has been set } if AltPressed then KeyDown(vk_Menu); { press Control key if flag has been set } if ControlPressed then KeyDown(vk_Control); { if shift is pressed, or shifted key and control is not pressed... } if (((Hi(VKeyCode) and 1) <> 0) and (not ControlPressed)) or ShiftPressed then KeyDown(vk_Shift); { ...press shift } KeyDown(Lo(VKeyCode)); { press key down } KeyUp(Lo(VKeyCode)); { release key } { if shift is pressed, or shifted key and control is not pressed... } if (((Hi(VKeyCode) and 1) <> 0) and (not ControlPressed)) or ShiftPressed then KeyUp(vk_Shift); { ...release shift } { if shift flag is set, reset flag } if ShiftPressed then begin
765
ShiftPressed := False; end; { Release Control key if flag has been set, reset flag } if ControlPressed then begin KeyUp(vk_Control); ControlPressed := False; end; { Release Alt key if flag has been set, reset flag } if AltPressed then begin KeyUp(vk_Menu); AltPressed := False; end; end; procedure ProcessKey(S: String); { This function parses each character in the string to create the message list } var KeyCode: word; Key: byte; index: integer; Token: TKeyString; begin index := 1; repeat case S[index] of KeyGroupOpen : begin { It's the beginning of a special token! } Token := ''; inc(index); while S[index] <> KeyGroupClose do begin { add to Token until the end token symbol is encountered } Token := Token + S[index]; inc(index); { check to make sure the token's not too long } if (Length(Token) = 7) and (S[index] <> KeyGroupClose) then raise EInvalidToken.Create('No closing brace'); end; { look for token in array, Key parameter will } { contain vk code if successful } if not FindKeyInArray(Token, Key) then raise EInvalidToken.Create('Invalid token'); { simulate keypress sequence } SimKeyPresses(MakeWord(Key, 0)); end; AltKey : begin { set Alt flag } AltPressed := True; end; ControlKey : begin { set Control flag } ControlPressed := True; end; ShiftKey : begin { set Shift flag } ShiftPressed := True; end; else begin { A normal character was pressed } { convert character into a word where the high byte contains } { the shift state and the low byte contains the vk code } KeyCode := vkKeyScan(MakeWord(Byte(S[index]), 0));
766
{ simulate keypress sequence } SimKeyPresses(KeyCode); end; end; inc(index); until index > Length(S); end; function SendKeys(S: String): TSendKeyError; export; { This is the one entry point. Based on the string passed in the S } { parameter, this function creates a list of keyup/keydown messages, } { sets a JournalPlayback hook, and replays the keystroke messages. } var i: byte; begin try Result := sk_None; { assume success } MessageList := TMessageList.Create; { create list of messages } ProcessKey(S); { create messages from string } StartPlayback; { set hook and play back messages } except { if an exception occurs, return an error code, and clean up } on E:ESendKeyError do begin MessageList.Free; if E is ESetHookError then Result := sk_FailSetHook else if E is EInvalidToken then Result := sk_InvalidToken; end else { Catch-all exception handler ensures than an exception } { doesn't walk up into application stack } Result := sk_UnknownError; end; end; exports SendKeys index 1; begin end
Simulating ButtonDown From: "James D. Rofkar"
No problem Paulo: You'll probably want to be using 10 TSpeedButton controls, or an array of them, since this button provides a "Down" property. Anyhow, set the "KeyPreview" property of your main form to "True". Then, in your "OnKeyDown" event handler, write something like this... case Key of VK_NUMPAD0: VK_NUMPAD1: VK_NUMPAD2: VK_NUMPAD3:
btn0.Down btn1.Down btn2.Down btn3.Down
:= := := :=
True; True; True; True;
767
VK_NUMPAD4: VK_NUMPAD5: VK_NUMPAD6: VK_NUMPAD7: VK_NUMPAD8: VK_NUMPAD9: end;
btn4.Down btn5.Down btn6.Down btn7.Down btn8.Down btn9.Down
:= := := := := :=
True; True; True; True; True; True;
And, in your "OnKeyUp" event handler, write something like... case Key of VK_NUMPAD0: VK_NUMPAD1: VK_NUMPAD2: VK_NUMPAD3: VK_NUMPAD4: VK_NUMPAD5: VK_NUMPAD6: VK_NUMPAD7: VK_NUMPAD8: VK_NUMPAD9: end;
btn0.Down btn1.Down btn2.Down btn3.Down btn4.Down btn5.Down btn6.Down btn7.Down btn8.Down btn9.Down
:= := := := := := := := := :=
False; False; False; False; False; False; False; False; False; False;
You'll want to experiment with the "AllowAllUp" property and the "GroupIndex" property to get the button response/effect you like. Again, an array of TSpeedButtons would be the most elegant solution to this problem, since you could use the VK_ constant as the index, and make both event handlers a one line call to Button[VK_x].Down := True {or False}.
How? ENTER key instead of TAB Here is something I picked up off Compuserve that should help. Simon Callcott CIS: 100574,1034 Using the &tl;Enter≷ key like a &tl;Tab≷ key with Delphi Controls The example code supplied here demonstrates how to trap the &tl;Enter≷ key and the cursor keys to provide better data entry processing. The trick is to overide the Keypress and KeyDown events so that they process the keys the way you want. In the examples supplied I have used the &tl;Enter≷ key to move to the next control (like the &tl;Tab≷ key) and the cursor Up and Down keys to move to the previous and next controls respectively. The Edit and EBEdit use the cursor keys as stated above, but the Combobox and the Listbox use Shift-Up and Shift-Down instead so as not to interfere with existing functionality. The Grid control uses the &tl;Enter≷ key to move between fields, however it will not move from the last field of the last row. It is very easy to make it exit the grid at this point if you need to. The method used to move to the next/previous control is the Windows API call SendMessage which is used to dispatch a WM_NEXTDLGCTL to the form the controls are children to. Delphi provides a function called GetParentForm to get the handle of the parent form of the control. These simple extensions can be expanded to respond to almost any keyboard event, and I think using this method is less trouble than trapping keys in the forms OnKey events (using keypreview:=true). Feel free to use the code as you wish, but if you discover something new please let me in on it! 768
{ Edit control that reponds as if the &tl;Tab≷ key has been pressed when an &tl;Enter≷ key is pressed, moving to the next control. Very simple extension to the KeyPress event, this technique should work with TDBedit as well, Useful for data entry type apps. Less trouble than using the Keypreview function of the form to do the same thing. Please Use Freely. Simon Callcott
CIS: 100574, 1034
} unit Entedit; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TEnterEdit = class(TEdit) private protected procedure KeyPress(var Key: Char); override; procedure KeyDown(var Key: Word; Shift: TShiftState); override; public published end; procedure Register; implementation procedure Register; begin RegisterComponents('Samples', [TEnterEdit]); end; procedure TEnterEdit.KeyPress(var Key: Char); var MYForm: TForm; begin if Key = #13 then begin MYForm := GetParentForm( Self ); if not (MYForm = nil ) then SendMessage(MYForm.Handle, WM_NEXTDLGCTL, 0, 0); Key := #0; end; if Key &tl;≷ #0 then inherited KeyPress(Key); end; procedure TEnterEdit.KeyDown(var Key: Word; Shift: TShiftState);
769
var MYForm: TForm; CtlDir: Word; begin if (Key = VK_UP) or (Key = VK_DOWN) then begin MYForm := GetParentForm( Self ); if Key = VK_UP then CtlDir := 1 else CtlDir :=0; if not (MYForm = nil ) then SendMessage(MYForm.Handle, WM_NEXTDLGCTL, CtlDir, 0); end else inherited KeyDown(Key, Shift); end; end.
Solution 2 Q. "Is there a way to use the return key for data entry, instead of tab or the mouse?" Ken Hale [email protected] Compuserve: 74633.2474 A. Use this code for an Edit's OnKeyPress event. procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char); begin If Key = #13 Then Begin SelectNext(Sender as tWinControl, True, True ); Key := #0; end; end;
This causes Enter to behave like tab. Now, select all controls on the form you'd like to exhibit this behavior (not Buttons) and go to the Object Inspector and set their OnKeyPress handler to EditKeyPress. Now, each control you selected will process Enter as Tab. If you'd like to handle this at the form (as opposed to control) level, reset all the controls OnKeyPress properties to blank, and set the _form_'s OnKeyPress property to EditKeyPress. Then, change Sender to ActiveControl and set the form's KeyPreview property to true: procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char); begin If Key = #13 Then begin SelectNext(ActiveControl as tWinControl, True, True ); Key := #0; end; end;
This will cause each control on the form (that can) to process Enter as Tab.
Caps Lock (and others like it) From: [email protected] (Mark Vaughan) ]-How Do I turn them on? (IN A DELPHI PROGRAM OF COURSE) i have tried and asked around
770
try this... procedure TMyForm.Button1Click(Sender: TObject); Var KeyState : TKeyboardState; begin GetKeyboardState(KeyState); if (KeyState[VK_NUMLOCK] = 0) then KeyState[VK_NUMLOCK] := 1 else KeyState[VK_NUMLOCK] := 0; SetKeyboardState(KeyState); end;
for caps lock substitute VK_CAPITAL for VK_NUMLOCK.
KeyDown Example "Dmitry"
disable Ctrl-Alt-Del From: Richard Leigh
The program should be nice and small so it can load before a user can hit CTRL-ALTDEL. My Solution : Compile a single WIN32API call into a small .exe in delphi. The Program : program small; {written by Richard Leigh, Deakin Univesity 1997} uses WinProcs; {$R *.RES} var
Dummy : integer;
begin Dummy := 0; {Disable ALT-TAB} SystemParametersInfo( SPI_SETFASTTASKSWITCH, 1, @Dummy, 0); {Disable CTRL-ALT-DEL} SystemParametersInfo( SPI_SCREENSAVERRUNNING, 1, @Dummy, 0); end.
This is the main unit - No forms and compiles small. Please email me and tell me if you liked this page. Last modified 03/12/00 12:08:28
772
Forms 42. Delphi Applications without Forms?
43. Showing own logo on start-up 44. Moving a form without a caption barforms2 45. Hack: Want VCL controls in a form's title bar caption area? 46. Storing TForm and/or its properties in a BLOB
47. Removing icon on taskbar forms5 48. How can I hide the form caption bar?? 49. Floating toolbar - here's some code to do it 50. Programming for different resolutions in Delphi 2.0 51. How do I know a Form is 'ready' resizing? 52. Preventing the user from resizing my window vertically 53. Preventing a From from Resizing 54. messagedlg centering 55. Center a Form
56. custom shaped forms?forms14
57. minimize non-main forms to the taskbarforms15
Delphi Applications without Forms? From: [email protected] (Bob Peck) You bet! First, select File|New Project and choose "CRT Application" from the Browse Gallery dialog. This will provide you with a project that is still a Windows program, but 773
WriteLn, ReadLn will be allowed in a Window but work like they did in DOS. If you wish, you can remove the WinCrt unit from the uses statement (if no user input/output is required, but is nice for debugging). I've done this before just to see how small an app can be and I've been able to create a simple EXE (it just beeps) that is only 3200 bytes or so in size! Try to do that in C++ these days! BTW, these "formless" apps are still Windows applications, so they can still call the Windows API routines. You'll just need to add WinProcs, WinTypes to your uses clause. You'll probably also want to add SysUtils and any other unit you find yourself needing.
Showing own logo on start-up From: "Mark R. Holbrook"
NOT showmodal }
. . { Do other app startup stuff here like open databases etc... } . .
{ Just after the block of code that creates all your forms and before the Application.Run statement do: }
logoform.Hide; logoform.Release;
This will display your logo form until you actually start the app running.
Moving a form without a caption bar Drag outline window From: [email protected] (Matthias Gerstgrasser) The following is from DKBS Helpfile (Delphi Knowledge Base System), they state, that this is one of Borland's TI's: Q: How can I make a form move by clicking and dragging in the client area instead of on the caption bar? A: The easiest way to do this is to "fool" Windows into thinking that you're actually clicking on the caption bar of a form. Do this by handling the wm_NCHitTest windows message... type
774
TForm1 = class(TForm) private { Private-Deklarationen } public { Public-Deklarationen } procedure WMNCHitTest(var M: TWMNCHitTest); message wm_NCHitTest; end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.WMNCHitTest(var M: TWMNCHitTest); begin inherited; { call the inherited message handler if M.Result = htClient then { is the click in the client area? M.Result := htCaption; { if so, make Windows think it's { on the caption bar. end;
} } } }
Drag whole window From: [email protected] But if i implement your code and drag the window, the window stays on the same spot and i drag an outline of the window. Can this be changed so i drag the whole window (like win95 plus)?
Here's a simple little component I wrote that'll handle it... Unit WinDrag; interface uses windows, sysutils, classes, dsgnintf; type TWinDrag = class(TComponent) private { Private Declarations } protected { Protected Declarations } public { Public Declarations } constructor Create(AOwner: TComponent); override; destructor Destroy; override; function GetDragFlag: boolean; procedure SetDragFlag(Status: Boolean); published Property DragFlag: Boolean read GetDragFlag write SetDragFlag; end; procedure Register; implementation constructor TWinDrag.Create(AOwner: TComponent); begin inherited Create(AOwner); DragFlag := GetDragFlag; end; destructor TWinDrag.Destroy;
775
begin inherited Destroy; end; function TWinDrag.GetDragFlag: boolean; var Value : Boolean; begin SystemParametersInfo(SPI_GETDRAGFULLWINDOWS, 0, @Value, 0); Result := Value; end; procedure TWinDrag.SetDragFlag(Status: Boolean); begin SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, Integer(Status), POINTER(0), 0); end; procedure Register; begin RegisterComponents('Samples', [TWinDrag]); end; end.
Moving form by dragging label From: "Rodney E Geraghty"
Hack: Want VCL controls in a form's title bar caption area? From: "James D. Rofkar"
776
Treat your controls like they belong in a separate modeless dialog that just so happens to track the movement and resizing of your main form. In addition, it always appear over the main form's caption area. This said, here's a simple hack that involves 2 forms and a drop-down listbox. After running this program, the drop-down listbox will appear in the Main form's caption area. Two key issues are: 1) trapping the Main form's WM_MOVE message; and 2) returning focus back to the Main form after users press any focus-grabbing controls (like a TComboBox, TButton, etc.) [FYI, I'm using 32-bit Delphi 2.0 Developer under Win95 -- even though this technique should work for all versions of Delphi] Here's the source for the Main form: unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) procedure FormResize(Sender: TObject); procedure FormShow(Sender: TObject); procedure FormHide(Sender: TObject); private { Private declarations } public { Public declarations } procedure WMMove(var Msg: TWMMove); message WM_MOVE; end; var Form1: TForm1; implementation uses Unit2; {$R *.DFM} procedure TForm1.FormResize(Sender: TObject); begin with Form2 do begin {Replace my magic numbers with real SystemMetrics info} Width := Form1.Width - 120; Top := Form1.Top + GetSystemMetrics(SM_CYFRAME); Left := ((Form1.Left + Form1.Width) - Width) - 60; end; end; procedure TForm1.FormShow(Sender: TObject); begin Form2.Show; end; procedure TForm1.FormHide(Sender: TObject); begin Form2.Hide; end; procedure TForm1.WMMove(var Msg: TWMMove);
777
begin inherited; if (Visible) then FormResize(Self); end; end.
Here's the source for the pseudo-caption area form. This is the form that contains the VCL controls you wish to place in the Main form's caption area. Essentially, it's a modeless dialog with the following properties: Caption='' {NULL string} Height={height of caption area} Width={width of all controls in form} BorderIcons=[] {none} BorderStyle=bsNone FormStyle=fsStayOnTop
Anyhow, here's the source for Form2: unit Unit2; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm2 = class(TForm) ComboBox1: TComboBox; procedure FormCreate(Sender: TObject); procedure ComboBox1Change(Sender: TObject); procedure FormResize(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form2: TForm2; implementation uses Unit1; {$R *.DFM} procedure TForm2.FormCreate(Sender: TObject); begin Height := ComboBox1.Height - 1; Width := ComboBox1.Width - 1; end; procedure TForm2.ComboBox1Change(Sender: TObject); begin Form1.SetFocus; end; procedure TForm2.FormResize(Sender: TObject); begin ComboBox1.Width := Width; end;
778
end.
The project file (.DPR) is fairly straightforward: program Project1; uses Forms, Unit1 in 'Unit1.pas' {Form1}, Unit2 in 'Unit2.pas' {Form2}; {$R *.RES} begin Application.Initialize; Application.CreateForm(TForm1, Form1); Application.CreateForm(TForm2, Form2); Application.Run; end.
That's it! Although some Delphi book authors state: "You can't place a Delphi component on the title bar, so there's literally no way to put a button there." you can at least "fake" the illusion...
Storing TForm and/or its properties in a BLOB From: [email protected] (Oliver Bollmann) Hallo, here are examples you need, I hope: procedure SaveToField(FField:TBlobField;Form:TComponent); var Stream: TBlobStream; FormName: string; begin FormName := Copy(Form.ClassName, 2, 99); Stream := TBlobStream.Create(FField, bmWrite); try Stream.WriteComponentRes(FormName, Form); finally Stream.Free; end; end; procedure LoadFromField(FField:TBlobField;Form:TComponent); var Stream: TBlobStream; I: integer; begin try Stream := TBlobStream.Create(FField, bmRead); try {delete all components} for I := Form.ComponentCount - 1 downto 0 do Form.Components[I].Free; Stream.ReadComponentRes(Form); finally Stream.Free;
779
end; except on EFOpenError do {nothing}; end; end;
Removing icon on taskbar From: AVONTURE Christophe
How can I hide the form caption bar?? From: "James D. Rofkar"
Then, in the actual CreateParams() method, specify something like this: procedure TForm1.Createparams(var Params: TCreateParams); begin inherited CreateParams(Params); with Params do Style := (Style or WS_POPUP) and (not WS_DLGFRAME); end;
Hopefully, you'll provide some UI mechanism for moving and closing the window.
Floating toolbar - here's some code to do it From: [email protected] (Anders Ohlsson) Someone asked for some code to make a form with no title bar moveable, kind of like a floating toolbar, for example FreeDock. Actually, for some of the stuff in here I spied on the FreeDock sources... This requires the use of some WinAPI functions. All WinAPI functions are however available at a touch of a key (F1 - OnLine Help)... Here's some code that does this (about 100 lines)... To make this work like intended: OR start a new project, make the form's borderstyle bsNone, add a panel, set the border style of the panel to bsSingle, add another panel with some caption, add a button that says 'toggle title bar', cut out the below code and insert it were it should be, enable the panel's three event handlers (MouseDown, MouseMove, MouseUp), enable the button's event handler (Click). Hope I didn't forget anything... ;-) It's done faster in Delphi than it's written here... ;-) 780
unit Unit1; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls; type TForm1 = class(TForm) Panel1: TPanel; Panel2: TPanel; Button1: TButton; procedure Panel1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure Panel1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); procedure Panel1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure Button1Click(Sender: TObject); private { Private declarations } OldX, OldY, OldLeft, OldTop : Integer; ScreenDC : HDC; MoveRect : TRect; Moving : Boolean; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.Panel1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if Button = mbLeft then begin SetCapture(Panel1.Handle); ScreenDC := GetDC(0); OldX := X; OldY := Y; OldLeft := X; OldTop := Y; MoveRect := BoundsRect; DrawFocusRect(ScreenDC,MoveRect); Moving := True; end; end; procedure TForm1.Panel1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin if Moving then begin DrawFocusRect(ScreenDC,MoveRect); OldX := X; OldY := Y; MoveRect := Rect(Left+OldX-OldLeft,Top+OldY-OldTop,
781
Left+Width+OldX-OldLeft,Top+Height+OldY-OldTop); DrawFocusRect(ScreenDC,MoveRect); end; end; procedure TForm1.Panel1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin if Button = mbLeft then begin ReleaseCapture; DrawFocusRect(ScreenDC,MoveRect); Left := Left+X-OldLeft; Top := Top+Y-OldTop; ReleaseDC(0,ScreenDC); Moving := False; end; end; procedure TForm1.Button1Click(Sender: TObject); var TitleHeight, BorderWidth, BorderHeight : Integer; begin TitleHeight := GetSystemMetrics(SM_CYCAPTION); BorderWidth := GetSystemMetrics(SM_CXBORDER)+GetSystemMetrics(SM_CXFRAME)-1; BorderHeight := GetSystemMetrics(SM_CYBORDER)+GetSystemMetrics(SM_CYFRAME)-2; if BorderStyle = bsNone then begin BorderStyle := bsSizeable; Top := Top-TitleHeight-BorderHeight; Height := Height+TitleHeight+2*BorderHeight; Left := Left-BorderWidth; Width := Width+2*BorderWidth; end else begin BorderStyle := bsNone; Top := Top+TitleHeight+BorderHeight; Height := Height-TitleHeight-2*BorderHeight; Left := Left+BorderWidth; Width := Width-2*BorderWidth; end; end; end.
Comments From: Steve Teixeira
782
private procedure WMNCHitTest(var M: TWMNCHitTest); message wm_NCHitTest; end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.WMNCHitTest(var M: TWMNCHitTest); begin inherited; { call the inherited message handler if M.Result = htClient then { is the click in the client area? M.Result := htCaption; { if so, make Windows think it's { on the caption bar. end;
} } } }
procedure TForm1.Button1Click(Sender: TObject); begin Close; end; end.
Programming for different resolutions in Delphi 2.0 You need to download: Ti2861 - Form display with different screen resolutions. from the Delphi Technical Support area of our web site at www.borland.com.
How do I know a Form is 'ready' resizing? From: [email protected] The methods you might be concerned with are: {Trap the GetMinMaxInfo message and set minimum window size} { using declared constants } procedure TForm1.WMGETMINMAXINFO( var message: TMessage ); var mStruct: PMinMaxInfo; begin mStruct := PMinMaxInfo(message.lParam); mStruct.ptMinTrackSize.x := HORIZONTALSIZE; mStruct.ptMinTrackSize.y := VERTICALSIZE; message.Result := 0; end;
783
Preventing the user from resizing my window vertically From: Bill Dekleris
and in the implementation section : procedure TMyForm.WMGetMinMaxInfo(var Msg: TWMGetMinMaxInfo); begin { ---------------------------------------------} { Put your numbers in place of } { MIN_WIDTH, MIN_HEIGHT, MAX_WIDTH, MAX_HEIGHT } { } { To allow only horizontal sizing, put } { form's 'Height' property in place of MIN_HEIGHT, MAX_HEIGHT } { ---------------------------------------------} Msg.MinMaxInfo^.ptMinTrackSize := Point(MIN_WIDTH, MIN_HEIGHT); Msg.MinMaxInfo^.ptMaxTrackSize := Point(MAX_WIDTH, MAX_HEIGHT); inherited end;
It should work fine.
Preventing a From from Resizing Taken from Borland tech info articles 2958: Preventing a From from Resizing In some cases, developers would want to create a regular window (Form) in Delphi that contains some of the characteristics of a dialog box. For example, they do not want to allow their users to resize the form at runtime due to user interface design issues. Other than creating the whole form as a dialog box, there is not a property or a method to handle this in a regular window in Delphi. But due to the solid connection between Delphi and the API layer, developers can accomplish this easily. The following example demonstrates a way of handling the Windows message "WM_GetMinMaxInfo" which allows the developer to restrict the size of windows (forms) at runtime to a specific value. In this case, it will be used to disable the functionality of sizing the window (form) at runtime. Consider the following unit: unit getminmax; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; type TForm1 = class(TForm)
784
private { Private declarations } procedure WMGetMinMaxInfo(var Msg: TWMGetMinMaxInfo); message WM_GETMINMAXINFO; procedure WMInitMenuPopup(var Msg: TWMInitMenuPopup); message WM_INITMENUPOPUP; procedure WMNCHitTest(var Msg: TWMNCHitTest); message WM_NCHitTest; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.WMGetMinMaxInfo(var Msg: TWMGetMinMaxInfo); begin inherited; with Msg.MinMaxInfo^ do begin ptMinTrackSize.x:= form1.width;
end;
ptMaxTrackSize.x:= form1.width; ptMinTrackSize.y:= form1.height; ptMaxTrackSize.y:= form1.height;
end; procedure TForm1.WMInitMenuPopup(var Msg: TWMInitMenuPopup); begin inherited; if Msg.SystemMenu then EnableMenuItem(Msg.MenuPopup, SC_SIZE, MF_BYCOMMAND or MF_GRAYED) end; procedure TForm1.WMNCHitTest(var Msg: TWMNCHitTest); begin inherited; with Msg do if Result in [HTLEFT, HTRIGHT, HTBOTTOM, HTBOTTOMRIGHT, HTBOTTOMLEFT, HTTOP, HTTOPRIGHT, HTTOPLEFT] then Result:= HTNOWHERE end; end. { End of Unit}
A message handler for the windows message "WM_GetMinMaxInfo" in the code above was used to set the minimum and maximum TrackSize of the window to equal the width and height of the form at design time. That was actually enough to disable the resizing of the window (form), but the example went on to handle another couple of messages just to make the application look professional. The first message was the "WMInitMenuPopup" and that was to gray out the size option from the System Menu so that the application does not give the impression that this functionality is available. The second message was the "WMNCHitTest" and that was used to disable the change of the cursor icon whenever the mouse goes over one of the borders of the window (form) for the same reason which is not to give the impression that the resizing functionality is available.
785
messagedlg centering From: "Jonathan M. Bell"
Center a Form I had a problem with centering a form after I had changed its dimensions at run-time. The poScreenCenter only works when the form is shown. But if you change the dimensions at run time your form doesn't center automatically.
[Robert Meek, [email protected]] I've used this in my FormCreate, but I guess it could be called during an OnPaint whenever you change the size of the form too couldn't it?
786
Form1.Left := (Screen.Width div 2) - (Form.Width div 2); Form1.Top := (Screen.Height div 2) - (Form.Height div 2);
[Giuseppe Madaffari, [email protected]] if you use SetBounds, form won't be repainted twice (one time for Left assignment and other time for Top assignment). Try: procedure CenterForm(AForm:TForm); var ALeft,ATop:Integer; begin ALeft := (Screen.Width - AForm.Width) div 2; ATop := (Screen.Height - AForm.Height) div 2; AForm.SetBounds(ALeft, ATop, AForm.Widht, AForm.Height); end;
[Jaycen Dale, [email protected]] Procedure CenterForm(aForm: TForm); Begin aform.left := (screen.width - aform.width) shr 1; aform.top := (screen.height - aform.height) shr 1; End;
custom shaped forms? From: "vanmc"
787
{ Private declarations } procedure CreateParams(var Params: TCreateParams); override; public { Public declarations } end; var Form1: TForm1; implementation procedure TForm1.CreateParams(var Params: TCreateParams); begin inherited createparams(params); {This makes a borderless and captionless form} params.style:=params.style or ws_popup xor ws_dlgframe; end; {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); var formrgn:hrgn; begin {makes the form clear} form1.brush.style:=bsclear; {makes the form round} GetWindowRgn(form1.Handle, formRgn); DeleteObject(formRgn); formrgn:= CreateroundRectRgn(0, 0,form1.width,form1.width,form1.width,form1.width); SetWindowRgn(form1.Handle, formrgn, TRUE); end; procedure TForm1.SpeedButton1Click(Sender: TObject); begin form1.close; end; end.
minimize non-main forms to the taskbar From: David Watt
788
2. Create a procedure in the secondary form's implementation section which intercepts messages and puts in place a substitution for SC_MINIMIXE messages so that the Application.Minimize procedure is executed instead. All other messages pass through normally; they are inherited. procedure TForm2.WMSysCommand(var Message: TMessage); begin if (Message.WParam = SC_MINIMIZE) then Application.Minimize else inherited; end;
That's does the trick. Please email me and tell me if you liked this page. Last modified 03/12/00 12:07:38
789
Networking 58. Browsing for a Network Machine (ala Network Neighborhood) 59. Accessing Netware Usernames 60. How to Connect to a Network Drive in Delphi 61. accessing network drive mapping dialog 62. Detect my own IP Address ?
Browsing for a Network Machine (ala Network Neighborhood) From: [email protected] (Michael J. Loeffler) I started messing around with a utility like this, just for fun. I never finished it. I know it did work at the time. You might be able to use some of the code as a base point. Don't know if you feel like poring through the details, but hope it helps. {
}
Network resource utility. Neighborhood.
Similar in function to NetWork-
Michael J. Loeffler 1997.01.31
unit netres_main_unit; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ComCtrls, StdCtrls, Buttons, Menus, ExtCtrls; type TfrmMain = class(TForm) tvResources: TTreeView; btnOK: TBitBtn; btnClose: TBitBtn; Label1: TLabel; barBottom: TStatusBar; popResources: TPopupMenu; mniExpandAll: TMenuItem; mniCollapseAll: TMenuItem; mniSaveToFile: TMenuItem; mniLoadFromFile: TMenuItem; grpListType: TRadioGroup; grpResourceType: TRadioGroup;
790
dlgOpen: TOpenDialog; dlgSave: TSaveDialog; procedure FormCreate(Sender: TObject); procedure btnCloseClick(Sender: TObject); procedure FormShow(Sender: TObject); procedure mniExpandAllClick(Sender: TObject); procedure mniCollapseAllClick(Sender: TObject); procedure mniSaveToFileClick(Sender: TObject); procedure mniLoadFromFileClick(Sender: TObject); procedure btnOKClick(Sender: TObject); private ListType, ResourceType: DWORD; procedure ShowHint(Sender: TObject); procedure DoEnumeration; procedure DoEnumerationContainer(NetResContainer: TNetResource); procedure AddContainer(NetRes: TNetResource); procedure AddShare(TopContainerIndex: Integer; NetRes: TNetResource); procedure AddShareString(TopContainerIndex: Integer; ItemName: String); procedure AddConnection(NetRes: TNetResource); public { Public declarations } end; var frmMain: TfrmMain; implementation {$R *.DFM} procedure TfrmMain.ShowHint(Sender: TObject); begin barBottom.Panels.Items[0].Text:=Application.Hint; end; procedure TfrmMain.FormCreate(Sender: TObject); begin Application.OnHint:=ShowHint; barBottom.Panels.Items[0].Text:=''; end; procedure TfrmMain.btnCloseClick(Sender: TObject); begin Close; end; {
Enumerate through all network resources: } procedure TfrmMain.DoEnumeration; var NetRes: Array[0..2] of TNetResource; Loop: Integer; r, hEnum, EntryCount, NetResLen: DWORD; begin case grpListType.ItemIndex of { Connected resources: } 1: ListType:=RESOURCE_CONNECTED; { Persistent resources: } 2: ListType:=RESOURCE_REMEMBERED; { Global: } else ListType:=RESOURCE_GLOBALNET; end;
791
case grpResourceType.ItemIndex of { Disk resources: } 1: ResourceType:=RESOURCETYPE_DISK; { Print resources: } 2: ResourceType:=RESOURCETYPE_PRINT; { All: } else ResourceType:=RESOURCETYPE_ANY; end; Screen.Cursor:=crHourGlass; try { Delete any old items in the tree view: } for Loop:=tvResources.Items.Count-1 downto 0 do tvResources.Items[Loop].Delete; except end; { Start enumeration: } r:=WNetOpenEnum(ListType,ResourceType,0,nil,hEnum); if r<>NO_ERROR then begin if r=ERROR_EXTENDED_ERROR then MessageDlg('Unable to Enumerate the Network.'+#13+ 'A network-specific error occurred.',mtError,[mbOK],0) else MessageDlg('Unable to Enumerate the Network.', mtError,[mbOK],0); Exit; end; try { We got a valid enumeration handle; walk the resources: } while (1=1) do begin EntryCount:=1; NetResLen:=SizeOf(NetRes); r:=WNetEnumResource(hEnum,EntryCount,@NetRes,NetResLen); case r of 0: begin { It's a container, iterate it: } if NetRes[0].dwUsage=RESOURCEUSAGE_CONTAINER then DoEnumerationContainer(NetRes[0]) else { Persistent and connected resources show up here: } if ListType in [RESOURCE_REMEMBERED,RESOURCE_CONNECTED] then AddConnection(NetRes[0]); end; { Done: } ERROR_NO_MORE_ITEMS: Break; { Other error: } else begin MessageDlg('Error Walking Resources.',mtError,[mbOK],0); Break; end; end; end; finally Screen.Cursor:=crDefault; { Close enumeration handle: } WNetCloseEnum(hEnum); end; end;
792
{ Enumerate through the specified container: This function is usually recursively called.
} procedure TfrmMain.DoEnumerationContainer(NetResContainer: TNetResource); var NetRes: Array[0..10] of TNetResource; TopContainerIndex: Integer; r, hEnum, EntryCount, NetResLen: DWORD; begin { Add container name to tree view: } AddContainer(NetResContainer); { Keep track of this item as current root: } TopContainerIndex:=tvResources.Items.Count-1; { Start enumeration: } if ListType=RESOURCE_GLOBALNET then { Enumerating global net: } r:=WNetOpenEnum(ListType,ResourceType,RESOURCEUSAGE_CONTAINER, @NetResContainer,hEnum) else { Enumerating connections or persistent (won't normally get here): } r:=WNetOpenEnum(ListType,ResourceType,RESOURCEUSAGE_CONTAINER, nil,hEnum); { Couldn't enumerate through this container; just make a note of it and continue on: } if r<>NO_ERROR then begin AddShareString(TopContainerIndex,'
793
end; end; end; { Close enumeration handle: } WNetCloseEnum(hEnum); end; procedure TfrmMain.FormShow(Sender: TObject); begin DoEnumeration; end; { Add item to tree view; indicate that it is a container: } procedure TfrmMain.AddContainer(NetRes: TNetResource); var ItemName: String; begin ItemName:=Trim(String(NetRes.lpRemoteName)); if Trim(String(NetRes.lpComment))<>'' then begin if ItemName<>'' then ItemName:=ItemName+' '; ItemName:=ItemName+'('+String(NetRes.lpComment)+')'; end; tvResources.Items.Add(tvResources.Selected,ItemName); end; {
Add child item to container denoted as current top: } procedure TfrmMain.AddShare(TopContainerIndex: Integer; NetRes: TNetResource); var ItemName: String; begin ItemName:=Trim(String(NetRes.lpRemoteName)); if Trim(String(NetRes.lpComment))<>'' then begin if ItemName<>'' then ItemName:=ItemName+' '; ItemName:=ItemName+'('+String(NetRes.lpComment)+')'; end; tvResources.Items.AddChild(tvResources.Items[TopContainerIndex],ItemName); end; { Add child item to container denoted as current top; this just adds a string for purposes such as being unable to enumerate a container. That is, the container's shares are not accessible to us.
} procedure TfrmMain.AddShareString(TopContainerIndex: Integer; ItemName: String); begin tvResources.Items.AddChild(tvResources.Items[TopContainerIndex],ItemName); end; { Add a connection to the tree view. Mostly used for persistent and currently connected resources to be displayed.
} procedure TfrmMain.AddConnection(NetRes: TNetResource);
794
var ItemName: String; begin ItemName:=Trim(String(NetRes.lpLocalName)); if Trim(String(NetRes.lpRemoteName))<>'' then begin if ItemName<>'' then ItemName:=ItemName+' '; ItemName:=ItemName+'-> '+Trim(String(NetRes.lpRemoteName)); end; tvResources.Items.Add(tvResources.Selected,ItemName); end; { Expand all containers in the tree view: } procedure TfrmMain.mniExpandAllClick(Sender: TObject); begin tvResources.FullExpand; end; { Collapse all containers in the tree view: } procedure TfrmMain.mniCollapseAllClick(Sender: TObject); begin tvResources.FullCollapse; end; { Allow saving of tree view to a file: } procedure TfrmMain.mniSaveToFileClick(Sender: TObject); begin if dlgSave.Execute then tvResources.SaveToFile(dlgSave.FileName); end; { Allow loading of tree view from a file: } procedure TfrmMain.mniLoadFromFileClick(Sender: TObject); begin if dlgOpen.Execute then tvResources.LoadFromFile(dlgOpen.FileName); end; { Rebrowse: } procedure TfrmMain.btnOKClick(Sender: TObject); begin DoEnumeration; end; end.
Accessing Netware Usernames From: "Ryan Smith"
795
unit GetLogin; {This unit is a wrapper for several external functions in the NWCALLS.DLL} {Adapted by Ray Buck from code written by Gregory Trubetskoy} {The unit contains a function that returns the Netware User ID} {and one that returns the user's full name.} interface uses SysUtils, Messages, Dialogs; function GetUserLogin: string; function GetUserFullName( SomeUser: string): string; implementation type NWTimeStamp = record Year: byte; Month: byte; Day: byte; Hour: byte; Minute: byte; Second: byte; DayOfWeek: byte; end; {Netware API's - require NWCALLS.DLL} function NWGetDefaultConnectionID(var Connection: word): word; far; external 'NWCALLS'; function NWGetConnectionNumber(Connection: word; var ConnectionNumber: word): word; far; external 'NWCALLS'; function NWGetConnectionInformation(Connection: word; ConnectionNumber: word; ObjectName: pchar; var ObjectType: word; var ObjectID: word; var LoginTime: NWTimeStamp):word; far; external 'NWCALLS'; function NWReadPropertyValue(Connection: ObjectName: ObjectType: PropertyName: DataSetIndex: DataBuffer: var More: var Flags: far; external 'NWCALLS'; { end of Netware API stuff }
word; pChar; word; pChar; byte; pChar; byte; byte): word;
function GetUserLogin: string; var ConnectionID: word; ConnectionNumber: word; RC: word; Name: array[0..50] of Char; ObjectType: word; ObjectID: word; LoginTime: NWTimeStamp; begin RC := NWGetDefaultConnectionID(ConnectionID); RC := NWGetConnectionNumber(ConnectionID, ConnectionNumber); RC := NWGetConnectionInformation( ConnectionID, ConnectionNumber,
796
Name, ObjectType, ObjectID, LoginTime); Result := StrPas(Name); end; function GetUserFullName( SomeUser: string): string; {The real user name is a 'property' called 'IDENTIFICATON'. You have to call NWReadPropertyValue passing it (among other things) your ConnectionID, the name of the object (same as the login of the user who's full name you need) and the name property you want to retrieve, in this case 'IDENTIFICATION' (which translated from Novellish to English means 'Full Name').} var ConnectionID: word; RC: word; Name: array[0..50] of Char; ObjectType: word; PropName: array[0..14] of Char; DataSetIndex: byte; FullName: array[0..127] of Char; More: byte; Flags: byte; begin RC := NWGetDefaultConnectionID(ConnectionID); ObjectType := 256; {user} StrPCopy(PropName, 'IDENTIFICATION'); DataSetIndex := 1; StrPCopy(Name, SomeUser); RC := NWReadPropertyValue( ConnectionID, Name, ObjectType, PropName, DataSetIndex, FullName, More, Flags); if RC = 35324 then MessageDlg('No user ' + SomeUser + ' exists on this server!', mtError, [mbOK], 0); Result := StrPas(FullName); end; end.
How to Connect to a Network Drive in Delphi This document explains how to create a 'Network' button that brings up a connection dialog and then sets a drive box to point to the new drive in Delphi. The code was created in Delphi 2, but doing it in Delphi 1 should be about the same procedure. Create a command button named NetBtn, and a drive combo box named DriveBox. Then type this code in the OnClick event for the button: procedure TStartForm.NetBtnClick(Sender: TObject); var OldDrives: TStringList; i: Integer; begin OldDrives := TStringList.Create;
797
OldDrives.Assign(Drivebox.Items); // Remember old drive list // Show the connection dialog if WNetConnectionDialog(Handle, RESOURCETYPE_DISK) = NO_ERROR then begin DriveBox.TextCase := tcLowerCase; // Refresh the drive list box for i := 0 to DriveBox.Items.Count - 1 do begin if Olddrives.IndexOf(Drivebox.Items[i]) = -1 then begin // Find new Drive letter DriveBox.ItemIndex := i; // Updates the drive list box to new drive letter DriveBox.Drive := DriveBox.Text[1]; // Cascades the update to connected directory lists, etc end; end; DriveBox.SetFocus; end; OldDrives.Free; end;
You must also add the WinProcs and WinTypes units to the uses clause of your unit. The difficult part here is that the DriveComboBox lacks a refresh function. By setting the TextCase property, we force an update of the box. Copyright © 1997 by Josef Garvi
accessing network drive mapping dialog From: Edward McSweeney
Detect my own IP Address ? From: Andreas Hoerstemeier
798
end;
This give the (first) network address of the local computer, and if not connected the 127.0.0.1 as the standard address for the local computer. You only need a winsock.dcu/winsock.pas as this one isn't included with D1; I have one together with my tcpip component pack (where I snipped out the above routine). Please email me and tell me if you liked this page. Last modified 03/12/00 12:09:27
799
Printer 63. Help on Printer Control Codes 64. How to get Paper Source? 65. Printing Rotated text...printer2 66. Stretched bitmap on TPrinter 67. How to print a bitmap? 68. Dump a text file 69. Printing a line at a time 70. Printing Tricks 71. Passthough escape function 72. How to print exact sizes 73. Windows API about Printer 74. Property changes in same printprinter11 75. Paper Formatsprinter12
Help on Printer Control Codes From: [email protected] (David Block) Vincent Lim
You need to use the Passthrough printer Escape function to send data directly to the printer. If you're using WriteLn, then it won't work. Here's some code to get you started: unit Passthru; interface uses printers, WinProcs, WinTypes, SysUtils; Procedure
PrintTest;
implementation Type
TPassThroughData = Record nLen : Integer; Data : Array[0..255] of byte; end;
Procedure DirectPrint(s : String); var PTBlock : TPassThroughData; Begin PTBlock.nLen := Length(s); StrPCopy(@PTBlock.Data,s); Escape(printer.handle, PASSTHROUGH,0,@PTBlock,nil); End;
800
Procedure PrintTest; Begin Printer.BeginDoc; DirectPrint(CHR(27)+'&l1O'+'Hello, World!'); Printer.EndDoc; End; end.
How to get Paper Source? 'Joe C. Hecht'
Another way is to change TPrinter. This will enable you to change settings in mid job. You must make the change >>>between<<< pages. To do this: Before every startpage() command in printers.pas in the Source\VCL directory add something like: DevMode.dmPaperSize:=DMPAPER_LEGAL {any other devicemode settings go here} Windows.ResetDc(dc,Devmode^);
801
This will reset the pagesize. you can look up DEVMODE in the help to find other paper sizes. You will need to rebuild the vcl source for this to work, by adding the path to the VCL source directory to the beginning of the library path s tatement under tools..options.. library...libaray path. Quit Delphi then do a build all. Another quick note... When changing printers, be aware that fontsizes may not always scale properly. To ensure proper scaling set the PixelsPerInch property of the font. Here are two examples: uses Printers; var MyFile: TextFile; begin AssignPrn(MyFile); Rewrite(MyFile); Printer.Canvas.Font.Name := 'Courier New'; Printer.Canvas.Font.Style := [fsBold]; Printer.Canvas.Font.PixelsPerInch:= GetDeviceCaps(Printer.Canvas.Handle, LOGPIXELSY); Writeln(MyFile, 'Print this text'); System.CloseFile(MyFile); end;
uses Printers; begin Printer.BeginDoc; Printer.Canvas.Font.Name := 'Courier New'; Printer.Canvas.Font.Style := [fsBold]; Printer.Canvas.Font.PixelsPerInch:= GetDeviceCaps(Printer.Canvas.Handle, LOGPIXELSY); Printer.Canvas.Textout(10, 10, 'Print this text'); Printer.EndDoc; end;
Printing Rotated text... 1. From: "Peter Szymiczek"
802
procedure AngleTextOut(CV: TCanvas; const sText: String; x, y, angle:integer); var LogFont: TLogFont; SaveFont: TFont; begin SaveFont := TFont.Create; SaveFont.Assign(CV.Font); GetObject(SaveFont.Handle, sizeof(TLogFont), @LogFont); with LogFont do begin lfEscapement := angle *10; lfPitchAndFamily := FIXED_PITCH or FF_DONTCARE; end; {with} CV.Font.Handle := CreateFontIndirect(LogFont); SetBkMode(CV.Handle, TRANSPARENT); CV.TextOut(x, y, sText); CV.Font.Assign(SaveFont); SaveFont.Free; end;
2. From: Jukka Palomäki
Draw(x, y, b2); b1.Free; b2.Free; end end;
3. From: "Dmitry"
.... and then I used any draw text function for text output. 4. "Eric Lawrence"
803
The method (1) shown is quite slow, as it requires drawing the text, and then inefficiently rotating it. Try this instead: procedure TForm1.TextUp(aRect:tRect;aTxt:String); var LFont: TLogFont; hOldFont, hNewFont: HFont; begin GetObject(Canvas.Font.Handle,SizeOf(LFont),Addr(LFont)); LFont.lfEscapement := 900; hNewFont := CreateFontIndirect(LFont); hOldFont := SelectObject(Canvas.Handle,hNewFont); Canvas.TextOut(aRect.Left+2,aRect.Top,aTxt); hNewFont := SelectObject(Canvas.Handle,hOldFont); DeleteObject(hNewFont); end;
Stretched bitmap on TPrinter [email protected] (Alexander Wernhart) On Tue, 4 Feb 1997 20:54:43 -0300, Ruy Ponce de Leon Junior
Try this: procedure DrawImage(Canvas: TCanvas; DestRect: TRect; ABitmap: TBitmap); var Header, Bits: Pointer; HeaderSize: Integer; BitsSize: Longint; begin GetDIBSizes(ABitmap.Handle, HeaderSize, BitsSize); Header := MemAlloc(HeaderSize); Bits := MemAlloc(BitsSize); try GetDIB(ABitmap.Handle, ABitmap.Palette, Header^, Bits^); StretchDIBits(Canvas.Handle, DestRect.Left, DestRect.Top, DestRect.Right, DestRect.Bottom, 0, 0, ABitmap.Width, ABitmap.Height, Bits,TBitmapInfo(Header^), DIB_RGB_COLORS, SRCCOPY); { you might want to try DIB_PAL_COLORS instead, but this is well beyond the scope of my knowledge. } finally MemFree(Header, HeaderSize); MemFree(Bits, BitsSize); end; end; { Print a Bitmap using the whole Printerpage } procedure PrintBitmap(ABitmap: TBitmap); var relheight, relwidth: integer;
804
begin screen.cursor := crHourglass; Printer.BeginDoc; if ((ABitmap.width / ABitmap.height) > (printer.pagewidth /printer.pageheight)) then begin { Stretch Bitmap to width of Printerpage } relwidth := printer.pagewidth; relheight := MulDiv(ABitmap.height, printer.pagewidth,ABitmap.width); end else begin { Stretch Bitmap to height of Printerpage } relwidth := MulDiv(ABitmap.width, printer.pageheight, ABitmap.height); relheight := printer.pageheight; end; DrawImage(Printer.Canvas, Rect(0, 0, relWidth, relHeight), ABitmap); Printer.EndDoc; screen.cursor := crDefault; end;
How to print a bitmap? Use the following code. Remember to include the Printers unit in the uses clause : Lines followed by // ** are essential. The others are to get the scaling correct otherwise you end up with extremely small images. Printer resolutions are higher than your screen resolution. procedure TForm1.Button1Click(Sender: TObject); var ScaleX, ScaleY: Integer; R: TRect; begin Printer.BeginDoc; // ** with Printer do try ScaleX := GetDeviceCaps(Handle, logPixelsX) div PixelsPerInch; ScaleY := GetDeviceCaps(Handle, logPixelsY) div PixelsPerInch; R := Rect(0, 0, Image1.Picture.Width * ScaleX, Image1.Picture.Height * ScaleY); Canvas.StretchDraw(R, Image1.Picture.Graphic); // ** finally EndDoc; // ** end; end;
Dump a text file From: Chris Monson
805
Then use WriteFile to send a string of characters or use While not TransmitCommChar( LPTHandle, CharToSend ) do Application.ProcessMessages;
It sends one raw character at a time to the parallel port. It waits for the recent character to get processed and then immediately sends a new one. I got it printing stuff pretty fast.
Printing a line at a time From: Peter van Lonkhuyzen
According to M$ this is illegal as it "defeats the multitasking nature" but I needed the same functionality. I created the following derivative of the TPrinter object. It works perfectly on dotmatrix printers. sample usage var Myprinter : TRawPrinter; oldprinter : TPrinter; begin MyPrinter:=TRawPrinter.Create; oldprinter:=setprinter(MyPrinter); try if Printdialog1.execute then begin myprinter.startraw; myprinter.write('khsdhskhkshdksd'); myprinter.writeln; myprinter.endraw; end finally setprinter(oldprinyter); myprinter.free; end end;
Here is the code for the raw printer object. unit Rawprinter; interface uses printers,windows; type TRawprinter =class(TPrinter) public dc2 : HDC; procedure startraw; procedure endraw; procedure write(const s : string); procedure writeln; end; implementation
806
uses sysutils,forms; function AbortProc(Prn: HDC; Error: Integer): Bool; stdcall; begin Application.ProcessMessages; Result := not Printer.Aborted; end; type TPrinterDevice = class Driver, Device, Port: String; constructor Create(ADriver, ADevice, APort: PChar); function IsEqual(ADriver, ADevice, APort: PChar): Boolean; end; constructor TPrinterDevice.Create(ADriver, ADevice, APort: PChar); begin inherited Create; Driver := ADriver; Device := ADevice; Port := APort; end; function TPrinterDevice.IsEqual(ADriver, ADevice, APort: PChar): Boolean; begin Result := (Device = ADevice) and (Port = APort); end; procedure TRawprinter.startraw; var CTitle: array[0..31] of Char; CMode : Array[0..4] of char; DocInfo: TDocInfo; r : integer; begin StrPLCopy(CTitle, Title, SizeOf(CTitle) - 1); StrPCopy(CMode, 'RAW'); FillChar(DocInfo, SizeOf(DocInfo), 0); with DocInfo do begin cbSize := SizeOf(DocInfo); lpszDocName := CTitle; lpszOutput := nil; lpszDatatype :=CMode; end; with TPrinterDevice(Printers.Objects[PrinterIndex]) do begin DC2 := CreateDC(PChar(Driver), PChar(Device), PChar(Port), nil); end; SetAbortProc(dc2, AbortProc); r:=StartDoc(dc2, DocInfo); end; procedure TRawprinter.endraw; var r : integer; begin r:=windows.enddoc(dc2); end; type passrec = packed record l : word; s : Array[0..255] of char; end; var pass : Passrec; procedure TRawprinter.write(const s : string);
807
begin pass.l:=length(s); strpcopy(pass.s,s); escape(dc2,PASSTHROUGH,0,@pass,nil); end; procedure TRawprinter.writeln; begin pass.l:=2; strpcopy(pass.s,#13#10); escape(dc2,PASSTHROUGH,0,@pass,nil); end; end.
Printing Tricks [email protected] (Robert Gilland) "Guy Vandenberg"
= 0.04;
record X,Y: Integer; end;
var FDeviceName : String; {Get the name} FPageHeightPixel, FPageWidthPixel : Integer ; {Page height and Page Width} FOrientation : TPrinterOrientation; {Orientation} FPrintOffsetPixels : TOffset; FPixelsPerMMX,FPixelsPerMMY: Real; MMSize, FPageHeightMM : Integer; TheReport, TheHead, HeadLine, RecordLine, TFname, TLname :String; procedure TMissing_Rep.GetDeviceSettings; var retval: integer; PixX, PixY: Integer; begin FDeviceName := Printer.Printers[Printer.PrinterIndex]; {Get the name} FPageHeightPixel := Printer.PageHeight; {Page height} FPageWidthPixel := Printer.PageWidth; {Page Width} FOrientation := Printer.Orientation; {Orientation} {Get the printable area offsets} {$IFDEF WIN32} FPrintOffsetPixels.X := GetDeviceCaps(Printer.Handle, PHYSICALOFFSETX); FPrintOffsetPixels.Y := GetDeviceCaps(Printer.Handle, PHYSICALOFFSETY); {$ELSE} retval := Escape(Printer.Handle,GETPRINTINGOFFSET, 0, nil, @FPrintOffsetPixels); {$ENDIF}
808
{Get Pixels per Milimeter Ratio} PixX := GetDeviceCaps(Printer.Handle, LOGPIXELSX); PixY := GetDeviceCaps(Printer.Handle, LOGPIXELSY); FPixelsPerMMX := INCHES_PER_MILIMETER * PixX; FPixelsPerMMY := INCHES_PER_MILIMETER * PixY; FPageHeightMM := Round(FPageHeightPixel/FPixelsPerMMY); end; function TMissing_Rep.PutText(mmX,mmY: Integer; S: string; LeftAlign: Boolean): boolean; var X, Y: Integer; align: WORD; begin if LeftAlign then align := SetTextAlign(Printer.Handle,TA_BOTTOM or TA_LEFT) else align := SetTextAlign(Printer.Handle,TA_BOTTOM or TA_RIGHT); result := FALSE; {Assume fail} X := Trunc(mmX * FPixelsPerMMX) - FPrintOffsetPixels.X; Y := Trunc(mmY * FPixelsPerMMY) - FPrintOffsetPixels.Y; if X < 0 then exit; if Y < 0 then exit; Printer.Canvas.TextOut(X,Y,S); result := TRUE; end; procedure TMissing_Rep.Print_ButClick(Sender: TObject); var PixelSize: Integer; begin Print_But.Enabled := False; if PrintDialog1.Execute then begin Printer.Canvas.Font := Missing_Rep.Font; PixelSize := Printer.Canvas.TextHeight('Yy'); MMSize := Round(PixelSize/FPixelsPerMMY); Printer.Title := 'Breast Cancer Project Missing Report'; Printer.BeginDoc; { begin to send print job to printer } PrintGenerator; Printer.EndDoc; { EndDoc ends and starts printing print job } end; Print_But.Enabled := True; end; procedure TMissing_Rep.PrintGenerator; Var yLoc , NumRows, TheRow :Integer; procedure Heading; begin yLoc := 20; PutText(20, 20, TheHead, TRUE); yLoc := yLoc + MMSize; PutText(20, yLoc, StringGrid1.Cells[0,0], PutText(60, yLoc, StringGrid1.Cells[1,0], PutText(100, yLoc, StringGrid1.Cells[2,0], PutText(120, yLoc, StringGrid1.Cells[3,0], PutText(150, yLoc, StringGrid1.Cells[4,0], yLoc := yLoc + MMSize; end;
TRUE); TRUE); TRUE); TRUE); TRUE);
procedure Footer; begin
809
PutText(100,FPageHeightMM,InttoStr(Printer.PageNumber), TRUE); end; begin Heading; TheRow := 1; while (TheRow < StringGrid1.RowCount) do begin if (yLoc > (FPageHeightMM - MMSize)) then begin Footer; Printer.NewPage; Heading; end; TheGauge.Progress := Round(100 * TheRow/(StringGrid1.RowCount - 1)); PutText(20, yLoc, StringGrid1.Cells[0,TheRow], TRUE); PutText(60, yLoc, StringGrid1.Cells[1,TheRow], TRUE); PutText(100, yLoc, StringGrid1.Cells[2,TheRow], TRUE); PutText(120, yLoc, StringGrid1.Cells[3,TheRow], TRUE); PutText(150, yLoc, StringGrid1.Cells[4,TheRow], TRUE); yLoc := yLoc + MMSize; TheRow := TheRow + 1; end; Footer; end;
Passthough escape function "Joe C. Hecht"
Although Delphi's TPrinter unit makes it easy to interface to a given printer, there are times when you may need to drop down to the printers level and send device specific escape sequences. Under sixteen bit versions of Windows, this was as easy as opening the printer port, but under Windows NT, directly accessing the hardware is is illegal. One solution is to use the Windows "PASSTHROUGH" escape to send an escape sequence directly to the printer. In order to use the "PASSTHROUGH" escape, it must be supported by the printer driver. Be forwarned that not all printer drivers will support this feature. It's worth noting that the "PASSTHROUGH" escape is documented as obsolete for thirtytwo bit applications. It should be a number of years before this escape goes by the way, since it is used in many commercial applications. The example code presented is not targeted to any specific printer model. You will need to know the correct escape sequences to send to the printer you are interfacing to. Note that you must still call the BeginDoc and EndDoc methods of TPrinter. During the BeginDoc call, the printer driver initializes the printer as necessary, and during the EndDoc call, the printer driver will uninitialize the printer and eject the page. When you do make your escape call, the printer may be set for the current windows mapping mode if the printer supports scaling internaly. Technically, you should not do anything that would cause the printer memory to be reset, or eject a page with an escape sequence. In other words, try 810
to leave the printer in the same state it was in when you made your escape. This is more important on intellegent printers (Postscript printers), and not important at all on a standard TTY line printer, where you can do just about anything you like, including ejecting pages. Code Example: You will need to declare a structure to hold the buffer you are sending. The structure of the buffer is defined as a word containing the length of the buffer, followed by the buffer containing the data. Before making the escape call to pass the data, we will use the escape "QUERYESCSUPPORT" to determine if the "PASSTHROUGH" escape is supported by the print driver. Finally, be aware that your data will be inserted directly into the printers data stream. On some printer models (Postscript), you may need to add a space to the start and end of your data to separate your data from the printer drivers data. (Postscript is a Registered Trademark of Adobe Systems Incorporated) *) unit Esc1; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation { add the printers unit } uses Printers; {$R *.DFM} { declare the "PASSTHROUGH" structure } type TPrnBuffRec = record BuffLength : word; Buffer : array [0..255] of char; end; procedure TForm1.Button1Click(Sender: TObject); var Buff : TPrnBuffRec; TestInt : integer; s : string; begin { Test to see if the "PASSTHROUGH" escape is supported } TestInt := PASSTHROUGH;
811
if Escape(Printer.Handle, QUERYESCSUPPORT, sizeof(TestInt), @TestInt, nil) > 0 then begin { Start the printout } Printer.BeginDoc; { Make a string to passthrough } s := ' A Test String '; { Copy the string to the buffer } StrPCopy(Buff.Buffer, s); { Set the buffer length } Buff.BuffLength := StrLen(Buff.Buffer); { Make the escape} Escape(Printer.Canvas.Handle, PASSTHROUGH, 0, @Buff, nil); { End the printout } Printer.EndDoc; end; end; end.
How to print exact sizes From: "Earl F. Glynn"
Use verbose style
Shows values returned by GetDeviceCaps Windows API function. efg, 19 September 1996} interface
812
uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls; type TForm1 = class(TForm) Print: TButton; Image1: TImage; procedure PrintClick(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation USES Printers; {WINAPI GetDeviceCaps Constants from C++ windows.h and wingdi.h} {The indivdual constants are defined here CONST DRIVERVERSION = 0; TECHNOLOGY = 2; {See windows.h HORZSIZE = 4; VERTSIZE = 6; HORZRES = 8; VERTRES = 10; BITSPIXEL = 12; PLANES = 14; NUMBRUSHES = 16; NUMPENS = 18; NUMMARKERS = 20; NUMFONTS = 22; NUMCOLORS = 24; PDEVICESIZE = 26; CURVECAPS = 28; {See windows.h LINECAPS = 30; {See windows.h POLYGONALCAPS = 32; {See windows.h TEXTCAPS = 34; {See windows.h CLIPCAPS = 36; {See windows.h RASTERCAPS = 38; {See windows.h ASPECTX = 40; ASPECTY = 42; ASPECTXY = 44; LOGPIXELSX LOGPIXELSY
= =
SIZEPALETTE NUMRESERVED COLORRES
= 104; = 106; = 108;
PHYSICALWIDTH PHYSICALHEIGHT PHYSICALOFFSETX PHYSICALOFFSETY SCALINGFACTORX SCALINGFACTORY
= = = = = =
for reference only} for mask values}
for for for for for for
mask mask mask mask mask mask
values} values} values} values} values} values}
for for for for for for
definition} definition} definition} definition} definition} definition}
88; 90;
110; 111; 112; 113; 114; 115;
{See {See {See {See {See {See
wingdi.h wingdi.h wingdi.h wingdi.h wingdi.h wingdi.h
DeviceCapsString: ARRAY[1..34] OF STRING = ('DRIVERVERSION', 'TECHNOLOGY', 'HORZSIZE',
813
'VERTSIZE', 'HORZRES', 'VERTRES', 'BITSPIXEL', 'PLANES', 'NUMBRUSHES', 'NUMPENS', 'NUMMARKERS', 'NUMFONTS', 'NUMCOLORS', 'PDEVICESIZE', 'CURVECAPS', 'LINECAPS', 'POLYGONALCAPS', 'TEXTCAPS', 'CLIPCAPS', 'RASTERCAPS', 'ASPECTX', 'ASPECTY', 'ASPECTXY', 'LOGPIXELSX', 'LOGPIXELSY', 'SIZEPALETTE', 'NUMRESERVED', 'COLORRES', 'PHYSICALWIDTH', 'PHYSICALHEIGHT', 'PHYSICALOFFSETX','PHYSICALOFFSETY','SCALINGFACTORX', 'SCALINGFACTORY'); DeviceCapsIndex: ARRAY[1..34] OF INTEGER = ( 0, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 88, 90, 104, 106, 108, 110, 111, 112, 113, 114, 115); {$R *.DFM} FUNCTION iPosition(const i: INTEGER): INTEGER; BEGIN RESULT := Integer(i * LongInt(Printer.PageWidth) END {iPosition};
DIV 1000)
FUNCTION jPosition(const j: INTEGER): INTEGER; BEGIN RESULT := Integer(j * LongInt(Printer.PageHeight) DIV 1000) END {jPosition}; procedure TForm1.PrintClick(Sender: TObject); VAR DestinationRectangle: TRect; GraphicAspectRatio : DOUBLE; i : INTEGER; j : INTEGER; iBase : INTEGER; iPixelsPerInch : WORD; jBase : INTEGER; jDelta : INTEGER; jPixelsPerInch : WORD; OffScreen : TBitMap; PixelAspectRatio : DOUBLE; SourceRectangle : TRect; TargetRectangle : TRect; value : INTEGER; x : DOUBLE; y : DOUBLE; begin Printer.Orientation := poLandscape; Printer.BeginDoc; {Draw a rectangle to show the margins} Printer.Canvas.Rectangle(0,0, Printer.PageWidth, Printer.PageHeight); {Properties of Printer and Page} Printer.Canvas.Font.Name := 'Times New Roman'; Printer.Canvas.Font.Size := 12; Printer.Canvas.Font.Style := [fsBold]; Printer.Canvas.TextOut(iPosition(50), jPosition(40), 'Printer/Page Properties'); Printer.Canvas.Font.Style := []; Printer.Canvas.Font.Size := 10;
814
iBase := iPosition(50); jBase := 60; jDelta := 18; Printer.Canvas.TextOut(iPosition(50), jPosition(jBase), Printer.Printers.Strings[Printer.PrinterIndex]); INC (jBase, jDelta); Printer.Canvas.TextOut(iBase, jPosition(jBase), 'Pixels: ' + IntToStr(Printer.PageWidth) + ' X ' + IntToStr(Printer.PageHeight)); INC (jBase, jDelta);
+
Printer.Canvas.TextOut(iBase, jPosition(jBase), 'Inches: ' + FormatFloat('0.000', Printer.PageWidth / Printer.Canvas.Font.PixelsPerInch) + ' X ' FormatFloat('0.000', Printer.PageHeight / Printer.Canvas.Font.PixelsPerInch)); INC (jBase, 2*jDelta); Printer.Canvas.TextOut(iBase, jPosition(jBase), 'Font: ' + Printer.Canvas.Font.Name + ' Size: IntToStr(Printer.Canvas.Font.Size)); INC (jBase, jDelta);
' +
Printer.Canvas.TextOut(iBase, jPosition(jBase), 'PixelsPerInch: ' + IntToStr(Printer.Canvas.Font.PixelsPerInch)); INC (jBase, jDelta); Printer.Canvas.TextOut(iBase, jPosition(jBase), '''TEXT'': ' + IntToStr(Printer.Canvas.TextWidth('TEXT')) + ' X ' + IntToStr(Printer.Canvas.TextHeight('TEXT')) + ' pixels'); {GetDeviceCaps Values} INC (jBase, 2*jDelta); Printer.Canvas.Font.Size := 12; Printer.Canvas.Font.Style := [fsBold]; Printer.Canvas.TextOut(iBase, jPosition(jBase), 'GetDeviceCaps'); INC (jBase, jDelta); Printer.Canvas.Font.Size := 10; Printer.Canvas.Font.Style := []; FOR j := LOW(DeviceCapsIndex) TO HIGH(DeviceCapsIndex) DO BEGIN value := GetDeviceCaps(Printer.Handle, DeviceCapsIndex[j]); Printer.Canvas.TextOut(iBase, jPosition(jBase), DeviceCapsString[j]); IF (DeviceCapsIndex[j] < 28) OR (DeviceCapsIndex[j] > 38) THEN Printer.Canvas.TextOut(iPosition(250), jPosition(jBase), Format('%-8d', [value])) ELSE Printer.Canvas.TextOut(iPosition(250), jPosition(jBase), Format('%.4x', [value])); INC (jBase, jDelta); END; {Put image in lower left corner} Printer.Canvas.Draw (iPosition(300), jPosition(100), Form1.Image1.Picture.Graphic); {Place same image, 1" wide with appropriate height at location 4" over and 1" down from top left}
815
GraphicAspectRatio := Form1.Image1.Picture.Height / Form1.Image1.Picture.Width; iPixelsPerInch := GetDeviceCaps(Printer.Handle, LOGPIXELSX); jPixelsPerInch := GetDeviceCaps(Printer.Handle, LOGPIXELSY); PixelAspectRatio := jPixelsPerInch / iPixelsPerInch; TargetRectangle := Rect(4*iPixelsPerInch, {4"} jPixelsPerInch, {1"} 6*iPixelsPerInch, {6" -- 2" wide} jPixelsPerInch + TRUNC(2*iPixelsPerInch * GraphicAspectRatio * PixelAspectRatio)); Printer.Canvas.TextOut(4*iPixelsPerInch, jPixelsPerInch Printer.Canvas.TextHeight('X'), '2" wide at (4", 1")'); Printer.Canvas.StretchDraw (TargetRectangle, Form1.Image1.Picture.Graphic); {Write to offscreen bitmap and then copy to Printer Canvas} SourceRectangle := Rect (0,0, 3*iPixelsPerInch-1, 2*jPixelsPerInch-1); {This should not work! Rectangle = Left, Top, Right, Bottom Top and Bottom are reversed?} DestinationRectangle := Rect(4*iPixelsPerInch, 6*jPixelsPerInch, 7*iPixelsPerInch-1, 4*jPixelsPerinch-1); Printer.Canvas.TextOut(4*iPixelsPerInch, 4*jPixelsPerInch Printer.Canvas.TextHeight('X'), IntToStr(3*iPixelsPerInch) + ' pixels by ' + IntToStr(2*jPixelsPerInch) + ' pixels -- ' + '3"-by-2" at (4",4")'); OffScreen := TBitMap.Create; TRY OffScreen.Width := SourceRectangle.Right + 1; OffScreen.Height := SourceRectangle.Bottom + 1; WITH OffScreen.Canvas DO BEGIN Pen.Color := clBlack; Brush.Color := clWhite; Rectangle(0,0, 3*iPixelsPerInch-1, 2*jPixelsPerInch-1); Brush.Color := clRed; MoveTo (0,0); LineTo (3*iPixelsPerInch-1, 2*jPixelsPerInch-1); Brush.Color := clBlue; MoveTo (0,0); FOR i := 0 TO 3*iPixelsPerInch - 1 DO BEGIN x := 12*PI*(i / (3*iPixelsPerInch - 1)); y := jPixelsPerInch + jPixelsPerInch*SIN(x); LineTo (i, TRUNC(y)); END END; Printer.Canvas.CopyRect(DestinationRectangle, OffScreen.Canvas, SourceRectangle); FINALLY OffScreen.Free END; {List the fonts for this printer} iBase := iPosition(750); Printer.Canvas.Font.Name := 'Times New Roman';
816
Printer.Canvas.Font.Size := 12; Printer.Canvas.Font.Style := [fsBold]; Printer.Canvas.TextOut(iBase, jPosition(40), 'Fonts'); Printer.Canvas.Font.Style := []; Printer.Canvas.Font.Size := 10; jDelta := 16; FOR j := 0 TO Printer.Fonts.Count - 1 DO BEGIN Printer.Canvas.TextOut(iBase, jPosition(60 + jDelta*j), Printer.Fonts.Strings[j]) END; Printer.EndDoc; end; end.
Windows API about Printer From: David and Rhonda Crowder
Getting the Right and Bottom Margins aren't quite so straightforward. There isn't an equivalent Escape call. You obtain these values by getting the physical width (physWidth) and height (physHeight) of the page, the printable width (PrintWidth) and height (PrintHeight) of the page, and then carrying out the following sums: RightMargin := physWidth - PrintWidth - LeftMargin BottomMargin := physHeight - PrintHeight - TopMargin
The physical page size is found using Escape, this time with the GETPHYSPAGESIZE parameter. The point pntPageSize contains the page width in pntPageSize.x and page height in pntPageSize.y var pntPageSize : TPoint; begin Escape(Printer.Handle, GETPHYSPAGESIZE,o,nil,@pntPageSize); end;
Property changes in same print
817
"D. Bussey"
5. Add the following procedure to the Implementation section of NewPrint.pas: procedure TPrinter.NewPageDC(DM: PDevMode); begin CheckPrinting(True); EndPage(DC); {Check to see if new device mode setting were passed} if Assigned(DM) then ResetDC(DC,DM^); StartPage(DC); Inc(FPageNumber); Canvas.Refresh; end;
6. Instead of adding "Printers" to the USES clause of your application, add "NewPrint". EVERYTHING ELSE WORKS EXACTLY THE SAME (ie BeginDoc, EndDoc, NewPage, etc.) but you now have the capability of changing printer settings on the fly between pages WITHIN THE SAME PRINT DOCUMENT. (The example below shows how.) Instead of calling: Printer.NewPage;
call: Printer.NewPageDC(DevMode);
Here is the small example (with bytes of code from other print altering routines I've gathered). procedure TForm1.Button1Click(Sender: TObject); var ADevice, ADriver, APort: array [0..255] of char;
818
ADeviceMode: THandle; DevMode: PDevMode; begin with Printer do begin GetPrinter(ADevice,ADriver,APort,ADeviceMode); SetPrinter(ADevice,ADriver,APort,0); GetPrinter(ADevice,ADriver,APort,ADeviceMode); DevMode := GlobalLock(ADeviceMode); if not Assigned(DevMode) then ShowMessage('Can''t set printer.') else begin with DevMode^ do begin {Put any other settings you want here} dmDefaultSource := DMBIN_UPPER; {these codes are listed in "Windows.pas"} end; GlobalUnlock(ADeviceMode); SetPrinter(ADevice,ADriver,APort,ADeviceMode); end; end; Printer.BeginDoc; Printer.Canvas.TextOut(50,50,'This page is printing from the UPPER PAPER TRAY.'); with DevMode^ do begin {Put any other settings you want here} dmDefaultSource := DMBIN_LOWER; {these codes are listed in "Windows.pas"} end; Printer.NewPageDC(DevMode); Printer.Canvas.TextOut(50,50,'This page is printing from the LOWER PAPER TRAY.'); Printer.EndDoc; end; {************************************************************* Notes from the author: I've used this myself in applications for my job so I know it will work. These modifications were made in Delphi Client/Server 2.01 running on WinNT 4.0 but they should work with Delphi 2.0 Standard and Professional, and under Windows95 as well. I have not tried them with Delphi 3 yet... If anyone has any comments or questions, feel free to mail me... David A. Bussey ERC/RAL Developer River City Bank [email protected] ************************************************************}
Paper Formats 819
here is an example that lists the paper formats for the default printer: procedure TForm1.Button2Click(Sender: TObject); Type TPaperName = Array [0..63] of Char; TPaperNameArray = Array [1..High(Cardinal) div Sizeof( TPaperName )] of TPaperName; PPapernameArray = ^TPaperNameArray; Var Device, Driver, Port: Array [0..255] of Char; hDevMode: THandle; i, numPaperformats: Integer; pPaperFormats: PPapernameArray; begin Printer.PrinterIndex := -1; Printer.GetPrinter(Device, Driver, Port, hDevmode); numPaperformats := WinSpool.DeviceCapabilities( Device, Port, DC_PAPERNAMES, Nil, Nil ); If numPaperformats > 0 Then Begin GetMem( pPaperformats, numPaperformats * Sizeof( TPapername )); try WinSpool.DeviceCapabilities( Device, Port, DC_PAPERNAMES, Pchar( pPaperFormats ), Nil); memo1.clear; For i:= 1 To numPaperformats Do memo1.lines.add( pPaperformats^[i] ); finally FreeMem( pPaperformats ); end; End; End;
Please email me and tell me if you liked this page. Last modified 03/12/00 12:09:37
820
Report Smith 76. Passing a report variable to ReportSmith
Passing a report variable to ReportSmith M Richens
This caused me a lot of grief to start with but example begin MillCardNewRep.InitialValues.Clear; {Send the Current Milling Number either just finished or the last one to ReportSmith} MillCardNewRep.InitialValues.Add('@MILLING=<'+MillingNoEdit.Text+'>') MillCardNewRep.Run; end;
I declared a report variable MILLING in RS report as a number which is entered from a box BUT notice that you MUST send the value from Delphi as the .Text not as a Value. If needed convert it to str (or PChar??) but I just read it from an edit box which was readonly as I used an AutoIncrement field. Please email me and tell me if you liked this page. Last modified 03/12/00 12:09:48
821
Screen Saver 77. How to Make a Windows Screen Saver in Delphi
How to Make a Windows Screen Saver in Delphi by Mark R. Johnson From time to time, I see questions asked about how to make a Windows screen saver in Delphi that can be selected in the Control Panel Desktop. After seeing a few general responses that only partially answered the question, I decided to give it a try myself. The code you will see here is the result: a simple Windows screen saver. The complete Delphi source code for this screen saver is available for FTP as spheres.zip (4K). Before getting into the details of the code, however, I would like to thank Thomas W. Wolf for the general screen saver tips he submitted to comp.lang.pascal, which I found helpful in writing this article.
Background A Windows screen saver is basically just a standard Windows executable that has been renamed to have a .SCR filename extension. In order to interface properly with the Control Panel Desktop, however, certain requirements must be met. In general, the program must: · maintain optional settings · provide a description of itself · distinguish between active mode and configuration mode · disallow multiple copies of itself to run · exit when the user presses a key or moves the mouse In the following description, I will try to show how each of these requirements can be met using Delphi.
Getting Started The screen saver we are going to create will blank the screen and begin drawing shaded spheres at random locations on the screen, periodically erasing and starting over. The user will be able to specify the maximum number spheres to draw before erasing, as well as the size and speed with which to draw them. To begin, start a new, blank project by selecting New Project from the Delphi File menu. (Indicate "Blank project" if the Browse Gallery appears.)
Configuration Form The first thing most people see of a screen saver is its setup dialog. This is where the user specifies values for options specific to the screen saver. To create such a form, change the properties of Form1 (created automatically when the new project was begun) as follows: BorderIcons
[biSystemMenu]
822
biSystemMenu biMinimize biMaximize BorderStyle Caption Height Name Position Visible Width
True False False bsDialog Configuration 162 CfgFrm poScreenCenter False 266
We want to be able to configure the maximum number of spheres drawn on the screen, the size of the spheres, and the speed with which they are drawn. To do this, add the following three Labels (Standard palette) and SpinEdits (Samples palette): (Note: You can select the following text, copy it to the clipboard, and paste it onto the configuration form to create the components.) object Label1: TLabel Left = 16 Top = 19 Width = 58 Height = 16 Alignment = taRightJustify Caption = 'Spheres:' end object Label2: TLabel Left = 41 Top = 59 Width = 33 Height = 16 Alignment = taRightJustify Caption = 'Size:' end object Label3: TLabel Left = 29 Top = 99 Width = 45 Height = 16 Alignment = taRightJustify Caption = 'Speed:' end object spnSpheres: TSpinEdit Left = 84 Top = 15 Width = 53 Height = 26 MaxValue = 500 MinValue = 1 TabOrder = 0 Value = 50 end object spnSize: TSpinEdit Left = 84 Top = 55 Width = 53 Height = 26 MaxValue = 250 MinValue = 50 TabOrder = 1 Value = 100 end object spnSpeed: TSpinEdit Left = 84 Top = 95 Width = 53 Height = 26 MaxValue = 10
823
MinValue = 1 TabOrder = 2 Value = 10 end
Finally, we need three buttons -- OK, Cancel, and Test. The Test button is not standard for screen saver setup dialogs, but it is convenient and easy to implement. Add the following three buttons using the BitBtn buttons of the "Additional" palette: object btnOK: TBitBtn Left = 153 Top = 11 Width = 89 Height = 34 TabOrder = 3 Kind = bkOK end object btnCancel: TBitBtn Left = 153 Top = 51 Width = 89 Height = 34 TabOrder = 4 Kind = bkCancel end object btnTest: TBitBtn Left = 153 Top = 91 Width = 89 Height = 34 Caption = 'Test...' TabOrder = 5 Kind = bkIgnore end
Once we have the form layout, we need to add some code to make it work. First, we need to be able to load and save the current configuration. To do this, we should place the Spheres, Size, and Speed values into an initialization file (*.INI) in the user's Windows directory. Delphi's TIniFile object is just the thing for this. Switch to the code view for the Setup form, and add the following uses clause to the implementation section of the configuration form's unit: uses IniFiles;
Then, add the following procedure declarations to the private section of the TCfgFrm declaration: procedure LoadConfig; procedure SaveConfig;
Now add the following procedure definitions after the uses clause in the implementation section: const CfgFile = 'SPHERES.INI'; procedure TCfgFrm.LoadConfig; var inifile : TIniFile; begin inifile := TIniFile.Create(CfgFile); try with inifile do begin spnSpheres.Value := ReadInteger('Config', 'Spheres', 50); spnSize.Value := ReadInteger('Config', 'Size', 100); spnSpeed.Value := ReadInteger('Config', 'Speed', 10); end; finally inifile.Free; end;
824
end; {TCfgFrm.LoadConfig} procedure TCfgFrm.SaveConfig; var inifile : TIniFile; begin inifile := TIniFile.Create(CfgFile); try with inifile do begin WriteInteger('Config', 'Spheres', spnSpheres.Value); WriteInteger('Config', 'Size', spnSize.Value); WriteInteger('Config', 'Speed', spnSpeed.Value); end; finally inifile.Free; end; end; {TCfgFrm.SaveConfig}
All that remains for the configuration form is to respond to a few events to properly load and save the configuration. First, we need to load the configuration automatically whenever the program starts up. We can use the setup form's OnCreate event to do this. Double- click the OnCreate field in the events section of the Object Inspector and enter the following code: procedure TCfgFrm.FormCreate(Sender: TObject); begin LoadConfig; end; {TCfgFrm.FormCreate}
Next, double-click the OK button. We need to save the current configuration and close the window whenever OK is pressed, so add the following code: procedure TCfgFrm.btnOKClick(Sender: TObject); begin SaveConfig; Close; end; {TCfgFrm.btnOKClick}
In order to simply close the form (without saving) when the Cancel button is pressed, double-click on the Cancel button and add: procedure TCfgFrm.btnCancelClick(Sender: TObject); begin Close; end; {TCfgFrm.btnCancelClick}
Finally, to test the screen saver, we will need to show the screen saver form (which we haven't yet created). Go ahead and double-click on the Test button and add the following code: procedure TCfgFrm.btnTestClick(Sender: TObject); begin ScrnFrm.Show; end; {TCfgFrm.btnTestClick}
Then add "Scrn" to the uses clause in the implementation section. Scrn refers to the screen saver form unit that we will create in the next step. In the meantime, save this form unit as "Cfg" by selecting Save File As from the File menu.
Screen Saver Form The screen saver itself will simply be a large, black, captionless form that covers the entire screen, upon which the graphics are drawn. To create the second form, select New Form from the File menu and indicate a "Blank form" if prompted by the Browse Gallery. BorderIcons biSystemMenu biMinimize biMaximize BorderStyle Color FormStyle
[] False False False bsNone clBlack fsStayOnTop
825
Name Visible
ScrnFrm False
To this form, add a single component -- a timer from the System category of the Delphi component palette. Set its properties accordingly: object tmrTick: TTimer Enabled = False OnTimer = tmrTickTimer Left = 199 Top = 122 end
No other components will be required for this form. However, we will need to add some code to handle drawing the shaded spheres. Switch to the code window accompanying the ScrnFrm form. In the TScrnFrm private section, add the following procedure declaration: procedure DrawSphere(x, y, size : integer; color : TColor);
Now, in the implementation section of the unit, add the code for this procedure: procedure TScrnFrm.DrawSphere(x, y, size : integer; color : TColor); var i, dw : integer; cx, cy : integer; xy1, xy2 : integer; r, g, b : byte; begin with Canvas do begin {Fill in the pen & brush settings.} Pen.Style := psClear; Brush.Style := bsSolid; Brush.Color := color; {Prepare colors for sphere.} r := GetRValue(color); g := GetGValue(color); b := GetBValue(color); {Draw the sphere.} dw := size div 16; for i := 0 to 15 do begin xy1 := (i * dw) div 2; xy2 := size - xy1; Brush.Color := RGB(Min(r + (i * 8), 255), Min(g + (i * 8), 255), Min(b + (i * 8), 255)); Ellipse(x + xy1, y + xy1, x + xy2, y + xy2); end; end; end; {TScrnFrm.DrawSphere}
As you can see from the code, we are given the (x,y) coordinates of the top, left corner of the sphere, as well as its diameter and base color. Then, to draw the sphere, we step through brushes of increasingly bright color, starting with the given base color. With each new brush, we draw a smaller filled circle concentric with the previous ones. You will also notice, however, that the function refers to another function, Min(). This is not a standard Delphi function, so we must add it to the unit, above the declaration for DrawSphere(). function Min(a, b : integer) : integer; begin if b < a then Result := b else Result := a; end; {Min}
In order to periodically call the DrawSphere() function, we must respond to the OnTimer event of the Timer component we added to the ScrnFrm. Double-click the Timer component on the form and fill in the automatically created procedure with the following code: procedure TScrnFrm.tmrTickTimer(Sender: TObject);
826
const sphcount : integer = 0; var x, y : integer; size : integer; r, g, b : byte; color : TColor; begin if sphcount > CfgFrm.spnSpheres.Value then begin Refresh; sphcount := 0; end; Inc(sphcount); x := Random(ClientWidth); y := Random(ClientHeight); size := CfgFrm.spnSize.Value + Random(50) - 25; x := x - size div 2; y := y - size div 2; r := Random($80); g := Random($80); b := Random($80); DrawSphere(x, y, size, RGB(r, g, b)); end; {TScrnFrm.tmrTickTimer}
This procedure keeps track of the number of spheres that have been drawn in sphcount, and refreshes (erases) the screen when we have reached the maximum number. In the meantime, it calculates the random position, size, and color for the next sphere to be drawn. (Note: The color range is limited to only the first half of the brightness spectrum in order to provide greater depth to the shading.) As you may have noticed, the tmrTickTimer() procedure references the CfgFrm form to retrieve the configuration options. In order for this reference to be recognized, add the following uses clause to the implementation section of the unit: uses Cfg;
Next, we will need a way to deactivate the screen saver when a key is pressed, the mouse is moved, or the screen saver form looses focus. One way to do this is to create an handler for the Application.OnMessage event that looks for the necessary conditions to terminate the screen saver. First, add the following variable declaration to the implementation section of the unit: var crs : TPoint;
This variable will be used to store the original position of the mouse cursor for later comparison. Now, add the following declaration to the private section of TScrnFrm: procedure DeactivateScrnSaver(var Msg : TMsg; var Handled : boolean);
Add the corresponding code to the implementation section of the unit: procedure TScrnFrm.DeactivateScrnSaver(var Msg : TMsg; var Handled : boolean); var done : boolean; begin if Msg.message = WM_MOUSEMOVE then done := (Abs(LOWORD(Msg.lParam) - crs.x) > 5) or (Abs(HIWORD(Msg.lParam) - crs.y) > 5) else done := (Msg.message = WM_KEYDOWN) or (Msg.message = WM_ACTIVATE) or (Msg.message = WM_ACTIVATEAPP) or (Msg.message = WM_NCACTIVATE); if done then Close; end; {TScrnFrm.DeactivateScrnSaver}
When a WM_MOUSEMOVE window message is received, we compare the new coordinates of the mouse to the original location. If it has moved more than our threshold (5 pixels in any direction), then we close the screen saver. Otherwise, if a key is pressed or another window or dialog box takes the focus, the screen saver closes. 827
In order for this procedure to go into effect, however, we need to set the Application.OnMessage property and get the original position of the mouse cursor. A good place to do this is in the form's OnShow event handler: procedure TScrnFrm.FormShow(Sender: TObject); begin GetCursorPos(crs); tmrTick.Interval := 1000 - CfgFrm.spnSpeed.Value * 90; tmrTick.Enabled := true; Application.OnMessage := DeactivateScrnSaver; ShowCursor(false); end; {TScrnFrm.FormShow}
Here we also specify the timer's interval and activate it, as well as hiding the mouse cursor. Most of these things should be undone, however, in the form's OnHide event handler: procedure TScrnFrm.FormHide(Sender: TObject); begin Application.OnMessage := nil; tmrTick.Enabled := false; ShowCursor(true); end; {TScrnFrm.FormHide}
Finally, we need to make sure that the screen saver form fills the entire screen when it is shown. To do this add the following code to the form's OnActivate event handler: procedure TScrnFrm.FormActivate(Sender: TObject); begin WindowState := wsMaximized; end; {TScrnFrm.FormActivate}
Take this opportunity to save the ScrnFrm form unit as "SCRN.PAS" by selecting Save File from the File menu.
The Screen Saver Description You can define the text that will appear in the Control Panel Desktop list of screen savers by adding a {$D text} directive to the project source file. The $D directive inserts the given text into the module description entry of the executable file. For the Control Panel to recognize the text you must start with the term "SCRNSAVE", followed by your description. Select Project Source from the Delphi View menu so you can edit the source file. Beneath the directive "{$R *.RES}", add the following line: {$D SCRNSAVE Spheres Screen Saver}
The text "Spheres Screen Saver" will appear in the Control Panel list of available screen savers when we complete the project.
Active Versus Configuration Mode Windows launches the screen saver program under two possible conditions: 1) when the screen saver is activated, and 2) when the screen saver is to be configured. In both cases, Windows runs the same program. It distinguishes between the two modes by adding a command line parameter -- "/s" for active mode and "/c" for configuration mode. For our screen saver to function properly with the Control Panel, it must check the command line for these switches.
Active Mode When the screen saver enters active mode (/s), we need to create and show the screen saver form. We also need create the configuration form, since it contains all of the configuration options. When the screen saver form closes, the entire program should then terminate. This fits the definition of a Delphi Main Form -- a form that starts when the program starts and signals the end of the application when the form closes.
828
Configuration Mode When the screen saver enters configuration mode (/c), we need to create and show the configuration form. We should also create the screen saver form, in case the user wishes to test configuration options. However, when the configuration form closes, the entire program should then terminate. In this case, the configuration form fits the definition of a Main Form.
Defining the Main Form Ideally, we would like to identify ScrnFrm as the Main Form when a /s appears on the command line, and CfgFrm as the Main Form in all other cases. To do this requires knowledge of an undocumented feature of the TApplication VCL object: The Main Form is simply the first form created with a call to Application.CreateForm(). Thus, to define different Main Forms according to our run-time conditions, modify the project source as follows: begin if (ParamCount > 0) and (UpperCase(ParamStr(1)) = '/S') then begin {ScrnFrm needs to be the Main Form.} Application.CreateForm(TScrnFrm, ScrnFrm); Application.CreateForm(TCfgFrm, CfgFrm); end else begin {CfgFrm needs to be the Main Form.} Application.CreateForm(TCfgFrm, CfgFrm); Application.CreateForm(TScrnFrm, ScrnFrm); end; Application.Run; end.
Just by changing the order of creation, we have automatically set the Main Form for that instance. In addition, the Main Form will automatically be shown, despite the fact that we have set the Visible properties to False for both forms. As a result, we achieve the desired effect with only minimal code. (Note: for the if statement to function as shown above, the "Complete boolean eval" option should be unchecked in the Options | Project | Compiler settings. Otherwise, an error will occur if the program is invoked with no command line parameters.) In order to use the UpperCase() Delphi function, SysUtils must be included in the project file's uses clause to give something like: uses Forms, SysUtils, Scrn in 'SCRN.PAS' {ScrnFrm}, Cfg in 'CFG.PAS' {CfgFrm};
Blocking Multiple Instances One difficulty with Windows screen savers is that they must prevent multiple instances from being run. Otherwise, Windows will continue to launch a screen saver as the given time period ellapses, even when an instance is already active. To block multiple instances of our screen saver, modify the project source file to add the outer if statement shown below: begin {Only one instance is allowed at a time.} if hPrevInst = 0 then begin if (ParamCount > 0) and (UpperCase(ParamStr(1)) = '/S') then begin ... end; Application.Run; end; end;
The hPrevInst variable is a global variable defined by Delphi to point to previous instances of the current program. It will be zero if there are no previous instances still running. 829
Now save the project file as "SPHERES.DPR" and compile the program. With that, you should be able to run the screen saver on its own. Without any command line parameters, the program should default to configuration mode. By giving "/s" as the first command line parameter, you can also test the active mode. (See Run | Parameters...)
Installing the Screen Saver Once you've tested and debugged your screen saver, you are ready to install it. To do so, simply copy the executable file (SPHERES.EXE) to the Windows directory, changing its filename extension to .SCR in the process (SPHERES.SCR). Then, launch the Control Panel, double-click on Desktop, and select Screen Saver | Name. You should see "Spheres Screen Saver" in the list of possible screen savers. Select it and set it up. Copyright © 1995 Mark R. Johnson. This is a Last revised September 4, 1995.
CITY ZOO production.
Please email me and tell me if you liked this page. Last modified 03/12/00 12:10:07
830
Serial Communication 78. DELPHI 2/3 79. Port command and win95, a summary. 80. Hardware port access in DELPHI 2
DELPHI 2/3 From: "Ed Lagerburg"
M_BaudRate M_ByteSize M_Parity M_Stopbits
=1; =2; =4; =8;
{$IFNDEF COMM_UNIT} {$R Script2.Res} {$ENDIF}
//versie informatie
{$IFDEF COMM_UNIT} Function Simple_Comm_Info:PChar;StdCall; Function Simple_Comm_Open(Port:PChar;BaudRate:DWORD;ByteSize,Parity,StopBits:Byte;Mas k:Integer;WndHandle:HWND;WndCommand:UINT;Var Id:Integer):Integer;StdCall; Function Simple_Comm_Close(Id:Integer):Integer;StdCall; Function Simple_Comm_Write(Id:Integer;Buffer:PChar;Count:DWORD):Integer;StdCall; Function Simple_Comm_PortCount:DWORD;StdCall; Const M_None Const M_All
= 0; = 15;
831
Implementation {$ENDIF} Const InfoString = 'Simple_Comm.Dll (c) const MaxPorts = 5; Const bDoRun : Array[0..MaxPorts-1] =(False,False,False,False,False); Const hCommPort: Array[0..MaxPorts-1] Const hThread: Array[0..MaxPorts-1] Const dwThread: Array[0..MaxPorts-1] Const hWndHandle: Array[0..MaxPorts-1] Const hWndCommand:Array[0..MaxPorts-1] Const PortCount:Integer
by E.L. Lagerburg 1997'; of boolean of of of of of
Integer Integer Integer Hwnd UINT
=(0,0,0,0,0); =(0,0,0,0,0); =(0,0,0,0,0); =(0,0,0,0,0); =(0,0,0,0,0); = 0;
Function Simple_Comm_Info:PChar;StdCall; Begin Result:=InfoString; End; //Thread functie voor lezen compoort Function Simple_Comm_Read(Param:Pointer):Longint;StdCall; Var Count:Integer; id:Integer; ReadBuffer:Array[0..127] of byte; Begin Id:=Integer(Param); While bDoRun[id] do Begin ReadFile(hCommPort[id],ReadBuffer,1,Count,nil); if (Count > 0) then Begin if ((hWndHandle[id]<> 0) and (hWndCommand[id] >> WM_USER)) then SendMessage(hWndHandle[id],hWndCommand[id],Count,LPARAM(@ReadBuffer)); End; End; Result:=0; End; //Export functie voor sluiten compoort Function Simple_Comm_Close(Id:Integer):Integer;StdCall; Begin if (ID < 0) or (id > MaxPorts-1) or (not bDoRun[Id]) then Begin Result:=ERROR_INVALID_FUNCTION; Exit; End; bDoRun[Id]:=False; Dec(PortCount); FlushFileBuffers(hCommPort[Id]); if not PurgeComm(hCommPort[Id],PURGE_TXABORT+PURGE_RXABORT+PURGE_TXCLEAR+PURGE_RXCL EAR) then Begin Result:=GetLastError; Exit; End; if WaitForSingleObject(hThread[Id],10000) = WAIT_TIMEOUT then if not TerminateThread(hThread[Id],1) then Begin
832
Result:=GetLastError; Exit; End; CloseHandle(hThread[Id]); hWndHandle[Id]:=0; hWndCommand[Id]:=0; if not CloseHandle(hCommPort[Id]) then Begin Result:=GetLastError; Exit; End; hCommPort[Id]:=0; Result:=NO_ERROR; End; Procedure Simple_Comm_CloseAll;StdCall; Var Teller:Integer; Begin For Teller:=0 to MaxPorts-1 do Begin if bDoRun[Teller] then Simple_Comm_Close(Teller); End; End; Function GetFirstFreeId:Integer;StdCall; Var Teller:Integer; Begin For Teller:=0 to MaxPorts-1 do Begin If not bDoRun[Teller] then Begin Result:=Teller; Exit; End; End; Result:=-1; End; //Export functie voor openen compoort Function Simple_Comm_Open(Port:PChar;BaudRate:DWORD;ByteSize,Parity,StopBits:Byte;Mas k:Integer;WndHandle:HWND;WndCommand:UINT;Var Id:Integer):Integer;StdCall; Var PrevId:Integer; ctmoCommPort:TCOMMTIMEOUTS; //Lees specificaties voor de compoort dcbCommPort:TDCB; Begin if (PortCount >= MaxPorts) or (PortCount < 0) then begin result:=error_invalid_function; exit; end; result:=0; previd:=id; id:=getfirstfreeid; if id = -1 then begin id:=previd; result:=error_invalid_function; exit; end; hcommport[id]:=createfile(port,generic_read or generic_write,0,nil,open_existing,file_attribute_normal,0); if hcommport[id]= invalid_handle_value then begin
833
bdorun[id]:=false; id:=previd; result:=getlasterror; exit; end; //lees specificaties voor het comm bestand ctmocommport.readintervaltimeout:=maxdword; ctmocommport.readtotaltimeoutmultiplier:=maxdword; ctmocommport.readtotaltimeoutconstant:=maxdword; ctmocommport.writetotaltimeoutmultiplier:=0; ctmocommport.writetotaltimeoutconstant:=0; //instellen specificaties voor het comm bestand if not setcommtimeouts(hcommport[id],ctmocommport) then begin bdorun[id]:=false; closehandle(hcommport[id]); id:=previd; result:=getlasterror; exit; end; //instellen communicatie dcbcommport.dcblength:=sizeof(tdcb); if not getcommstate(hcommport[id],dcbcommport) then begin bdorun[id]:=false; closehandle(hcommport[id]); id:=previd; result:=getlasterror; exit; end; if (mask and m_baudrate <> 0) then dcbCommPort.BaudRate:=BaudRate; if (Mask and M_ByteSize <> 0) then dcbCommPort.ByteSize:=ByteSize; if (Mask and M_Parity <> 0) then dcbCommPort.Parity:=Parity; if (Mask and M_Stopbits <> 0) then dcbCommPort.StopBits:=StopBits; if not SetCommState(hCommPort[Id],dcbCommPort) then Begin bDoRun[Id]:=FALSE; CloseHandle(hCommPort[Id]); Id:=PrevId; Result:=GetLastError; Exit; End; //Thread voor lezen compoort bDoRun[Id]:=TRUE; hThread[Id]:=CreateThread(nil,0,@Simple_Comm_Read,Pointer(Id),0,dwThread[Id] ); if hThread[Id] = 0 then Begin bDoRun[Id]:=FALSE; CloseHandle(hCommPort[Id]); Id:=PrevId; Result:=GetLastError; Exit; End else Begin SetThreadPriority(hThread[Id],THREAD_PRIORITY_HIGHEST); hWndHandle[Id]:=WndHandle; hWndCommand[Id]:=WndCommand; Inc(PortCount); Result:=NO_ERROR; End; End; //Export functie voor schrijven naar compoort; Function
834
Simple_Comm_Write(Id:Integer;Buffer:PChar;Count:DWORD):Integer;StdCall; Var Written:DWORD; Begin if (Id < 0) or (id > Maxports-1) or (not bDoRun[Id]) then Begin Result:=ERROR_INVALID_FUNCTION; Exit; End; if not WriteFile(hCommPort[Id],Buffer,Count,Written,nil) then Begin Result:=GetLastError(); Exit; End; if (Count <> Written) Then Result:=ERROR_WRITE_FAULT Else Result:=NO_ERROR; End; //Aantal geopende poorten voor aanroepende applicatie Function Simple_Comm_PortCount:DWORD;StdCall; Begin Result:=PortCount; End; {$IFNDEF COMM_UNIT} Exports Simple_Comm_Info Simple_Comm_Open Simple_Comm_Close Simple_Comm_Write Simple_Comm_PortCount
Index Index Index Index index
1, 2, 3, 4, 5;
Procedure DLLMain(dwReason:DWORD); Begin If dwReason = DLL_PROCESS_DETACH then Simple_Comm_CloseAll; End; Begin DLLProc:=@DLLMain; DLLMain(DLL_PROCESS_ATTACH);//geen nut in dit geval End. {$ELSE} Initialization Finalization Simple_Comm_CloseAll; end. {$ENDIF}
From: "Lennart"
835
var ComPort: Word; implementation uses Windows, SysUtils; const CPort: array [1..4] of String =('COM1','COM2','COM3','COM4'); var Com: THandle = 0; function OpenComm(InQueue, OutQueue, Baud : LongInt): Boolean; begin if Com > 0 then CloseComm; Com := CreateFile(PChar(CPort[ComPort]), GENERIC_READ or GENERIC_WRITE, 0, nil, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, 0); Result := (Com > 0) and SetCommTiming and SetCommBuffer(InQueue,OutQueue) and SetCommStatus(Baud) ; end; function SetCommTiming: Boolean; var Timeouts: TCommTimeOuts; begin with TimeOuts do begin ReadIntervalTimeout := 1; ReadTotalTimeoutMultiplier := 0; ReadTotalTimeoutConstant := 1; WriteTotalTimeoutMultiplier := 2; WriteTotalTimeoutConstant := 2; end; Result := SetCommTimeouts(Com,Timeouts); end; function SetCommBuffer(InQueue, OutQueue: LongInt): Boolean; begin Result := SetupComm(Com, InQueue, OutQueue); end; function SetCommStatus(Baud: Integer): Boolean; var DCB: TDCB; begin with DCB do begin DCBlength:=SizeOf(Tdcb); BaudRate := Baud; Flags:=12305; wReserved:=0; XonLim:=600; XoffLim:=150; ByteSize:=8; Parity:=0; StopBits:=0; XonChar:=#17; XoffChar:=#19; ErrorChar:=#0; EofChar:=#0;
836
EvtChar:=#0; wReserved1:=65; end; Result := SetCommState(Com, DCB); end; function SendCommStr(S: String): Integer; var TempArray : array[1..255] of Byte; Count, TX_Count : Integer; begin for Count := 1 to Length(S) do TempArray[Count] := Ord(S[Count]); WriteFile(Com, TempArray, Length(S), TX_Count, nil); Result := TX_Count; end; function ReadCommStr(var S: String) : Integer; var TempArray : array[1..255] of Byte; Count, RX_Count : Integer; begin S := ''; ReadFile(Com, TempArray, 255, RX_Count, nil); for Count := 1 to RX_Count do S := S + Chr(TempArray[Count]); Result := RX_Count; end; procedure CloseComm; begin CloseHandle(Com); Com := -1; end; end.
Port command and win95, a summary. From: Martin Larsson
APPOLOGY This was supposed to be a quick summary. It ended up being quite long. Hope it's not too boring...
THE PROBLEM Under MS-DOS, an application has control of the entire machine. This gives the programmer a lot of freedom. To maximize speed, you can access the hardware directly if necessary. Under Windows 3.x, this freedom was somewhat limited. You were no longer allowed to write directly to the screen, among other things. The problem is obvious: since the user could have any number of applications running, there was no guarantee that they were not accessing the same hardware simultaneously. Another problem that showed up was that you had to be nice to the other applications running at the same time. Win 3.x is co-operatively multitasked, meaning that each
837
application determines when it's done and other applications can run. Hogging the CPU for longer periods of time was not considered nice. But the fact that no applications would run unless we as programmers said so, could be worked to our advantage when accessing the hardware. Since the application were guaranteed full control over the machine for as long as it wished, it could, when it got the CPU, muck with the I/O ports or memory, but not give up control until it was done. Unfortunately, progress caught up with us; now there's Win32 (Windows NT and Windows 95). T hese are true operating systems, with true pre-emptive multi-tasking. Each thread (the execution unit) gets a certain amount of time with the processor. When the time is up, or a thread with higher priority comes along, the system will switch to the next thread, even though the first thread is not done. This switching can occur between any two assembly instructions; there's no guarantee that a thread will be able to complete any number of instructions before it's pre-empted, and there might be a long time 'till the next timeslot. This brings up a real problem with direct hardware access. A typical I/O read, for instance, is composed of several assembly instructions: mov mov out jmp Wait: mov in
dx, AddressPort al, Address dx, al Wait dx, DataPort al, dx
While the state of all registers are preserved on a thread-switch, the state of the I/O ports are not. So, it is very possible that three applications have their way with 'your' I/O port between the 'out' and the 'in' instructions above.
THE DOCUMENTED WAY The solution to this problem is to somehow tell all other applications that "Currently MyProg is using port 546, and everybody else better stay in line." What's needed is a mutex. Unfortunately, to use a mutex, all applications have to agree on a name for that mutex. But even if that was possible, you'd easily get into some thorny problems. Consider two applications App1 and App2. Both wants to execute the above code. Unfortunately, they're created by different people with different views, so App1 asks for the AddressPortMutex first, while App2 asks for the DataPortMutex first. And, by a sad coincidence, App1 gets the AddressPortMutex, then the system swithes to App2, which aquires the DataPortMutex, and we're deadlocked. App2 can't get the address port, 'cause App1 has that. App1 can't get the data port, 'cause App2 has that. And we're still waiting... The correct way to solve this problem is to create a device driver that owns the port/memory area. Access to the hardware is supported through an API. A typical function would be GetIOPortData(AddressPort, DataPort : word) : Byte;
GetIOPortData would aquire a mutex that protects both (possibly all) ports, then access the ports, and finally releasing the mutex before returning to the caller. If different threads are calling this function at the same time, one will get there first, the others must wait. Writing a device driver is not easy. It must be done in assembler or C, and they are really hard to debug. And just to be safe, a device driver for Windows 95 (a VxD) isn't compatible with a device driver for Windows NT (a VDD, for virtual device driver). They are said to converge, and Windows NT 6.0 and Windows 2000 might have compatible device drivers, but until then, we're stuck with writing two separate pieces of code. For more info see (for instance): 838
Microsoft's Windows 95 Device Driver Kit Microsoft's Windows NT Device Driver Kit Microsoft Press' "Systems Programming for Windows 95" by Walter Oney Also, check out Vireo's VtoolsD library for writing VxD's in C. http://www.vireo.com/.
THE UNDOCUMENTED WAY The above problem isn't too real. An application that accesses the hardware directly, is usually using some specialized hardware. A machine-configuration like that tend to run one application only, who's sole purpose is to access that hardware. In such a scenario, writing a device driver seems too much trouble. After all, the reason the thing is running on Windows, is just to get the nice GUI for (almost) free, not that 10 applications can be running simultaneously. Fortunately, Windows 95 is built to be compatible with Windows 3.x. This means that direct I/O must be allowed, simply because a lot of Win 3.x programs uses it. To access the I/O ports, simply step down to assembly. The following code was supplied by Arthur Hoornweg ([email protected]): function getport(p:word):byte; stdcall; begin asm push edx push eax mov dx,p in al,dx mov @result,al pop eax pop edx end; end; Procedure Setport(p:word;b:byte);Stdcall; begin asm push edx push eax mov dx,p mov al,b out dx,al pop pop end; end;
eax edx
François Piette also has some direct I/O access functions at http://rtfm.netline.be/fpiette/portiofr.htm
BUT WHAT ABOUT NT? The above will not work on Windows NT. NT is a much more robust operating system, and allowing all and everybody access to the hardware anytime they wanted, would seriously endager the stability. In addition, NT is cross platform, and access to I/O ports might be wildly different on different processors. Even so, it is possible to access the I/O ports directly under NT on x86 processors. This is highly undocumented, and will probably disappear in future versions of the operating system.
839
I have not much information on the process, but an article by D. Roberts in the May, 1996 issue of Dr. Dobb's Journal looks promising: "Direct Port I/O and Windows NT." This seems to be the only DDJ I'm missing, so I can't verify it. See http://www.ddj.com for ordring of back-issues. Windows Developer's Journal does have an article on "Port I/O under Windows." It's written by Karen Hazzah, and appeared in the June 1996 issue. See http://www.wdj.com for ordering of back-issues.
RESOURCES (Note, I know very little about these resources, check them out yourself.) There are newsgroups dedicated to the topic of writing VxD's and VDD's: comp.os.ms-windows.programmer.nt.kernel-mode (VDD) comp.os.ms-windows.programmer.vxd (VxD) Dejanews (http://www.dejanews.com) turned up quite a few hits on 'device driver direct I/O access 95'. BlueWater Systems have developed OCX's for direct I/O, memory access and interrupt handling under all Win32 platforms. They also seem to offer custom built device drivers. See their page at http://www.bluewatersystems.com/. I know some other company has been advertising here for their ability to write custom VxD's. But I can't find that reference.
Hardware port access in DELPHI 2 function InPort(PortAddr: word): byte; {$IFDEF VER90} assembler; stdcall; asm mov dx,PortAddr in al,dx end; {$ELSE} begin Result := Port[PortAddr]; end; {$ENDIF} procedure OutPort(PortAddr: word; Databyte: byte); {$IFDEF VER90} assembler; stdcall; asm mov al,Databyte mov dx,PortAddr out dx,al end; {$ELSE} begin Port[PortAddr] := DataByte; end; {$ENDIF}
Please email me and tell me if you liked this page. Last modified 03/12/00 12:10:13
840
Windows 81. ???? Data segment too large ???? 82. Read the Run Minimized Checkbox 83. Cannot properly minimize a form on startup 84. Shared memory in a DLL with Delphi 2.0 85. Shell_NotifyIcon 86. How do I make completely invisible main forms?? 87. HELP !! STAY-ON-TOP !!! 88. Hiding Windows 95 Taskbar 89. A Catch-a-Maximize Command Question 90. How do you detect Windows version? 91. How can I change the wallpaper programmatically? 92. Path / directory name for 'My Computer' 93. Determining which font (Large or Small) is in use 94. Large/Small Fonts? 95. How can I restore a window to its last state when I run it again? 96. How: to determine name of StartUp group 97. Finding Boot Drive 98. How to make a window system modal ? 99. Sending Keystrokes/Text to a Window... 100. Windows Messages Basics 101. Buttons in Win95 task bar 102. Control Panel 103. Associate filetype [extension)
104.
Hide Start Buttonwindows23
105.
Greying Close Button on system menuwindows24
???? Data segment too large ???? From: [email protected] (Sundial Services) In article <[email protected]> Jean-Luc Nicoulin
841
I get the message 'Data segment too large'. What is the matter and what can I do to solve the problem ?
In a Windows 3.1 application, three major data-areas share one(!) 64K memory segment: t he stack, the Windows 'local heap', and the data-segment containing all globals and initialized constants. This area can become used up extremely quickly. Consequently, W3.1 applications store almost -nothing- in the stack or in a global. Rather, they store -pointers- to them. This is why, in Delphi, 'an object is a pointer.' What you have to do is to comb your application looking for large globals and to move those into a common data block which your program allocates on startup (as an object) and destroys when done. You can see a lot about what's in the data segment by setting the 'linker map' to 'detailed' and looking for the 'DATA' segment. Whatever's in that segment is going to be trying to occupy space in the lower part of that 64K-memory segment.
Read the Run Minimized Checkbox Solution 1 Paul S. Knapp wrote: > > Does anyone know how to make as Delphi app read the Run Minimized > checkbox in the Windows 3.1 or 3.11 StartUp Group. > > I cannot get my application to run minimized when this checkbox is > checked. I would like to be able to give my users the option of > starting the application in their startup group, in either maximized or > minimized mode. Every Windows application I have ever used is able to > start in the mode selected by the checkbox in the Startup Group. I > assume Delphi applications should be able to as well, but so far I a > haven't found a way. > > Thanks in advance for your advice > Paul Knapp
Hi Paul! Use WinProcs unit and after created main form add call ShowWindow. You can use HInstance, HPrevInst, CmdShow and CmdLine global variables. program Project1; uses WinProcs, {*** use WinProcs} Forms, Unit1 in 'UNIT1.PAS' {Form1}; {$R *.RES} begin Application.CreateForm(TForm1, Form1); ShowWindow(Form1.handle, CmdShow); Application.Run; end.
Solution 2 From: Ken Kyler
Rubenking, Neil (1996). _Delphi Programming Problem Solver_. Foster City, CA: IDG Books. ISBN:1-56884-795-5. unit Unit1; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs; type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); procedure FormActivate(Sender: TObject); private { Private declarations } public { Public declarations } ShowHow : word ; end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); var SUI : TStartupInfo ; begin if CmdShow = SW_SHOWDEFAULT then begin GetStartupInfo( SUI ) ; ShowHow := SUI.wShowWindow ; end else ShowHow := CmdShow ; if ShowHow = SW_SHOWMAXIMIZED then WindowState := wsMaximized ; end; procedure TForm1.FormActivate(Sender: TObject); begin case ShowHow of SW_SHOWMINIMIZED, SW_MINIMIZE, SW_SHOWMINNOACTIVE : Application.Minimize ; end ; end; end.
Solution 3 From: [email protected] (Andreas Viebke) This works with NT 4 and Delphi 2.01. It took me one and a half hours to find out: Make your project code look like this:
843
begin Application.Initialize; Application.CreateForm(TForm1, Form1); Form1.Show; Application.Minimize; Application.Run; end.
It seems to be important that neither OnCreate nor OnShow is disturbed by a procedure that changes a window's state.
Cannot properly minimize a form on startup From: [email protected] (Abel du Plessis) I need to start my form minimized, unfortunetly it doesn't work. When I set the WindowState property of the main form to wsMinimized and run it, the form minimizes onto Win95 desktop instead of the taskbar how it properly should. Does anyone know how to fix this bug?
There was an article in The Delphi Magazine, Issue 19, March 1997 - the Delphi Clinic section which explained the problem. Here is my adaptation of the fix: unit Foobar; interface type
TfrmFoobar = class(TForm); procedure DoRestore(Sender: TObject); procedure FormCreate(Sender: TObject); private { Private declarations } public { Public declarations } end;
implementation procedure TfrmUVChannel.FormCreate(Sender: TObject); begin //Assign a temporary event handler for when the app gets restored Application.OnRestore := DoRestore; Application.Minimize; end; procedure TfrmFoobar.DoRestore(Sender: TObject); begin Application.ShowMainForm := True; //Restore the application Perform(wm_SysCommand, sc_Restore, 0); //Ensure all components draw properly Show; //Disconnect this event handler so it will not be called again Application.OnRestore := nil; end; initialization //Hide the minimized main form for now
844
Application.ShowMainForm := False; end.
Shared memory in a DLL with Delphi 2.0 From: [email protected] (John Crane) Sharing Memory Mapped Files... Check out the following code: var HMapping: THandle; PMapData: Pointer; const MAPFILESIZE = 1000; procedure OpenMap; var llInit: Boolean; lInt: Integer; begin HMapping := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, MAPFILESIZE, pchar('MY MAP NAME GOES HERE')); // Check if already exists llInit := (GetLastError() <> ERROR_ALREADY_EXISTS); if (hMapping = 0) then begin ShowMessage('Can''t Create Memory Map'); Application.Terminate; exit; end; PMapData := MapViewOfFile(HMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0); if PMapData = nil then begin CloseHandle(HMapping); ShowMessage('Can''t View Memory Map'); Application.Terminate; exit; end; if (llInit) then begin // Init block to #0 if newly created memset(PMapData, #0, MAPFILESIZE); end; end; procedure CloseMap; begin if PMapData <> nil then begin UnMapViewOfFile(PMapData); end; if HMapping <> 0 then begin CloseHandle(HMapping); end; end;
Any two or more applications or DLLs may obtain pointers to the same physical block of memory this way. PMapData will point to a 1000 byte buffer in this example, this buffer being initialized to #0's the first time in. One potential problem is synchronizing access to the memory. You may accomplish this through the use of mutexes. Here's an example of that: 845
{ Call LockMap before writing (and reading?) to the memory mapped file. to call UnlockMap immediately when done updating. }
Be sure
var HMapMutex: THandle; const REQUEST_TIMEOUT = 1000; function LockMap:Boolean; begin Result := true; HMapMutex := CreateMutex(nil, false, pchar('MY MUTEX NAME GOES HERE')); if HMixMutex = 0 then begin ShowMessage('Can''t create map mutex'); Result := false; end else begin if WaitForSingleObject(HMapMutex,REQUEST_TIMEOUT) = WAIT_FAILED then begin // timeout ShowMessage('Can''t lock memory mapped file'); Result := false; end; end; end; procedure UnlockMap; begin ReleaseMutex(HMixMutex); CloseHandle(HMixMutex); end;
Please excuse my unnecessary begin..end's. I come from a Clipper background, and I prefer to see my logic blocks capped off with end's - easier to follow.
Shell_NotifyIcon From: "Neil Clayton" <[email protected]> Rainer Perl
I have a question to the Shell_NotifyIcon function: I can add an icon to the taskbar I can modify an icon I can delete an icon. What I can't do: I can't receive Messages from the Icon!!
To receive messages you must add the NIF_MESSAGE flag to your notify structure and tell it what message to send and to which window. This is the code that I use: procedure TMainForm.UpdateTaskBar; area var NotifyData: TNotifyIconData; begin With NotifyData do data structure begin cbSize Wnd uID uFlags aspects to modify ...
// update the win95 taskbar icon
// set up the := SizeOf(TNotifyIconData); := MyForm.Handle; := 0; := NIF_ICON or NIF_MESSAGE or NIF_TIP;
// ... the
846
uCallbackMessage := WM_MY_MESSAGE; message to send back to us ... hIcon := hMyIcon; szTip := 'Tool Tip To Display'; tip end; Shell_NotifyIcon(dwMessage, @NotifyData); update end;
// ... the // ... and the tool // now do the
WM_MYMESSAGE is a user defined message. Usually defined as: const WM_MYMESSAGE = WM_USER +
How do I make completely invisible main forms?? From: [email protected] (Chris Randall) "J.J. Bakker"
I have run into the same problem but found the answer. This little bit of code works great. procedure TMainForm.FormCreate(Sender: TObject); begin Application.OnMinimize:=AppMinimize; Application.OnRestore:=AppMinimize; Application.Minimize; AppMinimize(@Self); end; procedure TMainForm.AppMinimize(Sender: TObject); begin ShowWindow(Application.Handle, SW_HIDE); end;
HELP !! STAY-ON-TOP !!! From: "James D. Rofkar"
Try using the Windows API function SetWindowPos(). Something like... with MyForm do SetWindowPos(Handle,
847
HWND_TOPMOST, Left, Top, Width, Height, SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE);
You may need to call this function in your Form's OnShow(), OnDeactivate(), and OnActivate() event handlers.
Hiding Windows 95 Taskbar From: "James D. Rofkar"
I'm guessing you're referring to the Windows 95 taskbar and system tray window, and not a statusbar. The answer: Sure you can! And what a cool idea! Here's how: 1. First declare a variable of type HWND to store the Window handle of the Windows 95 taskbar. TForm1 = class(TForm) ... private hTaskBar: HWND; ... end;
2. In your main form's OnCreate() event handler, place some code that resembles: hTaskBar := FindWindow('Shell_TrayWnd', nil); ShowWindow(hTaskBar, SW_HIDE);
3. Finally, in your main form's OnDestroy() event handler, code something like: ShowWindow(hTaskBar, SW_SHOW);
"Earl F. Glynn"
848
THEN ShowWindow(WindowHandle, SW_RESTORE) END {ShowWin95TaskBar};
A Catch-a-Maximize Command Question From: "Chami" <[email protected]> > I need to have a form in my application that zooms to half of > the screen when the Maximize button is pressed, not to full > screen. >
you could handle the WM_GETMINMAXINFO message from your form. for example, add the following declaration to the protected section of your form (interface): procedure _WM_GETMINMAXINFO( var mmInfo : TWMGETMINMAXINFO ); wm_GetMinMaxInfo;
message
then define (implementation) the above message handler as follows (TForm1 being the name of your form of course): procedure TForm1._WM_GETMINMAXINFO( var mmInfo : TWMGETMINMAXINFO ); begin // set the position and the size of your form when maximized: with mmInfo.minmaxinfo^ do begin ptmaxposition.x := Screen.Width div 4; ptmaxposition.y := Screen.Height div 4;
end;
ptmaxsize.x ptmaxsize.y
:= Screen.Width div 2; := Screen.Height div 2;
end;
How do you detect Windows version? From: [email protected] Check the API help In the WINPROCS unit try the function GetVersion: LongInt; The GetVersion function retrieves the current version numbers of the Windows and MSDOS operation systems. NOTE there is a error in the orginal API documentation, the major/minor Win version are reversed! As I have used it: Windows 3.1 show up as 3.1, WIN95 shows up as 3.95 Note For windows 95 and higher use the function GetVersionEx
849
How can I change the wallpaper programmatically? Solution 1 The following code comes from Loyds Help File (it can be found on most delphi web pages). I haven't tried it but I will use it in one of my apps as soon as I get the bitmap from the client. let me know if it works for you. unit Unit1; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs; type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); procedure FormPaint(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; Bitmap: TBitmap; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin Bitmap := TBitmap.Create; Bitmap.LoadFromFile('C:\WINDOWS\cars.BMP'); end; procedure TForm1.FormPaint(Sender: TObject); var X, Y, W, H: LongInt; begin with Bitmap do begin W := Width; H := Height; end; Y := 0; while Y < Height do begin X := 0; while X < Width do begin Canvas.Draw(X, Y, Bitmap); Inc(X, W); end; Inc(Y, H); end;
850
end; end.
Solution 2 From: "Dirk Faber "
{bitmap contains filename:
var pBitmap : pchar; begin bitmap:=bitmap+#0; pBitmap:=@bitmap[1]; SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, pBitmap, SPIF_UPDATEINIFILE); end; > Also, is there a way of saving it to the INI file for next session?
1. add inifiles to the uses list. 2. create an inifile with a texteditor like this: [LastUsedBitmap] LUBitmap= c:\mybitmap.bmp
3. use a procedure like this: (supposed the inifile is like above, and is named c:\Bitmap.ini) procedure WriteToIniFile(bitmap : string); var MyIniFile : TInifile; begin MyIniFile := Tinifile.Create( 'c:\Bitmap.ini' ); MyIniFile.WriteString( 'LastUsedBitmap', 'LUBitmap', bitmap); MyIniFile.Free; end; procedure ReadFromIniFile(var bitmap: string); var MyIniFile : TInifile; begin MyIniFile := Tinifile.Create( 'c:\Bitmap.ini' ); bitmap:= MyIniFile.ReadString('LastUsedBitmap', 'LUBitmap'); MyIniFile.Free; end;
Path / directory name for 'My Computer' Christian Piene Gundersen
851
This is a rather complicated matter, so if it isn't vital to your application, I suggest that you spend your time better than digging into it. However, I'll try to point you in the right direction. The windows 32 operating system is based on a shell which uses virtual folders, like 'my computer', 'desktop' and 'recycle bin'. Some of these folders are part of the physical file system. That is they have a corresponding directory in the file system. This is the case with 'desktop' and 'recycle bin'. These directories can be used as InitialDir for the TOpenDialog, but first you have to get their physical location, which can be different on different computers. To get the physical location of these folders, you have to use some special API calls (see example below). Other folders, like 'my computer' and 'printers' are not part of the file system - they are only virtual. I've noticed that you can browse to these folders using the TOpenDialog, but I don't think they can be used as InitialDir. Virtual folders are (a bit simplified) of the type SHITEMID (item identifier). They are normally accessed using pointers to item identifiers list (PIDL). To get the PIDL of a special folder, you can use the SHGetSpecialFolder function. The physical location of the corresponding directory can then be obtained by passing the PIDL to the GetPathFromIDList function. If the folder is part of the file system, the function will return the path as a string (which can be used as InitialDir). But if you want the OpenDialog to start in a folder that is only virtual (like 'my computer'), you'll have to make it accept a PIDL as InitialDir, which I don't think it will. My guess is that the TOpenDialog uses PIDLs when browsing, but only accepts physical directories as InitialDir. Here is an example that shows how to get the 'recent documents' path and use it as InitialDir: procedure TForm1.Button1Click(Sender: TObject); var PIDL: Pointer; Path: LPSTR; const CSIDL_RECENT = $0008; begin Path := StrAlloc(MAX_PATH); SHGetSpecialFolderLocation(Handle, CSIDL_RECENT, @PIDL); if SHGetPathFromIDList(PIDL, Path) then // returns false if folder isn't part of file system begin OpenDialog1.InitialDir := Path; OpenDialog1.Execute; end; StrDispose(Path); end;
I think you'll have to write a wrapper for these API calls. They are found in shell32.dll. The best advice I can give you if you want to dig into this is to study the ShlObj.h file. I don't program in C myself, but I found it very useful. Some constants you may need: CSIDL_DESKTOP CSIDL_PROGRAMS CSIDL_CONTROLS CSIDL_PRINTERS CSIDL_PERSONAL CSIDL_STARTUP CSIDL_RECENT CSIDL_SENDTO CSIDL_BITBUCKET CSIDL_STARTMENU CSIDL_DESKTOPDIRECTORY CSIDL_DRIVES
= = = = = = = = = = = =
$0000; $0002; $0003; $0004; $0005; $0007; $0008; $0009; $000a; $000b; $0010; $0011;
// My Computer
852
CSIDL_NETWORK CSIDL_NETHOOD CSIDL_FONTS CSIDL_TEMPLATES
= = = =
$0012; $0013; $0014; $0015;
Determining which font (Large or Small) is in use "Greg Peterson"
Large/Small Fonts? Gene Eighmy
When my programs run on systems with small fonts, I often get strange output. Labels too small to hold all the text, leaving the right, or the bottom, unshown, for instance. StringGrid's which don't align as expected.
Try this. This will rescale both the form size and also reform small vs. large fonts. Call it in Form.FormCreate. Hope this helps. unit geScale; interface uses Forms, Controls; procedure geAutoScale(MForm: TForm); implementation Type TFooClass = class(TControl); { needed to get at protected } { font property } procedure geAutoScale(MForm: const cScreenWidth :integer = cScreenHeight:integer = cPixelsPerInch:integer= cFontHeight:integer =
TForm); 800; 600; 96; -11;
{Design-time value of From.Font.Height}
853
var i: integer; begin { IMPORTANT!! : Set Scaled Property of TForm to FALSE with Object Inspector. The following routine will scale the form such that it looks the same regardless of the screen size or pixels per inch. The following section determines if the screen width differs from the design-time screen size. If it differs, Scaled is set true and component positions are rescaled such that they appear in the same screen location as the design-time location. } if (Screen.width &;lt> cScreenWidth)or(Screen.PixelsPerInch <> cPixelsPerInch) then begin MForm.scaled := TRUE; MForm.height := MForm.height * screen.Height DIV cScreenHeight; MForm.width := MForm.width * screen.width DIV cScreenWidth; MForm.ScaleBy(screen.width, cScreenWidth); end; { time
This section determines if the run-time font size differs from the designtime font size. If the run-time pixelsperinch differs form the design-
pixelsperinch, the fonts must be rescaled in order for the form to appear as designed. Scaling is calculated as the ratio of the design-time font.height to run-time font.height. Font.size will not work as it may equal the designtime value yet appear physically larger crowding and overrunning other components. For instance, a form designed in 800x600 small fonts has a font.size of 8. When you run the form on in 800x600 large fonts, font.size is also 8 but the text is noticably larger than when run in small font mode. This scaling will make them both appear to be the same size. } if (Screen.PixelsPerInch <> cPixelsPerInch) then begin for i := MForm.ControlCount - 1 downto 0 do TFooClass(MForm.Controls[i]).Font.Height := (MForm.Font.Height div cFontHeight) * TFooClass(MForm.Controls[i]).Font.Height; end; end; end.
How can I restore a window to its last state when I run it again? A: Here is WindowRestorer - a window size and state restorer 854
DESCRIPTION: Ever notice how professional programs seem to remember in what condition and location you left them and their child windows? Ever notice how most RAD apps don't? You can take that ragged edge off your program with this unit. It Allows apps to save the location, size, and state of windows so that when the user reopens them, they will look as the user left them. USE: Put WINRSTOR in the uses of clause of your main form and any forms that will be saving or restoring their own state, size, or location. (If you will be doing all the saving and restoring using WinSaveChildren and WinRestoreChildren from the main form, you only need reference it in the main form's uses clause.) In MainForm.Create, initialize the global WinRestorer object as follows (it's already declared in this file, but needs to be allocated): GlobalWinRestorer := TWinRestorer.create( Application, TRUE, WHATSAVE_ALL);
Which is the same as: GlobalWinRestorer := TWinRestorer.create( Application, TRUE, [location, size, state]);
Then, in MainForm.Destroy, deallocate the global WinRestorer object as follows: GlobalWinRestorer.free;
A good place to save a form's status is in the queryclose event or else attached to a button or menu item. I usually create an item in the File Menu captioned 'Save &Workspace' which does: GlobalWinRestorer.SaveChildren(Self, [default]);
And under main form's Close event I put: GlobalWinRestorer.SaveWin(Self, [WHATSAVE_ALL]);
I have tended to restore the children's status in their own show events like this: GlobalWinRestorer.RestoreWin(Self, [default]);
though I am moving toward putting in the main form's show event: GlobalWinRestorer.RestoreWin(Self, [default]); GlobalWinRestorer.RestoreChildren(Self, [default]);
HINTS: If you set TForm.Position to poScreenCenter or anything fancy, this unit won't do what you expect. poDesigned seems to work fairly well. I could have raised an exception if you try to set top and left of a poScreenCentere'd form, but then you have to be careful using WinRestoreChildren. I opted not to check the position property and leave that up to individual developers. unit WinRstor; INTERFACE USES SysUtils, Forms; TYPE {=============================================================}
855
{-----------------------------------------------------------------Windows restorer object class and related types. -------------------------------------------------------------------} EWinRestorer = class( Exception); TWhatSave = (default, size, location, state); STWhatSave = set of TWhatSave; TWinRestorer = class(TObject) protected mIniFile: string; mIniSect: string[80]; mIsInitialized: boolean; mDefaultWhat: STWhatSave; public constructor Create( TheApp: TApplication; LocalDir: boolean; DefaultWhatSave: STWhatSave); {If localDir is true, ini dir is the app dir. Else, ini dir is the windows dir.} procedure SaveWin(TheForm: TForm; What: STWhatSave); procedure SaveChildren(TheMDIForm: TForm; What: STWhatSave); procedure RestoreWin( TheForm: TForm; What: STWhatSave); procedure RestoreChildren(TheMDIForm: TForm; What: STWhatSave); property IniFileName: string read mIniFile; end; CONST WHATSAVE_ALL = [size, location, state]; VAR GlobalWinRestorer: TWinRestorer; IMPLEMENTATION Uses IniFiles; constructor TWinRestorer.create; var fname, path: string[100]; begin inherited create; {Calculate ini file name} if default in DefaultWhatSave then raise EWinRestorer.create( 'Attempt to initialize default window position paramaters with set ' + ' containing [default] item. ' + 'Default params may contain only members of [size, location, state]. ') else mDefaultWhat := DefaultWhatSave; fname := ChangeFileExt( ExtractFileName( TheApp.exeName), '.INI'); if LocalDir then begin {parse out path and add to file name} path := ExtractFilePath(TheApp.exeName); if path[length(path)] <> '\' then path := path + '\'; fname := path + fname; end; {fill object fields} mIniFile := fname; mIniSect := 'WindowsRestorer'; {It'd be nice to write some notes to a section called [WinRestorer Notes]} end; procedure TWinRestorer.RestoreWin; var FormNm, SectionNm: string[80]; ini: TIniFile; n,l,t,w,h: integer; {Left, Top Width, Height}
856
begin ini := TIniFile.create( mIniFile); TRY SectionNm := mIniSect; FormNm := TheForm.classname; if default in What then What := mDefaultWhat; {Update Window State if Necessary} if state in What then n := ini.ReadInteger( SectionNm, FormNm + '_WindowState', 0); case n of 1: TheForm.WindowState := wsMinimized; 2: TheForm.WindowState := wsNormal; 3: TheForm.WindowState := wsMaximized; end; {Update Size and Location if necessary.} with TheForm do begin l:=left; t:=top; h:=height; w:=width; end; {Save current vals.} if size in What then begin w := ini.ReadInteger( SectionNm, FormNm + '_Width', w); h := ini.ReadInteger( SectionNm, FormNm + '_Height', h); end; if location in What then begin t := ini.ReadInteger( SectionNm, FormNm + '_Top', t); l := ini.ReadInteger( SectionNm, FormNm + '_Left', l); end; TheForm.SetBounds(l,t,w,h); FINALLY ini.free; END; end; procedure TWinRestorer.RestoreChildren; var i: integer; begin if TheMDIForm.formstyle <> fsMDIForm then raise EWinRestorer.create('Attempting to save window sizes of children for a non MDI parent window.') else for i := 0 to TheMDIForm.MDIChildCount - 1 do RestoreWin( TheMDIForm.MDIChildren[i], what); end; procedure TWinRestorer.SaveWin; var FormNm, SectionNm: string[80];
w : STWhatsave; ini: TIniFile;
begin ini := TIniFile.create( mIniFile); TRY SectionNm := mIniSect; FormNm := TheForm.ClassName; if default in What then w := mDefaultWhat else w := mDefaultWhat; if size in w then begin ini.WriteInteger( SectionNm, FormNm + '_Width', TheForm.Width); ini.WriteInteger( SectionNm, FormNm + '_Height', TheForm.Height); end; if location in w then begin ini.WriteInteger( SectionNm, FormNm + '_Top', TheForm.Top); ini.WriteInteger( SectionNm, FormNm + '_Left', TheForm.Left); end; if state in w then case TheForm.WindowState of wsMinimized: ini.WriteInteger( SectionNm, FormNm + '_WindowState', 1); wsNormal: ini.WriteInteger( SectionNm, FormNm + '_WindowState', 2);
857
wsMaximized: end; FINALLY ini.free; END; end;
ini.WriteInteger( SectionNm, FormNm + '_WindowState', 3);
procedure TWinRestorer.SaveChildren; var i: integer; begin if TheMDIForm.formstyle <> fsMDIForm then raise EWinRestorer.create('Attempting to restore window sizes of children for a non MDI parent window.') else for i := 0 to TheMDIForm.MDIChildCount - 1 do SaveWin( TheMDIForm.MDIChildren[i], what); end; INITIALIZATION END.
{ This code came from Lloyd's help file! }
How: to determine name of StartUp group From: Allan Carlton
There is an entry in the registry under: HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Start Menu
Which might give you the info you need
Finding Boot Drive From: "HIKI Takehito"
I found it in the Registry. HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup
"BootDir" value may be BootDrive.
How to make a window system modal ? From: "Eric Lawrence"
858
Sending Keystrokes/Text to a Window... From: "David Zajac"
859
procedure SendKey(H: Hwnd; Key: char); var vKey, ScanCode, wParam: Word; lParam, ConvKey: longint; Shift, Ctrl: boolean; begin ConvKey:= OemKeyScan(ord(Key)); Shift:= (ConvKey and $00020000) <> 0; Ctrl:= (ConvKey and $00040000) <> 0; ScanCode:= ConvKey and $000000FF or $FF00; vKey:= ord(Key); wParam:= vKey; lParam:= longint(ScanCode) shl 16 or 1; if Shift then SendShift(H, true); if Ctrl then SendCtrl(H, true); SendMessage(H, WM_KEYDOWN, vKey, lParam); SendMessage(H, WM_CHAR, vKey, lParam); lParam:= lParam or $C0000000; SendMessage(H, WM_KEYUP, vKey, lParam); if Shift then SendShift(H, false); if Ctrl then SendCtrl(H, false); end; function EnumFunc(Handle: HWnd; TF: TForm1): Bool; Far; begin TF.AppWind:= 0; if GetWindowWord(Handle, GWW_HINSTANCE) = TF.AppInst then TF.AppWind:= Handle; result:= (TF.AppWind = 0); end; procedure TForm1.Button1Click(Sender: TObject); var Text: Array[0..255] of char; begin AppInst:= ShellExecute(Handle, 'open', 'notepad.exe', nil, '', SW_NORMAL); EnumWindows(@EnumFunc, longint(self)); AppWind:= GetWindow(AppWind, GW_CHILD); end; procedure TForm1.Button2Click(Sender: TObject); begin SendKey(AppWind, 'T'); SendKey(AppWind, 'e'); SendKey(AppWind, 's'); SendKey(AppWind, 't'); end; procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char); begin if AppWind <> 0 then SendKey(AppWind, Key); end; end.
Windows Messages Basics Can anybody out there send me some basic stuff about Windows Messages related to Delphi. All this WM_*** stuff is getting on my nerves, since I can't understand it.
[Jim Stanley, [email protected]] 860
All the Windows messages are listed in the Windows API help in your Delphi help topics. (I'm using D1, assume the same for future versions). The WM_ (and other) messages are essential to the way Windows works. You're well aware that Delphi is primarily an *event-driven* system; all those OnKeyPress, OnThis, OnThat methods. If you have the VCL source code, you'll find in there somewhere that those event handler methods are designed to *receive* particular Windows messages (and there are some threads in here showing how you can subclass a component and "teach" it to respond to other messages as well). Windows is constantly sending out those messages in response to actions performed by the user, and it's the business of the Delphi app (and of all Windows apps) to intercept them and handle them in ways you decide. Delphi puts a wrapper over most of the message system by creating the event handlers for components described above. In addition to recieving those messages, you can also *send* them as well. There are a couple of ways to work this: check out SendMessage and PostMessage (both native Win API functions), as well as the Delphi Perform method. The first two require you to use the Handle parameter of the component you're sending the message to, while Perform is a method belonging to that component. The messages go into the standard Windows message queue and are processed like every other message. Here's a trivial example: I want (for some bizarre reason) to insert a 'y' character whenever I type a '4' in a TMemo. [Think of automatically inserting a begin-end block or a closing parenthesis.) Now I could do a lot with the Memo's Lines property, but that gets pretty complex. A much simpler way of going about it is: procedure TForm1.Memo1KeyPress(Sender: TObject; var Key: Char); begin if Key = '4' then SendMessage(Memo1.Handle, WM_CHAR, Word('y'), 0); end;
Another example is something we were doing here at Jacobs that used a lot of combo boxes. We wanted them to automatically drop down when the user pressed a key, which is (unfortunately) not standard behavior. Here's what we did: procedure TFormEffortRates.ComboBoxMaterialKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); var iShowing : integer; { other code, then... } begin { This tells you whether the combo is already dropped } iShowing := SendMessage((Sender as TComboBox).Handle, CB_GETDROPPEDSTATE, 0, 0); if iShowing = 0 then { drop the combo box } SendMessage((Sender as TComboBox).Handle, CB_SHOWDROPDOWN, 1,0); end;
Another good example is getting the line and column from a TMemo. You have to go into the API to do it. Here's a (not particularly efficient - no flames please!!) method of determining them: function TMDIChild.GetMemoColumn(const TheMemo : TMemo) : integer; begin Result := TheMemo.SelStart (SendMessage(TheMemo.Handle, EM_LINEINDEX, GetMemoLine(TheMemo), 0)); end;
861
function TMDIChild.GetMemoLine(const TheMemo : TMemo) : integer; begin Result := SendMessage(TheMemo.Handle, EM_LINEFROMCHAR, TheMemo.SelStart, 0); end;
Again, all these messages can be found in your API help. The instructions for using them are a little vague, but I'm sure everyone will be glad to help you should you need it. In short, API messages provide you with a way to fine-tune your applications to respond in exactly the way you want them to. I would consider it an advanced Delphi topic, but it sounds like one you're more than ready for.
Buttons in Win95 task bar Can anyone tell me of a way or a component or whatever else that will allow delphi 2 or 3 to place a button on the task bar much like what PowerDesk 2.0 Toolbar does.
[[email protected]] Here are the code snipits to do just that! // This needs to be in your public declarations @ the top of the pas file procedure TForm1.IconCallBackMessage( var Mess : TMessage ); message WM_USER + 100;
procedure TForm1.FormCreate(Sender: TObject); var nid : TNotifyIconData; begin with nid do begin cbSize := SizeOf( TNotifyIconData ); Wnd := Form1.Handle; uID := 1; uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP; uCallbackMessage := WM_USER + 100; hIcon := Application.Icon.Handle; szTip := 'This is the hint!'; end; Shell_NotifyIcon( NIM_ADD, @nid ); end; procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); var nid : TNotifyIconData; begin with nid do begin cbSize := SizeOf( TNotifyIconData ); Wnd := Form1.Handle; uID := 1; uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP; uCallbackMessage := WM_USER + 100; hIcon := Application.Icon.Handle; szTip := 'This is the hint!'; // All the above is probably not needed. end; Shell_NotifyIcon( NIM_DELETE, @nid ); end;
862
procedure TForm1.IconCallBackMessage( var Mess : TMessage ); var sEventLog : String; begin case Mess.lParam of // Do whatever you wish here. For example popup up a menu on a right click. WM_LBUTTONDBLCLK : sEventLog := 'Left Double Click'; WM_LBUTTONDOWN : sEventLog := 'Left Down'; WM_LBUTTONUP : sEventLog := 'Left Up'; WM_MBUTTONDBLCLK : sEventLog := 'M Dbl'; WM_MBUTTONDOWN : sEventLog := 'M D'; WM_MBUTTONUP : sEventLog := 'M U'; WM_MOUSEMOVE : sEventLog := 'movement'; WM_MOUSEWHEEL : sEventLog := 'Wheel'; WM_RBUTTONDBLCLK : sEventLog := 'r dbl'; WM_RBUTTONDOWN : sEventLog := 'r down'; WM_RBUTTONUP : sEventLog := 'r up'; end; end;
Control Panel From: "Hiki Takehito"
If you use Delphi3, add Cpl unit at dpr file. I show you a sample code. ----------library Project1; {Change "program" to "library"} uses Cpl, {use Cpl unit} Windows, Forms, Unit1 in 'Unit1.pas' {Form1}; {$R *.RES} procedure ExecuteApp; begin Application.Initialize; Application.CreateForm(TForm1,Form1); Application.Run; end; {A callback function to export at Control Panel} function CPlApplet(hwndCPl: THandle; uMsg: DWORD; lParam1, lParam2: LongInt):LongInt;stdcall; var NewCplInfo:PNewCplInfo; begin Result:=0; case uMsg of {Initialization.Return True.} CPL_INIT: Result:=1; {Number of Applet.} CPL_GETCOUNT: Result:=1; {Transporting informations of this Applet to the Control Panel.}
863
CPL_NEWINQUIRE: begin NewCplInfo:=PNewCplInfo(lParam2); with NewCplInfo^ do begin dwSize:=SizeOf(TNewCplInfo); dwFlags:=0; dwHelpContext:=0; lData:=0; {An icon to display on Control Panel.} hIcon:=LoadIcon(HInstance,'MAINICON'); {Applet name} szName:='Project1'; {Description of this Applet.} szInfo:='This is a test Applet.'; szHelpFile:=''; end; end; {Executing this Applet.} CPL_DBLCLK: ExecuteApp; else Result:=0; end; end; {Exporting the function of CplApplet} exports CPlApplet; begin end.
To use this, change the extention from "dll" to "cpl". And put into the System folder. Applet means a piece of Control Panel.Display,Fonts,Mouse,System are all Applets.
Associate filetype [extension) From: Jeremy Collins
and set the "default" string value of this key to an "internal name" for your file type - for example MyApp.Document: HKEY_CLASSES_ROOT\ .ext\ Default = "MyApp.Document"
You then create another key with this name: HKEY_CLASSES_ROOT\ MyApp.Document\
Create a sub-key of this called "shell", a sub-key of *this* called "open" and a further subkey of "open" called "command". The default value uder this key is the location and name of your your application folled by "%1" which represents the filename parameter that Windows will pass to your executable: HKEY_CLASSES_ROOT\
864
MyApp.Document\ shell\ open\ command\ Default = "C:\myapp\myapp.exe %1"
You can do this in code with the TRegistry object, or use InstallShield, which can make registry changes for you. I'd advise doing both, in case the user trashes your registry entry. From: "Rodney E Geraghty" &tt;[email protected]> The easiest way I've found to do this is to modify the Extensions section of the win.ini file that is located in the Windows directory. This also works under Win 95 and will update the registry automatically under Win95. Look at the extensions section of the win.ini to see the format you have to use. Put IniFiles in your uses clause and then use something like this: var INIFile: TIniFile; begin try INIFile := TInifile.Create('WIN.INI'); INIFile.WriteString('Extensions','txt','c:\windows\notepad.exe ^.txt'); finally INIFile.Free; end; end;
This would associate *.txt files with Windows Notepad. If you had an app named MyApp in the c:\MyApps directory and your extension was *.MAP then you would change it like this: var INIFile: TIniFile; begin try INIFile := TInifile.Create('WIN.INI'); INIFile.WriteString('Extensions','map','c:\myapps\myapp.exe ^.map'); finally INIFile.Free; end; end;
This will work in both Win 3.11 and Win 95 and saves you from having to modify the Reqistry under Win 95. Not sure about Win NT (or Win95b) since I don't have a test machine available. Note that this is only the first part of the solution though since it will open the associated application but it won't load the file you clicked. To do this you have to read ParamStr(1), which would hold the full path of the file you clicked, and run the file name through your file opening routine.
Hide Start Button From: "Carsten Paasch"
865
C : Array[0..127] of Char; S : String; Begin Tray := FindWindow('Shell_TrayWnd', NIL); Child := GetWindow(Tray, GW_CHILD); While Child <> 0 do Begin If GetClassName(Child, C, SizeOf(C)) > 0 Then Begin S := StrPAS(C); If UpperCase(S) = 'BUTTON' then begin // IsWindowVisible(Child) startbutton_handle:=child; If Visi then ShowWindow(Child, 1) else ShowWindow(Child, 0); end; End; Child := GetWindow(Child, GW_HWNDNEXT); End; End;
Greying Close Button on system menu From: "Fred Hovey"
Please email me and tell me if you liked this page. Last modified 03/12/00 12:11:23
866
Miscellaneous 106. 107. 108. 109. 110. 111.
Compile Date How do I run a program? How to write text transparently on the canvas. using Textout Different colors for the lines in the DBCtrlGrid Overriding Virtual Methods SHAREWARE NAG EXAMPLE !!
112. 113.
Auto Scaling routine... Hint's Windowzmisc17
Compile Date Martin Larsson
I'm assuming you already do something like this, but for all those who haven't realised this workaround, write a program which outputs the current date to a text file and call it something like "today.inc". A DOS program works best ( run it from your autoexec.bat takes no time at all ), or stick a windows prog in you startup group/folder. "today.inc" will have the form const _day _date _month _year
: : : :
string[10] = 'Monday'; word = 12; word = 8; word = 1996;
Then, just do a {$I c:\today.inc} at the top of all your programs. Easy, although I agree - {$DATE} would be easier!
How do I run a program? From: Yeo Keng Hua
Check out FMXUTIL.PAS in Delphi examples: function ExecuteFile(const FileName, Params, DefaultDir: string; ShowCmd: Integer): THandle; var zFileName, zParams, zDir: array[0..79] of Char; begin Result := ShellExecute(Application.MainForm.Handle, nil, StrPCopy(zFileName, FileName), StrPCopy(zParams, Params), StrPCopy(zDir, DefaultDir), ShowCmd); end;
Called with the code : executeFile('maker.exe','text_file','c:\maker', SW_SHOWNORMAL);
How to write text transparently on the canvas. using Textout From: [email protected] This is a bit of code that came on a CD-ROM with a "How To Book" I bought.. The file is called "HowUtils.Pas" Fades Text in, and or out on a Canvas. function TFadeEffect.FadeInText(Target: TCanvas; X, Y: integer; FText: String): TRect; var Pic: TBitmap; W, H: integer; PicRect, TarRect: TRect; begin Pic := TBitmap.Create; Pic.Canvas.Font := Target.Font; W := Pic.Canvas.TextWidth(FText); H := Pic.Canvas.TextHeight(FText); Pic.Width := W; Pic.Height := H; PicRect := Rect(0, 0, W, H); TarRect := Rect(X, Y, X + W, Y + H); Pic.Canvas.CopyRect(PicRect, Target, TarRect); SetBkMode(Pic.Canvas.Handle, Transparent); Pic.Canvas.TextOut(0, 0, FText); FadeInto(Target, X, Y, Pic); Pic.Free; FadeInText := TarRect; end; procedure TFadeEffect.FadeOutText(Target: TCanvas; TarRect: TRect; Orig: TBitmap); var Pic: TBitmap; PicRect: TRect; begin Pic := TBitmap.Create; Pic.Width := TarRect.Right - TarRect.Left; Pic.Height := TarRect.Bottom - TarRect.Top; PicRect := Rect(0, 0, Pic.Width, Pic.Height);
868
Pic.Canvas.CopyRect(PicRect, Orig.Canvas, TarRect); FadeInto(Target, TarRect.Left, TarRect.Top, Pic); Pic.Free; end;
Different colors for the lines in the DBCtrlGrid Does anybody know how to set different colors for the lines in the DBCtrlGrid?
[Cory Lanou, [email protected]] use the drawColumnCell event. Also be sure to defautlDrawing false procedure TMain.ProjectGridDrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); begin projectGrid.canvas.brush.color := clWindow; projectGrid.canvas.fillRect(rect); if gdSelected in state then begin projectGrid.canvas.brush.color := clHighlight; if fsBold in projectGrid.canvas.font.style then begin projectGrid.canvas.font.color := clHighlightText; projectGrid.canvas.font.style := [fsBold]; end else projectGrid.canvas.font.color := clHighlightText; end else if gdFocused in state then begin projectGrid.canvas.brush.color := clWindow; if fsBold in projectGrid.canvas.font.style then begin projectGrid.canvas.font.color := clWindowText; projectGrid.canvas.font.style := [fsBold]; end else projectGrid.canvas.font.color := clWindowText; end else if gdFixed in state then begin projectGrid.canvas.brush.color := clHighlight; if fsBold in projectGrid.canvas.font.style then begin projectGrid.canvas.font.color := clHighlightText; projectGrid.canvas.font.style := [fsBold]; end else projectGrid.canvas.font.color := clHighlightText; end; with globalDataModule.qProjects do begin // test cirteria of record. Set properties to override the default; if fieldByName('EST_COMPL_DATE').asDateTime < date then projectgrid.Canvas.font.color := clRed; if compareStr(fieldByName('STAT_CODE').asString, 'HD') = 0 then projectgrid.Canvas.font.color := clOlive; if (compareStr(fieldByName('CHANGED').asString, 'Y') = 0) and
869
(fieldByName('ASSIGN_EMP_ID').asInteger = userRecord.UserId) then projectgrid.Canvas.font.style := [fsBold]; end; projectGrid.canvas.textOut(rect.left+2, rect.top+2, column.field.text); end;
Overriding Virtual Methods Anybody know what the difference is between OVERRIDING a virtual method and REPLACING it? I'm confused on this point.
[Brian Murray, [email protected]] Say you have a class TMyObject = class (TObject)
and a subclass TOverrideObject = class (TMyObject)
Further, TMyObject has a Wiggle method: procedure Wiggle; virtual;
and TOverrideObject overrides Wiggle procedure Wiggle; override;
and you've written the implementations for both. Now, you create a TList containing a whole bunch of MyObjects and OverrideObjects in the TList.Items[n] property. The Items property is a pointer so to call your Wiggle method you have to cast Items. Now you could do this: if TObject(Items[1]) is TMyObject then TMyObject(Items[1]).Wiggle else if TObject(Items[1]) is TOverrideObject then TOverrideObject(Items[1]).Wiggle;
but the power of polymorphism (and the override directive) allows you to do this: TMyObject(Items[1]).Wiggle;
your application will look at the specific object instance pointed to by Items[1] and say "yes this is a TMyObject, but, more specifically, it is a TOverrideObject; and since the Wiggle method is a virtual method and since TOverrideObject has an overridden Wiggle method I'm going to execute the TOverrideObject.Wiggle method NOT the TMyObject.Wiggle method." Now, say you left out the override directive in the declaration of the TOverrideObject.Wiggle method and then tried TMyObject(Items[1]).Wiggle;
The application would look and see that even though Items[1] is really a TOverrideObject, it has no overridden version of the Wiggle method so the application will execute TMyObject.Wiggle NOT TOverrideObject.Wiggle (which may or may not be what you want). So, overriding a method means declaring the method with the virtual (or dynamic) directive in a base class and then declaring it with the override directive in a sub class. Replacing a method means declaring it in the subclass without the override directive. Overriden methods of a subclass can be executed even when a specific instance of the subclass is cast as its base class. Replaced methods can only be executed if the specific instance is cast as the specific class. 870
SHAREWARE NAG EXAMPLE !! From: Karsten Heitmann
The advantages here are that you leave all the functionallity at the users disposal, but the moment he closes the program, its bootie-time, folks ! All you got to do is write some obscure text and save it in the Windows global atom table.
Auto Scaling routine... From: [email protected] (Randy) This routine has made life very very easy. This routine will insure that your application will look scaled at ANY resolution. Notice the 640 reference. This is because I develop apps in 640x480. You can adjust the routine to work from what YOU develop in so you dont have to worry about the odd and big screen resolutions that your users may have. Place, in the OnCreate event of the form you want auto-scaled: AdjustResolution(Self);
{ AdjustResolution ******************************************************* { This procedure scales all the children on a given form to conform to the { current screen resolution { ************************************************************************ procedure AdjustResolution(oForm:TForm); var iPercentage:integer; begin
} } } }
871
if Screen.Width > 640 then begin iPercentage:=Round(((Screen.Width-640)/640)*100)+100; oForm.ScaleBy(iPercentage,100); end; end;
Hint's Window From: Andreas Hoerstemeier
Please email me and tell me if you liked this page. Last modified 03/12/00 12:11:27
872
Miscellaneous Part 2 114. 115. 116. 117. 118. 119. 120. 121.
Moving from VB to Delphi sscanf in delphi? Supporting Cut Copy Paste Multiple icons in a Delphi exe? Credit card verification Searching text in a textfile Cool tip for hints on status bars Calling a Procedure with it's name in a variable
Moving from VB to Delphi The Graphical Gnome
sscanf in delphi? From: [email protected] (Barry) A kind soul sent me the following unit a while ago. I have found it quite useful, but there may be a problem with the %s tag since its use has generated errors on occasion. unit Scanf; interface uses SysUtils; type EFormatError = class(ExCeption); function Sscanf(const s: string; const fmt : string; const Pointers : array of Pointer) : Integer; implementation { Sscanf parses an input string. The parameters ... s - input string to parse fmt - 'C' scanf-like format string to control parsing %d - convert a Long Integer %f - convert an Extended Float %s - convert a string (delimited by spaces)
873
other char - increment s pointer past "other char" space - does nothing Pointers - array of pointers to have values assigned result - number of variables actually assigned for example with ... Sscanf('Name. Bill Time. 7:32.77 Age. 8', '. %s . %d:%f . %d', [@Name, @hrs, @min, @age]); You get ... Name = Bill
hrs = 7
min = 32.77
age = 8
}
function Sscanf(const s: string; const fmt : string; const Pointers : array of Pointer) : Integer; var i,j,n,m : integer; s1 : string; L : LongInt; X : Extended; function GetInt : Integer; begin s1 := ''; while (s[n] = ' ') and (Length(s) > n) do inc(n); while (s[n] in ['0'..'9', '+', '-']) and (Length(s) >= n) do begin s1 := s1+s[n]; inc(n); end; Result := Length(s1); end; function GetFloat : Integer; begin s1 := ''; while (s[n] = ' ') and (Length(s) > n) do inc(n); while (s[n] in ['0'..'9', '+', '-', '.', 'e', 'E']) and (Length(s) >= n) do begin s1 := s1+s[n]; inc(n); end; Result := Length(s1); end; function GetString : Integer; begin s1 := ''; while (s[n] = ' ') and (Length(s) > n) do inc(n); while (s[n] <> ' ') and (Length(s) >= n) do begin s1 := s1+s[n]; inc(n); end; Result := Length(s1); end; function ScanStr(c : Char) : Boolean; begin while (s[n] <> c) and (Length(s) > n) do inc(n); inc(n); If (n <= Length(s)) then Result := True else Result := False; end;
874
function GetFmt : Integer; begin Result := -1; while (TRUE) do begin while (fmt[m] = ' ') and (Length(fmt) > m) do inc(m); if (m >= Length(fmt)) then break; if (fmt[m] = '%') then begin inc(m); case fmt[m] of 'd': Result := vtInteger; 'f': Result := vtExtended; 's': Result := vtString; end; inc(m); break; end; if (ScanStr(fmt[m]) = False) then break; inc(m); end; end; begin n := 1; m := 1; Result := 0; for i := 0 to High(Pointers) do begin j := GetFmt; case j of vtInteger : begin if GetInt > 0 then begin L := StrToInt(s1); Move(L, Pointers[i]^, SizeOf(LongInt)); inc(Result); end else break; end; vtExtended : begin if GetFloat > 0 then begin X := StrToFloat(s1); Move(X, Pointers[i]^, SizeOf(Extended)); inc(Result); end else break; end; vtString : begin if GetString > 0 then begin Move(s1, Pointers[i]^, Length(s1)+1); inc(Result); end else break; end; else break; end; end; end; end.
875
Supporting Cut Copy Paste From: "Shejchenko Andrij"
Multiple icons in a Delphi exe? From: [email protected] (Jani Järvinen) Does anyone know how to get Delphi to place mutliple icons into one executable? ie so that when you set up a file type and browse your Delphi compiled application you get a number of icons, not just the single one you'd get by specifying an icon under Project|Options|Application|Icon
Just create a resource file (.res) for example with Image Editor, and store your icons there. Then link in the resource with the $R compiler directive, and your app has multiple icons.
Credit card verification From: [email protected] (Brian Near) unit Creditc; {***************************************************************************** Credit Card Number Validator Unit for Delphi
876
Version: 1.1 Date: December 20, 1996 This unit is based on the public domain program ccard by Peter Miller. It is released to the public for free of charge use, but the author reserves all rights. copyright 1996 by Shawn Wilson Harvell ( [email protected] ) usage: Add this unit to the uses clause of any unit that needs access to the validation function. IsValidCreditCardNumber( CardNumber, ReturnMessage ) returns Boolean for example, use it in an if statement that Messages user if invalid. CardNumber is a string containing the number that you want to validate ReturnMessage is a string where the function can place any messages it may return ( meaning that it will overwrite whatever is in it ) returns true if valid, false otherwise. dashes and space in the input value are taken care of by the function, if other characters are possible, you may wish to remove them as well. The function RemoveChar will take care of this quite easily, simply pass the input string and the char you wish to delete. Users are free to modify this unit for their own use, but in distributing you should advise all users of the changes made. Use this unit at your own risk, it does not come with any warranties either express or implied. Damages resulting from the use of this unit are the sole responsibility of the user. This should work as is for Delphi versions 1 and 2, some slight modifications may be necessary for Turbo Pascal ( mainly due to use to conversion functions from the SysUtils unit ). If you do find this useful, have any comments or suggestions, please drop the author an email at [email protected] Revision History version 1.1 -- December 20, 1996 blooper with Discover cards, added their length mask to the "database" version 1.0 -- October 26, 1996 initial release *****************************************************************************} interface uses SysUtils; function IsValidCreditCardNumber( CardNumber: String; var MessageText: String ): Boolean; implementation const
877
CardPrefixes: array[ 1..19 ] of string = ( '2014', '2149', '300', '301', '302', '303', '304', '305', '34', '36', '37', '38', '4', '51', '52', '53', '54', '55', '6011' ); CardTypes: array[ 1..19 ] of String = ( 'enRoute', 'enRoute', 'Diner Club/Carte Blanche', 'Diner Club/Carte Blanche', 'Diner Club/Carte Blanche', 'Diner Club/Carte Blanche', 'Diner Club/Carte Blanche', 'Diner Club/Carte Blanche', 'American Express', 'Diner Club/Carte Blanche', 'American Express', 'Diner Club/Carte Blanche', 'Visa', 'MasterCard', 'MasterCard', 'MasterCard', 'MasterCard', 'MasterCard', 'Discover' ); function RemoveChar(const Input: String; DeletedChar: Char): String; var Index: Word; { counter variable } begin { all this function does is iterate through string looking for char, if found } { it deletes it } Result := Input; for Index := Length( Result ) downto 1 do if Result[ Index ] = DeletedChar then Delete( Result, Index, 1 ); end; function ShiftMask( Input: Integer ): Integer; begin { simply a wrapper for this left bit shift operation } result := ( 1 shl ( Input - 12 ) ); end; function ConfirmChecksum( CardNumber: String ): Boolean; var CheckSum: Integer; { Holds the value of the operation } Flag: Boolean; { used to indicate when ready } Counter: Integer; { index counter } PartNumber: String; { used to extract each digit of number } Number: Integer; { used to convert each digit to integer } begin {************************************************************************** This is probably the most confusing part of the code you will see, I know that it is some of the most confusing I have ever seen. Basically, this function is extracting each digit of the number and subjecting it to the
878
checksum formula established by the credit card companies. It works from the end to the front. **************************************************************************} { get the starting value for our counter } Counter := Length( CardNumber ); CheckSum := 0; PartNumber := ''; Number := 0; Flag := false; while ( Counter >= 1 ) do begin { get the current digit } PartNumber := Copy( CardNumber, Counter, 1 ); Number := StrToInt( PartNumber ); { convert to integer } if ( Flag ) then { only do every other digit } begin Number := Number * 2; if ( Number >= 10 ) then Number := Number - 9; end; CheckSum := CheckSum + Number; Flag := not( Flag ); Counter := Counter - 1; end; result := ( ( CheckSum mod 10 ) = 0 ); end; function GetMask( CardName: String begin { the default case } result := 0;
): Integer;
if ( CardName = 'MasterCard' ) then result := ShiftMask( 16 ); if ( CardName = 'Visa' ) then result := ( ShiftMask( 13 ) or ShiftMask( 16 ) );
if ( CardName = 'American Express' ) then result := ShiftMask( 15 ); if ( CardName = 'Diner Club/Carte Blanche' ) then result := ShiftMask( 14 ); if ( CardName = 'Discover' ) then result := ShiftMask( 16 );
end; function IsValidCreditCardNumber( Boolean; var StrippedNumber: String; chars } Index: Integer; } TheMask: Integer; } FoundIt: Boolean; } CardName: String; } PerformChecksum: Boolean; } begin
CardNumber: String; var MessageText: String ): { used to hold the number bereft of extra { general purpose counter for loops, etc { number we will use for the mask { used to indicate when something is found { stores the name of the type of card { the enRoute type of card doesn't get it
{ first, get rid of spaces, dashes } StrippedNumber := RemoveChar( CardNumber, ' ' ); StrippedNumber := RemoveChar( StrippedNumber, '-' );
879
{ if the string was zero length, then OK too } if ( StrippedNumber = '' ) then begin result := true; exit; end; { initialize return variables } MessageText := ''; result := true; { set our flag variable } FoundIt := false; { check for invalid characters right off the bat } for Index := 1 to Length( StrippedNumber ) do begin case StrippedNumber[ Index ] of '0'..'9': FoundIt := FoundIt; { non op in other words } else MessageText := 'Invalid Characters in Input'; result := false; exit; end; end; { now let's determine what type of card it is } for Index := 1 to 19 do begin if ( Pos( CardPrefixes[ Index ], StrippedNumber ) = 1 ) then begin { we've found the right one } FoundIt := true; CardName := CardTypes[ Index ]; TheMask := GetMask( CardName ); end; end; { if we didn't find it, indicates things are already ary } if ( not FoundIt ) then begin CardName := 'Unknown Card Type'; TheMask := 0; MessageText := 'Unknown Card Type '; result := false; exit; end; { check the length } if ( ( Length( StrippedNumber ) > 28 ) and result ) then begin MessageText := 'Number is too long '; result := false; exit; end; { check the length } if ( ( Length( StrippedNumber ) < 12 ) or ( ( shiftmask( length( strippednumber ) ) and themask ) = 0 ) ) then begin messagetext := 'number length incorrect'; result := false; exit; end;
880
{ check the checksum computation } if ( cardname = 'enroute' ) then performchecksum := false else performchecksum := true; if ( performchecksum and ( not confirmchecksum( strippednumber ) ) ) then begin messagetext := 'bad checksum'; result := false; exit; end; { if result is still true, then everything is ok } if ( result ) then messagetext := 'number ok: card type: ' + cardname; { if the string was zero length, then ok too } if ( strippednumber = '' ) then result := true; end; end.
Searching text in a textfile Anyone knows which is the best way (speed) to look for a string in a textFile. unit BMSearch; (* ------------------------------------------------------------------Boyer-Moore string searching. This is one of the fastest string search algorithms. See a description in: R. Boyer and S. Moore. A fast string searching algorithm. Communications of the ACM 20, 1977, Pags 762-772 ------------------------------------------------------------------- *) interface type {$ifdef WINDOWS} size_t = Word; {$else} size_t = LongInt; {$endif} type TTranslationTable = array[char] of char; TSearchBM = class(TObject) private FTranslate : TTranslationTable;
{ translation table }
{ translation table }
881
FJumpTable FShift_1 FPattern FPatternLen
);
: : : :
array[char] of Byte; integer; pchar; size_t;
{ Jumping table }
public procedure Prepare( Pattern: pchar; PatternLen: size_t; IgnoreCase: Boolean procedure PrepareStr( const Pattern: string; IgnoreCase: Boolean ); function function end;
Search( Text: pchar; TextLen: size_t ): pchar; Pos( const S: string ): integer;
implementation uses
SysUtils;
(* ------------------------------------------------------------------Ignore Case Table Translation ------------------------------------------------------------------- *) procedure CreateTranslationTable( var T: TTranslationTable; IgnoreCase: Boolean ); var c: char; begin for c := #0 to #255 do T[c] := c; if not IgnoreCase then exit; for c := 'a' to 'z' do T[c] := UpCase(c); { Mapping all acented characters to their uppercase equivalent } T['Á'] T['À'] T['Ä'] T['Â']
:= := := :=
'A'; 'A'; 'A'; 'A';
T['á'] T['à'] T['ä'] T['â']
:= := := :=
'A'; 'A'; 'A'; 'A';
T['É'] T['È'] T['Ë'] T['Ê']
:= := := :=
'E'; 'E'; 'E'; 'E';
T['é'] T['è'] T['ë'] T['ê']
:= := := :=
'E'; 'E'; 'E'; 'E';
T['Í'] := 'I'; T['Ì'] := 'I';
882
T['Ï'] := 'I'; T['Î'] := 'I'; T['í'] T['ì'] T['ï'] T['î']
:= := := :=
'I'; 'I'; 'I'; 'I';
T['Ó'] T['Ò'] T['Ö'] T['Ô']
:= := := :=
'O'; 'O'; 'O'; 'O';
T['ó'] T['ò'] T['ö'] T['ô']
:= := := :=
'O'; 'O'; 'O'; 'O';
T['Ú'] T['Ù'] T['Ü'] T['Û']
:= := := :=
'U'; 'U'; 'U'; 'U';
T['ú'] T['ù'] T['ü'] T['û']
:= := := :=
'U'; 'U'; 'U'; 'U';
T['ñ'] := 'Ñ'; end;
(* ------------------------------------------------------------------Preparation of the jumping table ------------------------------------------------------------------- *) procedure TSearchBM.Prepare( Pattern: pchar; PatternLen: size_t; IgnoreCase: Boolean ); var i: integer; c, lastc: char; begin FPattern := Pattern; FPatternLen := PatternLen; if FPatternLen < 1 then FPatternLen := strlen(FPattern); { This algorythm is based in a character set of 256 } if FPatternLen > 256 then exit; { 1. Preparing translating table } CreateTranslationTable( FTranslate, IgnoreCase); { 2. Preparing jumping table } for c := #0 to #255 do FJumpTable[c] := FPatternLen; for i := FPatternLen - 1 downto 0 do begin
883
c := FTranslate[FPattern[i]]; if FJumpTable[c] >= FPatternLen - 1 then FJumpTable[c] := FPatternLen - 1 - i; end; FShift_1 := FPatternLen - 1; lastc := FTranslate[Pattern[FPatternLen - 1]]; for i := FPatternLen - 2 downto 0 do if FTranslate[FPattern[i]] = lastc then begin FShift_1 := FPatternLen - 1 - i; break; end; if FShift_1 = 0 then FShift_1 := 1; end; procedure TSearchBM.PrepareStr( const Pattern: string; IgnoreCase: Boolean ); var str: pchar; begin if Pattern <> '' then begin {$ifdef Windows} str := @Pattern[1]; {$else} str := pchar(Pattern); {$endif} Prepare( str, Length(Pattern), IgnoreCase); end; end;
{ Searching Last char & scanning right to left } function TSearchBM.Search( Text: pchar; TextLen: size_t ): pchar; var shift, m1, j: integer; jumps: size_t; begin result := nil; if FPatternLen > 256 then exit; if TextLen < 1 then TextLen := strlen(Text); m1 := FPatternLen - 1; shift := 0; jumps := 0; { Searching the last character } while jumps <= TextLen do begin Inc( Text, shift); shift := FJumpTable[FTranslate[Text^]]; while shift <> 0 do begin Inc( jumps, shift); if jumps > TextLen then exit; Inc( Text, shift);
884
shift := FJumpTable[FTranslate[Text^]]; end; { Compare right to left FPatternLen - 1 characters } if jumps >= m1 then begin j := 0; while FTranslate[FPattern[m1 - j]] = FTranslate[(Text - j)^] do begin Inc(j); if j = FPatternLen then begin result := Text - m1; exit; end; end; end; shift := FShift_1; Inc( jumps, shift); end; end; function TSearchBM.Pos( const S: string ): integer; var str, p: pchar; begin result := 0; if S <> '' then begin {$ifdef Windows} str := @S[1]; {$else} str := pchar(S); {$endif} p := Search( str, Length(S)); if p <> nil then result := 1 + p - str; end; end; end.
Cool tip for hints on status bars [David Strange, [email protected]] I just figured out how to have the status bars on multiple forms display hints correctly with minimal coding. There have been a couple of solutions out there, but you had to code for each form (as far as I have seen anyway). Step 1: Place a TStatusBar on every form you want hints on. Set the SimplePanel property to True, and give them all the same name (I use SBStatus). See the comment I put in Step 4 regarding the name. Step 2: Assign all the hints as you want them. Don't forget the '|' if you want long hints. Step 3: In your startup form put this line in the FormCreate Application.OnHint := DisplayHint;
885
Step 4: Create this procedure. Please take note of the comments. procedure TFrmMain.DisplayHint(Sender: TObject); var Counter, NumComps: integer; begin with Screen.ActiveForm do begin NumComps := ControlCount - 1; for Counter := 0 to NumComps do {SBStatus is what I call all of my status bars. Change this as needed.} if (TControl(Controls[Counter]).Name = 'SBStatus') then begin if (Application.Hint = '') then {ConWorkingName is a constant that use. You can replace it with anything.} TStatusBar(Controls[Counter]).SimpleText := ConWorkingName else TStatusBar(Controls[Counter]).SimpleText := Application.Hint; break; end; end; end; {DisplayHint}
Don't forget to put 'Procedure DisplayHint(Sender: TObject) in the Public section. That's all you have to do. If you want any other forms to have hints, simply whack a TStatusBar on them and set the hints. I hope everyone likes this.
Calling a Procedure with it's name in a variable From: [email protected] (Raoul De Kezel) > > > >
Calling a Procedure with it's name in a variable How can I call a procedure whose name comes from a table, list, etc.? In other words, based on the environment I want to load a procedure name into a variable and call it. What would be the instruction?
unit ProcDict; interface type MyProc = procedure (s : String); procedure RegisterProc(procName : String; proc : MyProc); procedure ExecuteProc(procName : String; arg : String); implementation uses Classes; var ProcDict : TStringList; procedure RegisterProc(procName : String; proc : MyProc); begin ProcDict.AddObject(procName, TObject(@proc)); end; procedure ExecuteProc(procName : String; arg : String);
886
var index : Integer; begin index := ProcDict.IndexOf(ProcName); if index >= 0 then MyProc(ProcDict.objects[index])(arg); // Missing error reporting end; initialization ProcDict := TStringList.Create; ProcDict.Sorted := true; finalization ProcDict.Free; end.
Please email me and tell me if you liked this page. Last modified 03/12/00 12:11:33
887
Miscellaneous Part 3 122. 123. 124. 125. 126. 127. 128. 129. 130. 131. 132.
Avoiding using stale pointers Multi Language Applications Associated Executable MAPI and MS Exchange Constucting Object Variables Example of variable number of parameters My new TWrapGrid component: Allows word wrap in TStringGrid. Resizing panels? Background processing. Round splash screens Screensaver
Avoiding using stale pointers "David S. Becker"
Author: David S. Becker ([email protected]) Date: 1/27/97 Copyright: None Distribution Rights: Free, unlimited use, provided you forward any and all changes or suggestions you have to me. This unit was created to aid in the managment of pointers and objects. Since the compiler does not initialize pointers or objects to nil, and does not set them to nil when freed, it is possible to accidentally reference stale pointers. For this reason, I recommend you add an 'initialization' section to all units and call Nilify() on all pointers/objects in that unit. This will ensure that all pointers/objects start off as nil. Furthermore, you should use the NilFree (for objects), NilDispose (for pointers created with New), and NilFreeMem (for pointers created with GetMem) instead of their standard counterparts. These procedures are safe to call on nil pointer/ objects, as they check for nil before performing any action. After freeing
888
}
the memory allocated to the pointer/object, they reset the pointer to nil. If you are strict in your use of these procedures, your risk of accessing stale pointer is greatly reduced. (Of course, you can still get stale pointers from the VCL as it obviously doesn't use these functions.)
{==============================================================================} interface {------------------------------------------------------------------------------} { Checks a pointer against nil } { NOTE: This function differs from Assigned() in that Assigned() requires a } { variable, whereas IsNil() does not. } function IsNil(const p: Pointer): Boolean; { Sets a pointer to nil } procedure Nilify(var p); { Frees a non-nil object, then sets it to nil } procedure NilFree(o: TObject); { Frees a non-nil pointer created by New, then sets it to nil } procedure NilDispose(var p: Pointer); { Frees a non-nil pointer, then sets it to nil } procedure NilFreeMem(var p: Pointer; size: Word); {==============================================================================} implementation {------------------------------------------------------------------------------} function IsNil(const p: Pointer): Boolean; begin Result := (p = nil); end; {------------------------------------------------------------------------------} procedure Nilify(var p); begin Pointer(p) := nil; end; {------------------------------------------------------------------------------} procedure NilFree(o: TObject); begin if not IsNil(o) then begin o.Free; Nilify(o); end; end; {------------------------------------------------------------------------------} procedure NilDispose(var p: Pointer); begin if not IsNil(p) then begin Dispose(p); Nilify(p); end; end; {------------------------------------------------------------------------------} procedure NilFreeMem(var p: Pointer; size: Word); begin if not IsNil(p) then begin FreeMem(p,size); Nilify(p); end; end; end.
889
Multi Language Applications Eddie Shipman
890
procedure TForm1.FormActivate(Sender: TObject); var {inifile : TIniFile; Optional} ProgramLanguage : String; begin { Here, I just set it to French } ProgramLanguage := 'fra'; { You can optionally get the language from Win.INI:} {inifile := TInifile.Create('WIN.INI'); ProgramLanguage := inifile.ReadString('intl', 'sLanguage', 'enu'); inifile.Free;} { Forgive me if I leave out any languages, Tthese are th only ones in my setup.inf for my copy of Windows. dan nld enu eng fin fra frc deu isl ita nor ptg esp esn sve
= = = = = = = = = = = = = = =
Danish Dutch English (American) English (International) Finnish French French Canadian German Icelandic Italian Norwegian Portuguese Spanish Spanish (Modern) Swedish
} if ProgramLanguage = 'enu' then begin StringIndex := 0; end else if ProgramLanguage = 'fra' then begin StringIndex := 8; end; end; procedure TForm1.Button1Click(Sender: TObject); var i,j,k : integer; DialogForm : tform; begin Application.NormalizeTopMosts; {no Condition Selected!} DialogForm := CreateMessageDialog(LoadStr(StringIndex+2),mtWarning,[mbOK]); {Attention} DialogForm.caption := LoadStr(StringIndex + 1); DialogForm.showmodal; Application.RestoreTopMosts; {Cannot Delete the 'always' condition} DialogForm := CreateMessageDialog(LoadStr(StringIndex+4),mtWarning,[mbOK]); {Always} DialogForm.caption := LoadStr(StringIndex + 3); DialogForm.showmodal; Application.RestoreTopMosts; {Delete the condition?} DialogForm := CreateMessageDialog(LoadStr(StringIndex+6),mtInformation, [mbYes, mbNo]); {confirmation} DialogForm.caption := LoadStr(StringIndex + 5);
891
for j := 0 to DialogForm.controlCount-1 do begin if DialogForm.controls[j] is tButton then with tButton(DialogForm.controls[j]) do begin if caption = '&Yes' then caption := LoadStr(StringIndex+7); if caption = '&No' then caption := LoadStr(StringIndex+8); end; end; DialogForm.showmodal; end; end.
Associated Executable Michael Ax
892
SetLength(result,strlen(pchar(result))); end; // procedure WinShellExecute(const Operation,AssociatedFile:string); var a1:string; begin a1:=Operation; if a1='' then a1:='open'; ShellExecute( application.handle //hWnd: HWND ,pchar(a1) //Operation: PChar ,pchar(AssociatedFile) //FileName: PChar ,'' //Parameters: PChar ,'' //Directory: PChar ,SW_SHOWNORMAL //ShowCmd: Integer ); // GetLastErrorString(0); //ucdialog end; procedure WinShellPrint(const AssociatedFile:string); begin WinShellExecute('print',AssociatedFile); end; procedure WinShellOpen(const AssociatedFile:string); begin WinShellExecute('open',AssociatedFile); end; {-----------------------------------------------------------------} end.
MAPI and MS Exchange Keith Anderson
Then use the following to send your message: MapiSendMail(mapihandle, 0,MapiMessage,0, 0);
Make sure the SUBJECT, RECIP and NOTTEXT fields are complete in the MapiMessage structure or the message won't be sent. Also make sure Exchange is running using the GetWindowHandle API function, and if it's not, use ShellExecute (or whatever) to launch it first.
Constucting Object Variables From: [email protected] (Coyote) 893
In the past few days there have been more than a few questions on this group indicating a lack of understanding about object instantiation. I'm guessing that these have been beginners, but in one case the questioner was taking a class on Delphi. I'd hope that an instructor would at least *try* to explain the subject. Anyway, for all of you having pointer errors, exceptions, and GPFs, take a quick look at this. When you declare a variable of some classtype such as... var MyVar: TMyClass;
....all you've asked the compiler to do is set up enough space to hold a pointer to an instance of your class on the heap. You haven't allocated memory for that class, just allocated memory for the pointer. I'd like to say that the compiler always presets this pointer to $FFFFFFFF, but that may not be accurate. Anyway, suffice it to say that it does *not* point to a valid memory location, and does *not* contain your class' information. Delphi handles all the messiness of memory allocation and disposal for you, but you do have to do a little bit of work. When you use one of Delphi's classes, or derive one of your own, you must instantiate the object. What that means is this: you must allocate the memory for it and set the pointer to that block of memory. In some languages that would be ugly; in Delphi it's as easy as... MyVar := TMyClass.Create;
It's that easy because the Create constructor method of the class TMyClass is a class method--it operates on the class, not on the individual object. When you call the constructor, Delphi allocates memory, and returns a pointer value. Take a look: doesn't it look like a function call? Well, if you weren't sure what it was returning, now you know. The call to TMyClass.Create returns a pointer to an object of type TMyClass. In the end, all you really need to remember is this... 1. Declare an object variable of some type. 2. Instantiate that object with a call to the class constructor method. 3. Use the object as normal. 4. Free the object. ==begin useless code block procedure Example; var MyObj: TMyClass; // a class that you've created MyList: TList; // a native class begin MyObj := TMyClass.Create; // now MyObj is instantiated--it means something MyList := TList.Create; // same for MyList .... do some stuff here .... MyList.Free; // MyList's resources are cleared from the heap MyObj.Free; // same for MyObj end;
894
Example of variable number of parameters From: [email protected] (Hallvard Vassbotn) program VarPar; { A simple program to demonstrate use of type-safe variable number of parameters in Delphi.
}
Written Mars 1995 by Hallvard Vassbotn [email protected]
uses WinCrt, SysUtils; { These are predefined in System: const vtInteger = 0; vtBoolean = 1; vtChar = 2; vtExtended = 3; vtString = 4; vtPointer = 5; vtPChar = 6; vtObject = 7; vtClass = 8; type TVarRec = record case Integer of vtInteger: (VInteger: Longint; VType: Byte); vtBoolean: (VBoolean: Boolean); vtChar: (VChar: Char); vtExtended: (VExtended: PExtended); vtString: (VString: PString); vtPointer: (VPointer: Pointer); vtPChar: (VPChar: PChar); vtObject: (VObject: TObject); vtClass: (VClass: TClass); end; } const TypeNames : array [vtInteger..vtClass] of PChar = ('Integer', 'Boolean', 'Char', 'Extended', 'String', 'Pointer', 'PChar', 'Object', 'Class'); { According to the on-line docs (search for TVarRec), array of const parameters are treated like array of TVarRec by the compiler. This example will work just as well if you change the declaration of TestMultiPar to: procedure TestMultiPar(const Args: array of TVarRec); This would make the implementation of the routine cleaner (no absolute variable declaration), but the interface would be less understandable to the user of the routine. The compiler looks at the parameters and builds the array directly on the stack. For each item in the array it also sets the VType field to one
895
of the pre-defined constants vtXXXX. The actual value is always sent as four bytes of information. For the Boolean and Char types, only the first byte contains useful information. So, go ahead, now you can write all those neat routines with variable number of parameters - and still keep the type safety! } function PtrToHex(P: pointer): string; begin Result := IntToHex(Seg(P^), 4) + ':' + IntToHex(Ofs(P^), 4); end; procedure TestMultiPar(const Args: array of const); var ArgsTyped : array [0..$fff0 div sizeof(TVarRec)] of TVarRec absolute Args; i : integer; begin for i := Low(Args) to High(Args) do with ArgsTyped[i] do begin Write('Args[', i, '] : ', TypeNames[VType], ' = '); case VType of vtInteger: writeln(VInteger); vtBoolean: writeln(VBoolean); vtChar: writeln(VChar); vtExtended: writeln(VExtended^:0:4); vtString: writeln(VString^); vtPointer: writeln(PtrToHex(VPointer)); vtPChar: writeln(VPChar); vtObject: writeln(PtrToHex(Pointer(VObject))); vtClass: writeln(PtrToHex(Pointer(VClass))); end; end; end; var MyObj : TObject; begin Writeln('Test of type-safe variable number of parameters in Delphi:'); MyObj := TObject.Create; TestMultiPar([123, 45.67, PChar('ASCIIZ'), 'Hello, world!', true, 'X', @ShortDayNames, TObject, MyObj]); MyObj.Free; { To verify that the type-safety is used in the supplied formatting routines, try this: } writeln(Format('%d', ['hi'])); { The supplied parameter is not of the type expected. The '%d' format string signals that the parameter should be an integer value, but instead we send a string. At run-time this will generate a exception, and if you have enabled IDE-trapping of exceptions, Delphi will show you the offending line. Using c-type sprintf funtions like this will result in undefined behaviour (read: system crash, GP or whatever) } end.
896
My new TWrapGrid component: Allows word wrap in TStringGrid. From: [email protected] (Luis de la Rosa) I have finally created a custom component, TWrapGrid that allows you to use a TStringGrid, but also wrap the text in a cell. This is the beta version, so I encourage you to experiment with it, try it out, and send me comments on what you think of it. When you use it, remember to se the RowHeights (or DefaultRowHeight) large enough so that when it wraps, it shows up in the cell. To install, copy the following text and paste it into a Unit. Save it under the name 'Wrapgrid.PAS'. Then follow the directions I put in the header of the component. I'm also looking for feedback on this component, so please try it and tell me what you think. Here is the code! {
This is a custom component for Delphi. It is wraps text in a TStringGrid, thus the name TWrapGrid. It was created by Luis J. de la Rosa. E-mail: [email protected] Everyone is free to use it, distribute it, and enhance it. To use:
Go to the 'Options' - 'Install Components' menu selection in Delphi. Select 'Add'. Browse for this file, which will be named 'Wrapgrid.PAS'. Select 'OK'. You have now added this to the Samples part of your component palette. After that, you can use it just like a TStringGrid.
Please send any questions or comments to [email protected] Enjoy! A few additional programming notes: I have overridden the Create and DrawCell methods. Everything else should behave just like a TStringGrid. The Create sets the DefaultDrawing to False, so you don't need to. Also, I am using the pure block emulation style of programming, making my code easier to read. } unit Wrapgrid; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, Grids; type TWrapGrid = class(TStringGrid) private { Private declarations } protected { Protected declarations } { This DrawCell procedure wraps text in the grid cell } procedure DrawCell(ACol, ARow : Longint; ARect : TRect; AState : TGridDrawState); override; public
897
{ Public declarations } { The Create procedure is overriden to use the DrawCell procedure by default } constructor Create(AOwner : TComponent); override; published { Published declarations } end; procedure Register; implementation constructor TWrapGrid.Create(AOwner : TComponent); begin { Create a TStringGrid } inherited Create(AOwner); { Make the drawing use our DrawCell procedure by default } DefaultDrawing := FALSE; end; { This DrawCell procedure wraps text in the grid cell } procedure TWrapGrid.DrawCell(ACol, ARow : Longint; ARect : TRect; AState : TGridDrawState); var Sentence, { What is left in the cell to output } CurWord : String; { The word we are currently outputting } SpacePos, { The position of the first space } CurX, { The x position of the 'cursor' } CurY : Integer; { The y position of the 'cursor' } EndOfSentence : Boolean; { Whether or not we are done outputting the cell } begin { Initialize the font to be the control's font } Canvas.Font := Font; with Canvas do begin { If this is a fixed cell, then use the fixed color } if gdFixed in AState then begin Pen.Color := FixedColor; Brush.Color := FixedColor; end { else, use the normal color } else begin Pen.Color := Color; Brush.Color := Color; end; { Prepaint cell in cell color } Rectangle(ARect.Left, ARect.Top, ARect.Right, ARect.Bottom); end; { Start the drawing in the upper left corner of the cell } CurX := ARect.Left; CurY := ARect.Top; { Here we get the contents of the cell } Sentence := Cells[ACol, ARow]; { for each word in the cell } EndOfSentence := FALSE; while (not EndOfSentence) do begin { to get the next word, we search for a space } SpacePos := Pos(' ', Sentence); if SpacePos > 0 then begin { get the current word plus the space } CurWord := Copy(Sentence, 0, SpacePos);
898
{ get the rest of the sentence } Sentence := Copy(Sentence, SpacePos + 1, Length(Sentence) - SpacePos); end else begin { this is the last word in the sentence } EndOfSentence := TRUE; CurWord := Sentence; end; with Canvas do begin { if the text goes outside the boundary of the cell } if (TextWidth(CurWord) + CurX) > ARect.Right then begin { wrap to the next line } CurY := CurY + TextHeight(CurWord); CurX := ARect.Left; end; { write out the word } TextOut(CurX, CurY, CurWord); { increment the x position of the cursor } CurX := CurX + TextWidth(CurWord); end; end; end; procedure Register; begin { You can change Samples to whichever part of the Component Palette you want to install this component to } RegisterComponents('Samples', [TWrapGrid]); end; end.
Resizing panels? From: [email protected] (Dion Kurczek) Here's the source code for a resizable panel. Give the panel an align property of alClient, throw some controls on it, and watch them resize at run time when you resize the form. There is some code that prohibits resizing during design time, but this can be taken out. This may not be perfect, because I threw it together in a few minutes, but it's worked for me so far. unit Elastic; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls; type TElasticPanel = class( TPanel ) private FHorz, FVert: boolean; nOldWidth, nOldHeight: integer; bResized: boolean; protected
899
procedure WMSize( var message: TWMSize ); message WM_SIZE; public nCount: integer; constructor Create( AOwner: TComponent ); override; published property ElasticHorizontal: boolean read FHorz write FHorz default TRUE; property ElasticVertical: boolean read FVert write FVert default TRUE; end; procedure Register; implementation constructor TElasticPanel.Create( AOwner: TComponent ); begin inherited Create( AOwner ); FHorz := TRUE; FVert := TRUE; nOldWidth := Width; nOldHeight := Height; bResized := FALSE; end; procedure TElasticPanel.WMSize( var message: TWMSize ); var bResize: boolean; xRatio: real; i: integer; ctl: TWinControl; begin Inc( nCount ); if Align = alNone then bResize := TRUE else bResize := bResized; if not ( csDesigning in ComponentState ) and bResize then begin if FHorz then begin xRatio := Width / nOldWidth; for i := 0 to ControlCount - 1 do begin ctl := TWinControl( Controls[i] ); ctl.Left := Round( ctl.Left * xRatio ); ctl.Width := Round( ctl.Width * xRatio ); end; end; if FVert then begin xRatio := Height / nOldHeight; for i := 0 to ControlCount - 1 do begin ctl := TWinControl( Controls[i] ); ctl.Top := Round( ctl.Top * xRatio ); ctl.Height := Round( ctl.Height * xRatio ); end; end; end else begin nOldWidth := Width; nOldHeight := Height; end; bResized := TRUE;
900
nOldWidth := Width; nOldHeight := Height; end; procedure Register; begin RegisterComponents('Additional', [TElasticPanel]); end; end.
Background processing. From: "David S. Becker"
Here is some source code that should do what you want. I just created it now, and it is completely untested, but very similar to something I've already done, so it should work. It does make one assumption that you should be aware of. It assumes that it is started at the same time as Windows is (perhaps in the startup group), so it uses GetTickCount, which returns msec since Windows was started), to perform a task once each hour that Windows is running. This may or may not be what you had in mind. Also, the value returned by GetTickCount is really a DWORD, but is stored in a LongInt in Delphi which means that some of the larger values will wind up being negative (after about 25 days). The effect this will have on my hour checking algorythm is undetermined (I haven't really considered it). Similarly, the value will recycle once every 49.7 days which could cause the check to occur twice in less than an hour once every 49.7 days. This may or may not be a problem for you. At any rate, this should get you started. Enjoy! program Project1; uses Messages, Windows; {$R *.RES} function KeepRunning: Boolean; var Msg: TMsg; begin Result := True; while PeekMessage(Msg, 0, 0, 0, PM_REMOVE) do begin if (Msg.Message = WM_QUIT) then Result := False; DispatchMessage(Msg); end; end; function OnTheHour: Boolean; begin { This actually checks for one second (or less) } { into the hour to allow for the possibility we } { may not get a timeslice exactly on the hour } Result := (GetTickCount mod (1{hr} * 60{min} * 60{sec} * 1000{msec}) < 1000); end;
901
const filetocheck = 'c:\somedir\somefile.ext'; magicsize = 1000000; var f: file; size: longint; begin { keep ourself alive, and wait to be shut down } while keeprunning do begin { see if we're on the hour } if onthehour then begin { open file with a record size of 1 byte } { and check its size } assignfile(f,filetocheck); reset(f,1); size := filesize(f); closefile(f); { now we check our file condition if (size >= MAGICSIZE) then begin { Do something special here } end;
}
{ Now wait until we're past our 'grace' } { period so we don't accidentally fire } { off multiple times in a row } while (KeepRunning and OnTheHour) do {nothing}; end; end; end.
Round splash screens A while ago I saw some emails about round/different splashscreens. I saved this somewhere and now I can't find it.
Also Neil Rubenking author of Delphi for Dummies and other good books posted this one one compuserve. It is donut shaped with a curved title bar and you can see and click on other programs through the hole! Create a new project and save the main unit so its name is RGNU.PAS. Paste in the following: unit rgnu; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Buttons, Menus; type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); procedure Button1Click(Sender: TObject); procedure FormPaint(Sender: TObject); private { Private declarations } rTitleBar : THandle; Center : TPoint; CapY : Integer;
902
Circum : Double; SB1 : TSpeedButton; RL, RR : Double; procedure TitleBar(Act : Boolean); procedure WMNCHITTEST(var Msg: TWMNCHitTest); message WM_NCHITTEST; procedure WMNCACTIVATE(var Msg: TWMNCACTIVATE); message WM_NCACTIVATE; procedure WMSetText(var Msg: TWMSetText); message WM_SETTEXT; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} CONST TitlColors : ARRAY[Boolean] OF TColor = (clInactiveCaption, clActiveCaption); TxtColors : ARRAY[Boolean] OF TColor = (clInactiveCaptionText, clCaptionText); procedure TForm1.FormCreate(Sender: TObject); VAR rTemp, rTemp2 : THandle; Vertices : ARRAY[0..2] OF TPoint; X, Y : INteger; begin Caption := 'OOOH! Doughnuts!'; BorderStyle := bsNone; {required} IF Width > Height THEN Width := Height ELSE Height := Width; {harder to calc if width <> height} Center := Point(Width DIV 2, Height DIV 2); CapY := GetSystemMetrics(SM_CYCAPTION)+8; rTemp := CreateEllipticRgn(0, 0, Width, Height); rTemp2 := CreateEllipticRgn((Width DIV 4), (Height DIV 4), 3*(Width DIV 4), 3*(Height DIV 4)); CombineRgn(rTemp, rTemp, rTemp2, RGN_DIFF); SetWindowRgn(Handle, rTemp, True); DeleteObject(rTemp2); rTitleBar := CreateEllipticRgn(4, 4, Width-4, Height-4); rTemp := CreateEllipticRgn(CapY, CapY, Width-CapY, Height-CapY); CombineRgn(rTitleBar, rTitleBar, rTemp, RGN_DIFF); Vertices[0] := Point(0,0); Vertices[1] := Point(Width, 0); Vertices[2] := Point(Width DIV 2, Height DIV 2); rTemp := CreatePolygonRgn(Vertices, 3, ALTERNATE); CombineRgn(rTitleBar, rTitleBar, rTemp, RGN_AND); DeleteObject(rTemp); RL := ArcTan(Width / Height); RR := -RL + (22 / Center.X); X := Center.X-Round((Center.X-1-(CapY DIV 2))*Sin(RR)); Y := Center.Y-Round((Center.Y-1-(CapY DIV 2))*Cos(RR)); SB1 := TSpeedButton.Create(Self); WITH SB1 DO BEGIN Parent := Self; Left := X; Top := Y; Width := 14; Height := 14;
903
OnClick := Button1Click; Caption := 'X'; Font.Style := [fsBold]; END;
end;
procedure TForm1.Button1Click(Sender: TObject); begin Close; End; procedure TForm1.WMNCHITTEST(var Msg: TWMNCHitTest); begin Inherited; WITH Msg DO WITH ScreenToClient(Point(XPos,YPos)) DO IF PtInRegion(rTitleBar, X, Y) AND (NOT PtInRect(SB1.BoundsRect, Point(X,Y))) THEN Result := htCaption; end; procedure TForm1.WMNCActivate(var Msg: TWMncActivate); begin Inherited; TitleBar(Msg.Active); end; procedure TForm1.WMSetText(var Msg: TWMSetText); begin Inherited; TitleBar(Active); end; procedure TForm1.TitleBar(Act: Boolean); VAR TF : TLogFont; R : Double; N, X, Y : Integer; begin IF Center.X = 0 THEN Exit; WITH Canvas DO begin Brush.Style := bsSolid; Brush.Color := TitlColors[Act]; PaintRgn(Handle, rTitleBar); R := RL; Brush.Color := TitlColors[Act]; Font.Name := 'Arial'; Font.Size := 12; Font.Color := TxtColors[Act]; Font.Style := [fsBold]; GetObject(Font.Handle, SizeOf(TLogFont), @TF); FOR N := 1 TO Length(Caption) DO BEGIN X := Center.X-Round((Center.X-6)*Sin(R)); Y := Center.Y-Round((Center.Y-6)*Cos(R)); TF.lfEscapement := Round(R * 1800 / pi); Font.Handle := CreateFontIndirect(TF); TextOut(X, Y, Caption[N]); R := R - (((TextWidth(Caption[N]))+2) / Center.X); IF R < RR THEN Break; END; Font.Name := 'MS Sans Serif'; Font.Size := 8; Font.Color := clWindowText; Font.Style := [];
904
end; end; procedure TForm1.FormPaint(Sender: TObject); begin WITH Canvas DO BEGIN Pen.Color := clBlack; Brush.Style := bsClear; Pen.Width := 1; Pen.Color := clWhite; Arc(1, 1, Width-1, Height-1, Width, 0, 0, Height); Arc((Width DIV 4)-1, (Height DIV 4)-1, 3*(Width DIV 4)+1, 3*(Height DIV 4)+1, 0, Height, Width, 0); Pen.Color := clBlack; Arc(1, 1, Width-1, Height-1, 0, Height, Width, 0); Arc((Width DIV 4)-1, (Height DIV 4)-1, 3*(Width DIV 4)+1, 3*(Height DIV 4)+1, Width, 0, 0, Height); TitleBar(Active); END; end; end.
Screensaver From: [email protected] (Shuji Maeda) For ScreenSaver documentation, see Lucian Wischik's Page at... http://classic.physiol.cam.ac.uk/scr/SCRB_TEC.HTM or http://classic.physiol.cam.ac.uk/scr/SCRB_GEN.HTM For sample sources, download Meik Weber's Saver from... http://sunsite.icm.edu.pl/delphi/authors/a782.htm Hope this helps. Shuji [email protected] Please email me and tell me if you liked this page. Last modified 03/12/00 12:11:46
905
Api 133. 134. 135. 136. 137. 138. 139.
How Can I Prevent Multiple Instances of My Application? Performing an action when Windows shuts down a Delphi app Getting DOS Variables GetModuleFileName Setting time system with Delphi How do I execute a program and have my code wait until it is finished? ExtractAssIcon and paint into Timage?
140. 141.
ExitWindows Power saver modeapi8
How Can I Prevent Multiple Instances of My Application? Solution 1 From: "David S. Lee"
Here is the unit I use: unit PrevInst; interface uses WinProcs, WinTypes, SysUtils; type PHWnd = ^HWnd; function EnumApps(Wnd: HWnd; TargetWindow: PHWnd): bool; export;
906
procedure ActivatePreviousInstance; implementation function EnumApps(Wnd: HWnd; TargetWindow: PHWnd): bool; var ClassName : array[0..30] of char; begin Result := true; if GetWindowWord(Wnd, GWW_HINSTANCE) = HPrevInst then begin GetClassName(Wnd, ClassName, 30); if STRIComp(ClassName,'TApplication')=0 then begin TargetWindow^ := Wnd; Result := false; end; end; end; procedure ActivatePreviousInstance; var PrevInstWnd: HWnd; begin PrevInstWnd := 0; EnumWindows(@EnumApps,LongInt(@PrevInstWnd)); if PrevInstWnd <> 0 then if IsIconic(PrevInstWnd) then ShowWindow(PrevInstWnd,SW_Restore) else BringWindowToTop(PrevInstWnd); end; end.
Solution 2 From: "The Graphical Gnome"
907
interface uses Forms, Windows, Dialogs, SysUtils; const MI_NO_ERROR = 0; MI_FAIL_SUBCLASS = 1; MI_FAIL_CREATE_MUTEX = 2; { Query this function to determine if error occurred in startup. } { Value will be one or more of the MI_* error flags. } function GetMIError: Integer; Function InitInstance : Boolean; implementation const UniqueAppStr : PChar;
{Change for every Application}
var MessageId: Integer; WProc: TFNWndProc = Nil; MutHandle: THandle = 0; MIError: Integer = 0; function GetMIError: Integer; begin Result := MIError; end; function NewWndProc(Handle: HWND; Msg: Integer; wParam, lParam: Longint): Longint; StdCall; begin { If this is the registered message... } if Msg = MessageID then begin { if main form is minimized, normalize it } { set focus to application } if IsIconic(Application.Handle) then begin Application.MainForm.WindowState := wsNormal; ShowWindow(Application.Mainform.Handle, sw_restore); end; SetForegroundWindow(Application.MainForm.Handle); end { Otherwise, pass message on to old window proc } else Result := CallWindowProc(WProc, Handle, Msg, wParam, lParam); end; procedure SubClassApplication; begin { We subclass Application window procedure so that } { Application.OnMessage remains available for user. } WProc := TFNWndProc(SetWindowLong(Application.Handle, GWL_WNDPROC, Longint(@NewWndProc))); { Set appropriate error flag if error condition occurred } if WProc = Nil then MIError := MIError or MI_FAIL_SUBCLASS; end; procedure DoFirstInstance; begin SubClassApplication;
908
MutHandle := CreateMutex(Nil, False, UniqueAppStr); if MutHandle = 0 then MIError := MIError or MI_FAIL_CREATE_MUTEX; end; procedure BroadcastFocusMessage; { This is called when there is already an instance running. } var BSMRecipients: DWORD; begin { Don't flash main form } Application.ShowMainForm := False; { Post message and inform other instance to focus itself } BSMRecipients := BSM_APPLICATIONS; BroadCastSystemMessage(BSF_IGNORECURRENTTASK or BSF_POSTMESSAGE, @BSMRecipients, MessageID, 0, 0); end; Function InitInstance : Boolean; begin MutHandle := OpenMutex(MUTEX_ALL_ACCESS, False, UniqueAppStr); if MutHandle = 0 then begin { Mutex object has not yet been created, meaning that no previous } { instance has been created. } ShowWindow(Application.Handle, SW_ShowNormal); Application.ShowMainForm:=True; DoFirstInstance; result := True; end else begin BroadcastFocusMessage; result := False; end; end; initialization begin UniqueAppStr := Application.Exexname; MessageID := RegisterWindowMessage(UniqueAppStr); ShowWindow(Application.Handle, SW_Hide); Application.ShowMainForm:=FALSE; end; finalization begin if WProc <> Nil then { Restore old window procedure } SetWindowLong(Application.Handle, GWL_WNDPROC, LongInt(WProc)); end; end.
Solution 3 From: "Jerzy A.Radzimowski"
909
MutexHandle:=0; MutexHandle:=CREATEMUTEX( NIL,TRUE, UniqueKey); IF MutexHandle<>0 THEN BEGIN IF GetLastError=ERROR_ALREADY_EXISTS THEN BEGIN Result:=TRUE; CLOSEHANDLE(MutexHandle); MutexHandle:=0; END; END; END; begin CmdShow:=SW_HIDE; MessageId:=RegisterWindowMessage(zAppName); Application.Initialize; IF IsNextInstance THEN PostMessage(HWND_BROADCAST, MessageId,0,0) ELSE BEGIN Application.ShowMainForm:=FALSE; Application.CreateForm(TMainForm, MainForm); MainForm.StartTimer.Enabled:=TRUE; Application.Run; END; IF MutexHandle<>0 THEN CLOSEHANDLE(MutexHandle); end.
in MainForm you need add code for process private message PROCEDURE TMainForm.OnAppMessage( VAR M:TMSG; VAR Ret:BOOLEAN ); BEGIN IF M.Message=MessageId THEN BEGIN Ret:=TRUE; // BringWindowToTop !!!!!!!! END; END; INITIALIZATION ShowWindow(Application.Handle, SW_Hide); END.
Performing an action when Windows shuts down a Delphi app From: [email protected] (Wes Jones) I did a little investigation, and here is what seems to be happening: Normally, when you exit a Delphi application by using the system menu or by calling the Form's Close method, the following event handlers are called: 12. FormCloseQuery - the default action sets the variable CanClose=TRUE so form close will continue. 13. FormClose 14. FormDestroy If the application is active and you attempt to exit Windows, the event handlers are called in the following sequence: 5. FormCloseQuery 910
6. FormDestroy The FormClose method never seems to be called. Here is the flow of events when the user chooses to end the Windows session: 1. Windows sends out a WM_QUERYENDSESSION message to all application windows one by one and awaits a response 2. Each application window receives the message and returns a non-zero value if it is OK to terminate, or 0 if it is not OK to terminate. 3. If any application returns 0, the Windows session is not ended, otherwise, Windows sends a WM_ENDSESSION message to all application windows 4. Each Application Window responds with a TRUE value indicating that Windows can terminate any time after all applications have returned from processing this message. This appears to be the location of the Delphi problem: Delphi applications seem to return TRUE and the FormDestroy method is called immediately, bypassing the FormClose method. 5. Windows exits One solution is to respond to the WM_QUERYENDSESSION message in the Delphi application and prevent Windows from exiting by returning a 0 result. This can't be done in the FormCloseQuery method because there is no way to determine the source of the request (it can either be the result of the WM_QUERYENDSESSION message or the user just simply closing the application). Another solution is to respond to the WM_QUERYENDSESSION message by calling the same cleanup procedure you call in the FormClose method. Example: unit Unit1; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs; type TForm1 = class(TForm) procedure FormClose(Sender: TObject; var Action: TCloseAction); private {---------------------------------------------------------------} { Custom procedure to respond to the WM_QUERYENDSESSION message } {---------------------------------------------------------------} procedure WMQueryEndSession( var Message: TWMQueryEndSession); message WM_QUERYENDSESSION; public { Public declarations } end; var Form1 : TForm1; implementation {$R *.DFM} {---------------------------------------------------------------} { Custom procedure to respond to the WM_QUERYENDSESSION message } { The application will only receive this message in the event } { that Windows is requesing to exit. } {---------------------------------------------------------------} procedure TForm1.WMQueryEndSession(var Message: TWMQueryEndSession); begin inherited; { let the inherited message handler respond first } {--------------------------------------------------------------------} { at this point, you can either prevent windows from closing... } { Message.Result:=0; } {---------------------------or---------------------------------------} { just call the same cleanup procedure that you call in FormClose... }
911
{ MyCleanUpProcedure; } {--------------------------------------------------------------------} end; procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin MyCleanUpProcedure; end; end.
I have not tested this code, but I think it will work correctly. Let me know how it turns out!
Getting DOS Variables From: "Bob Findley"
GetModuleFileName Here is an answer for you. I have used this on many occasions and it works well. procedure TForm1.Button1Click(Sender: TObject); var szFileName : array[0..49] of char; szModuleName : array[0..19] of char; iSize : integer; begin StrPCopy(szModuleName, 'NameOfModule'); iSize := GetModuleFileName(GetModuleHandle(szModuleName),szFileName, SizeOf(szFileName)); if iSize > 0 then ShowMessage('Full path name is : ' + StrPas(szFileName)) else ShowMessage('Path of module not found'); end;
Setting time system with Delphi [email protected] (Abel du Plessis) "Vitor Martins"
with Delphi 2.0
This works for us: //****************************************************************************** //Public function SetPCSystemTime changes the system date and time. //Parameter(s): tDati The new date and time //Returns: True if successful // False if not //****************************************************************************** function SetPCSystemTime(tDati: TDateTime): Boolean;
912
var tSetDati: TDateTime; vDatiBias: Variant; tTZI: TTimeZoneInformation; tST: TSystemTime; begin GetTimeZoneInformation(tTZI); vDatiBias := tTZI.Bias / 1440; tSetDati := tDati + vDatiBias; with tST do begin wYear := StrToInt(FormatDateTime('yyyy', tSetDati)); wMonth := StrToInt(FormatDateTime('mm', tSetDati)); wDay := StrToInt(FormatDateTime('dd', tSetDati)); wHour := StrToInt(FormatDateTime('hh', tSetDati)); wMinute := StrToInt(FormatDateTime('nn', tSetDati)); wSecond := StrToInt(FormatDateTime('ss', tSetDati)); wMilliseconds := 0; end; SetPCSystemTime := SetSystemTime(tST); end;
How do I execute a program and have my code wait until it is finished? From: Noel Rice
Here is the 32 bit version: function WinExecAndWait32(FileName:String; Visibility : integer):integer; var zAppName:array[0..512] of char; zCurDir:array[0..255] of char;
913
WorkDir:String; StartupInfo:TStartupInfo; ProcessInfo:TProcessInformation; begin StrPCopy(zAppName,FileName); GetDir(0,WorkDir); StrPCopy(zCurDir,WorkDir); FillChar(StartupInfo,Sizeof(StartupInfo),#0); StartupInfo.cb := Sizeof(StartupInfo); StartupInfo.dwFlags := STARTF_USESHOWWINDOW; StartupInfo.wShowWindow := Visibility; if not CreateProcess(nil, zAppName, { pointer to command line string } nil, { pointer to process security attributes } nil, { pointer to thread security attributes } false, { handle inheritance flag } CREATE_NEW_CONSOLE or { creation flags } NORMAL_PRIORITY_CLASS, nil, { pointer to new environment block } nil, { pointer to current directory name } StartupInfo, { pointer to STARTUPINFO } ProcessInfo) then Result := -1 { pointer to PROCESS_INF } else begin WaitforSingleObject(ProcessInfo.hProcess,INFINITE); GetExitCodeProcess(ProcessInfo.hProcess,Result); end; end;
{Thanks to Pat Ritchey for these functions.} { This code came from Lloyd's help file! Ldelphi.zip }
ExtractAssIcon and paint into Timage? From: "Joe C. Hecht (Borland)"
-----------------------------------------------------------------------------
914
ExitWindows Does anyone know how to access the function ExitWindows in User.exe. I would like to use this function to restart windows without restarting the computer.
[Mike O'Hanlon, [email protected]] Here are examples of how to restart Windows and also how to reboot the system: procedure TMainForm.RestartWindowsBtnClick(Sender: TObject); begin if not ExitWindows(EW_RestartWindows, 0) then ShowMessage('An application refused to terminate'); end;
procedure TMainForm.RebootSystemBtnClick(Sender: TObject); begin if not ExitWindows(EW_RebootSystem, 0) then ShowMessage('An application refused to terminate'); end;
The ExitWindows function has always been wrongly documented - Microsoft got it wrong in their documents and every one else followed along with the incorrect info. I believe the correct definition is: function ExitWindows (dwReturnCode: Longint; Reserved: Word): Bool;
Power saver mode From: [email protected] (AlanGLLoyd) Turn monitor off : SendMessage(Application.Handle, WM_SYSCOMMAND, SC_MONITORPOWER, 0);
Turn monitor on : SendMessage(Application.Handle, WM_SYSCOMMAND, SC_MONITORPOWER, -1);
Please email me and tell me if you liked this page. Last modified 03/12/00 12:05:48
915
Components 142. 143. 144. 145. 146. 147. 148. 149. 150. 151. 152. 153. 154. 155. 156. 157.
Array of components... Array of Edit boxes? how do i getting the component index at runtime How do I create a component at run-time? Create an event during Runtime? Setting read-only columns in StringGrid BMPs in a StringGrid TBitBtn control class question (change bitmap at runtime) OwnerDraw in TStatusBar Duplicating components and their children at runtime Splitter Bar How to Add Controls to TTabbedNotebook & TNotebook Saving and Loading aTstringgrid How do I synchronize two scroll boxes on different panels? Delayed scrolling / delayed OnChange? Unselectable Tab
Array of components... From: Lode Deleu <[email protected]> > Is it possible to create an array of components? I'm using a LED component for a status display, and I'd like to be able to access it via:
First of all, you'l need to declare the array: LED : array[1..10] of TLed;
(TLed being your led component type)
if you would create the LED components dynamically, you could do this during a loop like this : for counter := 1 to 10 do begin LED[counter]:= TLED.Create; LED[counter].top := ... LED[counter].Left := ... LED[counter].Parent := Mainform; end;
{or something alike}
If the components already exist on your form (visually designed), you could simply assign them to the array like this: leds := 0; for counter := 0 to Form.Componentcount do begin if (components[counter] is TLED) then begin inc(leds); LED[leds] := TLED(components[counter]); end end;
This however leaves you with a random array of LED's, I suggest you give each LED a tag in the order they should be in the array, and then fill the array using the tag : for counter := 0 to Form.Componentcount do
916
begin if (components[counter] is TLED) then begin LED[Component[counter].tag] := TLED(components[counter]); end end;
if you need a two dimensional array, you'll need to find another trick to store the index, I've used the hint property a number of times to store additional information.
Array of Edit boxes? From: "Volker Schneider"
{Notes: To find out what additional properties to set, have a look into ObjectInspector and/or view the Form in TextMode (RightClick Form, choose TextMode)
how do i getting the component index at runtime From: [email protected] (Brad Aisa) In article <[email protected]>, [email protected] (Claudio Tereso) wrote: >i need to find the component index in the parent's order. >i tried to modify prjexp.dll but with success? >does any one have an idea?
Here is a function that does this. It gets the parent control, and then iterates through its children, looking for a match. This has been tested and works. { function to return index order of a component in its parent's component collection; returns -1 if not found or no parent } function IndexInParent(vControl: TControl): integer; var ParentControl: TWinControl; begin {we "cousin" cast to get at the protected Parent property in base class } ParentControl := TForm(vControl.Parent); if (ParentControl <> nil) then begin for Result := 0 to ParentControl.ControlCount - 1 do begin if (ParentControl.Controls[Result] = vControl) then Exit; end;
917
end; { if we make it here, then wasn't found, or didn't have parent} Result := -1; end;
How do I create a component at run-time? From: [email protected] (Mark Vaughan) Var MyButton
:
TButton;
MyButton := TButton.Create(MyForm); // MyForm now "owns" MyButton with MyButton do BEGIN Parent := MyForm; // here MyForm is also the parent of MyButton height := 32; width := 128; caption := 'Here I Am!'; left := (MyForm.ClientWidth - width) div 2; top := (MyForm.ClientHeight - height) div 2; END;
Borland also publishes one of their TechInfo sheets on this subject. Look for ti2938.asc Creating Dynamic Components at Runtime which you can get from Borland's web site or ftp site.
Create an event during Runtime? From: "Hustin Olivier"
Setting read-only columns in StringGrid From: Mark Pritchard
BMPs in a StringGrid From: "James D. Rofkar"
In your StringGrid's OnDrawCell event handler, place some code that resembles:
918
with StringGrid1.Canvas do begin {...} Draw(Rect.Left, Rect.Top, Image1.Picture.Graphic); {...} end;
Using the Draw() or StretchDraw() method of TCanvas should do the trick. BTW, Image1 above is a TImage with a bitmap already loaded into it.
TBitBtn control class question (change bitmap at runtime) "David Zajac"
// It'll be zero when the program starts
procedure TForm1.Button1Click(Sender: TObject); var Image: TBitmap; begin // Changes the bitmap in BitBtn1 Image:= TBitmap.Create; if n < ImageList1.Count then ImageList1.GetBitmap(n, Image); {end if} BitBtn1.Glyph.Assign(Image) property
// NOTE: Assign is used to change an object
inc(n,2); // Button Bitmaps hold two images! if n > ImageList1.Count then n:= 0; {end if} Image.Free; end; procedure TForm1.Button2Click(Sender: TObject); begin // adds a new button bitmap to ImageList1 if OpenDialog1.Execute then ImageList1.FileLoad(rtBitMap,OpenDialog1.FileName,clBtnFace); label1.Caption:= 'ImageCount = ' + IntToStr(ImageList1.Count); end;
OwnerDraw in TStatusBar From: Chris Jobson
Just write an OnDrawPanel handler for the StatusBar something like procedure TForm1.StatusBar1DrawPanel(StatusBar: TStatusBar; Panel: TStatusPanel; const Rect: TRect); begin with statusbar1.Canvas do begin Brush.Color := clRed; FillRect(Rect); TextOut(Rect.Left, Rect.Top, 'Panel '+IntToStr(Panel.Index)); end; end;
Duplicating components and their children at runtime Gary McGhee
TUniqueReader = Class(TReader) LastRead: TComponent; procedure ComponentRead(Component: TComponent); procedure SetNameUnique( Reader: TReader; Component: TComponent; var Name: string ); end;
implementation procedure TUniqueReader.ComponentRead( Component: TComponent ); begin LastRead := Component; end; procedure TUniqueReader.SetNameUnique( // sets the name of the read component to something like "Panel2" if "Panel1" already exists Reader: TReader; Component: TComponent; // component being read
920
var Name: string
// Name to use and modify
); var i: Integer; tempname: string; begin
end;
i := 0; tempname := Name; while Component.Owner.FindComponent(Name) <> nil do begin Inc(i); Name := Format('%s%d', [tempname, i]); end;
function DuplicateComponents( AComponent: TComponent // original component ): TComponent; // returns created new component procedure RegisterComponentClasses( AComponent: TComponent ); var i : integer; begin RegisterClass(TPersistentClass(AComponent.ClassType)); if AComponent is TWinControl then if TWinControl(AComponent).ControlCount > 0 then for i := 0 to (TWinControl(AComponent).ControlCount-1) do RegisterComponentClasses(TWinControl(AComponent).Controls[i]); end; var
begin
Stream: TMemoryStream; UniqueReader: TUniqueReader; Writer: TWriter; result := nil; UniqueReader := nil; Writer := nil; try Stream := TMemoryStream.Create; RegisterComponentClasses(AComponent); try
finally end;
Writer := TWriter.Create(Stream, 4096); Writer.Root := AComponent.Owner; Writer.WriteSignature; Writer.WriteComponent(AComponent); Writer.WriteListEnd; Writer.Free;
Stream.Position := 0; try UniqueReader := TUniqueReader.Create(Stream, 4096); // create reader
// should probably move these routines into
theconstructor UniqueReader.OnSetName := UniqueReader.SetNameUnique; UniqueReader.LastRead := nil; if AComponent is TWinControl then
921
UniqueReader.ReadComponents( // read in components and sub-components TWinControl(AComponent).Owner, TWinControl(AComponent).Parent, UniqueReader.ComponentRead else
)
UniqueReader.ReadComponents( // read in components AComponent.Owner, nil, UniqueReader.ComponentRead ); result := UniqueReader.LastRead; finally UniqueReader.Free; finally
end; Stream.Free;
end;
end;
Splitter Bar From: [email protected] (Adam Redgewell) Bart Mertens
Assuming your treeview is meant to be on the left and the memo on the right, you need to do the following: · Set the Align property for the TreeView to alLeft. · Cut (Ctrl-X) the memo component from your form. · Add a Panel component and set its Align property to alClient. · Click inside the panel and add another Panel component. · Set its width to about 8, and its Align property to alLeft. · Paste your memo component back into Panel1 and set its Align property to alClient. Panel2 is the divider strip: you now need to add the procedures shown below. Your code will look something like the following: type TForm1 = class(TForm) TreeView1: TTreeview; Panel1: TPanel; Panel2: TPanel; Memo1: TMemo; procedure Panel1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure Panel1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure Panel1MouseMove(Sender: TObject; Shift:TShiftState; X, Y: Integer); private
922
Resizing: Boolean; public ... end; procedure TForm1.Panel2MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin Resizing:=true; end; procedure TForm1.Panel2MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin Resizing:=false; end; procedure TForm1.Panel2MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); begin if Resizing then begin TreeView1.Width:=TreeView1.Width+X; // Prevent occasional strange repaint errors on resize: Panel1.Invalidate; end; end;
This code can be adapted to split the screen horizontally - you get the idea...
How to Add Controls to TTabbedNotebook & TNotebook From: [email protected] (Mark R. Johnson) I have seen the question "how do you add controls to TTabbedNotebook or TNotebook at run-time?" several times here and elsewhere. Well, after finally getting a few spare minutes to check into it, I have stumbled across the solution: TTabbedNotebook Adding controls to a TTabbedNotebook during design time is a pretty simple task. All you need to do is set the PageIndex or ActivePage property to the page you want to add controls to, and begin dropping the controls onto the TTabbedNotebook. Adding controls to a TTabbedNotebook during run-time is also very simple. However, there is no mention what-so-ever in the Delphi documentation on how to do this. To make matters worse, the TTabbedNotebook source code is not included when you purchase the Delphi VCL source. Thus, we are left with a mystery. Fortunately, I have stumbled across the solution. The first step to solving this mystery was to take a look at \DELPHI\DOC\TABNOTBK.INT, the interface section of the TABNOTBK.PAS unit where TTabbedNotebook is defined. A quick examination will reveal the TTabPage class, which is described as holding the controls for a given page of the TTabbedNotebook. The second clue to solving this case comes from observation that the Pages property of TTabbedNotebook has a type of TStrings. It just so happens that Delphi's TStrings and TStringList classes provide both Strings and Objects property pairs. In other words, for every string in TStrings, there is a corresponding Objects pointer. In many cases, this extra pointer is ignored, but if you're like me, you're thinking "Ah-hah!"
923
After a quick little test in code, sure enough, the Objects property points to a TTabPage instance -- the one that corresponds to the page name in the Strings property. Bingo! Just what we were looking for. Now see what we can do: { This procedure adds places a button at a random location on the } { current page of the given TTabbedNotebook. } procedure AddButton(tabNotebook : TTabbedNotebook); var tabpage : TTabPage; button : TButton; begin with tabNotebook do tabpage := TTabPage(Pages.Objects[PageIndex]); button := TButton.Create(tabpage); try with button do begin Parent := tabpage; Left := Random(tabpage.ClientWidth - Width); Top := Random(tabpage.ClientHeight - Height); end; except button.Free; end; end;
TNotebook The process of adding controls to a TNotebook is almost exactly the same as that for TTabbedNotebook -- only the page class type is TPage instead of TTabPage. However, if you look in DELPHI\DOC\EXTCTRLS.INT for the type declaration for TPage, you won't find it. For some reason, Borland did not include the TPage definition in the DOC files that shipped with Delphi. The TPage declaration *IS* in the EXTCTRLS.PAS unit that you get when you order the VCL source, right where it should be in the interface section of the unit. Here's the TPage information they left out: TPage = class(TCustomControl) private procedure WMNCHitTest(var Message: TWMNCHitTest); message WM_NCHITTEST; protected procedure ReadState(Reader: TReader); override; procedure Paint; override; public constructor Create(AOwner: TComponent); override; published property Caption; property Height stored False; property TabOrder stored False; property Visible stored False; property Width stored False; end;
Now, to make the above procedure work for adding a button to a TNotebook, all we have to do is replace "TTabbedNotebook" with "TNotebook" and "TTabPage" with "TPage", as follows: { This procedure adds places a button at a random location on the } { current page of the given TNotebook. } procedure AddButton(Notebook1 : TNotebook); var page : TPage; button : TButton;
924
begin with Notebook1 do page := TPage(Pages.Objects[PageIndex]); button := TButton.Create(page); try with button do begin Parent := page; Left := Random(page.ClientWidth - Width); Top := Random(page.ClientHeight - Height); end; except button.Free; end; end;
That's all there is to it!
Saving and Loading aTstringgrid From: "Eric Lawrence"
925
How do I synchronize two scroll boxes on different panels? By handle OnScroll event ,follow code can synchronize two scroll boxes on different panels: (You have two scrollboxes in TMainForm:ScrollBar1 and ScrollBar2) procedure TMainForm.ScrollBar1Scroll(Sender: TObject; ScrollCode: TScrollCode; var ScrollPos: Integer); begin ScrollBar2.Position:=ScrollPos; end; procedure TMainForm.ScrollBar2Scroll(Sender: TObject; ScrollCode: TScrollCode; var ScrollPos: Integer); begin ScrollBar1.Position := ScrollPos; end;
Delayed scrolling / delayed OnChange? Erik Sperling Johansen >[email protected]> Stefan Hoffmeister wrote: If the user keeps either key pressed and the change of the item (ComboBox.OnChange) takes a long(ish) time an annoying delay will be noticed. As a "work around" I would like to react to the change of the ItemIndex only after a short period of time, e.g. 100 ms.
Here's an example. Written in D2, but technique should work OK in D1 too. Just a simple form with a combo and a label. You probably should consider using Yield in addition to the call to Application.ProcessMessages, to avoid slowing down the PC when the forms message queue is empty. unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; const // Just some message constant PM_COMBOCHANGE = WM_USER + 8001; // 500 ms CWantedDelay = 500; type TForm1 = class(TForm) ComboBox1: TComboBox; Label1: TLabel; procedure ComboBox1Change(Sender: TObject); private procedure PMComboChange(var message : TMessage); message PM_COMBOCHANGE; public end; var
926
Form1: TForm1; implementation {$R *.DFM} procedure TForm1.ComboBox1Change(Sender: TObject); begin PostMessage(Handle, PM_COMBOCHANGE, 0, 0); end; procedure TForm1.PMComboChange(var message : TMessage); const InProc : BOOLEAN = FALSE; StartTick : LONGINT = 0; begin if InProc then begin // Update the starting time for the delay StartTick := GetTickCount; end else begin // We're in the loop InProc := TRUE; // Initial starting time StartTick := GetTickCount; // Wait until wanted time has elapsed. // If proc gets called again, starting time will change while GetTickCount - StartTick < CWantedDelay do Application.ProcessMessages; // Increment a counter, just for illustration of when to do the actual OnChange work Label1.Caption := IntToStr ( StrToIntDef ( Label1.Caption, 0 ) + 1); // We're finished with the loop InProc := FALSE; end; end; end.
Unselectable Tab Is there any way at all of making a tab on a tabbed notebook unselectable? i.e not allowing the user to click and see its contents?
[Mike O'Hanlon, [email protected]] Yes, this is possible. The simplest way to do it is to remove the relevant page of the TabbedNotebook with something like: with TabbedNotebook do Pages.Delete(PageIndex);
and retrieve the deleted page (if necessary) by reloading the Form. Disabling (rather than deleting) is a bit trickier because you have to set up a loop in the Form's Create procedure to assign names to the tabs of the TabbedNotebook. Something like: J := 0; with TabbedNotebook do for I := 0 to ComponentCount - 1 do
927
if Components[I].ClassName = 'TTabButton' then begin Components[I].Name := ValidIdentifier(TTabbedNotebook( Components[I].Owner).Pages[J]) + 'Tab'; Inc(J); end;
where ValidIdentifier is a function which returns a valid Pascal identifier derived from the Tab string: function ValidIdentifier (theString: str63): str63; {----------------------------------------------------------} { Turns the supplied string into a valid Pascal identifier } { by removing all invalid characters, and prefixing with } { an underscore if the first character is numeric. } {----------------------------------------------------------} var I, Len: Integer; begin Len := Length(theString); for I := Len downto 1 do if not (theString[I] in LettersUnderscoreAndDigits) then Delete(theString, I, 1); if not (theString[1] in LettersAndUnderscore) then theString := '_' + theString; ValidIdentifier := theString; end; {ValidIdentifier}
A Tab of the TabbedNotebook may then be disabled with with TabbedNotebook do begin TabIdent := ValidIdentifier(Pages[PageIndex]) + 'Tab'; TControl(FindComponent(TabIdent)).Enabled := False; { Switch to the first enabled Tab: } for I := 0 to Pages.Count - 1 do begin TabIdent := ValidIdentifier(Pages[I]) + 'Tab'; if TControl(FindComponent(TabIdent)).Enabled then begin PageIndex := I; Exit; end; end; {for} end; {with TabbedNotebook}
and you could re-enable all tabs with: with TabbedNotebook do for I := 0 to Pages.Count - 1 do begin TabIdent := ValidIdentifier(Pages[I]) + 'Tab'; if not TControl(FindComponent(TabIdent)).Enabled then TControl(FindComponent(TabIdent)).Enabled := True; end; {for}
928
FONTS 158. 159. 160. 161. 162. 163.
How can my app use MY FONTS? not user's Include Font as a Resource in *.EXEfonts1 Font & Tregistry Store Fontstyle in INI Determining which font (Large or Small) is in use Font Sites
How can my app use MY FONTS? not user's From: [email protected] (Brad Choate) Can someone please tell me the neatest way to make sure my app uses fonts that I can provide, rather than the nearest font the user has installed on their system? I have tried copying a #.ttf file into the users windows\system directory but the app still can't pick it up.
The following is some Delphi 1 code that I have used for successfully installing dynamic fonts that are only loaded while the application is running. You can place the font file(s) within the application directory. It will be installed when the form loads and unloaded once the form is destroyed. You may need to modify the code to work with Delphi 2 since it calls various Windows API calls that may or may not have changed. Where you see "..." in the code, that is just to identify that other code can be placed there. Of course, substitute "MYFONT" for the name of your font file. type TForm1=class( TForm ) procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); ... private { Private declarations } bLoadedFont: boolean; public { Public declarations } end; procedure TForm1.FormCreate(Sender: TObject); var sAppDir: string; sFontRes: string; begin sAppDir := Application.ExeName; sAppDir := copy( sAppDir, 1, rpos( '\', sAppDir ) ); sFontRes := sAppDir + 'MYFONT.FOT'; if not FileExists( sFontRes ) then begin sFontRes := sFontRes + #0; sFont := sAppDir + 'MYFONT.TTF' + #0; CreateScalableFontResource( 0, @sFontRes[ 1 ], @sFont[ 1 ], nil ); end; sFontRes := sAppDir + 'MYFONT.FOT'; if FileExists( sFontRes ) then begin
929
sFontRes := sFontRes + #0; if AddFontResource( @sFontRes[ 1 ] ) = 0 then bLoadedFont := false else begin bLoadedFont := true; SendMessage( HWND_BROADCAST, WM_FONTCHANGE, 0, 0 ); end; end; ... end; procedure TForm1.FormDestroy(Sender: TObject); var sFontRes: string; begin if bLoadedFont then begin sFontRes := sAppDir + 'MYFONT.FOT' + #0; RemoveFontResource( @sFontRes[ 1 ] ); SendMessage( HWND_BROADCAST, WM_FONTCHANGE, 0, 0 ); end; end;
From: Paul Munn
The resulting FormDestroy code is as follows: var sFontRes, sAppDir: string; begin {...other code...} if bLoadedFont then
930
begin sAppDir := extractfilepath(Application.ExeName); sFontRes := sAppDir + 'MYFONT.TTF' + #0; RemoveFontResource( @sFontRes[ 1 ] ); SendMessage( HWND_BROADCAST, WM_FONTCHANGE, 0, 0 ); end; {...other code...} end; {FormDestroy}
To simplify these, I have created a simple function which can do both of these tasks. It returns a boolean which says whether or not the loading or unloading of the font was successful. {1998-01-16 Font loading and unloading function.} function LoadFont(sFontFileName: string; bLoadIt: boolean): boolean; var sFont, sAppDir, sFontRes: string; begin result := TRUE; if bLoadIt then begin {Load the font.} if FileExists( sFontFileName ) then begin sFontRes := sFontFileName + #0; if AddFontResource( @sFontRes[ 1 ] ) = 0 then result := FALSE else SendMessage( HWND_BROADCAST, WM_FONTCHANGE, 0, 0 ); end; end else begin {Unload the font.} sFontRes := sFontFileName + #0; result := RemoveFontResource( @sFontRes[1] ); SendMessage( HWND_BROADCAST, WM_FONTCHANGE, 0, 0 ); end; end; {LoadFont}
Include Font as a Resource in *.EXE From: "Steve Harman"
The first two parameters can be whatever you want. They get used in your program later. 16. Then, use the BRCC32.EXE command line compiler that ships with Delphi to create a *.res file. If your file in step 1 was MyFont.rc, the command from the DOS prompt would be:
931
BRCC32 MyFont
The program will append the .rc to the input, and create a file with the same name except it appends .res: MyFont.res 17. In your program, add a compiler directive to include your newly created file: {$R MyFont.res}
This can go right after the default {$R *.DFM} in the implementation section. 18. Add a procedure to create a file from the resource, then make the Font available for use. Example: procedure TForm1.FormCreate(Sender: TObject); var Res : TResourceStream; begin Res := TResourceStream.Create(hInstance, 'MY_FONT', Pchar('ANYOL1')); Res.SavetoFile('Bauhs93.ttf'); Res.Free; AddFontResource(PChar('Bauhs93.ttf')); SendMessage(HWND_BROADCAST,WM_FONTCHANGE,0,0); end;
19. You can now assign the font to whatever you wish: procedure TForm1.Button1Click(Sender: TObject); begin Button1.Font.Name := 'Bauhaus 93'; end;
Caveats: The above example provides for no error checking whatsoever. :-) Notice that the File name is NOT the same as the Font name. It's assumed that you know the font name associated with the file name. You can determine this by double clicking on the file name in the explorer window. I would recommend placing your font file in the C:\WINDOWS\FONTS folder. It's easier to find them later. Your newly installed font can be removed programatically, assuming the font is not in use anywhere: procedure TForm1.FormDestroy(Sender: TObject); begin RemoveFontResource(PChar("Bauhs93.ttf")) SendMessage(HWND_BROADCAST,WM_FONTCHANGE,0,0); end;
Check the Win32 help for further details on the AddFontResource and RemoveFontResource.
Font & Tregistry See here
Store Fontstyle in INI See here
932
Resource File 164. 165. 166. 167.
String Tables How To Include JPEG's In Your Executable (Delphi 3) resource1 [Q] Resource files (*.res) / scripts (*.rc)<> Loading bitmaps from a resource DLL into a TImage component.resource3
String Tables From: "Joe C. Hecht"
The file starts with the key word stringtable denoting that a string table resource will follow. Enclosed in the curly braces are the strings. Each string is listed by it's index identifier, followed by the actual string data in quotes. Each string may contain up to 255 characters. If you need to use a non-standard character, insert the character as a backslash character followed by the octal number of the character you wish to insert. The only exception is when you want to embed a backslash character, you will need to use two backslashes. Here are two examples: 933
1, "A two\012line string" 2, "c:\\Borland\\Delphi"
The Index numbers that you use are not important to the resource compiler. You should keep in mind that string tables are loaded into memory in 16 string segments. To compile the .rc file to a .res file that can be linked with your application, simply type on the dos command line the full path to the resource compiler, and the full path to the name of the .rc file to compile. Here is an example: c:\Delphi\Bin\brcc32.exe c:\Delphi\strtbl32.rc
When the compiler is finished, you should have a new file with the same name as the .rc file you've compiled, only with an extension of ".res". You can link the resource file with your application simply by adding the following statement to your application's code, substituting the name of your resource file: {$R ResFileName.RES}
Once the .res file is linked to your program, you can load the resource from any module, even if you specified the $R directive in the implementation section of a different unit. Here is an example of using the Windows API function LoadString(), to load the third string contained in a string resource into a character array: if LoadString(hInstance, 3, @a, sizeof(a)) <> 0 then ....
In this example, the LoadString() function accepts the hInstance of the module containing the resource, the string index to load, the address of the character array to load the string to, and the size of the character array. The LoadString function returns the number of characters that where actually loaded not including the null terminator. Be aware that this can differ from the number of bytes loaded when using unicode. Here is a complete example of creating an international application with Borland's Delphi. The application is compatible with both 16 and 32 bit versions of Delphi. To do this, you will need to create two identical .rc files, one for the 16 bit version, and the other for the 32 bit version, since the resources needed for each platform are different. In this example. we will create one file named STRTBL16.rc and another called STRTBL32.rc. Compile the STRTBL16.rc file using the BRCC.exe compiler found in Delphi 1.0's bin directory, and compile STRTBL32.rc using the BRCC32.exe compiler found in Delphi 2.0's bin directory. We have taken into account the language that Windows is currently using at runtime. The method for getting this information differs under 16 and 32 bit Windows. To make the code more consistant, we have borrowed the language constants from the Windows.pas file used in 32 bit versions of Delphi. {$IFDEF WIN32} {$R STRTBL32.RES} {$ELSE} {$R STRTBL16.RES} const LANG_ENGLISH = $09; const LANG_SPANISH = $0a; const LANG_SWEDISH = $1d; {$ENDIF} function GetLanguage : word; {$IFDEF WIN32} {$ELSE} var s : string; i : integer; {$ENDIF} begin {$IFDEF WIN32}
934
GetLanguage := GetUserDefaultLangID and $3ff; {$ELSE} s[0] := Char(GetProfileString('intl', 'sLanguage', 'none', @s[1], sizeof(s)-2)); for i := 1 to length(s) do s[i] := UpCase(s[i]); if s = 'ENU' then GetLanguage := LANG_ENGLISH else if s = 'ESN' then GetLanguage := LANG_SPANISH else if s = 'SVE' then GetLanguage := LANG_SWEDISH else GetLanguage := LANG_ENGLISH; {$ENDIF} end; procedure TForm1.FormCreate(Sender: TObject); var a : array[0..255] of char; StrTblOfs : integer; begin {Get the current language and stringtable offset} case GetLanguage of LANG_ENGLISH : StrTblOfs := 0; LANG_SPANISH : StrTblOfs := 16; LANG_SWEDISH : StrTblOfs := 32; else StrTblOfs := 0; end; {Load language dependent "Yes" and set the button caption} if LoadString(hInstance, StrTblOfs + 1, @a, sizeof(a)) <> 0 then Button1.Caption := StrPas(a); {Load language dependent "No" and set the button caption} if LoadString(hInstance, StrTblOfs + 2, @a, sizeof(a)) <> 0 then Button2.Caption := StrPas(a); end;
How To Include JPEG's In Your Executable (Delphi 3) From: Marko Peric
935
STEP ONE: Create a resource script file (*.RC) with a simple text editor like Notepad and add the following line: 1
RCDATA
"MyPic.jpg"
The first entry is simply the index of the resource. The second entry specifies that we are dealing with a user-defined resource. The third and final entry is the name of the jpeg file.
STEP TWO: Use Borland's Resource Compiler, BRCC32.EXE, to compile it into a .RES file. At the MSDOS command line: BRCC32 MyPic.RC
This will create a resource file called MyPic.RES.
STEP THREE: Add a compiler directive to the source code of your program. It should immediately follow the form directive, as shown here: {$R *.DFM} {$R MyPic.RES}
STEP FOUR: Add the following code to your project (I've created a procedure for it): procedure LoadJPEGfromEXE; var MyJPG : TJPEGImage; // JPEG object ResStream : TResourceStream; // Resource Stream object begin try MyJPG := TJPEGImage.Create; ResStream := TResourceStream.CreateFromID(HInstance, 1, RT_RCDATA); MyJPG.LoadFromStream(ResStream); // What!? Yes, that easy! Canvas.Draw(12,12,MyJPG); // draw it to see if it really worked! finally MyJPG.Free; ResStream.Free; end; end; // procedure
See the second parameter of the CreateFromID procedure of the TResourceStream component? It's simply the resource index. You can include more than one jpeg in your executable just by adding a line for each jpeg (with a different index) in the resource script (.RC) file.
STEP FIVE: Call the procedure, run the program, and voila! Now go eat some nachos.
936
[Q] Resource files (*.res) / scripts (*.rc)<> align="TOP"> From: Ken White
RESOURCETYPE
FILENAME
For example, to include a bitmap in the resource file: MyBitmap
BITMAP
MyBitmap.bmp
To include a cursor: MyCursor
CURSOR
MyCursor.cur
You use the same technique to include AVI, WAV and ICO files. For other filetypes, you can use the type RC_DATA. To use the resource script (.RC) file, you use Borland's Resource Compiler (BRCC.EXE with Delphi 1, BRCC32.EXE otherwise) which is located in the Delphi Bin directory. The syntax is: BRCC32 MyRes.RC
This creates MyRes.RES. You then add the {$R MYRES.RES} to a unit in your application to have Delphi's linker include it, and use one of the Load... API calls (or Delphi's wrappers for some components) to use the resource at runtime.
Loading bitmaps from a resource DLL into a TImage component. From: "Jason Wallace"
Please email me and tell me if you liked this page. Last modified 03/12/00 12:09:55
937
Quick Report 168. 169. 170.
QuickReports FONT Problems Preview Form Caption QuickReports on different paper sizes
QuickReports FONT Problems From: [email protected] (Max Nilson) [email protected] (Bill Artemik) wrote: I'm using Delphi 2.0 and QuickReports 1.1 >I created a VERY complex (graphically, anyway) report and just found >out that ALTHOUGH it previews fine in design mode, does NOT display or >print correctly in runtime. The error appears to be that I'm running >with LARGE fonts in Windows 95! What a crock!
I too was getting this problem _very_ seriously, but only when printing from Win 95 to the office HP 4M, or when viewing the report under NT 4.0. The _exact_ same application when run under NT 3.51 SP5 worked perfectly. I had seen discussion of one other bug in the NT 4.0 GDI code, so I wasn't suprised when the above test showed that there seemed to be another. I looked very closely at the QuickReports code and noticed that the primary text output routine alters its ouput font for every field. Its does this via the following code segment in TQRCustomControl.Print: QRPrinter.Canvas.Font:=Font; QRPrinter.Canvas.Font.size:=trunc(abs(parentreport.xpos(font.size))); QRPrinter.Canvas.brush.Color:=Color;
Now those of us who have spent far too much time reading the VCL will know that the VCL maintains a resource cache via a handle manager in Graphics.pas, and that this attempts to lower the number of Windows resources allocated by using a reference counted approach to reusing old resource handles. The code above manages to circumvent this most excellently! Every time the printers canvas is set to the fields font, thus lowering the printer's old font resource count to zero and releasing it, and then scaling the fonts size to match the printer metrics, thus requiring a new font resource to be allocated. The upshot of all this is that _every_ field of the report was being drawn using a newly alocated font resource, and probably driving the meta file recorder into spasms trying to keep track of all this choping and changing of font resources. A quick hack where by I created a non printing label of the same size as the scaled printer font, thereby keeping the resource cached, made the bug go away! This proved that there is some sort of problem with the NT 4.0 ehnanced meta file recorder and too many font changes. A better hack has been introduced into QuickReports as follows: // MRN Save the printer font while setting new font SaveFont := TFont.Create; SaveFont.Assign(QRPrinter.Canvas.Font); QRPrinter.Canvas.Font:=Font;
938
QRPrinter.Canvas.Font.size:=trunc(abs(parentreport.xpos(font.size))); QRPrinter.Canvas.brush.Color:=Color; // MRN Free the saved printer font. Its done its work now. SaveFont.Free; SaveFont := nil;
This manages to ensure that the printer font is reused if the same font is selected into the printer canvas. This works perfectly and produces correct reports on NT 4.0. By some strange coincidence 8-) this also stopped the wierd bold, out sized fonts being printed under Win95. So if you have a registered version of QuickReports thaen you simply make this patch and things will run faster, smother and no more font corruptions will occur due to the limitations of the enhanced meta file code and the GDI.
Preview Form Caption Bruno Sonnino [[email protected]] You can set QRPrinter PreviewCaption to your caption like this: QRPrinter.PreviewCaption := 'My Caption';
QRPrinter is a global variable defined in Quickrep.pas
QuickReports on different paper sizes From: [email protected] Hi! Have you looked at the TPrinter-object? You can set the printer you want to use with the Printer.PrinterIndex. For example: Printer.PrinterIndex:=0; Sets the first printer you have installed Printer.PrinterIndex:=-1; Sets the printer to the default printer
Observe! That this function don't make an global change to your system, it's just for the time being. Please email me and tell me if you liked this page. Last modified 03/12/00 12:09:40
939
Strings 171. 172. 173. 174. 175. 176. 177. 178. 179. 180. 181. 182.
Equivalent of Trim$(),Mid$(), etc? String Pattern matching GetTokenstrings2 Replacing substrings Capitalize the first letter of each word in a string How do I determine if two strings sound alike? What are the values for the virtual keys? Delphi currency amount converter strings7 Remove Unwanted from String from String String Parsing? REGULR EXPRESSIONSstrings10 4 Small String Routinesstrings11
Equivalent of Trim$(),Mid$(), etc? Solution 1 From: [email protected] (Bob Swart) unit TrimStr; {$B-} { File: TrimStr Author: Bob Swart [100434,2072] Purpose: routines for removing leading/trailing spaces from strings, and to take parts of left/right of string (a la Basic). Version: 2.0 LTrim() RTrim() Trim() RightStr() LeftStr() MidStr()
-
Remove all spaces from the left side of a string Remove all spaces from the right side of a string Remove all extraneous spaces from a string Take a certain portion of the right side of a string Take a certain portion of the left side of a string Take the middle portion of a string
} interface Const Space = #$20; function function function function function function
LTrim(Const Str: String): String; RTrim(Str: String): String; Trim(Str: String): String; RightStr(Const Str: String; Size: Word): String; LeftStr(Const Str: String; Size: Word): String; MidStr(Const Str: String; Size: Word): String;
implementation function LTrim(Const Str: String): String; var len: Byte absolute Str;
940
i: Integer; begin i := 1; while (i <= len) and (Str[i] = Space) do Inc(i); LTrim := Copy(Str,i,len) end {LTrim}; function RTrim(Str: String): String; var len: Byte absolute Str; begin while (Str[len] = Space) do Dec(len); RTrim := Str end {RTrim}; function Trim(Str: String): String; begin Trim := LTrim(RTrim(Str)) end {Trim}; function RightStr(Const Str: String; Size: Word): String; var len: Byte absolute Str; begin if Size > len then Size := len; RightStr := Copy(Str,len-Size+1,Size) end {RightStr}; function LeftStr(Const Str: String; Size: Word): String; begin LeftStr := Copy(Str,1,Size) end {LeftStr}; function MidStr(Const Str: String; Size: Word): String; var len: Byte absolute Str; begin if Size > len then Size := len; MidStr := Copy(Str,((len - Size) div 2)+1,Size) end {MidStr}; end.
Solution 2 From: [email protected] (Joseph Bui) For Mid$, use Copy(S: string; start, length: byte): string; You can make copy perform Right$ and Left$ as well by doing: Copy(S, 1, Length) for left$ and Copy(S, Start, 255) for right$ Note: Start and Length are the byte positions of your starting point, get these with Pos(). Here are some functions I wrote that come in handy for me. Way down at the bottom is a trim() function that you can modify into TrimRight$ and TrimLeft$. Also, they all take pascal style strings, but you can modify them to easily null terminated. const BlackSpace = [#33..#126]; { squish() returns a string with all whitespace not inside single quotes deleted. } function squish(const Search: string): string; var Index: byte; InString: boolean; begin
941
InString:=False; Result:=''; for Index:=1 to Length(Search) do begin if InString or (Search[Index] in BlackSpace) then AppendStr(Result, Search[Index]); InString:=((Search[Index] = '''') and (Search[Index - 1] <> '\')) xor InString; end; end; {
before() returns everything before the first occurance of Find in Search. If Find does not occur in Search, Search is returned. } function before(const Search, Find: string): string; var index: byte; begin index:=Pos(Find, Search); if index = 0 then Result:=Search else Result:=Copy(Search, 1, index - 1); end; { after() returns everything after the first occurance of Find in Search. If Find does not occur in Search, a null string is returned. } function after(const Search, Find: string): string; var index: byte; begin index:=Pos(Find, Search); if index = 0 then Result:='' else Result:=Copy(Search, index + Length(Find), 255); end; { RPos() returns the index of the first character of the last occurance of Find in Search. Returns 0 if Find does not occur in Search. Like Pos() but searches in reverse. } function RPos(const Find, Search: string): byte; var FindPtr, SearchPtr, TempPtr: PChar; begin FindPtr:=StrAlloc(Length(Find)+1); SearchPtr:=StrAlloc(Length(Search)+1); StrPCopy(FindPtr,Find); StrPCopy(SearchPtr,Search); Result:=0; repeat TempPtr:=StrRScan(SearchPtr, FindPtr^); if TempPtr <> nil then if (StrLComp(TempPtr, FindPtr, Length(Find)) = 0) then begin Result:=TempPtr - SearchPtr + 1; TempPtr:=nil; end else
942
TempPtr:=#0; until TempPtr = nil; end; { inside() returns the string between the most inside nested Front ... Back pair. } function inside(const Search, Front, Back: string): string; var Index, Len: byte; begin Index:=RPos(Front, before(Search, Back)); Len:=Pos(Back, Search); if (Index > 0) and (Len > 0) then Result:=Copy(Search, Index + 1, Len - (Index + 1)) else Result:=''; end; { leftside() returns what is to the left of inside() or Search. } function leftside(const Search, Front, Back: string): string; begin Result:=before(Search, Front + inside(Search, Front, Back) + Back); end; { rightside() returns what is to the right of inside() or Null. } function rightside(const Search, Front, Back: string): string; begin Result:=after(Search, Front + inside(Search, Front, Back) + Back); end; {
trim() returns a string with all right and left whitespace removed. } function trim(const Search: string): string; var Index: byte; begin Index:=1; while (Index <= Length(Search)) and not (Search[Index] in BlackSpace) do Index:=Index + 1; Result:=Copy(Search, Index, 255); Index:=Length(Result); while (Index > 0) and not (Result[Index] in BlackSpace) do Index:=Index - 1; Result:=Copy(Result, 1, Index); end;
String Pattern matching From: [email protected] (David Stidolph) There are many times when you need to compare two strings, but want to use wild cards in the match - all last names that begin with 'St', etc. The following is a piece of code I got from Sean Stanley in Tallahassee Florida in C. I translated it into Delphi an am uploading 943
it here for all to use. I have not tested it extensivly, but the original function has been tested quite thoughly. I would love feedback on this routine - or peoples changes to it. I want to forward them to Sean to get him to release more tidbits like this. { This function takes two strings and compares them. The first string can be anything, but should not contain pattern characters (* or ?). The pattern string can have as many of these pattern characters as you want. For example: MatchStrings('David Stidolph','*St*') would return True. Orignal code by Sean Stanley in C Rewritten in Delphi by David Stidolph } function MatchStrings(source, pattern: String): Boolean; var pSource: Array [0..255] of Char; pPattern: Array [0..255] of Char; function MatchPattern(element, pattern: PChar): Boolean; function IsPatternWild(pattern: PChar): Boolean; var t: Integer; begin Result := StrScan(pattern,'*') <> nil; if not Result then Result := StrScan(pattern,'?') <> nil; end; begin if 0 = StrComp(pattern,'*') then Result := True else if (element^ = Chr(0)) and (pattern^ <> Chr(0)) then Result := False else if element^ = Chr(0) then Result := True else begin case pattern^ of '*': if MatchPattern(element,@pattern[1]) then Result := True else Result := MatchPattern(@element[1],pattern); '?': Result := MatchPattern(@element[1],@pattern[1]); else if element^ = pattern^ then Result := MatchPattern(@element[1],@pattern[1]) else Result := False; end; end; end; begin StrPCopy(pSource,source); StrPCopy(pPattern,pattern); Result := MatchPattern(pSource,pPattern); end;
944
GetToken Thomas Scheffczyk
: Char;
945
StrLen TNum TEnd
: Byte; : Byte; : Byte;
begin if SepChar = '#' then begin RChar := '*' end else begin RChar := '#' end; StrLen := Length(aString); TNum := 0; TEnd := StrLen; while TEnd <> 0 do begin Inc(TNum); TEnd := Pos(SepChar,aString); if TEnd <> 0 then begin aString[TEnd] := RChar; end; end; NumToken1 := TNum; end;
"Hrvoje Brozovic"
I know that in GetToken SepChar parameter ( c_fence in my case ) is string, not char, but comment says that he is expecting single char in that string, and it is obvious that if you send more than one char, it won't work correctly. ( Delete(aString,1,TEnd) is buggy if Length( SepChar ) > 1 ).
946
Replacing substrings From: [email protected] (Michael Bialas) Does anyone know a fast algorithm that replaces all occurences of any substring sub1 to any string sub2 in any string str.
This should do the job: function ReplaceSub(str, sub1, sub2: String): String; var aPos: Integer; rslt: String; begin aPos := Pos(sub1, str); rslt := ''; while (aPos <> 0) do begin rslt := rslt + Copy(str, 1, aPos - 1) + sub2; Delete(str, 1, aPos + Length(sub1)); aPos := Pos(sub1, str); end; Result := rslt + str; end;
Capitalize the first letter of each word in a string Erik Sperling Johansen
From: "Cleon T. Bailey"
Function TfrmLoadProtocolTable.ToMixCase(InString: String): String; Var I: Integer; Begin Result := LowerCase(InString); Result[1] := UpCase(Result[1]); For I := 1 To Length(InString) - 1 Do Begin If (Result[I] = ' ') Or (Result[I] = '''') Or (Result[I] = '"') Or (Result[I] = '-') Or (Result[I] = '.') Or (Result[I] = '(') Then Result[I + 1] := UpCase(Result[I + 1]); End; End;
From: "Paul Motyer"
How do I determine if two strings sound alike? { This code came from Lloyd's help file! } Soundex function--determines whether two words sound alike. Written after reading an article in PC Magazine about the Soundex algorithm. Pass the function a string. It returns a Soundex value string. This value can be saved in a database or compared to another Soundex value. If two words have the same Soundex value, then they sound alike (more or less). Note that the Soundex algorithm ignores the first letter of a word. Thus, "won" and "one" will have different Soundex values, but "Won" and "Wunn" will have the same values. Soundex is especially useful in databases when one does not know how to spell a last name. Function Soundex(OriginalWord: string): string; var Tempstring1, Tempstring2: string; Count: integer; begin Tempstring1 := '';
948
Tempstring2 := ''; OriginalWord := Uppercase(OriginalWord); {Make original word uppercase} Appendstr(Tempstring1, OriginalWord[1]); {Use the first letter of the word} for Count := 2 to length(OriginalWord) do {Assign a numeric value to each letter, except the first} case OriginalWord[Count] of 'B','F','P','V': Appendstr(Tempstring1, '1'); 'C','G','J','K','Q','S','X','Z': Appendstr(Tempstring1, '2'); 'D','T': Appendstr(Tempstring1, '3'); 'L': Appendstr(Tempstring1, '4'); 'M','N': Appendstr(Tempstring1, '5'); 'R': Appendstr(Tempstring1, '6'); {All other letters, punctuation and numbers are ignored} end; Appendstr(Tempstring2, OriginalWord[1]); {Go through the result removing any consecutive duplicate numeric values.} for Count:=2 to length(Tempstring1) do if Tempstring1[Count-1]<>Tempstring1[Count] then Appendstr(Tempstring2,Tempstring1[Count]); Soundex:=Tempstring2; {This is the soundex value} end;
SoundAlike--pass two strings to this function. It returns True if they sound alike, False if they don't. Simply calls the Soundex function. Function SoundAlike(Word1, Word2: string): boolean; begin if (Word1 = '') and (Word2 = '') then result := True else if (Word1 = '') or (Word2 = '') then result := False else if (Soundex(Word1) = Soundex(Word2)) then result := True else result := False; end;
What are the values for the virtual keys? vk_LButton vk_RButton vk_Cancel vk_MButton vk_Back vk_Tab vk_Clear vk_Return vk_Shift vk_Control vk_Menu vk_Pause vk_Capital vk_Escape vk_Space vk_Prior vk_Next
= = = = = = = = = = = = = = = = =
$01; $02; $03; $04; $08; $09; $0C; $0D; $10; $11; $12; $13; $14; $1B; $20; $21; $22;
vk_End vk_Home vk_Left
= $23; = $24; = $25;
{ NOT contiguous with L & RBUTTON }
949
vk_Up = vk_Right = vk_Down = vk_Select = vk_Print = vk_Execute = vk_SnapShot = { vk_Copy = vk_Insert = vk_Delete = vk_Help = { vk_A thru vk_Z { vk_0 thru vk_9
$26; $27; $28; $29; $2A; $2B; $2C; $2C not used by keyboards } $2D; $2E; $2F; are the same as their ASCII equivalents: 'A' thru 'Z' } are the same as their ASCII equivalents: '0' thru '9' }
vk_NumPad0 vk_NumPad1 vk_NumPad2 vk_NumPad3 vk_NumPad4 vk_NumPad5 vk_NumPad6 vk_NumPad7 vk_NumPad8 vk_NumPad9 vk_Multiply vk_Add vk_Separator vk_Subtract vk_Decimal vk_Divide vk_F1 vk_F2 vk_F3 vk_F4 vk_F5
= = = = = = = = = = = = = = = = = = = = =
$60; $61; $62; $63; $64; $65; $66; $67; $68; $69; $6A; $6B; $6C; $6D; $6E; $6F; $70; $71; $72; $73; $74;
vk_F6 vk_F7 vk_F8 vk_F9 vk_F10 vk_F11 vk_F12 vk_F13 vk_F14 vk_F15 vk_F16 vk_F17 vk_F18 vk_F19 vk_F20 vk_F21 vk_F22 vk_F23 vk_F24 vk_NumLock vk_Scroll
= = = = = = = = = = = = = = = = = = = = =
$75; $76; $77; $78; $79; $7A; $7B; $7C; $7D; $7E; $7F; $80; $81; $82; $83; $84; $85; $86; $87; $90; $91;
{ This code came from Lloyd's help file! }
Delphi currency amount converter From: "Donald Johnson"
Function HundredAtATime(TheAmount:Integer):String; var TheResult : String; Begin TheResult := ''; TheAmount := Abs(TheAmount); While TheAmount > 0 do Begin If TheAmount >= 900 Then Begin TheResult := TheResult + 'Nine hundred '; TheAmount := TheAmount - 900; End; If TheAmount >= 800 Then Begin TheResult := TheResult + 'Eight hundred '; TheAmount := TheAmount - 800; End; If TheAmount >= 700 Then Begin TheResult := TheResult + 'Seven hundred '; TheAmount := TheAmount - 700; End; If TheAmount >= 600 Then Begin TheResult := TheResult + 'Six hundred '; TheAmount := TheAmount - 600; End; If TheAmount >= 500 Then Begin TheResult := TheResult + 'Five hundred '; TheAmount := TheAmount - 500; End; If TheAmount >= 400 Then Begin TheResult := TheResult + 'Four hundred '; TheAmount := TheAmount - 400; End; If TheAmount >= 300 Then Begin TheResult := TheResult + 'Three hundred '; TheAmount := TheAmount - 300; End; If TheAmount >= 200 Then Begin TheResult := TheResult + 'Two hundred '; TheAmount := TheAmount - 200; End; If TheAmount >= 100 Then Begin TheResult := TheResult + 'One hundred '; TheAmount := TheAmount - 100; End; If TheAmount >= 90 Then Begin TheResult := TheResult + 'Ninety '; TheAmount := TheAmount - 90; End; If TheAmount >= 80 Then Begin TheResult := TheResult + 'Eighty '; TheAmount := TheAmount - 80; End; If TheAmount >= 70 Then Begin TheResult := TheResult + 'Seventy '; TheAmount := TheAmount - 70; End; If TheAmount >= 60 Then Begin TheResult := TheResult + 'Sixty '; TheAmount := TheAmount - 60; End; If TheAmount >= 50 Then Begin TheResult := TheResult + 'Fifty '; TheAmount := TheAmount - 50; End; If TheAmount >= 40 Then Begin
951
TheResult := TheResult + 'Fourty '; TheAmount := TheAmount - 40; End; If TheAmount >= 30 Then Begin TheResult := TheResult + 'Thirty '; TheAmount := TheAmount - 30; End; If TheAmount >= 20 Then Begin TheResult := TheResult + 'Twenty '; TheAmount := TheAmount - 20; End; If TheAmount >= 19 Then Begin TheResult := TheResult + 'Nineteen '; TheAmount := TheAmount - 19; End; If TheAmount >= 18 Then Begin TheResult := TheResult + 'Eighteen '; TheAmount := TheAmount - 18; End; If TheAmount >= 17 Then Begin TheResult := TheResult + 'Seventeen '; TheAmount := TheAmount - 17; End; If TheAmount >= 16 Then Begin TheResult := TheResult + 'Sixteen '; TheAmount := TheAmount - 16; End; If TheAmount >= 15 Then Begin TheResult := TheResult + 'Fifteen '; TheAmount := TheAmount - 15; End; If TheAmount >= 14 Then Begin TheResult := TheResult + 'Fourteen '; TheAmount := TheAmount - 14; End; If TheAmount >= 13 Then Begin TheResult := TheResult + 'Thirteen '; TheAmount := TheAmount - 13; End; If TheAmount >= 12 Then Begin TheResult := TheResult + 'Twelve '; TheAmount := TheAmount - 12; End; If TheAmount >= 11 Then Begin TheResult := TheResult + 'Eleven '; TheAmount := TheAmount - 11; End; If TheAmount >= 10 Then Begin TheResult := TheResult + 'Ten '; TheAmount := TheAmount - 10; End; If TheAmount >= 9 Then Begin TheResult := TheResult + 'Nine '; TheAmount := TheAmount - 9; End; If TheAmount >= 8 Then Begin TheResult := TheResult + 'Eight '; TheAmount := TheAmount - 8; End; If TheAmount >= 7 Then Begin TheResult := TheResult + 'Seven '; TheAmount := TheAmount - 7; End; If TheAmount >= 6 Then Begin TheResult := TheResult + 'Six '; TheAmount := TheAmount - 6;
952
End; If TheAmount >= 5 Then Begin TheResult := TheResult + 'Five '; TheAmount := TheAmount - 5; End; If TheAmount >= 4 Then Begin TheResult := TheResult + 'Four '; TheAmount := TheAmount - 4; End; If TheAmount >= 3 Then Begin TheResult := TheResult + 'Three '; TheAmount := TheAmount - 3; End; If TheAmount >= 2 Then Begin TheResult := TheResult + 'Two '; TheAmount := TheAmount - 2; End; If TheAmount >= 1 Then Begin TheResult := TheResult + 'One '; TheAmount := TheAmount - 1; End; End; HundredAtATime := TheResult; End; Function Real2CheckAmount(TheAmount:Real):String; Var IntVal : LongInt; TmpVal : Integer; TmpStr, RetVal : String; begin TheAmount := Abs(TheAmount); { cents} TmpVal IntVal TmpStr If TmpStr RetVal If IntVal
:= Round(Frac(TheAmount) * 100); := Trunc(TheAmount); := HundredAtATime(TmpVal); = '' Then TmpStr := 'Zero '; := TmpStr + 'cents'; > 0 Then RetVal := 'dollars and ' + RetVal;
{ hundreds } TmpVal := IntVal := TmpStr := RetVal :=
Round(Frac((IntVal * 1.0) / 1000.0) * 1000); Trunc((IntVal * 1.0) / 1000.0); HundredAtATime(TmpVal); TmpStr + RetVal;
{ thousands } TmpVal := Round(Frac((IntVal * 1.0) / 1000.0) * 1000); IntVal := Trunc((IntVal * 1.0) / 1000.0); TmpStr := HundredAtATime(TmpVal); If TmpStr <> '' Then RetVal := TmpStr + 'Thousand ' + RetVal; { millions } TmpVal := Round(Frac((IntVal * 1.0) / 1000.0) * 1000); IntVal := Trunc((IntVal * 1.0) / 1000.0); TmpStr := HundredAtATime(TmpVal); If TmpStr <> '' Then RetVal := TmpStr + 'Million ' + RetVal; { billions } TmpVal := Round(Frac((IntVal * 1.0) / 1000.0) * 1000); IntVal := Trunc((IntVal * 1.0) / 1000.0); TmpStr := HundredAtATime(TmpVal);
953
If TmpStr <> '' Then RetVal := TmpStr + 'Billion ' + RetVal; Real2CheckAmount := RetVal; end;
From: [email protected] (Nazar Aziz) Hmmm... What about this.... and some nice recursion too!!!..:))) unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) num: TEdit; spell: TEdit; Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } function trans9(num: integer): string; function trans19(num: integer): string; function trans99(num: integer): string; function IntToSpell(num: integer): string; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} function TForm1.IntToSpell(num: integer): string; var spell: string; hspell: string; hundred: string; thousand: string; tthousand: string; hthousand: string; million: string; begin if num ≶ 10 then spell := trans9(num); {endif} if (num < 20) and (num > 10) then spell := trans19(num); {endif} if (((num < 100) and (num > 19)) or (num = 10)) then begin hspell := copy(IntToStr(num),1,1) + '0'; spell := trans99(StrToInt(hspell)); hspell := copy(IntToStr(num),2,1); spell := spell + ' ' + IntToSpell(StrToInt(hspell)); end; if (num < 1000) and (num > 100) then begin
954
hspell := copy(IntToStr(num),1,1); hundred := IntToSpell(StrToInt(hspell)); hspell := copy(IntToStr(num),2,2); hundred := hundred + ' hundred and ' + IntToSpell(StrToInt(hspell)); spell := hundred; end; if (num < 10000) and (num > 1000) then begin hspell := copy(IntToStr(num),1,1); thousand := IntToSpell(StrToInt(hspell)); hspell := copy(IntToStr(num),2,3); thousand := thousand + ' thousand ' + IntToSpell(StrToInt(hspell)); spell := thousand; end; if (num < 100000) and (num > 10000) then begin hspell := copy(IntToStr(num),1,2); tthousand := IntToSpell(StrToInt(hspell)); hspell := copy(IntToStr(num),3,3); tthousand := tthousand + ' thousand ' + IntToSpell(StrToInt(hspell)); spell := tthousand; end; if (num < 1000000) and (num > 100000) then begin hspell := copy(IntToStr(num),1,3); hthousand := IntToSpell(StrToInt(hspell)); hspell := copy(IntToStr(num),4,3); hthousand := hthousand + ' thousand and ' + IntToSpell(StrToInt(hspell)); spell := hthousand; end; if (num < 10000000) and (num > 1000000) then begin hspell := copy(IntToStr(num),1,1); million := IntToSpell(StrToInt(hspell)); hspell := copy(IntToStr(num),2,6); million := million + ' million and ' + IntToSpell(StrToInt(hspell)); spell := million; end; IntToSpell := spell; end; function TForm1.trans99(num: integer): string; var spell: string; begin case num of 10 : spell := 'ten'; 20 : spell := 'twenty'; 30 : spell := 'thirty'; 40 : spell := 'fourty'; 50 : spell := 'fifty'; 60 : spell := 'sixty'; 70 : spell := 'seventy'; 80 : spell := 'eighty'; 90 : spell := 'ninty'; end; trans99 := spell; end; function TForm1.trans19(num: integer): string;
955
var spell: string; begin case num of 11 : spell := 'eleven'; 12 : spell := 'twelve'; 13 : spell := 'thirteen'; 14 : spell := 'fourteen'; 15 : spell := 'fifteen'; 16 : spell := 'sixteen'; 17 : spell := 'seventeen'; 18 : spell := 'eighteen'; 19 : spell := 'nineteen'; end; trans19 := spell; end; function TForm1.trans9(num: integer): string; var spell : string; begin case num of 1 : spell := 'one'; 2 : spell := 'two'; 3 : spell := 'three'; 4 : spell := 'four'; 5 : spell := 'five'; 6 : spell := 'six'; 7 : spell := 'seven'; 8 : spell := 'eight'; 9 : spell := 'nine'; end; trans9 := spell; end; procedure TForm1.Button1Click(Sender: TObject); var numb: integer; begin spell.text := IntToSpell(StrToInt(num.text)); end;
Remove Unwanted from String from String "Joseph Y. Wong"
Use: NewStr:=RemoveInvalid('
956
"Laurie Bisman"
String Parsing? From: [email protected] This is a unit where I have gathered lots of this type of routines. Some of the function names are in swedish, but maybe you can figure out what they are doing. The one you need is called stringreplaceall which takes three parameters, the string, what to search for and what to replace with and it return the changed string. But beware if you are changing something to something that contains the first. You must do it in two passes or you will end up in an endless loop. So if you have text containing the word Joe and you like all occurances to be changed to Joey you need to first do something like: text := stringreplaceall (text,'Joe','Joeey'); and then text := stringreplaceall (text,'Joeey','Joey'); unit sparfunc; interface uses sysutils,classes; function antaltecken (orgtext,soktext : string) : integer; function beginsWith (text,teststreng : string):boolean; function endsWith (text,teststreng : string):boolean; function hamtastreng (text,strt,slut : string):string; function hamtastrengmellan (text,strt,slut : string):string; function nastadelare (progtext : string):integer; function rtf2sgml (text : string) : string; Function sgml2win(text : String) : String; Function sgml2mac(text : String) : String; Function sgml2rtf(text : string) : String; function sistamening(text : string) : string; function stringnthfield (text,delim : string; vilken : integer) : string; function stringreplace (text,byt,mot : string) : string; function stringreplaceall (text,byt,mot : string) : string; function text2sgml (text : string) : string; procedure SurePath (pathen : string); procedure KopieraFil (infil,utfil : string); function LasInEnTextfil (filnamn : string) : string; implementation function LasInEnTextfil (filnamn : string) : string; var infil : textfile; temptext, filtext : string;
957
begin filtext := ''; //Öppna angiven fil och läs in den try assignfile (infil,filnamn); //Koppla en textfilsvariabel till pathname reset (infil); //Öppna filen while not eof(infil) do begin //Så länge vi inte nått slutet readln (infil,temptext); //Läs in en rad filtext := filtext+temptext; //Lägg den till variabeln SGMLTEXT end; // while finally //slutligen closefile (infil); //Stäng filen end; //try result := filtext; end; procedure KopieraFil (infil,utfil : string); var InStream : TFileStream; OutStream : TFileStream; begin InStream := TFileStream.Create(infil,fmOpenRead); try OutStream := TFileStream.Create(utfil,fmOpenWrite or fmCreate); try OutStream.CopyFrom(InStream,0); finally OutStream.Free; end; finally InStream.Free; end; end; procedure SurePath (pathen : string); var temprad,del1 : string; antal : integer; begin antal := antaltecken (pathen,'\'); if antal<3 then createdir(pathen) else begin if pathen[length(pathen)] <> '\' then pathen := pathen+'\'; pathen := stringreplace(pathen,'\','/'); del1 := copy(pathen,1,pos('\',pathen)); pathen := stringreplace(pathen,del1,''); del1 := stringreplace(del1,'/','\'); createdir (del1); while pathen <> '' do begin temprad := copy(pathen,1,pos('\',pathen)); pathen := stringreplace(pathen,temprad,''); del1 := del1+ temprad; temprad := ''; createdir(del1); end; end; end; function antaltecken (orgtext,soktext : string) : integer; var i,traffar,soklengd : integer; begin traffar := 0; soklengd := length(soktext); for i := 1 to length(orgtext) do
958
begin if soktext = copy(orgtext,i,soklengd) then traffar := traffar +1; end; result := traffar; end; function nastadelare (progtext var i,j : integer; begin i := pos('.',progtext); j := pos('!',progtext); if (j0) then i := j := pos('!',progtext); if (j0) then i := j := pos('?',progtext); if (j0) then i := result := i; end;
: string):integer;
j; j; j;
function stringnthfield (text,delim : string; vilken : integer) : string; var start,slut,i : integer; temptext : string; begin start := 0; if vilken >0 then begin temptext := text; if vilken = 1 then begin start := 1; slut := pos (delim,text); end else begin for i:= 1 to vilken -1 do begin start := pos(delim,temptext)+length(delim); temptext := copy(temptext,start,length(temptext)); end; slut := pos (delim,temptext); end; if start >0 then begin if slut = 0 then slut := length(text); result := copy (temptext,1,slut-1); end else result := text; end else result := text; end; function StringReplaceAll (text,byt,mot : string ) :string; {Funktion för att byta ut alla förekomster av en sträng mot en annan sträng in en sträng. Den konverterade strängen returneras. Om byt finns i mot måste vi gå via en temporär variant!!!} var plats : integer; begin While pos(byt,text) > 0 do begin plats := pos(byt,text);
959
delete (text,plats,length(byt)); insert (mot,text,plats); end; result := text; end; function StringReplace (text,byt,mot : string ) :string; {Funktion för att byta ut den första förekomsten av en sträng mot en annan sträng in en sträng. Den konverterade strängen returneras.} var plats : integer; begin if pos(byt,text) > 0 then begin plats := pos(byt,text); delete (text,plats,length(byt)); insert (mot,text,plats); end; result := text; end; function hamtastreng (text,strt,slut : string):string; {Funktion för att hämta ut en delsträng ur en annan sträng. Om start och slut finns i text så returneras en sträng där start ingår i början och fram till tecknet före slut.} var stplats,slutplats : integer; resultat : string; begin resultat :=''; stplats := pos(strt,text); if stplats >0 then begin text := copy (text,stplats,length(text)); slutplats := pos(slut,text); if slutplats >0 then begin resultat := copy(text,1,slutplats-1); end; end; result := resultat; end; function hamtastrengmellan (text,strt,slut : string):string; {Funktion för att hämta ut en delsträng ur en annan sträng. Om start och slut finns i text så returneras en sträng där start ingår i början och fram till tecknet före slut.} var stplats,slutplats : integer; resultat : string; begin resultat :=''; stplats := pos(strt,text); if stplats >0 then begin text := copy (text,stplats+length(strt),length(text)); slutplats := pos(slut,text); if slutplats >0 then begin resultat := copy(text,1,slutplats-1); end; end; result := resultat; end; function endsWith (text,teststreng : string):boolean;
960
{Kollar om en sträng slutar med en annan sträng. Returnerar true eller false.} var textlngd,testlngd : integer; kollstreng : string; begin testlngd := length(teststreng); textlngd := length (text); if textlngd > testlngd then begin kollstreng := copy (text,(textlngd+1)-testlngd,testlngd); if kollstreng = teststreng then result := true else result := false; end else result := false; end; function beginsWith (text,teststreng : string):boolean; {Funktion för att kolla om text börjar med teststreng. Returnerar true eller false.} var textlngd,testlngd : integer; kollstreng : string; begin testlngd := length(teststreng); textlngd := length (text); if textlngd >= testlngd then begin kollstreng := copy (text,1,testlngd); if kollstreng = teststreng then result := true else result := false; end else result := false; end; function sistamening(text : string) : string; //Funktion för att ta fram sista meningen i en sträng. Söker på !?. var i:integer; begin i :=length(text)-1; while (copy(text,i,1)<> '.') and (copy(text,i,1)<> '!') and (copy(text,i,1)<> '?') do begin dec(i); if i =1 then break end; if i>1 then result := copy(text,i,length(text)) else result := ''; end; Function text2sgml(text : String) : String; {Funktion som byter ut alla ovanliga tecken mot entiteter. Den färdiga texten returneras.} begin text := stringreplaceall (text,'&','##amp;'); text := stringreplaceall (text,'##amp','&');
961
text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall result := text; End;
(text,'å','å'); (text,'Å','Å'); (text,'ä','ä'); (text,'Ä','Ä'); (text,'á','á'); (text,'Á','Á'); (text,'à','à'); (text,'À','À'); (text,'æ','æ'); (text,'Æ','&Aelig;'); (text,'Â','Â'); (text,'â','â'); (text,'ã','ã'); (text,'Ã','Ã'); (text,'ç','ç'); (text,'Ç','Ç'); (text,'é','é'); (text,'É','É'); (text,'ê','ê'); (text,'Ê','Ê'); (text,'ë','ë'); (text,'Ë','Ë'); (text,'è','è'); (text,'È','È'); (text,'î','î'); (text,'Î','Î'); (text,'í','í'); (text,'Í','Í'); (text,'ì','ì'); (text,'Ì','Ì'); (text,'ï','ï'); (text,'Ï','Ï'); (text,'ñ','ñ'); (text,'Ñ','Ñ'); (text,'ö','ö'); (text,'Ö','Ö'); (text,'ò','ò'); (text,'Ò','Ò'); (text,'ó','ó'); (text,'Ó','Ó'); (text,'ø','ø'); (text,'Ø','Ø'); (text,'Ô','Ô'); (text,'ô','ô'); (text,'õ','õ'); (text,'Õ','Õ'); (text,'ü','ü'); (text,'Ü','Ü'); (text,'ú','ú'); (text,'Ú','Ú'); (text,'Ù','Ù'); (text,'ù','ù'); (text,'û','û'); (text,'Û','Û'); (text,'ý','ý'); (text,'Ý','Ý'); (text,'ÿ','ÿ'); (text,'|',' ');
Function sgml2win(text : String) : String; {Funktion som ersätter alla entiteter mot deras tecken i windows. Den färdiga strängen returneras.} begin text := stringreplaceall (text,'á','á');
962
text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall result := text; End;
(text,'Á','Á'); (text,'æ','æ'); (text,'&Aelig;','Æ'); (text,'à','à'); (text,'À','À'); (text,'å','å'); (text,'Å','Å'); (text,'ä','ä'); (text,'Ä','Ä'); (text,'Â' ,'Â'); (text,'â' ,'â'); (text,'ã','ã'); (text,'Ã','Ã'); (text,'ç','ç'); (text,'Ç','Ç'); (text,'é','é'); (text,'É','É'); (text,'è','è'); (text,'È','È'); (text,'ê' ,'ê'); (text,'Ê' ,'Ê'); (text,'ë' ,'ë'); (text,'Ë' ,'Ë'); (text,'î' ,'î'); (text,'Î' ,'Î'); (text,'í','í'); (text,'Í','Í'); (text,'ì','ì'); (text,'Ì','Ì'); (text,'ï' ,'ï'); (text,'Ï' ,'Ï'); (text,'ñ','ñ'); (text,'Ñ','Ñ'); (text,'ò','ò'); (text,'Ò','Ò'); (text,'ó','ó'); (text,'Ó','Ó'); (text,'ö','ö'); (text,'Ö','Ö'); (text,'ø','ø'); (text,'Ø','Ø'); (text,'Ô' ,'Ô'); (text,'ô' ,'ô'); (text,'õ','õ'); (text,'Õ','Õ'); (text,'ü','ü'); (text,'Ü','Ü'); (text,'ú','ú'); (text,'Ú','Ú'); (text,'û' ,'û'); (text,'Û' ,'Û'); (text,'Ù','Ù'); (text,'ù','ù'); (text,'ý',' ý'); (text,'Ý',' Ý'); (text,'ÿ' ,'ÿ'); (text,' ','|'); (text,'&','&');
Function sgml2mac(text : String) : String; {Funktion som ersätter alla entiteter mot deras tecken i mac. Den färdiga strängen returneras.} begin text := stringreplaceall (text,'á',chr(135));
963
text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall text := stringreplaceall result := text; End;
(text,'Á',chr(231)); (text,'æ',chr(190)); (text,'&Aelig;',chr(174)); (text,'à',chr(136)); (text,'À',chr(203)); (text,'å',chr(140)); (text,'Å',chr(129)); (text,'Ä',chr(128)); (text,'ä',chr(138)); (text,'Â' ,chr(229)); (text,'â' ,chr(137)); (text,'ã',chr(139)); (text,'Ã',chr(204)); (text,'ç',chr(141)); (text,'Ç',chr(130)); (text,'é',chr(142)); (text,'É',chr(131)); (text,'è',chr(143)); (text,'È',chr(233)); (text,'ê' ,chr(144)); (text,'Ê' ,chr(230)); (text,'ë' ,chr(145)); (text,'Ë' ,chr(232)); (text,'î' ,chr(148)); (text,'Î' ,chr(235)); (text,'í' ,chr(146)); (text,'Í' ,chr(234)); (text,'ì' ,chr(147)); (text,'Ì' ,chr(237)); (text,'ï' ,chr(149)); (text,'Ï' ,chr(236)); (text,'ñ',chr(150)); (text,'Ñ',chr(132)); (text,'ò',chr(152)); (text,'Ò',chr(241)); (text,'ó',chr(151)); (text,'Ó',chr(238)); (text,'Ô' ,chr(239)); (text,'ô' ,chr(153)); (text,'ø',chr(191)); (text,'Ø',chr(175)); (text,'õ',chr(155)); (text,'Õ',chr(239)); (text,'ö',chr(154)); (text,'Ö',chr(133)); (text,'ü',chr(159)); (text,'Ü',chr(134)); (text,'ú',chr(156)); (text,'Ú',chr(242)); (text,'û' ,chr(158)); (text,'Û' ,chr(243)); (text,'Ù',chr(244)); (text,'ù',chr(157)); (text,'ý','y'); (text,'ÿ' ,chr(216)); (text,'Ÿ' ,chr(217)); (text,' ',' '); (text,'&',chr(38));
Function sgml2rtf(text : string) : String; {Funktion för att byta ut sgml-entiteter mot de koder som gäller i RTF-textrutorna.} begin
964
text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text text
:= := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := := :=
stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall stringreplaceall
(text,'}','#]#'); (text,'{','#[#'); (text,'\','HSALSKCAB'); (text,'HSALSKCAB','\\'); (text,'æ','\'+chr(39)+'c6'); (text,'&Aelig;','\'+chr(39)+'e6'); (text,'á','\'+chr(39)+'e1'); (text,'Á','\'+chr(39)+'c1'); (text,'à','\'+chr(39)+'e0'); (text,'À','\'+chr(39)+'c0'); (text,'å','\'+chr(39)+'e5'); (text,'Å','\'+chr(39)+'c5'); (text,'Â','\'+chr(39)+'c2'); (text,'â','\'+chr(39)+'e2'); (text,'ã','\'+chr(39)+'e3'); (text,'Ã','\'+chr(39)+'c3'); (text,'ä','\'+chr(39)+'e4'); (text,'Ä','\'+chr(39)+'c4'); (text,'ç','\'+chr(39)+'e7'); (text,'Ç','\'+chr(39)+'c7'); (text,'é','\'+chr(39)+'e9'); (text,'É','\'+chr(39)+'c9'); (text,'è','\'+chr(39)+'e8'); (text,'È','\'+chr(39)+'c8'); (text,'ê','\'+chr(39)+'ea'); (text,'Ê','\'+chr(39)+'ca'); (text,'ë','\'+chr(39)+'eb'); (text,'Ë','\'+chr(39)+'cb'); (text,'î','\'+chr(39)+'ee'); (text,'Î','\'+chr(39)+'ce'); (text,'í','\'+chr(39)+'ed'); (text,'Í','\'+chr(39)+'cd'); (text,'ì','\'+chr(39)+'ec'); (text,'Ì','\'+chr(39)+'cc'); (text,'ï' ,'\'+chr(39)+'ef'); (text,'Ï' ,'\'+chr(39)+'cf'); (text,'ñ','\'+chr(39)+'f1'); (text,'Ñ','\'+chr(39)+'d1'); (text,'ö','\'+chr(39)+'f6'); (text,'Ö','\'+chr(39)+'d6'); (text,'ó','\'+chr(39)+'f3'); (text,'Ó','\'+chr(39)+'d3'); (text,'ò','\'+chr(39)+'f2'); (text,'Ò','\'+chr(39)+'d2'); (text,'ø','\'+chr(39)+'f8'); (text,'Ø','\'+chr(39)+'d8'); (text,'Ô','\'+chr(39)+'d4'); (text,'ô','\'+chr(39)+'f4'); (text,'õ','\'+chr(39)+'f5'); (text,'Õ','\'+chr(39)+'d5'); (text,'ú','\'+chr(39)+'fa'); (text,'Ú','\'+chr(39)+'da'); (text,'û','\'+chr(39)+'fb'); (text,'Û','\'+chr(39)+'db'); (text,'Ù','\'+chr(39)+'d9'); (text,'ù','\'+chr(39)+'f9'); (text,'ü','\'+chr(39)+'fc'); (text,'Ü','\'+chr(39)+'dc'); (text,'ý','\'+chr(39)+'fd'); (text,'Ý','\'+chr(39)+'dd'); (text,'ÿ','\'+chr(39)+'ff'); (text,'£','\'+chr(39)+'a3'); (text,'#]#','\}'); (text,'#[#','\{'); (text,' ','|'); (text,'&','&');
965
result := text; End; function rtf2sgml (text : string) : string; {Funktion för att konvertera en RTF-rad till SGML-text.} var temptext : string; start : integer; begin text := stringreplaceall (text,'&','##amp;'); text := stringreplaceall (text,'##amp','&'); text := stringreplaceall (text,'\'+chr(39)+'c6','æ'); text := stringreplaceall (text,'\'+chr(39)+'e6','&Aelig;'); text := stringreplaceall (text,'\'+chr(39)+'e5','å'); text := stringreplaceall (text,'\'+chr(39)+'c5','Å'); text := stringreplaceall (text,'\'+chr(39)+'e4','ä'); text := stringreplaceall (text,'\'+chr(39)+'c4','Ä'); text := stringreplaceall (text,'\'+chr(39)+'e1','á'); text := stringreplaceall (text,'\'+chr(39)+'c1','Á'); text := stringreplaceall (text,'\'+chr(39)+'e0','à'); text := stringreplaceall (text,'\'+chr(39)+'c0','À'); text := stringreplaceall (text,'\'+chr(39)+'c2','Â'); text := stringreplaceall (text,'\'+chr(39)+'e2','â'); text := stringreplaceall (text,'\'+chr(39)+'e3','ã'); text := stringreplaceall (text,'\'+chr(39)+'c3','Ã'); text := stringreplaceall (text,'\'+chr(39)+'e7','ç'); text := stringreplaceall (text,'\'+chr(39)+'c7','Ç'); text := stringreplaceall (text,'\'+chr(39)+'e9','é'); text := stringreplaceall (text,'\'+chr(39)+'c9','É'); text := stringreplaceall (text,'\'+chr(39)+'e8','è'); text := stringreplaceall (text,'\'+chr(39)+'c8','È'); text := stringreplaceall (text,'\'+chr(39)+'ea','ê'); text := stringreplaceall (text,'\'+chr(39)+'ca','Ê'); text := stringreplaceall (text,'\'+chr(39)+'eb','ë'); text := stringreplaceall (text,'\'+chr(39)+'cb','Ë'); text := stringreplaceall (text,'\'+chr(39)+'ee','î'); text := stringreplaceall (text,'\'+chr(39)+'ce','Î'); text := stringreplaceall (text,'\'+chr(39)+'ed','í'); text := stringreplaceall (text,'\'+chr(39)+'cd','Í'); text := stringreplaceall (text,'\'+chr(39)+'ec','ì'); text := stringreplaceall (text,'\'+chr(39)+'cc','Ì'); text := stringreplaceall (text,'\'+chr(39)+'ef','ï'); text := stringreplaceall (text,'\'+chr(39)+'cf','Ï'); text := stringreplaceall (text,'\'+chr(39)+'f1','ñ'); text := stringreplaceall (text,'\'+chr(39)+'d1','Ñ'); text := stringreplaceall (text,'\'+chr(39)+'f3','ó'); text := stringreplaceall (text,'\'+chr(39)+'d3','Ó'); text := stringreplaceall (text,'\'+chr(39)+'f2','ò'); text := stringreplaceall (text,'\'+chr(39)+'d2','Ò'); text := stringreplaceall (text,'\'+chr(39)+'d4','Ô'); text := stringreplaceall (text,'\'+chr(39)+'f4','ô'); text := stringreplaceall (text,'\'+chr(39)+'f5','õ'); text := stringreplaceall (text,'\'+chr(39)+'d5','Õ'); text := stringreplaceall (text,'\'+chr(39)+'f8','ø'); text := stringreplaceall (text,'\'+chr(39)+'d8','Ø'); text := stringreplaceall (text,'\'+chr(39)+'f6','ö'); text := stringreplaceall (text,'\'+chr(39)+'d6','Ö'); text := stringreplaceall (text,'\'+chr(39)+'fc','ü'); text := stringreplaceall (text,'\'+chr(39)+'dc','Ü'); text := stringreplaceall (text,'\'+chr(39)+'fa','ú'); text := stringreplaceall (text,'\'+chr(39)+'da','Ú'); text := stringreplaceall (text,'\'+chr(39)+'fb','û'); text := stringreplaceall (text,'\'+chr(39)+'db','Û'); text := stringreplaceall (text,'\'+chr(39)+'d9','Ù'); text := stringreplaceall (text,'\'+chr(39)+'f9','ù'); text := stringreplaceall (text,'\'+chr(39)+'fd','ý');
966
text := stringreplaceall (text,'\'+chr(39)+'dd','Ý'); text := stringreplaceall (text,'\'+chr(39)+'ff','ÿ'); text := stringreplaceall (text,'|',' '); text := stringreplaceall (text,'\'+chr(39)+'a3','£'); text := stringreplaceall (text,'\}','#]#'); text := stringreplaceall (text,'\{','#[#'); if (beginswith (text, '{\rtf1\')) or (beginswith (text, '{\colortbl\')) then begin result := ''; exit; end; //text := stringreplaceall (text,'{\fonttbl',''); {Skall alltid tas bort} //temptext := hamtastreng (text,'{\rtf1','{\f0');{Skall alltid tas bort} //text := stringreplace (text,temptext,''); //temptext := hamtastreng (text,'{\f0','{\f1');{Skall alltid tas bort} //text := stringreplace (text,temptext,''); //temptext := hamtastreng (text,'{\f1','{\f2');{Skall alltid tas bort} //text := stringreplace (text,temptext,''); //text := stringreplaceall (text,'{\f2\fswiss\fprq2 System;}}','');{Skall alltid tas bort} //text := stringreplaceall (text,'{\colortbl\red0\green0\blue0;}','');{Skall alltid tas bort} {I version 2.01 av Delphi finns inte \cf0 med i RTF-rutan. Tog därför bort det efter \fs16 och la istället en egen tvätt av \cf0.} //temptext := hamtastreng (text,'{\rtf1','\deflang'); //text := stringreplace (text,temptext,''); {Hämta och radera allt från start till deflang} text := stringreplaceall (text,'\cf0',''); temptext := hamtastreng (text,'\deflang','\pard');{Plocka från deflang till pard för att få } text := stringreplace (text,temptext,'');{oavsett vilken lang det är. Norska o svenska är olika} text := stringreplaceall (text,'\ltrpar',''); text := stringreplaceall (text,'\ql',''); text := stringreplaceall (text,'\ltrch',''); {Här skall vi plocka bort fs och flera olika siffror beroende på vilka alternativ vi godkänner.} //text := stringreplaceall (text,'\fs16','');{8 punkter} //text := stringreplaceall (text,'\fs20','');{10 punkter} {Nu städar vi istället bort alla tvåsiffriga fontsize.} while pos ('\fs',text) >0 do begin //application.processmessages; start := pos ('\fs',text); Delete(text,start,5); end; while pos ('\f',text) >0 do begin //application.processmessages; start := pos ('\f',text); Delete(text,start,3); end; text := stringreplaceall (text,'\pard\li200200{\*\pn\pnlvlblt\pnf1\pnindent200{\pntxtb\'+chr(39)+'b7}}\plain ','
'); text := stringreplaceall (text,'\par \plain\b\ul ','
\tab ',text)>0) then begin text := stringreplaceall (text,'\par \tab ','
\tab ','
'); text := stringreplaceall (text,'#]#','}'); text := stringreplaceall (text,'#[#','{'); text := stringreplaceall (text,'\\','\'); if pos('