The Unofficial Newsletter of Delphi Users - Banner

Downloadable UNDU Issues Delphi User Groups

News Page

March 26th, 1998

Clinton Johnson brings us some more great material. First, he discusses using the TMediaPlayer to manage Multiple CD Rom Drives. Then, he gets a little artistic with a great discussion of how Bezier Curves and Paths work.

Grahame Marsh also has something for us today. He has put together a very nice discussion of implementing Windows Hooks in your applications.

March 25th, 1998

Do you need to create a help system for your Delphi applicatioin or component? WinHelp Office to the rescue! Check out my review of this fantastic help authoring system.

March 20th, 1998

I'm currently working on a number of great articles, so stay tuned this weekend for more.

On a completely unrelated note however, I have 2 unused software packages that I will let go for best offer if anyone is interested. The first is "BoundsChecker 5.0" for Visual C++ (NT/95) still shrinkwrapped (Hey... I am a Delphi programmer, why would I want this?). The second (also unused) is "Web Speed Workshop" by Progress Software, a development toolset for building Internet transaction processing applications. I will pay shipping on these to whoever gets them. Email me if you are interested.

March 18th, 1998

A bunch of great stuff today. Peter Larsen brings us two great pieces of code that work well together... (Yes, both a desert topping and a floor wax!!!). Check out the articles on creating shortcuts in Taking a Shortcut, and also getting information on Windows 95 special directories with his WinInfo Unit. There is also a nice demonstration program that illustrates their use.

Also, Angus Johnson brings us another interesting component showing how you can draw on the surface of a TEdit component. He uses this technique to create a simple button on the right side of the control. Handy for creating drop-down calendars, calculators, and so on! Take a look at this article on Drawing on a TEdit Component.

Want another great resource for Delphi tips & techniques? Many of you already know this, but Borland has a great page that has all sorts of technical documents on techniques for Delphi. Check it out!

March 17th, 1998

Over the past few days, I have received a bit of mail and sample components utilizing the technique first illustrated by Matt Hamilton earlier this month. Clinton Johnson brings a very complete solution to moving and resizing a control in his Sizeable/Moveable Panel component.

John Mertus also brings us a very interesting article on Using the Clipboard with WAV files. Now's your chance to use this interesting and powerful technique in your Delphi Apps!

March 10th, 1998

Thanks for all the great feedback on the March 6th article on sizing components. I can see we really sparked some creative juices out there! I am going to sort through what I have received on this and post them in the next few days.

Grahame Marsh has two new offerings today. The first is a way of Determining the CPUType in a machine including its vendor ID string. The second article is an interesting discussion of Compressing 256 color BMPs in your Delphi applications.

I also have updated and added new links to both the Tips & Tricks page and the Articles & Reviews page.

March 6th, 1998

Matt Hamilton brings us a truly sick and twisted technique for Moving and Sizing Components at Runtime. The basic behavior is one line of code. Everyone has to check this out, as it is terribly cool...

March 5th, 1998

The winners of the monthly UNDU prizes have been selected! In case everyone doesn't know what I am talking about... each month, I will take all the names of contributors to UNDU and put them in a hat. Then I will draw one name at random and that person will win some cool prize.

