Changing information in all Objects the same time - class

I'm a bit new with objects and I have really have been stuck on this, I hope you guys can help me.
I create a simple application to exemplify the problem I am having with Delphi.
I have an object that inherited from TButton, but in creation time I can assign a "Group" to this button, and after I want to be able to change the caption of all button created and to enable and disable it depending on the group that it belongs.
There are 5 button in the form:
Left G1 - Create a button to group one in the left side the form
Right G2 - Create a button to group two in the right side the form
Add caption - Add caption to all button created independent of the group (same caption to all)
Enable G1 - Enable all button that belongs to group one
Disable G1 - Disable all button that belongs to group one
What I want to do is to be able to create as many buttons I want for each different group and then change all the captions at once and enable and disable separate groups at once, this is a sample project from a much bigger application that have lots of objects created so go trough all the objects in the form would be very consuming it would be nice if the change could be made by the object not by the main form.
Please guys I don't want to anyone to make the work for me, I want someone to point me in the right direction, should I use a class even knowing that I can't change the individual properties of the objects is there a way around? Can I somehow implement it on the object or do I need to implement it on the unit calling those objects. Any pointer?
Many thanks in advance.
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm2 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
Button5: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
type
TSample = class(TButton)
private
class var count: integer;
protected
public
procedure increseCount;
constructor Create(AOwner: TComponent; Ypos, Group:Integer); overload;
class procedure rename(name: string);
class procedure enableGroup(Group: Integer; value: Boolean);
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
constructor TSample.Create(AOwner: TComponent; Ypos, Group:Integer);
begin
self.Create(AOwner);
self.Top := count *50;
self.Left := Ypos;
self.Tag := Group;
increseCount;
self.Parent := AOwner as TWinControl;
end;
procedure TSample.increseCount;
begin
count := count + 1;
end;
class procedure TSample.enableGroup(Group: Integer; value: Boolean);
begin
//???
end;
class procedure TSample.rename(name: string);
begin
//self.Caption := name; ???
end;
procedure TForm2.Button1Click(Sender: TObject);
var
left: TSample;
begin
left := TSample.Create(self, 24, 1);
end;
procedure TForm2.Button2Click(Sender: TObject);
var
right: TSample;
begin
right := TSample.Create(self, 200, 2);
end;
procedure TForm2.Button3Click(Sender: TObject);
begin
TSample.rename('Oi!');
end;
procedure TForm2.Button4Click(Sender: TObject);
begin
TSample.enableGroup(1, True);
end;
procedure TForm2.Button5Click(Sender: TObject);
begin
TSample.enableGroup(1, False);
end;
end.

Ok, after the hint from David I find the solution:
First I create an array the same kind of the object
Then I create a class function that create this object and save into this array
Now every time I need to access any of those objects I can just browse this list and change what need to be change.
Why I did this way? Before I was browsing all objects in my main form what was taking a lot of processing, doing this way I just have to browse objects of the kind I want to change.
Thanks I hope it can help others in the future.
unit Unit2;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TForm2 = class(TForm)
Button1: TButton;
Button2: TButton;
Button3: TButton;
Button4: TButton;
Button5: TButton;
procedure Button1Click(Sender: TObject);
procedure Button2Click(Sender: TObject);
procedure Button3Click(Sender: TObject);
procedure Button4Click(Sender: TObject);
procedure Button5Click(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
end;
type
TSample = class(TButton)
private
class var count: integer;
class var List: Array of TSample;
protected
public
procedure increseCount;
constructor Create(AOwner: TComponent; Ypos, Group:Integer); overload;
class function CreateInstance(AOwner: TComponent; Ypos, Group:Integer): TSample; overload;
class procedure rename(name: string);
class procedure enableGroup(Group: Integer; value: Boolean);
end;
var
Form2: TForm2;
implementation
{$R *.dfm}
class function TSample.CreateInstance(AOwner: TComponent; Ypos, Group:Integer): TSample;
var
i: Integer;
begin
i := Length(List)+1;
SetLength(List, i);
List[i-1] := self.Create(AOwner, Ypos, Group);
end;
constructor TSample.Create(AOwner: TComponent; Ypos, Group:Integer);
begin
self.Create(AOwner);
self.Top := count *50;
self.Left := Ypos;
self.Tag := Group;
increseCount;
self.Parent := AOwner as TWinControl;
end;
procedure TSample.increseCount;
begin
count := count + 1;
end;
class procedure TSample.enableGroup(Group: Integer; value: Boolean);
var
i: Integer;
begin
for i := 0 to Length(List)-1 do
if List[i].Tag = Group then
List[i].Enabled := value;
end;
class procedure TSample.rename(name: string);
var
i: Integer;
begin
for i := 0 to Length(List)-1 do
List[i].Caption := name;
end;
procedure TForm2.Button1Click(Sender: TObject);
var
left: TSample;
begin
left := TSample.CreateInstance(self, 24, 1);
end;
procedure TForm2.Button2Click(Sender: TObject);
var
right: TSample;
begin
right := TSample.CreateInstance(self, 200, 2);
end;
procedure TForm2.Button3Click(Sender: TObject);
begin
TSample.rename('Oi!');
end;
procedure TForm2.Button4Click(Sender: TObject);
begin
TSample.enableGroup(1, True);
end;
procedure TForm2.Button5Click(Sender: TObject);
begin
TSample.enableGroup(1, False);
end;
end.

Related

Delphi Access Violation on form show

Sorry for having to open new question but I can't find an answer anywhere.
My app is still in progress, but basically I'm trying to call another Form from my MainForm when initializing players, however I get an Access Violation error. Would you please explain to me why this could be happening?
My MainForm code:
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, Buttons, ExtCtrls, jpeg, pngimage, getPlayer_u;...
procedure TfrmMain.FormCreate(Sender: TObject);
begin
Randomize;
InitGameSetup();
end;...
procedure TfrmMain.InitGameSetup();
begin
SetWindowProperties();
InitBackGround();
InitMainMenu();
InitGameBoard();
InitScrabbleTileRack();
InitPlayers();
// GameLoop();
end; ...
procedure TfrmMain.InitPlayers();
var
I : Integer;
sName, sSurname : string;
begin
setLength(Players, NUMBER_OF_PLAYERS);
for I := 1 to High(Players) do
begin
GetPlayer(); ---------------- problem is here
with Players[I] do
begin
Name := sName;
Surname := sSurname;
end;
end;
end;...
procedure TfrmMain.GetPlayer();
begin
frmGetPlayer.Show;
end;
My frmGetPlayer:
unit getPlayer_u;
interface
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls;
type
TfrmGetPlayer = class(TForm)
btnSubmit: TButton;
edtName: TEdit;
edtSurname: TEdit;
procedure FormCreate(Sender: TObject);
procedure btnSubmitClick(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
sPlayerName, sPlayerSurname : string;
end;
var
frmGetPlayer: TfrmGetPlayer;
implementation
{$R *.dfm}
procedure TfrmGetPlayer.btnSubmitClick(Sender: TObject);
begin
sPlayerName := edtName.Text;
sPlayerSurname := edtSurname.Text;
if not ((Length(sPlayerName) >= 1) and (Length(sPlayerSurname) >= 1)) then
MessageDlg('Please enter a name and surname.', mtInformation, [mbOK], 0)
else
Self.Free;
end;
procedure TfrmGetPlayer.FormCreate(Sender: TObject);
begin
with Self do
begin
Position := poScreenCenter;
BorderStyle := bsDialog;
end;
end;
end.
My dpr:
program main_p;
uses
Forms,
main_u in 'main_u.pas' {frmMain},
getPlayer_u in 'getPlayer_u.pas' {frmGetPlayer};
{$R *.res}
begin
Application.Initialize;
Application.MainFormOnTaskbar := True;
Application.CreateForm(TfrmMain, frmMain);
Application.Run;
end.
The error:
Only your MainForm object is created automatically at program startup. Inside its OnCreate event, your Player Form object hasn't been created yet, so the frmGetPlayer variable is not pointing at a valid object.
frmGetPlayer is a global variable, so it is initially nil. The error message is telling you that you are accessing invalid memory near address 0, which is almost always an indication of accessing a member of a class via a nil pointer.
So, you can't call frmGetPlayer.Show() until after you have created the Player Form object and assigned frmGetPlayer to point at it. Which the code you showed is not doing.

How hide an Form of a dll file?

I have a dll file that contains several Forms.
I have a reference to main function in console application that loads this dll.
I want hide main Form of dll file, but all attempts until now was without sucess.
I tried several ways for this, but nothing that I tried worked, except for hide from taskbar.
This is my code:
Main form on dll
unit Conectar;
interface
uses
Windows, Messages, SysUtils, Classes {.......};
type
TForm1 = class(TForm)
procedure FormShow(Sender: TObject);
private
{ Private declarations }
public
{ Public declarations }
procedure CreateParams(var Params: TCreateParams); override;
end;
procedure TForm1.CreateParams(var Params: TCreateParams);
begin
inherited;
params.exstyle := params.exstyle and not WS_EX_APPWINDOW or WS_EX_TOOLWINDOW;
end;
procedure TForm1.FormShow(Sender: TObject);
begin
ShowWindow(Form1.Handle,SW_HIDE);
end;
Dll
library DLL;
uses
System.SysUtils,
Conectar,
vcl.Forms,
Classes;
{$R *.res}
procedure ShowDllForm; stdcall;
begin
Form1 := TForm1.Create(nil);
try
Form1.ShowModal;
finally
FreeAndNil(Form1);
end;
end;
exports
ShowDllForm;
begin
end.
Console application
{$APPTYPE GUI}
var
Msg: TMsg;
procedure ShowDllForm;stdcall;
external 'DLL.dll' name 'ShowDllForm';
begin
CoInitialize(nil);
ShowDllForm;
CoUninitialize;
end.
procedure TForm1.FormCreate(Sender: TObject);
begin
SetWindowLong(Handle, GWL_EXSTYLE, WS_EX_TOOLWINDOW);
end;
SOLUTION:
Dll
procedure ShowDllForm; stdcall;
begin
Application.CreateForm(TForm1, Form1);
ShowWindow(Application.Handle, SW_HIDE);
end;
Console application
var
Msg: TMsg;
begin
ShowDllForm;
while GetMessage(Msg, 0, 0, 0) do begin
TranslateMessage(Msg);
DispatchMessage(Msg);
end;
end.
OR
on dll file in FormActivate event:
Form1.Hide;

Is there an Indy server property equivalent to TCustomWinSocket.Data?

When I work with TServerSocket I can use the property Data to store a pointer to a class for example for each client.
Now I will use Indy TIdCmdTCPServer and I'd like to know if there is an equivalent property.
Yes, there is - the TIdContext.Data property. In TIdCmdTCPServer events that give you a TIdCommand parameter instead of a TIdContext parameter, you can access the TIdContext object from the TIdCommand.Context property. For example:
type
TMyClass = class
// add whatever you need...
end;
procedure TForm1.IdCmdTCPServer1Connect(AContext: TIdContext);
var
MyCls: TMyClass;
begin
MyCls := TMyClass.Create;
// initialize MyCls as needed...
AContext.Data := MyCls;
end;
procedure TForm1.IdCmdTCPServer1Disconnect(AContext: TIdContext);
begin
AContext.Data.Free;
AContext.Data := nil;
end;
procedure TForm1.IdCmdTCPServer1CommandHandlerCommand(ACommand: TIdCommand);
var
MyCls: TMyClass;
begin
MyCls := TMyClass(ACommand.Context.Data);
// use MyCls as needed...
end;
Indy also has another useful feature. You can derive a custom class from TIdServerContext, add whatever you want to it, and then assign it to the server's ContextClass property before activating the server. That way, you can simply typecast any TIdContext pointer to your class type when you need to access your custom members. For example:
type
TMyContext = class(TIdServerContext)
public
// add whatever you need...
constructor Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil); override;
destructor Destroy; override;
end;
constructor TMyContext.Create(AConnection: TIdTCPConnection; AYarn: TIdYarn; AList: TIdContextThreadList = nil);
begin
inherited;
//...
end;
destructor TMyContext.Destroy;
begin
//...
inherited;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
IdCmdTCPServer1.ContextsClass := TMyContext;
end;
procedure TForm1.IdCmdTCPServer1Connect(AContext: TIdContext);
var
MyCtx: TMyContext;
begin
MyCtx := TMyClass(AContext);
// initialize MyCtx as needed...
end;
procedure TForm1.IdCmdTCPServer1CommandHandlerCommand(ACommand: TIdCommand);
var
MyCtx: TMyContext;
begin
MyCtx := TMyClass(ACommand.Context);
// use MyCtx as needed...
end;
This way, you don't need to waste time and memory allocating a separate class per client, when you can use the one that the server already creates for you.

How to access TPanel child form controls?

I have a main form with TPanel. I have also a Form2 with a TButton which I show in TPanel as a child. I mean TPanel of main form is parent of Form2. I use these steps to create the form2 in MainForm OnCreate method
MainFormOnCreate()
Form2 := TForm2.create(nil)
Form2.Parent := Panel1;
Form2.show;
But the problem is that when I access the button on Form2 it does nothing. For example, when I want to disable the button on Form2 I use this method
A button2 on main form with on click event
btn2OnClick();
Form2.btn.enabled := false;
But it does nothing. Some friends says it's because of child to TPanel it will get no message.
So give me a solution.
Thanks in advance
The main problem is, that you create 2 instances of TForm2.
Your .dpr file look like this
begin
Application.Initialize;
Application.CreateForm( TForm1, Form1 );
Application.CreateForm( TForm2, Form2 );
Application.Run;
end.
After you create an instance of TForm2 in TForm1.OnCreate and save this instance into global variable Form2, another instance of TForm2 is created and stored into Form2.
In the TForm1.btn5.OnClick event you address the second created, non visible TForm2.
Solution
go to Project / Options -> Formula and remove TForm2 from AutoCreate List
store the instance of TForm2 created inside of TForm1 in a private class field of TForm1
Your code should look like this
.dpr file:
begin
Application.Initialize;
Application.CreateForm( TForm1, Form1 );
Application.Run;
end.
Unit1.pas
TForm1 = class( TForm )
...
procedure FormCreate( Sender : TObject );
procedure btn2Click( Sender : TObject );
private
FForm2 : TForm2;
...
end;
procedure TForm1.FormCreate( Sender : TObject );
begin
FForm2 := TForm2.Create( Self );
FForm2.Parent := Panel1;
FForm2.Show;
end;
procedure TForm1.btn2Click( Sender : TObject );
begin
FForm2.btn.Enabled := True;
end;
Try this one
uses
Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
Dialogs, StdCtrls, ExtCtrls, Unit2;
type
TForm1 = class(TForm)
Panel1: TPanel;
Button1: TButton;
procedure Button1Click(Sender: TObject);
procedure FormCreate(Sender: TObject);
private
lForm: TForm;
public
{ Public declarations }
end;
var
Form1: TForm1;
implementation
{$R *.dfm}
procedure TForm1.Button1Click(Sender: TObject);
begin
if Assigned(lForm) then
TForm2(lForm).Button1.Enabled:= False;
end;
procedure TForm1.FormCreate(Sender: TObject);
begin
lForm := TForm2.Create(self);
lForm.Parent := Panel1;
lForm.Align:= alClient;
lForm.show;
end;

Array of a custom class as a property

I am trying to use an array of a custom class as a property for my component, but the problem is that the values are not been saved to the component, that means that if I set the values, save everything and open again the project, the values for the component disappears... My code looks like the following:
unit Unit1;
interface
uses Windows, ExtCtrls,Classes,Controls;
type
TMyClass=class(TPersistent)
private
FName: string;
FValue: double;
public
property Name: string read FName write FName;
property Value: double read FValue write FValue;
end;
TMyComponent= class(TCustomPanel)
private
FMyArray: array[0..200] of TMyClass;
function GetmyArray(Index: Integer): TMyClass;
procedure SetMyArray(index: Integer; Value: TMyClass);
public
property myArray[index: Integer]: TMyClass read GetMyArray write SetMyArray;
end;
implementation
function TMyComponent.GetmyArray(Index: Integer): TMyClass;
begin
result:= FmyArray[Index];
end;
procedure TMyComponent.SetMyArray(index: Integer; Value: TMyClass);
begin
FMyArray[index].FName:= Value.FName;
FMyArray[index].FValue:= Value.FValue;
end;
end.
I know that that only published properties can be streamed, but the problem is that my property is an array and it can not be published...
A suggestion that I had was to use DefineProperties() to provide a custom streaming but I don't see how to do this with an array.
Other possibility that I thought was to modify TMyClass to a kind of class that TMyComponent could be the parent of it, like it is done in TChart, which you can add different classes of series to it. But I don't know What class this should be
TMyClass=class(T???????????)
With that I could take out the property MyArray and create TMyClass and add to TMyComponent as the following:
MyArray1.parent:= MyComponent1;
MyArray2.parent:= MyComponent2;
...
. Which one is the better option? Or is there any other better idea?
The simpliest (and preferred) solution is to change TMyClass to derive from TCollectionItem and change TMyComponent.FMyArray to TOwnedCollection. Then the DFM will stream the items automatically for you, and you gain native design-time support for creating and manipulating TMyClass objects and their properties.
Try this:
unit Unit1;
interface
uses
Windows, ExtCtrls, Classes, Controls;
type
TMyClass = class(TCollectionItem)
private
FName: string;
FValue: double;
procedure SetName(const AValue: string);
procedure SetValue(AValue: double);
public
procedure Assign(ASource: TPersistent); override;
published
property Name: string read FName write SetName;
property Value: double read FValue write SetValue;
end;
TMyArray = class(TOwnedCollection)
private
function GetItem(Index: Integer): TMyClass;
procedure SetItem(Index: Integer; const Value: TMyClass);
public
constructor Create(AOwner: TPersistent);
function Add: TMyClass; reintroduce;
function Insert(Index: Integer): TMyClass; reintroduce;
property Items[Index: Integer]: TMyClass read GetItem write SetItem; default;
end;
TMyComponent = class(TCustomPanel)
private
FMyArray: TMyArray;
procedure SetMyArray(Value: TMyArray);
public
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
published
property myArray: TMyArray read FMyArray write SetMyArray;
end;
implementation
procedure TMyClass.Assign(ASource: TPersistent);
begin
if ASource is TMyClass then
begin
with TMyClass(ASource) do
begin
Self.FName := Name;
Self.FValue := Value;
end;
Changed(False);
end else
inherited;
end;
procedure TMyClass.SetName(const AValue: string);
begin
if FName <> AValue then
begin
FName := AValue;
Changed(False);
end;
end;
procedure TMyClass.SetValue(AValue: double);
begin
if FValue <> AValue then
begin
FValue := AValue;
Changed(False);
end;
end;
constructor TMyArray.Create(AOwner: TPersistent);
begin
inherited Create(AOwner, TMyClass);
end;
function TMyArray.GetItem(Index: Integer): TMyClass;
begin
Result := TMyClass(inherited GetItem(Index));
end;
procedure TMyArray.SetItem(Index: Integer; const Value: TMyClass);
begin
inherited SetItem(Index, Value);
end;
function TMyArray.Add: TMyClass;
begin
Result := TMyClass(inherited Add);
end;
function TMyArray.Insert(Index: Integer): TMyClass;
begin
Result := TMyClass(inherited Insert(Index));
end;
constructor TMyComponent.Create(AOwner: TComponent);
begin
inherited;
FMyArray := TMyArray.Create(Self);
end;
destructor TMyComponent.Destroy;
begin
FMyArray.Free;
inherited;
end;
procedure TMyComponent.SetMyArray(Value: TMyArray);
begin
FMyArray.Assign(Value);
end;
end.
I'd vote for DefineProperties! The necessary code might look like this (assuming none of the instances in the array is nil):
procedure TMyComponent.DefineProperties(Filer: TFiler);
begin
inherited;
Filer.DefineProperty('MyArray', ReadMyArray, WriteMyArray, true);
end;
procedure TMyComponent.ReadMyArray(Reader: TReader);
var
N: Integer;
begin
N := 0;
Reader.ReadListBegin;
while not Reader.EndOfList do begin
Reader.ReadListBegin;
FMyArray[N].Name := Reader.ReadString;
FMyArray[N].Value := Reader.ReadFloat;
Reader.ReadListEnd;
Inc(N);
end;
Reader.ReadListEnd;
end;
procedure TMyComponent.WriteMyArray(Writer: TWriter);
var
I: Integer;
begin
Writer.WriteListBegin;
for I := 0 to High(FMyArray) do begin
Writer.WriteListBegin;
Writer.WriteString(FMyArray[I].Name);
Writer.WriteFloat(FMyArray[I].Value);
Writer.WriteListEnd;
end;
Writer.WriteListEnd;
end;