Solved

Getting an Access Violation when closing a project

Posted on 2006-06-25
2
267 Views
Last Modified: 2010-04-05
Hello,

I'm working on a component that saves and restores the window size and position.  I had it working, but it gave an access violation when you tried to close the project.  I tried moving the save code, but i think I made it worse because it doesn't save the position or size any more...  and I still have the violation  :(  Can someone look at this and tell me what's wrong?

unit FormMetrix;

interface

uses
  SysUtils, Classes, Controls, IniFiles, Forms;

type
  TMetrix = record
    Height: Integer;
    Width: Integer;
    Top: Integer;
    Left: Integer;
    Name : String;
  end;

  TFormMetrix = class(TComponent)
  private
    { Private declarations }
    FSaveSize:Boolean;
    FSavePosition:Boolean;
    FFileName:String;
    Ini:TIniFile;
  protected
    { Protected declarations }
    { Protected write methods for the properties }
    procedure SetSaveSize(ASaveSize:Boolean);
    procedure SetSavePosition(ASavePosition:Boolean);
    procedure SetFileName(AFileName:String);
    procedure Loaded; override;
  public
    { Public declarations }
     constructor    Create(AOwner: TComponent); override;
     destructor     Destroy; override;
  published
    { Published declarations }
    property SaveSize:Boolean
      read FSaveSize write SetSaveSize default true;
    property SavePosition:Boolean
      read FSavePosition write SetSavePosition default true;
    property FileName:string
      read FFileName write SetFileName;
  end;

procedure Register;

implementation

procedure Register;
begin
  RegisterComponents('ssMetrix', [TFormMetrix]);
end;

////  Executes when component is loaded, (form loaded)
///////////////////////////////////////////////////////
procedure TFormMetrix.Loaded;
var  compOwner:     TComponent;
     formOwner:     TCustomForm;
     FormProperties: TMetrix;
begin
   If FFileName = '' then FFileName := 'Metrix.ini';
   Ini := TIniFile.Create('.\' + FFileName);
  // Check state
  if not(csDesigning in ComponentState) then
  begin
     // Get owner component

     compOwner:=Owner;
     // Walk the owner chain to get the form
     while Assigned(compOwner) do
     begin
        // Check component type
        if (compOwner is TCustomForm) then
        begin
           // Cast as form
           formOwner:=TCustomForm(compOwner);
           // Set internal variables
           FormProperties.Height := Ini.ReadInteger(formOwner.Name, 'Height', 0);
           FormProperties.Width := Ini.ReadInteger(formOwner.Name, 'Width', 0);
           FormProperties.Top := Ini.ReadInteger(formOwner.Name, 'Top', 0);
           FormProperties.Left := Ini.ReadInteger(formOwner.Name, 'Left', 0);
            with FormProperties Do
            begin
               if FSaveSize then
               begin
                if not(height = 0) then
                  formOwner.Height := height;
                if not(width = 0) then
                  formOwner.width := width;
               end;
               if FSavePosition then
               begin
                if not(top = 0) then
                  formOwner.Top := top;
                if not(left = 0) then
                  formOwner.Left := Left;
               end;
            end;
           // Done
           break;
        end;
        // Get the next component in the chain
        compOwner:=compOwner.Owner;
     end;
  end;
    Ini.Free;
end;

procedure TFormMetrix.SetSaveSize(ASaveSize:Boolean);
begin
  FSaveSize := ASaveSize;
end;

procedure TFormMetrix.SetSavePosition(ASavePosition:Boolean);
begin
  FSavePosition := ASavePosition;
end;

procedure TFormMetrix.SetFileName(AFileName:String);
begin
  FFileName := AFileName;
end;

destructor TFormMetrix.Destroy;
var Ini: TIniFile;
    Metrix: TMetrix;
    compOwner:     TComponent;
    formOwner:     TCustomForm;
begin
// Check state
  if not(csDesigning in ComponentState) then
  begin
      If FFileName = '' then FFileName := 'Metrix.ini';
      Ini := TIniFile.Create('.\' + FFileName);
     // Get owner component
     compOwner:=Owner;
     // Walk the owner chain to get the form
     while Assigned(compOwner) do
     begin
        // Check component type
        if (compOwner is TCustomForm) then
        begin
           // Cast as form
           formOwner:=TCustomForm(compOwner);
           // Set internal variables
           Metrix.Top :=formOwner.Top;
           Metrix.Left :=formOwner.Left;
           Metrix.Width :=formOwner.Width;
           Metrix.Height :=formOwner.Height;
           Metrix.Name :=formOwner.Name;
           if FSaveSize then
           begin
           Ini.WriteInteger(Metrix.Name,
                         'Height', Metrix.Height);
           Ini.WriteInteger(Metrix.Name,
                         'Width', Metrix.Width);
           end;
           if FSavePosition then
           Ini.WriteInteger(Metrix.Name,
                         'Top', Metrix.Top);
           Ini.WriteInteger(Metrix.Name,
                         'Left', Metrix.Left);
           end;
           // Done
           break;
        end;
        // Get the next component in the chain
        compOwner:=compOwner.Owner;
     end;
  Ini.Free;
  inherited Destroy;

end;

constructor TFormMetrix.Create(AOwner: TComponent);
begin
  // Perform inherited
  inherited Create(AOwner);
end;

end.

// it's going to be free, so please don't "borrow" my idea  :)
0
Comment
Question by:fibdev
2 Comments
 
LVL 4

Expert Comment

by:LMuadDIb
Comment Utility
first thing I would do is put a try finally block around the TIniFile code

Ini := TIniFile.Create('.\' + FFileName);
try
  ...
finally
  Ini.free;
end;

and put it inside your "not(csDesigning in ComponentState)" statement. You have it inside on the Destroy but not the Loaded procedures

and maybe check to see if "Assigned(compOwner)" is assigned before accessing it:

compOwner:=Owner;
// Walk the owner chain to get the form
if Assigned(compOwner) then
 while Assigned(compOwner) do
     begin
        // Check component type
        if (compOwner is TCustomForm) then
        begin
        ...
0
 
LVL 26

Accepted Solution

by:
Russell Libby earned 125 total points
Comment Utility
Regarding:

> it's going to be free, so please don't "borrow" my idea  :)

The idea is not new, and others have done variations of this technique; myself included. My own code is listed below, and does the same thing, albeit it saves the data in a specified registry key.

As to your code, the problem lies in that fact that attempting to perform the "save" in the Destructor is to late. You should be registering your component to get the free notification from the form, then peforming the save from the Notification routine (when operation is opRemove). You may feel fee to borrow from my code <g>

Regards,
Russell


unit FormState;
////////////////////////////////////////////////////////////////////////////////
//
//   Unit           :  FORMSTATE
//   Author         :  rllibby
//   Date           :  06.23.2005
//   Description    :  Form persistence component for Delphi. Saves the form's
//                     state as well as last position, width, and height.
//
////////////////////////////////////////////////////////////////////////////////
interface

////////////////////////////////////////////////////////////////////////////////
//   Include units
////////////////////////////////////////////////////////////////////////////////
uses
  Windows, Messages, SysUtils, Classes, Graphics, Controls, Forms, Dialogs;

////////////////////////////////////////////////////////////////////////////////
//   Form state registry constants
////////////////////////////////////////////////////////////////////////////////
const
  KEY_FORMSTATE     =  'FormState';

////////////////////////////////////////////////////////////////////////////////
//   Form state registry types
////////////////////////////////////////////////////////////////////////////////
type
  TRegistryKey      =  (rkClassesRoot, rkCurrentUser, rkLocalMachine, rkUsers);

////////////////////////////////////////////////////////////////////////////////
//   TFormState
////////////////////////////////////////////////////////////////////////////////
type
  TAfterLoad        =  procedure(Key: HKEY) of object;
  TAfterSave        =  procedure(Key: HKEY) of object;
  TFormState        =  class(TComponent)
  private
     // Private declarations
     FAfterLoad:    TAfterLoad;
     FAfterSave:    TAfterSave;
     FAutoSave:     Boolean;
     FAutoLoad:     Boolean;
     FRegRoot:      TRegistryKey;
     FRegistryKey:  String;
  protected
     // Protected declarations
     procedure      BindFormNotification;
     function       GetActualKey: HKEY;
     function       GetEnabled: Boolean;
     function       GetOwningFormHandle: HWND;
     procedure      Loaded; override;
     procedure      Notification(AComponent: TComponent; Operation: TOperation); override;
     procedure      OverridePosition;
  public
     // Public declarations
     constructor    Create(AOwner: TComponent); override;
     destructor     Destroy; override;
     procedure      LoadSettings;
     procedure      SaveSettings;
     property       Enabled: Boolean read GetEnabled;
  published
     // Published declarations
     property       AfterLoad: TAfterLoad read FAfterLoad write FAfterLoad;
     property       AfterSave: TAfterSave read FAfterSave write FAfterSave;
     property       AutoLoad: Boolean read FAutoLoad write FAutoLoad;
     property       AutoSave: Boolean read FAutoSave write FAutoSave;
     property       RegistryKey: String read FRegistryKey write FRegistryKey;
     property       RegistryRoot: TRegistryKey read FRegRoot write FRegRoot;
  end;

////////////////////////////////////////////////////////////////////////////////
//   Registration functions
////////////////////////////////////////////////////////////////////////////////
procedure Register;

implementation

//// TFormState ////////////////////////////////////////////////////////////////
procedure TFormState.LoadSettings;
var  wpLoad:        TWindowPlacement;
     dwSize:        DWORD;
     dwType:        DWORD;
     hkLoad:        HKEY;
     hwndForm:      HWND;
begin

  // Check registry key
  if (Length(FRegistryKey) > 0) then
  begin
     // Attempt to open the registry key
     if (RegOpenKey(GetActualKey, PChar(FRegistryKey), hkLoad) = ERROR_SUCCESS) then
     begin
        // Resource protection
        try
           // Set the buffer size
           dwSize:=SizeOf(wpLoad);
           // Attempt to load the binary setting
           if (RegQueryValueEx(hkLoad, KEY_FORMSTATE, nil, @dwType, @wpLoad, @dwSize) = ERROR_SUCCESS) then
           begin
              // Type needs to be set to binary
              if (dwType = REG_BINARY) then
              begin
                 // Override the positioning
                 OverridePosition;
                 // Get the form handle
                 hwndForm:=GetOwningFormHandle;
                 // Apply the setting
                 SetWindowPlacement(hwndForm, @wpLoad);
                 // Fire the after load event
                 try
                    if Assigned(FAfterLoad) then FAfterLoad(hkLoad);
                 except
                    // Don't raise exceptions
                 end;
              end;
           end;
        finally
           // Close the registry key
           RegCloseKey(hkLoad);
        end;
     end;
  end;

end;

procedure TFormState.SaveSettings;
var  wpSave:        TWindowPlacement;
     hkSave:        HKEY;
     hwndForm:      HWND;
begin

  // Get the form handle
  hwndForm:=GetOwningFormHandle;

  // Check handle
  if IsWindow(hwndForm) and (Length(FRegistryKey) > 0) then
  begin
     // Attempt to create the registry key
     if (RegCreateKey(GetActualKey, PChar(FRegistryKey), hkSave) = ERROR_SUCCESS) then
     begin
        // Resource protection
        try
           // Set save length
           wpSave.length:=SizeOf(TWindowPlacement);
           // Get the current window placement
           GetWindowPlacement(hwndForm, @wpSave);
           // Attempt to save the binary setting
           if (RegSetValueEx(hkSave, KEY_FORMSTATE, 0, REG_BINARY, @wpSave, SizeOf(wpSave)) = ERROR_SUCCESS) then
           begin
              try
                 // Fire the after save event
                 if Assigned(FAfterSave) then FAfterSave(hkSave);
              except
                 // Don't raise exceptions
              end;
           end;
        finally
           // Close the registry key
           RegCloseKey(hkSave);
        end;
     end;
  end;

end;

function TFormState.GetActualKey: HKEY;
begin

  // Convert to real HKEY
  case FRegRoot of
     rkClassesRoot  :  result:=HKEY_CLASSES_ROOT;
     rkCurrentUser  :  result:=HKEY_CURRENT_USER;
     rkLocalMachine :  result:=HKEY_LOCAL_MACHINE;
     rkUsers        :  result:=HKEY_USERS;
  else
     // Should never get here
     result:=HKEY_CURRENT_USER;
  end;

end;

procedure TFormState.Loaded;
begin

  // Check autoload state
  if FAutoLoad then LoadSettings;

  // Bind the FreeNotification for the form
  if not(csDesigning in ComponentState) then BindFormNotification;

end;

function TFormState.GetEnabled: Boolean;
begin

  // Check owning form handle and registry key
  result:=IsWindow(GetOwningFormHandle) and (Length(FRegistryKey) > 0) and not(csDesigning in ComponentState);

end;

procedure TFormState.OverridePosition;
var  compOwner:     TComponent;
begin

  // Get owner component
  compOwner:=Owner;

  // Walk the owner chain to get the form
  while Assigned(compOwner) do
  begin
     // Check component type
     if (compOwner is TCustomForm) then
     begin
        // Set default positioning
        TForm(compOwner).Position:=poDesigned;
        // Done
        break;
     end;
     // Get the next component in the chain
     compOwner:=compOwner.Owner;
  end;

end;

function TFormState.GetOwningFormHandle: HWND;
var  compOwner:     TComponent;
begin

  // Set default result
  result:=0;

  // Get owner component
  compOwner:=Owner;

  // Walk the owner chain to get the form
  while Assigned(compOwner) do
  begin
     // Check component type
     if (compOwner is TCustomForm) then
     begin
        // Return the window handle
        result:=TCustomForm(compOwner).Handle;
        // Done
        break;
     end;
     // Get the next component in the chain
     compOwner:=compOwner.Owner;
  end;

end;

procedure TFormState.BindFormNotification;
var  compOwner:     TComponent;
begin

  // Get owner component
  compOwner:=Owner;

  // Walk the owner chain to get the form
  while Assigned(compOwner) do
  begin
     // Check component type
     if (compOwner is TCustomForm) then
     begin
        // Indicate that we wish to know when the form is going to be freed
        compOwner.FreeNotification(Self);
        // Done
        break;
     end;
     // Get the next component in the chain
     compOwner:=compOwner.Owner;
  end;

end;

procedure TFormState.Notification(AComponent: TComponent; Operation: TOperation);
begin

  // Check for Form free
  if (AComponent is TCustomForm) and (Operation = opRemove) then
  begin
     // Resource protection
     try
        // Check enabled state and auto save value
        if GetEnabled and FAutoSave then SaveSettings;
     finally
        // Don't want any more notifications
        AComponent.RemoveFreeNotification(Self);
     end;
  end;

end;

constructor TFormState.Create(AOwner: TComponent);
begin

  // Perform inherited
  inherited Create(AOwner);

  // Set defaults
  FAutoSave:=True;
  FAutoLoad:=True;
  FRegRoot:=rkCurrentuser;
  SetLength(FRegistryKey, 0);

end;

destructor TFormState.Destroy;
begin

  // Perform inherited
  inherited Destroy;

end;

//// Registration //////////////////////////////////////////////////////////////
procedure Register;
begin

  // Regsiter the component
  RegisterComponents('Additional', [TFormState]);

end;

end.
0

Featured Post

Top 6 Sources for Identifying Threat Actor TTPs

Understanding your enemy is essential. These six sources will help you identify the most popular threat actor tactics, techniques, and procedures (TTPs).

Join & Write a Comment

This article explains how to create forms/units independent of other forms/units object names in a delphi project. Have you ever created a form for user input in a Delphi project and then had the need to have that same form in a other Delphi proj…
Introduction The parallel port is a very commonly known port, it was widely used to connect a printer to the PC, if you look at the back of your computer, for those who don't have newer computers, there will be a port with 25 pins and a small print…
It is a freely distributed piece of software for such tasks as photo retouching, image composition and image authoring. It works on many operating systems, in many languages.
You have products, that come in variants and want to set different prices for them? Watch this micro tutorial that describes how to configure prices for Magento super attributes. Assigning simple products to configurable: We assigned simple products…

762 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

8 Experts available now in Live!

Get 1:1 Help Now