2006. június 30., péntek

Make a TActionMainMenuBar on a secondary form work correctly

Problem/Question/Abstract:

On my main form, I have a TActionMainMenuBar with an ActionManager. On my subform, which is placed on the main form, I have a TActionToolbar with a second Actionmanager. Both works fine, but when then second form is active, only the (distinct) shortcuts of the main form are working, the ones of the client form are not.

Answer:

Solve 1:

In order to use a TActionMainMenuBar component on a second form, use the following code in the second form. This overcomes the problem where pressing "ALT" causes the first forms menu bar to appear.

procedure TForm2.WMSysCommand(var Message: TWMSysCommand);
begin
if (Message.CmdType = SC_KEYMENU) then
begin
Message.Result := SendMessage(ActionMainMenuBar.Handle, Message.Msg,
Message.CmdType, Message.Key);
if (Message.Result = 0) then
inherited;
end
else
inherited;
end;


Solve 2:

Try this code in you application's main form (duplicate it for CMActionUpdate):

{ ... }

procedure CMActionExecute(var Message: TMessage); message CM_ACTIONEXECUTE;
{ ... }

procedure TForm1.CMActionexecute(var Message: TMessage);
var
bPerformed: Boolean;
i: Integer;
Form: TCustomForm;
begin
bPerformed := False;
for i := 0 to Pred(Application.ComponentCount) do
begin
if Application.Components[i] is TCustomForm then
begin
Form := TCustomForm(Application.Components[i]);
if Form.Active then
begin
Message.Result := Form.Perform(Message.Msg, 0, Message.LParam);
{Check the result.}
bPerformed := Message.RESULT = S_OK;
if bPerformed then
begin
exit;
end;
end;
end;
end;
if not bPerformed then
begin
{If we havent sent the message anywhere then  perform the usual}
inherited;
end;
end;


2006. június 29., csütörtök

Write a non-visible component that allows only one instance of itself at design time


Problem/Question/Abstract:

How to write a non-visible component that allows only one instance of itself at design time

Answer:

Adapted singleton class from Borland Comunity. My prototype allows for inheritance, such as:

{ ... }
type
  {TApplication}
  TApplication = class(TSingleton)
  protected
    procedure InitializeInstance; override;
    procedure FinalizeInstance; override;
  end;

  {TScreen}
  TScreen = class(TSingleton)
  protected
    procedure InitializeInstance; override;
    procedure FinalizeInstance; override;
  end;

All internal members (data/objects) will be created/ destroyed in InitializeInstance/ FinalizeInstance

{ ... }
var
  A1, A2: TApplication;
  S1, S2: TScreen;
begin
  A1 := TApplication.Create;
  A2 := TApplication.Create;
  S1 := TScreen.Create;
  S2 := TScreen.Create;
  { ... }
  {Note, my code: A1 = A2 and S1 = S2 and A1 <> S1}
  A1.Free;
  A2.Free;
  S2.Free;
  S1.Free;
end;

To optimize the code I would suggest using this approach for  creation of objects inheriting from TSingleton:

unit singleton;

interface

uses
  Classes;

type
  {you can inherit from TSingleton and create different singleton objects}
  TSingleton = class
  private
    FRef: Integer;
  protected
    procedure InitializeInstance; virtual;
    procedure FinalizeInstance; virtual;
  public
    class function NewInstance: TObject; override;
    procedure FreeInstance; override;
  end;

implementation

var
  Singletons: TStringList = nil;

procedure TSingleton.FreeInstance;
var
  Index: Integer;
  Instance: TSingleton;
begin
  Singletons.Find(ClassName, Index);
  Instance := TSingleton(Singletons.Objects[Index]);
  Dec(Instance.FRef);
  if Instance.FRef = 0 then
  begin
    Singletons.Delete(Index);
    Instance.FinalizeInstance;
    {at this point, Instance = Self. We want to call TObject.FreeInstance}
    inherited FreeInstance;
  end;
end;

procedure TSingleton.FinalizeInstance;
begin
end;

procedure TSingleton.InitializeInstance;
begin
end;

class function TSingleton.NewInstance: TObject;
var
  Index: Integer;
begin
  if Singletons = nil then
  begin
    Singletons := TStringList.Create;
    Singletons.Sorted := true;
    Singletons.Duplicates := dupError;
  end;
  if not Singletons.Find(ClassName, Index) then
  begin
    Result := inherited NewInstance;
    TSingleton(Result).FRef := 1;
    TSingleton(Result).InitializeInstance;
    Singletons.AddObject(ClassName, Result);
  end
  else
  begin
    Result := Singletons.Objects[Index];
    Inc(TSingleton(Result).FRef);
  end;
end;

procedure CleanupSingletons;
var
  i: integer;
begin
  if Singletons <> nil then
  begin
    for i := 0 to Pred(Singletons.Count) do
      if Assigned(Singletons.Objects[i]) then
        Singletons.Objects[i].Free;
    Singletons.Free;
  end;
end;

initialization

finalization
  CleanupSingletons;

end.

2006. június 28., szerda

Version Info for DLL project


Problem/Question/Abstract:

I noticed that the options on the 'Version Info' tab of the 'Project
Options' dialog are all grayed out for a DLL project. Why is that?
Is there a different way of including version info in a DLL?
I would like to include version information in my DLL because it makes upgrade
installations simpler for our customers.

Answer:

I feel that something else is broken in your installation. Make sure you check the box at the
top left of that tab which says 'Include version information in project'.

Also make sure that you have a resource file in your project file (*.dpr).
The line for this looks like this:

{$R *.res}

If you removed this line by accident, no version information will be generated or even linked into your DLL.

2006. június 27., kedd

Using two Data Controls to display Data Source and Table data


Problem/Question/Abstract:

How can I use two different data controls to display data from a single data source and table?For instance, I'd like to use a DBGrid as a navigating tool as follows:

Clients table with standard name, phone number etc.
One DBText for each edit-able field
DBGrid contains just the name of the client
Click on a record in the DBGrid and the DBTexts are updated according to the record clicked.

At the moment, I'm using two data sources because I don't want phone numbers, etc. to show up in the DBGrid. However, because they're separate data sources, when I click on the DBGrid the DBTexts are not updated. Is there any way I can do this? The clients table is going to be hundreds of entries long, and paging through them is a pain. Also, editing them all in a grid would result in a grid about 25 columns wide :-( and this is also a pain.

Answer:

The Delphi 1.0 Method

If you're using Delphi 1.0, you can't do it with native Data Control components. They just don't have the capability. However, you can simulate the behavior of TDBGrid with a TListBox (this is just one of number of ways to approach this problem). At program startup, you would load the values from your Client Name field into the TListBox by doing the following (assuming you have a TTable and TListBox embedded on your form):

Table1.Open;
with Table1 do {Load the list box}
  while not EOF do
  begin
    ListBox1.Items.Add(FieldByName('Client Name').AsString);
    Next;
  end;
ListBox1.ItemIndex := 0; {Select the first item in the list}

What you have to do next is trap the OnClick event of your TListBox so that whenever a user moves to or clicks on a new item, the value of that item is used to search the TTable and synchronize the values of your TDBEdit components. Assuming that you have your table keyed with the Client Name, you can do the following in the OnClick event;

procedure TForm1.ListBox1Click(Sender: TObject);
begin
  with Table1 do
    FindKey([ListBox1.Items[ListBox1.ItemIndex]]);
end;

You could quit here, but your work isn't quite done yet. You will have to be able to handle deletes and inserts to the table, and with those actions you need to update the list as well. I won't go into real specifics here for brevity's sake, but I'll give you an idea of what you have to do below:

Handling Inserts

In this situation you'll probably be better off using another form that has only TEdits that correspond to the fields in your table that you want to update, and OK and Cancel buttons on it. When the user clicks OK, you'll do a Form1.Table1.Insert; to insert a new record into the database and use the TTable.FieldByName method to update the fields.

Once you're done doing that, you have to add the new client name to the list. Since you want to simulate the indexing of your table in your list box, you can't just arbitrarily add an item. You have to load the entire list over again, using the code in the FormCreate method to accomplish this. However, you can get a HUGE performance gain by using the WinAPI function LockWindowUpdate to prevent the form from continuously painting while the list gets loaded. Here's an example of the modified loading code:

LockWindowUpdate(Form1.Handle);
while not EOF do
begin
  ListBox1.Items.Add(FieldByName('Prompt').AsString);
  Next;
end;
LockWindowUpdate(0);

Now the list will load, and the user won't even see the update happening. I'll leave it up to you to write the code to get to the newly added item.

Handling a Delete

Handling a delete is a simpler matter because it doesn't involve another form. All you do is call the TTable.Delete method to delete the current record. After that, reload the list box using the technique described above.

The Delphi 2.0 Method

Fortunately, with the arrival of Delphi 2.0, all the stuff we had to do above is a wash -- for the simple reason that the TDBGrid has more features. The one in particular that is relevant to this topic is the Columns Editor. With the TDBGrid's Columns Editor, you can specify which columns to display in the grid and how to display them. Now you don't have to rely on adding TTable TFields to the form, which severly limited what kinds of objects you could use to access the other field values if you changed the display properties. Now, you can drop a grid on your form, attach it to your table, and pick which column you want to display. Then you can drop other Data Controls such as TDBEdits and such and point them at the same table.They share the same data source, so any change such as movement or editing within the TDBGrid will be immediately reflected in the other Data Controls.

2006. június 26., hétfő

Use a TProgressbar within a TListbox


Problem/Question/Abstract:

How to use a TProgressbar within a TListbox

Answer:

It is possible, but you have to tie the TProgressBar to the listbox at runtime, the listbox will not accept a component dropped on it as child. Create a new project, drop a TButton, TListbox, TProgressbar, TTimer on it. Set the timers Interval to 100, its Enabled property to false. Attach handlers to the Timers OnTimer and buttons onClick event, complete as below:

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls,
  Forms, Dialogs, stdctrls, ExtCtrls, ComCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    ListBox1: TListBox;
    ProgressBar1: TProgressBar;
    Timer1: TTimer;
    procedure Timer1Timer(Sender: TObject);
    procedure Button1Click(Sender: TObject);
  private
    { Private declarations }
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.dfm}

procedure TForm1.Timer1Timer(Sender: TObject);
begin
  progressbar1.StepIt;
  if progressbar1.Position >= progressbar1.Max then
    timer1.enabled := false;
end;

procedure TForm1.Button1Click(Sender: TObject);
var
  i: integer;
begin
  if timer1.Enabled then
    exit;
  listbox1.clear;
  for i := 1 to 20 do
    listbox1.Items.add(Format('Item %d', [i]));
  progressbar1.Position := progressbar1.Min;
  if progressbar1.Parent <> listbox1 then
  begin
    progressbar1.Parent := listbox1;
    progressbar1.BoundsRect := listbox1.ItemRect(2);
  end;
  timer1.enabled := true;
end;

end.

Build and run, click the button.

2006. június 25., vasárnap

Handling previous instances of an application


Problem/Question/Abstract:

I was surprised to find that I didn't have an article covering this topic - I wrote a couple of 'em for inquiry.com a couple of years ago. Oh well... they're in another place and time... In any case, what I'll give you here are two techniques for handling previous instances of your applications.

Answer:

Oh! Were that only the phrase I used when I was single! Alas, like most, it unfortunately wasn't. But at least I could apply that phrase in my professional life; of course, in a totally different way. Okay, enough of the play on words.

There are many applications that lend themselves to having more than one copy running at any time. But in my experience, most of the applications I build only lend themselves to a single instance. For instance, it isn't practical to run more than one instance of a data-entry application; especially when it updates local data. In cases such as this, I limit the execution of another instance of a program by executing some simple code.

What I'm going to show you is two different functions that do exactly the same thing. The only difference between the two is that the first function can only be run in Win32, and the other function can run in either Win16 or Win32. Here's the code:

// ===================================================
// Called by your project file, prevents a 2nd
// instance of the program from executing and
// instead activates the already executing instance.
// Returns TRUE if a previous instance of the
// program is already running. Win32 ONLY
// ===================================================

function IsPrevInst: Boolean;
var
  semName,
    appClass: PChar;
  hSem: THandle;
  hWndMe: HWnd;
  appTitle: array[0..MAX_PATH] of Char;
begin
  // Init
  Result := FALSE;
  GetMem(semName, 15);
  GetMem(appClass, 15);
  StrPCopy(semName, 'SemaphoreName');
  StrPCopy(appClass, 'TApplication');
  StrPCopy(appTitle, ExtractFileName(Application.Title));

  // Create a Semaphore in memory.  If this is the
  // first instance, then hSem's value should be 0.
  hSem := CreateSemaphore(nil, 0, 1, semName);

  // Check to see if the semaphore exists
  if (hSem <> 0) and (GetLastError() =
    ERROR_ALREADY_EXISTS) then
  begin
    CloseHandle(hSem);

    // Get the current window's handle then change
    // its title so we can look for the other instance
    hWndMe := FindWindow(appClass, appTitle);
    SetWindowText(hWndMe, 'ZZZZZZZ');

    // Search for other instance of this window then bring
    // it to the top of the Z-order stack.  We find it by
    // matching the Application Class and
    // Application Title.
    hWndMe := FindWindow(appClass, appTitle);
    if (hWndMe <> 0) then
    begin
      BringWindowToTop(hWndMe);
      ShowWindow(hWndMe, SW_SHOWNORMAL);
    end;

    Result := TRUE;
  end;

  // Destroy PChars
  FreeMem(semName, 15);
  FreeMem(appClass, 15);
end;

//This is a different twist on the previous example.
//It uses a mutex (MUTually EXclusive) instead of a sema-
//phore.

procedure CheckPrevInstEx(MainFormClassName,
  MainFormCaption: string);
var
  PrevWnd: HWnd;
  Mutex: THandle;
begin
{$IFDEF Win32}
  Mutex := CreateMutex(nil, False, 'InstanceMutex');
  if WaitForSingleObject(Mutex, 10000) = WAIT_TIMEOUT then
    Application.Terminate;
{$ELSE}
  if HPrevInst = 0 then
    Application.Terminate;
{$ENDIF}

  PrevWnd := FindWindow(PChar(MainFormClassName),
    PChar(MainFormCaption));
  if PrevWnd <> 0 then
    PrevWnd := GetWindow(PrevWnd, GW_OWNER);
  if PrevWnd <> 0 then
  begin
    if IsIconic(PrevWnd) then
      ShowWindow(PrevWnd, SW_SHOWNORMAL)
    else
{$IFDEF Win32}
      SetForegroundWindow(PrevWnd);
{$ELSE}
      BringWindowToTop(PrevWnd);
{$ENDIF}
    Application.Terminate;
  end;
  ReleaseMutex(Mutex);
  CloseHandle(Mutex);
end;

To use the functions above, you can either embed them in the project file, or better yet, place them in a globally accessible library for use in all your applications that need them. Here's some example code for implementing them:

//This example uses the IsPrevInst function
program RxProto;

uses
  Forms,
  Main in 'Main.pas' {MainForm},
  Proc in 'Proc.pas',
  //This is my global library
  UTIL32 in '..\Lib\UTIL\Util32.pas',
  LoopPnThr in '..\Packages\LoopPnThr.pas';

{$R *.RES}

begin
  if not IsPrevInst then
  begin
    Application.Initialize;
    Application.CreateForm(TMainForm, MainForm);
    Application.Run;
  end
  else
    Application.Terminate;
end.
//Here's the other way...
program RxProto;

uses
  Forms,
  Main in 'Main.pas' {MainForm},
  Proc in 'Proc.pas',
  UTIL32 in '..\Lib\UTIL\Util32.pas',
  LoopPnThr in '..\Packages\LoopPnThr.pas';

{$R *.RES}

begin
  CheckPrevInstEx('TApplication', 'My Application');
  //This code won't do anything if CheckPrevInstEx doesn't
  //pass muster
  Application.Initialize;
  Application.CreateForm(TMainForm, MainForm);
  Application.Run;
end.

As you can see, pretty simple stuff. Have fun with it!

2006. június 24., szombat

How to combine two icons


Problem/Question/Abstract:

I want to combine 2 icons like Windows does with the links (the small arrow). Can anyone tell me how that works?

Answer:

function CombineIcons(FrontIcon, BackIcon: HIcon): HIcon;
var
  WinDC: HDC;
  FrontInfo: TIconInfo;
  FrontDC: HDC;
  FrontSv: HBITMAP;
  BackInfo: TIconInfo;
  BackDC: HDC;
  BackSv: HBITMAP;
  BmpObj: tagBitmap;
begin
  WinDC := GetDC(0);
  GetIconInfo(FrontIcon, FrontInfo);
  FrontDC := CreateCompatibleDC(WinDC);
  FrontSv := SelectObject(FrontDC, FrontInfo.hbmMask);
  GetIconInfo(BackIcon, BackInfo);
  BackDC := CreateCompatibleDC(WinDC);
  BackSv := SelectObject(BackDC, BackInfo.hbmMask);
  GetObject(FrontInfo.hbmMask, SizeOf(BmpObj), @BmpObj);
  BitBlt(BackDC, 0, 0, BmpObj.bmWidth, BmpObj.bmHeight, FrontDC, 0, 0, SRCAND);
  SelectObject(BackDC, BackInfo.hbmColor);
  DrawIconEx(BackDC, 0, 0, FrontIcon, 0, 0, 0, 0, DI_NORMAL);
  Result := CreateIconIndirect(BackInfo);
  SelectObject(FrontDC, FrontSv);
  DeleteDC(FrontDC);
  SelectObject(BackDC, BackSv);
  DeleteDC(BackDC);
  ReleaseDC(0, WinDC);
  DeleteObject(FrontInfo.hbmColor);
  DeleteObject(FrontInfo.hbmMask);
  DeleteObject(BackInfo.hbmColor);
  DeleteObject(BackInfo.hbmMask);
