So, once again I am learning new things and I came across Smart Pointers. I had code
procedure TForm3.BitBtn1Click(Sender: TObject);
var
_StringList: ISmartPointer<TStringList>;
begin
_StringList := TSmartPointer<TStringList>.Create(TStringList.Create);
end;
As you see variable declaration is kinda odd, and simplification is needed. I came across another solution
procedure TForm3.btnDelphiClick(Sender: TObject);
var
_StringList: TStringList;
begin
_StringList := SmartGuard.SmartGuard<TStringList>(TStringList.Create(False));
end;
Sadly, it does not work with parameterless constructor
procedure TForm3.btnDelphiClick(Sender: TObject);
var
_StringList: TStringList;
begin
_StringList := SmartGuard.SmartGuard<TStringList>(TStringList.Create);
end;
[dcc32 Error] Main.pas(47): E2089 Invalid typecast
Am I out of luck here?
P.S. I know some of you would argue I should stick to try..finally block, but this is out of curiosity.
unit SmartGuard;
interface
type
IGuard = interface
['{CE522D5D-41DE-4C6F-BC84-912C2AEF66B3}']
end;
TGuard = class(TInterfacedObject, IGuard)
private
FObject: TObject;
public
constructor Create(AObject: TObject);
destructor Destroy; override;
end;
SmartGuard<T: class> = record
private
FGuard: IGuard;
FGuardedObject: T;
public
class operator Implicit(GuardedObject: T): SmartGuard<T>;
class operator Implicit(Guard: SmartGuard<T>): T;
end;
implementation
uses
{Delphi}
System.SysUtils
{Project}
;
constructor TGuard.Create(AObject: TObject);
begin
FObject := AObject;
end;
destructor TGuard.Destroy;
begin
FObject.Free;
inherited;
end;
{ SmartGuard }
class operator SmartGuard<T>.Implicit(GuardedObject: T): SmartGuard<T>;
begin
Result.FGuard := TGuard.Create(GuardedObject);
Result.FGuardedObject := GuardedObject;
end;
class operator SmartGuard<T>.Implicit(Guard: SmartGuard<T>): T;
begin
Result := Guard.FGuardedObject;
end;
end.
I would love to find a solution that would not require additional "method" calling as in here https://github.com/marcocantu/DelphiSessions/blob/master/DelphiLanguageCodeRage2018/02_SmartPointers/SmartPointerClass.pas e.g. _StringList.Value.Add('foo'); and "special" brackets e.g. _StringList := TSmartPointer<TStringList>.Create(TStringList.Create)();
The compiler needs help disambiguating
TStringList.Create
The compiler doesn't know whether this is a reference to a method, or a call to the method.
Disambiguate by adding parens to indicate that it is a call.
_StringList := SmartGuard.SmartGuard<TStringList>(TStringList.Create());
Related
I simplified the following code at the maximum to only show my problem.
When the destructor TClass3.Destroy is done, the action FreeAndNil(FClass3) causes a problem and the program stops. If have a look in the Heap.trc file I can read
Marked memory at $0000000001528FD0 invalid
Wrong signature $2951FD2D instead of 5C063D8B
program Project_testFree;
{$mode objfpc}{$H+}
uses
sysutils;
type
TClass1 = class
private
protected
public
constructor Create;
end;
TClass2 = class(TClass1)
private
protected
FTClass2 : cardinal;
public
end;
TClass3 = class
private
protected
FClass3 : TClass1;
public
constructor Create;virtual;
destructor Destroy;override;
end;
TClass4 = class(TClass3)
private
function GetLocalClass2: TClass2;
protected
public
constructor Create;override;
destructor destroy;override;
property pClass2:TClass2 read GetLocalClass2;
end;
constructor TClass1.Create;
begin
inherited;
end;
constructor TClass3.Create;
begin
FClass3 := TClass1.create;
end;
destructor TClass3.Destroy;
begin
FreeAndNil(FClass3);
writeln('Destroy');
inherited Destroy;
end;
constructor TClass4.Create;
begin
inherited Create;
pClass2.FTClass2 := 1;
end;
destructor TClass4.destroy;
begin
inherited destroy;
end;
function TClass4.GetLocalClass2: TClass2;
begin
result := TClass2(FClass3);
end;
var
c:TClass4;
begin
if FileExists('heap.trc') then
DeleteFile('heap.trc');
SetHeapTraceOutput('heap.trc');
c:=TClass4.Create;
c.free;
end.
I use Lazarus 1.6.2.
Thanks for your help.
You cast FClass3 to be TClass2. But you instantiated TClass1. The cast is therefore incorrect which would explain the error. Essentially, you lied to the compiler and it exacted its revenge.
Had you used a checked cast, using as, then a runtime error would have been raised.
I am creating a program that moves through an array of records and save these student records to a file.
However I now wish to reload the data (StudentName,Class,Grade) back into the array and subsequently display them in a list box on another form.
I have tried a few methods but with no success.
This is the code that wrote the file:
unit NewStudent;
{$mode objfpc}{$H+}
interface
uses
Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, StdCtrls,
ExtCtrls, studentdata;
{ TFormNewStudent }
Type
TFormNewStudent = class(TForm)
Button1: TButton;
ButtonAddStudent: TButton;
Button3: TButton;
ComboBoxPredictedGrade: TComboBox;
EditClass: TEdit;
EditName: TEdit;
procedure Button1Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure ButtonAddStudentClick(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
{ private declarations }
public
{ public declarations }
end;
Type
TPupil = Record
Name:String[30];
ClassGroup:String;
ComboBoxPredictedGrade:Integer;
end;
var
FormNewStudent: TFormNewStudent;
StudentRecArray : Array[1..30] of TPupil;
StudentNo:integer;
studentFile:TextFile;
implementation
{$R *.lfm}
{ TFormNewStudent }
procedure TFormNewStudent.Button1Click(Sender: TObject);
begin
FormStudentData.visible:=true;
FormNewStudent.visible:=false;
end;
procedure TFormNewStudent.Button3Click(Sender: TObject);
begin
FormStudentData.visible:=False;
FormNewStudent.visible:=True;
end;
procedure TFormNewStudent.ButtonAddStudentClick(Sender: TObject);
var
newStudent:string;
Begin
assignfile(studentFile,'G:\ExamGen\studentfile.txt');
StudentRecArray[StudentNo].Name:=EditName.text;
StudentRecArray[StudentNo].ClassGroup:=EditClass.text;
StudentRecArray[StudentNo].ComboBoxPredictedGrade:=ComboBoxPredictedGrade.ItemIndex;
append(studentFile);
newStudent:=(StudentRecArray[StudentNo].Name)+','+(StudentRecArray[StudentNo].ClassGroup)+','+(IntToStr(StudentRecArray[StudentNo].ComboBoxPredictedGrade));
writeln(studentFile,newStudent);
closefile(StudentFile);
StudentNo := StudentNo + 1;
end;
procedure TFormNewStudent.FormCreate(Sender: TObject);
begin
ComboBoxPredictedGrade.Items.Add('A');
ComboBoxPredictedGrade.Items.Add('B');
ComboBoxPredictedGrade.Items.Add('C');
ComboBoxPredictedGrade.Items.Add('D');
ComboBoxPredictedGrade.Items.Add('E');
ComboBoxPredictedGrade.Items.Add('U');
end;
end.
ScreenShot 1: StudentFile
ScreenShot 2: AddStudent Form
Answer given by Zamrony P. Juhara is correct, but your approach here may be not the most convenient. You define record which contains information about each student, then you write procedures to write this record to file and another one to read it. If you'll eventually change format of your record, you'll have to rewrite this code also. There are better ways, in my opinion.
You can define record containing only simplest members, like Ken White suggested:
TPupil = Record
Name:String[30];
ClassGroup:String[20]; //some convenient value
ComboBoxPredictedGrade:Integer;
end;
Such a record have fixed size and contains all needed information in itself (version with ClassGroup:String actually stores pointer to another area in memory where your string is), and then you can save and load it extremely easy:
var
myFile : File of TPupil;
procedure TFormNewStudent.ButtonAddStudentClick(Sender: TObject);
Begin
assignfile(studentFile,'G:\ExamGen\studentfile.txt');
StudentRecArray[StudentNo].Name:=EditName.text;
StudentRecArray[StudentNo].ClassGroup:=EditClass.text;
StudentRecArray[StudentNo].ComboBoxPredictedGrade:=ComboBoxPredictedGrade.ItemIndex;
append(studentFile);
Write(studentFile,StudentRecArray[StudentNo]); //THAT'S IT!
closefile(StudentFile);
inc(StudentNo);
end;
procedure TFormNewStudent.ReadFromFile;
begin
AssignFile(myFile,'G:\ExamGen\studentfile.txt');
Reset(studentFile);
StudentNo:=1;
while not Eof(studentFile) do begin
Read(studentFile,StudentRecArray[i]);
inc(StudentNo);
end;
end;
There is little drawback: file is not so readable as it was before, because Integer is saved exactly as 4-byte value, not its decimal representation.
There is much more interesting possibilities if you move from record to class, in that case you can use streaming system in a way as IDE saves forms to disc, in .dfm or .lfm files, so you'll be able to automatically save complex ierarchies of objects and load them back.
var
myFile : TextFile;
text : string;
lines : TStringList;
i : integer;
...
lines := TStringList.Create();
AssignFile(studentFile,'G:\ExamGen\studentfile.txt');
Reset(studentFile);
i:=1;
while not Eof(studentFile) do
begin
ReadLn(studentFile, text);
lines.CommaText := text;
studentRecArray[i].Name := lines[0];
studentRecArray[i].ClassGroup := lines[1];
studentRecArray[i].ComboBoxPredictedGrade := StrToInt(lines[2]);
inc(i);
end;
CloseFile(studentFile);
lines.Free();
In order to learn multithreading, I've created a thread inside a COM Thread (TRemoteDataModule).
This is my Component Factory:
TComponentFactory.Create(ComServer, TServerConn2, Class_ServerConn2, ciMultiInstance, tmApartment);
Inside the Thread, I didn't needed to Call CoInitialize to use TADOQuery.Create, .Open... .Exec
I read that I need to initialize the COM library on a thread before you call any of the library functions except CoGetMalloc, to get a pointer to the standard allocator, and the memory allocation functions.
But in this case, the absence of CoInitialize didn't brought me any trouble.
Is this related with Thread Model?
Where can I Find the explanation for this subject?
UPDATE:
When I say INSIDE, it means inside the COM method context:
interface
type
TWorker = class(TThread);
TServerConn2 = class(TRemoteDataModule, IServerConn2)
public
procedure Method(); safecall;
end;
implementation
procedure TServerConn2.Method();
var W: TWorker;
begin
W := TWorkerTread.Create(Self);
end;
UPDATE 2:
The TADOConnection used to connect to database are currently being created in the COM Thread context (TThread.Create constructor). Although, TADOConnection.Open and TADOQuery.Create/.Open are both being performed inside TThread.Execute .
UPDATE 3 - Simulacrum
Interface:
type
TServerConn2 = class;
TWorker = class(TThread)
private
FDB: TADOConnection;
FOwner: TServerConn2;
protected
procedure Execute; override;
public
constructor Create(Owner: TServerConn2);
destructor Destroy; override;
end;
TServerConn2 = class(TRemoteDataModule, IServerConn2)
ADOConnection1: TADOConnection;
procedure RemoteDataModuleCreate(Sender: TObject);
private
{ Private declarations }
protected
class procedure UpdateRegistry(Register: Boolean; const ClassID, ProgID: string); override;
procedure CheckException; safecall;
public
User, Pswd, Str: String;
Ok: Boolean;
end;
Implementation:
class procedure TServerConn2.UpdateRegistry(Register: Boolean; const ClassID, ProgID: string);
begin
if Register then
begin
inherited UpdateRegistry(Register, ClassID, ProgID);
EnableSocketTransport(ClassID);
EnableWebTransport(ClassID);
end else
begin
DisableSocketTransport(ClassID);
DisableWebTransport(ClassID);
inherited UpdateRegistry(Register, ClassID, ProgID);
end;
end;
{ TWorker }
constructor TWorker.Create(Owner: TServerConn2);
begin
inherited Create(False);
FreeOnTerminate := True;
FDB := TADOConnection.Create(nil);
FOwner := Owner;
end;
destructor TWorker.Destroy;
begin
FDB.Free;
FOwner.Ok := True;
inherited;
end;
procedure TWorker.Execute;
var Qry: TADOQuery;
begin
FDB.LoginPrompt := False;
FDB.ConnectionString := FOwner.Str;
FDB.Open(FOwner.User, FOwner.Pswd);
Qry := TADOQuery.Create(nil);
try
Qry.Connection := FDB;
Qry.LockType := ltReadOnly;
Qry.SQL.Text := 'SELECT TOP 1 * FROM MyTable';
Qry.Open;
finally
Qry.Free;
end;
end;
procedure TServerConn2.CheckException;
var W: TWorker;
begin
W := TWorker.Create(Self);
while not Ok do Sleep(100);
end;
procedure TServerConn2.RemoteDataModuleCreate(Sender: TObject);
begin
User := 'user';
Pswd := 'pass';
Str := ADOConnection1.ConnectionString;
end;
initialization
TComponentFactory.Create(ComServer, TServerConn2,
Class_ServerConn2, ciMultiInstance, tmApartment);
end.
UPDATE 4
The error should happen here:
function CreateADOObject(const ClassID: TGUID): IUnknown;
var
Status: HResult;
FPUControlWord: Word;
begin
asm
FNSTCW FPUControlWord
end;
Status := CoCreateInstance(ClassID, nil, CLSCTX_INPROC_SERVER or
CLSCTX_LOCAL_SERVER, IUnknown, Result);
asm
FNCLEX
FLDCW FPUControlWord
end;
if (Status = REGDB_E_CLASSNOTREG) then
raise Exception.CreateRes(#SADOCreateError) else
OleCheck(Status);
end;
By somehow (because of TComponentFactory maybe?) CoCreateInstance identifies that TWorker is in the same context than TServerConn2 and don't raise errors?
Either or both of the following might apply:
On a thread not initialized with COM all existing interface pointers keep working until you make a COM API call or otherwise require COM marshalling which then fails detecting an uninitialized thread. That is, your "didn't brought me any trouble" might actually be too early to say.
If any thread in the process calls CoInitialize[Ex] with the COINIT_MULTITHREADED flag, then that not only initializes the current thread as a member of the multi-threaded apartment, but it also says, "Any thread which has never called CoInitialize[Ex] is also part of the multi-threaded apartment." - so called impicit MTA thing
The TADOConnection used to connect to database are currently being created in the COM Thread context (TThread.Create constructor). Although, TADOConnection.Open and TADOQuery.Create/.Open are both being performed inside TThread.Execute .
That will not work, for 2 reasons:
TWorker.Create() and TWorker.Execute() will run in different thread contexts. Create() will run in the context of the thread that is calling TServerConn2.CheckException() (which will have already called CoInitialize/Ex() on itself beforehand), but Execute() will run in the context of the TThread thread instead. ADO is apartment threaded, which means its COM interfaces cannot be used across thread/apartment boundaries unless you marshal them, either via the IGlobalInterfaceTable interface or the CoMarshalInterThreadInterfaceInStream() and CoGetInterfaceAndReleaseStream() functions.
even if you did marshal the ADO interfaces, TWorker.Execute() must call CoInitialize/Ex() on itself. EVERY individual thread must initialize COM to establish its threading model before then accessing any COM interfaces. The threading model dictates how COM accesses interfaces (direct or through proxies), whether message queues are used, etc.
So the simple solution to your problem is to NOT create and use the ADO components across thread boundaries at all. Move your TADOConnection into Execute() instead:
constructor TWorker.Create(Owner: TServerConn2);
begin
inherited Create(False);
FreeOnTerminate := True;
FOwner := Owner;
end;
destructor TWorker.Destroy;
begin
FOwner.Ok := True;
inherited;
end;
procedure TWorker.Execute;
var
DB: TADOConnection;
Qry: TADOQuery;
begin
CoInitialize;
try
DB := TADOConnection.Create(nil);
try
DB.LoginPrompt := False;
DB.ConnectionString := FOwner.Str;
DB.Open(FOwner.User, FOwner.Pswd);
Qry := TADOQuery.Create(nil);
try
Qry.Connection := DB;
Qry.LockType := ltReadOnly;
Qry.SQL.Text := 'SELECT TOP 1 * FROM MyTable';
Qry.Open;
finally
Qry.Free;
end;
finally
DB.Free;
end;
finally
CoUninitialize;
end;
end;
When you create an apartment thread using TComponentFactory it calls CoInitialize and CoUnInitialize for you - it's right in the VCL source (System.Win.VCLCom.pas):
procedure TApartmentThread.Execute;
var
msg: TMsg;
Unk: IUnknown;
begin
try
CoInitialize(nil); // *** HERE
try
FCreateResult := FFactory.CreateInstanceLic(FUnkOuter, nil, FIID, '', Unk);
FUnkOuter := nil;
FFactory := nil;
if FCreateResult = S_OK then
CoMarshalInterThreadInterfaceInStream(FIID, Unk, IStream(FStream));
ReleaseSemaphore(FSemaphore, 1, nil);
if FCreateResult = S_OK then
while GetMessage(msg, 0, 0, 0) do
begin
DispatchMessage(msg);
Unk._AddRef;
if Unk._Release = 1 then break;
end;
finally
Unk := nil;
CoUninitialize; // ** AND HERE
end;
except
{ No exceptions should go unhandled }
end;
end;
I am in front of following problem:
My Main programming Language is C++ with the Qt4 Library, but now I have to write a Pascal Wrapper, which should give the possibility to use the functions of a C DLL in Pascal.
Now I want to make it possible to invoke a method from any Pointer. But I can't find a Pascal method to invoke a method. I want something like the QMetaObject::invokeMethod method in QT. I got following code:
unit CgPConnect;
//{$mode objfpc}{$H+}
{$mode delphi}
interface
uses
Classes, SysUtils, dynlibs;
type
Callback = Record
var callbackObject: Pointer;
var objectFunction: string;
end;
CallbackObject = Record
var objectName: string;
var callback: Callback;
end;
MutableObject = Object
var name: string;
var state: string;
var properties: array of VariantMap;
var annotations: array of VariantMap;
end;
PConnect = Class
constructor create(connectorPath: string);
destructor destroy;
private
var hostactionCallbacks: array of CallbackObject;
var mConnectorPath: string;
var mConnectorLibrary: TLibHandle;
function loadConnectorLibrary: Boolean;
public
procedure registerCallbackForHostaction(objectName, objectFunction: string; callbackObject: pointer);
procedure callHostactionCallback(receivedObject :MutableObject);
var mLibraryLoaded: Boolean;
end;
implementation
constructor PConnect.create(connectorPath: string);
begin
mConnectorPath:= connectorPath;
mLibraryLoaded:= false;
//Eventuell noch slash hinzufügen
mLibraryLoaded:= loadConnectorLibrary;
end;
destructor PConnect.destroy;
begin
UnloadLibrary(mConnectorLibrary);
end;
procedure PConnect.registerCallbackForHostaction(objectName, objectFunction: string; callbackObject: pointer);
var c: Callback;
var callbackCount: integer;
begin
if mLibraryLoaded = true then
begin
c.callbackObject:= callbackObject;
c.objectFunction:= objectFunction;
callbackCount:= Length(hostactionCallbacks)+1;
SetLength(hostactionCallbacks, callbackCount);
hostactionCallbacks[callbackCount].objectName:= objectName;
hostactionCallbacks[callbackCount].callback:= c;
end;
end;
procedure PConnect.callHostactionCallback(receivedObject :MutableObject);
var receivedObjectName, objectFunction: string;
var i, count: integer;
var callbackObject: pointer;
begin
if mLibraryLoaded = true then
begin
receivedObjectName:= receivedObject.name;
count:= Length(hostactionCallbacks);
for i:=0 to count do
begin
if hostactionCallbacks[i].objectName = receivedObjectName
begin
objectFunction:= hostactionCallbacks[i].callback.objectFunction;
callbackObject:= hostactionCallbacks[i].callback.callbackObject;
if callbackObject <> 0 then
//INVOKE METHOD (objectFunction) OF OBJECT (callbackObject)
end;
end;
end;
end;
end.
I would be happy about a fast answer :)
You cannot directly and portably call a C++ method from Pascal. If your callbackfunction is a C++ object, forget it.
Otherwise fill a TMethod object and cast that to the proper "procedure of object" declaration. Don't forget the calling convention.
For more bizarre solutions you might want to have a look at (RemObjects') Pascalscript.
P.s. this is the same that you can't even call a C++ method reliably from another C++ compiler. It is not Pascal vs C++ per se.
Complete source code can be found here:
http://www.eyeClaxton.com/download/delphi/SkinProject.zip
I'm trying to create a skinned form with no "Caption or Borders", but still leaving me with the full access to System Menu (I.E: Move, Minimize, Maximize, Restore and Size). I can achieve all of the menu items by overriding the CreateParams procedure by using WS_SYSMENU, WS_MAXIMIZEBOX, WS_MINIMIZEBOX. Using the WS_SIZEBOX gives me access to the menu "Size" command but paints a border I do not want. I have included a complete (Delphi 7) example in the link above. If more information is needed, please feel free to ask.
procedure TMainFrm.CreateParams(var Params: TCreateParams);
begin
FormStyle := fsNormal;
try
if (BorderIcons <> []) then BorderIcons := [];
if (BorderStyle <> bsNone) then BorderStyle := bsNone;
inherited CreateParams(Params);
Params.ExStyle := (Params.ExStyle and (not WS_EX_WINDOWEDGE)
and (not WS_EX_STATICEDGE) and (not WS_EX_DLGMODALFRAME) and (not WS_EX_CLIENTEDGE));
Params.Style := (Params.Style and (not WS_CAPTION) and (not DS_MODALFRAME)
and (not WS_DLGFRAME) and (not WS_THICKFRAME));
Params.Style := (Params.Style or WS_SYSMENU or WS_MAXIMIZEBOX or WS_MINIMIZEBOX or WS_SIZEBOX);
finally
Position := poScreenCenter;
end;
end;
SOLUTION:
unit WndProcUnit;
interface
uses
Windows, Messages, Classes, Controls, Forms, SysUtils;
type
EWndProc = class(Exception);
TWndProcMessages = class(TComponent)
private
{ Private declarations }
FOwnerWndProc: TFarProc;
FNewWndProc: TFarProc;
protected
{ Protected declarations }
procedure WndProc(var theMessage: TMessage); virtual;
public
{ Public declarations }
constructor Create(theOwner: TComponent); override;
destructor Destroy(); override;
procedure DefaultHandler(var theMessage); override;
end;
TWndProc = class(TWndProcMessages)
private
{ Private declarations }
protected
{ Protected declarations }
procedure Loaded(); override;
public
{ Public declarations }
constructor Create(theOwner: TComponent); override;
destructor Destroy(); override;
published
{ Published declarations }
end;
implementation
{ TWndProcMessages }
constructor TWndProcMessages.Create(theOwner: TComponent);
var
X, I: Integer;
begin
inherited Create(theOwner);
if (not (Owner is TForm)) then
raise EWndProc.Create('TWndProc parent must be a form!');
I := 0;
for X := 0 to (Owner.ComponentCount - 1) do
begin
if (Owner.Components[X] is TWndProc) then Inc(I);
if (I > 1) then Break;
end;
if (I > 1) then
begin
raise EWndProc.Create('The form already contains a TWndProc!');
end
else begin
FOwnerWndProc := TFarProc(GetWindowLong((Owner as TForm).Handle, GWL_WNDPROC));
FNewWndProc := Classes.MakeObjectInstance(WndProc);
if (not (csDesigning in ComponentState)) then
SetWindowLong((Owner as TForm).Handle, GWL_WNDPROC, LongInt(FNewWndProc));
end;
end;
destructor TWndProcMessages.Destroy();
begin
if Assigned(FNewWndProc) then
try
Classes.FreeObjectInstance(FNewWndProc);
finally
if (Pointer(FNewWndProc) <> nil) then Pointer(FNewWndProc) := nil;
end;
if Assigned(FOwnerWndProc) then Pointer(FOwnerWndProc) := nil;
inherited Destroy();
end;
procedure TWndProcMessages.DefaultHandler(var theMessage);
begin
if ((Owner as TForm).Handle <> 0) then
begin
case TMessage(theMessage).Msg of
WM_DESTROY:
SetWindowLong((Owner as TForm).Handle, GWL_WNDPROC, LongInt(FOwnerWndProc));
WM_INITMENU:
EnableMenuItem(TMessage(theMessage).WParam, SC_SIZE, MF_BYCOMMAND or MF_ENABLED);
else
with TMessage(theMessage) do
Result := CallWindowProc(FOwnerWndProc, (Owner as TForm).Handle, Msg, WParam, LParam);
end;
end
else
inherited DefaultHandler(theMessage);
end;
procedure TWndProcMessages.WndProc(var theMessage: TMessage);
begin
Dispatch(theMessage);
end;
{ TWndProc }
constructor TWndProc.Create(theOwner: TComponent);
begin
inherited Create(theOwner);
end;
destructor TWndProc.Destroy();
begin
inherited Destroy();
end;
procedure TWndProc.Loaded();
begin
inherited Loaded();
if (not (csDesigning in ComponentState)) then
GetSystemMenu((Owner as TForm).Handle, False);
end;
end.
Complete "updated" source code can be found here:
http://www.eyeClaxton.com/download/delphi/SkinProject.zip
Instead of having a border-less form and faking borders and caption all in the client area, the correct way to do this would be to handle WM_NCPAINT and draw your caption and border in the non-client area. Then, you wouldn't have to use an undocumented message to show the system menu on a caption-less window, or try to have the 'size' system menu item enabled on a window without a sizing border.
Anyway, if you want a quick workaround, enable the item yourself:
type
TMainFrm = class(TForm)
[...]
procedure FormCreate(Sender: TObject);
private
procedure WmInitMenuPopup(var Msg: TWMInitMenuPopup); message WM_INITMENUPOPUP;
[...]
procedure TMainFrm.FormCreate(Sender: TObject);
begin
GetSystemMenu(Handle, False); // force a copy of the system menu
[...]
end;
procedure TMainFrm.WmInitMenuPopup(var Msg: TWMInitMenuPopup);
begin
inherited;
if Msg.SystemMenu then
EnableMenuItem(Msg.MenuPopup, SC_SIZE, MF_BYCOMMAND or MF_ENABLED);
end;
PS:
In the code sample in the question, you're excluding WS_THICKFRAME, but including WS_SIZEBOX. They're, in fact, the same flag.
You've got a bit of a weird try-finally in your CreateParams. Form positioning have got nothing to do with the preceding code, you can put the 'Position := ' statement just before or after setting 'FormStyle' and drop the try-finally.