Categories
General

Royal Mail are still promoting killing trees

Junk Mail, I hate it.  My own personal view is to never buy anthing from any company that posted unsolicited mail through my post box.  I take my hat off to the Mail Preference service (MPS), which after registration with them a couple of years ago, has cut the amount of Junk Mail that I receive, but the latest “get round” for compainies is to pay Royal mail to deliver junk mail unaddressed with my other mail. 

This completely negates the great work that the MPS do and in my opinion is a disgusting policy adopted by Royal Mail.  If I wanted the stuff I wouldn’t have registered with MPS.  What gives Royal Mail the right to deliver unsolicited items where other companies that honour the MPS lists loose out.

I wonder if there is an online petition or something.  If you know how to stop Royal Mail delivering this rubbish please let me know.

Categories
General

Searching for MP3s on the net

Can’t remember where I found this tip, but it’s a great one.

Looking for SonicEncoders, try replacing the mp3 with sonicencoders.msi!

People leave music files in unprotected directories all of the time. Here’s how to get to them.
Directions
If you properly query google, you can find open indexes of music files easily. Here are some hints to get you started.
Google: -inurl:htm -inurl:html intitle:”index of” mp3

    Using this string we are using the “index of” and the inurl commands to attempt to isolate directories only. The mp3 tag on the end tells google to look for mp3s in these open directories. You can change this to wma or ogg if you wish.

You can add additional search criteria to focus your search more.
Google: -inurl:htm -inurl:html intitle:”index of” mp3 “pearl jam”

    This command will find open directories with mp3 files with Pearl Jam. Obviously you can change the band name or file type to better define your search.
Categories
General

Creating an upgradable Mambo Component

Mambo and Joomla don’t currently seem to have any standards for creating a component that can upgrade.
Generally, when a component is installed, it creates the tables it requires and when it is uninstalled it removes them.
However at some point the developers introduced the com_install() and com_uninstall() functions which can be used instead of putting sql in the XML component file.
The component I am writting is a component for uploading themes, basically stores an image and a file. so where “theme” occurs in classes and table names, it is referring to my component, adapt these to suit your own.
The installation / upgrade of tables is quite easy, I created a ‘theme_version’ table which simply has one field and one record that stores the version number.  This is used to track the version of the tables installed.
If this table does not exist, then it is assumed to be a new install of the component and the rest of th etables are created and populated.  Note the first thing you should do is create the theme_version table and add a single record with a value of 1 to denote the first version of the tables.
If the table does exist, we get the value of the version field and then simply add a switch statement to perform the alter / update sql dependant on the version value.  If you ensure the case statements are in numeric order and don’t bother with break; statements, this system can even upgrade version 1 to version 5 for example.
The ony problem now is what happens on uninstall.  Generally in the uninstall script you would put all the drop table sql to remove the component’s tables.  However, if a user is uninstalling to upgrade, the last thing we want is to remove the tables.
To get round this, I added a boolean field to my component’s configuration table which when set to Yes, will remove the tables.  As a default this field is set to No, so to actually uninstall the component fully, including deleting the tables, the user must first goto the configuration screen, save the configuration with “uninstall tables” set and then uninstall the component.
Personally, I don’t think this is a problem, as if the user forgets, they can just re-install the component, change the config and then uninstall to ensure the whole component is removed.
This assumes you have a configuration section on your component.
The code below mentions themes, this is the component  I was developing. Change the theme to the relevant name for you
component.
Step 1) Create the uninstallTables option.
Add a new field to the configuration table called uninstallTables, this should be set with a default value of either 0 or 1,
depending on whether or not you want to uninstall the tables as default.  Personally, I default it to 0.
e.g.
`uninstallTables` TINYINT(1) NOT NULL
Add the new field to the extended mosDBTable class for the configuration table if used.
Modify the edit configuation html to add a Yes/ No option for the new field.
e.g.

            <tr>
                <td>Uninstall Tables ?</td>
                <td>
                <? echo mosHTML::yesnoSelectList( "uninstallTables", "", $row->uninstallTables ); ?>
                </td>
                <td>By default, the Themes component does not remove tables it has created so they can be upgraded.  If you

