I have searched and searched but found no examples. I would like to dynamically create PageControl instances each with their own setoff TTabsheets. I get no complaints from the Delphi IDE, however I do get:
Access violation in module FormApplication.exe write of address 00000000
Is there something I am missing?
procedure TForm1.FormCreate(Sender: TObject);
type
ABC_Status_Object = record
ABC_PageControl_instance: TPageControl;
quickStat_instance: TTabsheet;
detailStat_instance: TTabsheet;
abc_light: TShape;
end;
var
ABC_Status: array of ABC_Status_Object;
I: Integer;
Frac, Icnt: Extended;
begin
inifile := TIniFile.Create(ChangeFileExt(Application.ExeName,'.ini'));
loadGlobalConfig;
Frac := 100/NUM/100;
for I := 0 to NUM do
begin
{// Create the Tabs: }
ABC_Status[I].ABC_PageControl_instance := TPageControl.Create(self);
ABC_Status[I].ABC_PageControl_instance.Parent := self;
ABC_Status[I].quickStat_instance := TTabsheet.Create(ABC_Status[I].ABC_PageControl_instance);
ABC_Status[I].detailStat_instance := TTabsheet.Create(ABC_Status[I].ABC_PageControl_instance);
ABC_Status[I].quickStat_instance.PageControl := ABC_Status[I].ABC_PageControl_instance;
ABC_Status[I].detailStat_instance.PageControl := ABC_Status[I].ABC_PageControl_instance;
{// Set the attributes of each instance of PageControl, including the tabs: }
ABC_Status[I].ABC_PageControl_instance.Visible := TRUE;
ABC_Status[I].ABC_PageControl_instance.Top := 0;
if(NUM = 1) then
ABC_Status[I].ABC_PageControl_instance.Width := ClientWidth;
if(NUM > 1) AND (NUM < 4) then
begin
Icnt := 100/(I+1)/100;
ABC_Status[I].ABC_PageControl_instance.Width := Trunc(ClientWidth*Frac);
ABC_Status[I].ABC_PageControl_instance.Left := 30;
end;
ABC_Status[I].quickStat_instance.Caption := 'Quick Status';
ABC_Status[I].quickStat_instance.Visible := TRUE;
ABC_Status[I].detailStat_instance.Caption := 'Details';
ABC_Status[I].detailStat_instance.Visible := TRUE;
end;
end;
You did not allocate the array. You need to add the following before you access the array:
SetLength(ABC_Status, NUM+1);
The +1 is because of the rather non-standard loop bounds that you used.
Also 100/100 = 1 and so the expression 100/NUM/100 seems odd. You may as well write 1/NUM.
Your use of the non-standard Extended type also seems strange. I don't see much need for that.
There are probably lots more errors, but I'm stopping here.
Related
today I've a question about streaming a part of a form to a file.
In this example i use a Tmemo instead of file in order to see the stream.
here is my form:
The panel on the right top of the form has some controls, like label, edit and so on.
with the "Save panel" butto I save the panel on a TStream:
Here the code:
procedure TfrmMain.btnSaveClick(Sender: TObject);
var
idx: Integer;
MemStr: TStream;
begin
MemStr := TMemoryStream.Create;
PanelStr := TMemoryStream.Create;
try
for idx := 0 to pnlSource.ControlCount - 1 do begin
MemStr.Position := 0;
MemStr.WriteComponent(pnlSource.Controls[idx]);
StreamConvert(MemStr);
end;
PanelStr.Position := 0;
mmoStream.Lines.LoadFromStream(PanelStr);
finally
MemStr.Free;
end;
end;
and here the StreamConvert:
{ Conversione stream in formato testo }
procedure TfrmMain.StreamConvert(aStream: TStream);
var
ConvStream: TStream;
begin
aStream.Position := 0;
ConvStream := TMemoryStream.Create;
try
ObjectBinaryToText(aStream, ConvStream);
ConvStream.Position := 0;
PanelStr.CopyFrom(ConvStream, ConvStream.Size);
lblStreamSize.Caption := IntToStr(ConvStream.Size);
finally
ConvStream.Free;
end;
end;
PanelStr is a TStream object declared in private section of the form and create during form create.
This part works good and, as you can see in right part of the image the elements present on the form are register correctly.
Now my problem is to restore this element into the panel on the left bottom of the form.
I've tryed this routine:
{ Carica i controlli presenti nel pannello pnlSource in uno stream }
procedure TfrmMain.btnLoadClick(Sender: TObject);
var
idx: Integer;
MemStr: TStream;
begin
pnlSource.Free;
MemStr := TMemoryStream.Create;
try
PanelStr.Position := 0;
ObjectTextToBinary(PanelStr, MemStr);
MemStr.Position := 0;
MemStr.ReadComponent(pnlTarget);
finally
MemStr.Free;
end;
end;
but it doesn't work and in the following picture you can see the result:
What is wrong in my routine, and How can I read all the element present in the stream and not only the first?
Can someone help me in this headache?
The code you are currently running effectively transforms the source panel to a label. That's because the first object streamed is a label and the code is reading only one component. IOW, when the reader reaches the first end, reading is complete since there are no sub controls in the stream.
So, first of all, you have to write the panel - and only the panel. The panel is the one that is supposed to stream it's children. To have it to do so, it must own it's controls.
var
idx: Integer;
MemStr: TStream;
begin
MemStr := TMemoryStream.Create;
PanelStr := TMemoryStream.Create;
try
// transfer ownership of controls to the panel
for idx := 0 to pnlSource.ControlCount - 1 do
pnlSource.InsertComponent(pnlSource.Controls[idx]);
// write the panel
MemStr.WriteComponent(pnlSource);
StreamConvert(MemStr);
PanelStr.Position := 0;
mmoStream.Lines.LoadFromStream(PanelStr);
finally
MemStr.Free;
end;
This produces an output to the memo like this:
object pnlSource: TPanel
Left = 8
Top = 8
Width = 201
Height = 265
Caption = 'pnlSource'
TabOrder = 0
object Label1: TLabel
Left = 48
Top = 208
Width = 31
Height = 13
Caption = 'Label1'
end
object Label2: TLabel
...
Note the indentation of the label definition and the missing 'end' of the owning panel (it's at the end).
You will need to register classes for the streamer to be able to find them when loading:
var
idx: Integer;
MemStr: TStream;
begin
pnlSource.Free;
RegisterClasses([TLabel, TEdit, TCheckBox, TRadioButton]);
MemStr := TMemoryStream.Create;
try
PanelStr.Position := 0;
ObjectTextToBinary(PanelStr, MemStr);
MemStr.Position := 0;
MemStr.ReadComponent(pnlTarget);
finally
MemStr.Free;
end;
Registration can be of course moved to elsewhere, like form creation or unit initialization.
You can also transfer ownership of the controls back to the form if it's required, like in the saving code.
As I put in my comments, you need to surround your data with Panel2 information. You also need to register each control type you are saving and restoring.
This means that only the load procedure needs to change - like this:
procedure TfrmMain.btnLoadClick(Sender: TObject);
var
iTemp, iTemp2 : TStringList;
MemStr: TStream;
i: Integer;
begin
// first read the destination panel an put it into a string list
pnlSource.Free;
iTemp := TStringList.Create;
iTemp2 := TStringList.Create;
iTemp.Duplicates := TDuplicates.dupAccept;
iTemp2.Duplicates := TDuplicates.dupAccept;
MemStr := TMemoryStream.Create;
try
PanelStr.Position := 0;
iTemp2.LoadFromStream( PanelStr ); // our original source
PanelStr.Size := 0;
MemStr.Position := 0;
MemStr.WriteComponent(pnlTarget);
StreamConvert(MemStr);
// PanelStr now has our destination poanel.
PanelStr.Position := 0;
iTemp.LoadFromStream( PanelStr );
for i := 0 to iTemp2.Count - 1 do
begin
iTemp.Insert( ITemp.Count - 1, iTemp2[ i ]);
end;
PanelStr.Size := 0;
iTemp.SaveToStream( PanelStr );
PanelStr.Position := 0;
mmoStream.Lines.LoadFromStream(PanelStr);
MemStr.Size := 0;
PanelStr.Position := 0;
ObjectTextToBinary( PanelStr, MemStr);
MemStr.Position := 0;
RegisterClass( TLabel );
RegisterClass( TPanel );
RegisterClass( TEdit );
RegisterClass( TCheckBox );
RegisterClass( TRadioButton );
MemStr.ReadComponent( pnlTarget );
finally
iTemp.Free;
iTemp2.Free;
MemStr.Free;
end;
end;
As commented in the previous answer, registration can be put somewhere else.
Unlike the previous answer, you do not need to change the ownership of the controls first. (That is just a comment - not a criticism). This is just an implementation of my comment.
My naming conventions are different to yours. I have tried to use the same names, but forgive me if I have missed any.
procedure TForm1.controlClick(Sender: TObject);
var
i: Integer;
begin
for i := 2 to Dest.Count-1 do
begin
img[i-2].Create(Form1);
with img[i-2] do begin
Parent:= Panel1;
Width:= 100;
Height:= 150;
Top:= 10;
Left:= (i-2)*100;
end;
end;
end;
img type is array of TImage, control is a tab. I want to timages to show like an android gallery. But this gives me an error Access Violation.
This looks like the classic error in creating an object. Instead of
obj.Create;
you must write:
obj := TSomeClass.Create;
In your case you need to first of all allocate the array:
SetLength(img, Dest.Count-2);
And then in the loop you write:
img[i-2] := TImage.Create(Form1);
to instantiate the images.
Is there support in the Delphi XE VCL for ensuring only a single instance of an application is running?
In the past, I've used library code to control a Mutex which has always seemed complicated. As I'm starting a new project in Delphi XE, I wonder if I need to dig up that old code, or if there is support built into XE already? Or is there another easy to apply code that is nice and modern?
You create a named Mutex when you start the application. Check GetLastError to see if an other instance is already running.
Put this code right after "begin" in your DPR file. Replace the GUID with one of your own. When I need a text constant that's unlikely to be used for anything else, I usually just hit Ctrl+G to get a GUID!
if CreateMutex(nil, True, '6EACD0BF-F3E0-44D9-91E7-47467B5A2B6A') = 0 then
RaiseLastOSError;
if GetLastError = ERROR_ALREADY_EXISTS then
Exit;
It might look like the code is leaking an handle because it's not saving the return of CreateMutex. It's not. Windows will automatically release the handle when our application is terminated, and that's absolutely fine with us.
I use JCL to do this:
program MyProgram;
uses
JclAppInst;
begin
JclAppInstances.CheckSingleInstance; // Added instance checking
Application.Initialize;
Application.CreateForm(TMainForm, MainForm);
Application.Run;
end.
Documentation for this, and the notification scheme, is at the JCL Wiki.
I use this, works in XE2 through to Alexandria, has the benefit of being able to bring the currently running instance to the front.
Those that say it shouldn't do that, well, given the last thing the user did was to try launch the app, bringing a currently running instance to the front makes sense
unit CheckPrevious;
interface
uses
Windows, SysUtils, WinSock;
function RestoreIfRunning(const AppHandle : THandle; MaxInstances : integer = 1) : boolean;
implementation
type
PInstanceInfo = ^TInstanceInfo;
TInstanceInfo = packed record
PreviousHandle : THandle;
RunCounter : integer;
end;
var
MappingHandle: THandle;
InstanceInfo: PInstanceInfo;
MappingName : string;
RemoveMe : boolean = True;
function RestoreIfRunning(const AppHandle : THandle; MaxInstances : integer = 1) : boolean;
begin
Result := True;
MappingName := StringReplace(ParamStr(0),'\','',[rfReplaceAll, rfIgnoreCase]);
MappingHandle := CreateFileMapping($FFFFFFFF,nil,PAGE_READWRITE,0,SizeOf(TInstanceInfo),PChar(MappingName));
if MappingHandle = 0 then
RaiseLastOSError
else
begin
if GetLastError <> ERROR_ALREADY_EXISTS then
begin
InstanceInfo := MapViewOfFile(MappingHandle,FILE_MAP_ALL_ACCESS,0,0,SizeOf(TInstanceInfo));
InstanceInfo^.PreviousHandle := AppHandle;
InstanceInfo^.RunCounter := 1;
Result := False;
end
else //already runing
begin
MappingHandle := OpenFileMapping(FILE_MAP_ALL_ACCESS, False, PChar(MappingName));
if MappingHandle <> 0 then
begin
InstanceInfo := MapViewOfFile(MappingHandle,FILE_MAP_ALL_ACCESS,0,0,SizeOf(TInstanceInfo));
if InstanceInfo^.RunCounter >= MaxInstances then
begin
RemoveMe := False;
if IsIconic(InstanceInfo^.PreviousHandle) then
ShowWindow(InstanceInfo^.PreviousHandle, SW_RESTORE);
SetForegroundWindow(InstanceInfo^.PreviousHandle);
end
else
begin
InstanceInfo^.PreviousHandle := AppHandle;
InstanceInfo^.RunCounter := 1 + InstanceInfo^.RunCounter;
Result := False;
end
end;
end;
end;
end;
initialization
finalization
//remove one instance
if RemoveMe then
begin
MappingHandle := OpenFileMapping(FILE_MAP_ALL_ACCESS, False, PChar(MappingName));
if MappingHandle <> 0 then
begin
InstanceInfo := MapViewOfFile(MappingHandle, FILE_MAP_ALL_ACCESS, 0, 0, SizeOf(TInstanceInfo));
InstanceInfo^.RunCounter := -1 + InstanceInfo^.RunCounter;
end
else
RaiseLastOSError;
end;
if Assigned(InstanceInfo) then
UnmapViewOfFile(InstanceInfo);
if MappingHandle <> 0 then
CloseHandle(MappingHandle);
end.
In your project DPR, add the CheckPrevious unit to the uses, then just after begin put the following
if RestoreIfRunning(Application.Handle, 1) then
Exit;
I have no idea of where this code originated, otherwise I would gladly credit the author. (A search of RestoreIfRunning may suggest it was from Zarko Gajic)
This is how i do it.
closeProc(extractfilename(paramstr(0)));
function TForm1.closeProc(pname : string): integer;
const
PROCESS_TERMINATE = $0001;
var
ContinueLoop: BOOL;
FSnapshotHandle: THandle;
FProcessEntry32: TProcessEntry32;
i : integer;
pname2 : string;
begin
try
Result := 0;
i := 0;
FSnapshotHandle := CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
FProcessEntry32.dwSize := SizeOf(FProcessEntry32);
ContinueLoop := Process32First(FSnapshotHandle, FProcessEntry32);
while Integer(ContinueLoop) <> 0 do
begin
pname2 := trim(UpperCase(ExtractFileName(FProcessEntry32.szExeFile)));
if ( pname2 = uppercase(pname)) then
if FProcessEntry32.th32ProcessID <> GetCurrentProcessId then
begin
Result := Integer(TerminateProcess(OpenProcess(PROCESS_TERMINATE, BOOL(0), FProcessEntry32.th32ProcessID), 0));
inc(i);
end;
ContinueLoop := Process32Next(FSnapshotHandle, FProcessEntry32);
if i > 50 then
break;
end;
CloseHandle(FSnapshotHandle);
except
end;
end;
how can I find by name and get the Item in a collection of object ?
procedure TfoMain.InitForm;
begin
// Liste des produits de la pharmacie 1
FListeDispoProduit := TListeDispoProduit.Create(TProduit);
with (FListeDispoProduit) do
begin
with TProduit(Add) do
begin
Name := 'Produit 01';
CIP := 'A001';
StockQty := 3;
AutoRestock := 1;
QtyMin:= 2;
end;
with TProduit(Add) do
begin
Name := 'Produit 02';
CIP := 'A002';
StockQty := 5;
AutoRestock := 0;
QtyMin:= 2;
end;
function getProductByName(productName: String): TProduit;
var
i : integer;
begin
for i := 0 to fProductList.Count -1 do
begin
if (TProduit(fProductList.Items[i]).Name = productName)
Result :=
end;
end;
I want to edit qty about a product name.
How can I do this?
thank you
If your collection object is a TCollection, then it has an Items property (which you should have been about to see in the documentation, or in the source code). Use that and its Count property to write a loop where you inspect each item to see whether it matches your target.
var
i: Integer;
begin
for i := 0 to Pred(FListeDespoProduit.Count) do begin
if TProduit(FListeDespoProduit.Items[i]).Name = productName then begin
Result := TProduit(FListeDespoProduit.Items[i]);
exit;
end;
end;
raise EItemNotFound.Create;
end;
Items is a default property, which means you can omit it from your code and just use the array index by itself. Instead of FListeDespoProduit.Items[i], you can shorten it to just FListeDespoProduit[i].
function getProductByName(productName: String): TProduit;
var
i : integer;
begin
for i := 0 to fProductList.Count -1 do
begin
if (TProduit(fProductList.Items[i]).Name = productName)
Result := TProduit(fProductList.Items[i]); // this???
end;
end;
You can then go:
MyProduit := getProductByName('banana');
MyProduit.StockQty := 3;
Or whatever you wish.
Your TProduit implements (Add). It doesn't already implement (Get) (or something similar)?
Are you inheriting this code? Is there more detail?
Edit: otherwise you'll have to create the Get procedure yourself, possibly by looping over the list and finding a match, then returning it.
Is there a simple way to duplicate all child components under parent component, including their published properties?
For example:
TPanel
TLabel
TEdit
TListView
TSpecialClassX
Of course the most important factor, it should duplicate any new component which I drop on the TPanel without modifying the code under normal circumstances.
I've heard of the RTTI, but never used it actually. Any ideas?
You can propably use the CLoneProperties routine from the answer to "Replace visual component at runtime", after you have created the dup components in a loop thru the parent's controls.
Update: some working code....
. I assume from your question that you want to duplicate the Controls that are contained in a WinControl (as a Parent is a TWinControl).
. As I did not know if you also wanted to hook the duplicated controls with the same Event Handlers as the originals, I made an option for that.
. And you may want to give a proper meaningful Name to the duplicated controls.
uses
TypInfo;
procedure CloneProperties(const Source: TControl; const Dest: TControl);
var
ms: TMemoryStream;
OldName: string;
begin
OldName := Source.Name;
Source.Name := ''; // needed to avoid Name collision
try
ms := TMemoryStream.Create;
try
ms.WriteComponent(Source);
ms.Position := 0;
ms.ReadComponent(Dest);
finally
ms.Free;
end;
finally
Source.Name := OldName;
end;
end;
procedure CloneEvents(Source, Dest: TControl);
var
I: Integer;
PropList: TPropList;
begin
for I := 0 to GetPropList(Source.ClassInfo, [tkMethod], #PropList) - 1 do
SetMethodProp(Dest, PropList[I], GetMethodProp(Source, PropList[I]));
end;
procedure DuplicateChildren(const ParentSource: TWinControl;
const WithEvents: Boolean = True);
var
I: Integer;
CurrentControl, ClonedControl: TControl;
begin
for I := ParentSource.ControlCount - 1 downto 0 do
begin
CurrentControl := ParentSource.Controls[I];
ClonedControl := TControlClass(CurrentControl.ClassType).Create(CurrentControl.Owner);
ClonedControl.Parent := ParentSource;
CloneProperties(CurrentControl, ClonedControl);
ClonedControl.Name := CurrentControl.Name + '_';
if WithEvents then
CloneEvents(CurrentControl, ClonedControl);
end;
end;
procedure TForm1.Button1Click(Sender: TObject);
begin
DuplicateChildren(Panel1);
end;
have a read of this page
Run-Time Type Information In Delphi - Can It Do Anything For You?
Noting the section Copying Properties From A Component To Another
which has a unit, RTTIUnit with a Procedure, which seems to do part of what you want but i don't think it will copy any child components with out extra code.
(i think its ok to paste it here...)
procedure CopyObject(ObjFrom, ObjTo: TObject);
var
PropInfos: PPropList;
PropInfo: PPropInfo;
Count, Loop: Integer;
OrdVal: Longint;
StrVal: String;
FloatVal: Extended;
MethodVal: TMethod;
begin
//{ Iterate thru all published fields and properties of source }
//{ copying them to target }
//{ Find out how many properties we'll be considering }
Count := GetPropList(ObjFrom.ClassInfo, tkAny, nil);
//{ Allocate memory to hold their RTTI data }
GetMem(PropInfos, Count * SizeOf(PPropInfo));
try
//{ Get hold of the property list in our new buffer }
GetPropList(ObjFrom.ClassInfo, tkAny, PropInfos);
//{ Loop through all the selected properties }
for Loop := 0 to Count - 1 do
begin
PropInfo := GetPropInfo(ObjTo.ClassInfo, PropInfos^[Loop]^.Name);
// { Check the general type of the property }
//{ and read/write it in an appropriate way }
case PropInfos^[Loop]^.PropType^.Kind of
tkInteger, tkChar, tkEnumeration,
tkSet, tkClass{$ifdef Win32}, tkWChar{$endif}:
begin
OrdVal := GetOrdProp(ObjFrom, PropInfos^[Loop]);
if Assigned(PropInfo) then
SetOrdProp(ObjTo, PropInfo, OrdVal);
end;
tkFloat:
begin
FloatVal := GetFloatProp(ObjFrom, PropInfos^[Loop]);
if Assigned(PropInfo) then
SetFloatProp(ObjTo, PropInfo, FloatVal);
end;
{$ifndef DelphiLessThan3}
tkWString,
{$endif}
{$ifdef Win32}
tkLString,
{$endif}
tkString:
begin
{ Avoid copying 'Name' - components must have unique names }
if UpperCase(PropInfos^[Loop]^.Name) = 'NAME' then
Continue;
StrVal := GetStrProp(ObjFrom, PropInfos^[Loop]);
if Assigned(PropInfo) then
SetStrProp(ObjTo, PropInfo, StrVal);
end;
tkMethod:
begin
MethodVal := GetMethodProp(ObjFrom, PropInfos^[Loop]);
if Assigned(PropInfo) then
SetMethodProp(ObjTo, PropInfo, MethodVal);
end
end
end
finally
FreeMem(PropInfos, Count * SizeOf(PPropInfo));
end;
end;
You can write the source component into a stream and read it back into the target component.
MemStream := TMemoryStream.Create;
try
MemStream.WriteComponent(Source);
MemStream.Position := 0;
MemStream.ReadComponent(Target);
finally
MemStream.Free;
end;
You may get problems with duplicate component names though.
It's actually fairly easy to duplicate existing components at runtime. The difficult part is to copy all of their published properties to the new (duplicated) objects.
I'm sorry, but my code example is in C++Builder. The VCL is the same, just a different language. It shouldn't be too much trouble to translate it Delphi:
for (i = 0; i < ComponentCount; ++i) {
TControl *Comp = dynamic_cast<TControl *>(Components[i]);
if (Comp) {
if (Comp->ClassNameIs("TLabel")) {
TLabel *OldLabel = dynamic_cast<TDBEdit *>(Components[i]);
TLabel *NewLabel = new TLabel(this); // new label
// copy properties from old to new
NewLabel->Top = OldLabel->Top;
NewLabel->Left = OldLabel->Left;
NewLabel->Caption = Oldlabel->Caption
// and so on...
} else if (Comp->ClassNameIs("TPanel")) {
// copy a TPanel object
}
Maybe somebody has a better method of copying all of the published properties of the old control to the new one.