2009. február 28., szombat

Controlling the Field Display in a TDBGrid


Problem/Question/Abstract:

I am developing a database/intranet package for which I need to use a grid based on a TQuery, but I only need a few of the fields to show up in the grid. The wizard won't let me do it? What am I missing/overlooking?

Answer:

This is a stumbling block for a lot of people. And it's not really due to missing something, it's just due to unfamiliarity with the way the data-aware components work. Think of the way data is displayed in Delphi as a layer cake:

Layer 4 Data-aware Component
Layer 3 DataSource
Layer 2 Field-link (TField)
Layer 1 DataSet

The top layer is the component that displays the data; in your case, the TDBGrid. It's connected to a DataSource component, which in turn is connect to TDataLinks or TFields. These are then connected to the underlying dataset.

You might think that the "visibility" of data is controlled by the DBGrid. Actually, data visibility is controlled at a much lower level, the TField level/Data-link level. I won't go into TDataLink, because that's a bit more on the esoteric side of things, but I will say this: If you don't use TField definitions for your dataset, Delphi will default to use the data links, which means that all your columns will be displayed. This is probably the behavior you're seeing right now.

However, if you define TFields for your dataset component, only the ones you add will be displayed in your grid or, if you've added all fields from the dataset to have TField definitions, only those fields that have their Visible property set to True will have their data displayed.

But how do you do define TFields for a dataset in the first place?

Well, I'm assuming that you have a TDBGrid, a TDataSource and a TQuery dropped onto your form. I'm further assuming that you've set up your query. To get at the TFields, all you have to do is double-click on the TQuery component. This will bring up the Fields Editor. When you do this, it'll be blank, but don't worry. Press Ctrl-A to add fields, and an "Add Fields" dialog box will pop up, containing a list of available fields that can have TField definitions attached to them. By default, all of the fields are selected. Press OK if you want all of them, or select only the fields you want to display.

Once you've selected your fields, you'll see them listed in the fields editor. Click on one of the fields. Look in the Object Inspector. You'll see a bunch of properties associated with the field. In particular, look at the Visible property. By default, it will be set to true. If you don't want the data to be displayed, set the property to false.

That's it in a nutshell. Refer to the online help for a more detailed explanation. Also, the Database Application Developer's Guide that came with your Delphi manual set will provide you with more robust information than I've given here.

2009. február 27., péntek

How to extract an audio stream of an *.avi file


Problem/Question/Abstract:

How can I extract an audio stream of an *.avi file?

Answer:

Note, you must use VfW.pas. You may find this file somewhere in the internet or simply write me an email. In addition, I was not able to test this code under Delphi 5.x or lower. Only Delphi 6.x. Please send me a piece of information, if you can test this for me. Thanks


uses VfW;

function CallBack(i: int): BOOL; pascal;
begin
  Label1.Caption := IntToStr(i) + '%';
  Application.ProcessMessages;
  Result := False;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  PFile: IAviFile;
  PAvi: IAviStream;
  plpOptions: PAviCompressOptions;