want to completely remove this component set this option to 'yes' and save the configuration before uninstalling</td>
            </tr>

You should now be able to modify this field, set it to yes or no and have it persisted in the table.
Step 2) com_install() function
Move you table creations from the XML to the com_install function and add the theme_version table and the install / updgrad
condition e.g.

<?php
global $mainframe;
require_once( $mainframe->getPath( 'class' ) );

function com_install() {
    global $database;

    $table_prefix = $database->_table_prefix;  // get the mos_ table prefix

    $tableList = $database->getTableList(); // get a list of the tables
    if (in_array("{$table_prefix}themes_version",$tableList))  // if the version table exists
    {
        // get the current version
        $SQL = "SELECT  * FROM #__themes_version LIMIT 1";
        $database->setQuery($SQL);
        $rows = $database -> loadObjectList();
        if ($database -> getErrorNum()) {
            echo $database -> stderr();
            return false;
        }
        $row = $rows[0];

        $tableVersion = intval($row->version);  // get the current table version

        switch($tableVersion)
        {
            case 1 :
                // run alter statements to upgrade next version
                ...

                // set the new version
                $SQL = "UPDATE #__themes_version SET `version`=2";
                $database->setQuery($SQL);
                $database->query() or die( $database->stderr() );

        // note we don't have a break here so the V2 - V3 upgrade below will get run as well
            case 2 :
                // run alter statements to upgrade next version
                ...

                // set the new version
                $SQL = "UPDATE #__themes_version SET `version`=3";
                $database->setQuery($SQL);
                $database->query() or die( $database->stderr() );
            break;
        }

    }
    else
    {
        // Themes_Version does not exist so create the tables

        // Themes_version table
        $SQL = "DROP TABLE IF EXISTS #__themes_version";
        $database->setQuery($SQL);
        $database->query() or die( $database->stderr() );

        $SQL = "CREATE TABLE #__themes_version (";
        $SQL = $SQL." version int ";
        $SQL = $SQL." ) TYPE=MyISAM;";
        $database->setQuery($SQL);

   $database->query() or die( $database->stderr() );

        $SQL = "INSERT INTO #__themes_version (version) values(3);";  // change the value here to the table version each time

you release
        $database->setQuery($SQL);
        $database->query() or die( $database->stderr() );

        // themes configuration table
        $SQL = "DROP TABLE IF EXISTS #__themes_config";
        $database->setQuery($SQL);
        $database->query() or die( $database->stderr() );

        $SQL = "CREATE TABLE #__themes_config (";
        $SQL = $SQL." `configid` TINYINT(4) NOT NULL,";
        $SQL = $SQL." `themePath` char(240) NOT NULL,";
        $SQL = $SQL." `uninstallTables` TINYINT(1) NOT NULL";  // this is the uninstall Tables field
        $SQL = $SQL." ) TYPE=MyISAM;";
        $database->setQuery($SQL);
        $database->query() or die( $database->stderr() );

        $SQL = "INSERT INTO `#__themes_config`";
        $SQL = $SQL."(configid, themePath, uninstallTables) values(1, '/themes',0);";  // unistallTables is initialised to 0
        $database->setQuery($SQL);
        $database->query() or die( $database->stderr() );

    // create other tables
    ...

    }

}
?>

Step 3) com_uninstall() function
The uninstall is nice and simple, check the uninstallTables field in the configuration then uninstall if set

<?
global $mainframe;
global $mosConfig_absolute_path;
require_once( $mainframe->getPath( 'class' ) );

function getConfig()
{
    // this function gets the row from the config table
    global $database;

    $database->setQuery("SELECT * FROM #__themes_config LIMIT 1");
    $rows = $database -> loadObjectList();
    if ($database -> getErrorNum()) {
        echo $database -> stderr();
        return false;
    }

    return $rows[0];
}

function com_uninstall() {
    global $database;

    $config = getConfig();

    if ($config->uninstallTables == 1)
    {

        $SQL = "DROP TABLE IF EXISTS #__themes_version";
        $database->setQuery($SQL);
        $database->query() or die( $database->stderr() );

        $SQL = "DROP TABLE IF EXISTS #__themes_config";
        $database->setQuery($SQL);
        $database->query() or die( $database->stderr() );

    // drop other tables
    ...
    }
}
?>