end;



Remember: The icon created with this function must be destroyed with DestroyIcon() function when finished using it.

2006. június 23., péntek

Copy, inherit or use?


Problem/Question/Abstract:

The three ways to use forms in the the Objects Repository

Answer:

COPY, INHERIT OR USE?

Code reusability saves us time and effort, increasing our productivity. Object-oriented programming has something to do with that, and in the case of Delphi we can reuse forms and even entire projects. For example if we have a form with a table, a dbgrid, a navigator and several buttons, we can save it like a model in the Object Repository to reuse it in several parts of our application or other applications. The same for a standard form of the type "Save, Don't save, Cancel". To add a form to the repository you have to right-click it and select "Add to Repository..." in the context menu. To save a project in the repository choose "Add to Repository..." from the Project menu.

To use a form of the repository in our application, in the File menu we chose New and in the New Items dialog we click the Forms tab to see the forms available in the repository. Then we select the form we want, the method of use (Copy, Inherit, or Use) and click the OK button. The dferences between these hree methods of use are described briefly here:

COPY

Creates a form that is copy of the form that is in repository. The changes you make to the copy won't affect the form in the repository (nor other projects that use it), and changes made to the form in the repository won't affect forms previously copied from it. This option is used when the form in the repository is just a base to work, with a very low level of standardization. Full-adaptation is possible.

INHERIT

It creates a form that derives from the form in the repository. Changes made to this derived form (inherited) won't affect the form in the repository, but the inverse is not true. This option is used when the form in the repository is well standardized but it is desired to allow some adaptation. It's the most powerful way to use a form.

USE

It adds the form of the repository to your project. It's not a copy, but the form of the repository itself, and thus any modification that you make to it will apply to other projects that USE or INHERIT it. This option is used when the form of the repository is a standard and is defined in itself (it doesn't require particular adaptations for each case/application).

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

2006. június 22., csütörtök

The power of accessors


Problem/Question/Abstract:

Making properties in your objects and using the accesors to do something.

Answer:

Property accessors are great.  They give you the power to do stuff like initialize an object or produce data that are dependent on the object's other properties.

You can make properties by declaring them in your interface section and then pressing Ctrl+Shift+'c'  it wil make a private variable called Fsomeproperty and a set methode called SetSomeProperty.

In this example i made an object that has a property that holds an other object and on the Read of this property i'll check whether it's not nil so i can create it on runtime.

You can also check if a value is within it's bounds on the Property's Set Accessor

type
  TMySubObject = class

  end;

  TMyObject = class
  private
    fMySubObject: TMySubObject;
    FASecondNum: Integer;
    FAFirstnum: Integer;
    function getMySubObject: TMySubObject;
    function GetTotaalOfFirstAndSecondNum: integer;
    procedure SetFirstnum(const Value: Integer);
  public

    property MySubObject: TMySubObject read getMySubObject;
    property AFirstnum: Integer read FAFirstnum write SetFirstnum;
    property ASecondNum: Integer read FASecondNum write FAFirstnum;
    property TotaalOfFirstAndSecondNum: integer read
      GetTotaalOfFirstAndSecondNum;
  end;

implementation

{$R *.DFM}

{ TMyObject }

function TMyObject.getMySubObject: TMySubObject;
begin
  // i check here to see if its assigned
  if not Assigned(fMySubObject) then
    fMySubObject := TMySubObject.Create;
  Result := fMySubObject;
end;

function TMyObject.GetTotaalOfFirstAndSecondNum: integer;
begin
  Result := FAFirstnum + FASecondNum;
end;

procedure TMyObject.SetFirstnum(const Value: Integer);
begin
  // here on the set u can check bounds :) .
  if (Value > 0) and (Value < 1000) then
    FAFirstnum := Value
  else
    raise Exception.Create('Number out of bounds');
end;

2006. június 21., szerda

Restricting TMemo length


Problem/Question/Abstract:

I was wondering if it were possible to restrict the number of lines in a TMemo.
For example my TMemo shall not allow to show more than 7 lines of text at once.

Answer:

Handle the OnChange event with the code below.

  
procedure TForm1.Memo1Change(Sender: TObject);
begin
  if Memo1.Lines.Count > 7 then
  begin
    MessageBeep($FFFFFFFF);
    Memo1.Lines.Delete(7);
  end;
end;

2006. június 20., kedd

EventLog change notification in real-time


Problem/Question/Abstract:

I needed a way to be notified in real-time when someone acceded my computer inside an intranet. After doing some research, the solution would pass by using the Security event log that is used when you activate any audit option.

Answer:

In the Control Panel\Administrative Tools\Local Security Police\Local Polices\Audit Police I could audit various account events; in my case I was specially looking for 'Audit account logon events' and 'Audit logon events'.
After setting up these to audit success events, I could confirm that 'Security event log' really logs these attempts. But, how could I be notified by the system when a new event was added to the event log?

The timer - newbies solution

The first solution would be to use a timer that would check the event log count every second, and if the count was different from the last one the log has changed.

To test this we need to create a new project. Declare in the private part of the form the following:

private
{ Private declarations }
FLastCount: Integer; // last event log count
FLog: THandle; // handle to the opened log

Because to work with the log we need to open it first, we do that in the Button1.Click;

procedure TForm1.Button1Click(Sender: TObject);
begin
  FLog := OpenEventLog(nil, PChar('Security'));
  Timer1.Enabled := True;
end;

And now we only need to check the event log count

procedure TForm1.Timer1Timer(Sender: TObject);
var
  lCount: Cardinal;

begin
  if GetNumberOfEventLogRecords(FLog, lCount) and (lCount <> FLastCount) and (lCount >
    0) then
  begin
    FLastCount := lCount;
    ListBox1.Items.Add('Changed at ' + DateTimeToStr(Now()));
  end;
end;

If we are pragmatic we know that this solution works in spite of the fact that there is a little problem. We know that if this were the best solution, I wouldn't be here writing this article ;)

Event object - professional solution

In the Win32 API under the event log group we can find a function that can help us detecting when the event log changes.
The NotifyChangeEventLog function lets an application receive notification when an event is written to the event log file specified by the hEventLog parameter.
When the event is written to the event log file, the function causes the event object specified by the hEvent parameter to become signaled.

Great, this is exactly what we want. But, what is an event object?
An event object is a object that can be created to signal operations between different system processes. The event object is under the same category has Mutex, Process and Semaphores.

To create an event object we use the CreateEvent function, which creates a named or unnamed event object.

The code to test this new option is:

private
{ Private declarations }
FLog: THandle; // handle to the opened log
FEvent: THandle; // handle to the event object

procedure WaitForChange;

procedure TForm1.Button1Click(Sender: TObject);
begin
  FLog := OpenEventLog(nil, PChar('Security'));
  FEvent := CreateEvent(nil, True, False, nil); // create unnamed object
  NotifyChangeEventLog(FLog, FEvent); // start the event log change notification

  WaitForChange;
end;

The way we have to check if a existent event object is signaled or not is by using the WaitForSingleObject function. This function returns when one of the following occurs:
the specified object is in the signaled state.
the time-out interval elapses.

Because, we don't know when the log changes we will not use a time-out interval, so the only way of this function returns is when the event object is in the signaled state.

So, we will need a way of having a loop, which will be constantly calling WaitForSingleObject when it returns. This is the job for the recursive WaitForChange method.

procedure TForm1.WaitForChange;
var
  lResult: Cardinal;

begin
  // reset event signal, so the system can signal it again
  ResetEvent(FEvent);
  // wait for event to be signalled
  lResult := WaitForSingleObject(FEvent, INFINITE);
  // check event result
  case lResult of
    WAIT_OBJECT_0:
      begin
        ListBox1.Items.Add('Changed at ' + DateTimeToStr(Now()));
        Application.ProcessMessages;
      end;
  end;

  // wait for change again
  WaitForChange;
end;

As you have noticed, this solution has a big problem. The application stops responding, but why? Well, WaitForSingleObject function checks the current state of the specified object. If the object's state is non signaled, the calling thread enters an efficient wait state. The thread consumes very little processor time while waiting for the object state to become signaled or the time-out interval to elapse.
Unfortunaly the 'efficient wait state' makes the calling thread being as it was sleeping... sleeping as a rock!

If we didn't need to have an available interface, we could end here. But we need to have a working interface, at least to close the application.

The only solution to our case is to have a secondary thread, which will do this wait, and will call an event of the main thread when a change occurs.

The main code for this thread is:

procedure TNotifyChangeEventLog.Execute;
var
  lResult: DWORD;

begin
  while (not Terminated) do
  begin
    // reset event signal, so we can get it again
    ResetEvent(FEventHandle);
    // wait for event to happen
    lResult := WaitForSingleObject(FEventHandle, INFINITE);
    // check event result
    case lResult of
      WAIT_OBJECT_0: Synchronize(DoChange);
    else
      Synchronize(DoChange);
    end;
  end;
end;

The complete project

Because I needed more things than just to know if the event log changed or not, I ended up by creating a component to work with the event log.
This component is not just another event log component, because it incorporates more things than the usual: read event log, change notifications, functions to scroll the event log (First, Last, Eof, Next).

At this moment I've already sent this component to be approved for incorporating the JVCL (JEDI VCL) at http://jvcl.sourceforge.net
Because it steel wasn't approved I included it with this sample.

Final notes

Why all this trouble to use the event object solution if the timer solution was enough? The problem is that the event object solution is safer.

For example, if you wanted to use the RegNotifyChangeKeyValue function that notifies the caller about changes to the attributes or contents of a specified registry key.
What you would do? You're right, it would almost impossible to use a timer, and would be so simple to use an event object.

2006. június 19., hétfő

Add controls to a form at runtime and stream the result to a file


Problem/Question/Abstract:

Is it possible to add controls to a form at runtime, then stream the result to single file, thus preserving all changes for next application run?

Answer:

Yes, with some caveats. You need to register all control classes that are used on the form plus all that the user may add at runtime. Here is a little example:

unit Unit1;

interface

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

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

var
  Form1: TForm1;

implementation

{$R *.DFM}

const
  formdata = 'formdata.bin';

procedure TForm1.Button1Click(Sender: TObject);
begin
  with TEdit.Create(self) do
  begin
    left := button1.Left + button1.width + 10;
    top := button1.top;
    Name := 'NewEdit';
    parent := self;
  end;
end;

procedure TForm1.Button2Click(Sender: TObject);
var
  fs: TFileStream;
begin
  fs := TFileStream.Create(ExtractFilePath(ParamStr(0)) + formdata, fmCreate);
  try
    fs.WriteComponent(self);
  finally
    fs.free
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  filename: string;
  fs: TFileStream;
  i: integer;
begin
  filename := ExtractFilePath(ParamStr(0)) + formdata;
  if fileExists(filename) then
  begin
    for i := ComponentCount - 1 downto 0 do
      Components[i].Free;
    fs := Tfilestream.create(filename, fmOpenRead or fmShareDenyWrite);
    try
      fs.ReadComponent(self);
      button1.enabled := false;
    finally
      fs.free
    end;
  end;
end;

initialization
  RegisterClasses([TButton, TEdit]);
end.

2006. június 18., vasárnap

Set the Cursor Speed


Problem/Question/Abstract:

How to set the cursor speed

Answer:

This is an easy one. Just put a simple line in the form create method and all you text input component will receive the message.

Line is:

SetCaretBlinkTime(Speed);

Where speed is an integer

Exemple:

SetCaretBlinkTime(500);

The more the integer, the more time it take to flash.

Be aware that if you don't reset your cursor speed your cursor speed will remain the same for all you windows session. Just execute this line, close you app and come back here. Try to type an url in the adress bar you'll see.

SetCaretBlinkTime(1);

2006. június 17., szombat

How to trap and process Windows messages before the applications' window procedure is executed (2)


Problem/Question/Abstract:

I wrote code for the OnMessage event handler of Application object to trap all Windows messages sent to my application, but it doesn't seem to fire on all messages. Is there a way to trap all messages sent to my application?

Answer:

There sure is. And the answer to this "problem" is amazingly simple. But before I go into trapping messages at the application level, I should probably discuss some mechanics.

TApplication's "Hidden" Window

It's not a commonly known fact that the default Application object creates a hidden window when your application is started. But you can seen evidence of this by creating a new application saving it, then running it (make sure you don't rename anything - just keep the main form as "Form1" and the project as "Project1). When you run the application, you'll notice that the caption bar for your main form says, "Form1" while the icon displayed on the task bar says "Project1." That icon represents the application's hidden window, and it affects your program in many ways, especially when you're trying to handle messages sent to your application.

Delphi surfaces the OnMessage event for the Application object. The OnMessage event handler is "supposed" to allow you trap every message sent to your application. But there's a problem with this: OnMessage will only fire when there's something in the Application object's message queue. These messages are typically window management messages such as WM_PAINT or messages sent to the application from Windows through PostMessage, Broadcast or SystemMessage . However, messages sent directly to a window using SendMessage bypass the Application object's message queue, so OnMessage doesn't fire for those types of situations.

Some of you more familiar with handling windows messages might think that a solution to the problem above might be to override the WndProc method for the Application object. Unfortunately, that's not possible because TApplication's WndProc method is not only private, it's also declared as a static method which means it's not overrideable. So it's not only invisible, you can't create a TApplication subclass to override WndProc (not that you'd want either). But that doesn't mean that you can't get to the WndProc method using alternative means.

"Hooking" All Messages

Even though WndProc is all but closed to direct subclassing, TApplication does include a method called HookMainWindow that allows you to insert your own message handler at the top of WndProc to intercept messages sent to your application before they're handled by the Application object. This is convenient for all developers, and solves the problem of trapping any message sent to your application.

HookMainWindow is declared under TApplication as follows:

procedure HookMainWindow(Hook: TWindowHook);

Notice that HookMainWindow takes one parameter, Hook of type TWindowHook. TWindowHook is a method pointer type that's defined like so:

type
  TWindowHook = function(var Message: TMessage): Boolean of object;

Since TWindowHook is a method pointer, you can define your own method as the hook function as long as it follows the nomenclature defined for TWindowHook. Notice that the return value of the function is of type Boolean. This is the equivalent of the "Handled" parameter of OnMessage. If your function handles a particular message, you'd return true. This will be passed back to the Application's WndProc and message processing for that message will be terminated. Otherwise, you'd return False. Here's an example method:

function TForm1.AppHookFunc(var Message: TMessage): Boolean;
begin
  Result := False; //I just do this by default
  if Message.Msg = WM_ < SomethingOrOther > then
  begin
    ...DoSomething...
      Result := True;
  end;
end;

Okay, now that we've set up everything, we need to make the application hook the messages. This can be done in the main form's OnCreate method:

function TForm1.FormCreate(Sender: TObject);
begin
  HookMainWindow(AppHookFunc);
end;

I should mention that you need to clear the hook using, you guessed it, UnHookMainWindow, after you're done using it, and this can be done in the OnDestroy for the main form:

function TForm1.FormDestroy(Sender: TObject);
begin
  UnHookMainWindow(AppHookFunc);
end;

Okay, disgustingly simple. But I feel the best things in life are those that give maximum satisfaction for the least amount of cost (please don't read ANYTHING into that <G>). So, now you've got the tools to create your own message "hooker" (sorry, had to do that at least once). Until next time...

2006. június 16., péntek

How to attach a popup menu to a TMenuItem at runtime


Problem/Question/Abstract:

How do you attach a popup menu to the TMenuItem of a main menu as a submenu at runtime?

Answer:

This is one way to do it. Note: The PopupMenuItems are added to an existing menu item of the mainmenu mnuWhatEver.

{ ... }
var
  I: integer;
  MenuItem: TMenuItem;
begin
  for I := 0 to PopupMenu1.Items.Count - 1 do
  begin
    with PopupMenu1.Items[I] do
      MenuItem := NewItem(Caption, ShortCut, Checked, Enabled, OnClick, HelpContext,
        Name);
    mnuWhatEver.Add(MenuItem);
  end;
end;

Note: when using NewItem the MenuItem has no owner you'll have to free them yourself when you're done with it.

You could also use:

MenuItem := TMenuItem.Create(Self);

and then copy all the properties yourself.

MenuItem.Captions := PopupMenu1.Items[I].Caption;
MenuItem.OnClick := PopupMenu1.Items[I].OnClick;
{etc.}

2006. június 15., csütörtök

How to prevent the use of the listview's popup menu in a TOpenDialog


Problem/Question/Abstract:

Using the TOpenDialog and TSaveDialog components you get the standard Windows dialog boxes including the ability to create/ delete files or folders. Is it possible to block the create/ delete options?

Answer:

Well, few things are really impossible if you try hard enough, but this is at least somewhat difficult. The common dialog API has no facility to do this, the dialogs simply use the same listview class Explorer also uses, so it has all the same functionality. So to block these functions one would have to subclass (the API way) the listview control (to block right mouse clicks and the offending keyboard messages as well as WM_CONTEXTMENU). One could do that in the dialogs OnShow event. The problem is finding the handle of the listview, these Explorer-style dialogs have an utterly weird internal window hierarchy. And the listview in question has not been created yet when the OnShow event fires (go figure). So you have to post a user message to the form from the OnShow event and do the subclassing in that messages handler.

Here is a quick sketch of a modified opendialog class that prevents the use of the listviews popup menu. For some reason it is not possible to trap the DEL key press on the listview level, so if you want to also trap that, and perhaps even the editing of filenames, you will also need to subclass the shellview, which is the parent of the listview, and look for WM_NOTIFY messages from the listview there.

{ ...}
type
  TSafeOpenDialog = class(Dialogs.TOpenDialog)
  private
    FOldListviewProc: Pointer;
    FListviewMethodInstance: Pointer;
    FLIstview: HWND;
    procedure WMApp(var msg: TMessage); message WM_APP;
  protected
    procedure DoShow; override;
    procedure ListviewWndProc(var msg: TMessage);
  public
    destructor Destroy; override;
  end;

destructor TSafeOpenDialog.Destroy;
begin
  inherited;
  if Assigned(FListviewMethodInstance) then
    FreeObjectInstance(FListviewMethodInstance);
end;

procedure TSafeOpenDialog.DoShow;
begin
  inherited;
  PostMessage(handle, WM_APP, 0, 0);
end;

procedure TSafeOpenDialog.ListviewWndProc(var msg: TMessage);
begin
  msg.result := 0;
  case msg.Msg of
    WM_RBUTTONDOWN, WM_RBUTTONUP, WM_CONTEXTMENU:
      Exit;
  end;
  msg.result := CallWindowProc(FOldListviewProc, FLIstview, msg.Msg,
    msg.WParam, msg.LParam);
end;

procedure TSafeOpenDialog.WMApp(var msg: TMEssage);
begin
  FListviewMethodInstance := MakeObjectInstance(ListviewWndProc);
  FListview := FindWindowEx(Windows.GetParent(handle), 0, 'SHELLDLL_DefView', nil);
  if FListview <> 0 then
  begin
    FListview := GetWindow(FListview, GW_CHILD);
    if FListview <> 0 then
      FOldListviewProc := Pointer(SetWindowLong(FListview, GWL_WNDPROC,
        Integer(FListviewMethodInstance)))
    else
      OutputDebugString('Listview not found');
  end
  else
    OutputDebugString('Shell view not found');
end;

2006. június 14., szerda

Creating shaped forms


Problem/Question/Abstract:

Cool Bitmap shaped forms the easy way

Answer:

Hey! Bored with rectangular windows? HERE'S THE CODE to make any shape you want based on a bitmap picture. How to do it:

1. First, make or choose any background bitmap you want your form to have. Then fill areas you want to go transparent with a distinct color (In this example, it is white).  NOTE: The bitmap's size must be the actual size you want on your form. No stretching in Delphi will work.

2. In Delphi, add a TImage(Image1) component on the form. Choose your bitmap and put the component where you want it. Autosize must be true. Other visual components must be on top of the "visible" part of the picture so that they will be seen.

3. Add the following code (...I mean short code) to your FormCreate procedure. I know I should have made a component for it so that no code would be needed. But just to show you how, I guess this would suffice.

procedure TForm1.FormCreate(Sender: TObject);
const
  // Image Color to be made transparent
  MASKCOLOR = clWhite;

  // Cutting adjustments
  ADJ_TOP = 22; {TitleBar}
  ADJ_BOTTOM = 22; {TitleBar}
  ADJ_LEFT = 3; {Border}
  ADJ_RIGHT = 3; {Border}
var
  ShowRegion, CutRegion: HRgn;
  y, x1, x2: integer;
  PixelColor: TColor;
begin

  ShowRegion := CreateRectRgn(Image1.Left + ADJ_LEFT, Image1.Top + ADJ_TOP,
    Image1.Left + Image1.Width + ADJ_RIGHT, Image1.Top + Image1.Height + ADJ_BOTTOM);

  // Cut the parts whose color is equal to MASKCOLOR by rows
  for y := 0 to Image1.Picture.Bitmap.Height - 1 do
  begin
    x1 := 0; // starting point of cutting
    x2 := 0; // end point of cutting
    repeat
      PixelColor := Image1.Picture.Bitmap.Canvas.Pixels[x2, y];
      // the above will return -1 if x2 reached beyond the image
      if (PixelColor = MaskColor) then
        Inc(x2)
      else
      begin
        //do following if pixel reached beyond image or if color of pixel
                                is not MaskColor
        if x1 <> x2 then
        begin
          // Create the region to be cut. The region will be one line of
                                 pixels/a pixel with color of                  
                                 // MaskColor
          CutRegion := CreateRectRgn(
            X1 + Image1.Left + ADJ_LEFT,
            Y + Image1.Top + ADJ_TOP,
            X2 + Image1.Left + ADJ_RIGHT,
            Y + Image1.Top + ADJ_TOP + 1);

          try
            CombineRgn(ShowRegion, ShowRegion, CutRegion, RGN_DIFF);
            // RGN_DIFF will get the difference of ShowRegion
          finally
            DeleteObject(CutRegion);
          end;
        end;

        Inc(x2);
        x1 := x2;
      end;
    until PixelColor = -1;
  end;

  // Set the window to have the above defined region
  SetWindowRgn(Form1.Handle, ShowRegion, True);

  // NOTE : Do not free close/delete ShowRegion because it will become owned
  // by the operating system

  // You can manually disable the showing of the whole
  //form while dragging, with the following line but
  // just leave it since it is dependent on your
  // windows settings. Some people may want to have their
  // windows show its contents while dragging.

  // SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, 0, nil, 0); {Disable drag showing}
  // SystemParametersInfo(SPI_SETDRAGFULLWINDOWS, 1, nil, 0); {Enable drag showing}
end;

NOW FOR THE FORM DRAGGING PART

1. Add this line to the private declarations of your Form:

procedure WMNCHitTest(var Msg: TWMNCHitTest); message wm_NCHitTest;

2. In the implementation part. Add the following (assuming your Form name is Form1):

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

Also, add a button to close the form because the title bar cannot be seen. That's all!

2006. június 13., kedd

How to save and restore font properties in the registry (3)


Problem/Question/Abstract:

What is the best way to store the font for a TDBGrid. I can change it fine with a Font Dialog but how can i store those settings so they take effect the next time the user fires up the program ?

Answer:

Save a font:


function SaveFont(F_Font: TFont): boolean;
var
  FLog: TLogFont;
  Reggy: TRegistry;
begin
  GetObject(F_Font.Handle, SizeOf(FLog), @FLog);
  Reggy := TRegistry.Create;
  try
    Reggy.OpenKey({'\REGKEYNAME'}, true);
    Reggy.WriteBinaryData({'VALUENAME'}, FLog, SizeOf(FLog));
    result := true;
  finally
    Reggy.Free;
  end;
end;


Get a font:


function SetFont(F_Font: TFont): boolean;
var
  FLog: TLogFont;
  Reggy: TRegistry;
  NewFHnd: longint;
begin
  result := false;
  Reggy := TRegistry.Create;
  try
    if Reggy.OpenKey({'\REGKEYNAME'}, false) and
      Reggy.ValueExists({'VALUENAME'}) then
    begin
      Reggy.ReadBinaryData({'VALUENAME'}, FLog, SizeOf(FLog));
      {set Font to the retrieved font}
      NewFHnd := CreateFontIndirect(FLog);
      result := (NewFHnd <> 0);
      if result then
        F_Font.Handle := NewFHnd;
    end;
  finally
    Reggy.Free;
  end;
end;

2006. június 12., hétfő

How to filter a TDateField


Problem/Question/Abstract:

How can I use the table filter for dates?

Answer:

Enclose the date literals in quotation marks:

Table1.Filter := 'DateFld = ' + QuotedStr('01/12/2000');

2006. június 11., vasárnap

How to automatically set the font color according to the background color


Problem/Question/Abstract:

How can I make the font color dependent from the background color? I want to make the color selection between white and black dependent from the background color.

Answer:

This seems to work pretty well, at least on a non-palette video mode:

function InverseColor(color: TColor): TColor;
var
  rgb_: TColorref;
  function Inv(b: Byte): Byte;
  begin
    if b > 128 then
      result := 0
    else
      result := 255;
  end;
begin
  rgb_ := ColorToRgb(color);
  rgb_ := RGB(Inv(GetRValue(rgb_)), Inv(GetGValue(rgb_)), Inv(GetBValue(rgb_)));
  Result := rgb_;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  with colordialog do
  begin
    color := label1.color;
    if execute then
    begin
      label1.color := color;
      label1.font.color := InverseColor(color);
    end;
  end;
end;

2006. június 10., szombat

How to get the font family when the user selects a font


Problem/Question/Abstract:

Windows organizes fonts by family and categorizes families with five family names. A sixth name ("Dontcare") allows an application to use the default font. These family names correspond to constants found in the WINGDI.H file: FF_DECORATIVE, FF_DONTCARE, FF_MODERN, FF_ROMAN, FF_SCRIPT, and FF_SWISS. An application uses these constants when it creates a font, selects a font, or retrieves information about a font. Fonts within a family are distinguished by size (10 point, 24 point, and so on) and style (regular, italic, and so on). However, it doesn't say how I can get that setting for any given font. Is there a way to use GetObject to do this?

Answer:

The following table describes the font-family names:

Font-family name: Description
Decorative: Specifies a novelty font. An example is Old English.
Dontcare: Specifies a generic family name. This name is used when information about a font does not exist or does not matter.
Modern: Specifies a monospace font with or without serifs. Monospace fonts are usually modern; examples include Pica, Elite, and Courier New.
Roman: Specifies a proportional font with serifs. An example is Times New Roman.
Script: Specifies a font that is designed to look like handwriting; examples include Script and Cursive.
Swiss: Specifies a proportional font without serifs. An example is Arial.

Here's a sample method, using the form's font:

procedure TForm1.Button1Click(Sender: TObject);
var
  LogFont: TLogFont;
  BytesReturned: integer;
  s: string;
begin
  BytesReturned := GetObject(Font.Handle, sizeof(LogFont), @LogFont);
  if BytesReturned = 0 then
  begin
    caption := 'Failed';
    exit;
  end;
  case LogFont.lfPitchAndFamily and $F0 of
    FF_DONTCARE: s := 'Don''t care, or don''t know.';
    FF_ROMAN: s := 'Variable stroke width, serifed.';
    FF_SWISS: s := 'Variable stroke width, sans-serifed.';
    FF_MODERN: s := 'Constant stroke width, serifed or sans-serifed.';
    FF_SCRIPT: s := 'Script; Cursive, etc.';
    FF_DECORATIVE: s := 'Decorative: Old English, etc.';
  end;
  caption := s;
end;

2006. június 9., péntek

How to calculate the current week


Problem/Question/Abstract:

How to calculate the current week

Answer:

Solve 1:

There are 2 other functions included which are required for our function. One checks for a leap year, the other returns the number of days in a month (checking the leap year) and the third is the one you want, the week of the year.


function kcIsLeapYear(nYear: Integer): Boolean;
begin
  Result := (nYear mod 4 = 0) and ((nYear mod 100 <> 0) or (nYear mod 400 = 0));
end;

function kcMonthDays(nMonth, nYear: Integer): Integer;
const
  DaysPerMonth: array[1..12] of Integer = (31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31);
begin
  Result := DaysPerMonth[nMonth];
  if (nMonth = 2) and kcIsLeapYear(nYear) then
    Inc(Result);
end;

function kcWeekOfYear(dDate: TDateTime): Integer;
var
  X, nDayCount: Integer;
  nMonth, nDay, nYear: Word;
begin
  nDayCount := 0;
  DecodeDate(dDate, nYear, nMonth, nDay);
  for X := 1 to (nMonth - 1) do
    nDayCount := nDayCount + kcMonthDays(X, nYear);
  nDayCount := nDayCount + nDay;
  Result := ((nDayCount div 7) + 1);
end;


Solve 2:

function CalendarWeek(ADate: TDateTime): integer;

{Author: Ralph Friedman (ralphfriedman@email.com)

Calculates calendar week assuming:
Monday is the 1st day of the week
The 1st calendar week is the 1st week of the year that contains a Thursday

-1 result indicates error.
Any other negative result indicates week 52 or 53 of the previous year.}

var
  day: word;
  dayOne: word;
  firstOfYear: TDateTime;
  month: word;
  monthOne: word;
  prevDayOne: word;
  year: word;
begin
  Result := -1;
  try
    DecodeDate(ADate, year, month, day);
  except
    Exit;
  end;
  case DayOfWeek(EncodeDate(year, 1, 1)) of
    1: dayOne := 2; {Sunday}
    2: dayOne := 1; {Monday}
    3: dayOne := 31; {Tuesday}
    4: dayOne := 30; {Wednesday}
    5: dayOne := 29; {Thursday}
    6: dayOne := 4; {Friday}
    7: dayOne := 3; {Saturday}
  else
    dayOne := 0;
  end;
  case DayOfWeek(EncodeDate(year - 1, 1, 1)) of
    1: prevDayOne := 2; {Sunday}
    2: prevDayOne := 1; {Monday}
    3: prevDayOne := 31; {Tuesday}
    4: prevDayOne := 30; {Wednesday}
    5: prevDayOne := 29; {Thursday}
    6: prevDayOne := 4; {Friday}
    7: prevDayOne := 3; {Saturday}
  else
    prevDayOne := 0;
  end;
  if (prevDayOne = 0) or (dayOne = 0) then
    Exit;
  if dayOne > 4 then
  begin
    Dec(year);
    monthOne := 12
  end
  else
    monthOne := 1;
  firstOfYear := EncodeDate(year, monthOne, dayOne);
  if (ADate < firstOfYear) then
    if (PrevDayOne > 4) then
      Result := -53
    else
      Result := -52
  else
    Result := (Trunc(ADate - firstOfYear) div 7) + 1;
end;

2006. június 8., csütörtök

Duplicate a metafile without the fill color of the original


Problem/Question/Abstract:

I have a TMetafile that is created drawing to a TMetafileCanvas. There are various polygons that are drawn using a solid brush style and a grey fill color (red outline). Some of the time I would like to duplicate the metafile but have no fill color (ie just red outlines). But I want to keep it as a metafile and not have to regenerate the original with no fill. I've been looking into changing the palette color, but the metafile does not seem to have any palette entries. Anyone have any ideas?

Answer:

Solve 1:

function unfilledMetafile(const in_metafile: TMetafile): TMetafile;
var
  metaHeaderSize: DWORD;
  metaHeader: PEnhMetaHeader;
  data: PByteArray;
  metaRec: PEMR;
  brushRec: PEMRCreateBrushIndirect;
  i: cardinal;
  {Debug, copy to clipboard
  MyFormat: Word;
  AData: Cardinal;
  APalette: HPALETTE; }
begin
  {Get the header}
  metaHeaderSize := GetEnhMetaFileHeader(in_metafile.Handle, 0, nil);
  if (metaHeaderSize > 0) then
  begin
    GetMem(metaHeader, metaHeaderSize);
    try
      GetEnhMetaFileHeader(in_metafile.Handle, metaHeaderSize, metaHeader);
      GetMem(data, metaHeader^.nBytes);
      ZeroMemory(data, metaHeader^.nBytes);
      try
        GetEnhMetaFileBits(in_metafile.Handle, metaHeader^.nBytes, @(data^[0]));
        {Go through the metafile and update brushes to unfilled}
        i := metaHeaderSize;
        while (i < metaHeader^.nBytes) do
        begin
          {Get record info}
          metaRec := PEMR(@(data^[i]));
          if (metaRec^.itype = EMR_CREATEBRUSHINDIRECT) then
          begin
            brushRec := PEMRCreateBrushIndirect(metaRec);
            brushRec^.lb.lbStyle := BS_HOLLOW;
          end;
          Inc(i, metaRec^.nSize);
        end;
        {Put the data into a new metafile}
        Result := TMetafile.Create();
        Result.Handle := SetEnhMetaFileBits(metaHeader^.nBytes, PChar(data));
      finally
        FreeMem(data, metaHeader^.nBytes);
      end;
    finally
      FreeMem(metaHeader, metaHeaderSize);
    end;
  end
  else
  begin
    raise Exception.Create('Unable to create unfilled metafile');
  end;
  {Debug, copy to clipboard
  Result.SaveToClipboardFormat(MyFormat, AData, APalette);
  ClipBoard.SetAsHandle(MyFormat, AData); }
end;


Solve 2:

The "callback" function:

function EnhMetaFileProc(DC: HDC; {handle to device context}
  lpHTable: PHANDLETABLE; {Pointer to metafile handle table}
  lpEMFR: PENHMETARECORD; {Pointer to metafile record}
  nObj: Integer; {Count of objects}
  aCanvas: TCanvas): Integer; stdcall;
var
  tmpPen, OldPen: HPen;
  tmpBrush, OldBrush: HBrush;
begin
  aCanvas.Pen.Color := clBlack;
  tmpPen := aCanvas.Pen.Handle;
  aCanvas.Brush.Style := bsClear
    tmpBrush := aCanvas.Brush.Handle;
  OldPen := SelectObject(aCanvas.Handle, tmpPen);
  OldBrush := SelectObject(aCanvas.Handle, tmpBrush);
  {Draw the metafile record}
  PlayEnhMetaFileRecord(dc, lpHTable^, lpEMFR^, nObj);
  SelectObject(aCanvas.Handle, OldPen);
  SelectObject(aCanvas.Handle, OldBrush);
  {Set to zero to stop metafile enumeration}
  Result := 1;
end;

And then, you draw it using EnumEnhMetafile with the callback:

EnumEnhMetaFile(Canvas.Handle, FWMFImage.Handle, @EnhMetaFileProc, Canvas, tmpRect);

2006. június 7., szerda

Develop a wizard driven style program


Problem/Question/Abstract:

What is the best way to develop a wizard driven style program such as the InstallShield setup programs?

Answer:

Use a TPageControl with each TTabSheet having the property TabVisible set to False. Place Back and Next buttons under the TPageControl and add:

YourPageCtl.SelectNextPage(True);

for the Next button OnClick event, and:

YourPageCtl.SelectNextPage(False);

for the Back button OnClick event.

One caveat. During design-time TabVisible has no effect, and so when you run the program, all of your controls on the TPageControl will shift up by the TabHeight! That's why it's a good workaround to set TabHeight to 1, so that the difference between run-time and design-time is as small as possible.

An even better workaround would be (if you use TabSheets with TabVisible := False) to change the tab style to buttons. Although they are not visible at runtime, they do stop that infuriating extra border.

2006. június 6., kedd

How to add a menu item to the Windows Taskbar popup menu


Problem/Question/Abstract:

I want to add a menu item to the menu, which pops up when you right-click on the taskbar button for your application. I know how to add item to form system menu, but it doesn't change the popup menu on the taskbar.

Answer:

Solve 1:

unit Unit1;

interface

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

const
  idSysAbout = 100;

type
  TForm1 = class(TForm)
    procedure FormCreate(Sender: TObject);
  public
    procedure AppMessage(var Msg: TMsg; var Handled: Boolean);
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.AppMessage(var Msg: TMsg; var Handled: Boolean);
begin
  if (Msg.message = WM_SYSCOMMAND) and (Msg.WParam = idSysAbout) then
  begin
    ShowMessage('This is a test');
    Handled := True;
  end;
end;

procedure TForm1.FormCreate(Sender: TObject);
begin
  AppendMenu(GetSystemMenu(Application.Handle, False), MF_SEPARATOR, 0, '');
  AppendMenu(GetSystemMenu(Application.Handle, False), MF_STRING, idSysAbout, 'Test');
  Application.OnMessage := AppMessage;
end;

end.


Solve 2:

The popup menu for a Taskbar button is the Application windows system menu. To add items to it you need to use API functions.

{ ... }
var
  hSysMenu: HMENU;
begin
  hSysMenu := getSystemMenu(Application.handle, false);
  AppendMenu(hSysmenu, MF_STRING, $100, 'My Item');

The item IDs you use ($100 in this example) need to be multiples of 16, in hex notation that means the last digit is 0. Take care not to conflict with the predefined SC_ menu constants for the system menu. All of these have values above 61000.

To get informed when the user selects your new item you need to hande the WM_SYSCOMMAND message send to the Application window. For this you attach a hook to it, using Application.HookMainWindow.

procedure TForm1.FormCreate(Sender: TObject);
var
  hSysMenu: HMENU;
begin
  hSysMenu := getSystemMenu(application.handle, false);
  AppendMenu(hSysmenu, MF_STRING, $100, 'My Item');
  Application.HookMainWindow(AppHook);
end;

function Tform1.AppHook(var Message: TMessage): Boolean;
begin
  if (message.Msg = WM_SYSCOMMAND) and ((message.WParam and $FFF0) = $100) then
  begin
    Result := true;
    ShowMessage('Hi, folks');
  end
  else
    Result := false;
end;

2006. június 5., hétfő

How to print bitmaps


Problem/Question/Abstract:

How to print bitmaps

Answer:

I use this function for printing bitmaps and it has never failed so far. R is the restangle on the printer canvas that the printout must fit in:


procedure StretchPrint(R: TRect; ABitmap: Graphics.TBitmap);
var
  dc: HDC;
  isDcPalDevice: Bool;
  hDibHeader: THandle;
  pDibHeader: pointer;
  hBits: THandle;
  pBits: pointer;
  ppal: PLOGPALETTE;
  pal: hPalette;
  Oldpal: hPalette;
  i: integer;
begin
  pal := 0;
  OldPal := 0;
  {Get the screen dc}
  dc := GetDc(0);
  {Allocate memory for a DIB structure}
  hDibHeader := GlobalAlloc(GHND, sizeof(TBITMAPINFO) + (sizeof(TRGBQUAD) * 256));
  {get a pointer to the alloced memory}
  pDibHeader := GlobalLock(hDibHeader);
  {fill in the dib structure with info on the way we want the DIB}
  FillChar(pDibHeader^, sizeof(TBITMAPINFO) + (sizeof(TRGBQUAD) * 256), #0);
  PBITMAPINFOHEADER(pDibHeader)^.biSize := sizeof(TBITMAPINFOHEADER);
  PBITMAPINFOHEADER(pDibHeader)^.biPlanes := 1;
  PBITMAPINFOHEADER(pDibHeader)^.biBitCount := 8;
  PBITMAPINFOHEADER(pDibHeader)^.biWidth := ABitmap.width;
  PBITMAPINFOHEADER(pDibHeader)^.biHeight := ABitmap.height;
  PBITMAPINFOHEADER(pDibHeader)^.biCompression := BI_RGB;
  {find out how much memory for the bits}
  GetDIBits(dc, ABitmap.Handle, 0, ABitmap.height, nil, TBitmapInfo(pDibHeader^),
    DIB_RGB_COLORS);
  {Alloc memory for the bits}
  hBits := GlobalAlloc(GHND, PBitmapInfoHeader(pDibHeader)^.BiSizeImage);
  {Get a pointer to the bits}
  pBits := GlobalLock(hBits);
  {Call fn again, but this time give us the bits!}
  GetDIBits(dc, ABitmap.Handle, 0, ABitmap.height, pBits, PBitmapInfo(pDibHeader)^,
    DIB_RGB_COLORS);
  {Release the screen dc}
  ReleaseDc(0, dc);
  {Just incase the printer drver is a palette device}
  isDcPalDevice := false;
  if GetDeviceCaps(Printer.Canvas.Handle, RASTERCAPS) and RC_PALETTE = RC_PALETTE then
  begin
    {Create palette from dib}
    GetMem(pPal, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)));
    FillChar(pPal^, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)), #0);
    pPal^.palVersion := $300;
    pPal^.palNumEntries := 256;
    for i := 0 to (pPal^.PalNumEntries - 1) do
    begin
      pPal^.palPalEntry[i].peRed := PBitmapInfo(pDibHeader)^.bmiColors[i].rgbRed;
      pPal^.palPalEntry[i].peGreen := PBitmapInfo(pDibHeader)^.bmiColors[i].rgbGreen;
      pPal^.palPalEntry[i].peBlue := PBitmapInfo(pDibHeader)^.bmiColors[i].rgbBlue;
    end;
    pal := CreatePalette(pPal^);
    FreeMem(pPal, sizeof(TLOGPALETTE) + (255 * sizeof(TPALETTEENTRY)));
    oldPal := SelectPalette(Printer.Canvas.Handle, Pal, false);
    isDcPalDevice := true
  end;
  {send the bits to the printer}
  StretchDiBits(Printer.Canvas.Handle, R.Left, R.Top, R.Right - R.Left,
    R.Bottom - R.Top, 0, 0, ABitmap.Width, ABitmap.Height, pBits,
    PBitmapInfo(pDibHeader)^, DIB_RGB_COLORS, SRCCOPY);
  {Just incase you printer drver is a palette device}
  if isDcPalDevice = true then
  begin
    SelectPalette(Printer.Canvas.Handle, oldPal, false);
    DeleteObject(Pal);
  end;
  {Clean up allocated memory}
  GlobalUnlock(hBits);
  GlobalFree(hBits);
  GlobalUnlock(hDibHeader);
  GlobalFree(hDibHeader);
end;

2006. június 4., vasárnap

List of all virtual key values


Problem/Question/Abstract:

List of all virtual key values

Answer:

{Virtual Keys, Standard Set}

  VK_LBUTTON = 1;
  VK_RBUTTON = 2;
  VK_CANCEL = 3;
  VK_MBUTTON = 4;  {Not contiguous with L & RBUTTON }
  VK_BACK = 8;
  VK_TAB = 9;
  VK_CLEAR = 12;
  VK_RETURN = 13;
  VK_SHIFT = $10;
  VK_CONTROL = 17;
  VK_MENU = 18;
  VK_PAUSE = 19;
  VK_CAPITAL = 20;
  VK_ESCAPE = 27;
  VK_SPACE = $20;
  VK_PRIOR = 33;
  VK_NEXT = 34;
  VK_END = 35;
  VK_HOME = 36;
  VK_LEFT = 37;
  VK_UP = 38;
  VK_RIGHT = 39;
  VK_DOWN = 40;
  VK_SELECT = 41;
  VK_PRINT = 42;
  VK_EXECUTE = 43;
  VK_SNAPSHOT = 44;
  VK_INSERT = 45;
  VK_DELETE = 46;
  VK_HELP = 47;
  {VK_0 thru VK_9 are the same as ASCII '0' thru '9' ($30 - $39)}
  {VK_A thru VK_Z are the same as ASCII 'A' thru 'Z' ($41 - $5A)}
  VK_LWIN = 91;
  VK_RWIN = 92;
  VK_APPS = 93;
  VK_NUMPAD0 = 96;
  VK_NUMPAD1 = 97;
  VK_NUMPAD2 = 98;
  VK_NUMPAD3 = 99;
  VK_NUMPAD4 = 100;
  VK_NUMPAD5 = 101;
  VK_NUMPAD6 = 102;
  VK_NUMPAD7 = 103;
  VK_NUMPAD8 = 104;
  VK_NUMPAD9 = 105;
  VK_MULTIPLY = 106;
  VK_ADD = 107;
  VK_SEPARATOR = 108;
  VK_SUBTRACT = 109;
  VK_DECIMAL = 110;
  VK_DIVIDE = 111;
  VK_F1 = 112;
  VK_F2 = 113;
  VK_F3 = 114;
  VK_F4 = 115;
  VK_F5 = 116;
  VK_F6 = 117;
  VK_F7 = 118;
  VK_F8 = 119;
  VK_F9 = 120;
  VK_F10 = 121;
  VK_F11 = 122;
  VK_F12 = 123;
  VK_F13 = 124;
  VK_F14 = 125;
  VK_F15 = 126;
  VK_F16 = 127;
  VK_F17 = 128;
  VK_F18 = 129;
  VK_F19 = 130;
  VK_F20 = 131;
  VK_F21 = 132;
  VK_F22 = 133;
  VK_F23 = 134;
  VK_F24 = 135;
  VK_NUMLOCK = 144;
  VK_SCROLL = 145;
  {VK_L & VK_R - left and right Alt, Ctrl and Shift virtual keys. Used only as parameters to
  GetAsyncKeyState() and GetKeyState(). No other API or message will distinguish left and right
  keys in this way.}
  VK_LSHIFT = 160;
  VK_RSHIFT = 161;
  VK_LCONTROL = 162;
  VK_RCONTROL = 163;
  VK_LMENU = 164;
  VK_RMENU = 165;
  VK_PROCESSKEY = 229;
  VK_ATTN = 246;
  VK_CRSEL = 247;
  VK_EXSEL = 248;
  VK_EREOF = 249;
  VK_PLAY = 250;
  VK_ZOOM = 251;
  VK_NONAME = 252;
  VK_PA1 = 253;
  VK_OEM_CLEAR = 254;

2006. június 3., szombat

FTP-Handling


Problem/Question/Abstract:

If I wish to send files to an FTP server how difficult is it to do?

Answer:

After HTTP, FTP is possibly the most used protocols. It allows files to be transferred to and from FTP servers. The only disadvantage with using FTP is that the username and password are sent unencrypted in plain text. Even Internet Explorer can handle FTP.

The FTP protocol is reasonably easy to implement if you know how to use Winsock, but it has already been done by many people so this is probably one wheel you do NOT need to reinvent. The following list includes source code and they are free. ICS (Francois Piette�s superb library at (http://users.swing.be/francois.piette/indexuk.htm), Winshoes or Indy as it is now known (and soon to be included in Delphi 6 I believe) at http://www.nevrona.com/Indy and MonsterFtp which is on www.torry.ru on the Internet part of the VCL section under FTP.

Of these I tried Monster FTP but found a bug using it within a firewall, but Winshoes version 7 (8 is now being released as Indy) worked fine and the code shown below shows just how simple it is to upload files using the FTP. I haven�t tried ICS or any other kits so apologies if I overlooked any.

For any FTP account you need the following:
Username
Password
Server URL (ftp:// �) or IP Address
And optionally, a folder to change to, after the connection is established.

In the code below, ftpObject is a Winshoes TSimpleFTPObject. FtpUpload is a record or class containing Server (Ip Address or Name), Username, Password, Timeout (in milliseconds) and optionally Directory (to change into).  The file transferred is passed in as FilenametoSend.Just add your own error Procedure to deal with errors.
Procedure Error(Const ErrorString:String);  

Depending on the type of file transferred you may wish to transfer files as binary or as Ascii. The only difference is that Ascii transferred files have Carriage Return/Line Feeds added or stripped (according to direction of flow) if between Unix systems and Windows.

Note this needs to be slotted into a procedure or Method.

try
  FtpObject.Hostname := FtpUpload.Server;
  FtpObject.Username := FtpUpload.Username;
  FtpObject.Password := FtpUpload.Password;
  Ftpobject.ConnectTimeout := Ftpupload.Timeout * 1000;

  if not FTPObject.Connect then
  begin
    Error('failed to connect to server');
    exit;
  end;

except
  on E: Exception do
  begin
    Error(Format('Failed to connect to FTP server %s', [FTPUpload.Server]));
    EXIT;
  end;
end;

{ Change Working Directory }
try
  if FtpUpload.Directory <> '' then
    FtpObject.ChangeRemoteDir(FtpUpload.Directory);
except
  on E: Exception do
  begin
    Error(Format('Failed to switch to FTP folder %s', [FtpUpload.Directory]));
    EXIT;
  end;
end;

//FTPObject.Mode(MODE_BYTE);
FTPObject.Transfertype := ttBinary;
LocalFile := CommonExportFolder + FTPUpload.FileNameToSend;
{ Includes date/time in remote file name to keep name unique on a resend }
RemoteFile := Prefix + FormatDateTime('yyyymmddhhnnss', now) + NameList[i];
try
  FTpObject.PutQualifiedFile(LocalFile, RemoteFile);
except
  Error('Failed Copying File ' + Localfile + ' To ' + Remotefile);
end;

2006. június 2., péntek

Create a unique GUID (for COM)


Problem/Question/Abstract:

Create a unique GUID (for COM)

Answer:

Do you need to create a GUID in your application?

Very simple in the IDE.. just press Ctrl+Shift+G

2006. június 1., csütörtök

How to obtain a list of all published property names and types defined in a component


Problem/Question/Abstract:

How to obtain a list of all published property names and types defined in a component

Answer:

Solve 1:

function GetComponentProperties(Instance: TPersistent; AList: TStrings): Integer;
var
  I, Count: Integer;
  PropInfo: PPropInfo;
  PropList: PPropList;
begin
  Result := 0;
  Count := GetTypeData(Instance.ClassInfo)^.PropCount;
  if Count > 0 then
  begin
    GetMem(PropList, Count * SizeOf(Pointer));
    try
      GetPropInfos(Instance.ClassInfo, PropList);
      for I := 0 to Count - 1 do
      begin
        PropInfo := PropList^[I];
        if PropInfo = nil then
          Break;
        if IsStoredProp(Instance, PropInfo) then
        begin
          {
          case PropInfo^.PropType^.Kind of
            tkInteger:
            tkMethod:
            tkClass:
            ...
          end;
          }
        end;
        Result := AList.Add(PropInfo^.Name);
      end;
    finally
      FreeMem(PropList, Count * SizeOf(Pointer));
    end;
  end;
end;


Solve 2:

uses
  TypInfo

procedure ListProperties(AInstance: TPersistent; AList: TStrings);
var
  i: integer;
  pInfo: PTypeInfo;
  pType: PTypeData;
  propList: PPropList;
  propCnt: integer;
  tmpStr: string;
begin
  pInfo := AInstance.ClassInfo;
  if (pInfo = nil) or (pInfo^.Kind <> tkClass) then
    raise Exception.Create('Invalid type information');
  pType := GetTypeData(pInfo); {Pointer to TTypeData}
  AList.Add('Class name: ' + pInfo^.Name);
  {If any properties, add them to the list}
  propCnt := pType^.PropCount;
  if propCnt > 0 then
  begin
    AList.Add(EmptyStr);
    tmpStr := IntToStr(propCnt) + ' Propert';
    if propCnt > 1 then
      tmpStr := tmpStr + 'ies'
    else
      tmpStr := tmpStr + 'y';
    AList.Add(tmpStr);
    FillChar(tmpStr[1], Length(tmpStr), '-');
    AList.Add(tmpStr);
    {Get memory for the property list}
    GetMem(propList, sizeOf(PPropInfo) * propCnt);
    try
      {Fill in the property list}
      GetPropInfos(pInfo, propList);
      {Fill in info for each property}
      for i := 0 to propCnt - 1 do
        AList.Add(propList[i].Name + ': ' + propList[i].PropType^.Name);
    finally
      FreeMem(propList, sizeOf(PPropInfo) * propCnt);
    end;
  end;
end;

function GetPropertyList(AControl: TPersistent; AProperty: string): PPropInfo;
var
  i: integer;
  props: PPropList;
  typeData: PTypeData;
begin
  Result := nil;
  if (AControl = nil) or (AControl.ClassInfo = nil) then
    Exit;
  typeData := GetTypeData(AControl.ClassInfo);
  if (typeData = nil) or (typeData^.PropCount = 0) then
    Exit;
  GetMem(props, typeData^.PropCount * SizeOf(Pointer));
  try
    GetPropInfos(AControl.ClassInfo, props);
    for i := 0 to typeData^.PropCount - 1 do
    begin
      with Props^[i]^ do
        if (Name = AProperty) then
          result := Props^[i];
    end;
  finally
    FreeMem(props);
  end;
end;

And calling this code by:

ListProperties(TProject(treeview1.items[0].data), memo3.lines);

My tProject is defined as

type
  TProject = class(tComponent)
  private
    FNaam: string;
    procedure SetNaam(const Value: string);
  public
    constructor Create(AOwner: tComponent);
    destructor Destroy;
  published
    property Naam: string read FNaam write SetNaam;
  end;

Also note the output, there seem to be 2 standard properties (Name and Tag) !

Memo3
Class name: TProject

3 Properties
-------------------
Name: TComponentName
Tag: Integer
Naam: String