2009. szeptember 30., szerda

Get only file name


Problem/Question/Abstract:

How to get only file name (without extension) ?  Example: If i have: c:\test\other\anyfile.doc  and i want get only "anyfile".

Answer:

Solve 1:

function FileName(Value: string): string;
begin
  Result := ExtractFileName(Value);
  if ExtractFileExt(Value) <> '' then
    Result := Copy(ExtractFileName(Value), 1,
      Pos(ExtractFileExt(Value), ExtractFileName(Value)) - 1);
end;


Solve 2:

function FileName(Value: string): string;
begin
  Result := ChangeFileExt(ExtractFileName(Value), '');
end;

2009. szeptember 29., kedd

Using alternative formats with TDateTimePicker


Problem/Question/Abstract:

Here is a working example of how to use alternative date formats in the TDateTimePicker component for Delphi 5.

Answer:

The problem for me with the TDateTimePicker component, is that it would not only allow dates based on the Windows settings for ShortDate and LongDate, but it also left gaps in between the separators for dates like ' 2/ 3/01'. This made it look terrible.  

I did some research into the problem, and found many "solutions" that did not seem to work all the time.  So far, I haven't had any problems with this one, and although I tested it in Delphi 5, it should work in some previous versions.

Simply enter the following line of code into the OnActivate Event for each TDateTimePicker on your form.  This will manually set the format, and it should remain in place until the form is destroyed.

DateTimePicker1.Perform(DTM_SETFORMAT, 0, DWORD(PCHAR('MM/dd/yyyy')));

The above example assumes that the TDateTimePicker is named DateTimePicker1, and you can vary the format by changing the format string ('MM/dd/yyyy').

I am not sure what format's are available, and it seems a bit quirky.  I had tried to use 'mm/dd/yyyy', and I wound up with a month of '52'!!!  The above string forces two digit months and days, and four-digit years.

2009. szeptember 28., hétfő

Changing folder in Open/Save dialogs


Problem/Question/Abstract:

It is possible to specify initial folder in Open/Save dialogs. However sometimes it is necessary to change current folder, for example, in responce to change of filter.

Answer:

Windows doesn't offer a way to change current dialog. However there is a very handy approach to this problem - imitate user actions. Below is the sample code of how the folder is changed when filter is changed.

procedure SaveDialogTypeChange is a handler for OnTypeChange event.
Depending on the type different folders are selected. This piece of code saves the value of file name edit box, puts a new folder name there, then imitates click of OK button and then restores contents of the edit box.
Add Dlgs to "uses" clause of your unit.

procedure TMainForm.SaveDialogTypeChange(Sender: TObject);
var
  S, S1: string;
  EditHandle: THandle;
  startp,
    endp: DWORD;
begin
  s := '';
  if SaveDialog.FilterIndex = 2 then
  begin
    s := 'c:\program files';
  end
  else if SaveDialog.FilterIndex = 3 then
  begin
    s := 'd:\program files';
  end;
  if s <> '' then
  begin
    EditHandle := GetDlgItem(GetParent(SaveDialog.Handle), edt1);
    if EditHandle <> 0 then
    begin
      SetLength(S1, GetWindowTextLength(EditHandle) + 1);
      GetWindowText(EditHandle, PChar(S1), Length(S1));
      SetLength(S1, StrLen(PChar(S1)));
      SendMessage(EditHandle, EM_GETSEL, Integer(@StartP), Integer(@EndP));
      SetWindowText(EditHandle, PChar(S));
      SendMessage(GetParent(SaveDialog.Handle), WM_COMMAND, 1,
        GetDlgItem(GetParent(SaveDialog.Handle), IDOK));
      if Length(S1) > 0 then
        if S1[Length(S1)] = #10 then
          Delete(S1, Length(S1), 1);
      SetWindowText(EditHandle, PChar(S1));
      SendMessage(EditHandle, EM_SETSEL, StartP, EndP);
    end;
  end;
end;

2009. szeptember 27., vasárnap

How do I create a QReport group band at runtime?


Problem/Question/Abstract:

How do I create a group band at runtime?

Answer:

You can call the band's create constructor and set it's properties.

with TQRGroup.Create(Self) do
begin
  Parent := QuickRep1;
  Master := Parent;
  Expression := 'CustNo';
end;

with TQRDBText.Create(Self) do
begin
  Parent := QRGroup1;
  Dataset := QuickRep1.Dataset;
  DataField := 'CustNo';
end

2009. szeptember 26., szombat

Draw an antialiased line


Problem/Question/Abstract:

How to draw an antialiased line

Answer:

Here's a procedure that uses a modified version of Gupta-Sproull anti-aliasing:

procedure DrawLine(Canvas: TCanvas; x1, y1, x2, y2: integer);

  procedure DrawPixel(x, y: integer; Distance: double);
  var
    Alpha: integer;
  begin
    Alpha := 255 - Trunc(255 * Sqr(1 - Distance * 2 / 3));
    Canvas.Pixels[x, y] := Alpha or (Alpha shl 8) or (Alpha shl 16);
  end;

var
  i, deltax, deltay, numpixels, d, dinc1, dinc2, x, xinc1, xinc2, y, yinc1, yinc2:
    Integer;
  du: Integer;
  twovdu: Integer;
  invD: Double;
  invD2du: Double;
  vincx, vincy: Integer;
begin
  {Calculate deltax and deltay for initialisation}
  deltax := abs(x2 - x1);
  deltay := abs(y2 - y1);
  {Initialize all vars based on which is the independent variable}
  if deltax >= deltay then
  begin
    {x is independent variable}
    numpixels := deltax + 1;
    d := (2 * deltay) - deltax;
    dinc1 := deltay shl 1;
    dinc2 := (deltay - deltax) shl 1;
    xinc1 := 1;
    xinc2 := 1;
    yinc1 := 0;
    yinc2 := 1;
    du := deltax;
    vincx := 0;
    vincy := 1;
  end
  else
  begin
    {y is independent variable}
    numpixels := deltay + 1;
    d := (2 * deltax) - deltay;
    dinc1 := deltax shl 1;
    dinc2 := (deltax - deltay) shl 1;
    xinc1 := 0;
    xinc2 := 1;
    yinc1 := 1;
    yinc2 := 1;
    du := deltay;
    vincx := 1;
    vincy := 0;
  end;
  twovdu := 0;
  invD := 1 / (2 * sqrt(deltax * deltax + deltay * deltay));
  invD2du := 2 * (du * invD);
  {Make sure x and y move in the right directions}
  if x1 > x2 then
  begin
    xinc1 := -xinc1;
    xinc2 := -xinc2;
    vincx := -vincx;
  end;
  if y1 > y2 then
  begin
    yinc1 := -yinc1;
    yinc2 := -yinc2;
    vincy := -vincy;
  end;
  {Start drawing at [x1, y1]}
  x := x1;
  y := y1;
  {Draw the pixels}
  for i := 1 to numpixels do
  begin
    DrawPixel(x, y, twovdu * invD);
    DrawPixel(x + vincx, y + vincy, invD2du - twovdu * invD);
    DrawPixel(x - vincx, y - vincy, invD2du + twovdu * invD);
    if d < 0 then
    begin
      twovdu := d + du;
      d := d + dinc1;
      x := x + xinc1;
      y := y + yinc1;
    end
    else
    begin
      twovdu := d - du;
      d := d + dinc2;
      x := x + xinc2;
      y := y + yinc2;
    end;
  end;
end;

2009. szeptember 25., péntek

Count the number of words in a string

Problem/Question/Abstract:

How to count the number of words in a string

Answer:

{ ... }
type
TCharSet = TSysCharSet;
{ ... }

function WordCount(const S: string; const WordDelims: TCharSet): Integer;
var
SLen, I: Cardinal;
begin
Result := 0;
I := 1;
SLen := Length(S);
while I <= SLen do
begin
while (I <= SLen) and (S[I] in WordDelims) do
Inc(I);
if I <= SLen then
Inc(Result);
while (I <= SLen) and not (S[I] in WordDelims) do
Inc(I);
end;
end;

Use the following statement to call the function:

WordCount(Edit1.Text, [' ', ','])


2009. szeptember 24., csütörtök

Zooming effect when minimizing windows


Problem/Question/Abstract:

In win95 or NT4, there's a 'zooming effect' when an application is minimized
to the taskbar or restored from the taskbar.
However, this zooming effect does not seem to be presented in Delphi applications.

Answer:

Yes, the reason for this is simple: if you click the minimize button of the
main form in a Delphi app, the main form is not minimized but hidden. In its
place the zero-size Application window is minimized. Having the animation
in place here gives some rather weird visiual effects so the VCl designers
decided to disable it.

You can switch the effect on or off with the following piece of code, which works for Win95 and for NT 4:

procedure TForm1.SwitchEffect(Sender: TObject);
var
  Inf: TAnimationInfo;
begin
  Inf.cbSize := sizeof(Inf);
  // 1 = switch it on,
  // 0 = switch if off
  Inf.iMinAnimate := 1;
  SystemparametersInfo(SPI_SETANIMATION, 0, @Inf, SPIF_SENDWININICHANGE);
end;

2009. szeptember 23., szerda

How to copy a running program to a given folder


Problem/Question/Abstract:

How to copy a running program to a given folder

Answer:

copyfile(PCHAR(filename), PCHAR('c:\temp\' + extractfilename(filename)), true);

2009. szeptember 22., kedd

How to load data from a table into a TListBox


Problem/Question/Abstract:

How can I create a generic procedure that will load data from a table into a TListBox?

Answer:

Actually, this tip pertains to any control that has a property such as an Items property of type TStrings or is a descendant of TStrings. For example, the following controls have properties of type TStrings: TComboBox, TMemo, TQuery (SQL Property), TDBRadioGroup, TDirectoryListBox, TDriveComboBox, TFileListBox, TFilterComboBox, TListBox, TRadioGroup.

I should say that TStrings is actually an abstract class, and though you will see the help file list properties such as Items as TStrings, they're actually descendants of TStrings. More commonly, you will see something like the TStringList being used almost interchangeably (at least in concept) with TStrings. Essentially, a TStrings object is a list of strings, with array-like properties. The advantage of a TStrings object over an array is that you don't have to worry about allocating and deallocating memory to add and subtract items (which is why they're ideal when working with a loose collection of strings).

To load data from a column in a table into something like a TListBox is easy. All it takes is stepping through the table, picking out the values you want and adding them to the TStrings object - all in one line of code.

{Loads a list using values from a specific field from an open table}

procedure DBLoadList(srcTbl: TTable; srcFld: string; const lst: TStrings);
{srcTbl: Source Table, srcFld: Source Field, lst: Target List}
begin
  with srcTbl do
    if (RecordCount > 0) then
    begin
      {Don't bother if there are no records}
      lst.Clear;
      while not EOF do
      begin
        lst.Add(FieldByName(srcFld).AsString);
        next;
      end;
    end;
end;

The code above assumes you have an open TTable whose values you can access. The real workhorse of the procedure is the while loop which steps through the table and grabs the value from the specified field and assigns it to a new entry into the TStrings object.

Operations such as the one above can be done any of those objects because the properties descend from a common ancestor.

2009. szeptember 21., hétfő

How to pass multidimensional arrays as parameters to a function or procedure


Problem/Question/Abstract:

How to pass multidimensional arrays as parameters to a function or procedure

Answer:

Passing an array to a procedure or function is straight forward and behaves as expected. However, passing a multi-dimensional array to a function or procedure is not handled in the same way. Consider MyArray to be defined as:


var
  MyArray: array[1..3, 1..5] of double;


And you want to pass it to a procedure called DoSomeThing() defined as:


procedure DoSomeThing(MyArray: array of double);
begin
  showmessage(floattostr(MyArray[1, 1]));
end;


One might think a simple statement like DoSomeThing(MyArray); would do the trick. Unfortunately, this is not the case. The statement DoSomeThing(MyArray); will not compile. The compiler sees the two data structures involved as different types - so it will not allow the statement. The DoSomeThing() procedure is expecting an array of doubles, but the example is passing a multi-dimensional array.

Delphi handles multi-dimensional arrays as user defined type, so there is no syntax to tell a procedure that its parameter(s) are multi-dimensional arrays - without declaring a type. Creating a type, and using this type as the parameter is the correct method to pass a multi-dimensional array. We could just pass a pointer to the array, but inside the function, we need to typecast that pointer. What type to cast it as is the next issue. You would have to have the type defined, or declared identically in 2 places. This method just doesn't make sense.

By defining a type, we can change the process to this:


type
  TMyArray = array[1..3, 1..5] of double;

var
  MyArray: TMyArray;

procedure DoSomeThing(MyArray: TMyArray);
begin
  showmessage(floattostr(MyArray[1, 1]));
end;


Now the actual call looks as we expected:


DoSomeThing(MyArray);


If you want to use the method of passing a pointer, your function might look like this:


type
  PMyArray = ^TMyArray;
  TMyArray = array[1..3, 1..5] of double;

var
  MyArray: TMyArray;

procedure DoSomeThing(MyArray: PMyArray);
begin
  showmessage(floattostr((MyArray[2, 3])));
end;


Note, under 32 bit version Delphi, you do not need to dereference the MyArray variable inside DoSomeThing(). Under older versions you might have to refer to MyArray as MyArray^.

If you want to pass just a generic pointer, you may not be able to use it directly. You can declare a local variable, and use it. Again, you may need to cast the local variable for older versions of PASCAL. Of course this method does offer more flexibility in data usage.


procedure DoSomeThing(MyArray: pointer);
var
  t: ^TMyArray;
begin
  t := MyArray;
  ShowMessage(FloatToStr(t[2, 3]));
end;


Regardless, both calls that use a pointer method, will look something like:


MyArray[2, 3] := 5.6;
DoSomeThing(@MyArray);

2009. szeptember 20., vasárnap

How to detect which item of an open TComboBox dropdown list the mouse is over


Problem/Question/Abstract:

How would I display a hint over items in a ComboBox dropdown if the item text of the highlighted item is wider than the dropdown? I also want to be able to do the same thing in a ListBox. For a ComboBox I don't know how to proceed. There is no ItemAtPos method for a ComboBox. My first thought was no problem, I'll look at the source for TListBox.ItemAtPos and create a TComboBox.ItemAtPos method. However, I ran into a wall. TListBox.ItemAtPos uses the LB_GetItemRect message to do its magic. There is no corresponding CB_GetItemRect message for a ComboBox. Does anyone out there have any ideas on how to proceed?

Answer:

procedure TForm1.appIdle(sender: TObject; var done: Boolean);
var
  pt: TPoint;
  wnd: HWND;
  buf: array[0..128] of Char;
  i: Integer;
begin
  GetCursorPos(pt);
  wnd := WindowFromPoint(pt);
  buf[0] := #0;
  if wnd <> 0 then
  begin
    GetClassName(wnd, buf, sizeof(buf));
    if StrIComp(buf, 'ComboLBox') = 0 then
    begin
      Windows.ScreenToClient(wnd, pt);
      i := SendMessage(wnd, LB_ITEMFROMPOINT, 0, lparam(PointToSmallpoint(pt)));
      if i >= 0 then
      begin
        SendMessage(wnd, LB_GETTEXT, i, integer(@buf));
        statusbar1.simpletext := buf;
        Exit;
      end;
    end;
  end;
  statusbar1.simpletext := '';
end;

As to showing a custom hint, there is a CM_HINTSHOW message that is send to a control before a hint is popped up. It comes with a pointer to a record that allows you to customize the hints position and the hint text. The thing is undocumented, like all the internal VCL messages, so you need to use the VCL source to figure out how it is used. Or search for examples in the newsgroups archives. When the mouse moves to the next item you call Application.Cancelhint to remove the old hint and wait for the new one to reappear. Application has a number of hint-related properties you can tweak to make the hint appear fast.

2009. szeptember 19., szombat

Automatically capitalize the first letter of every word when typing in the field of a TStringGrid


Problem/Question/Abstract:

How to automatically capitalize the first letter of every word when typing in the field of a TStringGrid

Answer:

Solve 1:

procedure TForm1.StringGrid1KeyPress(Sender: TObject; var Key: Char);
var
  s: string;
  c: Byte;
begin
  with StringGrid1 do
    s := Cells[Col, Row];
  if Length(s) = 0 then
  begin
    if Key in ['a'..'z'] then
    begin
      c := Ord(Key) - 32;
      Key := Chr(c);
    end;
    exit;
  end;
  if s[Length(s)] = ' ' then
    if Key in ['a'..'z'] then
    begin
      c := Ord(Key) - 32;
      Key := Chr(c);
    end;
end;


Answer 2:

In an onKeyPress event, do this:

if length(field.text) = 0 then
  key := upCase(key);

2009. szeptember 18., péntek

How to retrieve row information in a TDBGrid


Problem/Question/Abstract:

I would like to have a message displayed which shows the actual row in a DBGRid; i.e. "row 32 of 144". How can I achieve this?

Answer:

You could query the BDE with two calls to get the number of records and the cursor position like below:

function GetNumRecords(T: TTable): LongInt;
function GetRecordNumber(DataSet: TDataSet): Longint;

function TMainForm.GetNumRecords(T: TTable): LongInt;
var
  Count: LongInt;
begin
  Check(DbiGetRecordCount(T.Handle, Count));
  Result := Count;
end;

function TMainForm.GetRecordNumber(DataSet: TDataSet): Longint;
var
  CursorProps: CurProps;
  RecordProps: RecProps;
begin
  with DataSet do
  begin
    Check(DbiGetCursorProps(Handle, CursorProps));
    UpdateCursorPos;
    Check(DbiGetRecord(Handle, dbiNOLOCK, nil, @RecordProps));
    Result := RecordProps.iSeqNum;
  end;
end;

2009. szeptember 17., csütörtök

How to deactivate and reactivate [CTRL] + [ALT] + [DEL] or [ALT] + [TAB] key combinations


Problem/Question/Abstract:

How to deactivate and reactivate [CTRL] + [ALT] + [DEL] or [ALT] + [TAB] key combinations

Answer:

Solve 1:

unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

const
  RSP_SIMPLE_SERVICE = 1;
  RSP_UNREGISTER_SERVICE = 0;

type
  TRegisterServiceProcess = function(dwProcessID, dwType: DWORD): DWORD; stdcall;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
var
  Hndl: THandle;
begin
  Hndl := LoadLibrary('KERNEL32.DLL');
  @RegisterServiceProcess := GetProcAddress(Hndl, 'RegisterServiceProcess');
  if @RegisterServiceProcess < > nil then
    {check for function, if its there load it}
    RegisterServiceProcess(GetCurrentProcessID, RSP_SIMPLE_SERVICE);
  FreeLibrary(Hndl);
end;

end.


Solve 2:

On Windows 95/ 98/ ME you can disallow CTRL + ALT + DEL in a straight forward manner:


procedure DisableCtrlAltDel;
var
  Dummy: Integer;
begin
  Dummy := 0;
  SystemParametersInfo(SPI_SCREENSAVERRUNNING, 1, @Dummy, 0)
end;

procedure EnableCtrlAltDel;
var
  Dummy: Integer;
begin
  Dummy := 0;
  SystemParametersInfo(SPI_SCREENSAVERRUNNING, 0, @Dummy, 0)
end;


In fact, the above code snippets will disable Ctrl+Esc too. To disable ALT + TAB, you can use:


procedure DisableAltTab;
var
  Dummy: Integer;
begin
  Dummy := 0;
  SystemParametersInfo(SPI_SETFASTTASKSWITCH, 1, @Dummy, 0);
end;

procedure EnableAltTab;
var
  Dummy: Integer;
begin
  Dummy := 0;
  SystemParametersInfo(SPI_SETFASTTASKSWITCH, 0, @Dummy, 0);
end;


All this is still for Windows 95/ 98/ ME. On NT based systems (Windows NT/ 2000), you can get rid of Alt+Tab and Ctrl+Esc in normal manner (different from the above snippets). But to disable Ctrl+Alt+Del, you need to perform key-remapping and that is a bit tricky. Doing key-remapping can render a system useless if not done properly. You need to do a reinstall in such a case.

2009. szeptember 16., szerda

Cracking XOR Encryption


Problem/Question/Abstract:

Well, there seems to be a trend to post xor encryption routines... so here a simple example of how to break the encryption...

Answer:

Drop a button and edit box on the form, text in the edit box has to be at least 8 chars. if you're using to actualy break an encrypted string, just fill buffer [0..7] with encrypted data and fill [0..7] with the plaintext you assume is encrypted (pretty easy, you username of something of the sort).

I'd like to thank Cheng Wei for pointing out my rediculously slow calls to Edit1.text[i]. i've rethought the algorithm out, and it now tests keys as 2 longwords. as a result of this fix, it now scans 100,000,000 keys in 15seconds on my duron 600! WAAAHOOOOO! Thanks allot Cheng!

Please don't go on ranting about how most ppl don't know how to break xor encryption so it's good enough. it's simply negligence. if someting is worth encrypting then do it properly or don't bother.

procedure TForm1.Button1Click(Sender: TObject);
var
  i, j: longword;
  thistime, lasttime: longword;
  buffer: array[0..7] of byte;
  b: array[0..1] of longword absolute buffer[0];
  plaintext: array[0..7] of byte;
  p: array[0..1] of longword absolute plaintext[0];
  key: array[0..7] of byte;
  k: array[0..1] of longword absolute key[0];
begin
  lasttime := gettickcount;
  randomize;
  if length(edit1.text) < 8 then
    exit;
  for i := 0 to 7 do
  begin
    plaintext[i] := byte(edit1.text[i + 1]);
    buffer[i] := plaintext[i] xor random(256); //encrypt
  end;
  i := 0;
  repeat
    for j := 0 to 1000000 do //loop is unrolled by compiler
    begin
      randseed := i;
      key[0] := random(256);
      key[1] := random(256);
      key[2] := random(256);
      key[3] := random(256);
      key[4] := random(256);
      key[5] := random(256);
      key[6] := random(256);
      key[7] := random(256);
      if b[0] xor k[0] = p[0] then //test key in blocks of 4
        if b[1] xor k[1] = p[1] then
        begin
          thistime := gettickcount;
          caption := 'The key is: ' + inttostr(i) + ' (' + inttostr((thistime -
            lasttime) div 1000) + 'sec)';
          Exit;
        end;
      inc(i, 1);
    end;
    caption := inttostr(i);
    application.processmessages;
  until i > longword(MaxInt);
end;

I'll be posting an article on writting a complete cryptosystem soon enough. i need to read into the legalities first because of canadian encryption laws.

2009. szeptember 15., kedd

How to perform a Locate on multiple fields


Problem/Question/Abstract:

Is it possible to locate a record in a TTable with matching two fields? For example for name='Peter' and age=25

Answer:

if mytable.locate('name; age', VarArrayOf([Variant(YourName),
  Variant(YourAge)]), [lopartialKey]) then
  { ... }

2009. szeptember 14., hétfő

Detect when a TPopupMenu is closed


Problem/Question/Abstract:

Is it possible to know when a popup menu closes? I really need to differentiate between when a user selects an item or when the menu disappears because the user clicks somewhere else and it loses focus. Is there some message sent to the app window once TrackPopupMenu() returns?

Answer:

Solve 1:

There are messages that are send to the window specified as the menus owner in the call to TrackPopupMenu. If you are using Delphi 5, add the following unit to your project and your form will get the three custom messages defined in the units interface:

unit ExPopupList;

interface

uses Controls;

const
  CM_MENUCLOSED = CM_BASE - 1;
  CM_ENTERMENULOOP = CM_BASE - 2;
  CM_EXITMENULOOP = CM_BASE - 3;

implementation

uses Messages, Forms, Menus;

type
  TExPopupList = class(TPopupList)
  protected
    procedure WndProc(var Message: TMessage); override;
  end;

  { TExPopupList }

procedure TExPopupList.WndProc(var Message: TMessage);

  procedure Send(msg: Integer);
  begin
    if Assigned(Screen.Activeform) then
      Screen.ActiveForm.Perform(msg, Message.wparam, Message.lparam);
  end;

begin
  case message.Msg of
    WM_ENTERMENULOOP:
      Send(CM_ENTERMENULOOP);
    WM_EXITMENULOOP:
      Send(CM_EXITMENULOOP);
    WM_MENUSELECT:
      with TWMMenuSelect(Message) do
        if (Menuflag = $FFFF) and (Menu = 0) then
          Send(CM_MENUCLOSED);
  end;
  inherited;
end;

initialization
  Popuplist.Free;
  PopupList := TExPopupList.Create;
  {Note: will be freed by Finalization section of Menus unit}

end.


Solve 2

The TPopupMenu.Popup method (which is used to display such a menu even when presented "automatically" by the VCL) has it's own message pump whilst being displayed. i.e. the Popup procedure only returns to the caller when the menu has been dismissed.

I used this feature to implement a minor extension to TPopupMenu that not only raises an event when the menu has been dismissed, but also peeks in the relevant message queue for the presence of a WM_COMMAND message - i.e. was the menu dismissed because an item was selected or because the menu was cancelled with no item selected. This can then be reflected in the event.

{ ... }
type
  TIXPopupMenuEvent = procedure(Sender: TObject; Cancelled: Boolean) of object;

  TIXPopupMenu = class(TPopupMenu)
  private
    eOnDismissed: TIXPopupMenuEvent;
  public
    procedure Popup(X, Y: Integer); override;
  published
    property OnDismissed: TIXPopupMenuEvent read eOnDismissed write eOnDismissed;
  end;

implementation

{TIXPopupMenu}

procedure TIXPopupMenu.Popup(X, Y: Integer);
var
  msg: tagMSG;
begin
  inherited;
  if Assigned(OnDismissed) then
    OnDismissed(Self, PeekMessage(msg, PopupList.Window, WM_COMMAND,
      WM_COMMAND, PM_NOREMOVE) = FALSE);
end;

2009. szeptember 13., vasárnap

How to correctly maximize a window


Problem/Question/Abstract:

I have some forms with the property WindowState set to wsMaximized. The problem is that some forms are hiding the taskbar. I can't find the problem since some others forms are working fine.

Answer:

Solve 1:

If I'm right, the only way to get correctly maximized windows (regarding the taskbar and other "taskbar like" windows) is using API functions:


var
  x: pointer;
  r: TRect;
begin
  x := Addr(r);
  SystemParametersInfo(SPI_GETWORKAREA, 0, x, 0);
  Left := 0;
  Width := r.Right;
  Top := 0;
  Height := r.Bottom;
  { ... }


Solve 2:

This seems to be a particular problem when you do it in the forms OnCreate event. To fix it add a handler for the WM_GETMINMAXINFO message:


private
{ Private declarations }

procedure wmGetMinMaxInfo(var msg: TWMGetMinMaxInfo); message WM_GETMINMAXINFO;

  procedure TForm1.wmGetMinMaxInfo(var msg: TWMGetMinMaxInfo);
  var
    r: TRect;
  begin
    SystemParametersInfo(SPI_GETWORKAREA, 0, @r, 0);
    with msg.MinMaxInfo^ do
    begin
      ptMaxSize := r.BottomRight;
      ptMaxPosition := r.TopLeft;
    end;
  end;


If your form is set to Position := poDesigned and WindowState := wsMaximized, then don't set any value for Constraints. Otherwise the form won't maximize correctly at runtime, if the Windows taskbar is resized.

2009. szeptember 12., szombat

Delete a shortcut link


Problem/Question/Abstract:

How to delete a shortcut link

Answer:

procedure DeleteShortcut(const
  User {See scUserKey... codes},
  Where {See scWhereKey... codes},
  Name {Name for the shortcut link, w/o ".lnk"}: string);
var
  s: string;
begin
  s := GetEnvVar(scWinDirKey);
  if User <> '' then
    s := s + '\Profiles\' + User
  else if OprSysPlatform = osWinNT then
    raise Exception.CreateFmt('Must have user name to delete shortcut (%s) for NT',
      [Name]);
  if Where = '' then
    raise Exception.CreateFmt('Where must be specified to delete shortcut (%s)',
      [Name]);
  if not DirectoryExists(s) then
    raise Exception.Create('User profile does not exist for ' + User);
  s := s + '\' + Where;
  DeleteFile(s + '\' + Name + '.lnk');
end;

2009. szeptember 11., péntek

How to activate and deactivate a screensaver


Problem/Question/Abstract:

I have written an application which sits in the system tray. At a particular time of the day my application will pop up and I need it to stop a Windows screensaver if one is running. It also needs to disable the screensaver while the program is on screen so the screensaver does not run. When the application has finished what it has been doing, it will pop down to the system tray again and then it needs to enable the screensaver and run it. How?

Answer:

For this small example you need a form with a timer and a button:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, StdCtrls, TlHelp32, ShellAPI;

type
  TForm1 = class(TForm)
    Timer1: TTimer;
    ListBox1: TListBox;
    Button1: TButton;
    procedure Timer1Timer(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    FLastScreenSaver: string;
  public
    {Public Declarations}
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

function ExeNameFromWndHandle(hWnd: THandle): string;
var
  ProcessID: Integer;
  Process: TProcessEntry32;
  Snap: THandle;
  s: string;
begin
  try
    GetWindowThreadProcessId(hWnd, @ProcessID);
    Snap := CreateToolHelp32SnapShot(TH32CS_SNAPALL, 0);
    Process.dwSize := sizeof(TProcessEntry32);
    Process32First(Snap, Process);
    repeat
      if Process.th32ProcessID = ProcessID then
      begin
        if length(string(Process.szExeFile)) > 0 then
          s := Process.szExeFile
        else
          s := '';
        break;
      end;
    until
      not Process32Next(Snap, Process);
  except
    s := '';
  end;
  Result := s;
end;

procedure TForm1.Timer1Timer(Sender: TObject);
var
  hWnd: THandle;
  s: string;
begin
  hWnd := GetForegroundWindow;
  s := ExeNameFromWndHandle(hWnd);
  if LowerCase(ExtractFileExt(s)) = '.scr' then
  begin
    FLastScreenSaver := s;
    {As a mouse movement terminates a screensaver we generate one. Some screensavers
    only quit when the user clicks, so perhaps you should generate a click instead of
                a mouse movement }
    {Don't delete double lines else it will not work}
    mouse_event(MOUSEEVENTF_MOVE, 8, 8, 0, GetMessageExtraInfo);
    mouse_event(MOUSEEVENTF_MOVE, 8, 8, 0, GetMessageExtraInfo);
    mouse_event(MOUSEEVENTF_MOVE, -8, -8, 0, GetMessageExtraInfo);
    mouse_event(MOUSEEVENTF_MOVE, -8, -8, 0, GetMessageExtraInfo);
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  {Click on the Button to reactivate the screensaver}
  ShellExecute(0, 'open', PChar(FLastScreenSaver), '/s', '', SW_SHOWNORMAL);
  FLastScreenSaver := '';
  Timer1.Enabled := False;
  {It is important that the timer is deactivated else the screensaver will be
  immediatley deactivated after you restarted it.
        So adapt the Timer enabling to your needs.}
end;

end.

2009. szeptember 10., csütörtök

Creating a persistent BDE alias


Problem/Question/Abstract:

How to creat a persistent BDE alias

Answer:

There has been a number of occasions where I needed to create persistent BDE aliases. The point is that the DB API isn't very discussed and is unkown to most Delphi developers. Despite that fact, the Dbi calls are very powerful and useful functions.

The function below, CreateAlias, encapsulates the DbiAddAlias call, with some error checking and BDE initialization and finalization procedures.

uses Windows, SysUtils, DbiProcs, DbiErrs, DBTables;

const
  CRLF = #13 + #10;
  ERR_ALIASDRIVERNOTFOUND = 'Specified driver doesn''t exist.';
  ERR_ALIASALREADYEXISTS = 'The Alias (%s) already exists.' + CRLF +
    'Would you like to reconfigure it?';
  ERR_ALIASINVALIDPARAM = 'Invalid Alias name.';
  ERR_ALIASCLOSEBDE = 'Error closing the BDE.' + CRLF +
    'Please close all applications and restart Windows';
  ERR_ALIASOPENBDE = 'Error initializing BDE. Cannot create Alias.';

procedure CreateAlias(sAlias, sPath, sDriver: string;
  PersistentAlias: Boolean);
var
  dbEnv: DbiEnv;
  dbRes: DbiResult;
  Resp: word;
begin
  { Sets the BDE environment }
  with dbEnv do
  begin
    StrPCopy(szWorkDir, sPath);
    StrPCopy(szIniFile, '');
    bForceLocalInit := True;
    StrPCopy(szLang, '');
    StrPCopy(szClientName, 'dbClientName');
  end;
  { Initalizes BDE with the environment dbEnv }
  if DbiInit(@dbEnv) <> DbiERR_NONE then
    raise Exception.Create(ERR_ALIASOPENBDE);
  { Adds the specified Alias }
  if sDriver = 'STANDARD' then
    dbRes := DbiAddAlias(nil, pchar(sAlias), nil,
      pchar('PATH:' + sPath), PersistentAlias)
  else
    dbRes := DbiAddAlias(nil, pchar(sAlias), pchar(sDriver),
      pchar('PATH:' + sPath), PersistentAlias);
  case dbRes of
    DbiERR_INVALIDPARAM:
      raise Exception.Create(ERR_ALIASINVALIDPARAM);
    DbiERR_NAMENOTUNIQUE:
      begin
        resp := MessageBox(0, pchar(Format(ERR_ALIASALREADYEXISTS, [sAlias])),
          'CreateAlias', MB_ICONSTOP + MB_YESNO);
        if Resp = ID_YES then
        begin
          Check(DbiDeleteAlias(nil, pchar(sAlias)));
          CreateAlias(sAlias, sPath, sDriver, PersistentAlias);
        end;
      end;
    DbiERR_UNKNOWNDRIVER:
      raise Exception.Create(ERR_ALIASDRIVERNOTFOUND);
  end;
  if DbiExit <> DbiERR_NONE then
    raise Exception.Create(ERR_ALIASCLOSEBDE);
end; {CreateAlias}

The parameters for this function are:

sAlias: Name of the new alias to be created

sPath: Full path of the directory to which the alias should point. With little adjustments, this function can be used to create any kind of aliases, and, instead of passing the path info in this argument, pass all the parameters needed by the driver to create the alias.

sDriver: Name of an existing BDE driver, such as PARADOX, DBASE, STANDARD

PersistentAlias: Determines whether the new alias will be for future use (persistent) or just for the actual session.

Example of usage:

CreateAlias('DBTEST', 'c:\progra~1\borland\delphi~1\projects\cd3\data', 'PARADOX',
  true);

2009. szeptember 9., szerda

How to change the mouse speed


Problem/Question/Abstract:

How to change the mouse speed

Answer:

Solve 1:

The mouse speed is changed in seven steps. Adjust the values in mspeed, if you like.


unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    Label1: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormClose(Sender: TObject; var Action: TCloseAction);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

var
  check: array[0..2] of Integer;

const
  mspeed: array[0..6, 0..2] of Integer = ((0, 0, 0), (10, 0, 1), (7, 0, 1), (4, 0, 1), (4, 10, 2), (4, 7, 2), (4, 4, 2));
  tempo: Integer = -1;

procedure step;
begin
  inc(tempo);
  if tempo > 6 then
    tempo := 0;
  systemparametersinfo(SPI_SETMOUSE, 1, @mspeed[tempo], 0);
  form1.label1.caption := 'Step: ' + inttostr(tempo + 1);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  systemparametersinfo(SPI_GETMOUSE, 0, @check, 0);
  step;
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
begin
  systemparametersinfo(SPI_SETMOUSE, 1, @check, 0);
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  step;
end;

end.


Solve 2:

Use SystemParameters info as shown below.


type
  TMouseTresholdSpeed = array[0..2] of Cardinal;

function GetMouseTresholdSpeed: TMouseTresholdSpeed;
{ Retrieves the two mouse threshold values and the mouse speed }
begin
  SystemParametersInfo(SPI_GETMOUSE, 0, @Result, 0);
end;

procedure SetMouseTresholdSpeed(Value: TMouseTresholdSpeed);
{ Sets the two mouse threshold values and the mouse speed }
begin
  SystemParametersInfo(SPI_SETMOUSE, 0, @Value, 0);
end;

2009. szeptember 8., kedd

Display the full text search tab of a help file


Problem/Question/Abstract:

You know how to display the table of contents of a help file, but how can you switch directly to the full text search tab?

Answer:

Solve 1:

You can use the Winhelp macro "Find()". Here is an example:

procedure TForm1.ShowFulltextsearch;
var
  command: array[0..255] of Char;
begin
  command := 'FIND()';

  {Ensure that the Application.HelpFile is displayed}
  application.helpcommand(HELP_FORCEFILE, 0);

  {Open the finder window and switch to the search tab}
  application.helpcommand(HELP_COMMAND, Longint(@command));
end;


Solve 2:

procedure ShowHelpTab;
const
  Tab = 15;
  Find = -1;
  Index = -2;
  Contents = -3;
begin
  Application.HelpCommand(Tab, Find);
end;

2009. szeptember 7., hétfő

How to trap the system error when a user tries to access a floppy drive with no disk in it


Problem/Question/Abstract:

How can I trap the system error that results from a user trying to access a floppy drive that has no disk in it. I want to show my own error message etc., rather than the system one.

Answer:

procedure TForm1.Button1Click(Sender: TObject);
var
  ErrorMode: word;
begin
  ErrorMode := SetErrorMode(SEM_FAILCRITICALERRORS);
  try
    if DiskSize(1) = -1 then
      ShowMessage('No disk in drive A')
    else
    begin
      { Your code }
    end;
  finally
    SetErrorMode(ErrorMode);
  end;
end;

2009. szeptember 6., vasárnap

Custom sort a TCheckListBox


Problem/Question/Abstract:

I created a component TMyCheckListBox which inherited from TCheckListBox, and I want to sort the list of items in numeric not alphabetic order. Although I set the Sorted property to True, I cannot sort it in the way I want. I need to have a numeric sort with the Sorted property set to True.

Answer:

The Sorted property calls the built-in capability of the underlying windows control to sort itself alphabetically. In case you need to perform a custom sorting, just turn the Sorted property off, and do the sorting by using TStringList CustomSort method. Below is the sequence of possible steps

Assign listbox's items to the string list. Perform custom sorting by calling the CustomSort method. You should pass a function that compares two strings in the string list as parameter (see example below for details). Move items back to the listbox. Here's an example. It resorts the list's content in custom order:

{This function sorts items in the list}

function CompareStrings(List: TStringList; Index1, Index2: Integer): Integer;
var
  XInt1, XInt2: integer;
begin
  try
    XInt1 := strToInt(List[Index1]);
    XInt2 := strToInt(List[Index2]);
  except
    XInt1 := 0;
    XInt2 := 0;
  end;
  Result: = XInt1 - XInt2;
end;

procedure TForm1.SpeedButton5Click(Sender: TObject);
var
  XList: TStringList;
begin
  XList := TStringList.Create;
  CheckListBox1.Items.BeginUpdate;
  try
    XList.Assign(CheckListBox1.Items);
    XList.CustomSort(CompareStrings);
    CheckListBox1.Items.Assign(XList);
  finally
    XList.Free;
    CheckListBox1.Items.EndUpdate;
  end;
end;

2009. szeptember 5., szombat

Reading the IIS Meta Base


Problem/Question/Abstract:

In this article I am going to show you how to how to access to IIS Meta Base (in read-only mode). You can simply take this further to use the techniques shown here for administrative purposes. The IIS Meta Base is used to install web, ftp and gopher sites on your MS Windows server. This feature may be interesting for your installer applications.
You need MS Windows 2000 with IIS or MS Windows NT 4 SP 6a with IIS installed.

Answer:

IN THE BEGINNING

Many of you develop, just as I do, Internet server applications. After weeks of planning and testing your applications you are ready to deploy it. Now you have to write a detailed explaination of how to install and administrate your application. At this point this article will give you a little head start. Windows has the Registry, which is a great tool for administrating many aspects of the computer, however, not all aspects are administratable through the Registry.

The IIS has to be administrated through the IIS Metabase. In your {system32}\inetsrv\iisadmin folder there are many ASP examples on how to access the IIS Metabase, however, these are not easily taken to Delphi. Starting with the GetObject function, that does not exist in Delphi, going to enumarations and so on.

VB SCRIPTS GetObject

Scanning through the ASP files in the IISAdmin folder you will hit the GetObject function quite a few times. The GetObject will return the interface to an object already loaded into the computers memory. The object is named by a string similar to 'IIS://localhost'. GetObject will allow you to get access to objects running on other computers, too. In one of the D3K articles I have found a method that will compromise for this VB Script function. I have named the function VBGetObject, because as a function with this name already exists in Delphi.

function VBGetObject(const Name: string): IDispatch;
var
  BindContext: IBindCtx;
  Moniker: IMoniker;
  Eaten: Integer;
begin
  OleCheck(CreateBindCtx(0, BindContext));
  OleCheck(MkParseDisplayName(
    BindContext, PWideChar(WideString(Name)), Eaten, Moniker
    ));
  OleCheck(Moniker.BindToObject(BindContext, nil, IDispatch, Result));
end;

ENUMERATIONS (COLLECTIONS) IN DELPHI

VBScript has the nice construct for each ... in ..., a simple and fast way to access all objects (items) in an list ("array") of those. There is no similar construct in Delphi to use, well you want need it anyway, we'll work around it. :)

Usually all enumerations have a count property and an items property as well, however Micrsoft decided to NOT implement these in these ADSI classes properly. Therefore, we cannot us them in an for I := 0 to Pred(Count) do type of construction. We rather have to access the enumeration object and simulate the for...each loop ourselves.

procedure DoEnum(Cont: IADsContainer[...]);
var
  I: Cardinal;
  Enum: IEnumVariant;
  rgvar: OleVariant;
  [...]
begin
  try
    // get a hold on the variant collection
    Enum := Cont._NewEnum as IEnumVariant;
    Enum.Reset;
    Enum.Next(1, rgvar, I);
    // enumerate the variant collection
    while I > 0 do
    begin
      [...]
        Enum.Next(1, rgvar, I);
    end;
  except
  end;
end;

The function above is taken from the source below with a few parts omitted to show the basic idea of enumerations. First we get the Enumeration object and cast it as IEnumVariant, the default VB Script enumeration type. Next, we reset the enumeration, just in case and then we get the first item for the enumeration. We loop through the enumeration until no item is returned anymore. That's all.

CREATING THE APPLICATION

Start Delphi and create a new application (in case another is still open). The following code will assume a few component names, please add them accordingly:

your main form: NAME=frmMain

TTreeView: NAME=trvMBStructure ALIGN=alLeft

TListView: NAME=lstMBItems ALIGN=alClient VIEWSTYLE=vsReport, add three Columns to the list CAPTIONS=(Property,Type,Value)

TStatusBar: NAME=sttInfo SIMPLEPANEL=True


Save the Unit1 as uMainForm.pas.

Next go to the menu "Project|Import Type Library...". (NOTE: This step may not work on Delphi 6 properly - sorry, you will have to wait for the first service pack. :( ) Click the "Add.." button and select the "activeds.tlb" from your "Winnt\System32" directory. Select a unit directory and click the "Create Unit" button. The file "ActiveDs_TLB.pas" will be created.

Next paste the code from below and run your application. I hope the comments will give you all information you need.

THE CODE

unit uMainForm;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs,
  ExtCtrls, ComCtrls, ActiveDs_TLB, Contnrs;

type
  TADsContainer = class
  private
    FIntf: IADsContainer;
    FPath: string;
    FProperties: TStringList;
    function GetADsClass: IADsClass;
    procedure LoadProperties;
  protected
  public
    constructor Create(aIntf: IADsContainer);
    destructor Destroy; override;

    property Path: string read FPath;
    property Intf: IADsContainer read FIntf;
    property ADsClass: IADsClass read GetADsClass;
    property Properties: TStringList read FProperties;
  end;

  TfrmMain = class(TForm)
    trvMBStructure: TTreeView;
    splDummy: TSplitter;
    lstMBItems: TListView;
    sttInfo: TStatusBar;
    procedure FormCreate(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure trvMBStructureChange(Sender: TObject; Node: TTreeNode);
  private
    FADsContainer: TObjectList;
    procedure EnumIISMetaBase;
    procedure ShowItemInfo(ADsContainer: TADsContainer);
  public
  end;

var
  frmMain: TfrmMain;

implementation

uses
  ActiveX, ComObj;

{$R *.DFM}

// simulates VB Scripts GetObject - a method to get an instance to an already
// loaded object in memory

function VBGetObject(const Name: string): IDispatch;
var
  BindContext: IBindCtx;
  Moniker: IMoniker;
  Eaten: Integer;
begin
  OleCheck(CreateBindCtx(0, BindContext));
  OleCheck(MkParseDisplayName(
    BindContext, PWideChar(WideString(Name)), Eaten, Moniker
    ));
  OleCheck(Moniker.BindToObject(BindContext, nil, IDispatch, Result));
end;

{ TADsContainer }

constructor TADsContainer.Create(aIntf: IADsContainer);
begin
  inherited Create;
  FIntf := aIntf;
  FProperties := TStringList.Create;
  FPath := IADsSyntaxDisp(FIntf).ADsPath;
  LoadProperties;
end;

destructor TADsContainer.Destroy;
begin
  FreeAndNil(FProperties);
  FIntf := nil;
  inherited Destroy;
end;

function TADsContainer.GetADsClass: IADsClass;
begin
  Result := VBGetObject(IADsODisp(FIntf).Schema) as IADsClass;
end;

procedure TADsContainer.LoadProperties;
var
  I: Integer;
  Props: OleVariant;
begin
  // iis objects can have mandatory and optional properties
  // the must be loaded seperately
  // the IADS objects will return a safe-array if there are more than one
  // properties, a OleString will be returned if there is just one property
  FProperties.Clear;
  // load mandatory properties
  Props := ADsClass.MandatoryProperties;
  if VarType(Props) and varArray = varArray then
    for I := VarArrayLowBound(Props, 1) to VarArrayHighBound(Props, 1)
      do
      FProperties.Add(Props[I])
  else
    FProperties.Add(Props);
  // load optional properties
  Props := ADsClass.OptionalProperties;
  if VarType(Props) and varArray = varArray then
    for I := VarArrayLowBound(Props, 1) to VarArrayHighBound(Props, 1)
      do
      FProperties.Add(Props[I])
  else
    FProperties.Add(Props);
end;

{ TfrmMain }

procedure TfrmMain.EnumIISMetaBase;
  procedure DoEnum(Cont: IADsContainer; Parent: TTreeNode; Path: string);
  var
    I: Cardinal;
    Enum: IEnumVariant;
    rgvar: OleVariant;
    Node: TTreeNode;
    ADsContainer: TADsContainer;
  begin
    try
      // get a hold on the variant collection
      Enum := Cont._NewEnum as IEnumVariant;
      Enum.Reset;
      Enum.Next(1, rgvar, I);
      // enumerate the variant collection
      while I > 0 do
      begin
        // create a tree node for every item in the collection
        Node := trvMBStructure.Items.AddChild(Parent, rgvar.Name);
        ADsContainer := TADsContainer.Create(IDispatch(rgvar) as IADsContainer);
        FADsContainer.Add(ADsContainer);
        Node.Data := ADsContainer;
        // enumerate sub-items
        DoEnum(ADsContainer.Intf, Node, ADsContainer.Path);
        Enum.Next(1, rgvar, I);
      end;
    except
    end;
  end;
var
  Root: string;
begin
  trvMBStructure.Items.BeginUpdate;
  try
    // clear previous
    trvMBStructure.Items.Clear;
    FADsContainer.Clear;
    // you could enumerate other objects, like LDAP, too
    Root := 'IIS://LocalHost';
    DoEnum(VBGetObject(Root) as IADsContainer, nil, Root);
  finally
    trvMBStructure.Items.EndUpdate;
  end;
end;

procedure TfrmMain.FormCreate(Sender: TObject);
begin
  FADsContainer := TObjectList.Create;
  // load the iis meta base
  EnumIISMetaBase;
end;

procedure TfrmMain.FormDestroy(Sender: TObject);
begin
  FreeAndNil(FADsContainer);
end;

procedure TfrmMain.ShowItemInfo(ADsContainer: TADsContainer);
var
  I: Integer;
  PropName: string;
  LI: TListItem;
begin
  lstMBItems.Items.BeginUpdate;
  try
    lstMBItems.Items.Clear;
    if ADsContainer <> nil then
    begin
      // show current iis path
      sttInfo.SimpleText := ADsContainer.Path;
      // iterate all properties, skip the first ('')
      for I := 1 to Pred(ADsContainer.Properties.Count) do
      begin
        LI := lstMBItems.Items.Add;
        // get the property name
        PropName := ADsContainer.Properties.Strings[I];
        // load property name
        LI.Caption := PropName;
        // get property type
        LI.SubItems.Add('0x' + IntToHex(VarType(
          IADsDisp(ADsContainer.Intf).Get(PropName)
          ), 8));
        // get property value
        case VarType(IADsDisp(ADsContainer.Intf).Get(PropName)) of
          varEmpty:
            LI.SubItems.Add('(value is empty)');
          varNull:
            LI.SubItems.Add('(value is null)');
          varSmallint, varInteger, varSingle, varDouble, varCurrency,
            varDate, varOleStr, varBoolean:
            LI.SubItems.Add(IADsDisp(ADsContainer.Intf).Get(PropName));
        else
          LI.SubItems.Add('(data type not handled)');
        end;
      end;
    end
    else
    begin
      sttInfo.SimpleText := '';
    end;
  finally
    lstMBItems.Items.EndUpdate;
  end;
end;

procedure TfrmMain.trvMBStructureChange(Sender: TObject; Node: TTreeNode);
begin
  if Node = nil then
    ShowItemInfo(nil)
  else
    ShowItemInfo(TADsContainer(Node.Data));
end;

end.

2009. szeptember 4., péntek

How to make the Enter key act as the Tab key while inside a TDBGrid


Problem/Question/Abstract:

How to make the Enter key act as the Tab key while inside a TDBGrid

Answer:

This code also includes the processing of the [Enter] key for the entire application - including fields, etc. The grid part is handled in the ELSE portion of the code. The provided code does not mimic the behavior of the key stepping down to the next record when it reaches the last column in the grid - it moves back to the first column.

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
{This is the event handler for the form's OnKeyPress event!}
{You should also set the Form's KeyPreview property to True}
begin
  if Key = #13 then { if it's an Enter key }
    if not (ActiveControl is TDBGrid) then { if not on a TDBGrid }
    begin
      Key := #0; { eat Enter key }
      Perform(WM_NEXTDLGCTL, 0, 0); { move to next control }
    end
    else if (ActiveControl is TDBGrid) then { if it is a TDBGrid }
      with TDBGrid(ActiveControl) do
        if selectedindex < (fieldcount - 1) then { increment the field }
          selectedindex := selectedindex + 1
        else
          selectedindex := 0;
end;

2009. szeptember 3., csütörtök

Creating a system wide shortcut or hotkey


Problem/Question/Abstract:

How to create and handle a system wide shortcut or hotkey (one that is handled beyond the application)

Answer:

{**********************************************************

  Copyright &copy; by Jim McKeeth
  Licensed under LGPL
  ( http://www.gnu.org/licenses/licenses.html#LGPL )


  Demo of creating a system wide hotkey
  or shortcut

This was written in Delphi 7,
but should work in most other versions
(but obviously not Kylix)

  You need a form with
  1) a THotKey named HotKey1
  2) a TCheckBox named CheckBox1

  To demo
  1) Change the hotkey in the value
  2) Check the box
  3) Minimize the application
  4) Press the hot key
  5) Be impressed

**********************************************************}

unit SystemHotKeyUnit1;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics,
  Controls, Forms, StdCtrls, ComCtrls, Dialogs,
  // Menus need to be added for calls in the code
  Menus;

type
  TForm1 = class(TForm)
    HotKey1: THotKey;
    CheckBox1: TCheckBox;
    procedure FormCreate(Sender: TObject);
    procedure CheckBox1Click(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
  private
    { Private declarations }
  protected
    // Handle the global hot key messages when they are sent to the window
    procedure HotyKeyMsg(var msg: TMessage); message WM_HOTKEY;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

var
  myAtom: integer;

function ShiftState2Modifier(const Shift: TShiftState): Word;
begin
  Result := 0;
  if ssShift in Shift then
    Result := Result or MOD_SHIFT;
  if ssAlt in Shift then
    Result := Result or MOD_ALT;
  if ssCtrl in Shift then
    Result := Result or MOD_CONTROL;
end;

function GetShortCutKey(ShortCut: TShortCut): Word;
var
  shift: TShiftState;
begin
  ShortCutToKey(ShortCut, Result, shift); // call in Menus!
end;

function GetShortCutModifier(ShortCut: TShortCut): Word;
var
  key: Word;
  shift: TShiftState;
begin
  ShortCutToKey(ShortCut, key, shift); // call in Menus!
  Result := ShiftState2Modifier(shift);
end;

function RegisterHotShortCut(const h: THandle; const Atom: integer; const ShortCut:
  TShortCut): Boolean;
var
  key: Word;
  Shift: TShiftState;
begin
  UnregisterHotKey(h, Atom); // call in Windows
  ShortCutToKey(ShortCut, key, shift);
  Result := RegisterHotKey(h, Atom, ShiftState2Modifier(Shift), key);
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  // you need to type cast it as a pChar if you are using a string
  myAtom := GlobalAddAtom(pchar('HotKeyDemo'));
end;

procedure TForm1.FormDestroy(Sender: TObject);
begin
  UnregisterHotKey(Handle, myAtom);
  GlobalDeleteAtom(myAtom);
end;

procedure TForm1.CheckBox1Click(Sender: TObject);
begin
  if CheckBox1.Checked then
    RegisterHotShortCut(Handle, myAtom, HotKey1.HotKey)
  else
    UnregisterHotKey(Handle, myAtom);
end;

procedure TForm1.HotyKeyMsg(var msg: TMessage);
begin
  if (msg.LParamLo = GetShortCutModifier(HotKey1.HotKey))
    and (msg.LParamHi = GetShortCutKey(HotKey1.HotKey)) then
  begin
    Application.BringToFront;
    Showmessage('Hey, now that is a system wide hot key!')
  end;
end;

end.

2009. szeptember 2., szerda

How to copy all settings from a MenuItem made at designtime to a MenuItem created at runtime


Problem/Question/Abstract:

I am trying to make it possible for my program to create new MenuItems at runtime. I want to copy the settings from a MenuItem made at designtime to the new MenuItems. All the properties and event handlers.

Answer:

procedure CopyMenuRaw(const Source, MenuTest: TMenuItem);
begin
  MenuTest.Caption := Source.Caption;
  MenuTest.Checked := Source.Checked;
  MenuTest.Default := Source.Default;
  MenuTest.Enabled := Source.Enabled;
  MenuTest.GroupIndex := Source.GroupIndex;
  MenuTest.HelpContext := Source.HelpContext;
  MenuTest.Hint := Source.Hint;
  MenuTest.ImageIndex := Source.ImageIndex;
  MenuTest.Name := Source.Name;
  MenuTest.RadioItem := Source.RadioItem;
  MenuTest.Tag := Source.Tag;
  MenuTest.Visible := Source.Visible;
  MenuTest.OnClick := Source.OnClick;
  MenuTest.OnDrawItem := Source.OnDrawItem;
  MenuTest.OnMeasureItem := Source.OnMeasureItem;
end;

procedure CopyMenuItem(var Source, MenuTest: TMenuItem);
begin
  MenuTest := TMenuItem.Create(nil);
  CopyMenuRaw(Source, MenuTest);
end;

Used like this:

CopyMenuItem(WholeScreen1, MenuTest);

Where WholeScreen1 is an existing TMenuItem and MenuTest is TMenuItem that is defined in
your Var section.

2009. szeptember 1., kedd

How to draw a circle pixel by pixel


Problem/Question/Abstract:

How to draw a circle pixel by pixel

Answer:

{ ... }

implementation

{$R *.DFM}

uses
  Math;

procedure DrawCircle(CenterX, CenterY, Radius: Integer; Canvas: TCanvas; Color:
  TColor);

  procedure PlotCircle(x, y, x1, y1: Integer);
  begin
    Canvas.Pixels[x + x1, y + y1] := Color;
    Canvas.Pixels[x - x1, y + y1] := Color;
    Canvas.Pixels[x + x1, y - y1] := Color;
    Canvas.Pixels[x - x1, y - y1] := Color;
    Canvas.Pixels[x + y1, y + x1] := Color;
    Canvas.Pixels[x - y1, y + x1] := Color;
    Canvas.Pixels[x + y1, y - x1] := Color;
    Canvas.Pixels[x - y1, y - x1] := Color;
  end;

var
  x, y, r: Integer;
  x1, y1, p: Integer;
begin
  x := CenterX;
  y := CenterY;
  r := Radius;
  x1 := 0;
  y1 := r;
  p := 3 - 2 * r;
  while (x1 < y1) do
  begin
    plotcircle(x, y, x1, y1);
    if (p < 0) then
      p := p + 4 * x1 + 6
    else
    begin
      p := p + 4 * (x1 - y1) + 10;
      y1 := y1 - 1;
    end;
    x1 := x1 + 1;
  end;
  if (x1 = y1) then
    plotcircle(x, y, x1, y1);
end;

Used like this:

procedure TForm1.Button1Click(Sender: TObject);
begin
  DrawCircle(ClientWidth div 2, ClientHeight div 2, Min(ClientWidth div 2,
    ClientHeight div 2), Canvas, clBlack);
  {Add Math to the uses clause for the Min function}
end;