If there are any mambo / joomla developers reading this then it would be nice for something similar to this to be integrated into the CMS, as I the inability to upgrade components as standard as quite a flaw in the component architecture.
Maybe, you can add a standard table which holds a version number and uninstall tables option for each component.  In addition the install can then either call the com_install() or a new com_upgrade($version) script.

Categories
Delphi

Creating a Transparent Splashscreen

To create a transparent splashscreen, like for example the splash displayed when adobe acrobat reader starts, do the following:

  1. Create the a new form
  2. Set the Border Style to bsNone
  3. Set the Position to poScreenCenter
  4. Overide the create constuctor and add the line Brush.Style = bsClear
  5. Override the createparams method and add the line
    Params.ExStyle := Params.ExStyle + WS_EX_TRANSPARENT +WS_EX_TOPMOST;
    after the call to inherited
  6. Add an image componet to the form and load your transparent image into it. I use PNG’s rather than bitmaps with a transparent color.  PNGImage (a PNG wrapper for TPicture is available at sourceforge).
  7. Set the image and form’s Autosize to true.
  8. Slap on a timer to close the form after a couple of seconds.
Categories
Delphi

TStreamAdapter and Images

I can’t remember where I found this code, if I come across the site again, I’ll put a credit to it in this article. 
When trying to load / save PNG Images from a stream using GDI+ routines to load and save from IStream, there were problems, it turns out with the date/time information returned in the Istream Stats.
The following unit provides a Fixed Stream Adapter that correctly returns the stat information.

unit FixedStreamAdapter;
interface
uses
  classes, sysUtils, activex, windows;
type
  TFixedStreamAdapter = class(TStreamAdapter)
public
function Stat(out statstg: TStatStg;
      grfStatFlag: Longint): HResult; override; stdcall;
end;
implementation

function DateTimeToFileTime(DateTime: TDateTime): TFileTime;
// copied from JclDateTime.pas
const
  FileTimeBase      = -109205.0;
  FileTimeStep: Extended = 24.0 * 60.0 * 60.0 * 1000.0 * 1000.0 *10.0; // 100 nSek per Day
var
  E: Extended;
  F64: Int64;
begin
  E := (DateTime – FileTimeBase) * FileTimeStep;
  F64 := Round(E);
  Result := TFileTime(F64);
end;
function TFixedStreamAdapter.Stat(out statstg: TStatStg; grfStatFlag: Longint): HResult;
Begin
  Result := S_OK;
try
if (@statstg <> nil) then
with statstg do
begin
        FillChar(statstg, sizeof(statstg), 0);
        dwType := STGTY_STREAM;
        cbSize := Stream.size;
        mTime := DateTimeToFileTime(now);
        cTime := DateTimeToFileTime(now);
        aTime := DateTimeToFileTime(now);
        grfLocksSupported := LOCK_WRITE;
end;
except
    Result := E_UNEXPECTED;
end;
end;
end.

Categories
Delphi

A routine for painting tiled / stretched images to a canvas

If you want to paint a tiled image to a canvas, this routine will do it.

ACanvas – the canvas to paint to
ARect – A TRect of the area to paint to
Offset – A TPoint which specifies the origin that the images should start from. Usually Point(0,0)
AnImage – Any TGraphic
AMode – No Tile, Tile across, Tile down, Tile both, Stretch to Rect
AColor – sets the rect to this color first. – this is best used when using a transparent graphic format such as png or gif


type
	TZ9TileMode = (pmNoRepeat, pmRepeatX, pmRepeatY, pmRepeatXY, pmFitXY );

procedure PaintBackground(ACanvas : TCanvas; ARect : TRect; Offset : TPoint; AnImage : TGraphic; AMode : TZ9TileMode; BGColor : TColor);
var
	x, y : integer;
begin
	if (AnImage is TPNGObject) then
	begin
		ACanvas.Brush.Color := BGColor;
		ACanvas.FillRect(ARect);
	end;
	if (AnImage.Width = 0) or (AnImage.Height = 0) then exit;
	
	case Amode of
	pmRepeatXY :
		begin
			y := Offset.Y;
			while y <= Arect.Bottom do
			begin
				x := Offset.X;
				while x <= Arect.Right do
				begin
					ACanvas.Draw(x,y,AnImage);
					inc(x, AnImage.Width);
				end;
				inc(y, AnImage.Height)
			end;
		end;
	pmRepeatX :
		begin
			y := Offset.Y;
			x := Offset.X;
			while x <= Arect.Right do
			begin
				ACanvas.Draw(x,y,AnImage);
				inc(x, AnImage.Width);
			end;
			ACanvas.Brush.Color := BGColor;
			ACanvas.FillRect( Rect(ARect.Left,y+AnImage.Height,ARect.Right,ARect.Bottom));
		end;
	pmRepeatY :
		begin
			x := Offset.X;
			y := Offset.Y;
			while y <= Arect.Bottom do
			begin
				ACanvas.Draw(x,y,AnImage);
				inc(y, AnImage.Height)
			end;
			ACanvas.Brush.Color := BGColor;
			ACanvas.FillRect( Rect(x + AnImage.width,ARect.top,ARect.Right,ARect.Bottom));
		end;
	pmNoRepeat :
		begin
			x := Offset.X;
			y := Offset.Y;
			ACanvas.Draw(x,y,AnImage);
			ACanvas.Brush.Color := BGColor;
			ACanvas.FillRect( Rect(X+AnImage.width,y+AnImage.Height,ARect.Right,ARect.Bottom));
			ACanvas.FillRect( Rect(ARect.left,y+AnImage.Height,X+AnImage.width,ARect.Bottom));
			ACanvas.FillRect( Rect(X+AnImage.Width,ARect.Top,ARect.Right,y+AnImage.Height));
		end;
	pmFitXY :
		begin
			x := Offset.X;
			y := Offset.Y;
			ACanvas.StretchDraw(Rect(x,y,x + (ARect.Right-ARect.left), y + (ARect.bottom-ARect.top) ),AnImage);
		end;
	end;
end;
Categories
Delphi

Resolving “Catastrophic Failure” in COM

When an interface returns a delegated object that causes an error, the factory or autofactory has not been assigned.

The default implementation of SafeCallException refers to FFactory which, in this case is NIL. Override SafeCallException to fix. In COMOBJ.pas, the SafeCallException procedure refers to FFactory.ErrorID, FFactory.ProgID, etc in the call to HandleSafeCallException. FFactory is nil, causing a AV in the error handler, which causes a Catastrophic Error to be raise.

If you descend your objects from a TFudgedAutoObject which simply overrides the SafeCallException message, you can solve the problem.

function TFudgedAutoObject.SafeCallException(ExceptObject: TObject;ExceptAddr: Pointer): HRESULT;
var
  E: TObject;
  CreateError: ICreateErrorInfo;
  ErrorInfo: IErrorInfo;
begin
  Result := E_UNEXPECTED;
  E := ExceptObject;
if Succeeded(CreateErrorInfo(CreateError)) then
begin
    CreateError.SetGUID(Class_EPO);
    CreateError.SetSource(PWideChar(‘MY ERROR SOURCE’));
    CreateError.SetHelpFile(PWideChar(‘MY HELPFILE NAME.HLP’));
if E is Exception then
begin
      CreateError.SetDescription(PWideChar(WideString(Exception(E).Message)));
      CreateError.SetHelpContext(Exception(E).HelpContext);
if (E is EOleSysError) and (EOleSysError(E).ErrorCode < 0) then
        Result := EOleSysError(E).ErrorCode;
end;
if CreateError.QueryInterface(IErrorInfo, ErrorInfo) = S_OK then
    SetErrorInfo(0, ErrorInfo);
end;
end;

Comments

Very cool tip!
Written by Guest on 2005-06-08 15:53:46


I’ve been stuck on this problem for a bit. Hopefully this will fix it. Thank you! 
In D7 I had to 
1. Add ActiveX to my uses clause 
2. Define a string constant GUID like this 
const 
MYGUID = ‘{678077B3-095F-4ECE-836D-7E1172C20979}’; 
3. Change the call to SetGUID to  
CreateError.SetGUID(StringToGUID(MYGUID)); 
4. Change the calls to SetSource and SetHelpFile to 
CreateError.SetSource(StringToOleStr(‘MY ERROR SOURCE’));

Written by lummie on 2005-06-09 11:19:00


Ah yes I left my application GUID in SetGUID. 
Customize it to your application as above. Thanks Guest

Categories
Delphi

How do you get the Special Folders Directories from the Windows registry ?

Use the following function to return them. A quck adaption and you should be able to set them as well. For more values to pass into the function check the current contents of the registry key.

function GetSpecialDir(ValueName : string): String;
//  gets the path to a windows special folder and ensures the trailing backslash is on the path.
//  Some Valid ValueNames are
//  ‘Personal’ – My Documents
//  ‘Desktop’ – User’s Desktop
//  ‘Favorites’ – The User Favorites store
var
  Reg: TRegistry;
begin
  result := ”;
  Reg := TRegistry.Create;
try
    Reg.RootKey := HKey_Current_User;
if Reg.OpenKeyReadOnly(‘SoftwareMicrosoftWindowsCurrentVersionExplo
rerShell Folders’) then
      result := IncludeTrailingPathDelimiter(Reg.ReadString(ValueName));
finally
    Reg.Free;
end;
end;

Categories
Delphi

Tracking COM Object instancing

Having written your nice COM Object model to allow access to your application, how do you find out if you have neen clearing up the objects correctly and check you are handling zombie objects correctly too.

If you add the following unit to your aplication and descend your comobjects from TTracker instead of TAutoObject, this unit will count the number of addrefs and releases that occur on the object.

All you need to do is call TTrackerlog.dump to get a list of all the objects, the maximum number of instances ever alive and the current number alive.

All we are doing, is overriding the addref and release implementation, and maintaing a list of stats based up on the objects classname.

unit ObjectTracker;
interface
uses
   Classes, SysUtils, ComObj, Contnrs, SyncObjs, ActiveX;
type
   TTrackedItemInfo = (iiCreationOnAdd, iiCreationOnRelease);
   TTrackedItemInfos = set of TTrackedItemInfo;
   TTracker = class (TAutoObject)
     FTrackedClassName: string;
public
constructor create; virtual;
function ObjAddRef: Integer; override; stdcall;
function ObjRelease: Integer; override; stdcall;
end;
   TTrackedItem = class (TObject)
private
     FDesc: string;
     FInfo: TTrackedItemInfos;
     FMaxCount: Integer;
     FMinCount: Integer;
     FObjAddress: Pointer;
     FRefCount: Integer;
public
constructor Create(AnObj : pointer);
procedure AddReference;
procedure RemoveReference;
property Desc: string read FDesc write FDesc;
property Info: TTrackedItemInfos read FInfo write FInfo;
property MaxCount: Integer read FMaxCount;
property MinCount: Integer read FMinCount;
property ObjAddress: Pointer read FObjAddress write FObjAddress;
property RefCount: Integer read FRefCount;
end;
   TTrackerLog = class (TObject)
     FList: TObjectList;
     FLock: TMultiReadExclusiveWriteSynchronizer;
protected
constructor CreateInstance;
class function AccessInstance(Request: Integer): TTrackerLog;
public
constructor Create; virtual;
destructor Destroy; override;
procedure AddReference(AnObj : TObject; TrackedClassName : string);
procedure Dump(const AFilename : widestring);
class function Instance: TTrackerLog;
class procedure ReleaseInstance;
procedure ReleaseReference(AnObj : TObject; TrackedClassName : string);
end;
implementation
{ TTracker }
{
*************************** TTracker ***************************
}

constructor TTracker.create;
begin
inherited;
   FTrackedClassName := Classname;
end;
function TTracker.ObjAddRef: Integer;
begin
   result := inherited ObjAddRef;
   TTrackerLog.Instance.AddReference(self,FTrackedClassName);
end;
function TTracker.ObjRelease: Integer;
begin
   result := inherited ObjRelease;
   TTrackerLog.Instance.ReleaseReference(self,FTrackedClassName);
end;
{ TTrackerLog }
{
**************************** TTrackedItem *************************
}

constructor TTrackedItem.Create(AnObj : pointer);
begin
inherited Create;
   FMinCount := 0;
   FMaxCount := 0;
   FObjAddress := Anobj;
   FRefCount := 0;
   FInfo := [];
end;
procedure TTrackedItem.AddReference;
begin
   inc(FRefCount);
if FMaxCount < FRefCount then FMaxCount := FRefCount;
end;
procedure TTrackedItem.RemoveReference;
begin
   Dec(FRefCount);
if FMinCount > FRefCount then FMinCount := FRefCount;
end;
{
**************************** TTrackerLog ****************************
}

constructor TTrackerLog.Create;
begin
raise Exception.CreateFmt(‘Access class %s through Instance only’, [ClassName]);
inherited create;
end;
constructor TTrackerLog.CreateInstance;
begin
inherited Create;
   FList := TObjectList.create(true);
   FLock := TMultiReadExclusiveWriteSynchronizer.create;
end;
destructor TTrackerLog.Destroy;
begin
if AccessInstance(0) = Self then AccessInstance(2);
   FreeAndNil(FLock);
   FreeAndNil(FList);
inherited Destroy;
end;
class function TTrackerLog.AccessInstance(Request: Integer): TTrackerLog;
{$WRITEABLECONST ON}
const FInstance: TTrackerLog = nil;
{$WRITEABLECONST OFF}
begin
case Request of
0 : ;
1 : if not Assigned(FInstance) then FInstance := CreateInstance;
2 : FInstance := nil;
else
raise Exception.CreateFmt(‘Illegal request %d in AccessInstance’, [Request]);
end;
   Result := FInstance;
end;
procedure TTrackerLog.AddReference(AnObj : TObject; TrackedClassName : string);
var
   i: Integer;
   Item: TTrackedItem;
begin
   FLock.BeginWrite;
try
     i := FList.count -1;
while (i >= 0) and (TTrackedItem(FList.items[i]).ObjAddress <> AnObj) do dec(i);
if i = -1 then // not found
begin
       item := TTrackedItem.create(AnObj);
       item.Info := Item.Info + [iiCreationOnAdd];
       item.Desc := TrackedClassName;
       Flist.add(item);
end
else
       item := TTrackedItem(FList.items[i]);
     item.AddReference;
finally
   &nbsp
; FLock.EndWrite
end;
end;
procedure TTrackerLog.Dump(const AFilename : widestring);
var
   FName: string;
   i: Integer;
   Item: TTrackedItem;
   fs: TFileStream;
   t: string;
begin
   FLock.BeginRead;
try
//  FName := GetModuleName(HInstance);
//  FName := ChangeFileExt(FName,formatdatetime(‘.hhnnss’,now)) + ‘.csv’;
     FName := AFilename;
if FList.Count > 0 then
begin
       fs := TFileStream.create(fname,fmCreate);
       t := ‘”Address”,”ClassName”,”RefCount&quo
t;,”Max”,”Min”‘ + #13#10;
       fs.Write(t[1],length(t));
for i := 0 to FList.count -1 do
begin
         item := TTrackedItem(FList.items[i]);
         t := format(‘”%x”,”%s”,%d,%d,%d’,[integer(item.
ObjAddress),item.Desc, item.RefCount, item.MaxCount, item.MinCount]) + #13#10;
         fs.Write(t[1],length(t));
end;
       fs.free;
end;
finally
     FLock.EndRead;
end;
end;
class function TTrackerLog.Instance: TTrackerLog;
begin
   Result := AccessInstance(1);
end;
class procedure TTrackerLog.ReleaseInstance;
begin
   AccessInstance(0).Free;
end;
procedure TTrackerLog.ReleaseReference(AnObj : TObject; TrackedClassName : string);
var
   i: Integer;
   Item: TTrackedItem;
begin
   FLock.BeginWrite;
try
     i := FList.count – 1;
while (i >= 0) and (TTrackedItem(FList.items[i]).ObjAddress <> AnObj) do dec(i);
if i = -1 then // not found
begin
       item := TTrackedItem.create(AnObj);
       item.Info := Item.Info + [iiCreationOnRelease];
       item.Desc := TrackedClassName;
       Flist.add(item);
end
else
       item := TTrackedItem(FList.items[i]);
     item.RemoveReference;
finally
     FLock.EndWrite
end;
end;
initialization
finalization
if TTrackerLog.Instance <> nil then TTrackerLog.ReleaseInstance;
end.

Categories
Delphi

Creating a TRect published property that can be changed in the Object Inspector

The object inspector has an inbuilt ability to edit any TPersistent based object.
It uses RTTI to inspect the published properties of the TPersistent descendant and displays them as an expandable property. This is how the constraints property is implemented for example.

The following unit provides an implementation of an editable TRect. I use this in controls for specifing margins for example.

unit PersistentRect;
interface
uses
   Classes, SysUtils, types;
type
   TPersistentRect = class(TPersistent)
private
     FRect : TRect;
     FOnChange: TNotifyEvent;
function GetRect: TRect;
procedure SetRect(const Value: TRect);
procedure SetRectBottom(const Value: integer);
procedure SetRectLeft(const Value: integer);
procedure SetRectRight(const Value: integer);
procedure SetRectTop(const Value: integer);
protected
procedure AssignTo(Dest: TPersistent); override;
public
property AsRect : TRect read GetRect Write SetRect;
constructor create; virtual;
published
property Left : integer read FRect.Left write SetRectLeft;
property Top : integer read FRect.Top write SetRectTop;
property Right : integer read FRect.Right write SetRectRight;
property Bottom : integer read FRect.Bottom write SetRectBottom;
property OnChange : TNotifyEvent read FOnChange write FOnChange;
end;
implementation
{ TPersistentRect }
procedure TPersistentRect.AssignTo(Dest: TPersistent);
begin
if Dest is TPersistentRect then
with TPersistentRect(Dest) do
begin
       AsRect := Self.AsRect;
end
else inherited AssignTo(Dest);
end;
constructor TPersistentRect.create;
begin
inherited;
   FOnChange := nil;
end;
function TPersistentRect.GetRect: TRect;
begin
   result := FRect;
end;
procedure TPersistentRect.SetRect(const Value: TRect);
begin
   FRect.Left := value.left;
   FRect.top := value.top;
   FRect.right := value.right;
   FRect.bottom := value.bottom;
if assigned(FOnChange) then FOnChange(self);
end;
procedure TPersistentRect.SetRectBottom(const Value: integer);
begin
   FRect.Bottom := Value;
if assigned(FOnChange) then FOnChange(self);
end;
procedure TPersistentRect.SetRectLeft(const Value: integer);
begin
   FRect.Left := Value;
if assigned(FOnChange) then FOnChange(self);
end;
procedure TPersistentRect.SetRectRight(const Value: integer);
begin
   FRect.Right := Value;
if assigned(FOnChange) then FOnChange(self);
end;
procedure TPersistentRect.SetRectTop(const Value: integer);
begin
   FRect.Top := Value;
if assigned(FOnChange) then FOnChange(self);
end;
end.

Example of usage
In the Component that you want an editable rect for add the following :

 TPanelWithMargins = class(TCustomPanel)
private
{ Private declarations }
     FMargins: TPersistentRect;
protected
{ Protected declarations }
procedure MarginsChanged(Sender : TObject);
procedure AdjustClientRect(var Rect: TRect); override;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
{ Published declarations }
property Margins : TPersistentRect read FMargins write FMargins;
end;
procedure TPanelWithMargins.AdjustClientRect(var Rect: TRect);
begin
inherited;
   Rect.Left := rect.Left + FMargins.Left;
   Rect.Top := rect.top + FMargins.Top;
   Rect.Right := rect.Right – FMargins.Right;
   Rect.Bottom := rect.Bottom – FMargins.Bottom;
end;
constructor TPanelWithMargins.Create(AOwner: TComponent);
begin
inherited;
   FMargins := TPersistentRect.create;
   FMargins.OnChange := MarginsChanged;
   FMargins.AsRect := Rect(2,2,2,2);
end;
destructor TPanelWithMargins.Destroy;
begin
   FreeAndNil(FMargins);
inherited;
end;
procedure TPanelWithMargins.MarginsChanged(Sender: TObject);
begin
   Realign;
end;