To inagurate the launching of the new UNDU site, I will be giving prizes to all contributors this past month. The first two winners are: Tex Murphy (for his article on Digital Sound) and Clint Johnson (for his discussion of Delphi 3's Code Parameters). They each win a copy of the Borland Developer's Collection CD Volume II, courtesy of The Cobb Group. Next, comes Tom Deprez (for his article on CD Change Notificaitons) who wins a one-year subscription to Delphi Informant magazine. Next, comes Lance Leonard (for his article on clipboard functions with the active control) who wins a copy of Creating Cool Web Applets with Java by Paul J. Perry. The fifth winner is Herbert Beemster (for his article on Giving Editboxes Some Attention) who wins a copy of Developing Active X Web Controls from Coriolis Books. And the last and final winner is Eddie Shipman (for his article on the Win95 FindFiles dialog) who wins a copy of Kick Ass Java Programming (also from Coriolis Books). If the winners would email me with their snail-mail addresses, I will send your prizes off! Congratulations to all of you and thank you again for your support of UNDU!

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 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.

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.


Borland Developer's Collection CD - Volume II

 The Unofficial Newsletter of Delphi Users - by Robert Vivrette

Borland Developer's Collection CD - Volume II

The prize is a copy of the Borland Developer's Collection CD, Volume II published by The Cobb Group. The CD contains all the past articles, figures, and source code from The Delphi Developer's Journal, C++ Builder Developer's Journal, Borland C++ Developer's Journal, Inside Visual dBASE, and Inside Paradox for Windows. The Cobb Group sponsors The Cobb Corner on Borland's Delphi site and the Delphi Tip of The Week. You're invited to sample a free issue of the Delphi Developer's Journal.

http://www.cobb.com/ddj (The Delphi Developer's Journal)
http://www.cobb.com/cpb/ (C++ Builder Developer's Journal)
http://www.borland.com/delphi/delcobb.html (The Cobb Corner)
http://www.zdtips.com/ddj/zdt-f.htm (Delphi Tip of The Week)
http://www.cobb.com/forms/ddj/cuw13m.htm (free issue)


Moving and Resizing Controls at Runtime

 The Unofficial Newsletter of Delphi Users - by Robert Vivrette

Moving and Sizing Controls at Runtime

by Matt Hamilton - MHamilton@bunge.com.au (and a little extra by Robert Vivrette)

After some experimentation with Captionless Forms and my custom panel components, I have found a (relatively) easy way to make your component movable at runtime. That is, when a user clicks and drags on your component, a "frame" appears just like when you move a window, so they can drag the component around.

The method involves catching a Windows Message called WM_NCHITTEST, which is passed to windows whenever a mouse operation (like moving the pointer or clicking) occurs on them. Let's go through the steps:

1. In the protected section of your component, add the following line:

    procedure NCHitTest(var Msg: TWMNCHitTest); message WM_NCHITTEST;
2. In the implementation section of the unit, define the method thusly:
    procedure TMyComponent.NCHitTest(var Msg: TWMNCHitTest);
    begin
       inherited;
       Msg.Result := HTCAPTION;
    end;
That's it! Now when you click on the component at runtime, you can drag it around the form. The next thing to do is test whether the mouse has been clicked on one particular section of your component (like a "title bar" area, for example). Redefine your definition like this:
    procedure TMyComponent.NCHitTest(var Msg: TWMNCHitTest);
    var
      rc: TRect;
      pt: TPoint;
    begin
      inherited;
      pt.x := Msg.XPos;
      pt.y := Msg.YPos;
      pt := ScreenToClient(pt);
      rc := Rect(0, 0, Width, 16);
      if PtInRect(rc, pt) then Msg.Result := HTCAPTION;
    end;
This will check that the user clicked somewhere within the top 16 pixels of your component. Otherwise, the usual stuff will happen (OnClick or whatever).

Keep coding!

Notes from the Editor:

When I first got this, I was reading through it and decided that no, it wasn't going to work. But sure enough... It did! Basically, this is just tricking the component into thinking that you are over its window banner area (HTCAPTION). All controls are basically "windows", it's just that most don't have banners like a form does. By overridding the WM_NCHitTest message, you are just telling the control "the mouse is now over your banner" and then all the default windows behavior kicks in for this condition.

For example, I tried this with a TEdit and it basically worked as advertised... but with a few undesireable exceptions. First, since it eats the mouse message, you can't click on such a control to give it focus (unless you modify the NCHitTest to shrink the size of the "caption" area as he did above). Also, since the component was now thinking that you were over its "banner", I wondered what would happen if I double-clicked on it. Sure enough... It maximizes the control to fill the form! Definitely got to stop that one... but it does open up some interesting possibilities.

I reviewed this message again and decided to try all the other possible message results (i.e. those other than HTCAPTION). If you open up the WIN32.HLP file and search on WC_NCHITTEST, you will see the other result flags that can be managed. For example, if you change HTCAPTION in the first example to HTBOTTOM, when the mouse moves into the control, it thinks that you are now on the bottom of the "window's" resize border and the double-headed arrow shows up. Any click and drag process within the control then resizes the bottom edge of the control. It is obvious that with just a few lines of code, you could enable all the behavior of a standard resizable window into just about any control.

For those of you who need a little help setting this up as a custom component, here is the entire code for what I did to experiment with his technique.

    unit Edit1;

    interface

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

    type
      TEdit1 = class(TEdit)
      protected
        procedure NCHitTest(var Msg: TWMNCHitTest); message WM_NCHITTEST;
      end;

    procedure Register;

    implementation

    procedure Register;
    begin
      RegisterComponents('Win32', [TEdit1]);
    end;

    procedure TEdit1.NCHitTest(var Msg: TWMNCHitTest);
    begin
       inherited;
       Msg.Result := HTCAPTION;
    end;

    end.

If you wanted to go a little further with this technique, you could add a property to a custom component that allows you to turn on and off movement and/or resizing. Then in your overridden NCHitTest method, you just test to see if it is enabled and only return the result if it is. You could then use this technique to allow users of your application to design the size and placement of controls on the form. Simply turn on a "design-mode" (which sets the Movable/Resizable property of the form's controls), and then let the user move and resize all the controls on the form. When he is done, he turns off the design-mode and your application can save the positions of the control to the registry or an INI file.

The possibilities are endless. I would love to see additional work on this by some enterprising reader out there. In fact, I have a few ideas of my own... stay tuned!


Authentic or Genuine?

 The Unofficial Newsletter of Delphi Users - by Robert Vivrette

Authentic or Genuine?

by Grahame Marsh - grahame.s.marsh@corp.courtaulds.co.uk

I recently upgraded my motherboard to a 233Mhz AMD processor. I knew that you could provoke an Intel chip into returning the text “GenuineIntel” in a series of registers so I was curious to know what the AMD chip would return. The answer proved to be the text “AuthenticAMD”:

This bitmap was clipped from a simple form which just has two labels that are initialised in the FormCreate event handler:

    procedure TForm1.FormCreate(Sender: TObject);
    begin
      Label1.Caption := 'Vendor: ' + VendorId;
      Label2.Caption := 'Type: ' + IntToStr (CpuType)
    end;
All you need to do to test this out is add the two lines above to your FormCreate, and add the CPUInfo unit to your Uses clause.

The VendorId and CpuType values are obtained using the CpuInfo unit listed below. Portions of this are based off of Intels published information on obtaining CPU ID's:

Enjoy!

    // obtain information on the CPU

    unit CpuInfo;

    interface

    type
      TFeatures = record
        case integer of
          0: (RegEAX,
              RegEBX,
              RegEDX,
              RegECX  : integer);
          1 : (I : array [0..3] of integer);
          2 : (C : array [0..15] of char);
          3 : (B : array [0..15] of byte)
      end;

    const
{$IFNDEF WIN32}
i8086       = 1; // includes
8088/V20/V30/80186/80188
i80286      = 2;
i80386      = 3;
{$ENDIF}
i80486      = 4;
Chip486     = 4;
iPentium    = 5;
Chip586     = 5;
iPentiumPro = 6;
Chip686     = 6;

    // known vendor id strings:
      Intel = 'GenuineIntel';
      AMD   = 'AuthenticAMD';

    var
      CpuType   : byte = 0;         // type of CPU
      VendorId  : string [12]= '';  // contains vendor string
      Features  : TFeatures;        // contains signature and feature information

    procedure LoadFeatures (I : integer);

    implementation

    {$O-}
    const
      CpuId = $0a20f;  // hardcoded CpuId instruction
                       // not supported by inline assembler
    var
      CpuIdFlag : boolean = false;  // true if CpuId instruction works
      MaxCPUId  : integer;          // contains max value of eax value for CpuId instruction

    // procedure to load features from CPUId instruction
    // call with eax containing CPUId instruction number
    procedure GetF;
    asm
             dw        CpuId           // Hardcoded CPUID instruction
             mov       [Features.RegEAX], eax
             mov       [Features.RegEBX], ebx
             mov       [Features.RegECX], ecx
             mov       [Features.RegEDX], edx
    end;

    // procedure to clear features record to zero
    procedure ClearF;
    asm
             mov       edi, offset Features  // edi point at features record
             xor       eax, eax              // clear eax
             mov       ecx, eax
             mov       cl, 4                 // 4 double words, 16 bytes
             cld                             // forward direction
             rep       stosd                 // do it
    end;

    // This procedure determines the type of processor in a system
    // and sets the CpuType variable with the appropriate
    // value. If the CPUID instruction is available, it is used
    // to determine more specific details about the processor.
    // All registers are used by this procedure, none are preserved.
    // To avoid AC faults, the AM bit in CR0 must not be set.

    procedure CheckOutCpu;
    asm
    {$IFNDEF WIN32}
    // 8086 processor check
    // Bits 12-15 of the FLAGS register are always set on the
    // 8086 processor.
             pushf                     // push original FLAGS
             pop       ax              // get original FLAGS
             mov       cx, ax          // save original FLAGS
             and       ax, 0fffh       // clear bits 12-15 in FLAGS
             push      ax              // save new FLAGS value on stack
             popf                      // replace current FLAGS value
             pushf                     // get new FLAGS
             pop       ax              // store new FLAGS in AX
             and       ax, 0f000h      // if bits 12-15 are set, then
             cmp       ax, 0f000h      // processor is an 8086
             mov       [CPUType], 1    // set flag to 8086/8088
             je        @@2             // jump if processor is 8086/8088

    // 286 processor check
    // Bits 12-15 of the FLAGS register are always clear on the
    // 286 processor in real-address mode.
             or        cx, 0f000h      // try to set bits 12-15
             push      cx              // save new FLAGS value on stack
             popf                      // replace current FLAGS value
             pushf                     // get new FLAGS
             pop       ax              // store new FLAGS in AX
             and       ax, 0f000h      // if bits 12-15 are clear
             mov       [CPUType], 2    // processor=80286, turn on 80286 flag
             jz        @@2             // if bits set, processor is 80286

    // 386 processor check
    // The AC bit, bit #18, is a new bit introduced in the EFLAGS
    // register on the 486 processor to generate alignment faults.
    // This bit cannot be set on the Intel386 processor.
             pushfd                    // push original EFLAGS
             pop       eax             // get original EFLAGS
             mov       ecx, eax        // save original EFLAGS
             xor       eax, 40000h     // flip AC bit in EFLAGS
             push      eax             // save new EFLAGS value on stack
             popfd                     // replace current EFLAGS value
             pushfd                    // get new EFLAGS
             pop       eax             // store new EFLAGS in EAX
             xor       eax, ecx        // can't toggle AC bit, processor=80386
             mov       [CPUType], 3    // turn on 80386 processor flag
             jz        @@2             // jump if 80386 processor
             push      ecx
             popfd                     // restore AC bit in EFLAGS first
    {$ENDIF}

    // 486 processor check:
    // Checking for ability to set/clear ID flag (Bit 21) in EFLAGS
    // which indicates the presence of a processor with the CPUID instruction.
             mov       [CPUType], 4    // turn on 80486 processor flag
             mov       eax, ecx        // get original EFLAGS
             xor       eax, 200000h    // flip ID bit in EFLAGS
             push      eax             // save new EFLAGS value on stack
             popfd                     // replace current EFLAGS value
             pushfd                    // get new EFLAGS
             pop       eax             // store new EFLAGS in EAX
             xor       eax, ecx        // can't toggle ID bit,
             je        @@2             // processor=80486

    // higher than 486 check:
    // Execute CPUID instruction to determine vendor, family,
    // model, stepping and features. For the purpose of this
    // code, only the initial (0) set of CPUID information is saved.
             mov       [CPUIdFlag], 1  // flag indicating use of CPUID inst.
             push      ebx             // save ebx register
             mov       eax, 0          // set up for CPUID instruction
             dw        CpuId           // Hardcoded CPUID instruction
             mov       [MaxCPUId], eax // keep max value
    // get and save vendor ID
             mov       [byte ptr VendorId], 12
             mov       [dword ptr VendorId+1], ebx
             mov       [dword ptr VendorId+5], edx
             mov       [dword ptr VendorId+9], ecx

    // get family/model/stepping/features
             call      ClearF
             mov       eax, 1
             call      GetF            // get feature stuff
             shr       eax, 8          // isolate family
             and       eax, 0fh
             mov       [CPUType], al   // set CPUType with family
    @@1:     pop       ebx             // restore ebx register
    @@2:
    end;

    // requires that a call has been made to CpuId procedure to set flags correctly
    procedure LoadFeatures (I : integer);
    asm
             call      ClearF
             cmp       [CpuIdFlag], 0  // test if CPUId instruction valid
             je        @@1
             mov       eax, [I]        // test if argument in range
             cmp       [MaxCpuId], eax
             jl        @@1
             call      GetF            // ok let's do it
    @@1:
    end;

    initialization
      CheckOutCPU;
    end.


Compressing Bitmaps

The Unofficial Newsletter of Delphi Users - by Robert Vivrette

Compressing 256 colour bitmaps

by Grahame Marsh - GSMarsh@aol.com

The general tools for saving windows bitmaps to disc provided with Delphi save the images in an uncompressed format. This format, known as RGB encoded can be very greedy on system resources, especially disc space and memory to load. But the windows specification for bitmaps includes another encoding scheme known as run length encoding or RLE. For bitmaps that contain areas of the same colour, the technique can save huge amounts of resources, and savings are even possible on complex images as I will show.

Consider this 16 by 16 bitmap:

it uses 256 bytes to store the colour information (together with a bunch of file and format information), but with RLE the information can be stored in just 66 bytes!

I won't go into how RLE works, as you can easily read about it in win32.hlp if you search on "RLE". What I want to show you is a simple method of incorporating RLE into your own code and how to compress images for use in splash screens etc.

Part of Windows 95 apparently not implemented by Borland is the bitmap compression and decompression routines held in msvfw32.dll. The dll contains a great deal of useful stuff but I'll just look at four functions and some constants and a record:

    const
      ICMODE_COMPRESS = 1;
      ICTYPE_VIDEO = ord ('v') +
                     ord ('i') shl  8 +
                     ord ('d') shl 16 +
                     ord ('c') shl 24;

    function ICLocate (fccType, fccHandler: DWORD; lpbiIn, lpbmOut : PBitmapInfoHeader;
    wFlags: word): THandle; stdcall; external 'msvfw32.dll' name 'ICLocate';

    function ICImageCompress (Handle: DWORD; uiFlags: UINT; lpbiIn: PBitmapInfo;
      lpBits: pointer; lpbiOut: PBitmapInfo; lQuality: integer; plSize: PInteger): THandle;
      stdcall; external 'msvfw32.dll' name 'ICImageCompress';

    type
      PICInfo = ^TICInfo;
      TICInfo = packed record
        dwSize,
        fccType,
        fccHandler,
        dwFlags,
        dwVersio,
        dwVersionICM : DWORD;
        szName : array [0..15] of wchar;
        szDescription : array [0..127] of wchar;
        szDriver : array [0..127] of wchar;
      end;

    function ICGetInfo (Handle: THandle; var ICInfo: TICInfo; cb: DWORD): LRESULT;
      stdcall; external 'msvfw32.dll' name 'ICGetInfo';

    function ICClose (Handle: THandle): LRESULT;
      stdcall; external 'msvfw32.dll' name 'ICClose';

The key function here is ICImageCompress (suprise, suprise), it is used like this:

    function ICImageCompress (
      Handle: DWORD;               // the handle to a compressor
      uiFlags: UINT;               // flags - unused
      lpbiIn: PBitmapInfo;         // the bitmap information that you want to compress
                                   // ie the RGB encoded bitmap
      lpBits: pointer;             // the data that represents the pixels
      lpbiOut: PBitmapInfo;        // the bitmap you want to got to ie RLE
      lQuality: integer;           // a quality factor (0 to 10000)
      plSize: PInteger)            // this returns the number of bytes in the new data
          : THandle;                  // a handle to a global memory block containing the
                                   // data is returned

The handle for the compressor is obtained using the ICLocate function, ICGetInfo is used to obtain information on the compressor (such as a name, description, the driver name and where it's located), and finally ICClose is used to dispose of the Handle when we are finished.

Below, you will see a screen shot of a small demo program I put together to illustrate this technique. You can load a file into the left hand pane and compress it into another file in the righthand pane. The quality slider allows you to see how the compressor trades-off file size for the quality of the final image. If you want to download the unit for this project, you can click here.

Not within the ICxxxx calls (or at least I couldn't find it) is compression of the palette. Not all images use all 256 colours and the unused information can be stripped out to save a little more space (but only 4 bytes per unused palette entry). This doesn't matter too much on large images like those above, but is very useful on small images where the 1024 bytes used to store the colour information can become significant. You can turn this feature on and off to see the effect on space.

Turning quality to zero on the "handshake" bitmap reduces the filesize to 25122 bytes which is getting down toward saving half the space on the original image.

To give you one more example, the large bitmap above has a file length of 299002 bytes, compressed at 85% quality (which more-or-less is identical with the original) it now occupies 96718 bytes - about a third of the original!

In these days of 20 K Delphi 3 applications (like this one), it can seem silly to have a 50K graphic on a splash screen. This utility will I hope allow you to manage with a 25K splash!

Cheers
Grahame


Extending the Sizeable/Moveable Component Technique

 The Unofficial Newsletter of Delphi Users - by Robert Vivrette

Extending the Sizeable/Moveable Component Technique

by Clinton R. Johnson - xepol@poboxes.com

Last week, there was an interesting offer from Matt Hamilton on how to make virtually any component moveable or resizable. Since then I have received quite a bit of excellent suggestions on how to extend these capabilities. Clinton Johnson offers this use of the technique in his Sizeable/Moveable Panel demo.

In a nutshell, he did the following... First, he added AllowMove and AllowResize properties to control whether the panel could be moved or sized. In the event that a move area needed to be established, he added a CaptionHeight property. This property controls the size of the region where you can click and drag to move the component. Next, he included a set property called ResizeDirection, allowing the user to control which edges of the control could be resized. He then added events that could be overridden for OnCanMove or OnCanResize. Finally, he implemented resizing restrictions using the WM_GETMINMAXINFO message. This allows the user to specify the maximum or minimum size that the panel can be.

The code is well commented, and will illustrate well the steps you can take to add resizing or moving control to your own components.

All-in-all a very complete solution. Well done Clinton!

unit SMPanel;

interface

//*****************************************************************************
// Sizable/Movable Panel Demo prepared for UNDO
//
// Written by Clinton R. Johnson - xepol@poboxes.com
//
// Based on an article by M. Hamilton - MHamilton@bunge.com.au
(and a little extra
// by Robert Vivrette)
//
// Fully extends the concept of resizing, and provides
the fundamentals required to
// include this code in any component.  Minimum
and maximum window sizes are enforced.
// Moving the mouse over the regions which cause
resizing also cause the cursor to
// change to the appropriate cursor shape.
//
// This code is released into the public domain.
//*****************************************************************************

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

Type
  TResizeDirection = (rdNone,rdTop,rdBottom,rdLeft,rdRight,rdTopLeft,rdTopRight,rdBottomLeft,rdBottomRight);
  TResizeDirectionSet = Set Of TResizeDirection;

Type
TNotifyMoveEvent = Procedure (Sender : TObject;
X: Integer; Y : Integer; Var CanMove : Boolean) Of Object;
TNotifyResizeEvent = Procedure (Sender : TObject;
ResizeDirection : TResizeDirection; Var CanResize : Boolean) Of Object;

type
  TSMPanel = class(TPanel)
  private
    { Private declarations }
    fAllowMove          : Boolean;
    fAllowResize        : Boolean;
    fCaptionHeight      : Integer;
    fMaxHeight          : Integer;
    fMaxWidth           : Integer;
    fMinHeight          : Integer;
    fMinWidth           : Integer;
    fOnCanMove          : TNotifyMoveEvent;
    fOnCanResize        : TNotifyResizeEvent;
    fResizeDirection    : TResizeDirectionSet;

    Procedure WMGetMinMaxInfo(Var Msg : TWMGetMinMaxInfo); Message WM_GetMinMaxInfo;
    Procedure WMNCHitTest(var Msg : TWMNCHitTest); Message WM_NCHITTEST;
    Procedure WMMouseMove(Var Msg : TWMMouseMove); Message WM_MouseMove;
    Procedure TestHit(XPos : INteger; YPos : INteger; Var Result : INteger);
  protected
    { Protected declarations }
    Procedure DoCanMove(X : Integer; Y : Integer; Var CanMove : Boolean); Virtual;
    Procedure DoCanResize(ResizeDirection : TResizeDirection; Var CanResize : Boolean); Virtual;
  public
    { Public declarations }
    Constructor Create(AnOwner : TComponent); OverRide;
  published
    { Published declarations }
    Property AllowMove       : Boolean             Read fAllowMove       Write fAllowMove;
    Property AllowResize     : Boolean             Read fAllowResize     Write fAllowResize;
    Property CaptionHeight   : Integer             Read fCaptionHeight   Write fCaptionHeight;
    Property MaxHeight       : Integer             Read fMaxHeight       Write fMaxHeight;
    property MaxWidth        : Integer             Read fMaxWidth        Write fMaxWidth;
    Property MinHeight       : Integer             Read fMinHeight       Write fMinHeight;
    property MinWidth        : Integer             Read fMinWidth        Write fMinWidth;
    Property ResizeDirection : TResizeDirectionSet Read fResizeDirection Write fResizeDirection;

    Property OnCanMove : TNotifyMoveEvent          Read fOnCanMove       Write fOnCanMove;
    Property OnCanResize : TNotifyResizeEvent      Read fOnCanResize     Write fOnCanResize;
  end;

procedure Register;

implementation

Constructor TSMPanel.Create(AnOwner : TComponent);
Begin
  Inherited Create(AnOwner);
  fAllowMove := True;
  fAllowResize := True;
  fCaptionHeight := 0;
  fMaxHeight := 0;
  fMaxWidth := 0;
  fMinHeight := 1;
  fMinWidth := 1;
  fResizeDirection := [rdTop,rdBottom,rdLeft,rdRight,rdTopLeft,rdTopRight,rdBottomLeft,rdBottomRight];
End;

Procedure TSMPanel.DoCanMove(X : Integer; Y : Integer; Var CanMove : Boolean);
Begin
  If Assigned(FOnCanMove) Then fOnCanMove(Self,X,Y,CanMove);
End;

Procedure TSMPanel.DoCanResize(ResizeDirection : TResizeDirection; Var CanResize : Boolean);
Begin
  If Assigned(fOnCanResize) Then fOnCanResize(Self,ResizeDirection,CanResize);
End;

Procedure TSMPanel.WMGetMinMaxInfo(Var Msg : TWMGetMinMaxInfo);
Var
P
: TPoint;
Begin
Inherited;
Msg.MinMaxInfo^.ptMinTrackSize := Point(MinWidth,MinHeight);
P := Msg.MinMaxInfo^.ptMaxTrackSize;
If (MaxWidth<>0) Then P.X := MaxWidth;
If (MaxHeight<>0) Then P.Y := MaxHeight;
Msg.MinMaxInfo^.ptMaxTrackSize := P;
End;

Procedure TSMPanel.TestHit(XPos : INteger; YPos : INteger; Var Result : INteger);
Var
  BottomB               : Boolean;
  CanMove               : Boolean;
  CanResize             : Boolean;
  LeftB                 : Boolean;
  RightB                : Boolean;
  TopB                  : Boolean;
  BWidth                : Integer;
  Pt                    : TPoint;
  Client                : TRect;
  Direction             : TResizeDirection;
Begin
//*****************************************************************************
// We don't want to interfere with the IDE, no design time.
//*****************************************************************************
  If Not (csDesigning In ComponentState) Then
  Begin
//*****************************************************************************
// X&Y come in as screen co-ordinates.  Switch to Client for easier
// manipulation.
//*****************************************************************************
    Pt := ScreenToClient(Point(XPos,YPos));
//*****************************************************************************
// We'll consider the bevel & border areas the area for resizing here.
//*****************************************************************************
    BWidth := BorderWidth;
    If (BevelInner<>bvNone) Then BWidth := BWidth+BevelWidth;
    If (BevelOuter<>bvNone) Then BWidth := BWidth+BevelWidth;
//*****************************************************************************
// Set the client area so we can test later for dragging.
//*****************************************************************************
    Client := Rect(0,0,Width,Height);
    If AllowResize Then
    Begin
//*****************************************************************************
// See where the mouse is in the resizing areas.
//*****************************************************************************
      TopB := (Pt.Y<BWidth);
      LeftB := (Pt.X<BWidth);
      RightB := (Pt.X>=(Width-BWidth));
      BottomB := (Pt.Y>=(Height-BWidth));
//*****************************************************************************
// Adjust the client area for dragging to NOT include any valid direction
// for resizing.
//*****************************************************************************
      If ((fResizeDirection * [rdTop,rdTopLeft,rdTopRight])<>[]) Then
      Begin
        Client.Top := Client.Top+BWidth;
      End;
      If ((fResizeDirection * [rdBottom,rdBottomLeft,rdBottomRight])<>[]) Then
      Begin
        Client.Bottom := Client.Bottom-BWidth;
      End;
      If ((fResizeDirection * [rdLeft,rdTopLeft,rdBottomLeft])<>[]) Then
      Begin
        Client.Left:= Client.Left+BWidth;
      End;
      If ((fResizeDirection * [rdRight,rdTopRight,rdBottomRight])<>[]) Then
      Begin
        Client.Right := Client.Right-BWidth;
      End;
//*****************************************************************************
// Determine exactly what direction we need to resize in.
// Bottom & right are given preference here.  This is so that if the client
// area is shrunk to its smallest, the object will always grow down and to the
// right.
//
// rdNone is primarily as semaphore for choosing a direction here, but if
// it is included, then all resizing is turned off, regardless of how all the
// rest of the flags are set.
//
//*****************************************************************************
      Direction := rdNone;
      If Not (rdNone IN fResizeDirection) Then
      Begin
        If (rdTop In fResizeDirection) And TopB Then Direction := rdTop;
        If (rdLeft In fResizeDirection) And LeftB Then Direction := rdLeft;
        If (rdBottom In fResizeDirection) And BottomB Then Direction := rdBottom;
        If (rdRight In fResizeDirection) And RightB Then Direction := rdRight;
        If (rdTopLeft In fResizeDirection) And (TopB And LeftB) Then Direction := rdTopLeft;
        If (rdTopRight In fResizeDirection) And (TopB And RightB) Then Direction := rdTopRight;
        If (rdBottomLeft In fResizeDirection) And (BottomB And LeftB) Then Direction := rdBottomLeft;
        If (rdBottomRight In fResizeDirection) And (BottomB And RightB) Then Direction := rdBottomRight;
      End;
//*****************************************************************************
// If the resize direction is not blank, then ask the code using this
// component to validate the resize - this allows the component's
// resizing to be controlled in a more flexible manner.
//*****************************************************************************
      CanResize := (Direction<>rdNone);
      If CanResize Then DoCanResize(Direction,CanResize);
//*****************************************************************************
// Test the resizing.  Only allow resizing in allowed directions.
// Bottom & right are given preference here again.
//*****************************************************************************
      If CanResize Then
      Begin
        If (rdTop=Direction)         Then Result := HTTOP;
        If (rdLeft=Direction)        Then Result := HTLEFT;
        If (rdBottom=Direction)      Then Result := HTBOTTOM;
        If (rdRight=Direction)       Then Result := HTRight;
        If (rdTopLeft=Direction)     Then Result := HTTOPLEFT;
        If (rdTopRight=Direction)    Then Result := HTTOPRight;
        If (rdBottomLeft=Direction)  Then Result := HTBOTTOMLEFT;
        If (rdBottomRight=Direction) Then Result := HTBOTTOMRIGHT;
      End;
    End;
//*****************************************************************************
// If the CaptionHeight is non-zero, the user only wants part of the height
// of the control to be used as a drag area.
//*****************************************************************************
    If (CaptionHeight<>0) Then
    Begin
      Client.Bottom := Client.Top+CaptionHeight;
    End;
//*****************************************************************************
// Test if Moving is permitted, and the point is in the client area for moving.
//*****************************************************************************
    CanMove := AllowMove And PtInRect(Client,Pt);
//*****************************************************************************
// Always leave the Final decision to move to the code using the component.
// It can change a false to a true, and vice versa.  This allows people
// to refind the caption area to a greater detail.
//*****************************************************************************
    DoCanMove(Pt.X,Pt.Y,CanMove);
    If CanMove Then Result := HTCAPTION;
  End;
End;

procedure TSMPanel.WMNCHitTest(var Msg: TWMNCHitTest);
Begin
  Inherited;
  TestHit(Msg.XPos,Msg.YPos,Msg.Result);
End;

procedure TSMPanel.WMMouseMove(var Msg : TWMMouseMove);
Begin
  Inherited;
  TestHit(Msg.XPos,Msg.YPos,Msg.Result);
End;

procedure Register;
begin
  RegisterComponents('Samples', [TSMPanel]);
end;

end.


The Clipboard and WAV Files

 The Unofficial Newsletter of Delphi Users - by Robert Vivrette

The Clipboard and WAV files

by John Mertus - John_Mertus@Brown.EDU

Delphi programmers can use the CLIPBRD unit to copy and paste text and graphics to and from the Windows Clipboard. However this unit gives no method to copy and paste WAV sound files. Luckily, it is not very hard to implement the functions. There are various functions for inquiring about the clipboard, more information than what is presented can be found the the WIN32.HLP help file distributed with Delphi 2 and 3.

Brief Overview of the Clipboard's Innards

The clipboard is really just a list of global memory handles and formats; for example, a text is stored as a pointer to a null terminated string and has the attached format CF_TEXT, a bitmap file is stored as a global handle with format CF_BITMAP. These formats are predefined in the WINDOWS unit of Delphi. Formats are not restricted to these predefined ones, one can resister a custom format with the clipboard. Delphi does that with the CF_PICTURE format. In the case of WAV files, there is already a predefined format CF_WAVE.

Although there can only be one memory pointer with a given format, there may be multiple formats in the clipboard at the same time. Thus the clipboard can have text, bitmap and a sound in it all at once. It is important to understand that the clipboard itself does not understand these formats and does no checking to see if the data is in that format. To belabor the point, the clipboard does not know the structure of a bitmap, or the structure of a sound file. It just knows that a certain memory handle has a certain number(format) attached to it. It is up to application programs to understand the individual formats.

Lastly, it is the application that allocates the memory necessary to copy data to the clipboard. But after the data is sent, the clipboard owns the memory and will dispose it it when its done. An application program never disposes of the clipboard's predefined format memory handle!

Copying a sound file to the Clipboard

With this understanding, copying a sound to the clipboard is quite simple.

    1. Allocate the correct amount and type of memory.
    2. Copy the sound file into that memory.
    3. Send the data to the clipboard with format CF_WAVE.
However, we must be careful because we are allocating global memory, if we don't check on all operations and release the memory on any errors, we could find ourselves eating system resources.

The following snipets of code show how to copy a WAV file, in this case called JUNK.WAV to the clipboard. First we must find how much memory to allocate.

    Var
      fh, mh    : THandle;
      DataPtr   : PChar;

      fh := FileOpen('JUNK.WAV', fmOpenRead or fmShareDenyNone);
      If (fh <= 0) Then Exit;
      size := GetFileSize(fh, nil);
      If (Size = -1) Then Exit;

Next we allocate the memory, this must be Moveable and DDE shareable.
       mh := GlobalAlloc(GMEM_MOVEABLE + GMEM_DDESHARE, Size);
      If (mh = 0) Then Exit;                // Memory alloc failed
Now read the sound file into memory, when done, close the file.
      DataPtr := GlobalLock(mh);
      If (FileRead(fh, DataPtr^, Size) <> Size) Then
        Begin
          GlobalFree(mh);           // If we want to abort the process we must release
          FileClose(fh);            // the memory
          Exit;
        End;
      FileClose(fh);
Finally post the data to the clipboard. To do this, we must open the clipboard; this prohibits any other program from interfering with the clipboard while we are working on it.
    If (OpenClipboard(Application.Handle) Then
      Begin
        EmptyClipBoard            // This will delete anything in the clipboard
        If (SetClipboardData(CF_WAVE, Data) = Nil) Then
          Begin
            CloseClipBoard;        // We have failed, release everything.
            GlobalFree(mh);
            Exit;
          End;

        CloseClipboard;            // Success,
        GlobalUnlock(Data);       // Notice we unlock the data, we do no free it.
      End;
A few comments, notice that we really didn't do much checking that the JUNK.WAV was a sound file, it could have been any old file. This shows that just because data has a the format CF_WAVE, does not necessarily mean the data is valid sound data. Thus when reading the file, always check if it is valid.

A few comments about WAV files.

WAV files evolved over many years starting with the simple format of a header followed by data. Later it changed into a general purpose file, involving chunks of data which can contain information besides sound information. Many programs deal with a wave file as a header followed by data. This greatly simplifies the programming necessary to deal with these files and will work when there is only sound data in the file. However, the correct way of dealing with sound files is to open them as multi-media files using the mmioXXXX routines. The next sections, we will treat sound files as header followed by data; the very last section will show how to open the clipboard as a multi-media file.

Playing out the Clipboard.

A sound in the clipboard is nothing but a memory image of a WAV file. It is very simple to play out a WAV file memory image using playsound. The following code shows how this is done. First we check to see if there is a WAV format in the clipboard, this is done by using the IsClipboardFormatAvailable function.

    If IsClipboardFormatAvailable(CF_WAVE) Then    // see if format is CF_WAVE
      If (OpenClipboard(Application.Handle)) Then
       Begin
         mh := GetClipBoardData(CF_WAVE);      // Return is memory handle to sound
         If (mh <> 0) Then
           Begin
             fp := GlobalLock(mh);              // Make it a pointer
             PlaySound(fp, 0, SND_MEMORY);      // Play out the sound from memory
             GlobalUnLock(mh);
          End;
         CloseClipBoard;
       End;
A few comments. What happens if the clipboard data is not really a wave format? In that case, PlaySound will fail gracefully. Thus we really don't care about checking carefully the code. Also, the sound can be played SND_ASYNC; however be aware that the program must unlock the memory and close the clipboard after being done. If you do not, other programs cannot copy to the clipboard.

Reading the Clipboard.

If one is confident or assumes the sound in the clipboard is a valid WAV file, then reading the clipboard can be done by just writing the memory out into a file. The only real trick involved is getting the size of the sound.

    Var
      fp, wp : PChar;
      mh, fh : THANDLE;
      SizeF  : LongInt;

    If IsClipboardFormatAvailable(CF_WAVE) Then
// once again see if format is there
If (OpenClipboard(Application.Handle)) Then
Begin
mh := GetClipBoardData(CF_WAVE);
// we should check for valid returns
fp := GlobalLock(mh);
// but this is just a demo

          { Find the Size of
the file }
wp := fp;
inc(wp,4);
// The 4 byte of a WAV
Move(wp^,SizeF,4);
// file gives the remaining size
SizeF := SizeF + 8;
// Add 8 to get total file size

          { Now create the file
}
fh := FileCreate(fname);
If (fh <= 0) Then
Begin
CloseClipBoard;
GlobalUnLock(mh)
Exit;
End;
FileWrite(fh, fp^,
SizeF);           //
Just write it out
FileClose(fh);
GlobalUnlock(mh);
CloseClipBoard;
End;

This in fact does not really check to see if the clipboard has a valid WAV file. In fact the writing may fail if the 2an long word does not contain the length of the file.

Opening the Clipboard as an Multi-Media File

As said earlier, the best way to read a WAV file is through the multi-media interface. I will briefly describe a way of opening the clipboard as a multimedia file. Once the file is open, the usual mmioXXX routines can be called to process the data chunks.

First, the mmioOpen function uses a MMIOINFO structure. Depending upon the setting of this structure, mmioOpen can be told to use special I/O handlers, even user defined ones. Windows provides a predfined memory I/O handler which is what we will use.

To make the code more readable, assume that the clipboard has been opened and checked for CF_WAVE data; moreover, we will not check for invalid returns.

    var
       fp, wp   : PChar;
       mh       : THANDLE;
       mmioinfo : TMMIOINFO;
Get a pointer to the clipboard in the usual way.
       mh  := GetClipBoardData(CF_WAVE);
       fp := GlobalLock(mh);
First we must define the mmioinfo structure to use memory, this is done by
       fillchar(mmioinfo, sizeof(TMMIOINFO), 0);    // All other data values are 0
       mmioinfo.fccIOProc := FOURCC_MEM;            // Use MEMORY as the I/O handler
       mmioinfo.pchBuffer := fp;                    // This is the address
The last piece of the mmioinfo structure needs is the size of the memory,
       wp := PByte(fp);
       inc(wp,4);
       Move(wp^,mmioinfo.cchBuffer,4);
       mmioinfo.cchBuffer := mmioinfo.cchBuffer + 8;
Structure is set up, we can not open it
       mmfp := mmioOpen(Nil, @mmioinfo, MMIO_READ);
If mmfp is not 0, then the clipboard has been opened as a multi-media WAV file. You can use all the mmio calls but just remember to close mmfp and to unlock the memory.

Being a good Neighbor

The clipboard is designed to exchange data between programs. The OpenClipboard and CloseClipboard functions are used to synchronize reading/writing to the clipboard. Thus, unlike a file, one should not keep the clipboard open for a long time. Open it, process the data, and close it quickly.

Unit and Demo Programs.

The URL http://www.cog.brown.edu/~mertus/components.html contains a ZIP file which contains a unit for copying and pasting waveform from the clipboard together with a small demonstration program.


Drawing on a TEdit Component

 The Unofficial Newsletter of Delphi Users - by Robert Vivrette

Drawing on a TEdit Component

by Angus Johnson - ajohnson@rpi.net.au

I started this component - as an exercise - to create my own DatePicker component (still using Delphi 2.01) based on the TDateEdit component first published in UNDU way back in 1995! TButtonEdit just draws a button on the TEdit surface. Clicking the button initiates an OnClick event. All you have to do is write the OnClick method!

I initially wrote the WM_PAINT method using API functions - to avoid some of the issues of using TCanvas but Robert Vivrette (what a kind fellow) thought the code may be simpler using a TCanvas object to draw the button. Well, I've rewritten WM_PAINT but left the old API code (commented out) at the end so you can decide. Why do things the easy way when you've already done it the hard way?

Editors Note: Both techniques are fine, however, we need to remember that Delphi does a lot of caching of Pens, Brushes, etc to optimize the performance of things like this. As a result, I suspect that the Delphi implementation might be a little faster as well as being easier to code and maintain.

Created on 5 March 1998, modified 18 March 1998.

unit ButtonEdit;

interface

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

type
TButtonEdit = class(TEdit)
private
//FCanvas: TCanvas; //commented
out - see below!
//do you want to 'click' when
the up or down arrow key pressed as well?
FClickOnArrow: boolean;
//do you want to 'click' when
the Return key pressed as well?
FClickOnReturn: boolean;
//flag - is the button pressed
or not
FPressed: boolean;
procedure Click; override;
procedure CreateWnd; override;
procedure WMPAINT(var Message:
TMessage); message WM_PAINT;
procedure WMLBUTTONDOWN(var Message:
TWMMouse); message WM_LBUTTONDOWN;
procedure WMLBUTTONUP(var Message:
TWMMouse); message WM_LBUTTONUP;
procedure WMMOUSEMOVE(var Message:
TWMMouse); message WM_MOUSEMOVE;
//procedure WMSETFOCUS(var Message:
TMessage); message WM_SETFOCUS;
protected
procedure KeyDown(var Key: Word;
Shift: TShiftState); override;
public
constructor Create(AOwner: TComponent);
override;
published
property ClickOnArrow: boolean
read FClickOnArrow write FClickOnArrow;
property ClickOnReturn: boolean
read FClickOnReturn write FClickOnReturn;
end;

procedure Register;

implementation

const
  BUTTONWIDTH = 17;

{----------------------------------------------------------------------}
{----------------------------------------------------------------------}

procedure Register;
begin
  RegisterComponents('Samples', [TButtonEdit]);
end;
{----------------------------------------------------------------------}

constructor TButtonEdit.Create(AOwner: TComponent);
begin
  inherited Create(AOwner);
  FPressed := false;
  FClickOnArrow := true;
  FClickOnReturn := false;
end;
{----------------------------------------------------------------------}

procedure TButtonEdit.CreateWnd;
begin
inherited CreateWnd;
//this is crucial to stop text disappearing
under the button...
perform(EM_SETMARGINS,EC_RIGHTMARGIN,(BUTTONWIDTH+2)
shl 16);
end;
{----------------------------------------------------------------------}

procedure TButtonEdit.WMLBUTTONDOWN(var Message: TWMMouse);
begin
inherited;
//draw button in pressed state...
if message.xpos >= clientwidth-BUTTONWIDTH+1
then begin
FPressed := true;
Refresh;
end;
end;
{----------------------------------------------------------------------}

procedure TButtonEdit.WMLBUTTONUP(var Message: TWMMouse);
begin
  inherited;
  //draw button in non-pressed state...
  if FPressed then begin
    FPressed := false;
    Refresh;
  end;
end;
{----------------------------------------------------------------------}

procedure TButtonEdit.WMMOUSEMOVE(var Message: TWMMouse);
begin
inherited;
//change cursor when over the button to an
arrow (not the default I-beam)...
if message.xpos >= clientwidth-BUTTONWIDTH+1
then cursor := crArrow
else cursor := crDefault;
end;
{----------------------------------------------------------------------}

procedure TButtonEdit.Click;
var
pt: TPoint;
begin
//fix a minor cosmetic problem...
if FPressed then begin
FPressed := false;
Repaint;
end;
//Only process an OnClick method if the button
is clicked,
//NOT if the text is clicked!
GetCursorPos(pt);
if PtInRect(Rect(clientwidth-BUTTONWIDTH+1,0,clientwidth,clientheight),
ScreenToClient(pt)) then inherited
Click;
end;
{----------------------------------------------------------------------}

procedure TButtonEdit.KeyDown(var Key: Word; Shift: TShiftState);
begin
  //respond to up or down arrow keys or Return key with OnClick event if
  //"ClickOnArrow" or "ClickOnReturn" property set...
  inherited KeyDown(Key, Shift);
  if ((Key = vk_Down) or (Key = vk_Up))
      and (Shift = []) and FClickOnArrow then begin
    Key := 0;
    inherited Click;
    end
  else if (Key = vk_return) and FClickOnReturn then begin
    Key := 0;
    inherited Click;
  end;
end;
{----------------------------------------------------------------------}

//This no longer seems to be necessary ... I've left
it here just in case!
{procedure TButtonEdit.WMSETFOCUS(var Message: TMessage);
begin
inherited;
repaint;
end;}
{----------------------------------------------------------------------}

procedure TButtonEdit.WMPAINT(var Message: TMessage);
var
  dc: HDC;
  CntrPt: TPoint;
  pic: array [0..3] of TPoint; //arrow 'picture' points
begin
  // let windows draw the text!
  // I don't really want to struggle with all the scrolling issues etc!
  inherited;

  //NOW DRAW THE BUTTON ... (not as bad as it looks if you take out the comments!)

  //find the centre of the button...
CntrPt := point(clientwidth - BUTTONWIDTH
div 2, clientheight div 2);
//offset CntrPt by 1 if pressed...
if FPressed then CntrPt := point(CntrPt.x+1,CntrPt.y+1);
//get button arrow drawing coordinates from
CntrPt...
pic[0] := point( CntrPt.x-5,CntrPt.y);
pic[1] := point( CntrPt.x,CntrPt.y-5);
pic[2] := point( CntrPt.x+5, CntrPt.y);
pic[3] := point( CntrPt.x, CntrPt.y+5);

  //Notes:
//1. As I'm calling the inherited WMPAINT
method before drawing the button -
//  I have to use getDC(handle) instead
of beginpaint(handle,paintstruct)
//  otherwise I don't see the button!
(I think due to clipping.)
//2. If I wanted to draw the text as well
as the button (without calling
//  the inherited method) then I would
have to use beginpaint(handle,paintstruct).
dc := getDC(handle);

  //To make this method a little more efficient you could add a private Canvas field
  // to the component and create it once only in TButtonEdit.create and free it in
  //TButtonEdit.destroy. I've kept it all here for simplicity.
  //(Don't use TControlCanvas instead of TCanvas in TButtonEdit.create -
  //It doesn't work! - Someone might explain TControlCanvas to me.)
  with TCanvas.create do begin
    Handle := dc;
    Brush.Color := clBtnFace;
    //Brush.style := bsSolid;

    //paint the button surface...
FillRect(rect(clientwidth-BUTTONWIDTH+1,0,clientwidth,clientheight));
//draw the button edges...
if FPressed then Pen.color :=
clBtnShadow else Pen.color := clBtnHighlight;
Moveto(clientwidth-BUTTONWIDTH+2,clientheight-1);
Lineto(clientwidth-BUTTONWIDTH+2,1);
Lineto(clientwidth-1,1);
if FPressed then Pen.color :=
clBtnHighlight else Pen.color := clBtnShadow;
Lineto(clientwidth-1,clientheight-1);
Lineto(clientwidth-BUTTONWIDTH+2,clientheight-1);
//draw the arrows...
Brush.Color := clGreen;
Pen.color := clBlack;
polygon(pic);
Pen.color := clBtnFace;
Moveto(CntrPt.x-5,CntrPt.y);
Lineto(CntrPt.x+6,CntrPt.y);
Handle := 0;
free; //the canvas.
end;
ReleaseDC(handle,dc);
end;
{----------------------------------------------------------------------}

(*
//Old WMPAINT Method (not using TCanvas)...

procedure TButtonEdit.WMPAINT(var Message: TMessage);
var
dc: HDC;
SilverBrush, ArrowBrush, Oldbrush: HBrush;
WhitePen, GrayPen, SilverPen, OldPen: HPen;
CntrPt: TPoint;
pic: array [0..3] of TPoint; // for arrow
'picture'.
begin
inherited;

  //NOW DRAW BUTTON ...

  //find the centre of the button...
CntrPt := point(clientwidth - BUTTONWIDTH
div 2, clientheight div 2);
//offset by 1 if pressed...
if FPressed then CntrPt := point(CntrPt.x+1,CntrPt.y+1);
//get button arrow coordinates...
pic[0] := point( CntrPt.x-5,CntrPt.y);
pic[1] := point( CntrPt.x,CntrPt.y-5);
pic[2] := point( CntrPt.x+5, CntrPt.y);
pic[3] := point( CntrPt.x, CntrPt.y+5);

  //create handles ...
  dc := getDC(handle);
  SilverBrush := CreateSolidBrush(GetSysColor(COLOR_BTNFACE));
  ArrowBrush := CreateSolidBrush(clGreen);
  WhitePen := CreatePen(PS_SOLID,1,clWhite);
  GrayPen := CreatePen(PS_SOLID,1,clGray);
  SilverPen := CreatePen(PS_SOLID,1,GetSysColor(COLOR_BTNFACE));

  //draw button surface and outline...
  OldBrush := SelectObject(dc, ArrowBrush);
  FillRect(dc,rect(clientwidth-BUTTONWIDTH+1,0,clientwidth,clientheight),SilverBrush);

  if FPressed then OldPen := SelectObject(dc,GrayPen)
  else OldPen := SelectObject(dc,WhitePen);
  MovetoEx(dc,clientwidth-BUTTONWIDTH+2,clientheight-1,nil);
  Lineto(dc,clientwidth-BUTTONWIDTH+2,1);
  Lineto(dc,clientwidth-1,1);
  if FPressed then SelectObject(dc,WhitePen)
  else SelectObject(dc,GrayPen);
  Lineto(dc,clientwidth-1,clientheight-1);
  Lineto(dc,clientwidth-BUTTONWIDTH+2,clientheight-1);

  //draw up&down arrows...
  SelectObject(dc,OldPen);
  polygon(dc,pic,4);
  SelectObject(dc,SilverPen);
  MovetoEx(dc,CntrPt.x-5,CntrPt.y,nil);
  Lineto(dc,CntrPt.x+6,CntrPt.y);

  //clean up ...
  SelectObject(dc,OldPen);
  SelectObject(dc, OldBrush);

  DeleteObject(WhitePen);
  DeleteObject(SilverPen);
  DeleteObject(GrayPen);
  DeleteObject(SilverBrush);
  DeleteObject(ArrowBrush);
  ReleaseDC(handle,dc);
end;
*)
{----------------------------------------------------------------------}

end.


Taking a Shortcut

The Unofficial Newsletter of Delphi Users - by Robert Vivrette

Taking A Shortcut...

by Robert Vivrette - RobertV@csi.com & Peter Lennard Larsen - s01pll@msektor.auc.dk

Do you need to have your Delphi app create shortcuts on the Desktop? Perhaps it needs to install another application and you want control over where it is installed and the icons that are created for it. Well, you need look no further.

Prior to Windows 95, the appropriate way to perform these techniques was through a DDE conversation with the Windows Program Manager. Times change and so does our operating system. When Win95 was introduced they changed the whole concept of creating program groups, and adding icons to the operating system. We know them as ShortCuts, but there is a lot going on behind the scenes to support them. Shortcuts are created and managed through an interface called IShellLink and there have been a few articles on their structure and use that can be found on the Microsoft Developers Network. Some information on this interface can also be found in the Delphi Help files under the keyword ISHELLLINK.

Peter had originally sent me a great little unit that defined an object that did all this for you. However, it was only compatible with Delphi 2 and I had a few problems getting it to work under Delphi 3. It did spark what few creative juices I have however, so I decided to create my own version of his work in Delphi 3. It performs essentially the same operation, but is a procedure in a unit (rather than an object). It also is slightly extended in the fact that it allows you to define the location that you want the shortcut to appear as a parameter of the CreateLink call.

If you are interested in Peter's Delphi 2 code of this technique, you can click here. If you are interested in the Delphi 3 code, I have listed a little of it below and have included a demo of its use which you can download by clicking here.

Below is the Delphi 3 unit that implements the shortcut creation mechanism. This technique can of course easily be extended to provide support for some of the additional features of the IShellLink interface. As a supplement to this capability, Peter put together a great unit called WinInfo which gives access to some of the more common destinations of Shortcuts (like the Desktop, Start Menu, etc.) You can see this article, by clicking here.

Hope you enjoy this. If you have questions, feel free to contact myself or Peter.

unit Shortcut;

interface

uses ShlObj, ActiveX, ComObj, Windows, WinInfo;

// TARGET is the file that the shortcut will point to.

// ARGS is the command-line arguments you wish to pass to the Target app.

// WORKDIR is the directory that will be current when the link is launched.

// SHORTCUTNAME is the name of the shortcut link file.

// LOC is the location the file should go. Uses the WinInfo unit to get the location
//   of special system locations. Set to slNone if ShortCutName fully defines the
//   location of the shortcut.

type
  TSpecialLocations = (slNone,slDesktop,slFavorites,slFonts,slNetHood,slPersonal,
                       slPrograms,slRecent,slSendTo,slStartMenu,slStartup,slTemplates);

procedure CreateLink(Target,Args,WorkDir,ShortCutName: String; Loc: TSpecialLocations);

implementation

procedure CreateLink(Target,Args,WorkDir,ShortCutName: String; Loc:
TSpecialLocations);
var
IObj    : IUnknown;
Link    : IShellLink;
IPFile  : IPersistFile;
TargetW : WideString;
begin
IObj := CreateComObject(CLSID_ShellLink);
Link := IObj as IShellLink;
IPFile  := IObj as IPersistFile;
with Link do
begin
SetPath(PChar(Target));
SetArguments(PChar(Args));
SetWorkingDirectory(PChar(WorkDir));
end;
case Loc of
slDesktop   : TargetW :=
WindowsInfo.DesktopDir+'\'+ShortCutName;
slFavorites : TargetW :=
WindowsInfo.FavoritesDir+'\'+ShortCutName;
slFonts     : TargetW :=
WindowsInfo.FontsDir+'\'+ShortCutName;
slNetHood   : TargetW :=
WindowsInfo.NetHoodDir+'\'+ShortCutName;
slPersonal  : TargetW :=
WindowsInfo.PersonalDir+'\'+ShortCutName;
slPrograms  : TargetW :=
WindowsInfo.ProgramsDir+'\'+ShortCutName;
slRecent    : TargetW :=
WindowsInfo.RecentDir+'\'+ShortCutName;
slSendTo    : TargetW :=
WindowsInfo.SendToDir+'\'+ShortCutName;
slStartMenu : TargetW :=
WindowsInfo.StartMenuDir+'\'+ShortCutName;
slStartup   : TargetW :=
WindowsInfo.StartupDir+'\'+ShortCutName;
slTemplates : TargetW :=
WindowsInfo.TemplatesDir+'\'+ShortCutName;
else            TargetW :=
ShortCutName;
end;
IPFile.Save(PWChar(TargetW),False);
end;

end.


You Can Never Have Too Much WinInfo!

The Unofficial Newsletter of Delphi Users - by Robert Vivrette

You Can Never Have Too Much WinInfo!

by Peter Lennard Larsen - s01pll@msektor.auc.dk

Do you need to find out where a users "Program Files" directory is? You could assume that it was off of the root, but remember, the operating system allows you to move it! The same holds true for most (if not all) of the other "special" directories managed by Windows. This simple component allows you to always have this information at the tip of your fingers. Simply include the unit in your Uses clause. Then, when you need to know the location of one of these special directories, you simply call the appropriate property from the WindowsInfo object this unit creates. For example, the location of the desktop is WindowsInfo.DesktopDir. Simple!

Note also that this unit works well in conjunction with my other article on Creating Shortcuts. And there is also a sample project that illustrates the use of both of these techniques. You can download this demo by clicking here (for my Delphi 2 demo) or by clicking here (for Robert Vivrette's Delphi 3 demo).

unit wininfo;

interface

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

type
TWindowsInfo = class
private
{ Private declarations }
FDesktopDir: string;
FFavoritesDir: string;
FFontsDir: string;
FNetHoodDir: string;
FPersonalDir: string;
FProgramsDir: string;
FRecentDir: string;
FSendToDir: string;
FStartMenuDir: string;
FStartupDir: string;
FTemplatesDir: string;
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create;
published
{ Published declarations }
property DesktopDir: string read
FDesktopDir;
property FavoritesDir: string read
FFavoritesDir;
property FontsDir: string read
FFontsDir;
property NetHoodDir: string read
FNetHoodDir;
property PersonalDir: string read
FPersonalDir;
property ProgramsDir: string read
FProgramsDir;
property RecentDir: string read
FRecentDir;
property SendToDir: string read
FSendToDir;
property StartMenuDir: string read
FStartMenuDir;
property StartupDir: string read
FStartupDir;
property TemplatesDir: string read
FTemplatesDir;
end;

var
   WindowsInfo: TWindowsInfo;

implementation

constructor TWindowsInfo.Create;
const
WinKey = '\Software\Microsoft\Windows\CurrentVersion\';
ExplorerKey =
'\Software\Microsoft\Windows\CurrentVersion\Explorer\';
var
Reg: TRegistry;
begin
inherited Create;
Reg := TRegistry.Create;
try
Reg.RootKey := HKey_Current_User;
if Reg.OpenKey(ExplorerKey+'Shell
Folders', FALSE) then begin
FDesktopDir:=Reg.ReadString('Desktop');
FFavoritesDir:=Reg.ReadString('Favorites');
FFontsDir:=Reg.ReadString('Fonts');
FNetHoodDir:=Reg.ReadString('NetHood');
FPersonalDir:=Reg.ReadString('Personal');
FProgramsDir:=Reg.ReadString('Programs');
FRecentDir:=Reg.ReadString('Recent');
FSendToDir:=Reg.ReadString('SendTo');
FStartMenuDir:=Reg.ReadString('Start Menu');
FStartupDir:=Reg.ReadString('Startup');
FTemplatesDir:=Reg.ReadString('Templates');
end;
finally
Reg.Free;
end;
end;

initializiation
   WindowsInfo := TWindowsInfo.Create;

finalization
   WindowsInfo.Free;

end.


WinHelp Office Review - Part I

 The Unofficial Newsletter of Delphi Users - by Robert Vivrette

WinHelp Office Review

by Robert Vivrette - RobertV@mail.com

As most of you may know, The Unofficial Newsletter of Delphi Users had very humble beginnings. Initially, I used Microsoft Word and hand-coded all the necessary material for generating my UNDU Help files. Not a pretty picture, mind you and I wouldn't recommend it. It involved creating footnotes with the HLP-specific codes necessary for the operatoni of the actual HLP file. When I wanted to test the file, I had to save the whole thing as an RTF file and then run the DOS help compiler. It was a process that was fraught with pitfalls. The key reason for the difficulty was of course that Word was not designed to be a Help development system so it didn't provide much assistance.

After the first 2 issues, Borland had pity on me and bought me a copy of ForeHelp. It was quite a step up and for several issues, I happily churned out UNDU using this tool.

Then the time came for me to take the leap and start posting UNDU has HTML files. Enter Blue Sky Software and their excellent RoboHelp and HLP2HTML packages. RoboHelp was a huge leap over ForeHelp and really made the creation of the help files as effortless as you could be expect. It served as an add-in to MS Word and had a little toolbar that would automatically create topics, links, and so on and did all the grunt work for you. After I was done with the HLP file, I could then take their HLP2HTML program and make web pages out of the resulting issue of UNDU. I had tried a number of ways of converting HLP files to HTML and only Blue Sky's solution really made it effortless. I didn't need the original source of the HLP file, it just took the file and wrote out the appropriate HTML pages ready for posting to a web site.

For the bulk of the past few years, this has been the basic tools I have used for the creation of UNDU. RoboHelp to make the HLP file, and then HLP2HTML to convert it to web pages. The end result was that I had both versions of each issue and readers could either download the help files and read them offline, or could just go to the UNDU web site and read the issues there.

Now with their latest release, BlueSky Software has made an already powerful help authoring tool even better.

The WinHelp Office Suite includes the following tools:

    RoboHelp 5.0 - The core of the system. Integrates with MS Word to create Windows HLP files.
    Graphics Tools - WinHelp Graphics Locator, Graphics Library, ReSize
    Inspection and Checking Tools - WinHelp Inspector, Keyword Reporter, BugHunter
    Knowledge Bases - PC HelpDesk
    Video Kit - WinHelp Software Video Camera, WinHelp Video Wizard
    WinHelp 3.1 Extension Pack - Compatibility Wizard, HyperViewer
    Conversion Tools - Help-to-Source, Help-to-Word, Help-to-HTML, Moving to WinHelp 95
WinHelp Office Suite also includes RoboHTML, a totally new system developed by Blue Sky for the creation of HTML Help files. You can also buy some of these components by themselves, or in smaller packages, so you may want to contact them for furthern information on this.

So how does it all work. Pretty darn nice actually! You can get into RoboHelp in any numer of ways including launching the New Office Document icon that Word sets up. The various help projects that RoboHelp can create are setup as templates in this configuration. You can also just launch RoboHelp directly, and tell it you want to start a new project. What you will get is something like this:

As you can see there are a number of different startup templates available including ones for Delphi Help and various other Borland products. When you chose these options, RoboHelp sets various internal configurations that tailor it to that particular use. In the case of some of the programming languages RoboHelp will also generate the appropriate source code necessary to integrate the help file in with the application. For Delphi, for example, it creates an interface unit that maps the topic ID's to integer constants for use in the application.

As soon as you specify the type of project, and give RoboHelp general information (such as project title, where it will be stored and so on), RoboHelp launches with the new project ready. In addition, it also brings up Word and synchronizes the two together as you see below (please excuse the smashed-down size of these screen shots, I was trying to keep them small for the web site):

The interface of RoboHelp is really a treat to work with and the synchronization between it and Word is rock solid. You can make changes to either side and RoboHelp makes sure they stay in sync. As you can see by the treeview section on the right, RoboHelp lists all Help topics that have been created, along with any that might have broken links or might have references to external help documents. If you want to create a new topic, you can simply right click on the Topics node of the tree and pick "New". Simple as that.

As you go, you can pick the option that will build your help file so you can immediately test it at any point. RoboHelp's compilation process has additional hints, warnings and errors that it provides beyond those you would see with just the basic Help Compiler.

In addition, towards the bottom of the tree is the Single Source node which ties in the various formats that RoboHelp supports. As you can see from the following figure (at the left), at any point, you can take your Help file and export it out to any number of different formats, including WinHelp 3 and 4, HTML Help, and so on. Blue Sky has done a really fantastic job of integrating all of these capabilities into one simple to use interface. I am not kidding when I say this package is really a joy to use.

They also give you a myriad different ways to view the help project including a Table Of Contents view similar to what you would see in the Help file itself (figure to right).

Now as many of you know, I have changed the format of UNDU a bit. Instead of starting with HLP files, and making web pages out of them, I will be creating web pages and occasionally packaging them together as HLP files. Originally, I had felt that this decision would rob me of my most important authoring tool. But have no fear! Blue Sky Software to the rescue again! It is clearly their intention to be the one tool you need for online and offline authoring. With the migration of the Windows Help system away from the old style of HLP files and to the new format of HTML Help, a new authoring system was definitely needed. With the introduction of RoboHTML, they are really providing the same great tools for HTML that they did for HLP files. In a future review, I will take a look at RoboHTML. So Stay tuned! In case you want to get a jump on this however, Blue Sky is offering a trial download version of RoboHTML on their web site (see below).

WinHelp Office (I think now called RoboHelp Office) and particularly the RoboHelp system is really a fantastic tool. I have worked with a number of excellent Help authoring systems over the past few years, but RoboHelp really does the job the best. It neatly integrates a wide variety of tools and capabilities into one simple yet powerful interface. If you need to create a help system for your Delphi App or Component, or if you need to create a Help system for any other purpose, I feel you need look no further than RoboHelp/WinHelp Office. I know I might sound like I am gushing, but I do believe in giving praise where it is due. I am not the only one, either... RoboHelp has received awards galore... you can take a look at these credentials on their web site.

I do have one negative thing to say about the suite however. In my opinion, Blue-Sky's tools are a bit on the pricey side. Fortunately, you can get some of their tools individually (including RoboHelp I believe) so that helps to bring the cost down. In addition, I have seen an effort on their part lately to do some competitive pricing so hopefully that trend will continue.

Aside from that one caveat, WinHelp Office (and particularly RoboHelp) is a great package. I give it a solid 10 out of 10.

Robert

For more information you can check out Blue Sky Software's web site at http://www.blue-sky.com.


Manage Multiple CD's with TMediaPlayer

 The Unofficial Newsletter of Delphi Users - by Robert Vivrette

Manage Multiple CD's with TMediaPlayer

by Clinton R. Johnson - xepol@poboxes.com

Borland tends to hide the best functionality. Don't ask me why. The TMediaPlayer, as a result, is probably the most under appreciated component in the entire VCL. I've noticed that many people in the Borland newsgroups have asked how to use multiple CDroms. Well, with this example, I'll show you not just how to use multiple CDroms, but also how to find which drives are CDroms (source is listed at the bottom).

I started by writing a function called CDList. It asks the system for a list of available drives in the system(GETLOGICALDRIVES). This returns a map showing which drive letters are used. Next, I use a bitmask to determine which drives are CDroms. By calling GETDRIVETYPE with the drive letter (formatted as the root directory ie : D:\), I can determine which drives are CDroms.

Next, I use the result of my CDList function to fill a ComboBox. I ensure that each drive letter added to the ComboBox has a colon appended to it. This makes it easier to use with the TMediaPlayer component.

Finally, I add an OnChange event handler to the ComboBox. First, if the TMediaPlayer is open, I stop it and close it. Stopping the CDrom is an important step. First, its a good habit to make sure that anything you deal with is in a known state, and secondly, if the system has multiple physical CDroms, the CDrom will keep playing even if the MediaPlayer is closed. I assign the FILENAME property of the MediaPlayer to the selected line in the ComboBox, and open the MediaPlayer again. The Filename property must be the drive letter appended by a colon, an undocumented feature, although Borland has recently added this information to their FAQ.

One of the reasons most people have problems with the Win32 API is its lack of consistency. You may have noticed that I had to pass the drive as a full path to the GETDRIVETYPE system call, but I only assign the drive letter and a colon to the MediaPlayer (and thus directly to the MCI layers). It's always important to remember little details like this - it makes the difference between functional code, and non functional code (and occasionally functional code, leading to premature hair loss).

On a final note, I set the Shareable property on the TMediaPlayer to true. I consider this a good habit. It lets you share the drive with other programs. I also re-open the drive, so that I can send it text commands. I've found that the text based commands are better documented than the binary MCI calls, and at times, more functional. I would not be able to do this if I did not set the Sharable property to true.

Here is the DFM file and source code for the demo unit.

object Form1: TForm1
  Left = 200
  Top = 110
  Width = 441
  Height = 81
  Caption = 'Form1'
  Font.Charset = DEFAULT_CHARSET
  Font.Color = clWindowText
  Font.Height = -11
  Font.Name = 'MS Sans Serif'
  Font.Style = []
  OnCreate = FormCreate
  PixelsPerInch = 96
  TextHeight = 13
  object ComboBox1: TComboBox
    Left = 272
    Top = 16
    Width = 145
    Height = 21
    Style = csDropDownList
    ItemHeight = 13
    TabOrder = 0
    OnChange = ComboBox1Change
  end
  object MediaPlayer1: TMediaPlayer
    Left = 12
    Top = 8
    Width = 253
    Height = 30
    DeviceType = dtCDAudio
    Shareable = True
    TabOrder = 1
  end
end

unit Unit1;

interface

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

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

var
  Form1: TForm1;

implementation

{$R *.DFM}

//*****************************************************************************
// Returns a string, made of all CDROM drive letters in the system.
//*****************************************************************************
Function CDList : String;

Var
  Drives                : Integer;
  Loop                  : Integer;
  Work                  : String;

begin
//*****************************************************************************
// Get a map of the drives.  Bit 0= A, Bit 25=Z.
If the bit is set, the drive
// is present on the system.
//*****************************************************************************
Drives := GetLogicalDrives;
Result := '';
For Loop := 0 To 25 Do
Begin
//*****************************************************************************
// For each drive in the system, check if it is a
CDROM, if so, all the letter
// to the result string.
//*****************************************************************************
If (((1 Shl Loop) And Drives)<>0)
Then
Begin
Work := Char(65+Loop)+':\';
If (GetDriveType(PChar(Work))=DRIVE_CDROM)
Then
Begin
Result
:= Result+Char(65+Loop);
End;
End;
End;
End;

procedure TForm1.FormCreate(Sender: TObject);

Var
  CDs                   : String;
  Loop                  : Integer;

begin
//*****************************************************************************
// Get a list of CD drives.
//*****************************************************************************
CDs := CDList;
ComboBox1.Clear;
//*****************************************************************************
// Fill a combo box with a list of drives.
I append : to the drive letter
// here, to make working with the TMediaPlayer later
that much easier.
//*****************************************************************************
For Loop := 1 To Length(CDs) Do
Begin
ComboBox1.Items.Add(Copy(CDs,Loop,1)+':');
End;
end;

procedure TForm1.ComboBox1Change(Sender: TObject);

begin
//*****************************************************************************
// DeviceID is 0 when the device is not OPEN.
If it is open, we need to
// stop and close it. This isn't required will all
CD players (ie multi tray
// players like the NEC 4x4), but if you have multiple
physical CDs, like I
// have, then it is required.
//*****************************************************************************
If (MediaPlayer1.DeviceId<>0) Then
Begin
MediaPlayer1.Stop;
MediaPlayer1.Close;
End;
//*****************************************************************************
// Here is the magic that makes the mediaplayer use
a different drive.
//*****************************************************************************
MediaPlayer1.FileName := ComboBox1.Text;
//*****************************************************************************
// Obviously no good without openning it!
//*****************************************************************************
MediaPlayer1.Open;
end;

end.


Beziers and Paths

The Unofficial Newsletter of Delphi Users - by Robert Vivrette

Beziers and Paths

by Clinton R. Johnson - xepol@poboxes.com

I felt it was about time that there were some examples on how to use some of the undocumented features of the graphics API. Its a shame that Borland declined implementing these features within the TCanvas, as most of them are fairly straight forward.

In the BezierTools unit, I provide 2 procedures for drawing Bezier curves. Each procedure takes a Canvas and an array of TPoints as parameters. The Points can either be dynamically declared, or a static array created in a VAR declaration.

A Beziers curve is defined by a start point, and an end point and 2 tension points. It is possible to draw more than one curve at a time, providing the curves are attached. The PolyBezier function considers the end point of the previous curve as the start point for the next point. You would pass the normal 4 points for the first curve, and only 3 points for each curve after that (2 tension points, and a end point). The rule is : for N Bezier curves, you need 3N+1 points.

The graphics API does not draw filled Bezier curves. It seemed to me that this would be a neat feature to have, so combined Bezier curves with Paths. Paths are a type of recording device. Not a sophisticated as metafiles, but decidedly useful. Each canvas can only have one path at a time, and you must create the path before you use it. Paths (generally) can not be saved and replaying into the canvas like metafiles, nor can you move paths easily between canvases like metafiles, bitmaps etc (which is not to say it's impossible, but it requires more coding to accomplish).

You record a path by issuing a BEGINPATH command, with the Canvas handle as a parameter. You then draw, as normal, onto the canvas. Once you have completed your figure, call ENDPATH. If you are going to draw the path as a filled area, you should call CLOSEFIGURE before ENDPATH. You should use exception handling around a BEGINPATH/ENDPATH pair. Any drawing to the canvas while a path is being recorded is sent to the path, and not to the canvas it self.

Once you have created a path, you need to draw the path onto the screen. The two most useful calls are STROKEPATH, and STROKEANDFILLPATH. STROKEPATH draws path with the current pen(and is not filled), while STROKEANDFILLPATH uses both the pen and brush to provide a filled polygon effect. I use STROKEANDFILLPATH with the FilledBezier procedure to provide a filled bezier.

I used the TSMPanel to represent the start/end and tension points of the curves. I turn off any sizing abilities, and ensure that the caption section is high enough to cover the entire control for dragging. They made idea controls, because I did not need any extra code to move them. If you have the Plus package for Windows, or IE4.0, turn on Show Window Contents While Dragging for best effect. You will see the curves redraw AS you drag the control points, not just the results of moving them.

The example I have provided is written in Delphi 3. Click here to download this demo code.


There's Method in My (Hook) Madness!

 The Unofficial Newsletter of Delphi Users - by Robert Vivrette

There's Method in My (Hook) Madness!

by Grahame Marsh - gsmarsh@aol.com

Recently I was experimenting with an application which used a hook to monitor events. Hooks are really useful for a number of like tasks but they are unsupported directly in Delphi, so you use the (win 32) API calls, such as:

    var
      ThisHook : hHook;
    .....
      ThisHook := SetWindowsHookEx (WH_CALLWNDPROC, HookProc, HInstance, GetCurrentThreadID);
This puts a WH_CALLWNDPROC type hook in to the current application's instance and thread. When the hook fires it calls the HookProc, which is declared like this:
    function HookProc (nCode: Integer; wParam, lParam: Longint): Longint; stdcall;
    begin
    //.... my code goes here
      Result := CallNextHookEx (ThisHook, nCode, wParam, lParam);
    //.... or goes in here
    end;
And finally, when you are finished with the hook you disconnect it:
    UnhookWindowsHookEx (ThisHook);
You'll find an example of these calls used in Borland's FORMS.PAS; also you can search the WIN32.HLP file for SetWindowsHookEx and this will show you the types of hooks available. But in this simple example you'll have noticed that I have had to use a global variable and a stand-alone function. This gives you a problem if you multiple hooks in the same application are a possibility, or if you want to refer to methods or variables inside a class, without going through yet another global variable (as in the case of FORMS.PAS).

The same problem arises when you want to write your own windows message procedures inside a class. But Borland have provided the MakeObjectInstance (and a corresponding FreeObjectInstance) to allow message procedures to be methods. So I wondered if the same technique could be used to make a hook function a method. The resulting code used the Borland code for MakeObjectInstance but substituted a call suitable for hooks. I came up with this code which is only a little modified from the Borland original (also in FORMS.PAS):

    unit
      HookInst;

    interface

    uses
      Windows;

    type
      THookCall = packed record
        Code   : integer;
        WParam : WPARAM;
        LParam : LPARAM;
        Result : LResult
      end;

      THookMethod = procedure (var HookCall: THookCall) of object;

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

    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;

    end.

I'm sorry that this unit is not better documented, but although I understand the principle of what is happening, I've not worked out the details! But it does work!

So how do you use it?

Let's say you want a WH_CALLWNDPROC hook in a class. You define your hook function, a pointer variable (that will be a pointer to the method) and a hook handle (so you can correctly chain the hook events):

    type
      TMyClass = class (TWhat)
    ....
      private
        ThisHook : hHook;
        HookProc : pointer;
    ....
       function MyHookProc (var HookCall: THookCall);
    ....
      end;
And in the class constructor, you create a pointer to the class method and set the hook running with a call to the API:
      HookProc := MakeHookInstance (MyHookProc);
      ThisHook := SetWindowsHookEx (WH_CALLWNDPROC, HookProc, HInstance, GetCurrentThreadID);
And next you must define your hook function, which is now a method:
    function TMyClass.MyHookProc (var HookCall: THookCall);
    begin
    ....
      with HookCall do
        Result := CallNextHookEx (ThisHook, Code, wParam, lParam);
    ....
    end;
Finally, in the destructor, you clean up:
      UnhookWindowsHookEx (ThisHook);
      FreeHookInstance (HookProc);
So, in conclusion...

No global variables! No stand alone hook function! The hook function can easily reference the class methods!


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 March, 1998

March 26th, 1998

Clinton Johnson brings us some more great material. First, he discusses using the TMediaPlayer to manage Multiple CD Rom Drives. Then, he gets a little artistic with a great discussion of how Bezier Curves and Paths work.

Grahame Marsh also has something for us today. He has put together a very nice discussion of implementing Windows Hooks in your applications.

March 25th, 1998

Do you need to create a help system for your Delphi applicatioin or component? WinHelp Office to the rescue! Check out my review of this fantastic help authoring system.

March 20th, 1998

I'm currently working on a number of great articles, so stay tuned this weekend for more.

On a completely unrelated note however, I have 2 unused software packages that I will let go for best offer if anyone is interested. The first is "BoundsChecker 5.0" for Visual C++ (NT/95) still shrinkwrapped (Hey... I am a Delphi programmer, why would I want this?). The second (also unused) is "Web Speed Workshop" by Progress Software, a development toolset for building Internet transaction processing applications. I will pay shipping on these to whoever gets them. Email me if you are interested.

March 18th, 1998

A bunch of great stuff today. Peter Larsen brings us two great pieces of code that work well together... (Yes, both a desert topping and a floor wax!!!). Check out the articles on creating shortcuts in Taking a Shortcut, and also getting information on Windows 95 special directories with his WinInfo Unit. There is also a nice demonstration program that illustrates their use.

Also, Angus Johnson brings us another interesting component showing how you can draw on the surface of a TEdit component. He uses this technique to create a simple button on the right side of the control. Handy for creating drop-down calendars, calculators, and so on! Take a look at this article on Drawing on a TEdit Component.

Want another great resource for Delphi tips & techniques? Many of you already know this, but Borland has a great page that has all sorts of technical documents on techniques for Delphi. Check it out!

March 17th, 1998

Over the past few days, I have received a bit of mail and sample components utilizing the technique first illustrated by Matt Hamilton earlier this month. Clinton Johnson brings a very complete solution to moving and resizing a control in his Sizeable/Moveable Panel component.

John Mertus also brings us a very interesting article on Using the Clipboard with WAV files. Now's your chance to use this interesting and powerful technique in your Delphi Apps!

March 10th, 1998

Thanks for all the great feedback on the March 6th article on sizing components. I can see we really sparked some creative juices out there! I am going to sort through what I have received on this and post them in the next few days.

Grahame Marsh has two new offerings today. The first is a way of Determining the CPUType in a machine including its vendor ID string. The second article is an interesting discussion of Compressing 256 color BMPs in your Delphi applications.

I also have updated and added new links to both the Tips & Tricks page and the Articles & Reviews page.

March 6th, 1998

Matt Hamilton brings us a truly sick and twisted technique for Moving and Sizing Components at Runtime. The basic behavior is one line of code. Everyone has to check this out, as it is terribly cool...

March 5th, 1998

The winners of the monthly UNDU prizes have been selected! In case everyone doesn't know what I am talking about... each month, I will take all the names of contributors to UNDU and put them in a hat. Then I will draw one name at random and that person will win some cool prize.

To inagurate the launching of the new UNDU site, I will be giving prizes to all contributors this past month. The first two winners are: Tex Murphy (for his article on Digital Sound) and Clint Johnson (for his discussion of Delphi 3's Code Parameters). They each win a copy of the Borland Developer's Collection CD Volume II, courtesy of The Cobb Group. Next, comes Tom Deprez (for his article on CD Change Notificaitons) who wins a one-year subscription to Delphi Informant magazine. Next, comes Lance Leonard (for his article on clipboard functions with the active control) who wins a copy of Creating Cool Web Applets with Java by Paul J. Perry. The fifth winner is Herbert Beemster (for his article on Giving Editboxes Some Attention) who wins a copy of Developing Active X Web Controls from Coriolis Books. And the last and final winner is Eddie Shipman (for his article on the Win95 FindFiles dialog) who wins a copy of Kick Ass Java Programming (also from Coriolis Books). If the winners would email me with their snail-mail addresses, I will send your prizes off! Congratulations to all of you and thank you again for your support of UNDU!

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 February, 1998


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: