2007. szeptember 30., vasárnap

Adding password security to your app with paradox


Problem/Question/Abstract:

Adding password security to your app with paradox

Answer:

I'm using a method to implement password security to your app using paradox.

First you make a table having a username field, name, password etc.. and you add Rights fiels type alpha(100) and a Level field of the same type.
Then in the app you got to use an actionlist to center all the commands your users will have access to.
The key here is to manage which action the user has right to use base in the rights field and (optionally) what level of access tha user has for the given action.

I use the tag property of each action in the actionlist to put the value of an accii char.
So, the first action will have a tag value o 65 the next 66,and so on.

Whe the app starts and acepts a user, it must check the chars of the Rights field againts each action's tag, if there's a macht that action is enabled.
So if a user has 'ABCZDH' in his/her rights field then will only have access to those actions marked by the asccii value in their tags.

Off course all of this comes with a nice screen where an 'administrador' can activate/deactivate the users's rights.

2007. szeptember 29., szombat

Get special Windows folder location (2)


Problem/Question/Abstract:

How to get special Windows folder location

Answer:

The two key functions to browse through those virtual Windows folders are

SHGetSpecialFolderLocation();

and

SHGetPathFromIDList()

They can be used as shown in the sample code. You may replace the CSIDL_PROGRAMS constant with another one from the list in the comment below.

uses ShlObj, ActiveX;

procedure TForm1.Button1Click(Sender: TObject);
var
  BI: TBrowseInfo;
  Buf: PChar;
  Dir,
    Root: PItemIDList;
  Alloc: IMalloc;
begin
  SHGetMalloc(Alloc);
  Buf := Alloc.Alloc(Max_Path);

  // CSIDL_BITBUCKET  RecycleBin
  // CSIDL_CONTROLS   ControlPanel
  // CSIDL_DESKTOP    Desktop
  // CSIDL_DRIVES     My Computer
  // CSIDL_FONTS      Fonts
  // CSIDL_NETHOOD    Network Neighborhood
  // CSIDL_NETWORK    The virtual version of the above
  // CSIDL_PERSONAL   'Personal'
  // CSIDL_PRINTERS   printers
  // CSIDL_PROGRAMS   Programs in the Start Menu
  // CSIDL_RECENT     Recent Documents
  // CSIDL_SENDTO     Folder SendTo
  // CSIDL_STARTMENU  The whole Start menu
  // CSIDL_STARTUP    The Autostart Group
  // CSIDL_TEMPLATES  Document templates

  // use of the constants above
  SHGetSpecialFolderLocation(Handle, CSIDL_PROGRAMS, Root);

  with BI do
  begin
    hwndOwner := Form1.Handle;
    // NIL means show all
    pidlRoot := Root;
    pszDisplayName := Buf;
    lpszTitle := 'Choose Folder';
    ulFlags := 0;
    lpfn := nil;
  end;

  try
    Dir := SHBrowseForFolder(BI);
    if Dir <> nil then
    begin
      SHGetPathFromIDList(Dir, Buf);
      ShowMessage(Buf);
      Alloc.Free(Dir);
    end;
  finally
    Alloc.Free(Root);
    Alloc.Free(Buf);
  end;
end;

2007. szeptember 28., péntek

How to programmatically change the size of the TOpenDialog window


Problem/Question/Abstract:

Is there a way to programmatically change the size of the TOpenDialog window so that more files will be shown? In Win98, the user can drag the dialog window to increase its size. Can the window size be increased under program control?

Answer:

The OnShow event seems to be a bit too early to do it. It has to be delayed a bit. Like this:

type
  TForm1 = class(TForm)
    Button1: TButton;
    OpenDialog1: TOpenDialog;
    procedure Button1Click(Sender: TObject);
    procedure OpenDialog1Show(Sender: TObject);
  private
    { Private declarations }
    procedure MoveDialog(var Msg: TMessage); message WM_USER;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.Button1Click(Sender: TObject);
begin
  if OpenDialog1.Execute then
    Caption := OpenDialog1.FileName;
end;

procedure TForm1.OpenDialog1Show(Sender: TObject);
begin
  PostMessage(Self.Handle, WM_USER, 0, 0);
end;

function GetDesktopWorkArea: TRect;
begin
  if not SystemParametersInfo(SPI_GETWORKAREA, 0, @Result, 0) then
    Result := Rect(0, 0, Screen.Width, Screen.Height);
end;

procedure TForm1.MoveDialog(var Msg: TMessage);
var
  rec: TRect;
  wh: HWND;
  l, t, r, b: Integer;
begin
  wh := Windows.GetParent(OpenDialog1.Handle);
  {if GetWindowRect(wh, rec) then}
  if IsWindow(wh) then
  begin
    rec := GetDesktopWorkArea;
    l := rec.Left;
    t := rec.Top;
    r := rec.Right;
    b := rec.Bottom;
    MoveWindow(wh, l, t, r, b, True);
  end;
end;

2007. szeptember 27., csütörtök

Reorder a TPageControl at runtime


Problem/Question/Abstract:

Let's say we have a TPageControl with 15 pages, the design time PageIndex of each page is 0 to 14 respectively. Now we want to change this order at runtime. The new order is saved in an *.ini file, the values for the new order is stored in an array of integer we will call MyOrder. Here is what the code would look like to change the order:

var
  i: Integer;

  for i := 0 to PageControl1.PageCount - 1 do
    PageControl1.Pages[i].PageIndex = MyOrder[i];

After executing this block of code the order of the pages is unchanged. Can you tell my why this doesn't work?

Answer:

Solve 1:

Try this chunk of code. The key is that you set the 0th page index, then the 1rst, then the 2nd and so on. Since you're doing them in order, they don't get screwed up by later assignments.

procedure TForm1.ReorderBtnClick(Sender: TObject);
const
  NewOrder: array[0..6] of Integer = (2, 0, 6, 1, 5, 3, 4);
var
  X: Integer;
  OrigPages: array of TTabSheet;
begin
  {Keep an ordered list of tab sheets}
  SetLength(OrigPages, 7);
  for X := 0 to 6 do
    OrigPages[X] := PageControl.Pages[X];
  {Reorder tab sheets}
  for X := 0 to 6 do
    OrigPages[NewOrder[X]].PageIndex := X;
  {Release ordered list of tab sheets}
  OrigPages := nil;
end;


Solve 2:

This won't make order of the pages unchanged, but it won't give you the order you expect either. Remember that while you are re-ordering the pagecontrol, the pagecontrol also reorders itself. For example, you have a pagecontrol with 5 tabs, labelled One, Two, Three, Four and Five. If you try to reorder so that these pages move to the positions 3, 5, 1, 4, 2 respectively, then the order your code will put them in will be Four, Three, Two, One, Five because of the way they get reordered by the pagecontrol while you are moving them around.

Now, the really cool thing, and what seems to be happening to you, is that if you apply the exact same reordering (using your algorithm) as first used, the tabs will go back to the positions that they started from! Step through your code, and I'm sure your reordering routine will be called twice. To reorder them successfully, you would simply use a search and position algorithm, working from the last tab position back to the first to get around the pagecontrol's own re-ordering. I'm not sure from your post whether your array of positions is "for this position, this is the page that goes here" or "for this page, this is the position it should be in", so here is how to re-order your pagecontrol. We'll use the Tag property to let the page remember where it should be. Use only *one* of the first two commented for-loops, depending on your data structure.

{ ... }
var
  i, j: Integer;
begin
  {Use this loop if the following describes your array:
  "For this position (index), this is the page that goes here (contents)"
  (that is, the contents of the array is the current pageindex, and the array
  index is the new pageindex for that page)}

  for i := 0 to PageControl1.PageCount - 1 do
    PageControl1.Pages[MyOrder[i]].Tag := i;

  {OR use this loop if the following describes your array:
  "For this page (index), this is the position it should be in (contents)"
  (That is, the array index corresponds to the current pageindex, and the contents
  of the array is the new pageindex that page should have)}

  for i := 0 to PageControl1.PageCount - 1 do
    PageControl1.Pages[i].Tag := MyOrder[i];

  {Then simply reorder the pagecontrol}
  for i := PageControl1.PageCount - 1 downto 0 do
    for j := 0 to i do
      if PageControl1.Pages[j].Tag = i then
      begin
        PageControl1.Pages[j].PageIndex := i;
        Break;
      end;
end;

One of the two will work for you.


Solve 3:

The correct method to disconnect tabsheets and reconnect them:

{ ... }
var
  I: Integer;
  L: TList;
begin
  L := TList.Create;
  try
    {This just disconnects - you should replace this with your code to disconnect
                them in the order you want to reconnect them}
    for I := PageControl1.PageCount - 1 downto 0 do
    begin
      L.Add(PageControl1.Pages[I]);
      PageControl1.Pages[I].PageControl := nil;
    end;
    {Reconnect...}
    for I := 0 to L.Count - 1 do
      TTabSheet(L[I]).PageControl := PageControl1;
  finally
    L.Free;
  end;
end;

2007. szeptember 26., szerda

Create a sizeable form with a 3D look


Problem/Question/Abstract:

How to create a sizeable form with a 3D look

Answer:

Try these handlers for the WMNCPaint and WMNCHitTest messages. The form should have the Sizeable border style as the code uses the sizeable border area for drawing the 3D effect, whether or not you want the user to be able to resize. To prohibit resizing, include the WMNCHitTest handler, to allow it, leave it out.

procedure TForm1.WMNCPaint(var Msg: TWMNCPaint);
var
  DC: HDC;
  Frame_H: Integer;
  Frame_W: Integer;
  Menu_H: Integer;
  Caption_H: Integer;
  Frame: TRect;
  Extra: Integer;
  Canvas: TCanvas;
begin
  { Predetermine some window parameters }
  Frame_W := GetSystemMetrics(SM_CXFRAME);
  Frame_H := GetSystemMetrics(SM_CYFRAME);
  if (Menu <> nil) then
    Menu_H := GetSystemMetrics(SM_CYMENU)
  else
    Menu_H := -1;
  Caption_H := GetSystemMetrics(SM_CYCAPTION);
  GetWindowRect(Handle, Frame);
  Frame.Right := Frame.Right - Frame.Left - 1;
  Frame.Left := 0;
  Frame.Bottom := Frame.Bottom - Frame.Top - 1;
  Frame.Top := 0;
  { Let standard frame draw }
  inherited;
  { Repaint frame area in 3-D style }
  DC := GetWindowDC(Handle);
  Canvas := TCanvas.Create;
  try
    with Canvas do
    begin
      Handle := DC;
      { Left and Top edges }
      Pen.Color := clBtnShadow;
      PolyLine([Point(Frame.Left, Frame.Bottom), Point(Frame.Left, Frame.Top),
        Point(Frame.Right, Frame.Top)]);
      { Right and Bottom edges }
      Pen.Color := clWindowFrame;
      PolyLine([Point(Frame.Left, Frame.Bottom), Point(Frame.Right, Frame.Bottom),
        Point(Frame.Right, Frame.Top - 1)]);
      { Left and Top edge, 1 pixel in }
      Pen.Color := clBtnHighlight;
      PolyLine([Point(Frame.Left + 1, Frame.Bottom - 1), Point(Frame.Left + 1,
        Frame.Top + 1),
        Point(Frame.Right - 1, Frame.Top + 1)]);
      { Right and Bottom edge, 1 pixel in }
      Pen.Color := clBtnFace;
      PolyLine([Point(Frame.Left + 1, Frame.Bottom - 1), Point(Frame.Right - 1,
        Frame.Bottom - 1),
        Point(Frame.Right - 1, Frame.Top)]);
      { Remainder of Sizing border }
      for Extra := 2 to (GetSystemMetrics(SM_CXFRAME) - 1) do
      begin
        Brush.Color := clBtnFace;
        FrameRect(Rect(Extra, Extra, Frame.Right - Extra + 1, Frame.Bottom - Extra +
          1));
      end;
      { Left and Top Edge of Caption Area }
      Pen.Color := clBtnShadow;
      PolyLine([Point(Frame_W - 1, Frame_H + Caption_H + Menu_H - 1), Point(Frame_W -
        1,
          Frame_H - 1), Point(Frame.Right - Frame_W + 1, Frame_H - 1)]);
      { Right and Bottom Edge of Caption Area }
      Pen.Color := clBtnHighlight;
      PolyLine([Point(Frame_W - 1, Frame_H + Caption_H + Menu_H - 1),
        Point(Frame.Right - Frame_W + 1, Frame_H + Caption_H + Menu_H - 1),
          Point(Frame.Right - Frame_W + 1, Frame_H - 1)]);
    end;
  finally
    Canvas.Free;
    ReleaseDC(Handle, DC);
  end;
end;

procedure TForm1.WMNCHitTest(var Msg: TWMNCHitTest);
var
  HitCode: LongInt;
begin
  inherited;
  HitCode := Msg.Result;
  if ((HitCode = HTLEFT) or (HitCode = HTRIGHT) or (HitCode = HTTOP) or (HitCode =
    HTBOTTOM)
    or (HitCode = HTTOPLEFT) or (HitCode = HTBOTTOMLEFT) or (HitCode = HTTOPRIGHT) or
    (HitCode = HTBOTTOMRIGHT)) then
  begin
    HitCode := HTNOWHERE;
  end;
  Msg.Result := HitCode;
end;

2007. szeptember 25., kedd

Conditional defines for all compiler versions


Problem/Question/Abstract:

Conditional defines for all compiler versions

Answer:

The following standard conditional symbols tell you which compiler is used:

VER80
Delphi 1.x

VER90
Delphi 2.x

VER93
C++ Builder 1.0

VER100
Delphi 3.x

VER120
Delphi 4.x

VER130
Delphi 5.x

VER140
Delphi 6.x

2007. szeptember 24., hétfő

How to copy selected items from a TListBox to the clipboard without using the VCL Clipbrd unit


Problem/Question/Abstract:

I want to copy selected items (only text) from a TListBox that has LBS_EXTENDEDSEL style to the standard clipboard using only API functions.

Answer:

This code is untested and requires the unit APIClipboard from Tip "Clipboard access routines which use only API functions".

procedure CopySelectedListboxItemsToClipboard(listboxwnd: HWND);

  function GetItem(num: Integer): string;
  begin
    SetLength(Result, SendMessage(listboxwnd, LB_GETTEXTLEN, num, 0));
    if Length(Result) > 0 then
      SendMessage(listboxwnd, LB_GETTEXT, num, LPARAM(@Result[1]));
  end;

var
  num: Integer;
  selIndices: array of Integer;
  sl: TStringlist;
  S: string;
begin
  num = SendMessage(listboxwnd, LB_GETSELCOUNT, 0, 0);
  if num = LB_ERR then
  begin
    {listbox is a single selection listbox}
    num := SendMessage(listboxwnd, LB_GETCURSEL, 0, 0);
    if num = LB_ERR then
      Exit; {no selected item}
    S := GetItem(num);
  end
  else
  begin
    SetLength(selIndices, num);
    SendMessage(listboxwnd, LB_GETSELITEMS, num, LPARAM(@selIndices[0]));
    sl := TStringlist.Create;
    try
      for num := 0 to High(selIndices) do
        sl.Add(GetItem(selIndices[num]));
      S := sl.Text;
    finally
      sl.free;
    end;
  end;
  StringToClipboard(S);
end;

2007. szeptember 23., vasárnap

How to automatically drop down the lookup list in a TDBGrid


Problem/Question/Abstract:

I'm trying to do this: On enter in a cell of a DBGrid that is of fkLookup FieldKind type show the lookup list immediately without clicking on the little button that appears when I click in the cell.

Answer:

Solve 1:

Here is a sample how to drop down the lookup list automatically when the user enters a column and the OnColEnter event is fired.

procedure TForm1.DBGrid1ColEnter(Sender: TObject);
const
  MyFieldName: string = 'SomeFieldName';
var
  I: Integer;
  MyGrid: TCustomDBGrid;
begin
  MyGrid := Sender as TCustomDBGrid;
  if MyGrid.SelectedField.FullName = MyFieldName then
  begin
    { Put the grid in edit mode. }
    MyGrid.EditorMode := True;
    { TCustomGrid.InplaceEditor is declared as protected property and
                cannot be addressed directly.
                 Since the inplace editor window is a child window of the grid,
                we can find it. }
    for I := 0 to MyGrid.ControlCount - 1 do
    begin
      if MyGrid.Controls[I] is TInplaceEdit then
        { Simulate an Alt+DownArrow key stroke }
        PostMessage(TWinControl(MyGrid.Controls[I]).Handle, WM_KEYDOWN,
          VK_DOWN, $20000000);
      Break;
    end;
  end;
end;


Solve 2:

In the OnColEnter event, send an Alt-DownArrow keystroke:

{ ... }
if DBGrid1.SelectedField.FieldName = 'fieldname' then
begin
  DBGrid1.EditorMode := True;
  keybd_event(VK_MENU, MapVirtualKey(VK_MENU, 0), 0, 0);
  keybd_event(VK_DOWN, MapVirtualKey(VK_DOWN, 0), 0, 0);
  keybd_event(VK_DOWN, 0, KEYEVENTF_KEYUP, 0);
  keybd_event(VK_MENU, 0, KEYEVENTF_KEYUP, 0)
end;

Or use a cracker class to expose TCustomGrid.InplaceEditor:

type
  TCrackGrid = class(TDBGrid);
  { ... }

procedure TForm1.DBGrid1ColEnter(Sender: TObject);
begin
  with TCrackGrid(DBGrid1) do
    if SelectedField.FieldName = 'fieldname' then
    begin
      EditorMode := True;
      {send Alt-DownArrow keystroke}
      PostMessage(InplaceEditor.Handle, WM_KEYDOWN, VK_DOWN, $20000000)
    end;
end;

2007. szeptember 22., szombat

How to download a file from the web to a local drive


Problem/Question/Abstract:

I want to write an application that can download upgraded versions of itself.

Answer:

Solve 1:

If you are just going to download there is no need to use ActiveX controls. Windows has the function you need already declared in the UrlMon.dll. To download a file to a local disk just use this code. Note: This function is not described in Delphi Help nor in the Win32 Programmer's Reference.

uses
  URLMon;

{ ... }
if URLDownloadToFile(nil, 'http://go.to/masdp', 'c:\index.html', 0, nil) <> 0 then
  MessageBox(Handle, 'An error ocurred while downloading the file.', PChar(Application.Title), MB_ICONERROR or MB_OK);
{ ... }


Solve 2:

Downloading a file is not very difficult, something like:


uses
  Wininet;

var
  InternetBrowserUserAgent: string;
  {Set it as you like. Win98/IE uses 'Mozilla/4.0 (compatible; MSIE 5.5; Windows 98)' }

{ ... }

function GetInternetStream(URL: string; Stream: TStream): LongInt;
type
  TNetBuffer = array[0..1023] of Byte;
  PNetBuffer = ^TNetBuffer;
var
  ihConnect, iDocument: HINTERNET;
  NetBuffer: PNetBuffer;
  BufferSize: Integer;
  I: integer;
begin
  Result := -1;
  ihConnect := InternetOpen(PChar(InternetBrowserUserAgent), LOCAL_INTERNET_ACCESS, '', '', 0);
  try
    if ihConnect <> nil then
    begin
      iDocument := InternetOpenURL(ihConnect, PChar(URL), nil, Cardinal(-1),
        INTERNET_FLAG_RELOAD or INTERNET_FLAG_DONT_CACHE or
        INTERNET_FLAG_RAW_DATA, 0);
      try
        if iDocument <> nil then
        begin
          Result := 0;
          try
            New(NetBuffer);
            repeat
              InternetReadFile(iDocument, NetBuffer, SizeOf(TNetBuffer), BufferSize);
              if BufferSize > 0 then
              begin
                Result := Result + Stream.Write(NetBuffer^, BufferSize);
              end;
            until
              (BufferSize < SizeOf(TNetBuffer));
          finally
            Dispose(NetBuffer);
          end;
        end;
      finally
        internetCloseHandle(iDocument);
      end;
    end;
  finally
    InternetCloseHandle(ihConnect);
  end;
end;


If you call this function with a TFileStream, you have the file on your harddisk. If you have a ZIP, you probably want to unzip the file now, use a component that can do this (I think there are some around). The problem is that an application cannot replace itself (because it is write protected while it is running). The solution would be to call another application and terminate the first one. The second one has to update the first one (maybe wait a while until it is really terminated and not write protect any more) and start it again. If you just have to update non-executable files this is much easier. Another solution would be a separate update-application, that the user can call from somewhere (after he has closed the main application).

2007. szeptember 21., péntek

Creating a form without a title bar


Problem/Question/Abstract:

How can I create a form that doesn't have a caption, but can be re-sized?

Answer:

Solve 1:

As they say, "There's more than one way to skin a cat," and I can't agree more as far as programming is concerned. Let me share a little anecdote with you...

Being the "artistic dude" in my company, I'm always in search of new ways to present information to users. I do this by creating non-standard user interfaces (which I find rather boring), spicing them up with graphics and multimedia features. My philosophy centers around this question: Why should information retrieval be a boring task? Well, it shouldn't. And an extension to this question could be: Why do business programs have to all look the same? Well, they don't. So I choose to build "odd" business user interfaces.

My latest designs have followed game interfacess that use a plethora of high- resolution graphics and captionless forms (this is where it all kicks in). In the past, I didn't need my forms to move anywhere. But as my interfaces have become more complex, I've had to start providing ways to move them. Unfortunately, the method that I employed in the original article here, didn't account for clicking only in a certain area on a form. You just click and hold the mouse button down anywhere on the form, and the form will move. Unfortunately, that isn't always the best solution.

For instance, with one of my forms, I created a "pseudo" caption by aligning a TPanel at the top of the client area of my form. There's a bit more functionality built into the panel, but I wanted it to act very much like a regular caption: a click and drag would drag the form, and a double-click would maximize it. With that in mind, I set about writing the panel's click and drag method using what I originally wrote as a base. It didn't work. So doing a little research and asking a couple of questions around the newsgroups, Kerstin Thaler, a very helpful person, showed me a real cool method for implementing what I needed to do. Here it is:

procedure TMainFrm.Panel1MouseDown(Sender: TObject; Button:
  TMouseButton;
  Shift: TShiftState; X, Y: Integer);
const
  SC_DRAGMOVE = $F012;
begin
  if Button = mbLeft then
  begin
    ReleaseCapture;
    Perform(WM_SYSCOMMAND, SC_DRAGMOVE, 0);
  end;
end;

This is such incredibly easy code! Instead of overriding the default NC_HITTEST message handler, I could accomplish form movement from the MouseDown of my panel! Basically, all the method does is send a WM_SYSCOMMAND message to the form with the SC_DRAGMOVE constant to perform a drag move. Kerstin did say, that the $F012 isn't documented. But hey! the method works and it works well. So if you have a captionless form and want to move it by dragging from one of its child components, this is the way to do it!


Solve 2:

Many folks would say, "Just set the BorderStyle of the form to bsNone and you'll remove the caption." However, there's a problem with that suggestion: Not only do you lose the caption bar, you lose the entire border, which means you can't resize the form. The only way to get around this is to go behind the scenes in Delphi. Fortunately, it's a relatively simple process.

Delphi is not just ObjectPascal; it is also a very effective wrapper of the Windows API (Don't worry, we won't get into the Windows API too much in this article). In Windows, every window is created using one of two standard functions: CreateWindow and CreateWindowEx. CreateWindow makes a window with standard window styles, while CreateWindowEx is the same as CreateWindow, but you can add extended window styles to the window you want to create. (I encourage you to read through the help file for a thorough discussion of these two API calls since I won't be going into detail with these topics.)

When a form is created in Delphi, a call is made to CreateWindowEx &mdash TForm's Create method is the wrapper function for this call &mdash and Create passes a record structure to CreateWindowsEx through a virtual method of TForm called CreateParams.

CreateParams is a virtual method of TForm. This means you can override it which, in turn, means you can change the default style of a window when it's created to suit your particular needs. For our purposes, we want to eliminate the caption. That's easily done by changing the style bits of the LongInt Style field of the TCreateParams structure, the record that's passed to CreateWindowEx. Look at the code; we'll discuss particulars below:

unit NoCap;

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics,
  Controls, Forms, Dialogs, StdCtrls, Buttons, BDE, DB;

type
  TForm1 = class(TForm)
    Button1: TButton;
    procedure Button1Click(Sender: TObject);
  private
    {Here's what we're overriding}
    procedure CreateParams(var Params: TCreateParams); override;
    procedure WMNCHitTest(var Msg: TWMNcHitTest); message WM_NCHITTEST;
  end;

var
  Form1: TForm1;

implementation
{$R *.DFM}

procedure TForm1.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  with Params do
    Style := (Style or WS_POPUP) and (not WS_DLGFRAME);
  {or... Style := Style + WS_POPUP - WS_DLGFRAME; which is the
   equivalent to the above statement}
end;

procedure TForm1.WMNCHitTest(var msg: TWMNCHitTest);
begin
  inherited;
  if (msg.Result = htClient) then
    msg.Result := htCaption;
end;

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

end.

Notice in the line in CreateParams where I set the Style for the form: Style := (Style OR WS_POPUP) AND (NOT WS_DLGFRAME);. My first bit manipulation is Style OR WS_POPUP. This means give me the default style bits and make the window a regular pop-up window with a resizeable border. The second portion says don't include a dialog frame. With respect to this, the WS_DLGFRAME will produce a frame typical of dialog boxes. By masking it out, you remove the title bar. WS_POPUP ensures you have a resizeable border with which to work.

What about the WMNCHitTest message handler? Well, if you have a form with no title bar, you have absolutely no way to move it, because by convention, forms are moved by dragging the title bar. By trapping a mouse hit with the WM_NCHITTEST message and changing the default behavior of the mouse hit, you can allow dragging of the form from the client area.

Read through the Windows API help and look at all the style bits you can set. Play with different combinations to see what you get.

2007. szeptember 20., csütörtök

How to set the printer paper size (2)


Problem/Question/Abstract:

How I can select the printer's papersource through code? How I can read the printer default setting papersource in my Delphi program?

Answer:

{ ... }
var
  aDrvName, aPrtName, aPortName: array[0..127] of Char;
  aDeviceMode: THandle;
  PDevMode: ^TDevMode;
  i: Integer;
begin
  with Printer do
  begin
    GetPrinter(aDrvName, aPrtName, aPortName, aDeviceMode);
    PDevMode := GlobalLock(aDeviceMode);
    try
      if PDevMode^.dmPaperSize = DMPAPER_A4 then
        ShowMessage('A4')
      else if PDevMode^.dmPaperSize = DMPAPER_A3 then
        ShowMessage('A3')
      else
      begin
        ShowMessage('Size not supported!' + #13 + 'Resetting papersize to A4');
        PDevMode^.dmPaperSize := DMPAPER_A4;
      end;
    finally
      GlobalUnlock(aDeviceMode);
    end;
  end;
end;

2007. szeptember 19., szerda

How to save and load a TList to / from a stream


Problem/Question/Abstract:

Can anyone tell me how to stream a TList to a file? Given:

type
  PassWordItem = class(TObject)
    accountName: string[30];
    userName: string[25];
    passWd: string[10];
  end;

and...

PassWdList: TList;

How do I stream the contents of PassWdList to a file, and subsequently load TList from the file?

Answer:

Lets add a couple of methods to the PassWordItem class:


PassWordItem = class(TObject)
public
  accountName: string[30];
  userName: string[25];
  passWd: string[10];

  procedure SaveToStream(s: TStream);
  constructor CreatefromStream(S: TStream);
end;


With that you can write two procedures (or methods of a TPasswordList class derived from TList):


procedure SavePasswordlist(pwl: TLIst; S: TStream);
var
  i: Integer;
begin
  Assert(Assigned(pwl));
  Assert(Assigned(S));
  i := pwl.count;
  S.Write(i, sizeof(i));
  for i := 0 to pwl.count - 1 do
    PasswordItem(pwl[i]).SaveToStream(S);
end;

procedure LoadPasswordList(pwl: TList; S: TStream);
var
  count, n: Integer;
begin
  Assert(Assigned(pwl));
  Assert(Assigned(S));
  S.Read(count, sizeof(count));
  pwl.Capacity := count;
  for n := 1 to count do
    pwl.Add(PasswordItem.CreatefromStream(S));
end;

procedure Passworditem.SaveToStream(s: TStream);
begin
  Assert(Assigned(S));
  S.Write(accountname, Sizeof(accountname));
  S.Write(username, sizeof(username));
  S.Write(passwd, sizeof(passwd));
end;


constructor CreatefromStream(S: TStream);
begin
  Assert(Assigned(S));
  inherited Create;
  S.Read(accountname, Sizeof(accountname));
  S.Read(username, sizeof(username));
  S.Read(passwd, sizeof(passwd));
end;

2007. szeptember 18., kedd

How to toggle the AlwaysOnTop property of the Windows Taskbar


Problem/Question/Abstract:

Can anyone tell me how to set and reset the Windows TaskBar AlwaysOnTop property from within my application?

Answer:

procedure TForm1.Button1Click(Sender: TObject);
var
  hw: HWND;
begin
  hw := FindWindow('Shell_TrayWnd', nil);
  if hw <> 0 then
  begin
    if (GetWindowLong(hw, GWL_EXSTYLE) and WS_EX_TOPMOST) <> 0 then
    begin
      label1.caption := 'Taskbar is topmost';
      SetWindowPos(hw, HWND_NOTOPMOST, 0, 0, 0, 0, SWP_NOSIZE or SWP_NOMOVE);
    end
    else
    begin
      label1.caption := 'Taskbar is not topmost';
      SetWindowPos(hw, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOSIZE or SWP_NOMOVE);
    end;
  end;
end;

2007. szeptember 17., hétfő

Get notified when another application terminates


Problem/Question/Abstract:

My application is starting processes invoking ShellExecute API calls. These processes are manipulating files stored in a database BLOB field. I'd like to be notified when the external application terminates in order to save shanges made on the file. My idea was to use windows hooks, but I didn't guess exactly how. ShellExecute returns the Instance Handle of the application that was run (or DDE handle).

Answer:

Solve 1:

How about using the WndProc procedure and listening for the WM_QUERYENDSESSION in the main application, and broadcasting another message to the other one. Then your applications do what they need to do when they receive the message. For example:

{MainApp:}

const
  EndAppMsg = $FFF8B;

  public

  procedure WndProc(var Msg: TMessage); override;

procdure TMainForm.WndProc(var Msg: TMessage);
begin
  if Msg.Msg = WM_QUERYENDSESSION then
  begin
    BroadcastSystemMessage(BSF_POSTMESSAGE, BSM_ALLCOMPONENTS, EndAppMsg, 0, 0)
  end
  else
    inherited;
end;

And then use the WndProc again in the other applications that the main application calls, and listen for the EndAppMsg message. When it is recived execute the code that you need to execute. For example:

{Client/Worker App}

procedure WndProc(var Msg: Tmessage);
const
  EnaAppMsg = $FFF8B;
begin
  if Msg.Msg = EndAppMsg then
  begin
    {Execute your code here or call another procedure to execute the code}
  end
  else
    inherited;
end;


Solve 2:

You might try using ShellExecuteEx instead, then using the returned hProcess in a WaitForSingleObject call. Here's an example I used in a small demo application:

uses
  Windows, ShellApi;

var
  Info: TShellExecuteInfo;
begin
  FillChar(Info, SizeOf(Info), 0);
  Info.cbSize := SizeOf(Info);
  Info.fMask := SEE_MASK_NOCLOSEPROCESS;
  Info.Wnd := Handle;
  Info.lpVerb := 'open';
  Info.lpFile := 'C:\SomeTextFile.Text'; {Change me!}
  Info.lpParameters := nil;
  Info.lpDirectory := nil;
  Info.nShow := SW_SHOW;
  if (ShellExecuteEx(@Info)) then
  begin
    WaitForSingleObject(Info.hProcess, INFINITE);
    MessageBox(Handle, 'You closed the app I launched!', 'Finished!', MB_OK);
    CloseHandle(Info.hProcess);
  end;
end;

I'm not sure if you want your entire application becoming unresponsive while the launched application is running (the WaitForSingleObject call doesn't return until the application is closed), but if you don't, you might consider launching individual threads for each ShellExecuteEx, then using WaitForSingleObject in those threads (and writing an OnTerminate handler for the thread object to determine when the application finally finished).


2007. szeptember 16., vasárnap

Read a sender address for MailItem (MS Outlook)


Problem/Question/Abstract:

How can I retrieve SenderAddress by SenderName?

Answer:

Sometime ago I posted a few tips for MS Outlook automation. I want to continue this serie.

If you tried to work with messages from Delphi, you know that received message have the SenderName property (name of sender) but doesn't allow to read the real address of sender. Something like SenderAddress is not  available.

Exists a few methods to retrieve this information:

1. help file says that sender is in Recipients collection with Type property - 0 (olOriginator). But this way is not work for any version of MS Outlook. So just iterate thru collection of Recipients and find an item with Type=0 couldn't return required value

2. as alternative you can read a ReplyTo property - there you'll receive an address (but generally ReplyTo and Sender could be different). For example, in messages which I send from own mail account these values are
different.

3. to create a new MailItem (just will be destroyed without saving in end of work), define a Recipient as value which you received from SenderName of your original message and call a Resolve method - after that you'll recieve a correct email address of this sender.

4. more correct and fast solution is the next:

begin
  objCDO := CreateOLEObject('MAPI.Session');
  objCDO.Logon('', '', False, False);
  objMsg := objCDO.GetMessage(itemOL.EntryID, itemOL.Parent.StoreID);

  s := objMsg.Sender.Address;
  showmessage(s);
  objMsg := UnAssigned;
  objCDO := UnAssigned;
end

where itemOL is a MailItem which contain a SenderName but doesn't contain a SenderAddress:-)

2007. szeptember 15., szombat

Sort list items of a TListView with integer values as captions


Problem/Question/Abstract:

I have an integer value as ListItem caption (using InttoStr). I would like to sort according to the Integer value of these captions. Any Ideas?

Answer:

function Cussort(Item1, Item2: TListItem; lParamSort: Integer): Integer; stdcall;
var
  I1, I2: Integer;
begin
  I1 := StrToIntDef(Item1.Caption, -1);
  I2 := StrToIntDef(Item2.Caption, -1);
  if I1 > I2 then
    Result := 1
  else if I2 < I1 then
    Result := -1
  else
    Result := 0;
end;

MyListView.CustomSort(@Cussort, 0);

2007. szeptember 14., péntek

How to modify the color of a TCheckBox


Problem/Question/Abstract:

How to modify the color of a TCheckBox

Answer:

I would do the drawing in the CN_DRAWITEM message handler. Below is the code of such a checkbox:

{ ... }
type
  TMyCheckBox = class(TCheckBox)
  protected
    procedure CNDrawItem(var Message: TWMDrawItem); message CN_DRAWITEM;
    procedure CMEnabledchanged(var Message: TMessage); message CM_ENABLEDCHANGED;
    procedure CreateParams(var Params: TCreateParams); override;
    procedure CreateWnd; override;
    procedure SetChecked(Value: Boolean); override;
    procedure SetButtonStyle;
  public
    constructor Create(AOwner: TComponent); override;
  end;

  { ... }

constructor TMyCheckBox.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  ControlStyle := ControlStyle - [csDoubleClicks];
end;

procedure TMyCheckBox.CNDrawItem(var Message: TWMDrawItem);
var
  XCanvas: TCanvas;
  XCaptionRect, XGlyphRect: TRect;

  procedure xxDrawBitMap(ACanvas: TCanvas);
  const
    xx_h = 13;
    xx_w = 13;
  var
    xxGlyph: TBitmap;
    xxX, xxY, xxStepY, xxStepX: integer;
  begin
    xxGlyph := TBitmap.Create;
    try
      xxGlyph.Handle := LoadBitmap(0, PChar(OBM_CHECKBOXES));
      xxY := XGlyphRect.Top + (XGlyphRect.Bottom - XGlyphRect.Top - xx_h) div 2;
      xxX := 2;
      xxStepX := 0;
      xxStepY := 0;
      if Enabled then
      begin
        case State of
          cbChecked:
            xxStepX := xxStepX + xx_w;
          cbGrayed:
            xxStepX := xxStepX + xx_w * 3;
        end;
      end
      else if State = cbChecked then
        xxStepX := xxStepX + xx_w * 3
      else
        xxStepX := xxStepX + xx_w * 2;
      ACanvas.CopyRect(Rect(xxX, xxY, xxX + xx_w, xxY + xx_h), xxGlyph.Canvas,
        Rect(xxStepX, xxStepY, xx_w + xxStepX, xx_h + xxStepY));
    finally
      xxGlyph.Free;
    end;
  end;

  procedure xxDrawCaption;
  var
    xXFormat: longint;
  begin
    xXFormat := DT_VCENTER + DT_SINGLELINE + DT_LEFT;
    xXFormat := DrawTextBiDiModeFlags(xXFormat);
    DrawText(Message.DrawItemStruct.hDC, PChar(Caption),
      length(Caption), XCaptionRect, xXFormat);
  end;

begin
  XGlyphRect := Message.DrawItemStruct.rcItem;
  XGlyphRect.Right := 20;
  XCaptionRect := Message.DrawItemStruct.rcItem;
  XCaptionRect.Left := XGlyphRect.Right;
  XCanvas := TCanvas.Create;
  try
    XCanvas.Handle := Message.DrawItemStruct.hDC;
    XCanvas.Brush.Style := bsClear;
    xxDrawBitMap(XCanvas);
    xxDrawCaption;
  finally
    XCanvas.Free;
  end;
end;

procedure TMyCheckBox.CMEnabledchanged(var Message: TMessage);
begin
  inherited;
  Invalidate;
end;

procedure TMyCheckBox.CreateParams(var Params: TCreateParams);
begin
  inherited CreateParams(Params);
  Params.ExStyle := Params.ExStyle or WS_EX_Transparent;
end;

procedure TMyCheckBox.CreateWnd;
begin
  inherited CreateWnd;
  SetButtonStyle;
end;

procedure TMyCheckBox.SetChecked(Value: Boolean);
begin
  inherited SetChecked(Value);
  Invalidate;
end;

procedure TMyCheckBox.SetButtonStyle;
const
  BS_MASK = $000F;
var
  Style: Word;
begin
  if HandleAllocated then
  begin
    Style := BS_CHECKBOX or BS_OWNERDRAW;
    if GetWindowLong(Handle, GWL_STYLE) and BS_MASK <> Style then
      SendMessage(Handle, BM_SETSTYLE, Style, 1);
  end;
end;

2007. szeptember 13., csütörtök

Painting the Form Menu bar


Problem/Question/Abstract:

When i uses programs like the game freecell that comes with windows 9x i see that there is a text in the menu bar tht tells me how many cards left me.

How can i make something like that in my programs ?

Answer:

Well, First of all we need to put a main menu component on our form.
Now set the OwnerDraw property to true.

If you have an item that you wish to paint by yourself, now is the time to create it and to make the OnDrawItem.
In this line you put also this line:

{... }
ACanvas.TextOut(1, ARect.Top + 1, 'I''m in the MainMenuDrawbar');
{... }

Note, If you need to use a changed variable you can do it from another function and ll you need to do afther the change is to call the API function DrawMenuBar.

If you are using Delphi 2,3 Use the Messages WM_MESUREITEM and the message WM_DRAWITEM to make this effect.

2007. szeptember 12., szerda

How to print bitmaps and controls placed on a TPanel


Problem/Question/Abstract:

I have placed several images and assorted graphic controls on a TPanel. Now I want to print it. My problem is that the panel does not have a canvas property. Somehow I should be able to manipulate the "graphics" on the panel. What I thought might work is to do a screen capture of the panel area, but I am not sure what the function calls are. Does anybody have any ideas? I want to be able to scale the image and print it to a specific part of the page.

Answer:

The form has a canvas. You can create a new bitmap the same size as your panel and then use CopyRect to copy the panel and its content from the form to this in- memory bitmap. Then you can print the in-memory bitmap. Here's an example:

procedure TFormPrintWindows.ButtonPrintPanelClick(Sender: TObject);
var
  Bitmap: TBitmap;
  FromLeft, FromTop, PrintedWidth, PrintedHeight: Integer;
begin
  Printer.BeginDoc;
  try
    Bitmap := TBitmap.Create;
    try
      Bitmap.Width := Panel1.Width;
      Bitmap.Height := Panel1.Height;
      Bitmap.PixelFormat := pf24bit; {Avoid palettes}
      {Copy the panel area from the form into a separate bitmap}
      Bitmap.Canvas.CopyRect(Rect(0, 0, Bitmap.Width, Bitmap.Height),
                        FormPrintWindows.Canvas, Rect(Panel1.Left, Panel1.Top, Panel1.Left +
                        Panel1.Width - 1, Panel1.Top + Panel1.Height - 1));
      {Assumes 10% left, right and top margin}
      {Assumes bitmap aspect ratio > ~0.75 for portrait mode}
      PrintedWidth := MulDiv(Printer.PageWidth, 80, 100); {80%}
      PrintedHeight := MulDiv(PrintedWidth, Bitmap.Height, Bitmap.Width);
      FromLeft := MulDiv(Printer.PageWidth, 10, 100); {10%}
      FromTop := MulDiv(Printer.PageHeight, 10, 100); {10%}
      PrintBitmap(Printer.Canvas, Rect(FromLeft, FromTop, FromLeft + PrintedWidth,
        FromTop + PrintedHeight), Bitmap);
    finally
      Bitmap.Free
    end;
  finally
    Printer.EndDoc
  end;
end;

2007. szeptember 11., kedd

Can your video handle 16, 256, 32768, 16777216, or more colors


Problem/Question/Abstract:

Can your video handle 16, 256, 32768, 16777216, or more colors?

Answer:

You can use WIN API function GetDeviceCaps() to calculate the number of colors supported by the current video mode. To make it even easier to use, here's a function that will simply return the number of maximum simultaneous colors current video device can handle:

function GetColorsCount: integer;
var
  h: hDC;
begin
  Result := 0;
  try
    h := GetDC(0);
    Result :=
      1 shl
      (
      GetDeviceCaps(h, PLANES) *
      GetDeviceCaps(h, BITSPIXEL)
      );
  finally
    ReleaseDC(0, h);
  end;
end;

2007. szeptember 10., hétfő

How to use TCollection and TCollectionItem


Problem/Question/Abstract:

Has anyone out there attempted to use TCollection and TCollectionItem? What I am trying to do is mimic what the Columns Editor does in the TDBGrid for the TStringGrid component. This is the first time that I have made a component that needs properties and sub-properties. I am not sure how to go about this.

Answer:

This one worked for me:


unit ggImgLst;

interface

uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Dialogs,
  ExtCtrls, Dsgnintf; {, jpeg;}

type
  TAboutProperty = class(TPropertyEditor)
  private
  protected
  public
    procedure Edit; override;
    function GetAttributes: TPropertyAttributes; override;
    function GetName: string; override;
    function GetValue: string; override;
  end;

  TggImageListPropertyEditor = class(TPersistent);

  TggImageListProperty = class(TClassProperty);

  TggImageSizes = (ggSmall, ggMedium, ggLarge);
  {TggImageSize  = set of TggImageSizes;}

  TggImage = class;
  TggImageList = class;

  TggImage = class(TCollectionItem)
  private
    FSize: TggImageSizes;
    FPicture: TPicture;
    FName: string;
    function GetDisplayName: string; override;
    procedure SetPicture(Value: TPicture);
  public
    constructor Create(Collection: TCollection); override;
    destructor destroy; override;
  published
    property Size: TggImageSizes read FSize write FSize;
    property Name: string read FName write FName;
    property Picture: TPicture read FPicture write SetPicture;
  end;

  TggImageClass = class of TggImage;

  TggImages = class(TCollection)
  private
    FggImageList: TggImageList;
    FggImageListPropertyEditor: TggImageListPropertyEditor;
    function GetImage(Index: Integer): TggImage;
    procedure SetImage(Index: Integer; Value: TggImage);
  protected
    function GetOwner: TPersistent; override;
  public
    constructor create(ggImageList: TggImageList; ggImageClass: TggImageClass);
    function Add: TggImage;
    property ggImageList: TggImageList read FggImageList;
    property Items[Index: Integer]: TggImage read GetImage write SetImage; default;
  published
  end;

  TggImageList = class(TComponent)
  private
    FAbout: TAboutProperty;
    FImages: TggImages;
    procedure WriteImages(Writer: TWriter);
    procedure ReadImages(Reader: TReader);
    procedure SetImages(Value: TggImages);
  protected
    function CreateImages: Tggimages; dynamic;
    procedure DefineProperties(Filer: TFiler); override;
  public
    constructor Create(AOwner: TComponent); override;
    function GetImageNameList: TStringList;
    function GetPicture(PictureName: string): TPicture;
  published
    property About: TAboutProperty read FAbout write FAbout;
    property Images: TggImages read FImages write SetImages;
  end;

procedure Register;

implementation

uses
  jpeg;

{ggImage}

constructor TggImage.Create(Collection: TCollection);
var
  ggImageList: TggImageList;
begin
  FPicture := TPicture.Create;
  ggImageList := nil;
  if assigned(Collection) and (Collection is TggImages) then
    ggImageList := Tggimages(Collection).ggImageList;
  if assigned(ggImageList) then
    inherited Create(Collection);
end;

destructor TggImage.Destroy;
begin
  FPicture.Free;
  inherited Destroy;
end;

procedure TggImage.SetPicture(Value: TPicture);
begin
  FPicture.Assign(Value);
end;

function TggImage.GetDisplayName: string;
begin
  Result := Name;
  if Result = '' then
    Result := inherited GetDisplayName;
end;

{TggImages}

function TggImages.GetImage(Index: Integer): TggImage;
begin
  Result := TggImage(inherited Items[Index]);
end;

procedure TggImages.SetImage(Index: Integer; Value: TggImage);
begin
  Items[Index].Assign(Value);
end;

constructor TggImages.Create(ggImageList: TggImageList;
  ggImageClass: TggImageClass);
begin
  inherited Create(ggImageClass);
  FggImageList := ggImageList;
  FggImageListPropertyEditor := TggImageListPropertyEditor.Create;
end;

function TggImages.GetOwner: TPersistent;
begin
  Result := FggImageList;
end;

function TggImages.Add: TggImage;
begin
  Result := TggImage(inherited Add);
end;

{ggImageList}

procedure TggImageList.WriteImages(Writer: TWriter);
begin
  Writer.WriteCollection(Images);
end;

procedure TggImageList.ReadImages(Reader: TReader);
begin
  Images.Clear;
  Reader.ReadValue;
  Reader.ReadCollection(Images);
end;

procedure TggImageList.DefineProperties(Filer: TFiler);
begin
  Filer.DefineProperty('ggImages', ReadImages, WriteImages, Filer.Ancestor < > nil);
end;

procedure TggImageList.SetImages(Value: TggImages);
begin
  Images.Assign(Value);
end;

function TggImageList.CreateImages: TggImages;
begin
  Result := TggImages.Create(Self, TggImage);
end;

function TggImageList.GetImageNameList: TStringList;
var
  I: Integer;
begin
  Result := TStringList.Create;
  for I := 0 to Self.Images.Count - 1 do
    Result.Add(Self.Images.Items[I].Name);
end;

function TggImageList.GetPicture(PictureName: string): TPicture;
var
  I: Integer;
begin
  I := 0;
  Result := nil;
  PictureName := uppercase(Picturename);
  while I <= Self.Images.Count - 1 do
  begin
    if PictureName = uppercase(Self.Images.Items[I].Name) then
    begin
      Result := Self.Images.Items[I].Picture;
      I := Self.Images.Count;
    end
    else
      Inc(I);
  end;
end;

constructor TggImageList.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FImages := CreateImages;
end;

{TAboutProperty}

procedure TAboutProperty.Edit;
begin
  MessageBox(0, PChar('TggImageList component' + #13 + #13 + 'by Geurts Guido -
    guido.geurts@advalvas.be ' + #13 + ' 10 / 03 / 1999'),
    PChar('The GuidoG utilities present...'), MB_OK);
end;

function TAboutProperty.GetAttributes: TPropertyAttributes;
begin
  Result := [paDialog, paReadOnly];
end;

function TAboutProperty.GetName: string;
begin
  Result := 'About';
end;

function TAboutProperty.GetValue: string;
begin
  Result := GetStrValue;
end;

{Non class related procedures and functions:}

procedure register;
begin
  RegisterComponents('GuidoG', [TggImageList]);
  RegisterPropertyEditor(TypeInfo(TggImageListPropertyEditor), TGGImages,
    'Images', TGGImageListProperty);
  RegisterPropertyEditor(TypeInfo(TAboutProperty), TggImageList, 'About',
    TAboutProperty);
end;

end.

2007. szeptember 9., vasárnap

How to access a single object in a metafile


Problem/Question/Abstract:

How to access a single object in a metafile

Answer:

Below is an example of getting metafile information and enumerating each metafile record :

function MyEnhMetaFileProc(DC: HDC; {handle to device context}
  lpHTable: PHANDLETABLE; {pointer to metafile handle table}
  lpEMFR: PENHMETARECORD; {pointer to metafile record}
  nObj: integer; {count of objects}
  TheForm: TForm1): integer; stdcall;
begin
  {draw the metafile record}
  PlayEnhMetaFileRecord(dc, lpHTable^, lpEMFR^, nObj);
  {set to zero to stop metafile enumeration}
  result := 1;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  MyMetafile: TMetafile;
  lpENHMETAHEADER: PENHMETAHEADER; {extra metafile info}
  lpENHMETAHEADERSIZE: DWORD;
  NumMetaRecords: DWORD;
begin
  {Create a metafile}
  MyMetafile := TMetafile.Create;
  with TMetafileCanvas.Create(MyMetafile, 0) do
  try
    Brush.Color := clRed;
    Ellipse(0, 0, 100, 100);
    Ellipse(100, 100, 200, 200);
    Ellipse(200, 200, 300, 300);
    Ellipse(300, 300, 400, 400);
    Ellipse(400, 400, 500, 500);
    Ellipse(500, 500, 600, 600);
  finally
    Free;
  end;
  {we might as well get some extra metafile info}
  lpENHMETAHEADERSIZE := GetEnhMetaFileHeader(MyMetafile.Handle, 0, nil);
  NumMetaRecords := 0;
  if (lpENHMETAHEADERSIZE > 0) then
  begin
    GetMem(lpENHMETAHEADER, lpENHMETAHEADERSIZE);
    GetEnhMetaFileHeader(MyMetafile.Handle, lpENHMETAHEADERSIZE, lpENHMETAHEADER);
    {Here is an example of getting number of metafile records}
    NumMetaRecords := lpENHMETAHEADER^.nRecords;
    {enumerate the records}
    EnumEnhMetaFile(Canvas.Handle, MyMetafile.Handle, @MyEnhMetaFileProc, self,
      Rect(0, 0, 600, 600));
    FreeMem(lpENHMETAHEADER, lpENHMETAHEADERSIZE);
  end;
  MyMetafile.Free;
end;

2007. szeptember 8., szombat

Reference a column of a TDBGrid by name instead of integer index


Problem/Question/Abstract:

Is there a way in TDBGrid to reference a column by name rather than by integer index? Right now I am using "ListGrd.Columns[ 5 ]" (for example) to access a particular column but that is dangerous if moving columns is enabled. Can I reference a column by a column name instead?

Answer:

function TForm1.ColumnByFieldName(AGrid: TDBGrid; const AFieldName: string): TColumn;
var
  I: Integer;
begin
  for I := 0 to AGrid.Columns.Count - 1 do
  begin
    Result := AGrid.Columns[I];
    if AnsiCompareText(Result.FieldName, AFieldName) = 0 then
      Exit;
  end;
  raise Exception.Create(AGrid.Name + ', ' + AFieldName);
end;

2007. szeptember 7., péntek

How to create a countdown timer (2)


Problem/Question/Abstract:

All I want to do is to read the system time of the computer, then read it again a little later, and compare the times. I want hours, minutes, seconds and milliseconds. I have looked into TimeStamp and GetSystemTime, but I just can't get it to work. What should I do?

Answer:

var
  T0, T1: TDateTime;
  ElapsedSeconds: Double;
begin
  T0 := Now;
  { ... }
  T1 := Now;
  ElapsedSeconds := 86400.0 * (T1 - T0);
end;

2007. szeptember 6., csütörtök

How to put the content of a TStringGrid into an Excel range


Problem/Question/Abstract:

How to put the content of a TStringGrid into an Excel range

Answer:

{ ... }
var
  ArrV: Variant;
  Cell: Range;
  { ... }

ArrV := VarArrayCreate([0, NumRows, 0, NumCols], varOleStr);
for Row := 0 to NumRows do
  for Col := 0 to NumCols do
    ArrV[Row, Col] := StringGrid1.Cells[Col, Row];
Cell := Excel.ActiveCell;
WS.Range[Cell, Cell.Offset[NumRows, NumCols]].Value := ArrV;
{ ... }

2007. szeptember 5., szerda

Retrieve a list of available BDE language drivers


Problem/Question/Abstract:

How can I retrieve a list of available BDE language drivers?  

Answer:

The following Delphi procedure returns a formatted list of available BDE language drivers. The procedure can be called as shown in the buttonclick method below.

Add BDE and DBTables to your unit's uses clause.

procedure GetLdList(Lines: TStrings);
var
  hCur: hDBICur;
  LD: LDDesc;
  cnt: integer;
begin
  // get a cursor to the in-mem table containing language
  // driver information...
  cnt := 0;
  check(dbiinit(nil));
  Check(DbiOpenLdList(hCur));
  try
    while (DbiGetNextRecord(hCur, dbiNOLOCK, @LD, nil) = DBIERR_NONE) do
    begin
      cnt := cnt + ;
      Lines.Add(format('%4d %-6s%- 0s %- 0s%5s %- 0s %- 0s', [cnt, 'Name:', LD.szName,
        'Code Page:', IntToStr(LD.iCodePage), 'Description:', LD.szDesc]));
    end;
  finally Check(DbiCloseCursor(hCur));
    check(dbiexit);
  end;
end;

procedure TForm.Button Click(Sender: TObject);
begin
  getldlist(memo.lines);
end;

end.

2007. szeptember 4., kedd

How to compare two strings and measure the percentage they match


Problem/Question/Abstract:

Does anyone know how or does anyone know of a good procedure to match two strings? What I want is a % match between two strings. Something like Hart and Harts are 80% equal.

Answer:

uses
  math;

function IsStrMatch(s1, s2: string): Double;
var
  i, iMin, iMax, iSameCount: Integer;
begin
  iMax := Max(Length(s1), Length(s2));
  iMin := Min(Length(s1), Length(s2));
  iSameCount := -1;
  for i := 0 to iMax do
  begin
    if i > iMin then
      break;
    if s1[i] = s2[i] then
      Inc(iSameCount)
    else
      break;
  end;
  if iSameCount > 0 then
    Result := (iSameCount / iMax) * 100
  else
    Result := 0.00;
end;

2007. szeptember 3., hétfő

How to create non-selectable separator lines in a TComboBox


Problem/Question/Abstract:

How to create non-selectable separator lines in a TComboBox

Answer:

Note that the Combobox1.Style is csOwnerDrawvariable.

procedure TForm1.FormCreate(Sender: TObject);
begin
  with combobox1 do
  begin
    items.add('Item 1');
    items.add('Item 2');
    items.addObject('Item 3', Pointer(1));
    Perform(CB_SetItemHeight, 2, ItemHeight + 5);
    items.add('Item 4');
    items.add('Item 5');
  end;
end;

procedure TForm1.ComboBox1MeasureItem(Control: TWinControl; Index: Integer;
  var Height: Integer);
begin
  Height := (Control as TCombobox).Itemheight;
end;

procedure TForm1.ComboBox1DrawItem(Control: TWinControl; Index: Integer;
  Rect: TRect; State: TOwnerDrawState);
var
  needsSeparator: Boolean;
begin
  with Control as TCombobox do
  begin
    needsSeparator := Assigned(Items.Objects[index]) and not (odComboBoxEdit in State);
    if needsSeparator then
      Rect.Bottom := Rect.Bottom - 5;
    Canvas.FillRect(Rect);
    Canvas.TextRect(Rect, Rect.Left + 2, Rect.Top, Items[index]);
    if needsSeparator then
    begin
      Rect.Top := Rect.Bottom;
      Rect.Bottom := Rect.Bottom + 5;
      Canvas.Brush.Color := color;
      Canvas.Pen.Color := font.Color;
      Canvas.Pen.Style := psSolid;
      Canvas.Fillrect(Rect);
      Canvas.MoveTo(rect.left, rect.top + 2);
      Canvas.LineTo(rect.right, rect.top + 2);
    end;
  end;
end;

2007. szeptember 2., vasárnap

Get filepath from shortcut


Problem/Question/Abstract:

How to obtain the linked file from a shortcut

Answer:

uses ShellAPI;

function ExeFromLink(const linkname: string): string;
var
  FDir,
    FName,
    ExeName: PChar;
  z: integer;
begin
  ExeName := StrAlloc(MAX_PATH);
  FName := StrAlloc(MAX_PATH);
  FDir := StrAlloc(MAX_PATH);
  StrPCopy(FName, ExtractFileName(linkname));
  StrPCopy(FDir, ExtractFilePath(linkname));
  z := FindExecutable(FName, FDir, ExeName);
  if z > 32 then
    Result := StrPas(ExeName)
  else
    Result := '';
  StrDispose(FDir);
  StrDispose(FName);
  StrDispose(ExeName);
end;

2007. szeptember 1., szombat

How to catch windows keystrokes and pass them to an assigned event


Problem/Question/Abstract:

How to catch windows keystrokes and pass them to an assigned event

Answer:

For those interested, here's a keyboard hook component that catches windows keystrokes and passes them to an assigned event.

unit KeyboardHook;
{
  By William Egge
  Sep 20, 2002
  egge@eggcentric.com
  http://www.eggcentric.com

  This code may be used/modified however you wish.
}

interface

uses
  Windows, Classes;

type
  TCallbackThunk = packed record
    POPEDX: Byte;
    MOVEAX: Byte;
    SelfPtr: Pointer;
    PUSHEAX: Byte;
    PUSHEDX: Byte;
    JMP: Byte;
    JmpOffset: Integer;
  end;

  {See windows help on KeyboardProc or press F1 while your cursor is on "KeyboardProc"}
  TKeyboardCallback = procedure(code: Integer; wparam: WPARAM; lparam: LPARAM) of
    object;

  TKeyboardHook = class(TComponent)
  private
    { Private declarations }
    FHook: HHook;
    FThunk: TCallbackThunk;
    FOnCallback: TKeyboardCallBack;
    function CallBack(code: Integer; wparam: WPARAM; lparam: LPARAM): LRESULT stdcall;
    procedure SetOnCallback(const Value: TKeyboardCallBack);
  protected
    { Protected declarations }
  public
    { Public declarations }
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
  published
    { Published declarations }
    property OnCallback: TKeyboardCallBack read FOnCallback write SetOnCallback;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('EggMisc', [TKeyboardHook]);
end;

{ TKeyboardHook }

function TKeyboardHook.CallBack(code: Integer; wparam: WPARAM; lparam: LPARAM):
  LRESULT;
begin
  if Code < 0 then
    Result := CallNextHookEx(FHook, Code, wparam, lparam)
  else
  begin
    if Assigned(FOnCallback) then
      FOnCallback(Code, wParam, lParam);
    Result := 0;
  end;
end;

constructor TKeyboardHook.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FThunk.POPEDX := $5A;
  FThunk.MOVEAX := $B8;
  FThunk.SelfPtr := Self;
  FThunk.PUSHEAX := $50;
  FThunk.PUSHEDX := $52;
  FThunk.JMP := $E9;
  FThunk.JmpOffset := Integer(@TKeyboardHook.Callback) - Integer(@FThunk.JMP) - 5;
  FHook := SetWindowsHookEx(WH_KEYBOARD, TFNHookProc(@FThunk), 0, MainThreadID);
end;

destructor TKeyboardHook.Destroy;
begin
  UnhookWindowsHookEx(FHook);
  inherited;
end;

procedure TKeyboardHook.SetOnCallback(const Value: TKeyboardCallBack);
begin
  FOnCallback := Value;
end;

end.