begin
  AviFileInit;
  if AviFileOpen(PFile, 'C:\test.avi', 0, nil) <> 0 then
  begin
    ShowMessage('Couldn'' t open * .avi!');
    Exit;
  end;
  if AviFileGetStream(PFile, PAvi, StreamTypeAudio, 0) <> 0 then
  begin
    ShowMessage('Couldn' t load audio stream!');
      AviFileExit;
      Exit;
  end;
  if AviSaveV('C:\test.wav', nil, @CallBack, 1, PAvi, plpOptions) <> 0 then
  begin
    ShowMessage('Couldn'' t save * .wav - file!');
    AviStreamRelease(PAvi);
    AviFileExit;
    Exit;
  end;
  AviStreamRelease(PAvi);
  AviFileExit;
end;

2009. február 26., csütörtök

How to determine how many lines a TMemo is capable of showing


Problem/Question/Abstract:

How to determine how many lines a TMemo is capable of showing

Answer:

Here is the short and elegant way that works (most of the time):

function TForm1.MemoLinesShowing(memo: TMemo): integer;
var
  R: TRect;
begin
  Memo.Perform(EM_GETRECT, 0, Longint(@R));
  Result := (R.Bottom - R.Top) div Canvas.TextHeight('XXX');
end;

The problem with this code is that the TForm and the TMemo must both be using the same font. If the fonts are different, then the calculations are not accurate.

You have to retrieve the font height by selecting it into a device context. The reason you cannot use the font Height provided by Delphi is because Delphi caches the font infomation but doesn't acutally select the font into the DC (canvas) until it is actually going to draw something. This occurs in the painting event of the memo.

To get around this problem, you can get the memo's device context using the Windows API and get the font information from the device context to calculate the text height. The function below illustrates this process:

function TForm1.MemoLinesShowingLong(Memo: TMemo): integer;
var
  Oldfont: HFont; {the old font}
  DC: THandle; {Device context handle}
  i: integer; {loop variable}
  Tm: TTextMetric; {text metric structure}
  TheRect: TRect;
begin
  DC := GetDC(Memo.Handle); {Get the memo's device context}
  try
    {Select the memo's font}
    OldFont := SelectObject(DC, Memo.Font.Handle);
    try
      GetTextMetrics(DC, Tm); {Get the text metric info}
      Memo.Perform(EM_GETRECT, 0, longint(@TheRect));
      Result := (TheRect.Bottom - TheRect.Top) div (Tm.tmHeight +
        Tm.tmExternalLeading);
    finally
      SelectObject(DC, Oldfont); {Select the old font}
    end;
  finally
    ReleaseDC(Memo.Handle, DC); {Release the device context}
  end;
end;

2009. február 25., szerda

How can I find out / set the backgroundcolor of the site in TWebbrowser?


Problem/Question/Abstract:

How can I find out / set the backgroundcolor of the site in TWebbrowser?

Answer:

//You need a TWebbrowser and 3 TButtons

// First load a page

procedure TForm1.Button1Click(Sender: TObject);
begin
  WebBrowser1.Navigate('http://dkb.kastu.lt');
end;

// Show the background color

procedure TForm1.Button2Click(Sender: TObject);
begin
  ShowMessage(WebBrowser1.OleObject.Document.bgColor);
end;

// Set the background color

procedure TForm1.Button3Click(Sender: TObject);
begin
  WebBrowser1.OleObject.Document.bgColor := '#000000';
end;

2009. február 24., kedd

How can I check, whether the side in TWebbrowser is on local HD?


Problem/Question/Abstract:

How can I check, whether the side in TWebbrowser is on local HD?

Answer:

// You need: A TWebbrowser, TButton, TLabel

procedure TForm1.Button1Click(Sender: TObject);
begin
  Webbrowser1.Navigate('file:///c:/test.txt');
end;

procedure TForm1.WebBrowser1DocumentComplete(Sender: TObject;
  const pDisp: IDispatch; var URL: OleVariant);
begin
  if Webbrowser1.Oleobject.Document.Location.Protocol = 'file:' then
  begin
    label1.Caption := 'The file is on a local drive!';
  end;
end;

2009. február 23., hétfő

How to capture the WM_CUT, WM_CLEAR and WM_PASTE messages in a TComboBox


Problem/Question/Abstract:

By hooking into the WndProc I can listen to the messages generated for TWinControls. I am looking for the WN_CUT, WM_PASTE and WM_CLEAR so that I can lock a record before the cut, paste or clear change occurs. The TComboBox does not generate these events when the Style is csDropDown. Any way to capture these events?

Answer:

Override the ComboWndProc method �n a class derived from TCombobox. Example:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TCombobox = class(stdctrls.TComboBox)
  protected
    procedure ComboWndProc(var Message: TMessage; ComboWnd: HWnd;
      ComboProc: Pointer); override;
  end;
  TForm1 = class(TForm)
    ComboBox1: TComboBox;
    Memo1: TMemo;
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

{ TCombobox }

procedure Report(const S: string);
begin
  form1.memo1.lines.add(S);
end;

procedure TCombobox.ComboWndProc(var Message: TMessage; ComboWnd: HWnd;
  ComboProc: Pointer);
begin
  inherited;
  case message.Msg of
    WM_CUT: Report('CUT');
    WM_PASTE: Report('PASTE');
    WM_CLEAR: Report('CLEAR');
  end;
end;

end.

2009. február 22., vasárnap

How to use a blob field to hold a record structure


Problem/Question/Abstract:

I want to use a blob field to hold a record structure. ErrsTblErrs_Data has a blobType of "ftBlob"

type
  LogiRec = record
    LOGI_REG_NUM: array[1..9] of Char;
    LOGI_ENT_DATE: array[1..10] of Char;
    LOGI_Batch_num: array[1..6] of Char;
    LOGI_Trac_num: array[1..6] of Char;
    {...}
  end;

var
  Buffer: array[0..600] of char;
  Logi: LogiRec absolute Buffer;
  {...}
  {This works to get the data in}
  ErrsTblErrs_Data.asstring := Buffer;
  {This doesn't works to get the data out}
  Buffer := ErrsTblErrs_Data.asstring;

Is there a more elegant way of doing this?

Answer:

Try something like this to record the data:

var
  b: TBlobStream;
begin
  Table1.Append;
  b := TBlobStream.Create(Table1MyBlobField, bmWrite);
  try
    b.Write(MyRecord, sizeof(MyRecord));
  finally
    b.Free;
  end;
  Table1.Post;
end;

To read back:

var
  b: TBlobStream;
begin
  Table1.First;
  repeat
    begin
      b := TBlobStream.Create(Table1MyBlobField, bmRead);
      try
        b.seek(0, 0);
        b.Read(MyRecord, sizeof(MyRecord));
      finally
        b.Free;
      end;
    end;
  until Table1.EOF;
end;

2009. február 21., szombat

Getting file system information


Problem/Question/Abstract:

How do I use WinAPI functions like GetVolumeInformation to get information about my system?

Answer:

Now and then a user comes up with a question that intrigues me so much that I end up creating a full-blown application as well as article around the question. Such is the case with this article.

A user sent me an e-mail asking how to employ the WinAPI call GetVolumeInformation. I knew about this function, but had never really used it. But when I referenced the function in the help file, which I was supposed to use as material to answer the original question, I ended up following the various branches of hyperlinks off of the topic, struck by the thought that with a couple of other functions, I could create a program that could provide me with practically instant information about my file system. And that's what I did. Figure 1 shows the program in action.



The information that's displayed is the result of three WinAPI calls. Table 1 describes what was used and for what purpose:

Function
Usage
GetVolumeInformation
This handy function fills some variables passed as formal parameters to give you information such as the disk volume name, the file system employed, and various things the drive supports such as compression and long file names.
GetDiskFreeSpace
GetDiskFreeSpace fills four variables passed as formal parameters which provide information on the following: Sectors/Cluster, Bytes/Cluster, Free Clusters, and Total Clusters. A little arithmetic will get you the total and free number of megabytes on the hard disk.
GetDriveType
This returns a DWORD (4-byte assembler symbol that can be read as a short but is typically used as a bit array) that indicates the type of drive of the drive passed to it as a formal parameter.


These functions provide the most common information about your file system and the drive for which you want to get information. But since they're WinAPI calls, it's not quite readily apparent how to use them.

The WinAPI Dilemma

If you've followed this column for a while, and have read my articles dealing with WinAPI calls, you know how I feel about it. I have a love-hate relationship with the WinAPI. I love it because there's so much buried within it, yet I hate it, because it's big and employing its functions is not the easiest thing because of the type conversions and C language conventions. But for low-level Windows stuff, the WinAPI is the way to go. Remember, the VCL is pretty much one big wrapper for the WinAPI.

I've said the following before as well: Be prepared to have some references handy when using the WinAPI. Specifically, these are the WINDOWS.PAS WinAPI wrapper file and the online help. The help file is very important because it will give you explanations of the various formal parameters and return values (if any) for the function. It will also tell you what structures you might need to initialize prior to calling the function. You'll want to have the WINDOWS.PAS file on hand because when you look up function in the help file, you'll notice that it is described in C/C++ conventions. What you have to do is locate the function in WINDOWS.PAS and see what the Object Pascal/Delphi convention is for calling the function. For example, a WinAPI function may define one of its formal parameters as type LPSTR or LPCSTR. If you didn't have WINDOWS.PAS on hand, you'd never know these types translate to PChar. That said, let's look at how I've employed the functions I've used for getting file system information.

Getting at the Information

I used a single method in the main form of the program to get the volume and file system information to display in the various labels and the memo on the main form. Here's a listing of the method; below it, we'll discuss particulars.

procedure TForm1.GetVolInfo;
var
  //Volume Information Variables
  nVNameSer: PDWORD;
  drv: string;
  pVolName: PChar;
  FSSysFlags,
    maxCmpLen: DWord;
  I: Integer;
  pFSBuf: PChar;

  //Drive Information Variables;
  dType: TDrvType;
  SectPerCls,
    BytesPerCls,
    FreeCls,
    TotCls: DWord;

begin
  //initialize vars
  drv := DriveComboBox1.Drive + ':\';
  GetMem(pVolName, MAX_PATH);
  GetMem(pFSBuf, MAX_PATH);
  GetMem(nVNameSer, MAX_PATH);

  //Do some preliminary preparation stuff
  Memo1.Lines.Clear;

  //Now, get the volume information
  GetVolumeInformation(PChar(drv), pVolName, MAX_PATH, nVNameSer,
    maxCmpLen, FSSysFlags, pFSBuf, MAX_PATH);

  //Get descriptions for File System Flags
  for I := 0 to 5 do
  begin
    //do an AND bitwise operation to see if I is in the mask
    if ((FSSysFlags and I) <> 0) then
      case I of
        Ord(fsCaseIsPreserved):
          Memo1.Lines.Add('...preserves case with file names');
        Ord(fsCaseSensitive):
          Memo1.Lines.Add('...supports case sensitive file names');
        Ord(fsUnicodeStoredOnDisk):
          Memo1.Lines.Add('...stores Unicodes as on disk');
        Ord(fsPersistentAcls):
          Memo1.Lines.Add('...preserves and enforces ACLs');
        Ord(fsFileCompression):
          Memo1.Lines.Add('...supports file-based compression');
        Ord(fsVolumeIsCompressed):
          Memo1.Lines.Add('...resides on a compressed volume');
      end;
  end;

  //determine if system supports long file names
  if (maxCmpLen > 8.3) then
    Memo1.Lines.Add('...supports long file names');

  Label6.Caption := StrPas(pVolName);
  Label3.Caption := IntToStr(nVNameSer^);
  Label4.Caption := StrPas(pFSBuf);

  //Get the Drive Type information
  dType := TDrvType(GetDriveType(PChar(drv)));
  case dType of
    dtNotDetermined: Label10.Caption := 'Unable to Determine';
    dtNonExistent: Label10.Caption := 'Does not exist';
    dtRemoveable: Label10.Caption := 'Removable Drive (Floppy)';
    dtFixed: Label10.Caption := 'Fixed Disk';
    dtRemote: Label10.Caption := 'Remote or Network Drive';
    dtCDROM: Label10.Caption := 'CD-ROM Drive';
    dtRamDrive: Label10.Caption := 'RAM Drive';
  end;

  //Get the total and free space on selected drive and
  //display in MBs
  GetDiskFreeSpace(PChar(drv), SectPerCls, BytesPerCls, FreeCls, TotCls);
  Label11.Caption := FormatFloat('0.00', (SectPerCls * BytesPerCls *
    TotCls) / 1000000) + ' MB';
  Label12.Caption := FormatFloat('0.00', (SectPerCls * BytesPerCls *
    FreeCls) / 1000000) + ' MB';

  //Get rid of pointer resources
  FreeMem(pVolName, MAX_PATH);
  FreeMem(pFSBuf, MAX_PATH);
  FreeMem(nVNameSer, MAX_PATH);
end;

The first thing I did in the method was to initialize the variables that required initialization before usage. Whenever you use pointers of any type, they must be initialized and the system must be notified of how much memory will need to be reserved. For PChar this is a simple process of calling GetMem. After you've finished using a pointer, make sure to call FreeMem to release the resources used by the pointer.

Continuing on, after I initialized the variables, I made the call to GetVolumeInformation.  WINDOWS.PAS declares the function as follows:

function GetVolumeInformation(lpRootPathName: PChar;
  lpVolumeNameBuffer: PChar;
  nVolumeNameSize: DWORD;
  lpVolumeSerialNumber: PDWORD;
  var lpMaximumComponentLength,
  lpFileSystemFlags: DWORD;
  lpFileSystemNameBuffer: PChar;
  nFileSystemNameSize: DWORD):
  BOOL; stdcall;

As you can see there are quite a few formal parameters you have to fill in order to make this call. The first parameter, lpRootName, is a null-terminated string that holds the root directory of the drive or volume you want to get information on. As you can see in the code above, I've typecasted the value of the drv string variable, which is merely the value of the Drive property of a TDriveComboBox, plus a semi-colon and backslash, when making the call to GetVolumeInformation.

The second and third parameters have to do with the volume name. lpVolumeNameBuffer is a PChar that will be loaded with the name of the volume, and nVolumeNameSize is the size of the buffer. These two formal parameters are similar to the lpFileSystemNameBuffer and nFileSystemNameSize parameters in that they describe a destination buffer along with its size. Notice that I initialized both pointers' sizes to the numeric constant MAX_PATH and also used this value for nVolumeNameSize and nFileSystemNameSize. MAX_PATH's value is 260, which is probably overkill, but I wanted to use a common, globally defined system constant for initializing the size as opposed to a hard-coded value. In fact, you'll often find the Win32 help file stating that you should use a predefined constant to avoid differences in later versions of the compiler.

lpVolumeSerialNumber is the serial number of the disk on which the volume resides. This is a pointer to a DWORD, so you'll notice that I also initialized memory for it in addition to lpVolumeNameBuffer and lpFileSystemName with GetMem. lpMaximumComponentLength is a parameter you pass by reference; hence the var signfication associated with it. The number that gets returned is maximum file length of a file component, the characters between the backslashes of a path listing.

Finally, lpFileSystemFlags is a DWORD (four-byte Assembler type that is for all intents and purposes the same as a Short) that acts as a bit mask for specific system information. The way that works is there are predefined constants associated in the set of File System Flags. To determine if a particular flag is set in the bit mask, you do a bitwise AND against the flag value's bits. If the return value is one, then the bit is set and the flag is true. There are six file system flags. So in the code, I perform a loop to test each bit position. If a bit is set, I display some text in a memo box. Study the code above to see how I do this. In a nutshell, each successive bit represents a certain file system flag as defined in the online help. If a bit is set, I basically translate this into its associated definition and display it. (BTW, a big thanks goes to Steve Schafer of TeamBorland on CompuServe for his help with the bit mask operations.)

So...

The calls to GetDriveType and GetDiskFreeSpace were far easier to make than GetVolumeInformation. With respect to the call to GetDriveType, notice that in the code listing above I enclosed it in a typecast of TDrvType. This was a custom enumerated type that I declared that held the definitions of the possible drive types that could be encountered when using this function. By performing the typecast, I ensure that the return type, which is a DWORD, will conform to one of my descriptors.

GetDiskFreeSpace was even easier to call than any of the other functions employed by my application. All you do with it is pass the root directory name and four DWORD variables representing the following: Sectors/Cluster, Bytes/Cluster, Free Clusters, and Total Clusters, respectively. Then with a little arithmetic (as shown above) you can get the total and free space available on current volume. That's a snap.

Will you ever use these functions? Probably not very often. However, for getting some quick information about your file system, these are the functions you use. Yes, the Windows API is a bit cumbersome, but hey! this is our environment of choice. And my hunch is, the more you know about it, the better your understanding will be, and the more effective you'll be at writing Windows applications.

Note:There's a demo application available for this Articel

2009. február 20., péntek

How to remove blocks of text


Problem/Question/Abstract:

Never needed to remove blocks of text delimited by words or place holders from a text? Or comments, included in brackets, from a string? Here is a useful function that does that.

Answer:

Just use this function to remove text between round brackets, or specify a different start/end placeholder to remove what you nees.

function RemoveBlocks(Value: string; BeginWidth: string = '('; EndWith: string = ')'):
  string;
var
  BeginPoint: Integer;
  EndPoint: Integer;
begin
  BeginPoint := Pos(BeginWith, Value);
  EndPoint := Pos(EndWith, Value);
  while ((BeginPoint > 0) and (EndPoint > BeginPoint)) do
  begin
    Delete(Value, BeginPoint, (EndPoint - BeginPoint + 1));
    BeginPoint := Pos(BeginWith, Value);
    EndPoint := Pos(EndWith, Value);
  end;
  Result := Value;
end;

For example, you can remove all paragraphs in a HTML source just using this:

MyHTML := RemoveBlocks(MyHTML, '<', '>');

You can also improve the function by adding a "case-insensitive" function with something like this:

function RemoveBlocks(Value: string; BeginWidth: string = '('; EndWith: string = ')'):
  string;
var
  BeginPoint: Integer;
  EndPoint: Integer;
begin
  BeginWith := LowerCase(BeginWith);
  EndWith := LowerCase(EndWith);
  BeginPoint := Pos(BeginWith, LowerCase(Value));
  EndPoint := Pos(EndWith, LowerCase(Value));
  while ((BeginPoint > 0) and (EndPoint > BeginPoint)) do
  begin
    Delete(Value, BeginPoint, (EndPoint - BeginPoint + 1));
    BeginPoint := Pos(BeginWith, LowerCase(Value));
    EndPoint := Pos(EndWith, LowerCase(Value));
  end;
  Result := Value;
end;

2009. február 19., csütörtök

Display text in any angle


Problem/Question/Abstract:

Display text in any angle

Answer:

The following works only with TrueType fonts:


var
  LogFont: TLogFont;
  ...
    GetObject(Canvas.Font.Handle, SizeOf(TLogFont), @LogFont);
  // in 1/10 degrees, 450 = 45 degrees
  LogFont.lfEscapement := Angle * 10;
  Canvas.Font.Handle := CreateFontIndirect(LogFont);

2009. február 18., szerda

Jump to a certain key in Regedit


Problem/Question/Abstract:

How to jump to a certain key in Regedit?

Answer:

unit Unit1;

interface

uses
  Windows, Messages, Classes, Controls, Forms, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
    procedure JumpToKey(Key: string);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

uses
  ShellAPI;

procedure TForm1.JumpToKey(Key: string);
var
  i, n: Integer;
  hWin: HWND;
  ExecInfo: ShellExecuteInfoA;
begin
  hWin := FindWindowA(PChar('RegEdit_RegEdit'), nil);
  if hWin = 0 then
    {if Regedit doesn't run then we launch it}
  begin
    FillChar(ExecInfo, 60, #0);
    with ExecInfo do
    begin
      cbSize := 60;
      fMask := SEE_MASK_NOCLOSEPROCESS;
      lpVerb := PChar('open');
      lpFile := PChar('regedit.exe');
      nShow := 1;
    end;
    ShellExecuteExA(@ExecInfo);
    WaitForInputIdle(ExecInfo.hProcess, 200);
    hWin := FindWindowA(PChar('RegEdit_RegEdit'), nil);
  end;
  ShowWindow(hWin, SW_SHOWNORMAL);
  hWin := FindWindowExA(hWin, 0, PChar('SysTreeView32'), nil);
  SetForegroundWindow(hWin);
  i := 30;
  repeat
    SendMessageA(hWin, WM_KEYDOWN, VK_LEFT, 0);
    Dec(i);
  until i = 0;
  Sleep(500);
  SendMessageA(hWin, WM_KEYDOWN, VK_RIGHT, 0);
  Sleep(500);
  i := 1;
  n := Length(Key);
  repeat
    if Key[i] = '\' then
    begin
      SendMessageA(hWin, WM_KEYDOWN, VK_RIGHT, 0);
      Sleep(500);
    end
    else
      SendMessageA(hWin, WM_CHAR, Integer(Key[i]), 0);
    i := i + 1;
  until i = n;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  JumpToKey('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Internet Explorer');
end;

end.

2009. február 17., kedd

Create a form using Win API only


Problem/Question/Abstract:

How can I create a form without using the Forms unit?

Answer:

program InputAPI;

uses
  Windows, Messages;

var
  WinClass: TWndClassA;
  Inst, Handle, Button1, Button2: Integer;
  Msg: TMsg;
  hFont: Integer;

  { Custom WindowProc function }

function WindowProc(hWnd, uMsg, wParam, lParam: Integer): Integer; stdcall;
begin
  Result := DefWindowProc(hWnd, uMsg, wParam, lParam);
  { Checks for messages }
  if uMsg = WM_DESTROY then
    Halt;
end;

begin
  { Register Custom WndClass }
  Inst := hInstance;
  with WinClass do
  begin
    style := CS_CLASSDC or CS_PARENTDC;
    lpfnWndProc := @WindowProc;
    hInstance := Inst;
    hbrBackground := color_btnface + 1;
    lpszClassname := 'Test';
    hCursor := LoadCursor(0, IDC_ARROW);
  end;
  RegisterClass(WinClass);
  { Create Main Window }
  Handle := CreateWindowEx(WS_EX_WINDOWEDGE or WS_EX_CONTROLPARENT, 'Test',
    'TestWindow', WS_VISIBLE or WS_CAPTION or WS_SYSMENU, 300, 200, 300, 100,
    0, 0, Inst, nil);
  { Create a button }
  Button1 := CreateWindow('Button', 'Ok', WS_VISIBLE or WS_CHILD or WS_TABSTOP or
    BS_PUSHLIKE or BS_TEXT, 50, 20, 75, 25, handle, 0, Inst, nil);
  Button2 := CreateWindow('Button', 'Cancel', WS_VISIBLE or WS_CHILD or WS_TABSTOP or
    BS_PUSHLIKE or BS_TEXT, 150, 20, 75, 25, handle, 0, Inst, nil);
  { Create Font Handle }
  hFont := CreateFont(-15, 0, 0, 0, 400, 0, 0, 0, DEFAULT_CHARSET, OUT_DEFAULT_PRECIS,
    CLIP_DEFAULT_PRECIS, DEFAULT_QUALITY, DEFAULT_PITCH or FF_DONTCARE,
    'MS Sans Serif');
  { Change fonts }
  if hFont <> 0 then
  begin
    SendMessage(Button1, WM_SETFONT, hFont, 0);
    SendMessage(Button2, WM_SETFONT, hFont, 0);
  end;
  SetFocus(Button1);
  UpdateWindow(Handle);
  { Message Loop }
  while (GetMessage(Msg, 0, 0, 0)) do
    if not IsDialogMessage(handle, msg) then
    begin
      TranslateMessage(msg);
      DispatchMessage(msg);
    end;
end.

2009. február 16., hétfő

How to assign all property values of one class to another instance of the same class


Problem/Question/Abstract:

How can I assign all property values (or if it&#8217;s not possible only published property values, or some of them) of one class (TComponent) to another instance of the same class? What I want to do is:

MyComponent1. {property1} := MyComponent2. {property1};
{...}
MyComponent2. {propertyN} := MyComponent2. {propertyN};

Is there a better and shorter way to do this? I tried this: MyComponent1 := MyComponent2; But it doesn&#8217;t work. Why not? Can I point to the second component ?

Answer:

Solve 1:

MyComponent2 and MyComponent1 are pointers to your components, and this kind of assigment leads to MyComponent1 pointing to MyComponent2. But it will not copy its property values.

A better way is to override the assign method of your control, do all property assignment there and call it when you need to copy component attributes. Here's example:

procedure TMyComponent.Assign(Source: TPersistent);
begin
  if Source is TMyComponent then
  begin
    property1 := TMyComponent(Source).property1;
    { ... }
  end
  else
    inherited Assign(Source);
end;

To assign properties you'll need to set this line in the code:

MyComponent1.Assign(MyComponent2);


Solve 2:

procedure EqualClassProperties(AClass1, AClass2: TObject);
var
  PropList: PPropList;
  ClassTypeInfo: PTypeInfo;
  ClassTypeData: PTypeData;
  i: integer;
  NumProps: Integer;
  APersistent: TPersistent;
begin
  if AClass1.ClassInfo <> AClass2.ClassInfo then
    exit;
  ClassTypeInfo := AClass1.ClassInfo;
  ClassTypeData := GetTypeData(ClassTypeInfo);
  if ClassTypeData.PropCount <> 0 then
  begin
    GetMem(PropList, SizeOf(PPropInfo) * ClassTypeData.PropCount);
    try
      GetPropInfos(AClass1.ClassInfo, PropList);
      for i := 0 to ClassTypeData.PropCount - 1 do
        if not (PropList[i]^.PropType^.Kind = tkMethod) then
          {if Class1,2 is TControl/TWinControl on same form, its names must be unique}
          if PropList[i]^.Name <> 'Name' then
            if (PropList[i]^.PropType^.Kind = tkClass) then
            begin
              APersistent := TPersistent(GetObjectProp(AClass1, PropList[i]^.Name,
                TPersistent));
              if APersistent <> nil then
                APersistent.Assign(TPersistent(GetObjectProp(AClass2,
                  PropList[i]^.Name, TPersistent)))
            end
            else
              SetPropValue(AClass1, PropList[i]^.Name, GetPropValue(AClass2,
                PropList[i]^.Name));
    finally
      FreeMem(PropList, SizeOf(PPropInfo) * ClassTypeData.PropCount);
    end;
  end;
end;

Note that this code skips object properties inherited other than TPersistent.

2009. február 15., vasárnap

Changing Position of Current Played Track in TMediaPlayer


Problem/Question/Abstract:

Difficulties in moving forward/backward (changing position) the current played track in TMediaPlayer ??

Answer:

To change the current position of current playing track, you just need to take the usefull (advance) of two event: 1) onTimer of TTimer and 2) onChange of TScrollbar. For full code, read below.

Here are the codes:

procedure TForm1.Button1Click(Sender: TObject);
begin
  if (OpenDialog1.Execute) then
  begin
    Timer1.Enabled := false;
    MediaPlayer1.FileName := OpenDialog1.FileName;
    MediaPlayer1.Open;
    ScrollBar1.Max := MediaPlayer1.Length;
    ScrollBar1.Position := 0;
    Timer1.Enabled := true;
  end;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  ScrollBar1.OnChange := nil; // disable the event handler
  ScrollBar1.Position := MediaPlayer1.Position;
  ScrollBar1.OnChange := ScrollBar1Change; // enable it again
end;

procedure TForm1.ScrollBar1Change(Sender: TObject);
begin
  MediaPlayer1.Pause;
  MediaPlayer1.Position := ScrollBar1.Position;
  MediaPlayer1.Play;
end;

First thing you must do here is initiate the MAX range of TScrollBar with the length of the current song track (look at Button1Click above).

Then add onTimer event code like above, and so the onChange event of TScrollBar.
The key is you must set TMediaPlayer's position with your selected scroolbar position for each of onTimer happen. Do this by calling onChange event of TScrollBar in onTimer event of TTimer.

2009. február 14., szombat

Changing the screen resolution


Problem/Question/Abstract:

How can I change the screen resolution?

Answer:

To change the screen resolution you can use the following function which is a wrapper for the Windows API ChangeDisplaySettings. The function takes the desired width and height as parameters and returns
the return value of ChangeDisplaySettings (see the documentation for more datails).

function SetScreenResolution(Width, Height: integer): Longint;
var
  DeviceMode: TDeviceMode;
begin
  with DeviceMode do
  begin
    dmSize := SizeOf(TDeviceMode);
    dmPelsWidth := Width;
    dmPelsHeight := Height;
    dmFields := DM_PELSWIDTH or DM_PELSHEIGHT;
  end;
  Result := ChangeDisplaySettings(DeviceMode, CDS_UPDATEREGISTRY);
end;

You can use ChangeDisplaySettings to change other properties of the display like the color depth and the display frequency.

Sample call

In the following example first we get the current screen resolution before setting it to 800x600, and then we restore it calling SetScreenResolution again.

var
  OldWidth, OldHeight: integer;

procedure TForm1.Button1Click(Sender: TObject);
begin
  OldWidth := GetSystemMetrics(SM_CXSCREEN);
  OldHeight := GetSystemMetrics(SM_CYSCREEN);
  SetScreenResolution(800, 600);
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
  SetScreenResolution(OldWidth, OldHeight);
end;

Copyright (c) 2001 Ernesto De Spirito
Visit: http://www.latiumsoftware.com/delphi-newsletter.php

2009. február 13., péntek

How to find a window by its (partial) title and close it (2)


Problem/Question/Abstract:

I am trying to figure out how I can get the handle of the main window of another application so I can terminate that program. I simply need to terminate one application from another application.

Answer:

This Code waits x seconds for a certain window to appear, tries to put it on top and send keystrokes to it. Pay special attention to the FindWindow Command. The Caption is the "EXACT" text in the window title bar. To do a partial match you have to have Windows ENUMERATE all it's open windows, then test the list that comes back.

function TMainForm.WaitForWindowAndType(WindowCaption, TextToSend: string;
  SecondsToWait: Cardinal): boolean;
var
  h: Hwnd;
  i: cardinal;
  vk: word;
begin
  i := SecondsToWait;
  repeat
    Application.ProcessMessages;
    sleep(1000);
    h := FindWindow(nil, pchar(WindowCaption));
    dec(i);
  until
    (i < 1) or (h > 0);
  result := not (h < 1);
  if h < 1 then
  begin
    {do nothing}
  end
  else
  begin
    memo1.Lines.Add(format('Found %s, after %d seconds.', [WindowCaption,
                 SecondsToWait - i]));
    sendmessage(h, WM_ACTIVATE, 0, 0);
    sendmessage(h, WM_SETFOCUS, 0, 0);
    i := 1;
    while integer(i) <= length(TextToSend) do
    begin
      if texttosend[i] = '@' then
      begin
        inc(i);
        vk := VkKeyScan(texttosend[i]);
        keybd_event(VK_MENU, MapVirtualKey(VK_MENU, 0), 0, 0); {this is the ALT key}
        keybd_event(vk, MapVirtualKey(VK, 0), 0, 0); {ALT Letter}
        keybd_event(vk, MapVirtualKey(VK, 0), KEYEVENTF_KEYUP, 0); {Letter UP}
        keybd_event(VK_MENU, MapVirtualKey(VK_MENU, 0), KEYEVENTF_KEYUP, 0); {ALT UP}
      end
      else
      begin
        vk := VkKeyScan(texttosend[i]);
        if vk shr 8 = 1 then
        begin
          keybd_event(VK_SHIFT, 0, 0, 0);
          keybd_event(vk, 0, 0, 0);
          keybd_event(vk, 0, KEYEVENTF_KEYUP, 0);
          keybd_event(VK_SHIFT, 0, KEYEVENTF_KEYUP, 0);
        end
        else
        begin
          keybd_event(vk, 0, 0, 0);
          keybd_event(vk, 0, KEYEVENTF_KEYUP, 0);
        end;
      end;
      sleep(100);
      inc(i);
    end;
  end;
end;

2009. február 12., csütörtök

Missing unit 'Proxies.pas'


Problem/Question/Abstract:

If your application or expert uses designtime information, you have to replace
uses DsgnIntf;
with
uses DesignIntf, DesignEditors;
But then you will run into an error message 'Cannot find unit Proxies.pas'

Answer:

The solution is to add DesignIde.dcp to your list of required packages.

You will have to ensure that the run-time package does not require the design-time package(s). This change in Delphi 6 enforces Borland's licence restrictions on designtime editors more strongly, which have been in the license documents since Delphi 3, I believe.

2009. február 11., szerda

Detect your own IP Address


Problem/Question/Abstract:

Detect your own IP Address

Answer:

uses
  WinSock; // type PHostEnt

function My_IP_Address: longint;
var
  buf: array[0..255] of char;
  RemoteHost: PHostEnt;
begin
  Winsock.GetHostName(@buf, 255);
  RemoteHost := Winsock.GetHostByName(buf);
  if RemoteHost = nil then
    My_IP_Address := winsock.htonl($07000001) { 127.0.0.1 }
  else
    My_IP_Address := longint(pointer(RemoteHost^.h_addr_list^)^);
  Result := Winsock.ntohl(Result);
end;

2009. február 9., hétfő

Extract and display version info from files


Problem/Question/Abstract:

Extract and display version info from files

Answer:

This routine shows how to retrieve version information from the Windows resources and displays it with a ShowMessage box:

procedure TForm1.GetVersionInfo;
const
  n_Info = 10;
  InfoStr: array[1..n_Info] of string =
  ('CompanyName', 'FileDescription', 'FileVersion', 'InternalName',
    'LegalCopyright', 'LegalTradeMarks', 'OriginalFilename',
    'ProductName', 'ProductVersion', 'Comments');
var
  Info: string;
  BuffSize,
    Len, i: Integer;
  Buff: PChar;
  Value: PChar;
begin
  Info := Application.ExeName;
  BuffSize := GetFileVersionInfoSize(PChar(Info), BuffSize);
  if BuffSize > 0 then
  begin
    Buff := AllocMem(BuffSize);
    Memo1.Lines.Add('FileVersionInfoSize=' + IntToStr(BuffSize));
    GetFileVersionInfo(PChar(Info), 0, BuffSize, Buff);
    Info := Info + ':';
    for i := 1 to n_Info do
      if VerQueryValue(Buff, PChar('StringFileInfo\040904E4\' +
        InfoStr[i]), Pointer(Value), Len) then
        Info := Info + #13 + InfoStr[i] + '=' + Value;
    FreeMem(Buff, BuffSize);
    ShowMessage(Info);
  end
  else
    ShowMessage('No FileVersionInfo found');
end;

2009. február 8., vasárnap

How to direct all keyboard events to a specific window


Problem/Question/Abstract:

SetCapture directs all mouse events to a specific window. Is there a similar API for the keyboard? Can this be accomplished without using hooks? I want to track what the user presses on the keyboard and mouse and allow or reject the events.

Answer:

Look up TApplication.OnMessage and study the example given, then look at the example below:


procedure TMainForm.AppMessage(var Msg: TMsg; var Handled: Boolean);
const
  BLOCKEDKEYS = [VK_TAB, VK_UP, VK_DOWN, VK_LEFT, VK_RIGHT, VK_SPACE];
begin
  if (Msg.message = WM_KEYDOWN) then
  begin
    if (Msg.wParam in BLOCKEDKEYS) then
    begin
      Handled := True;
    end;
  end;
end;

2009. február 7., szombat

How to restore the original volume settings after changing the volume of a wave


Problem/Question/Abstract:

I'm trying to control the left/ right channel volume of a wave. I've checked with WaveOutGetDevCaps that volume and left/ right control is supported. When I try to read the volume with WaveOutGetVolume (using "word (Wave_Mapper)" as the device), I get MMSysErr_NotSupported. I need the original volume setting in order to restore it later. WaveOutSetVolume also returns the same error.

Answer:

The code below worked for me. You will see that it is in message handlers for WaveOutOpen etc. messages. The key might be that you need the handle to an open wave device rather than simply the constant for wave_mapper.


procedure TClockForm.mm_wom_open(var Msg: TMessage);
{This code handles the WaveOutOpen message by writing two buffers of data
to the wave device.  Plus other miscellaneous housekeeping.}
begin
  waveOutGetVolume(hWave_out, @saved_volumes);
  waveOutSetVolume(hWave_out, volumes);
  waveOutPrepareHeader(hWave_out, p_wave_hdr, SizeOf(TWaveHdr));
  waveOutWrite(hWave_out, p_wave_hdr, SizeOf(TWaveHdr));
end;

procedure TClockForm.mm_wom_done(var Msg: TMessage);
{Handle the wave out done message}
begin
  waveOutSetVolume(hWave_out, saved_volumes);
  waveOutReset(hWave_out);
  waveOutClose(hWave_out);
end;

2009. február 6., péntek

Protecting code the ... dizzy way


Problem/Question/Abstract:

Code crackers do not only use debuggers but also reverse dissassemblers, that allow viewing code in a more readable form.

Crackers set breakpoints (typically on windows calls or messages), and "peek" on the source code before actually single-stepping back.

This is a small example of how to insert VARIABLE dummy code between valid statements, which can make reverse-dissassembling and single-stepping a nightmare.

Answer:

Conditional ASM statements that actually insert dummy code are of this kind:

       JMP here
       DB byte,byte,byte,byte  ; garble data
  here:

so actually the garble data never get executed. One can find a LOT of combinations that actually drive reverse dissassemblers nuts. This include file, when used, includes variable length dummy statements

----- This is the Include file AsmDizzy.inc: ------

{$IFDEF DIZZY4}
{$UNDEF DIZZY1}
{$UNDEF DIZZY2}
{$UNDEF DIZZY3}
{$UNDEF DIZZY4}
{$ENDIF}

{$IFDEF DIZZY3}
{$IFNDEF DIZZY4}
asm
   DB $EB,$06,$55,$44,$55,$03,$a8,$09;
end;
{$DEFINE DIZZY4}
{$ENDIF}
{$ENDIF}

{$IFDEF DIZZY2}
{$IFNDEF DIZZY3}
{$IFNDEF DIZZY4}
asm
   DB $EB,$04,$75,$13,$a2,$14;
end;
{$DEFINE DIZZY3}
{$ENDIF}
{$ENDIF}
{$ENDIF}

{$IFNDEF DIZZY1}
{$IFNDEF DIZZY2}
{$IFNDEF DIZZY3}
{$IFNDEF DIZZY4}
asm
     DB $EB,$04,$55,$03,$a7,$44;
end;
{$DEFINE DIZZY2}
{$ENDIF}
{$ENDIF}
{$ENDIF}
{$ENDIF}
;

---- End of incude file ----

Having this include file, try putting this code in the event of a button click. The code executes normally, but a lot of VARIABLE garble code is between the statements, some times 4,5 or 6 bytes wide.

procedure TForm1.Button1Click(Sender: TObject);
begin
{$I AsmDizzy.inc}
  ShowMessage('1');
{$I AsmDizzy.inc}
  ShowMessage('2');
{$I AsmDizzy.inc}
  ShowMessage('3');
{$I AsmDizzy.inc}
  ShowMessage('4');
{$I AsmDizzy.inc}
  ShowMessage('1');
end;

This can make singlestepping a nightmare, even with simple statements (ShowMessage) in-between. The .inc file can be enhanced to produce real random code, but this is a task for you to do.

2009. február 5., csütörtök

Get the Processor Usage


Problem/Question/Abstract:

How to get the processor usage

Answer:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ComCtrls, CommCtrl, StdCtrls, Menus, WinSpool, ExtCtrls, Buttons, Registry;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    Label2: TLabel;
    Timer1: TTimer;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure Timer1Timer(Sender: TObject);
  private
    { Private declarations }
    started: boolean;
    reg: TRegistry;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
var
  Dummy: array[0..1024] of byte;
begin
  // Stats started by Button1 hit
  Reg := TRegistry.Create;
  Reg.RootKey := HKEY_DYN_DATA; // Statistic data is saved under this topic
  { Before starting retrieving statistic data you have to query
    the appropiate key under 'PerfStats\StartStat'. }
  Reg.OpenKey('PerfStats\StartStat', false);
    // Open this key first to start collecting performance data
  Reg.ReadBinaryData('KERNEL\CPUUsage', Dummy, Sizeof(Dummy));
  Reg.CloseKey;
  started := true;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  CPUU: integer;
begin
  { After starting the collection of statistic data, you can retrieve the
    recent value under the 'PerfStats\StatData' key. This is done by a timer
    event in this example }
  if started then
  begin
    Reg.OpenKey('PerfStats\StatData', false); // Open extension kex for txt files
    Reg.ReadBinaryData('KERNEL\CPUUsage', CPUU, SizeOf(Integer));
    Reg.CloseKey;
    Label1.Caption := IntToStr(CPUU) + '%';
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  Dummy: array[0..1024] of byte;
begin
  // Button2 hit stops statistic collection
  { Collecting statistic data is stopped by a query under 'PerfStats/StopStat' }
  Reg.OpenKey('PerfStats\StopStat', false);
    // Open this key first to start collecting performance data
  Reg.ReadBinaryData('KERNEL\CPUUsage', Dummy, SizeOf(Dummy));
  Reg.Free;
  Started := false;
end;

end.

2009. február 4., szerda

Outlook Automation - Scaning Outlook's Folders


Problem/Question/Abstract:

How I can information from Outlook in my application

Answer:

Sample how to work with Outlook from Delphi application

unit UScanOutlook;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  StdCtrls, Grids, Outline;
const
  olByValue = 1;
  olByReference = 4;
  olEmbeddedItem = 5;
  olOLE = 6;

  olMailItem = 0;
  olAppointmentItem = 1;
  olContactItem = 2;
  olTaskItem = 3;
  olJournalItem = 4;
  olNoteItem = 5;
  olPostItem = 6;

  olFolderDeletedItems = 3;
  olFolderOutbox = 4;
  olFolderSentMail = 5;
  olFolderInbox = 6;
  olFolderCalendar = 9;
  olFolderContacts = 10;
  olFolderJournal = 11;
  olFolderNotes = 12;
  olFolderTasks = 13;

type
  TForm1 = class(TForm)
    oline_outlook: TOutline;
    Button8: TButton;
    procedure Button8Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
    OlApp, NameSpace, root: OleVariant;
  end;

var
  Form1: TForm1;

implementation
uses ComObj;
{$R *.DFM}

procedure TForm1.Button8Click(Sender: TObject);
  procedure scan(ol: TOutline; root: OleVariant; s: string);
  var
    i, j, k: integer;
    bcount, rcount: integer;
    branch, MAPIFolder: olevariant;
    line: string;
  begin
    line := '';
    rcount := root.count;
    for i := 1 to rcount do
    begin
      line := s + root.item[i].name;
      ol.Lines.Add(line);
      branch := root.item[i].folders;
      bcount := branch.count;
      MAPIFolder := Namespace.GetFolderFromId(root.item[i].EntryID,
        root.item[i].StoreID);
      if MAPIFolder.Items.count > 0 then
        for j := 1 to MAPIFolder.Items.count do
          ol.Lines.Add(s + ' ' + MAPIFolder.Items[j].subject);
      if bcount > 0 then
      begin
        scan(ol, branch, s + ' ');
      end;
    end;
  end;
begin
  oline_outlook.Lines.Clear;
  OlApp := CreateOleObject('Outlook.Application');
  Namespace := OlApp.GetNameSpace('MAPI');
  root := Namespace.folders;
  scan(oline_outlook, root, '');
end;

end.

2009. február 3., kedd

Opening and Closing a CD Tray better


Problem/Question/Abstract:

Opening and Closing a CD Tray?

Answer:

uses
  MMSystem;

// Open CD Tray

{Simple Way:}

mciSendstring('SET CDAUDIO DOOR OPEN WAIT', nil, 0, Self.Handle);

{More complex way:}

function OpenCD(Drive: Char): Boolean;
var
  Res: MciError;
  OpenParm: TMCI_Open_Parms;
  Flags: DWORD;
  S: string;
  DeviceID: Word;
begin
  Result := False;
  S := Drive + ':';
  Flags := MCI_OPEN_TYPE or MCI_OPEN_ELEMENT;
  with OpenParm do
  begin
    dwCallback := 0;
    lpstrDeviceType := 'CDAudio';
    lpstrElementName := PChar(S);
  end;
  Res := mciSendCommand(0, MCI_OPEN, Flags, Longint(@OpenParm));
  if Res <> 0 then
    Exit;
  DeviceID := OpenParm.wDeviceID;
  try
    Res := mciSendCommand(DeviceID, MCI_SET, MCI_SET_DOOR_OPEN, 0);
    if Res = 0 then
      Exit;
    Result := True;
  finally
    mciSendCommand(DeviceID, MCI_CLOSE, Flags, Longint(@OpenParm));
  end;
end;

//Close CD Tray

{Simple Way:}

mciSendstring('SET CDAUDIO DOOR CLOSED WAIT', nil, 0, Self.Handle);

{More complex way:}

function CloseCD(Drive: Char): Boolean;
var
  Res: MciError;
  OpenParm: TMCI_Open_Parms;
  Flags: DWORD;
  S: string;
  DeviceID: Word;
begin
  Result := False;
  S := Drive + ':';
  Flags := MCI_OPEN_TYPE or MCI_OPEN_ELEMENT;
  with OpenParm do
  begin
    dwCallback := 0;
    lpstrDeviceType := 'CDAudio';
    lpstrElementName := PChar(S);
  end;
  Res := mciSendCommand(0, MCI_OPEN, Flags, Longint(@OpenParm));
  if Res <> then
    Exit;
  DeviceID := OpenParm.wDeviceID;
  try
    Res := mciSendCommand(DeviceID, MCI_SET, MCI_SET_DOOR_CLOSED, 0);
    if Res = 0 then
      Exit;
    Result := True;
  finally
    mciSendCommand(DeviceID, MCI_CLOSE, Flags, Longint(@OpenParm));
  end;
end;

2009. február 2., hétfő

Deleting Registry Key from .Reg file


Problem/Question/Abstract:

Deleting Registry Key from .Reg file

Answer:

If you want to import a key but delete another key at the same time, use the syntax in your Reg file:

[-HKEY_CURRENT_USER\Software\Local Applications]

Note the minus sign after the bracket.

2009. február 1., vasárnap

Create a TAction and its OnExecute event at runtime


Problem/Question/Abstract:

How to create a TAction and its OnExecute event at runtime

Answer:

{ ... }
type
  TForm1 = class(TForm)
    ActionList1: TActionList;
    procedure FormCreate(Sender: TObject);
  private
    Action1, Action2: TAction;
    procedure Test(Sender: TObject);
  end;

procedure TForm1.Test(Sender: TObject);
begin
  Caption := IntToStr(Actionlist1.ActionCount);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  Action1 := TAction.Create(Actionlist1);
  Action1.Actionlist := Actionlist1;
  Action1.OnExecute := Test;
  Form1.OnClick := Action1.OnExecute;
end;