The Unofficial Newsletter of Delphi Users - Banner

Downloadable UNDU Issues Delphi User Groups

News Page

April 22nd, 1998

Three great articles today:

The first is a continuation of Stanislav Holenda's series on sound. Check out Digital Sound in Delphi - Part II.

Next, if you have an adventurous streak in you and want to learn more about how Delphi 3's Packages are organized, take a look at Inside the Delphi 3 Package by Wei Bao.

And lastly, Evan Simpson brings us yet another improved method for managing the startup of your application including multiple instance management, and communication with a previous instance of your application. His article is entitled Controlling Application Startup in Delphi 3.

In answer to the question mentioned below (the April 15th Question), I received a number of good responses:

Michael Van Canneyt writes the following:

"It doesn't display graphics because the MicroSoft richedit component, (around which TRichedit is a Delphi wrapper) it isn't able to do so. It also destroys fields and some other things. Maybe Microsoft has released a renewed component (it resides in riched.dll or riched32.dll in the windows\system32 directory), but I don't think so. In general, the capabilities are limited to the capabilities that the Delphi wrapper offers you: textformatting and paragraph formatting. The Visual Basic development kit has a list in its manual with 'supported RTF codes'; this list gives all the codes that the component doesn't choke on; this doesn't necessarily mean that the component SUPPORTS them, i.e. displays them correctly. For instance I know for a fact that field codes are accepted (as it says in the list), but are destroyed when the Richedit component saves its text again."

But an actual solution to the problem was offered by Kevin Gallagher. On the DSP site, he found a freeware OLE RichEdit component that looks like it is just the solution we need. You can download it by clicking here. Thanks Kevin!

April 17th, 1998

A bunch of new stuff today...

Chuck Gadd presents an extension to a technique originally presented by Paul Harding. It's called Multiple Lines of Text in a String Grid - Improved!

Daniel Wojcik, like all of us, get tired of manually distributing upgrades to everyone in the company. His solution is presented in Auto Upgrading!

And finally, Stein Svendsen (from Norway, I think) illustrates an interesting extension of a technique originally presented by Alan Lloyd. Check out his work in Piggybacking Event Handlers with Components.

April 15th, 1998

A few things of interest today...

The first is a question that I am going to open up to anyone who might have an answer. This was posed by a reader from the Peoples Republic of China... (yes, Delphi lives there too!). His question relates to RTF files, and I will paraphrase it:

"I created a document using the WordPad application, included a graphic image (bitmap), and saved the file to the RTF format. When I try to load that RTF file into a Delphi TRichEdit component, I get the text but no graphic. How come?".

I didn't have an immediate answer, but it is an interesting question. I do know that RTF format does support graphics... does Delphi not?

Matt Hamilton brings us a great simple tip to improve the user interface when dealing with empty tables. Check out his tip on Using TDataSource to Improve Your User-Interface.

Clinton Johnson is back also with an extension of Graham Marsh's article on Hooks. Take a look at it in Hooking Into the VCL.

April 13th, 1998

Sorry for the little break there folks. I was catching up on a number of projects and unfortunately let my attention to UNDU slip a little.

The winners of the UNDU prizes for March are as follows:

For his articles on Windows Hooks, Determining CPU Types, and Compressing 256 color BMPs, Grahame Marsh wins a copy of PSetting by Pythoness Software. For his excellent tip on Moving and Resizing components at Runtime, Matt Hamilton wins a copy of Ray Lischner's book Hidden Paths of Delphi 3. And finally, for his article on Using the Clipboard with WAV files, John Mertus wins a copy of the Delphi Informant Works CD, which includes all issues of Delphi Informant through 1997. Congratulations to you all and thanks again for all the great articles!

I am putting together some good stuff for tomorrow... stay tuned!

In order to keep the size of the News Page to a minimum, I will be archiving previous months and listing them at the bottom.
News Page for March, 1998
News Page for February, 1998


The Unofficial Newsletter of Delphi Users - Tips & Tricks

Articles, Tips & Tricks

Below is a comprehensive list of links to all the Articles, Reviews, and Tips & Tricks published in The Unofficial Newsletter of Delphi Users. Because of the number of articles and tips & tricks in past issues, this will be a work in progress, so please keep checking back for updated links.

04/22/98 - Controlling Application Startup in Delphi 3
04/22/98 - Inside the Delphi 3 Package
04/22/98 - Digital Sound in Delphi Part II
04/17/98 - Multiple Lines of Text in a String Grid - Improved!
04/17/98 - Auto Upgrading!
04/17/98 - Piggybacking Event Handlers with Components
04/15/98 - Using TDataSource to Improve Your User-Interface
04/15/98 - Hooking Into the VCL
03/26/98 - Using TMediaPlayer to Manage Multiple CDRoms
03/26/98 - Beziers and Paths
03/26/98 - Windows Hooks
03/25/98 - WinHelp Office Review
03/18/98 - Creating Shortcuts in Delphi
03/18/98 - Can't Have Too Much WinInfo!
03/18/98 - Drawing on a TEdit Control
03/18/98 - Borland's Tech Sheets
03/17/98 - Extending the Moving/Resizing Technique
03/17/98 - Using the Clipboard with WAV Files
03/11/98 - Determining CPU Type
03/11/98 - Compressing 256 Color BMPs
03/06/98 - Moving and Sizing Controls at Runtime
02/28/98 - Digital Sound & Music in Delphi
02/26/98 - Accessing the Win95 FindFiles Dialog
02/23/98 - Improving Cut, Copy & Paste
02/23/98 - Giving Your EditBoxes Some Attention
02/21/98 - Delphi 3's Code Parameters
02/21/98 - CD Change Notifications
Issue #24 - Speeding Up Master-Detail Tables
Issue #24 - Beginners Block: An Introduction to Pointers
Issue #24 - NIL Pointers: How Delphi Frees Objects
Issue #24 - RGB & HSL Colour Models
Issue #24 - More on Moving From VB to Delphi
Issue #24 - A Second Look at MicroEdge's Visual SlickEdit
Issue #24 - Delphi and the Year 2000
Issue #23 - Playing and Recording Sounds in Delphi
Issue #23 - An Immediate IF Function
Issue #23 - Two Lines of Text in a String Grid
Issue #23 - A Simple Drawing Program
Issue #23 - Finding the Cursor Coordinates in a Memo Control
Issue #23 - A Browse Directory Component
Issue #23 - Changing Event Handlers at Runtime
Issue #23 - Book Review: Hidden Paths of Delphi 3
Issue #23 - Book Review: Building Delphi 2 Database Applications
Issue #23 - Review: Raize Components
Issue #23 - Making the Move: VB to Delphi
Issue #23 - Add a Web Search Engine to Your Application
Issue #22 - Bitmaps in String Grids
Issue #22 - A Cure for Previous Instance Problems
Issue #22 - A Word Search Tip
Issue #22 - Unbridled Acceleration
Issue #22 - Showing the Caret Position
Issue #22 - Using the Win95 Registry
Issue #22 - Wallpapering MDI Forms
Issue #22 - The InputQueryEx Function
Issue #22 - Exceeding the Data Segment
Issue #22 - Delphi 2.0 and 3.0 on the Same Computer
Issue #22 - Review: Programmers Guild Animated Tray Icon
Issue #22 - Review: MicroEdge's Visual SlickEdit
Issue #22 - Review: IniOut Component
Issue #22 - Review: Addict 2.2 Spell Checker for Delphi
Issue #22 - Getting Control of the Control Panel
Issue #21 - Low-Level Windows Stuff
Issue #21 - Listing Procedures
Issue #21- Hiding Apps from the TaskBar
Issue #21 - Excel OLE Tips
Issue #21 - Playing Sounds Asynchronously
Issue #21 - Bitmaps on StringGrids
Issue #21 - How to Find Up-To-Date Delphi Books
Issue #21 - Using the Margin Marker in Delphi 1 & 2
Issue #21 - Lock Violations Revisited
Issue #21 - Object Creation Tip
Issue #21 - How To Compress a Bitmap
Issue #21 - The TEndSession Component
Issue #21 - Video Capture and Delphi
Issue #21 - Review: Delphi Component Design
Issue #20 - Is Someone Else Running? Revisited!
Issue #20 - InputQueryEx Function
Issue #20 - Multi-Colored Text in a String Grid
Issue #20 - Converting Pascal Source to HTML
Issue #20 - Processing Large Database Tables
Issue #20 - Making Your App Look Normal at Different Resolutions
Issue #20 - How to Make Your EXE's Lighter!
Issue #20 - Win95 Shell Operations - Update
Issue #20 - Form Aspect Ratio
Issue #20 - Previous Instances - Revisited
Issue #20 - Printing Raw Data
Issue #20 - Implementing a "Tip Of The Day" Component
Issue #20 - TFieldPanel
Issue #20 - TPageControl - Update
Issue #20 - Learning How To Drive: Disk Information in Delphi
Issue #20 - Delphi Books and Periodicals
Issue #19 - Using Win95's SHFileOperation to Move/Copy/Delete Files
Issue #19 - How to Create a Polygon Splash Screen
Issue #19 - Is Someone Else Running?
Issue #19 - Lock Violation Errors in Paradox
Issue #19 - Printing Directly to the Printer
Issue #19 - Refreshing MDI Menus
Issue #19 - Extending the Background Bitmap Technique
Issue #19 - Paradox File Size Limits
Issue #19 - Safer Use of Enumerated Types
Issue #19 - Simplifying Code Management with Include
Issue #19 - A Look at the TreeView Component
Issue #19 - Aligning Text in a Grid
Issue #19 - TPageControl
Issue #19 - Big Bitmaps
Issue #19 - Transparency Masks
Issue #19 - Review: SpeedDaemon Code Profiler
Issue #19 - A Look at MagiKit
Issue #19 - Humor: Are you Computer Illiterate?
Issue #18 - Storing Fonts in INI files
Issue #18 - Sorting Columns in a DBGrid
Issue #18 - Adding UNDU to an Edit Menu
Issue #18 - Put Anything in Your Delphi EXE!
Issue #18 - A Simple Clipboard Viewer
Issue #18 - Object Express by OopSoft
Issue #18 - Announcement: IniOut Component
Issue #18 - Book Review: Delphi Component Design by Dany Thorpe

More links to come! To see the material in Issues 1 through 17, click here.


Hooking Into the VCL

The Unofficial Newsletter of Delphi Users - by Robert Vivrette

Hooking Into the VCL

by Clinton Johnson - xepol@poboxes.com

This represents an extension of a concept shown by Grahame Marsh earlier in UNDU (see There's Method to My Hook Madness!). I've extended his code, and created VCL non-visual wrappers for each possible system hook. I'm not going to go into much detail about what I've done this time, I'm going to let people go through the source for that.

Please remember that hooks can impact system performance greatly, particularily when you create a system hook instead of an application hook. On that note, I would like to draw your attention to the ACTIVE property. Just because the component exists, does not mean that it has a hook into the system. The hook is dynamically created as needed based on the active property, this should make the components a little easier to use and manage.

Enjoy...

unit hooks;

interface

Uses Windows,SysUtils,Classes,Controls;

{$IFNDEF VER100} THIS CODE WAS WRITTEN FOR DELPHI 3, YOU SHOULD NOT USE IT WITH ANY OTHER VERSION{$ENDIF}

//*****************************************************************************
//
// Hooks Unit - Written For Delphi 3 By Clinton R. Johnson, using code
written by
//
by Grahame Marsh - gsmarsh@aol.com, in an article for UNDU.
//
// Written April 5, 1998 - The code written by Clinton R. Johnson is
donated to
// the public domain.  Code written by Grahame Marsh may have
additional
// restrictions.
//
// Not all portions of this code has been tested in all
circumstances.  Use at
// your own discression, it comes with no warrenty express or implied.
//
// Grahame Marsh is responsible for the MakeHookInstance and
FreeHookInstance
// code (which is based on the MakeObjectInstance code in the
Forms.pas unit
// from Borland if you want further examples).  His goal was to
simplify the
// creation of Hooks in Delphi.  He acheived his goal, but I
figured it could
// be even simpler.  No housekeeping tasks involved, no need to
issue the low
// level calls to stay in the chain, etc.  Delphi is after all a
visual enviroment,
// so I dislike writing code where I can just drop a component.
The only change
// I've made to his code was to rename the THOOKCALL structure to
THOOKMSG, I
// found his name too confusing & kept mixing it up with the call
instance itself.
//
// If you investigate this code, the most power is exposed once you
get to the
// code level instead of visual implementation, but that's the way it
should be
// in my opinion - multiple levels of complexity, this keeps the true
novice from
// playing with things before they at least learn how to winow out
& use the
// most powerful units.
//
// I've created a basic non-visual component to host the hook, and
then created
// descendant objects for each hook.  I've not created any
special messages
// structures for each handle, and I've not done any of the low level
handing
// recommended.  I've left that up to you, to implement as you
see best.
//
//*****************************************************************************

Type
  THookMsg = Packed record
               Code   : integer;
               WParam : WPARAM;
               LParam : LPARAM;
               Result : LResult
             end;

Type
THook = Class;
THookMethod = procedure (var HookMsg: THookMsg) of object;
THookNotify = procedure (Hook : THook; var Hookmsg: THookMsg)
of object;

  THook = Class(TComponent)
  Private
    fHook               : hHook;
    fHookProc           : Pointer;
    fOnPreExecute       : THookNotify;
    fOnPostExecute      : THookNotify;
    fActive             : Boolean;
    fLoadedActive       : Boolean;

    fThreadID           : Integer;

    Procedure SetActive(NewState : Boolean);
Procedure SetThreadID(NewID : INteger);
Procedure HookProc(Var HookMsg : THookMsg);
Protected
Procedure PreExecute(Var HookMsg : THookMsg; Var
Handled : Boolean); Virtual;
Procedure PostExecute(Var HookMsg : THookMsg);
Virtual;
Function AllocateHook : hHook; Virtual; Abstract;
Procedure Loaded; Override;
Public
Constructor Create(Owner : TComponent); Override;
Destructor Destroy; Override;
Property ThreadID : Integer Read fThreadID Write
SetThreadID Stored False;
Property Active : Boolean Read fActive Write
SetActive;
Property OnPreExecute : THookNotify  Read
fOnPreExecute Write fOnPreExecute;
Property OnPostExecute : THookNotify  Read
fOnPostExecute Write fOnPostExecute;
Published
End;

Type
  TCallWndProcHook = Class(THook)
  Private
  Protected
  Public
    Function AllocateHook : hHook; Override;
  Published
    Property Active;
    Property OnPreExecute;
    Property OnPostExecute;
  End;

Type
  TCallWndProcRetHook = Class(THook)
  Private
  Protected
  Public
    Function AllocateHook : hHook; Override;
  Published
    Property Active;
    Property OnPreExecute;
    Property OnPostExecute;
  End;

Type
  TCBTHook = Class(THook)
  Private
  Protected
  Public
    Function AllocateHook : hHook; Override;
  Published
    Property Active;
    Property OnPreExecute;
    Property OnPostExecute;
  End;

Type
  TDebugHook = Class(THook)
  Private
  Protected
  Public
    Function AllocateHook : hHook; Override;
  Published
    Property Active;
    Property OnPreExecute;
    Property OnPostExecute;
  End;

Type
  TGetMessageHook = Class(THook)
  Private
  Protected
  Public
    Function AllocateHook : hHook; Override;
  Published
    Property Active;
    Property OnPreExecute;
    Property OnPostExecute;
  End;

Type
  TJournalPlaybackHook = Class(THook)
  Private
  Protected
  Public
    Function AllocateHook : hHook; Override;
  Published
    Property Active;
    Property OnPreExecute;
    Property OnPostExecute;
  End;

Type
  TJournalRecordHook = Class(THook)
  Private
  Protected
  Public
    Function AllocateHook : hHook; Override;
  Published
    Property Active;
    Property OnPreExecute;
    Property OnPostExecute;
  End;

Type
  TKeyboardHook = Class(THook)
  Private
  Protected
  Public
    Function AllocateHook : hHook; Override;
  Published
    Property Active;
    Property OnPreExecute;
    Property OnPostExecute;
  End;

Type
  TMouseHook = Class(THook)
  Private
  Protected
  Public
    Function AllocateHook : hHook; Override;
  Published
    Property Active;
    Property OnPreExecute;
    Property OnPostExecute;
  End;

Type
  TMsgHook = Class(THook)
  Private
  Protected
  Public
    Function AllocateHook : hHook; Override;
  Published
    Property Active;
    Property OnPreExecute;
    Property OnPostExecute;
  End;

Type
  TShellHook = Class(THook)
  Private
  Protected
  Public
    Function AllocateHook : hHook; Override;
  Published
    Property Active;
    Property OnPreExecute;
    Property OnPostExecute;
  End;

Type
  TSysMsgHook = Class(THook)
  Private
  Protected
  Public
    Function AllocateHook : hHook; Override;
  Published
    Property Active;
    Property OnPreExecute;
    Property OnPostExecute;
  End;

function  MakeHookInstance (Method: THookMethod): pointer;
procedure FreeHookInstance (ObjectInstance: pointer);

Procedure Register;

implementation

const
  InstanceCount = 313;  // set so that sizeof (TInstanceBlock) < PageSize

type
  PObjectInstance = ^TObjectInstance;
  TObjectInstance = packed record
                      Code: Byte;
                      Offset: Integer;
                    case Integer of
                      0: (Next: PObjectInstance);
                      1: (Method: THookMethod);
                    end;
Type
  PInstanceBlock = ^TInstanceBlock;
  TInstanceBlock = packed record
                     Next: PInstanceBlock;
                     Code: array[1..2] of Byte;
                     WndProcPtr: Pointer;
                     Instances: array[0..InstanceCount] of TObjectInstance;
                   end;
var
  InstBlockList : PInstanceBlock  = nil;
  InstFreeList  : PObjectInstance = nil;

function StdHookProc (Code, WParam: WPARAM; LParam: LPARAM): LResult; stdcall; assembler;

asm
  XOR     EAX,EAX
  PUSH    EAX
  PUSH    LParam
  PUSH    WParam
  PUSH    Code
  MOV     EDX,ESP
  MOV     EAX,[ECX].Longint[4]
  CALL    [ECX].Pointer
  ADD     ESP,12
  POP     EAX
end;

{ Allocate a hook method instance }
function CalcJmpOffset(Src, Dest: Pointer): Longint;
begin
  Result := Longint(Dest) - (Longint(Src) + 5);
end;

function MakeHookInstance(Method: THookMethod): Pointer;

const
  BlockCode: array [1..2] of Byte = ($59, $E9);
  PageSize = 4096;

var
  Block: PInstanceBlock;
  Instance: PObjectInstance;

begin
if InstFreeList = nil then
begin
Block := VirtualAlloc (nil, PageSize,
MEM_COMMIT,PAGE_EXECUTE_READWRITE);
Block^.Next := InstBlockList;
Move(BlockCode, Block^.Code, SizeOf(BlockCode));
Block^.WndProcPtr :=
Pointer(CalcJmpOffset(@Block^.Code[2],@StdHookProc));
Instance := @Block^.Instances;
repeat
Instance^.Code := $E8;
Instance^.Offset :=
CalcJmpOffset(Instance, @Block^.Code);
Instance^.Next := InstFreeList;
InstFreeList := Instance;
Inc(Longint(Instance),
SizeOf(TObjectInstance));
until Longint(Instance) - Longint(Block) >=
SizeOf(TInstanceBlock);
InstBlockList := Block
end;
Result := InstFreeList;
Instance := InstFreeList;
InstFreeList := Instance^.Next;
Instance^.Method := Method
end;

{ Free a hook method instance }
procedure FreeHookInstance (ObjectInstance: Pointer);

Begin
  if ObjectInstance <> nil then
  Begin
    PObjectInstance(ObjectInstance)^.Next := InstFreeList;
    InstFreeList := ObjectInstance
  End
End;

Constructor THook.Create(Owner : TComponent);

Begin
  Inherited Create(Owner);
  fHookProc := MakeHookInstance(HookProc);
  fActive := false;
  fLoadedActive := False;
  fHook := 0;
  ThreadID := GetCurrentThreadID;
End;

Destructor THook.Destroy;

Begin
  Active := False;
  FreeHookInstance(fHookProc);
  Inherited;
End;

Procedure THook.SetActive(NewState : Boolean);

Begin
  If (csLoading in componentState) Then
  Begin
    fLoadedActive := NewState;
  End Else If (fActive<>NewState) Then
  Begin
    fActive := NewState;
    Case (Active And (Not (csDesigning In ComponentState))) Of
      True : Begin
               fHook := AllocateHook;
               If (fHook=0) Then
               Begin
                 fActive := False;
                 Raise Exception.Create(Classname+' CREATION FAILED!');
               End;
             End;
      False : Begin
                If (FHook<>0) Then UnhookWindowsHookEx(fHook);
                fHook := 0;
              End;
    End;
  End;
End;

Procedure THook.SetThreadID(NewID : INteger);

Var
  IsActive              : Boolean;

Begin
  IsActive := fActive;
  Active := False;
  fThreadID := NewID;
  Active := IsActive;
End;

Procedure THook.Loaded;

Begin
  Inherited;
  Active := fLoadedActive;
End;

Procedure THook.HookProc(Var HookMsg : THookMsg);

Var
  Handled               : Boolean;

Begin
Handled := False;
PreExecute(HookMsg,Handled);
If Not Handled Then
Begin
with HookMsg do Result := CallNextHookEx (fHook,
Code, wParam, lParam);
PostExecute(HookMsg);
End;
End;

Procedure THook.PreExecute(Var HookMsg : THookMsg; Var Handled : Boolean);

Begin
  If Assigned(fOnPreExecute) then
  Begin
    fOnPreExecute(Self,HookMsg);
  End;
End;

Procedure THook.PostExecute(Var HookMsg : THookMsg);

Begin
  If Assigned(fOnPostExecute) then
  Begin
    fOnPostExecute(Self,HookMsg);
  End;
End;

Function TCallWndProcHook.AllocateHook : hHook;

Begin
Result := SetWindowsHookEx (WH_CALLWNDPROC, fHookProc,
HInstance, ThreadID);
End;

Function TCallWndProcRetHook.AllocateHook : hHook;

Begin
  Result := SetWindowsHookEx(WH_CALLWNDPROCRET,fHookProc,hInstance,ThreadID);
End;

Function TCBTHook.AllocateHook : hHook;

Begin
  Result := SetWindowsHookEx(WH_CBT,fHookProc,hInstance,ThreadID);
End;

Function TDebugHook.AllocateHook : hHook;

Begin
  Result := SetWindowsHookEx(WH_DEBUG,fHookProc,hInstance,ThreadID);
End;

Function TGetMessageHook.AllocateHook : hHook;

Begin
  Result := SetWindowsHookEx(WH_GETMESSAGE,fHookProc,hInstance,ThreadID);
End;

Function TJournalPlaybackHook.AllocateHook : hHook;

Begin
  Result := SetWindowsHookEx(WH_JOURNALPLAYBACK,fHookProc,hInstance,ThreadID);
End;

Function TJournalRecordHook.AllocateHook : hHook;

Begin
  Result := SetWindowsHookEx(WH_JOURNALRECORD,fHookProc,hInstance,ThreadID);
End;

Function TKeyboardHook.AllocateHook : hHook;

Begin
  Result := SetWindowsHookEx(WH_KEYBOARD,fHookProc,hInstance,ThreadID);
End;

Function TMouseHook.AllocateHook : hHook;

Begin
  Result := SetWindowsHookEx(WH_MOUSE,fHookProc,hInstance,ThreadID);
End;

Function TMsgHook.AllocateHook : hHook;

Begin
  Result := SetWindowsHookEx(WH_MSGFILTER,fHookProc,hInstance,ThreadID);
End;

Function TShellHook.AllocateHook : hHook;

Begin
Result :=
SetWindowsHookEx(WH_SHELL,fHookProc,hInstance,ThreadID);
End;
Function TSysMsgHook.AllocateHook : hHook;

Begin
  Result := SetWindowsHookEx(WH_SYSMSGFILTER,fHookProc,hInstance,ThreadID);
End;

Procedure Register;

Begin
  RegisterComponents('Hooks',[TCallWndProcHook,TCallWndProcRetHook,TCBTHook,TDebugHook,TGetMessageHook,
                             TJournalPlaybackHook,TJournalRecordHook,TKeyboardHook,TMouseHook,TMsgHook,
                             TShellHook,TSysMsgHook]);
End;

end.


Improve Your User-Interface

 The Unofficial Newsletter of Delphi Users - by Robert Vivrette

Using TDataSource to Improve Your User-Interface

by Matt Hamilton - mhamilton@bunge.com.au

When I first started creating database applications, I was frustrated with the problem of the user being able to press the "Edit" and "Delete" buttons even when there was no data in the table. Since there was nothing to Delete or Edit, the statements "Table1.Edit" and "Table1.Delete" would either raise an exception or do something non-intuitive.

Originally I got around that problem by checking for data first. That is, in the OnClick event for the buttons, I placed this:

    if Table1.RecordCount > 0 then
    begin
       ...
    end
    else
       MessageBeep(0);
But this was silly - why be able to press the button at all when it would have no effect?

So, back in my Delphi 1 days, I would catch the AfterPost and AfterDelete events on the TTable, and disable or enable the relevant buttons when the the table changed.

But then came datamodules. Now all my tables and datasources sat there! What to do? The datamodule shouldn't know anything about the form with the buttons on it, so it made no sense to catch those same events anymore.

The answer is perhaps obvious, but it took me a while to find it. Put your TDataSource on the form instead of on the datamodule, and catch the OnDataChange event. This event has a parameter called "Field", which is nil when all the fields have changed (eg when scrolling or deleting). It is therefore possible to do something like this:

    procedure TForm1.DataSource1DataChange(Sender: TObject; Field: TField);
    begin
      if Field = nil then
        with TDataSource(Sender).DataSet do
          begin
            EditBtn.Enabled := not IsEmpty;
            DeleteBtn.Enabled := not IsEmpty;
          end;
    end;
Presto! Now the edit and delete buttons become "grayed out" when all the records are deleted, and re-enable when a new record is created.

It all comes down to "user-friendliness"!

Hope this helps someone...


Piggybacking Event Handlers with Components

The Unofficial Newsletter of Delphi Users - by Robert Vivrette

Piggybacking Event Handlers with Components

by Stein Svendsen - stein.svendsen@idgruppen.no

I have enjoyed UNDU some time now, getting many hints and ideas from it. Reading a past issue, Alan Lloyd pointed out the way to change event handlers at runtime, I realized that I could clean out some of my custom forms in the repository.

In my repository growing full of different forms, I have quite a few that have the OnCreate event filled up with code. These forms don't hide my customization when inherited, so they all have plenty of code already in the OnCreate handler when I use one. I prefer to have my forms as clean and codeless as possible, with all event handlers free and fresh for use.

I found that i could move my customizations into components that chain into a form's event handlers. The example below shows an example of this.

The component below may be dropped onto a form, and will make the form materialize itself with a feathery effect when created. The Speed property determines how quickly the effect runs. But the main thing is that it won't interfere with any OnCreate code that the form may have from before.

As far as I can discover, a form, when created, will create all its objects first, paint itself, and then fire its OnCreate code (if any). This component's create() code maps the form's Oncreate event to it's own FormOncreateIntercept procedure. Hence, when the form is ready to execute it's OnCreate code it actually executes the code that the component's FormOncreateIntercept code. The FormOncreateIntercept will execute the form's original OnCreate event if there was one, allowing any other initializations to be completed, before it runs it's own.

(I imagine that this technique allows multiple components using it, to operate on the same form as long as they are different from each other, and don't conflict in their nature.)

Note: You can't use this component with a modal form.

Editors Comment: This is a pretty neat application of this technique. The function of the Speed property will differ though depending on how fast the machine is that is running the code. I am sure a Timer would probably make this work regardless of processor speed.

    Unit Slideform;

    interface

    uses
      forms,windows, sysutils, classes, dsgnintf,dialogs;

    type
      FormSideSlide = class(TComponent)
      private
        FSpeed: integer;
      protected
      public
        //Procedure pointers
        GrabbedFormOnCreate, InterceptedFormCreate:tnotifyevent;
        constructor Create(AOwner: TComponent);override;
        procedure FormOncreateIntercept(AOwner: TObject);
      published
        property Speed: integer read FSpeed write FSpeed Default 1;
      end;

    procedure Register;

    implementation

    // The procedure that the form will think is it's own...
procedure FormSideSlide.FormOncreateIntercept(AOwner: TObject);
var
R : HRgn;
inx:integer;
begin
// First, keep the form invisble for now...
with aowner as tform do visible:=false;
// Run the form's original OnCreate proc
if assigned(GrabbedFormOncreate) then
  GrabbedFormOncreate(aowner);
// Then do the wizzy wazzy thing...
with aowner as tform do
begin
inx:=0;
R := CreateRectRgn(0,0,inx,Height);
SetWindowRgn(Handle,R,True);
show;
while inx<=width do
begin
R :=
  CreateRectRgn(0,0,inx,Height);
SetWindowRgn(Handle,R,True);
inx:=inx+fspeed;
application.processmessages;
end;
SetWindowRgn(Handle,0,True);
DeleteObject(R);
end;
// Finished: The form has been created in a slightly fancy way
end;

    constructor FormSideSlide.Create(AOwner: TComponent);
begin
inherited create(Aowner);
// Assign initial property values
fspeed:=1;
interceptedformcreate := FormOncreateIntercept;
// Hooking an extra link into the forms OnCreate
with aowner as Tform do
if fspeed>0
  then
  //Speed=0 diables the effect...
if not (csDesigning in ComponentState)
  then    // Of course doing this at design time would cause disaster..
begin
GrabbedFormOncreate:=oncreate;
  //Catch the form's OnCreate handler for later use
oncreate:=interceptedformcreate;
  //Plant custom handler
end;
end;

    procedure Register;
    begin
      RegisterComponents('FancyForms', [FormSideSlide]);
    end;

    end.


Auto Upgrading!

 The Unofficial Newsletter of Delphi Users - by Robert Vivrette

Auto Upgrading!

by Daniel J. Wojcik - wojcik@liaeur.21taacom.army.mil

I sit in my little office waaaaay over here and write applications for all of those people waaaay over there in entirely too many different offices. Well, I'm much too lazy to visit them and reload their systems every time I make a change to the apps I write.

Luckily, we're all on the network, and I've come up with a fairly simple way of giving them updates as needed.

I created a small application whose sole purpose in life is to copy newer versions of the main app from a network server. Here's a skeleton of my main apps' DPR file:

    {**************************************************************************}

    program THIS;

    uses
      SysUtils,
      Windows,
      Forms,
      ...;

You'll have to put in the Windows, since Delphi doesn't normally include it in a DPR file. The '...' is where all the forms you've created go, of course.
    Var
      Fn  : String;
      TSI : TStartupInfo;
      TPI : TProcessInformation;

    {$R *.RES}

    begin

Use FileAge to check the dates...it doesn't mind UNC paths & filenames... why hard-code a drive letter, if you don't have to?
      IF FileAge('\\SomeServer\SomeDir\this.exe') > FileAge(ParamStr(0)) THEN
        IF Application.MessageBox('There is a newer version THIS.EXE available.'+
                                   #13'Would you like to get the newer version?',
                                   'Upgrade Time!',1) = 1 THEN
            Begin
Run the upgrade mini-app (which must be in the same directory as the main app). If it starts, exit from this copy of the main app. Since Application.Initialize hasn't been called yet, there be shouldn't anything to clean up, resources-wise.
              Fn := ExtractFilePath(ParamStr(0))+'UpgradeThis.exe';
              FillChar(TSI, SizeOf(TSI), 0);
              TSI.CB := SizeOf(TSI);
              IF CreateProcess (PChar(Fn), NIL, NIL, NIL, False,
                                DETACHED_PROCESS, NIL, NIL, TSI, TPI) THEN
                 Exit
Of course, if the mini-app doesn't start for some reason, tell the user they'll have to get the newer version the old-fashioned way. I still exit the main app to give them the opportunity to do so. However, just take out the exit in the next block to continue loading the current version of the app if you'd rather.
              ELSE
                Begin
                  messagebeep(0);
                  Application.MessageBox('Unable to copy the file.'+'
                                         ' Please copy it manually.',
                                         'Copy Error!',1);
                  Exit;
                End;
            End;
      Application.Initialize;
      Application.Title := 'This';
      Application.HelpFile := 'This.hlp';
      {autocreate forms, etc.}
      Application.Run;
    end.
The upgrade mini-app is a console application, so be sure to set the Linker option to Generate Console Application.
    program UpgradeThis;

    uses
      Forms,
      SysUtils,
      Windows;

    Var
      Fn : String;
      TSI : TStartupInfo;
      TPI : TProcessInformation;

    {$R *.RES}

    begin

Use CopyFile to copy the new version...just like FileAge, it doesn't mind UNC paths & filenames.
      IF CopyFile('\\SomeServer\SomeDir\this.exe',
                  PChar(ExtractFilePath(ParamStr(0))+'this.exe'),False) THEN
        Begin
Once the new version of the main app has been copied, attempt to run it. No reason it shouldn't run, so I didn't do any error checking in this block. The worst that would happen is the user will be forced to run the app as they normally do.
          Fn := ExtractFilePath(ParamStr(0))+'this.exe';
          FillChar(TSI, SizeOf(TSI), 0);
          TSI.CB := SizeOf(TSI);
          CreateProcess (PChar(Fn), NIL, NIL, NIL, False,
                         DETACHED_PROCESS, NIL, NIL, TSI, TPI);
        End
      ELSE
        Application.MessageBox('Could not copy THIS.EXE', 'Error!', mb_OK);
    end.
Notice there isn't any Application.Initialize or Application.Run in this little app.

These methods can be extended to check for and copy newer versions of the Help file (use GetWindowsDir or GetWindowsDirectory and append "Help" to it in the upgrade app) or even newer versions of the upgrade app.

The adventurous can make a universal upgrade app using commandline parameters instead of the hard-coded filenames I use.


Multiple Lines of Text in a String Grid - Improved!

The Unofficial Newsletter of Delphi Users - by Robert Vivrette

Multiple Lines of Text in a String Grid - Improved!

by Chuck Gadd - cgadd@cfxc.com

In issue #23 of UNDU, Paul Harding presented an article on how to draw two lines of text in a string grid. As it turns out, there is a simpler way which is even a bit more powerful. Using this technique, you can write any number of lines of text that are naturally wrapped within the cells bounds. If the cell width is modified, the text re-wraps accordingly.

To try this out, start a new project, drop a String Grid on the form and add the following code:

    procedure TForm1.FormCreate(Sender: TObject);
    begin
      StringGrid1.Cells[1,1] := 'This is just a small demonstration of wrapping text in a string grid.';
    end;

    procedure TForm1.StringGrid1DrawCell(Sender: TObject; Col, Row: Integer; Rect: TRect; State: TGridDrawState);
    var
      TheGrid : TStringGrid;
      A       : String;
    begin
      TheGrid := Sender as TStringGrid;
      if (Row < TheGrid.FixedRows) or (Col < TheGrid.FixedCols) then
        TheGrid.Canvas.Brush.Color := clBtnFace
      else
        TheGrid.Canvas.Brush.Color := clWhite;
      TheGrid.Canvas.FillRect(Rect);
      A := TheGrid.Cells[Col,Row];
      DrawText( TheGrid.Canvas.Handle,PChar(A),StrLen(PChar(A)),Rect,DT_WORDBREAK);
    end;

This code is Delphi 3 specific however... Delphi 1 and Delphi 2 can't typecast a String to a PChar, so you'll need to declare a PChar, use GetMem to get the memory for it, StrPCopy the pascal string into it and then pass that PChar into the DrawText Command. Later don't forget to use FreeMem to release the memory.


Digital Sound in Delphi - Part II

The Unofficial Newsletter of Delphi Users - by Robert Vivrette

Digital Sound in Delphi - Part II

by Stanislav Holenda - texmurphy@email.cz

As I promised in my previous article, I’m back with more Delphi sound stuff. Today we shall look at possibilities of producing multi-channel sound effects. First of all we should theoretically discuss how to play two samples at one time on one sound device - this bring us to:

Mixing the Samples

Windows has ability to play sounds (I call them samples) by "itself", but when you want to, for example, play digital music together with sound FX (like clicking sounds on buttons, ...) you have a hard choice - to play music or sounds?

Well if you want to play them both, you’ll have to mix them. Mixing of samples with same sample rate (frequency) is fairly easy - you just have to calculate average value of their current values:

If you have stored your sample in array like this (let’s assume they are 8-bit mono, <64kB):

    type
      Tsample = array[0..65520] of byte;
    var
      samples : array[1..4] of ^Tsample;

You will mix them like this:

    value:= (samples[1]^[ppos] + samples[2]^[ppos] + samples[3]^[ppos] + samples[4]^[ppos]) div 4;

In the ppos variable is stored the playing position, and the return value is the current value to be send to the DAC. A bit harder is when you must play sample on different rates (when playing music, you’ll have to play the instrument sample on different rate to create accord, for example) - you’ll have to choose the mixing rate which will be the frequency of sending data to DAC.

For example: Let’s assume we have mixing rate 11025 Hz (=11025 times per second, we will write to DAC) and want to play one sample on 11025 Hz and second on 22050 Hz. We will take each byte from first sample (play rate/mixing rate = step) and each second byte from second sample (22050/11025=2) and mix them together and the output will be one sound playing two times faster than another. This is relatively easy to understand but harder to make a mixing routine that is fast enough to keep up. To make a good mixing routine, you’ll have to think out an optimized algorithm and write it in assembly language, if you're a tough programmer, or you can use the easy and simple interface of the MIDAS Digital Sound System.

MIDAS Sound FX engine

I expect you are now either bored or confused from my previous paragraph full of theory and now you'd like to do something easy that works well... Well here goes a step-by-step sound FX engine guide:

First of all you need to initialize MIDAS (best in the form.create method or in application startup), but slightly different as in previous article:

    midasStartup;
    midasInit;
    MIDASstartBackgroundPlay(100); {this orders MIDAS to poll the buffers 100 times/second}

Second open some channels for sound FX:

    MIDASopenChannels(5); {opens 5 channels}
MIDASallocAutoEffectChannels(4); {allocates 4 for AUTO_CHANNEL mode
  (look below)}

Third load samples:

    var sample,sample2:MIDASsample;

    sample:=MIDASloadRawSample('explod11.snd',MIDAS_SAMPLE_8BIT_MONO,MIDAS_LOOP_NO);
    sample2:=MIDASloadWaveSample('laugh1.wav',MIDAS_LOOP_NO);

Fourth play them when you want:

    MIDASplaySample(sample,MIDAS_CHANNEL_AUTO,0,11025,63,MIDAS_PAN_MIDDLE);

'sample' is a variable of type MIDASsample which must point to some data loaded with MIDASloadWave/RawSample function. MIDAS_CHANNEL_AUTO commands MIDAS to select some free channel for playing the sample automatically. 0 this number is priority - higher means higher priority
11025 play rate of sample (=the rate you want to sample at). 63 is the volume, and MIDAS_PAN_MIDDLE is the panning constants - you can use constants or numbers in range -63..63 for panning from left to right.

Also this usage of PlaySample command will only play it, if you want also control sample properties during playback (rate, volume, panning) you’ll have to use it like this:

    var laugh_handle:MIDASsamplePlayHandle;
    laugh_handle:=MIDASplaySample(sample2,MIDAS_CHANNEL_AUTO,0,11025,63,MIDAS_PAN_RIGHT);

And then use commands described in MIDAS help under "Controlling sample playback".

Fifth you must deallocate all samples, close sound device, and other stuff when you exit your program:

    MIDASfreeAutoEffectchannels; {deallocate AUTO_CHANNELS}
MIDASfreeSample(sample); {free memory occupied by samples}
MIDASfreeSample(sample2);
{Note: MIDAS may remove samples automatically when midasClose is
  called, but I’m not sure}

    MIDASstopBackgroundPlay; {stop poll-playing}

    midasClose;

So this would be in brief how to use MIDAS sound mixing capabilities. Next time we might talk about echo-effects and post processor in MIDAS and also samples & modules type and formats. If anybody got any questions about this article, or other sound-related problems let me know.

You can get a copy of the source code for my demo application by clicking here, and you can also download the files for a minimal Midas configuration, by clicking here.


Inside the Delphi 3 Package

The Unofficial Newsletter of Delphi Users - by Robert Vivrette

Inside the Delphi 3 Package (*.dpl)

by Wei Bao - wei.bao@bj.col.com.cn

Editors Note: This is a very interesting technique, but I can't vouch for its safety. If you have an adventurous streak and don't mind digging into the bowels of compiled code, then this technique might be useful to you.

One of the most important improvements of Delphi 3 is the introduction of packages. Now the components are registered into individual packages. And you can dynamically load different packages in the IDE for individual projects. The best example is the IDE, and I think every Delphi expert wants to know how the IDE does this. In this article, I'll open the secret.

If you have written a component yourself, you know that in the unit of your component you have a procedure named Register. One for each component's unit. But you never call it in your application. And you even can't find the machine code for the procedure! Who needs it? or who calls it?

The answer is in fact that the component is bound into the package you've defined for Delphi. After Delphi compiles the package's source, the package's binary format (*.dpl) is loaded by the IDE through the LoadPackage procedure call.

If you look at the source of LoadPackage(in sysutils.pas), you'll see that there's a call to Windows API LoadLibrary. LoadLibrary? This function is used to load DLL files?

Well, let's look at the DPL file...

If you run the command 'tdump -e XXX.DPL', you may see that the DPL file is DLL indeed. In its export section, you may find something like following:

    ...
        ------- --------  ----
        0049    000058a8  @GetPackageInfoTable
        0046    000058a8  Dclqrt30.@GetPackageInfoTable@51F89FF7
        0045    000058b0  Dclqrt30.@PackageLoad@51F89FF7
        0044    000058c0  Dclqrt30.@PackageUnload@51F89FF7
        0047    000058c0  Finalize
        0048    000058b0  Initialize
        0042    00005638  QRNew.Finalization@51F89FF7
        0041    00005668  QRNew.QRNew@00000000
    *   0043    00005614  QRNew.Register@51F89FF7
        0038    0000426c  qreport.ExecuteDesignVerb@4807F569
        0036    00004f54  qreport.Finalization@51F89FF7
        0040    00004054  qreport.GetDesignVerb@0F6FDFF6
    ...
    (* is added by author)

And near the LoadPackage, you'll find a function called GetPackageInfo(in sysutils.pas). With this function, you can get the source file of a DPL within several lines:

    procedure EveryUnit(const Name: string; NameType: TNameType; Flags:
  Byte; Param: Pointer);
begin
if NameType = ntContainsUnit then
memo1.lines.add(name);
  // you get the unit name here !!
end;

    procedure GetUnitName;
    var
      HPack, flags : integer;
    begin
      HPack := LoadPackage('Some.DPL');
      GetPackageInfo(HPack, @Hpack, flags, EveryUnit);   // the second parameter is not used now, but later.
    end;

Yes!

Now you can load the package into your application space, but you can't use any components in it. If the package is written by you, you can solve this with the following method:

In the Initialization section of your component's unit, call registerClass(yourcomponent). The Initialization procedure is called after call to LoadLibrary in LoadPackage(see sysutils.pas). After doing this, you may create an instance of your component:

    AComponent := TComponentClass(GetClass('yourComponent')).create(Self);

But if the DPL is from the third party, you won't have an opportunity to do this.

Now let's go back to the dump of DPL, look at the line begins with '*' . You see the DPL exports a function named 'QRNew.Register@51F89FF7'. It's obviously that the 'QRNEW' is the unit name. What's '51F89FF7'? Is it an address of the Register procedure? No, after you look through the total output of the dump, you'll find that they are always '51F89FF7'. So we can call it a magic number. It's a const for Delphi 3.0 at least.

If we want to use the component in the DPL, we should call the Register ourselves.

So we come to the exciting part:

We should know the unit name to call 'unitname.Register@51F89FF7'. As I've given before, we can get the unit name in procedure EveryUnit . The rest thing is familiar to those who've been working with Windows APIs for a long time.

The new version of EveryUnit is :

    procedure EveryUnit(const Name: string; NameType: TNameType; Flags: Byte; Param: Pointer);
    var
      PRegister : pprocedure;
    begin
      if NameType = ntContainsUnit then
        begin
          //memo1.lines.add(name);                              // you get the unit name here !!
          @PRegister := GetProcAddress(PInteger(Param)^,        // get the HPack
            pchar(Name + '.Register@51F89FF7'));
          if @PRegister <> nil then PRegister;
        end;
    end;

Now all these work fine? No, you'll see a error messagebox 'register component ..'. Why? Everything is here. Now let's look at the procedure RegisterComponents you always call in procedure Register(in classes.pas). A pointer to a procedure is checked, and it always equal nil in our application! Of course, you can see that the solution is simple, write our procedure and set the pointer to it.

    procedure myRegisterComponentsProc(const Page: string;
  ComponentClasses: array of TComponentClass);
var i : integer;
begin
for i := low(ComponentClasses) to High(ComponentClasses) do
RegisterClass(ComponentClasses[i]);
end;

    initialization
      RegisterComponentsProc:= MyRegisterComponentsProc;

Every thing is well ? No, if you compile the project without runtime package, you'll get the same error messagebox. Yes, to use DPLs dynamically you can only compile your project as a runtime package.

If you try to load DPLs that ship with Delphi 3.0, you'll meet other problems, try to solve them! And you can contact the author at wei.bao@bj.col.com.cn or wei.bao@usa.net. I'm very interested to talk about these technologies with any one!

If you want to take a look at the source code I used for these examples, you can download it here...


Controlling Application Startup

 The Unofficial Newsletter of Delphi Users - by Robert Vivrette

Controlling Application Startup in Delphi 3

by Evan Simpson - esimpson@eramp.net

For many programs, having more than one instance running at the same time isn't a problem. It may even be a desirable feature, in SDI applications like Notepad, for instance. Often, though, the program will read and write data files (or registry keys) in a way which will cause corruption if another copy is launched, or it may simply not make any sense to have multiple instances running. In these cases it is valuable to have a simple mechanism to detect and control instance launches, especially with Windows 98 and its single-click launch interface.

Under 16 bit Windows, the solution was simple; Just examine the previous instance global variable to see if a copy is already running, and do the right thing. Under Win32, things are a bit more complicated. Several solutions are available on the Web, including one on the Borland Tips page, but I still felt compelled to write my own for several reasons. First, I needed to transmit the command line parameters of a second launch attempt to the primary instance in one program I wrote (it processes BMP files dragged onto its Explorer icon). Second, I wanted a plug-in unit which required minimal alteration from one program to the next, and little or no foolery in my DPR file. Third, I wanted it to be secure, reliable, and fast; In particular, it should run before any other VCL code initializes or allocates anything.

To meet the first requirement, I used WM_COPYDATA on the command line string. This in turn required me to get a window handle from the primary instance to use as a target for the message. FindWindow handles this nicely, but I couldn't use the automatic application window or any other standard window of the application, because of the third requirement.

The second requirement led me to place all of the code into a unit which is simply placed in the DPR "uses" clause. The actual instance management code runs in the unit's initialization. Global variables in the interface part provide the event hook and passed data storage.

Due to the third requirement, the unit must be the very first unit used by the DPR, and could not use any other application unit. In particular, almost every unit is likely to use Forms, and Forms is normally the first unit used by a DPR, but Forms sets up and initializes the whole Application framework.

The result of all this is the InstanceManager unit below. Simply make it the first unit in your program's DPR's uses list, and edit the constant declarations to fit your needs. Note that by using the same string constant for two or more different programs, you can create a "family" of executables which share instance management - I use this ability in a timeclock program, which not only should not run more than once, but cannot run at the same time as its administative utility. Also, this unit doesn't just prevent a second instance from running. It can call a notification event, which can decide on a case-by-case basis whether to allow the other instance to run, perhaps by examining the command line.

    unit InstanceManager;

    interface

    {Notes: make InstanceManager the *very first* unit in your program's USES
    clause.  To take advantage of the notification and launch-string, put a
    method with no parameters in one of your forms and assign it to triggerProc.
    Once triggerProc is called, rcvStr contains the command line of the launch
    attempt.

    If the only reaction you want is to bring the first instance to the front,
    just put a method like the following in your main form, and in the form's
    OnCreate set InstanceManager.triggerProc:=ToFront;

        procedure
TForm1.ToFront;
begin
Application.Restore;
Application.BringToFront;
end;

    If you don't have a dependable main form, make ToFront a class procedure of
    any old class.}

    { Customize these constants
before using }
const UniqueAppName = 'Unique application
name';
AppNotifyValue: integer
= 0;

    var rcvStr: string;
        rcvValue: integer;
        ForbidOtherInstance: boolean = True;
        triggerProc: procedure of object;

    implementation

    uses Windows, SysUtils, Messages;

    var mutex, thisWnd: HWND;
        IMWndClass: TWndClassA;
        mustHalt: boolean;
        copydata: TCOPYDATASTRUCT;

    function IMWndProc(HWindow: HWnd; Message,
WParam: Longint; LParam: Longint): Longint; stdcall;
begin
if Message=WM_COPYDATA then
begin
rcvStr := StrPas(PCOPYDATASTRUCT(lParam).lpData);
rcvValue := PCOPYDATASTRUCT(lParam).dwData;
if Assigned(triggerProc)
then triggerProc;
Result := Ord(ForbidOtherInstance);
end
else
Result := DefWindowProc(hWindow,
Message, WParam, LParam);
end;

    initialization

      FillChar(IMWndClass, SizeOf(IMWndClass), 0);
IMWndClass.lpfnWndProc := @IMWndProc;
IMWndClass.hInstance := HINSTANCE;
IMwndClass.lpszClassName := 'TInstanceManager';
if Windows.RegisterClass(IMWndClass)
= 0 then RaiseLastWin32Error;
mutex := CreateMutex(nil, True, UniqueAppName);
if GetLastError = ERROR_ALREADY_EXISTS
then
begin
mustHalt := True;
if WaitForSingleObject(mutex,
5000)=WAIT_OBJECT_0 then
begin
thisWnd := FindWindow(IMwndClass.lpszClassName, UniqueAppName);
if thisWnd = 0 then RaiseLastWin32Error;
CopyData.dwData := AppNotifyValue;
CopyData.lpData := CmdLine;
CopyData.cbData := StrLen(CmdLine);
mustHalt := (SendMessage(thisWnd,WM_COPYDATA,0,Integer(@CopyData))>0);
end;
thisWnd := 0;
ReleaseMutex(mutex);
if mustHalt
then Halt;
end
else
begin
thisWnd := CreateWindow(IMwndClass.lpszClassName,UniqueAppName,0,0,0,0,0,0,0,hInstance,
nil);
if thisWnd
= 0 then RaiseLastWin32Error;
ReleaseMutex(mutex);
end;

    finalization
      if thisWnd>0 then DestroyWindow(thisWnd);

    end.


Delphi Users Groups

If you run a Delphi Users Group that is not on this list, please feel free to send me an email with the essentials. Please include basic information about where and when the group meets, and particularly any web links you may have to support the group. I am going to keep the info to a minimum so I can include in each issue (or maybe every other issue). I have provided a sample form to help you provide the right information.

Arizona

    Tucson Delphi Users Group - TDug
California
    Orange County Delphi Users Group
    North Bay Delphi SIG
    Delphi SIG of the Sacramento PC User Group
Canada
    Metro Halifax Delphi Developers Group
    The Toronto Area Delphi Developers Association
    Vancouver Delphi Developers Association
Colorado
    Western Slope Visual Developers Group
    Denver Borland Users Group
Iowa
    Central Iowa Delphi Users Group
Kansas
    Kansas City Delphi Users Group
Massachusetts
    The Delphi Developers Group of Greater Boston
Minnesota
    TCPC Delphi SIG
Michigan
    Royal Oak Delphi Users Group
North Carolina
    Research Triangle Park Delphi Users Group
Ohio
    Delphi SIG of the Greater Cleveland PC Users Group
    Columbus Delphi SIG
    Greater Dayton Delphi Group
Oregon
    Oregon Delphi Users Group
Pennsylvania
    Pittsburgh Area Delphi Users Group
    Borland Technologies User Group
Utah
    Salt Lake City Delphi Users Group


Tucson Delphi Users Group

Group Name: | Tucson Delphi Users Group - TDug
Person(s) in Charge: | Matthew J. Brock, Oscar Speed
Meeting Date/Time: | Third Saturday of each month, 9:00am-10:00am
Meeting Location: | DRA Software Training Center, 3434 E. 22nd St. Suite 100, Tucson, AZ
Dues: | Free
Email Address: | mjb@azstarnet.com
Phone Number: | 520-571-7203
Internet URL: |
Description: | One or more presentations, Q&A, Product Reviews


Orange County Delphi Users Group

Group Name: | Orange County Delphi Users Group
Person(s) in Charge: | Greg Stevenson
Meeting Date/Time: | Fourth Tuesday of each month at 7:00 pm
Meeting Location: | See Web Site
Dues: | See Web Site
Email Address: | See Web Site
Phone Number: | See Web Site
Internet URL: | http://www.ocdelphi.org
Description: |


North Bay Delphi Special Interest Group

Group Name: | North Bay Delphi Special Interest Group
Person(s) in Charge: | Bjarne Winkler
Meeting Date/Time: | Second and Fourth Wednesday of each month from 7:00 pm to 9:00 pm
Meeting Location: |
Dues: |
Email Address: | nts4618086@aol.com
Phone Number: |
Internet URL: |
Description: | Announcements, Q&A, Special topics each meeting.


Delphi SIG of the Sacramento PC User Group

Group Name: | Delphi SIG of the Sacramento PC User Group
Person(s) in Charge: | Robert Muir, Mike Prestwood
Meeting Date/Time: | 2nd Monday of the Month - 6:30pm
Meeting Location: | New Horizons Building, 1215 Howe Avenue, Sacramento, CA
Dues: | None
Email Address: | robertmuir@worldnet.att.net or mike@prestwood.com
Phone Number: |
Internet URL: | www.prestwood.com/forums/delphi/sdug
Description: | Announcements, Q&A, Special Topics each meeting


Metro Halifax Delphi Developers Group

Group Name: | Metro Halifax Delphi Developers Group
Person(s) in Charge: | Dave Hackett
Meeting Date/Time: | First Tuesday of each month from 7:00 pm to 9:00 pm
Meeting Location: | Maritime Life Business Park
Dues: | None
Email Address: | 71650.2646@compuserve.com
Phone Number: |
Internet URL: |
Description: | News, Q&A, Presentations


Toronto Area Delphi Developers Association

Group Name: | Toronto Area Delphi Developers Association (TaDDA!)
Person(s) in Charge: | Henri Fournier (President), Robert Kozak (Membership Director), Ted Nye (Executive Director)
Meeting Date/Time: | First Tuesday of each month
Meeting Location: | North Youk Public Library, Toronto, Ontario, Canada
Dues: | $60/year
Email Address: | membership@TaDDA.com
Phone Number: | 416-366-7150
Internet URL: | http://www.tadda.com
Description: | 3 Hours. Sessions by well known Delphi experts such as Mark Miller, Ray Konopka, Charlie Calvert, etc. and in depth sessions on all aspects of Delphi for New to Advanced users.


Vancouver Delphi Developers Association

Group Name: | Vancouver Delphi Developers Association
Person(s) in Charge: | Michael Li
Meeting Date/Time: | Third Wednesday of each month, 6:30pm
Meeting Location: | Liquor Distribution Branch, 2625 Rupert Street, Vancouver, BC
Dues: | None
Email Address: | mli@infocan.com
Phone Number: | 604-736-5888 x728
Internet URL: | http://www.infocan.com
Description: | Michael Li is usually the main speaker at meetings. Informal setting. Michael's main focus is on Delphi and Borland related topics and discussion, updates on new technology (hardware, software) coming out to the market, updates on books that are useful to particular products (provide evaluation copies for review), information on conferences coming up in Vancouver. May have guest speakers depending on the topic.


Western Slope Visual Developers Group

Group Name: | Western Slope Visual Developers Group
Person(s) in Charge: | Shane Holmes, Software Engineer, ViA Software Services
Meeting Date/Time: | Last Wednesday of each month, 6:30pm
Meeting Location: | ViA Software Services, 743 Horizon Court, Suite 368, Grand Junction, CO 81506
Dues: | None
Email Address: | sholmes@viagj.com
Phone Number: | 970-257-7010, 970-248-9733
Internet URL: | http://www.gj.net/~hap
Description: |


Denver Borland Users Group

Group Name: | Denver Borland Users Group (DeBUG)
Person(s) in Charge: | Ron Kocsis, Chuck Gadd
Meeting Date/Time: | 6:30pm, 2nd Tuesday of the Month
Meeting Location: | Suite #1350 at 5299 E. Prentice Ave, Greenwood Village, CO
Dues: | $30/year
Email Address: | cgadd@cfxc.com
Phone Number: | 303-933-4159
Internet URL: | www.fcxc.com/debug
Description: | Presentation by members or third-party companies.


Central Iowa Delphi Users Group

Group Name: | Central Iowa Delphi Users Group
Person(s) in Charge: | David W. Body
Meeting Date/Time: | Third Thursday of every month at 5:30 pm
Meeting Location: | Offices of Iowa Bankers Association, Liberty Building, 418 6th Ave. Suite 430, Des Moines, Iowa.
Dues: | None
Email Address: |
Phone Number: | 515-984-6243
Internet URL: | http://www.bigcreek.com/delphi
Description: |


Kansas City Delphi User Group

Group Name: | Kansas City Delphi User Group
Person(s) in Charge: | Shawn McDonald, Kerri VanMevern
Meeting Date/Time: | 1st Wednesday of each month, 6:30pm
Meeting Location: | DRT Systems, 4501 College Blvd., Leawood, KS
Dues: | $24/year
Email Address: | mofnet@aol.com
Phone Number: | 913-661-9669 x265
Internet URL: | http://www.kcdelphi.org
Description: | Flexible format includes presentations by members, code reviews, and question and answer


Delphi Developers Group of Greater Boston

Group Name: | Delphi Developers Group of Greater Boston
Person(s) in Charge: | Al Renhart, DisCom Systems
Meeting Date/Time: | Third Monday of each month, 7:00 pm
Meeting Location: | 10 New England Executive Park, Burlington, MA
Dues: |
Email Address: | reinhart@discom.com
Phone Number: | 508-869-6417
Internet URL: |
Description: |


TCPC Delphi SIG

Group Name: | TCPC Delphi SIG
Person(s) in Charge: | Richard Hansen
Meeting Date/Time: | 4th Wednesday of Month, 7pm
Meeting Location: | 2850 Metro Drive, Room 124 - Bloomington, MN
Dues: | None
Email Address: | rickh@artemisalliance.com
Phone Number: | 612-227-7172
Internet URL: | http://www.tcpc.com
Description: | Questions & Answers, Monthlu topic, Focus Groups.


Royal Oak Delphi Users Group

Group Name: | Royal Oak Delphi Users Group
Person(s) in Charge: |
Meeting Date/Time: | First Thursday of each month at 7:00 pm
Meeting Location: | William Beaumont Hospital (Lower Level), Royal Oak, MI. Classrooms in AB-West building.
Dues: | None
Email Address: |
Phone Number: | 810-551-5000
Internet URL: | http://ourworld.compuserve.com/homepages/STNICOL/
Description: | Constructing DLL's, SQL Server 6.5, CGI, Email enabled databases, data collection alternatives, VCL parties, book review night, etc.


Research Triangle Park Delphi Users Group

Group Name: | Research Triangle Park Delphi Users Group
Person(s) in Charge: | Mark Hutchinson
Meeting Date/Time: | Third Wednesday of each month, 5:00 pm
Meeting Location: | New Horizons Computer Learning Center - Willow Oak Bldg, Suite 116, 4625 Creekstone Drive, Durham, NC
Dues: |
Email Address: | aikimark@aol.com
Phone Number: |
Internet URL: | http://www.geocities.com/CapeCanaveral/Lab/1185/rtpdig.html
Description: |


Delphi SIG of the Greater Cleveland PC Users Group

Group Name: | Delphi SIG of the Greater Cleveland PC Users Group
Person(s) in Charge: | Bill Querry
Meeting Date/Time: | Fourth Wednesday of each month, 6:30 pm - 8:30 pm
Meeting Location: | 2747 SOM Center Rd, Willoughby OH 44094
Dues: | Free for Meetings. $25/year for GCPCUG
Email Address: | bq@bigfoot.com
Phone Number: | 440-944-9980
Internet URL: | http://www.gcpcug.org
Description: | Demos of some feature of Delphi or 3rd Party Add-On. General discussion of user's current projects & problems. Talk about latest magazines/book releases.


Columbus Delphi SIG

Group Name: | Columbus Delphi SIG
Person(s) in Charge: | Rick Ross
Meeting Date/Time: | 2nd Wednesday of every month. 7-9pm
Meeting Location: | 1091 W. Broad St., Columbus, OH
Dues: | None
Email Address: | rickross@compuserve.com
Phone Number: | 614-898-2767
Internet URL: |
Description: | Demonstrations, discussion, and training


Greater Dayton Delphi Group

Group Name: | Greater Dayton Delphi Group (GDDG)
Person(s) in Charge: | Keith Wire
Meeting Date/Time: | Second Thursday of each month
Meeting Location: | Dayton, OH
Dues: | $50/Year
Email Address: | kwire@compuserve.com
Phone Number: |
Internet URL: | http://www.aii-usa.com/gddg
Description: | Sharing of Delphi-related information


Oregon Delphi Users Group

Group Name: | Oregon Delphi Users Group
Person(s) in Charge: | Greg Postlewaite
Meeting Date/Time: | 3rd Monday of every month, 7pm
Meeting Location: | Lorenzen Center, Emanuel Hospital, Portland, OR
Dues: |
Email Address: |
Phone Number: |
Internet URL: |
Description: |


Pittsburgh Area Delphi Users Group

Group Name: | Pittsburgh Area Delphi Users Group
Person(s) in Charge: | Kirk A. Farra
Meeting Date/Time: | Second Thursday of the month at 7:00 pm
Meeting Location: | Mastech Inc.
Dues: | None
Email Address: |
Phone Number: | 412-452-6121
Internet URL: | http://www.nauticom.net/www/kfarra
Description: | We cover Delphi and any related products/tools. Members are exposed to many 3rd party components and have a local support network for Q/A. Members get to review many products before they are released and get information on local job opportunities and contracting positions.


Borland Technologies User Group

Group Name: | Borland Technologies User Group
Person(s) in Charge: | Matt Beckius
Meeting Date/Time: | Undetermined (we are just forming now)
Meeting Location: | Philadelphia, PA
Dues: | Undetermined
Email Address: | Undetermined
Phone Number: | 610-409-8229 extension 8
Internet URL: | http://www.sourcetechcorp.com/borland.htm
Description: | Product Reviews, Demonstrations, Discussions


Salt Lake City Delphi Users Group

Group Name: | Salt Lake City Delphi Users Group
Person(s) in Charge: | Robert Love, John Rendell, Mike Mormondo
Meeting Date/Time: | 3rd Thursday of every month
Meeting Location: | Franklin Covey Headquarters
Dues: | None
Email Address: | slcdug@lovedata.com
Phone Number: | 801-296-0809
Internet URL: | http://www.lovedata.com/delphislc
Description: | Each meeting is different but usually it is a presentation of a new idea or product that relates to delphi. There is a Q/A Session in every meeting to help you move along as well.


Form For Users Groups

If your Delphi Users Group is not mentioned in the list, please take a moment to email me the particulars about your group. To make the information as uniform as possible, please cut and paste this form into an email and send it to me at RobertV@compuserve.com. Please only send one if you are in charge of the group… I don't want to have to wade through 30-40 copies for each group… Thanks!

Group Name:
Person(s) In Charge:
Meeting Date/Time:
Meeting Location (include City & State):
Dues:
Email Address:
Phone Number:
Internet URL:
Brief description of meeting format:


 The Delphi Workout
  • Professional Delphi Training for Under £100
  • One of the World's leading Delphi Trainers
  • Great sessions to enhance your Delphi Skills
  • FREE Full 170 page course book
  • FREE Developers CD with over 1200 tools and components
  • FREE £600 off Borland Tools
  • Sessions in London (Westminster Hall) and Manchester (United Football Club)

    • Zack Urlocker (Borland VP Marketing) says: "A must for Developers who want to take their Delphi skills to the next level. Highly recommended"

    To register:

    Phone:      0800 833022
    Fax back:   0118 954 6978
    E-Mail:     workout@multizone.com
    On-Line:    http://www.multizone.com/workout
    Mail:       The Delphi Workout
                Multizone International
                Caversham Business Centre
                23 Prospect St, Caversham, RG4 8JB.
    When:       19th, 20th March 1998 and other dates TBA

    Course Outline:

    8.30-9.00 Registration

    9.00-9.30 Warming up
    In this section we'll discuss some generic tips to working with Delphi 2 including some focus on exciting new methods of working in Delphi 3

    9.00-9.30 - Delphi Overview
    Delphi 2&3, ObjectPascal, Code Insights, Components, BDE. This session will give a very quick overview of some 32-bits Delphi features, such as Code Insights, the VCL Component Palette/Packages, Data Modules, Form Inheritance, ActiveX controls, and the Database-Form Wizard, etc.

    9.30-10.45 The Top 10 Ways to becoming a better Delphi Programmer
    In this section we'll focus on ObjectPascal, exception handling, and performance optimization, positioning most of this on Delphi 2, but highlighting new better ways to do it in Delphi 3 as well

    9.30 -10.00 - ObjectPascal
    ObjectPascal, Exceptions, Try-Except, Try-Finally, Raise, Assert. This session will start with new ObjectPascal features, followed by the benefits of structured exception handling as the preferred way of error detection, error handling and resource management. All Delphi 2 compatible. At the end of the session, we'll also examine a new Delphi 3 feature called assertions, to support preconditions and error detection.

    10.00-10.45 - 32-bits Efficiency
    Profiling, Algorithms & Datastructures, Language Features. This session will identify the steps of performance optimization: finding bottle-necks, checking algorithms & datastructures, and new 32-bits Delphi language features. A number of examples will illustrate what difference it can make in performance to know and apply the tips and techniques from this session.

    10.45-11.00 Coffee

    11.00-12.30 The Top 10 Secrets to building Rapid Applications
    In this section we'll focus on the developer and his building tools (components, packages and Delphi IDE experts/wizards) to help building Rapid Applications.

    11.00-11.45 - Component Building
    Component Wizard, Properties, Methods, Events. This session will teach the basics of component building, as we focus on the Component Wizard, followed by the creation of a component skeleton, the addition of properties, methods, events and event handlers. We'll move on by installing the component in the Component Palette, or in a Delphi 3-specific package

    11.45-12.00 - Delphi 3 Packages
    Design Time, Run Time, Deployment, Versioning. This session will explain what Delphi 3 packages are, how they can benefit our projects (size of executable), how to make our own custom packages with components, and how to deploy them correctly.

    12.00-12.30 - Experts and Wizards
    IDE, Open Tools API, Experts, Wizards. This session will show how to extend the Delphi IDE itself by using the Open Tools API to write Experts (that plug into the IDE) and Wizards, including some User Interface guidelines for writing Wizards.

    12.30-13.30 Lunch

    13.30-14.00 Unleashing the hidden power of Databases within Delphi
    BDE, Tables, Queries, data-aware components. This session will show the database basics, including tables, queries, data-aware components and how to (re-) create your own tables with ObjectPascal source code.

    14.00-14.45 - The Best of Delphi (UK-BUG)
    A Borland User Group representative will highlight how to unearth the most comprehensive guide (the DIL database) where to find free Delphi Code,
    tools, samples and support.

    14.45-17.00 Building Killer Web Applications
    In this section we'll start by seeing how we can use Delphi 2 for data-aware net development, followed by some next exciting techniques and net development support in Delphi 3.

    14.45-15.10 - Table to HTML
    HTML, Tables, Image Map, Table-layout, Index Fields, Frames. This session will shortly introduce the basic concepts of HTML, followed by a Wizard that can be used to convert the contents (records) of a table to a set of static HTML-pages that can be uploaded to the web.

    15.10-15.30 - CGI and WinCGI
    CGI/WinCGI, GET/POST, State, Debugging CGI. This session will extend our HTML knowledge with the Common Gateway Interface protocol, which is a way to interact between the web client and the web server. Delphi 2 compatible.

    15.30-15.45 Afternoon Tea

    15.45-16.15 - Delphi 3 ActiveForms
    New ActiveForm, Converting existing Forms, Deployment. This session will illustrate how to write, use and deploy ActiveForms on the web, including an example where we publish a table on an ActiveForm on the Internet.

    16.15-17.00 - Delphi 3 C/S WebModules
    WebModule, Dispatcher, PageProducer, TableProducers. This session will demonstrate the Delphi 3 Client/Server-only capabilities of the Web Modules to create and support CGI/WinCGI and ISAPI/NSAPI web server applications, including an example where we connect a database to the web, with options to browse, query and update the database contents.

    17.00-17.30 Cooling Down

    17.00-17.30 - The Future of Delphi (Borland)
    A Borland Product Manager will talk to the audience for half an hour on the future of Delphi... and answer questions from the audience.


Product Reviews & Announcements

I have moved all the announcements and reviews of books, and other Delphi related products to this page. If you have a product you would like to see reviewed, or wish to do a review yourself, please feel free to contact me at RobertV@csi.com.

03/25/98 - WinHelp Office Review
Issue #24 - A Second Look at MicroEdge's Visual SlickEdit
Issue #23 - Book Review: Hidden Paths of Delphi 3
Issue #23 - Book Review: Building Delphi 2 Database Applications
Issue #23 - Raize Components
Issue #22 - Programmers Guild Animated Tray Icon
Issue #22 - MicroEdge's Visual SlickEdit
Issue #22 - IniOut Component
Issue #22 - Addict 2.2 Spell Checker for Delphi
Issue #21 - Book Review: Delphi Component Design
Issue #20 - Delphi Books and Periodicals
Issue #19 - SpeedDaemon Code Profiler
Issue #19 - A Look at MagiKit
Issue #18 - Object Express by OopSoft
Issue #18 - Announcement: IniOut Component
Issue #18 - Book Review: Delphi Component Design by Danny Thorpe

More links to come! To see the material in Issues 1 through 17, click here.


Delphi Job Sites

Below are some of the Delphi Job sites currently available. If you know of others, please let me know about them and I will add a link on this page.

    Employment at Borland/Inprise
    http://www.borland.com/about/hr/

    Borland/Inprise Delphi Jobs Newsgroup
    news://forums.inprise.com/borland.public.delphi.jobs

    Delphi Job Hunter
    http://www.inthemiddle.co.uk/jobhunter/delphi/default.htm

    Mike Strock's Delphi Jobs Site
    http://www.oz.net/~mstrock/delphijobs.html

    Greg Postlewait's Delphi Jobs Site
    http://www.teleport.com/~gpost/djobs

    Richard Fellner's Delphi Job Exchange
    http://www.user.xpoint.at/r.fellner/DELPHIJ.HTM

    Richard Maley's Delphi Jobs Service
    http://www.advdelphisys.com/jobs/jobs_all.html

    Becker Technology, Inc.
    http://www.beckertechnology.com/emplopps.htm

Additional Programming-Related Job Sites
    Jobs for Programmers
    http://www.prgjobs.com

    JobServe UK
    http://www.jobserve.com


The Unofficial Newsletter of Delphi Users - News

News Page for April 1998

April 22nd, 1998

Three great articles today:

The first is a continuation of Stanislav Holenda's series on sound. Check out Digital Sound in Delphi - Part II.

Next, if you have an adventurous streak in you and want to learn more about how Delphi 3's Packages are organized, take a look at Inside the Delphi 3 Package by Wei Bao.

And lastly, Evan Simpson brings us yet another improved method for managing the startup of your application including multiple instance management, and communication with a previous instance of your application. His article is entitled Controlling Application Startup in Delphi 3.

In answer to the question mentioned below (the April 15th Question), I received a number of good responses:

Michael Van Canneyt writes the following:

"It doesn't display graphics because the MicroSoft richedit component, (around which TRichedit is a Delphi wrapper) it isn't able to do so. It also destroys fields and some other things. Maybe Microsoft has released a renewed component (it resides in riched.dll or riched32.dll in the windows\system32 directory), but I don't think so. In general, the capabilities are limited to the capabilities that the Delphi wrapper offers you: textformatting and paragraph formatting. The Visual Basic development kit has a list in its manual with 'supported RTF codes'; this list gives all the codes that the component doesn't choke on; this doesn't necessarily mean that the component SUPPORTS them, i.e. displays them correctly. For instance I know for a fact that field codes are accepted (as it says in the list), but are destroyed when the Richedit component saves its text again."

But an actual solution to the problem was offered by Kevin Gallagher. On the DSP site, he found a freeware OLE RichEdit component that looks like it is just the solution we need. You can download it by clicking here. Thanks Kevin!

April 17th, 1998

A bunch of new stuff today...

Chuck Gadd presents an extension to a technique originally presented by Paul Harding. It's called Multiple Lines of Text in a String Grid - Improved!

Daniel Wojcik, like all of us, get tired of manually distributing upgrades to everyone in the company. His solution is presented in Auto Upgrading!

And finally, Stein Svendsen (from Norway, I think) illustrates an interesting extension of a technique originally presented by Alan Lloyd. Check out his work in Piggybacking Event Handlers with Components.

April 15th, 1998

A few things of interest today...

The first is a question that I am going to open up to anyone who might have an answer. This was posed by a reader from the Peoples Republic of China... (yes, Delphi lives there too!). His question relates to RTF files, and I will paraphrase it:

"I created a document using the WordPad application, included a graphic image (bitmap), and saved the file to the RTF format. When I try to load that RTF file into a Delphi TRichEdit component, I get the text but no graphic. How come?".

I didn't have an immediate answer, but it is an interesting question. I do know that RTF format does support graphics... does Delphi not?

Matt Hamilton brings us a great simple tip to improve the user interface when dealing with empty tables. Check out his tip on Using TDataSource to Improve Your User-Interface.

Clinton Johnson is back also with an extension of Graham Marsh's article on Hooks. Take a look at it in Hooking Into the VCL.

April 13th, 1998

Sorry for the little break there folks. I was catching up on a number of projects and unfortunately let my attention to UNDU slip a little.

The winners of the UNDU prizes for March are as follows:

For his articles on Windows Hooks, Determining CPU Types, and Compressing 256 color BMPs, Grahame Marsh wins a copy of PSetting by Pythoness Software. For his excellent tip on Moving and Resizing components at Runtime, Matt Hamilton wins a copy of Ray Lischner's book Hidden Paths of Delphi 3. And finally, for his article on Using the Clipboard with WAV files, John Mertus wins a copy of the Delphi Informant Works CD, which includes all issues of Delphi Informant through 1997. Congratulations to you all and thanks again for all the great articles!

I am putting together some good stuff for tomorrow... stay tuned!


The Unofficial Newsletter of Delphi Users - Past UNDU Issues

Past UNDU Issues

Below are links to all the issues of UNDU that are currently available to read online. If you want to obtain these same issues in a downloadable format, please check the section on Downloadable UNDU Issues.

Issue #24 - December '97
Issue #23 - September '97
Issue #22 - June '97
Issue #21 - May '97
Issue #20 - March '97
Issue #19 - January '97
Issue #18 - November '96
Issue #17 - October '96
Issue #16 - September '96
Issue #15 - August '96
Issue #14 - June '96
Issue #13 - May '96
Issue #12 - February '96
Issue #11 - January '96
Issue #10 - December '95
Issue #9 - November '95
Issue #8 - October '95
Issue #7 - August '95
Issue #6 - July '95
Issue #5 - June '95
Issue #4 - May '95 (b)
Issue #3 - May '95 (a)
Issue #2 - April '95
Issue #1 - March'95


The Unofficial Newsletter of Delphi Users - Downloadable UNDU Issues

Downloadable UNDU Issues

Since issue #1 in March of 1995, all issues of The Unofficial Newsletter of Delphi Users has been published as Windows Help files. Below are links to each of these files so you can download them individually, or all at once. To speed downloading time, I have Zipped them, so you will need PKUNZIP or an equivalent program to decompress them. If you don't have a ZIP compatible program, you can download just the HLP file.

When you unzip them, I suggest that you put all issues in a single directory. Many of the files have links to other issues and if they are all present together, they will behave normally. Most of the later issues in ZIP file format also have a contents file (*.CNT) which is used to by the Windows help system to display a complete list of all issues available.

Effective January, 1998 - I will no longer be arranging UNDU as "issues", but rather as individual articles that will be posted on the UNDU web site every few days. At some time in the future, I may start packaging these articles as downloadable WinHelp files, but I am not promising anything at this point.

Issue # - Month of Release | Zip File | HLP File
Issue #24 - December '97 | DN971201.ZIP | DN971201.HLP
Issue #23 - September '97 | DN970901.ZIP | DN970901.HLP
Issue #22 - June '97 | DN970601.ZIP | DN970601.HLP
Issue #21 - May '97 | DN970501.ZIP | DN970501.HLP
Issue #20 - March '97 | DN970301.ZIP | DN970301.HLP
Issue #19 - January '97 | DN970101.ZIP | DN970101.HLP
Issue #18 - November '96 | DN961101.ZIP | DN961101.HLP
Issue #17 - October '96 | DN961001.ZIP | DN961001.HLP
Issue #16 - September '96 | DN960901.ZIP | DN960901.HLP
Issue #15 - August '96 | DN960801.ZIP | DN960801.HLP
Issue #14 - June '96 | DN960601.ZIP | DN960601.HLP
Issue #13 - May '96 | DN0596.ZIP | DN0596.HLP
Issue #12 - February '96 | DN0223.ZIP | DN0223.HLP
Issue #11 - January '96 | DN0118.ZIP | DN0118.HLP
Issue #10 - December '95 | DN1212.ZIP | DN1212.HLP
Issue #9 - November '95 | DN1109.ZIP | DN1109.HLP
Issue #8 - October '95 | DN1010.ZIP | DN1010.HLP
Issue #7 - August '95 | DN0831.ZIP | DN0831.HLP
Issue #6 - July '95 | DN0725.ZIP | DN0725.HLP
Issue #5 - June '95 | DN0626.ZIP | DN0626.HLP
Issue #4 - May '95 (b) | DN0524.ZIP | DN0524.HLP
Issue #3 - May '95 (a) | DN0501.ZIP | DN0501.HLP
Issue #2 - April '95 | DN0401.ZIP | DN0401.HLP
Issue #1 - March '95 | DN0315.ZIP | DN0315.HLP


Group Name:
Person(s) in Charge:
Meeting Date/Time:
Meeting Location:
Dues:
Email Address:
Phone Number:
Internet URL:
Description: