Windows
  1. ???? Data segment too large ????
  2. Port command and win95, a summary.
  3. Read the Run Minimized Checkbox
  4. Shared memory in a DLL with Delphi 2.0
  5. Shell_NotifyIcon
  6. How do I make completely invisible main forms??
  7. HELP !! STAY-ON-TOP !!!
  8. Hardware port access in DELPHI 2
  9. Hiding Windows 95 Taskbar
  10. A Catch-a-Maximize Command Question
  11. How do you detect Windows version?
  12. How can I change the wallpaper programmatically?
  13. Path / directory name for 'My Computer'
  14. Determining which font (Large or Small) is in use
  15. Large/Small Fonts?
  16. How can I restore a window to its last state when I run it again?
  17. How: to determine name of StartUp group
  18. Finding Boot Drive
  19. How to make a window system modal ?
  20. Sending Keystrokes/Text to a Window...
  21. Windows Messages Basics
  22. Buttons in Win95 task bar
  23. Control Panel[NEW]
  24. Associate filetype [extension)[NEW]

???? Data segment too large ????

From: info@sundialservices.com (Sundial Services)

In article <325B6BAB.7096@hec.unil.ch> Jean-Luc Nicoulin <Jean-Luc.Nicoulin@hec.unil.ch> writes:

I get the message 'Data segment too large'. What is the matter and what
can I do to solve the problem ?

In a Windows 3.1 application, three major data-areas share one(!) 64K memory segment: t he stack, the Windows 'local heap', and the data-segment containing all globals and initialized constants. This area can become used up extremely quickly.

Consequently, W3.1 applications store almost -nothing- in the stack or in a global. Rather, they store -pointers- to them. This is why, in Delphi, 'an object is a pointer.'

What you have to do is to comb your application looking for large globals and to move those into a common data block which your program allocates on startup (as an object) and destroys when done. You can see a lot about what's in the data segment by setting the 'linker map' to 'detailed' and looking for the 'DATA' segment. Whatever's in that segment is going to be trying to occupy space in the lower part of that 64K-memory segment.

Port command and win95, a summary.

From: Martin Larsson <martin.larsson@delfi-data.msmail.telemax.no>

APPOLOGY

This was supposed to be a quick summary. It ended up being quite long. Hope it's not too boring...

THE PROBLEM

Under MS-DOS, an application has control of the entire machine. This gives the programmer a lot of freedom. To maximize speed, you can access the hardware directly if necessary.

Under Windows 3.x, this freedom was somewhat limited. You were no longer allowed to write directly to the screen, among other things. The problem is obvious: since the user could have any number of applications running, there was no guarantee that they were not accessing the same hardware simultaneously.

Another problem that showed up was that you had to be nice to the other applications running at the same time. Win 3.x is co-operatively multitasked, meaning that each application determines when it's done and other applications can run. Hogging the CPU for longer periods of time was not considered nice.

But the fact that no applications would run unless we as programmers said so, could be worked to our advantage when accessing the hardware. Since the application were guaranteed full control over the machine for as long as it wished, it could, when it got the CPU, muck with the I/O ports or memory, but not give up control until it was done.

Unfortunately, progress caught up with us; now there's Win32 (Windows NT and Windows 95). T hese are true operating systems, with true pre-emptive multi-tasking. Each thread (the execution unit) gets a certain amount of time with the processor. When the time is up, or a thread with higher priority comes along, the system will switch to the next thread, even though the first thread is not done. This switching can occur between any two assembly instructions; there's no guarantee that a thread will be able to complete any number of instructions before it's pre-empted, and there might be a long time 'till the next timeslot.

This brings up a real problem with direct hardware access. A typical I/O read, for instance, is composed of several assembly instructions:


        mov dx, AddressPort
        mov al, Address
        out dx, al
        jmp Wait
    Wait:
        mov dx, DataPort
        in  al, dx

While the state of all registers are preserved on a thread-switch, the state of the I/O ports are not. So, it is very possible that three applications have their way with 'your' I/O port between the 'out' and the 'in' instructions above.

THE DOCUMENTED WAY

The solution to this problem is to somehow tell all other applications that "Currently MyProg is using port 546, and everybody else better stay in line." What's needed is a mutex. Unfortunately, to use a mutex, all applications have to agree on a name for that mutex. But even if that was possible, you'd easily get into some thorny problems. Consider two applications App1 and App2. Both wants to execute the above code. Unfortunately, they're created by different people with different views, so App1 asks for the AddressPortMutex first, while App2 asks for the DataPortMutex first. And, by a sad coincidence, App1 gets the AddressPortMutex, then the system swithes to App2, which aquires the DataPortMutex, and we're deadlocked. App2 can't get the address port, 'cause App1 has that. App1 can't get the data port, 'cause App2 has that. And we're still waiting...

The correct way to solve this problem is to create a device driver that owns the port/memory area. Access to the hardware is supported through an API. A typical function would be


GetIOPortData(AddressPort, DataPort : word) : Byte;

GetIOPortData would aquire a mutex that protects both (possibly all) ports, then access the ports, and finally releasing the mutex before returning to the caller. If different threads are calling this function at the same time, one will get there first, the others must wait.

Writing a device driver is not easy. It must be done in assembler or C, and they are really hard to debug. And just to be safe, a device driver for Windows 95 (a VxD) isn't compatible with a device driver for Windows NT (a VDD, for virtual device driver). They are said to converge, and Windows NT 6.0 and Windows 2000 might have compatible device drivers, but until then, we're stuck with writing two separate pieces of code.

For more info see (for instance):

Microsoft's Windows 95 Device Driver Kit

Microsoft's Windows NT Device Driver Kit

Microsoft Press' "Systems Programming for Windows 95" by Walter Oney

Also, check out Vireo's VtoolsD library for writing VxD's in C. http://www.vireo.com/.

THE UNDOCUMENTED WAY

The above problem isn't too real. An application that accesses the hardware directly, is usually using some specialized hardware. A machine-configuration like that tend to run one application only, who's sole purpose is to access that hardware. In such a scenario, writing a device driver seems too much trouble. After all, the reason the thing is running on Windows, is just to get the nice GUI for (almost) free, not that 10 applications can be running simultaneously.

Fortunately, Windows 95 is built to be compatible with Windows 3.x. This means that direct I/O must be allowed, simply because a lot of Win 3.x programs uses it. To access the I/O ports, simply step down to assembly. The following code was supplied by Arthur Hoornweg (hoornweg@hannover.sgh-net.de):


    function getport(p:word):byte; stdcall;
    begin
      asm
         push edx
         push eax
         mov  dx,p
         in   al,dx
         mov  @result,al
         pop  eax
         pop  edx
      end;
    end;


    Procedure Setport(p:word;b:byte);Stdcall;
    begin
      asm
        push edx
        push eax

        mov dx,p
        mov al,b
        out dx,al

        pop  eax
        pop  edx
      end;
    end;

François Piette also has some direct I/O access functions at http://rtfm.netline.be/fpiette/portiofr.htm

BUT WHAT ABOUT NT?

The above will not work on Windows NT. NT is a much more robust operating system, and allowing all and everybody access to the hardware anytime they wanted, would seriously endager the stability. In addition, NT is cross platform, and access to I/O ports might be wildly different on different processors.

Even so, it is possible to access the I/O ports directly under NT on x86 processors. This is highly undocumented, and will probably disappear in future versions of the operating system.

I have not much information on the process, but an article by D. Roberts in the May, 1996 issue of Dr. Dobb's Journal looks promising: "Direct Port I/O and Windows NT." This seems to be the only DDJ I'm missing, so I can't verify it. See http://www.ddj.com for ordring of back-issues.

Windows Developer's Journal does have an article on "Port I/O under Windows." It's written by Karen Hazzah, and appeared in the June 1996 issue. See http://www.wdj.com for ordering of back-issues.

RESOURCES

(Note, I know very little about these resources, check them out yourself.)

There are newsgroups dedicated to the topic of writing VxD's and VDD's:

comp.os.ms-windows.programmer.nt.kernel-mode (VDD)

comp.os.ms-windows.programmer.vxd (VxD)

Dejanews (http://www.dejanews.com) turned up quite a few hits on 'device driver direct I/O access 95'.

BlueWater Systems have developed OCX's for direct I/O, memory access and interrupt handling under all Win32 platforms. They also seem to offer custom built device drivers. See their page at http://www.bluewatersystems.com/.

I know some other company has been advertising here for their ability to write custom VxD's. But I can't find that reference.

Read the Run Minimized Checkbox

Solution 1

Paul S. Knapp wrote:
>
> Does anyone know how to make as Delphi app read the Run Minimized
> checkbox in the Windows 3.1 or 3.11 StartUp Group.
>
> I cannot get my application to run minimized when this checkbox is
> checked.  I would like to be able to give my users the option of
> starting the application in their startup group, in either maximized or
> minimized mode.  Every Windows application I have ever used is able to
> start in the mode selected by the checkbox in the Startup Group. I
> assume Delphi applications should be able to as well, but so far I a
> haven't found a way.
>
> Thanks in advance for your advice
> Paul Knapp

Hi Paul!

Use WinProcs unit and after created main form add call ShowWindow.

You can use HInstance, HPrevInst, CmdShow and CmdLine global variables.


program Project1;

uses
  WinProcs, {*** use WinProcs}
  Forms,
  Unit1 in 'UNIT1.PAS' {Form1};

{$R *.RES}

begin
  Application.CreateForm(TForm1, Form1);
  ShowWindow(Form1.handle, CmdShow);
  Application.Run;
end.

Solution 2

From: Ken Kyler <ken@kyler.com>

Here's and example, it's taken from

Rubenking, Neil (1996). _Delphi Programming Problem Solver_. Foster City, CA: IDG Books. ISBN:1-56884-795-5.


unit Unit1;

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs;

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

var
  Form1: TForm1;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
var
  SUI : TStartupInfo ;
begin
  if CmdShow = SW_SHOWDEFAULT then
  begin
    GetStartupInfo( SUI ) ;
    ShowHow := SUI.wShowWindow ;
  end
  else
    ShowHow := CmdShow ;

  if ShowHow = SW_SHOWMAXIMIZED then
    WindowState := wsMaximized ;
end;

procedure TForm1.FormActivate(Sender: TObject);
begin
  case ShowHow of
    SW_SHOWMINIMIZED,
    SW_MINIMIZE,
    SW_SHOWMINNOACTIVE :
      Application.Minimize ;
  end ;
end;

end.

Solution 3

From: a.viebke@berlin.snafu.de (Andreas Viebke)

This works with NT 4 and Delphi 2.01. It took me one and a half hours to find out: Make your project code look like this:


  begin
    Application.Initialize;
    Application.CreateForm(TForm1, Form1);
    Form1.Show;
    Application.Minimize;
    Application.Run;
  end.

It seems to be important that neither OnCreate nor OnShow is disturbed by a procedure that changes a window's state.

Shared memory in a DLL with Delphi 2.0

From: johnnysc@ix.netcom.com (John Crane)

Sharing Memory Mapped Files... Check out the following code:


var
  HMapping: THandle;
  PMapData: Pointer;

const
  MAPFILESIZE = 1000;

procedure OpenMap;
var
  llInit: Boolean;
  lInt: Integer;
begin
  HMapping := CreateFileMapping($FFFFFFFF, nil, PAGE_READWRITE, 0, MAPFILESIZE, pchar('MY MAP NAME GOES HERE'));
  // Check if already exists
  llInit := (GetLastError() <> ERROR_ALREADY_EXISTS);
  if (hMapping = 0) then begin
    ShowMessage('Can''t Create Memory Map');
    Application.Terminate;
    exit;
  end;
  PMapData := MapViewOfFile(HMapping, FILE_MAP_ALL_ACCESS, 0, 0, 0);
  if PMapData = nil then begin
    CloseHandle(HMapping);
    ShowMessage('Can''t View Memory Map');
    Application.Terminate;
    exit;
  end;
  if (llInit) then begin
    // Init block to #0 if newly created
    memset(PMapData, #0, MAPFILESIZE);
  end;
end;

procedure CloseMap;
begin
  if PMapData <> nil then begin
    UnMapViewOfFile(PMapData);
  end;
  if HMapping <> 0 then begin
    CloseHandle(HMapping);
  end;
end;

Any two or more applications or DLLs may obtain pointers to the same physical block of memory this way. PMapData will point to a 1000 byte buffer in this example, this buffer being initialized to #0's the first time in. One potential problem is synchronizing access to the memory. You may accomplish this through the use of mutexes. Here's an example of that:


{ Call LockMap before writing (and reading?) to the memory mapped file.  Be sure to call UnlockMap immediately when done updating. }

var
  HMapMutex: THandle;

const
  REQUEST_TIMEOUT = 1000;

function LockMap:Boolean;
begin
  Result := true;
  HMapMutex := CreateMutex(nil, false, pchar('MY MUTEX NAME GOES HERE'));
  if HMixMutex = 0 then begin
    ShowMessage('Can''t create map mutex');
    Result := false;
  end else begin
    if WaitForSingleObject(HMapMutex,REQUEST_TIMEOUT) = WAIT_FAILED then begin
      // timeout
      ShowMessage('Can''t lock memory mapped file');
      Result := false;
    end;
  end;
end;

procedure UnlockMap;
begin
  ReleaseMutex(HMixMutex);
  CloseHandle(HMixMutex);
end;

Please excuse my unnecessary begin..end's. I come from a Clipper background, and I prefer to see my logic blocks capped off with end's - easier to follow.

Shell_NotifyIcon

From: "Neil Clayton" <100101.602@compuserve.com>

Rainer Perl <Rainer.Perl@iddc.via.at> wrote in article

> I have a question to the Shell_NotifyIcon function:
> I can add an icon to the taskbar
> I can modify an icon
> I can delete an icon.
> What I can't do: I can't receive Messages from the Icon!!

To receive messages you must add the NIF_MESSAGE flag to your notify structure and tell it what message to send and to which window. This is the code that I use:


procedure TMainForm.UpdateTaskBar;            // update the win95 taskbar icon area
var
  NotifyData: TNotifyIconData;

begin
  With NotifyData do                                            // set up the data structure
  begin
    cbSize             := SizeOf(TNotifyIconData);
    Wnd                := MyForm.Handle;
    uID                  := 0;
    uFlags             := NIF_ICON or NIF_MESSAGE or NIF_TIP;   // ... the aspects to modify ...
    uCallbackMessage := WM_MY_MESSAGE;                         // ... the message to send back to us ...
    hIcon              := hMyIcon;
    szTip              := 'Tool Tip To Display';           // ... and the tool tip
  end;
  Shell_NotifyIcon(dwMessage, @NotifyData);                     // now do the update
end;

WM_MYMESSAGE is a user defined message. Usually defined as:


const
  WM_MYMESSAGE = WM_USER + <some number - can be zero>;

How do I make completely invisible main forms??

From: gt6298d@prism.gatech.edu (Chris Randall)

"J.J. Bakker" <J.J.Bakker@stud.rgl.ruu.nl> wrote:
>Does anyone know the answer, I', trying to make an app that has an icon in the notification area with a popupmenu. However the application is still visible on the taskbar. 
Using Application.ShowMainForm:=False; is not enough.
>

I have run into the same problem but found the answer. This little bit of code works great.


procedure TMainForm.FormCreate(Sender: TObject);
begin
  Application.OnMinimize:=AppMinimize;
  Application.OnRestore:=AppMinimize;
  Application.Minimize;
  AppMinimize(@Self);
end;

procedure TMainForm.AppMinimize(Sender: TObject);
begin
  ShowWindow(Application.Handle, SW_HIDE);
end;

HELP !! STAY-ON-TOP !!!

From: "James D. Rofkar" <jim_rofkar%lotusnotes1@instinet.com>

JAAD wrote:
>
> I want to make a Delphi-Form to REALLY stay on top, But not only within it own  application (thats simpel)
       No I want it to stay on top even when I am using  for instance EXCEL.
>

Try using the Windows API function SetWindowPos(). Something like...


   with MyForm do
      SetWindowPos(Handle,
                   HWND_TOPMOST,
                   Left,
                   Top,
                   Width,
                   Height,
                   SWP_NOACTIVATE or SWP_NOMOVE or SWP_NOSIZE);

You may need to call this function in your Form's OnShow(), OnDeactivate(), and OnActivate() event handlers.

Hardware port access in DELPHI 2


function InPort(PortAddr: word): byte;
{$IFDEF VER90}
assembler; stdcall;
asm
        mov dx,PortAddr
        in al,dx
end;
{$ELSE}
begin
Result := Port[PortAddr];
end;
{$ENDIF}

procedure OutPort(PortAddr: word; Databyte: byte);
{$IFDEF VER90}
assembler; stdcall;
asm
   mov al,Databyte
   mov dx,PortAddr
   out dx,al
end;
{$ELSE}
begin
Port[PortAddr] := DataByte;
end;
{$ENDIF}

Hiding Windows 95 Taskbar

From: "James D. Rofkar" <jim_rofkar%lotusnotes1@instinet.com>

Robert Copier wrote:
> Is there a way to hide the Windows 95 statusbar when i start my application made in delphi 2.01. When the user close the application the statusbar must become visible again.

I'm guessing you're referring to the Windows 95 taskbar and system tray window, and not a statusbar. The answer: Sure you can! And what a cool idea! Here's how:

  1. First declare a variable of type HWND to store the Window handle of the Windows 95 taskbar.
          TForm1 = class(TForm)
             ...
          private
             hTaskBar: HWND;
             ...
          end;
    

  2. In your main form's OnCreate() event handler, place some code that resembles:
          hTaskBar := FindWindow('Shell_TrayWnd', nil);
          ShowWindow(hTaskBar, SW_HIDE);
    

  3. Finally, in your main form's OnDestroy() event handler, code something like:
          ShowWindow(hTaskBar, SW_SHOW);

A Catch-a-Maximize Command Question

From: "Chami" <72223.10@compuserve.com>

> I need to have a form in my application that zooms to half of
> the screen when the Maximize button is pressed, not to full
> screen.
>

you could handle the WM_GETMINMAXINFO message from your form.

for example, add the following declaration to the protected section of your form (interface):


procedure _WM_GETMINMAXINFO( var mmInfo : TWMGETMINMAXINFO );  message wm_GetMinMaxInfo;

then define (implementation) the above message handler as follows (TForm1 being the name of your form of course):


procedure TForm1._WM_GETMINMAXINFO( var mmInfo : TWMGETMINMAXINFO );
begin
        // set the position and the size of your form when maximized:
        with mmInfo.minmaxinfo^ do
        begin
                ptmaxposition.x := Screen.Width div 4;
                ptmaxposition.y := Screen.Height div 4;

                ptmaxsize.x     := Screen.Width div 2;
                ptmaxsize.y     := Screen.Height div 2;
        end;
end;

How do you detect Windows version?

From: mdiluglio@aol.com

Check the API help In the WINPROCS unit try the function GetVersion: LongInt;

The GetVersion function retrieves the current version numbers of the Windows and MS-DOS operation systems.

NOTE there is a error in the orginal API documentation, the major/minor Win version are reversed!

As I have used it:

Windows 3.1 show up as 3.1, WIN95 shows up as 3.95

How can I change the wallpaper programmatically?

Solution 1

The following code comes from Loyds Help File (it can be found on most delphi web pages). I haven't tried it but I will use it in one of my apps as soon as I get the bitmap from the client. let me know if it works for you.


unit Unit1;

interface

uses
  SysUtils, WinTypes, WinProcs, Messages, Classes, Graphics, Controls,
  Forms, Dialogs;

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

var
  Form1: TForm1;
  Bitmap: TBitmap;

implementation

{$R *.DFM}

procedure TForm1.FormCreate(Sender: TObject);
begin
  Bitmap := TBitmap.Create;
  Bitmap.LoadFromFile('C:\WINDOWS\cars.BMP');
end;

procedure TForm1.FormPaint(Sender: TObject);
var
  X, Y, W, H: LongInt;
begin
  with Bitmap do begin
    W := Width;
    H := Height;
  end;
  Y := 0;
  while Y < Height do begin
    X := 0;
    while X < Width do begin
      Canvas.Draw(X, Y, Bitmap);
      Inc(X, W);
    end;
    Inc(Y, H);
  end;
end;

end.

Solution 2

From: "Dirk Faber " <d.j.faber@student.utwente.nl>

Rob Wilson <wilson@pelops.compd.com> wrote
> Does anyone know how I can change the wallpaper at runtime using a
> filename that I specifiy?

procedure ChangeWallpaper(bitmap: string);       {bitmap contains filename: *.bmp}

var pBitmap : pchar;

begin
 bitmap:=bitmap+#0;
 pBitmap:=@bitmap[1];
 SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, pBitmap, SPIF_UPDATEINIFILE);
end;

> Also, is there a way of saving it to the INI file for next session?
  1. add inifiles to the uses list.
  2. create an inifile with a texteditor like this:
    [LastUsedBitmap]
    LUBitmap= c:\mybitmap.bmp
    

  3. use a procedure like this: (supposed the inifile is like above, and is named c:\Bitmap.ini)
    procedure WriteToIniFile(bitmap : string);
    
    var MyIniFile : TInifile;
    
    begin
     MyIniFile := Tinifile.Create( 'c:\Bitmap.ini' );
     MyIniFile.WriteString( 'LastUsedBitmap', 'LUBitmap', bitmap);
     MyIniFile.Free;
    end;
    
    procedure ReadFromIniFile(var bitmap: string);
    
    var MyIniFile : TInifile;
    
    begin
      MyIniFile := Tinifile.Create( 'c:\Bitmap.ini' );
      bitmap:= MyIniFile.ReadString('LastUsedBitmap', 'LUBitmap');
      MyIniFile.Free;
    end;
    

Path / directory name for 'My Computer'

Christian Piene Gundersen <j.c.p.gundersen@jusstud.uio.no>

This is a rather complicated matter, so if it isn't vital to your application, I suggest that you spend your time better than digging into it. However, I'll try to point you in the right direction.

The windows 32 operating system is based on a shell which uses virtual folders, like 'my computer', 'desktop' and 'recycle bin'. Some of these folders are part of the physical file system. That is they have a corresponding directory in the file system. This is the case with 'desktop' and 'recycle bin'. These directories can be used as InitialDir for the TOpenDialog, but first you have to get their physical location, which can be different on different computers. To get the physical location of these folders, you have to use some special API calls (see example below). Other folders, like 'my computer' and 'printers' are not part of the file system - they are only virtual. I've noticed that you can browse to these folders using the TOpenDialog, but I don't think they can be used as InitialDir.

Virtual folders are (a bit simplified) of the type SHITEMID (item identifier). They are normally accessed using pointers to item identifiers list (PIDL). To get the PIDL of a special folder, you can use the SHGetSpecialFolder function. The physical location of the corresponding directory can then be obtained by passing the PIDL to the GetPathFromIDList function. If the folder is part of the file system, the function will return the path as a string (which can be used as InitialDir). But if you want the OpenDialog to start in a folder that is only virtual (like 'my computer'), you'll have to make it accept a PIDL as InitialDir, which I don't think it will. My guess is that the TOpenDialog uses PIDLs when browsing, but only accepts physical directories as InitialDir.

Here is an example that shows how to get the 'recent documents' path and use it as InitialDir:


procedure TForm1.Button1Click(Sender: TObject);
var
PIDL: Pointer;
Path: LPSTR;
const
CSIDL_RECENT = $0008;
begin
Path := StrAlloc(MAX_PATH);
SHGetSpecialFolderLocation(Handle, CSIDL_RECENT, @PIDL);
if SHGetPathFromIDList(PIDL, Path) then // returns false if folder isn't
part of file system
  begin
  OpenDialog1.InitialDir := Path;
  OpenDialog1.Execute;
  end;
StrDispose(Path);
end;

I think you'll have to write a wrapper for these API calls. They are found in shell32.dll. The best advice I can give you if you want to dig into this is to study the ShlObj.h file. I don't program in C myself, but I found it very useful.

Some constants you may need:


  CSIDL_DESKTOP            = $0000;
  CSIDL_PROGRAMS           = $0002;
  CSIDL_CONTROLS           = $0003;
  CSIDL_PRINTERS           = $0004;
  CSIDL_PERSONAL           = $0005;
  CSIDL_STARTUP            = $0007;
  CSIDL_RECENT             = $0008;
  CSIDL_SENDTO             = $0009;
  CSIDL_BITBUCKET          = $000a;
  CSIDL_STARTMENU          = $000b;
  CSIDL_DESKTOPDIRECTORY   = $0010;
  CSIDL_DRIVES             = $0011;  // My Computer
  CSIDL_NETWORK            = $0012;
  CSIDL_NETHOOD            = $0013;
  CSIDL_FONTS              = $0014;
  CSIDL_TEMPLATES          = $0015;

Determining which font (Large or Small) is in use

"Greg Peterson" <maxint@cwnet.com>

Try this:
FUNCTION SmallFonts : BOOLEAN;
{returns TRUE if  small fonts are set, FALSE if using Large Fonts }
VAR
  DC : HDC; { used to check for number of colors available }
BEGIN
  DC := GetDC(0);
  Result :=   (GetDeviceCaps(DC, LOGPIXELSX) = 96); 
  { LOGPIXELSX will = 120 if large fonts are in use }
  ReleaseDC(0, DC);
END;

Large/Small Fonts?

Gene Eighmy <eighmy@scott.net>

> 
> When my programs run on systems with small fonts, I
> often get strange output.  Labels too small to hold all
> the text, leaving the right, or the bottom, unshown, for
> instance.  StringGrid's which don't align as expected.
> 
Try this. This will rescale both the form size and also reform small vs. large fonts. Call it in Form.FormCreate. Hope this helps.


unit geScale;

interface
uses Forms, Controls;

procedure geAutoScale(MForm: TForm);

implementation
Type
TFooClass = class(TControl); { needed to get at protected }
                               { font property }


procedure geAutoScale(MForm: TForm);
const
     cScreenWidth :integer = 800;
     cScreenHeight:integer = 600;
     cPixelsPerInch:integer= 96;
     cFontHeight:integer   = -11;  {Design-time value of From.Font.Height}

var
  i: integer;

begin
     {
     IMPORTANT!! : Set Scaled Property of TForm to FALSE with Object Inspector.

     The following routine will scale the form such that it looks the same
     regardless of the screen size or pixels per inch.  The following section
     determines if the screen width differs from the design-time screen size.
     If it differs, Scaled is set true and component positions are rescaled such
     that they appear in the same screen location as the design-time location.
     }
     if (Screen.width &;lt> cScreenWidth)or(Screen.PixelsPerInch <> cPixelsPerInch) then
     begin
          MForm.scaled := TRUE;
          MForm.height := MForm.height * screen.Height DIV cScreenHeight;
          MForm.width  := MForm.width  * screen.width DIV cScreenWidth;
          MForm.ScaleBy(screen.width, cScreenWidth);

     end;

     {
      This section determines if the run-time font size differs from the design-
      time font size.  If the run-time pixelsperinch differs form the design-time
      pixelsperinch, the fonts must be rescaled in order for the form to appear
      as designed.  Scaling is calculated as the ratio of the design-time font.height
      to run-time font.height.  Font.size will not work as it may equal the design-
      time value yet appear physically larger crowding and overrunning other
      components.    For instance, a form designed in 800x600 small fonts
      has a font.size of 8.  When you run the form on in 800x600 large fonts,
      font.size is also 8 but the text is noticably larger than when run in small
      font mode. This scaling will make them both appear to be the same size.
     }

     if (Screen.PixelsPerInch <> cPixelsPerInch) then
     begin

         for i := MForm.ControlCount - 1 downto 0 do
              TFooClass(MForm.Controls[i]).Font.Height :=
               (MForm.Font.Height div cFontHeight) *
                 TFooClass(MForm.Controls[i]).Font.Height;

     end;

end;

end.

How can I restore a window to its last state when I run it again?

A: Here is WindowRestorer - a window size and state restorer

DESCRIPTION: Ever notice how professional programs seem to remember in what condition and location you left them and their child windows? Ever notice how most RAD apps don't? You can take that ragged edge off your program with this unit. It Allows apps to save the location, size, and state of windows so that when the user reopens them, they will look as the user left them.

USE: Put WINRSTOR in the uses of clause of your main form and any forms that will be saving or restoring their own state, size, or location. (If you will be doing all the saving and restoring using WinSaveChildren and WinRestoreChildren from the main form, you only need reference it in the main form's uses clause.)

In MainForm.Create, initialize the global WinRestorer object as follows (it's already declared in this file, but needs to be allocated):


	GlobalWinRestorer := TWinRestorer.create( Application, TRUE, WHATSAVE_ALL); 

Which is the same as:
	GlobalWinRestorer := TWinRestorer.create( Application, TRUE, [location, size, state]); 

Then, in MainForm.Destroy, deallocate the global WinRestorer object as follows:
GlobalWinRestorer.free; 

A good place to save a form's status is in the queryclose event or else attached to a button or menu item. I usually create an item in the File Menu captioned 'Save &Workspace' which does:
	GlobalWinRestorer.SaveChildren(Self, [default]); 

And under main form's Close event I put:
	GlobalWinRestorer.SaveWin(Self, [WHATSAVE_ALL]); 

I have tended to restore the children's status in their own show events like this:
	GlobalWinRestorer.RestoreWin(Self, [default]); 

though I am moving toward putting in the main form's show event:
	GlobalWinRestorer.RestoreWin(Self, [default]); 
	GlobalWinRestorer.RestoreChildren(Self, [default]);

HINTS: If you set TForm.Position to poScreenCenter or anything fancy, this unit won't do what you expect. poDesigned seems to work fairly well. I could have raised an exception if you try to set top and left of a poScreenCentere'd form, but then you have to be careful using WinRestoreChildren. I opted not to check the position property and leave that up to individual developers.


unit WinRstor;

INTERFACE

USES SysUtils, Forms;

TYPE {=============================================================}


{------------------------------------------------------------------
Windows restorer object class and related types.
-------------------------------------------------------------------}
EWinRestorer = class( Exception);
TWhatSave = (default, size, location, state);
STWhatSave = set of TWhatSave;
TWinRestorer = class(TObject)
 protected
  mIniFile: string;
  mIniSect: string[80];
  mIsInitialized: boolean;
  mDefaultWhat: STWhatSave;
 public
  constructor Create( TheApp: TApplication;

    LocalDir: boolean; DefaultWhatSave: STWhatSave);
    {If localDir is true, ini dir is the app dir.  Else, ini dir is the windows dir.}
  procedure SaveWin(TheForm: TForm; What: STWhatSave);
  procedure SaveChildren(TheMDIForm: TForm; What: STWhatSave);
  procedure RestoreWin( TheForm: TForm; What: STWhatSave);
  procedure RestoreChildren(TheMDIForm: TForm; What: STWhatSave);
  property IniFileName: string  read mIniFile;
end;

CONST
  WHATSAVE_ALL = [size, location, state];


VAR
GlobalWinRestorer: TWinRestorer;

IMPLEMENTATION

Uses IniFiles;

constructor TWinRestorer.create;
var fname, path: string[100];
begin
  inherited create;
{Calculate ini file name}
  if default in DefaultWhatSave then
    raise EWinRestorer.create(
     'Attempt to initialize default window position paramaters with set ' +
     ' containing [default] item.  ' +
     'Default params may contain only members of [size, location, state].  ')
  else mDefaultWhat := DefaultWhatSave;

  fname := ChangeFileExt( ExtractFileName( TheApp.exeName), '.INI');
  if LocalDir then begin {parse out path and add to file name}
    path := ExtractFilePath(TheApp.exeName);
    if path[length(path)] <> '\' then
      path := path + '\';
    fname := path + fname;
  end;
{fill object fields}
  mIniFile := fname;
  mIniSect := 'WindowsRestorer';
{It'd be nice to write some notes to a section called [WinRestorer Notes]}
end;

procedure TWinRestorer.RestoreWin;

var FormNm, SectionNm: string[80];   ini: TIniFile;
  n,l,t,w,h: integer; {Left, Top Width, Height}
begin
  ini := TIniFile.create( mIniFile);
  TRY
    SectionNm := mIniSect;
    FormNm := TheForm.classname;
    if default in What then What := mDefaultWhat;
{Update Window State if Necessary}
    if state in What then
      n := ini.ReadInteger( SectionNm, FormNm + '_WindowState', 0);
      case  n of
        1:   TheForm.WindowState := wsMinimized;
        2:  TheForm.WindowState := wsNormal;

        3:   TheForm.WindowState := wsMaximized;
      end;
{Update Size and Location if necessary.}
    with TheForm do begin l:=left; t:=top; h:=height; w:=width; end; {Save current vals.}
    if size in What then begin
      w := ini.ReadInteger( SectionNm, FormNm + '_Width', w);
      h := ini.ReadInteger( SectionNm, FormNm + '_Height', h);
    end;
    if location in What then begin
      t := ini.ReadInteger( SectionNm, FormNm + '_Top', t);
      l := ini.ReadInteger( SectionNm, FormNm + '_Left', l);

    end;
    TheForm.SetBounds(l,t,w,h);
  FINALLY
    ini.free;
  END;
end;

procedure TWinRestorer.RestoreChildren;
var i: integer;
begin
  if TheMDIForm.formstyle <> fsMDIForm then
    raise EWinRestorer.create('Attempting to save window sizes of children for a non MDI parent window.')
  else
    for i := 0 to TheMDIForm.MDIChildCount - 1 do
      RestoreWin( TheMDIForm.MDIChildren[i], what);
end;

procedure TWinRestorer.SaveWin;
var FormNm, SectionNm: string[80];   w : STWhatsave; ini: TIniFile;

begin
  ini := TIniFile.create( mIniFile);
  TRY
    SectionNm := mIniSect;
    FormNm := TheForm.ClassName;
    if default in What then w := mDefaultWhat else w := mDefaultWhat;
    if size in w then begin
      ini.WriteInteger( SectionNm, FormNm + '_Width', TheForm.Width);
      ini.WriteInteger( SectionNm, FormNm + '_Height', TheForm.Height);
    end;
    if location in w then begin
      ini.WriteInteger( SectionNm, FormNm + '_Top', TheForm.Top);
      ini.WriteInteger( SectionNm, FormNm + '_Left', TheForm.Left);

    end;
    if state in w then
      case TheForm.WindowState of
        wsMinimized:   ini.WriteInteger( SectionNm, FormNm + '_WindowState', 1);
        wsNormal:     ini.WriteInteger( SectionNm, FormNm + '_WindowState', 2);
        wsMaximized:   ini.WriteInteger( SectionNm, FormNm + '_WindowState', 3);
      end;
  FINALLY
    ini.free;
  END;
end;

procedure TWinRestorer.SaveChildren;
var i: integer;
begin
  if TheMDIForm.formstyle <> fsMDIForm then
    raise EWinRestorer.create('Attempting to restore window sizes of children for a non MDI parent window.')

  else
    for i := 0 to TheMDIForm.MDIChildCount - 1 do
      SaveWin( TheMDIForm.MDIChildren[i], what);
end;

INITIALIZATION
END.

{ This code came from Lloyd's help file! }

How: to determine name of StartUp group

From: Allan Carlton <zephyr@athene.co.uk>

In each language version of Windows the 'StartUp' folder has als a different name.
Is there any way to determine the correct name of this folder ??
There is an entry in the registry under:
HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\Shell Folders\Start Menu

Which might give you the info you need

Finding Boot Drive

From: "HIKI Takehito" <f8498008@ca.aif.or.jp>
Is there a function or API call to find the boot drive?
I found it in the Registry.


 HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Setup

"BootDir" value may be BootDrive.

How to make a window system modal ?

From: "Eric Lawrence" <deltagrp@keynetcorp.net>


SetSystemModalWindow(Form1.handle);

Sending Keystrokes/Text to a Window...

From: "David Zajac" <dzajac@HiWAAY.net> Hope this helps:
unit Unit1;

interface

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

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
    procedure FormKeyPress(Sender: TObject; var Key: Char);
  private
    AppInst: THandle;
    AppWind: THandle;
  public
    { Public declarations }
  end;

var
  Form1: TForm1;

implementation

{$R *.DFM}

uses ShellAPI;
 

procedure SendShift(H: HWnd; Down: Boolean);
var vKey, ScanCode, wParam: Word;
    lParam: longint;
begin
  vKey:= $10;
  ScanCode:= MapVirtualKey(vKey, 0);
  wParam:= vKey or ScanCode shl 8;
  lParam:= longint(ScanCode) shl 16 or 1;
  if not(Down) then lParam:= lParam or $C0000000;
  SendMessage(H, WM_KEYDOWN, vKey, lParam);
end;

procedure SendCtrl(H: HWnd; Down: Boolean);
var vKey, ScanCode, wParam: Word;
    lParam: longint;
begin
  vKey:= $11;
  ScanCode:= MapVirtualKey(vKey, 0);
  wParam:= vKey or ScanCode shl 8;
  lParam:= longint(ScanCode) shl 16 or 1;
  if not(Down) then lParam:= lParam or $C0000000;
  SendMessage(H, WM_KEYDOWN, vKey, lParam);
end;

procedure SendKey(H: Hwnd; Key: char);
var vKey, ScanCode, wParam: Word;
    lParam, ConvKey: longint;
    Shift, Ctrl: boolean;
begin
  ConvKey:= OemKeyScan(ord(Key));
  Shift:= (ConvKey and $00020000) <> 0;
  Ctrl:= (ConvKey and $00040000) <> 0;
  ScanCode:= ConvKey and $000000FF or $FF00;
  vKey:= ord(Key);
  wParam:= vKey;
  lParam:= longint(ScanCode) shl 16 or 1;
  if Shift then SendShift(H, true);
  if Ctrl then SendCtrl(H, true);
  SendMessage(H, WM_KEYDOWN, vKey, lParam);
  SendMessage(H, WM_CHAR, vKey, lParam);
  lParam:= lParam or $C0000000;
  SendMessage(H, WM_KEYUP, vKey, lParam);
  if Shift then SendShift(H, false);
  if Ctrl then SendCtrl(H, false);
end;

function EnumFunc(Handle: HWnd; TF: TForm1): Bool; Far;
begin
  TF.AppWind:= 0;
  if GetWindowWord(Handle, GWW_HINSTANCE) = TF.AppInst then
    TF.AppWind:= Handle;
  result:= (TF.AppWind = 0);
end;

procedure TForm1.Button1Click(Sender: TObject);
var Text: Array[0..255] of char;
begin
  AppInst:= ShellExecute(Handle, 'open', 'notepad.exe', nil, '', SW_NORMAL);
  EnumWindows(@EnumFunc, longint(self));
  AppWind:= GetWindow(AppWind, GW_CHILD);
end;


procedure TForm1.Button2Click(Sender: TObject);
begin
  SendKey(AppWind, 'T');
  SendKey(AppWind, 'e');
  SendKey(AppWind, 's');
  SendKey(AppWind, 't');
end;

procedure TForm1.FormKeyPress(Sender: TObject; var Key: Char);
begin
  if AppWind <> 0 then SendKey(AppWind, Key);
end;

end.

Windows Messages Basics

Can anybody out there send me some basic stuff about Windows Messages
related to Delphi. All this WM_*** stuff is getting on my nerves, since I
can't understand it.
[Jim Stanley, Jim.Stanley@jacobs.com]

All the Windows messages are listed in the Windows API help in your Delphi help topics. (I'm using D1, assume the same for future versions).

The WM_ (and other) messages are essential to the way Windows works. You're well aware that Delphi is primarily an *event-driven* system; all those OnKeyPress, OnThis, OnThat methods. If you have the VCL source code, you'll find in there somewhere that those event handler methods are designed to *receive* particular Windows messages (and there are some threads in here showing how you can subclass a component and "teach" it to respond to other messages as well). Windows is constantly sending out those messages in response to actions performed by the user, and it's the business of the Delphi app (and of all Windows apps) to intercept them and handle them in ways you decide. Delphi puts a wrapper over most of the message system by creating the event handlers for components described above.

In addition to recieving those messages, you can also *send* them as well. There are a couple of ways to work this: check out SendMessage and PostMessage (both native Win API functions), as well as the Delphi Perform method. The first two require you to use the Handle parameter of the component you're sending the message to, while Perform is a method belonging to that component. The messages go into the standard Windows message queue and are processed like every other message.

Here's a trivial example: I want (for some bizarre reason) to insert a 'y' character whenever I type a '4' in a TMemo. [Think of automatically inserting a begin-end block or a closing parenthesis.) Now I could do a lot with the Memo's Lines property, but that gets pretty complex. A much simpler way of going about it is:


procedure TForm1.Memo1KeyPress(Sender: TObject; var Key: Char);
begin
  if Key = '4' then
    SendMessage(Memo1.Handle, WM_CHAR, Word('y'), 0);
end;

Another example is something we were doing here at Jacobs that used a lot of combo boxes. We wanted them to automatically drop down when the user pressed a key, which is (unfortunately) not standard behavior. Here's what we did:
procedure TFormEffortRates.ComboBoxMaterialKeyDown(Sender: TObject; var
Key: Word;
  Shift: TShiftState);
var iShowing : integer;

{ other code, then... }
    begin
      { This tells you whether the combo is already dropped }
      iShowing := SendMessage((Sender as TComboBox).Handle, CB_GETDROPPEDSTATE, 0, 0);
      if iShowing = 0 then
        { drop the combo box }
        SendMessage((Sender as TComboBox).Handle, CB_SHOWDROPDOWN, 1,0);
    end;

Another good example is getting the line and column from a TMemo. You have to go into the API to do it. Here's a (not particularly efficient - no flames please!!) method of determining them:
function TMDIChild.GetMemoColumn(const TheMemo : TMemo) : integer;
begin
  Result := TheMemo.SelStart -
    (SendMessage(TheMemo.Handle, EM_LINEINDEX,
    GetMemoLine(TheMemo), 0));
end;

function TMDIChild.GetMemoLine(const TheMemo : TMemo) : integer;
begin
  Result := SendMessage(TheMemo.Handle, EM_LINEFROMCHAR,
TheMemo.SelStart, 0);
end;

Again, all these messages can be found in your API help. The instructions for using them are a little vague, but I'm sure everyone will be glad to help you should you need it.

In short, API messages provide you with a way to fine-tune your applications to respond in exactly the way you want them to. I would consider it an advanced Delphi topic, but it sounds like one you're more than ready for.

Buttons in Win95 task bar

Can anyone tell me of a way or a component or whatever else that will allow
delphi 2 or 3 to place a button on the task bar much like what PowerDesk
2.0 Toolbar does.
[Joolz@emarkt.com]

Here are the code snipits to do just that!


// This needs to be in your public declarations @ the top of the pas file
procedure TForm1.IconCallBackMessage( var Mess : TMessage ); message WM_USER
+ 100;


procedure TForm1.FormCreate(Sender: TObject);
var
   nid : TNotifyIconData;
begin
     with nid do
     begin
           cbSize := SizeOf( TNotifyIconData );
           Wnd := Form1.Handle;
           uID := 1;
           uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
           uCallbackMessage := WM_USER + 100;
           hIcon := Application.Icon.Handle;
           szTip := 'This is the hint!';
     end;
     Shell_NotifyIcon( NIM_ADD, @nid );
end;

procedure TForm1.FormClose(Sender: TObject; var Action: TCloseAction);
var
   nid : TNotifyIconData;
begin
     with nid do
     begin
           cbSize := SizeOf( TNotifyIconData );
           Wnd := Form1.Handle;
           uID := 1;
           uFlags := NIF_ICON or NIF_MESSAGE or NIF_TIP;
           uCallbackMessage := WM_USER + 100;
           hIcon := Application.Icon.Handle;
           szTip := 'This is the hint!';
// All the above is probably not needed.
     end;
     Shell_NotifyIcon( NIM_DELETE, @nid );
end;

procedure TForm1.IconCallBackMessage( var Mess : TMessage );
var
   sEventLog : String;
begin
     case Mess.lParam of
// Do whatever you wish here. For example popup up a menu on a right click.
          WM_LBUTTONDBLCLK  : sEventLog := 'Left Double Click';
          WM_LBUTTONDOWN    : sEventLog := 'Left Down';
          WM_LBUTTONUP      : sEventLog := 'Left Up';
          WM_MBUTTONDBLCLK  : sEventLog := 'M Dbl';
          WM_MBUTTONDOWN    : sEventLog := 'M D';
          WM_MBUTTONUP      : sEventLog := 'M U';
          WM_MOUSEMOVE      : sEventLog :=  'movement';
          WM_MOUSEWHEEL     : sEventLog := 'Wheel';
          WM_RBUTTONDBLCLK  : sEventLog := 'r dbl';
          WM_RBUTTONDOWN    : sEventLog := 'r down';
          WM_RBUTTONUP      : sEventLog := 'r up';
     end;
end;

Control Panel[NEW]

From: "Hiki Takehito" <takeone@pop06.odn.ne.jp>

>Anyone know how to put a Delphi application inside the Control Panel?
If you use Delphi3, add Cpl unit at dpr file.

I show you a sample code. -----------


library Project1; {Change "program" to "library"}

uses
  Cpl, {use Cpl unit}
  Windows,
  Forms,
  Unit1 in 'Unit1.pas' {Form1};

{$R *.RES}

procedure ExecuteApp;
 begin
  Application.Initialize;
  Application.CreateForm(TForm1,Form1);
  Application.Run;
end;

{A callback function to export at Control Panel}
function CPlApplet(hwndCPl: THandle; uMsg: DWORD;
                 lParam1, lParam2: LongInt):LongInt;stdcall;
var
  NewCplInfo:PNewCplInfo;
begin
  Result:=0;
  case uMsg of
   {Initialization.Return True.}
   CPL_INIT:
    Result:=1;
   {Number of Applet.}
   CPL_GETCOUNT:
    Result:=1;
   {Transporting informations of this Applet to the Control Panel.}
   CPL_NEWINQUIRE:
    begin
     NewCplInfo:=PNewCplInfo(lParam2);
     with NewCplInfo^ do
     begin
      dwSize:=SizeOf(TNewCplInfo);
      dwFlags:=0;
      dwHelpContext:=0;
      lData:=0;
      {An icon to display on Control Panel.}
      hIcon:=LoadIcon(HInstance,'MAINICON');
      {Applet name}
      szName:='Project1';
      {Description of this Applet.}
      szInfo:='This is a test Applet.';
      szHelpFile:='';
     end;
   end;
   {Executing this Applet.}
   CPL_DBLCLK:
    ExecuteApp;

   else Result:=0;
  end;
end;

{Exporting the function of CplApplet}
exports
  CPlApplet;

begin
end.
To use this, change the extention from "dll" to "cpl". And put into the System folder.

Applet means a piece of Control Panel.Display,Fonts,Mouse,System are all Applets.

Associate filetype [extension)[NEW]

From: Jeremy Collins <jem@jcollins.demon.co.uk>

Basically, you need to add two keys to the registry under HKEY_CLASSES_ROOT. Say your extension in ".ext", then the first key you add is the extension itself:


HKEY_CLASSES_ROOT\
  .ext\
and set the "default" string value of this key to an "internal name" for your file type - for example MyApp.Document:
HKEY_CLASSES_ROOT\
  .ext\
     Default = "MyApp.Document"
You then create another key with this name:
HKEY_CLASSES_ROOT\
  MyApp.Document\
Create a sub-key of this called "shell", a sub-key of *this* called "open" and a further sub-key of "open" called "command". The default value uder this key is the location and name of your your application folled by "%1" which represents the filename parameter that Windows will pass to your executable:
HKEY_CLASSES_ROOT\
  MyApp.Document\
    shell\
      open\
        command\
          Default = "C:\myapp\myapp.exe %1"
You can do this in code with the TRegistry object, or use InstallShield, which can make registry changes for you. I'd advise doing both, in case the user trashes your registry entry. From: "Rodney E Geraghty" &tt;gerarod@ibm.net>

The easiest way I've found to do this is to modify the Extensions section of the win.ini file that is located in the Windows directory. This also works under Win 95 and will update the registry automatically under Win95. Look at the extensions section of the win.ini to see the format you have to use. Put IniFiles in your uses clause and then use something like this:


var
  INIFile: TIniFile;
begin
  try
    INIFile := TInifile.Create('WIN.INI');
    INIFile.WriteString('Extensions','txt','c:\windows\notepad.exe ^.txt');
  finally
    INIFile.Free;
  end;
end;
This would associate *.txt files with Windows Notepad. If you had an app named MyApp in the c:\MyApps directory and your extension was *.MAP then you would change it like this:
var
  INIFile: TIniFile;
begin
  try
    INIFile := TInifile.Create('WIN.INI');
    INIFile.WriteString('Extensions','map','c:\myapps\myapp.exe ^.map');
  finally
    INIFile.Free;
  end;
end;
This will work in both Win 3.11 and Win 95 and saves you from having to modify the Reqistry under Win 95. Not sure about Win NT (or Win95b) since I don't have a test machine available. Note that this is only the first part of the solution though since it will open the associated application but it won't load the file you clicked. To do this you have to read ParamStr(1), which would hold the full path of the file you clicked, and run the file name through your file opening routine.
Please email me and tell me if you liked this page.