I have an assignment to create a game in Delphi and since the board will be the size 7x7 i wanted to do it by deriving my own component from TImage. Due to the fact that I wanted to have position of all tiles in the board in the array and I wanted to use Create method to do it but whenever I tried I have encountered EAccessViolation, while calling .Create(self)
Here is my component's code:
unit iles1;
interface
SysUtils, Classes, Controls, ExtCtrls;
type
Tiles1 = class(TImage)
private
FPlayer:Boolean; //determines whether it is an empty field or a player
FTeam:Boolean; //determines the team the tile belogns to
FBall:Boolean; //posession of the ball
{FBackLight : whether it is available to interact with this component,
with the method on click after one of the tiles has already been chosen,
if it is not lit but belongs to the same team, it is flagged as chosen
but not as lit, this field is used to determine whether i can pass a ball
to this direction or swap places with other player from the same team}
FBackLight:Boolean;
FChosen:Boolean; //whether the player decided to click on it
{FPostion determines where it is in a table, it ranges from
36 to 0 where its position divided by 10 determines the column
and position mod 10 determines the row}
FPosition:Byte;
{ Private declarations }
protected
{ Protected declarations }
public
constructor Create(AOwner : TComponent); override;
{ Public declarations }
published
property Team: boolean
read FTeam
write FTeam;
property Ball: boolean
read FBall
write FBall;
property Player:boolean
read FPlayer
write FPlayer;
property BackLight:boolean
read FBackLight
write FBackLight;
property Chosen:boolean
read FChosen
write FChosen;
property Position:byte
read FPosition
write FPosition;
end;
{ property Ball: Boolean;
//read FHasBall
//write FSetBall;
end;}
{ Published declarations }
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('Samples', [Tiles1]);
end;
{Creator procedure calling the Timage creator
and setting parent to self(impossible here, then i will do it in
main window), visible to true
}
constructor Tiles1.Create(AOwner:TComponent);
begin
inherited;
FPlayer:=false;
FTeam:=false;
FBall:=false;
FBackLight:=false;
FChosen:=false;
FPosition:=0;
end;
end.
And here i have my main menu method that uses it:
procedure TForm1.FormCreate(Sender: TObject);
var x,y:Integer;
begin
for y:=1 to INAROW do begin
for x:=1 to INAROW do begin
tiles[x,y].Create(self);
tiles[x,y].Parent:=self;
tiles[x,y].Visible:=true;
tiles[x,y].Top:=(y-1)*(GAPBETWEEN+TILES1HEIGHT)+GAPTOP;
tiles[x,y].Left:=GAPLEFT+(x-1)*(GAPBETWEEN+TILES1WIDTH);
tiles[x,y].Width:=TILES1WIDTH;
tiles[x,y].Height:=TILES1HEIGHT;
tiles[x,y].Position:=10*x+y;
tiles[x,y].BackLight:=false;
tiles[x,y].Ball:=false;
tiles[x,y].Player:=false;
tiles[x,y].Chosen:=false;
end;
end;
setAlphaTeam;
setBetaTeam;
setTiles;
end;
tiles[X,Y] := Tiles1.Create(self);
assuming tiles is an array of Tiles1.
Constructors are effectively class methods, you call them on the class, not the instance.
You are getting an access violation because tiles[X,Y] is nil. If you commented out the create line, you'd get it trying to set the Parent Property.
Related
how can i get dynamic values for my component's property variable
in my component i have a field named ColorDefault and i want to be able to set its value dynamically in program.
Original code
var // global
_V_TB_DefaultColor: TColor
type
TMyClass = class
...
property ColorDefault: tcolor read _V_TB_DefaultColor write FDefaultColor;
//[dcc32 Error] MyButton.pas(85): E2168 Field or method identifier expected
...
end;
Edit:
I did as below as tom described but color is not changing according to the global variable, color stays as when it is complied, for example my global color was clyellow and i complied my component and place it on the form and after that i changed the global color variable to clwhite and when i run the program it is still clyellow
type
TTestClass = class(TPanel)
private
{ Private declarations }
protected
{ Protected declarations }
FColorDefault:tcolor;
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
destructor Destroy; override;
function GetGlobalColorVariable:TColor;
published
{ Published declarations }
property DefaultColor:TColor read GetGlobalColorVariable write FColorDefault;
end;
procedure Register;
implementation
procedure Register;
begin
RegisterComponents('MyComponents', [TTestClass]);
end;
constructor TTestClass.Create(AOwner: TComponent);
begin
ColorInitiate;
inherited;
color:=DefaultColor;
end;
destructor TTestClass.Destroy;
begin
inherited;
end;
function TTestClass.GetGlobalColorVariable: TColor;
begin
result:=_V_TB_DefaultColor;
end;
end.
Looking at your question it seems that you are dealing with two problems.
First problem is how to access some global variable using property.
You can do this by using Getter method of your property like so:
type
TTestClass = class(TPanel)
private
{ Private declarations }
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
function GetDefaultColor: TColor;
published
{ Published declarations }
property DefaultColor: TColor read GetDefaultColor;
end;
var // global
_V_TB_DefaultColor: TColor
implementation
function TTestClass.GetGlobalColorVariable: TColor;
begin
result := _V_TB_DefaultColor;
end;
That is like you did in your question edit. This will always make your DefaultColor property to return the same value as it is stored in your global _V_TB_DefaultColor variable.
But do note that this won't detect when _V_TB_DefaultColor variable was changed. So if you want to update your components after the change you need to execute some updating procedure for each of them yourself.
Also bare in mind that using global variables like this is not a good practice.
If you want certain property of all of your components to have the same value the it would be much better to declare that property as class property like in the code bellow.
type
TTestClass = class(TPanel)
private
{ Private declarations }
class var FDefaultColor: TColot;
protected
{ Protected declarations }
public
{ Public declarations }
constructor Create(AOwner: TComponent); override;
class function GetDefaultColor: TColor;
published
{ Published declarations }
class property DefaultColor: TColor read FDefaultColor write FDefaultColor;
end;
Now changing your DefaultColor in one component will change DefaultColor for all components of the same class. But bare in mind that you will still have to execute proper procedure for each of your components to update itself. That won't be done automatically.
Now your second problem is to detect the change of your DefaultColor variable and update your component/s accordingly.
Now if you use global variable there is no mechanism to detect this. But if you are using class procedure then you can at least write a setter method that will either execute update methods of all of your components that needs to be updated or send them necessary notification so they can perform necessary update by themselves.
How to implement this?
One way would be to loop through all your forms components checking their type and executing necessary update procedure. But that can be quite slow if you have lots of other components that you need to check if they are correct ones.
Another probably better approach would be adding your components on separate list so you don't need to be type checking as you know that such list only contains the right components.
You neeed to review the documentation about properties
http://docwiki.embarcadero.com/RADStudio/XE7/en/Properties
Your code
property ColorDefault:tcolor read _V_TB_DefaultColor write FDefaultColor;
is not approved by the compiler because _V_TB_DefaultColor is not a field or method of your class. The property should probably be declared as:
property ColorDefault:tcolor read FDefaultColor write FDefaultColor;
To set FDefaultColor equal to the global variable _V_TB_DefaultColor you need
MyClass.ColorDefault := _V_TB_DefaultColor;
at a suitable place in your code, f.ex in the constructor of your class.
Note, that to change the color of your component, you need to assign a new value to the ColorDefault property. Changing the value of your global variable _V_TB_DefaultColor will not automagically change the value of the property.
In your revised code you added
function TTestClass.GetGlobalColorVariable: TColor;
begin
result:=_V_TB_DefaultColor;
end;
That will not do anything if you dont assign the result of the function to something. Since the function is a member of TTestCalss I believe you want to
set the Color property directly in this function:
function TTestClass.GetGlobalColorVariable: TColor;
begin
Color:=_V_TB_DefaultColor;
end;
If this is the case, you can change it to a procedure because you dont use the return value:
procedure TTestClass.GetGlobalColorVariable;
begin
Color:=_V_TB_DefaultColor;
end;
thanks everyone,
with all the suggestions what i do is using a function to get the value from global variable and using windows messages as trigger to apply the new values to component
I have created a component with TFrame as ancestor with the following code:
type
TCHAdvFrame = class(TFrame)
private
{ Private declarations }
FOnShow : TNotifyEvent;
FOnCreate : TNotifyEvent;
protected
procedure CMShowingChanged(var M: TMessage); message CM_SHOWINGCHANGED;
public
{ Public declarations }
constructor Create(AOwner: TComponent) ; override;
published
property OnShow : TNotifyEvent read FOnShow write FOnShow;
property OnCreate : TNotifyEvent read FOnCreate write FOnCreate;
end;
implementation
{$R *.dfm}
{ TCHAdvFrame }
procedure TCHAdvFrame.CMShowingChanged(var M: TMessage);
begin
inherited;
if Assigned(OnShow) then
begin
ShowMessage('onShow');
OnShow(self);
end;
end;
constructor TCHAdvFrame.Create(AOwner: TComponent);
begin
ShowMessage('OnCreate1');
inherited ;
ShowMessage('OnCreate2');
if Assigned(OnCreate) then
begin
ShowMessage('OnCreate3');
OnCreate(self);
end;
I have registered the new component and did some tests. ShowMessage('OnCreate1'); and ShowMessage('OnCreate2'); are correctly executed but not ShowMessage('OnCreate3');
This prevents to add code during the implementation of a new instance of TCHAdvFrame.
Why is it and how can I solve this ?
A frame is streamed in as part of its ultimate owner's constructor. Typically that will be a form. The form processes the .dfm file. It encounters new objects and creates them. Then it sets the properties of the newly created object. So, the frame's properties are set after its constructor returns.
This is the reason that TFrame does not have an OnCreate event. There is simply no way for the event to be fired because the event by necessity is assigned too late. The VCL designers omitted this event for the very same reason that led you to ask this question. So I do suspect that you likewise should not add this event.
How to solve this? Hard to say for sure unless we had a more detailed description of the problem. Perhaps you could override the frame's Loaded method to good effect. Or perhaps all you need to do is let consumers of your component override the constructor in their derived frames.
Related reading: http://delphi.about.com/od/delphitips2007/qt/tframe_oncreate.htm
On the new side of writing classes and have a little problem which I have tried researching but still no answer.
I want to create one instance of a class which creates multiple subclasses which creates subclasses of their own. The idea is to use code like this in main program:
procedure TForm1.Button1Click(Sender: TObject);
var
Temp : Integer;
begin
MainClass := TMainClass.Create(Form1);
Temp := MainClass.SubClass1.SubSubClass1.SomeValue;
end;
The main class looks like this and is created in seperate file:
TMainClass = class(TObject)
private
FSubClass1 : TSubClass1;
public
ValueFromAnySubClass : Integer;
property SubClass1 : TSubClass1 read FSubClass1 write FSubClass1;
procedure SetSomeValueFromMainClass(Value : Integer);
end;
...
...
...
procedure TMainClass.SetSomeValueFromMainClass(Value : Integer);
begin
ValueFromAnySubClass := Value;
end;
The sub class also in seperate file:
TSubClass1 = class(TObject)
private
FSubSubClass1 : TSubSubClass1;
public
property SubSubClass1 : TSubSubClass1 read FSubSubClass1 write FSubSubClass1;
end;
And now for the sub sub class also in seperate file:
TSubSubClass1 = class(TObject)
private
SomeValue : Integer;
function GetSomeValue : Integer;
procedure SetSomeValue(Value : Integer);
public
property SomeValue : Integer read GetSomeValue write SetSomeValue;
end;
...
...
...
procedure TSubSubClass1.SetSomeValue(Value : Integer);
begin
SetSomeValueFromMainClass(Value); <<< Error Here <<<
end;
How do I get to use the functions and procedures from the main class in my sub classes?
You don't need a subclass to use a function from another class. Also your sample code has not used subclasses at all. A proper subclass automatically has access to all public and proteced functions of its ancestors.
As David has already pointed out, there are serious flaws in your intended deisgn.
Furthermore, based on your comment:
The classes all perform vastly different functions but need to write data to a piece of hardware at the end of the day. The data is read from the hardware and kept in memory to work with until its written back to the hardware component once all work is completed. The procedure in the main class takes care of writing real time data to the hardware whenever it is required by any of the subclasses.
to David's answer: you don't need subclasses at all.
All you need is a public method on your hardware class. And for each instance of your other classes to have a reference to the correct instance of your hardware class.
type
THardwareDevice = class(TObject)
public
procedure WriteData(...);
end;
TOtherClass1 = class(TObject)
private
FDevice: THardwareDevice;
public
constructor Create(ADevice: THardwareDevice);
procedure DoSomething;
end;
constructor TOtherClass1.Create(ADevice: THardwareDevice);
begin
FDevice := ADevice;
end;
procedure TOtherClass1.DoSomething;
begin
//Do stuff, and maybe you need to tell the hardware to write data
FDevice.WriteData(...);
end;
//Now given the above you can get two distinct object instances to interact
//as follows. The idea can be extended to more "other class" types and instances.
begin
FPrimaryDevice := THardwareDevice.Create(...);
FObject1 := TOtherClass1.Create(FPrimaryDevice);
FObject1.DoSomething;
//NOTE: This approach allows extreme flexibility because you can easily
// reference different instances (objects) of the same hardware class.
FBackupDevice := THardwareDevice.Create(...);
FObject2 := TOtherClass1.Create(FBackupDevice);
FObject2.DoSomething;
...
end;
The design looks really poor. You surely don't want to have all these classes knowing all about each other.
And any time you see a line of code with more than one . operator you should ask yourself if the code is in the right class. Usually that indicates that the line of code that has multiple uses of . should be in one of the classes further down the chain.
However, if you want to call a method, you need an instance. You write:
procedure TSubSubClass1.SetSomeValue(Value : Integer);
begin
SetSomeValueFromMainClass(Value);
end;
And naturally this does not compile. Because SetSomeValueFromMainClass is not a method of TSubSubClass1. Rather SetSomeValueFromMainClass is a method of TMainClass. So, to call that method, you need an instance of TMainClass.
Which suggests that, if you really must do this, that you need to supply to each instance of TSubSubClass1 an instance of TMainClass. You might supply that in the constructor and make a note of the reference.
Of course, when you do this you now find that your classes are all coupled together with each other. At which point one might wonder whether or not they should be merged.
I'm not saying that merging these classes is the right design. I would not like to make any confident statement as to what the right design is. Perhaps what you need is an interface that promises to implement the setter as a means to decouple things. All I am really confident in saying is that your current design is not the right design.
As far as I know Subclass word is usually using in inheritance concept but the code you wrote are some compound classes. As you may see the constructor of many classes in Delphi have an argument which named AOwner that may be TComponent or TObject or ...
If you define the constructors of your TSubclass1 and TSubSubClass1 like as follow and Change the Owner of classes that you defined as properties to Self in set functions you may access to your TMainClass by typecasting the Owner property.
I changed your code a little to just work as you want, but I suggest change your design.
TSubSubClass1 = class(TObject)
private
FOwner: TObject;
function GetSomeValue:Integer;
procedure SetSomeValue(const Value: Integer);
procedure SetOwner(const Value: TObject);
public
constructor Create(AOwner:TObject);reintroduce;
property Owner:TObject read FOwner write SetOwner;
property SomeValue : Integer read GetSomeValue write SetSomeValue;
end;
TSubClass1 = class(TObject)
private
FSubSubClass1: TSubSubClass1;
FOwner:TObject;
procedure SetSubSubClass1(const Value: TSubSubClass1);
procedure SetOwner(const Value: TObject);
public
constructor Create(AOwner:TObject);reintroduce;
property Owner:TObject read FOwner write SetOwner;
property SubSubClass1 : TSubSubClass1 read FSubSubClass1 write SetSubSubClass1;
end;
TMainClass = class(TObject)
private
FSubClass1: TSubClass1;
procedure SetSubClass1(const Value: TSubClass1);
public
ValueFromAnySubClass : Integer;
constructor Create;
property SubClass1 : TSubClass1 read FSubClass1 write SetSubClass1;
procedure SetSomeValueFromMainClass(Value : Integer);
end;
implementation
{ TSubSubClass1 }
constructor TSubSubClass1.Create(AOwner: TObject);
begin
Owner:=AOwner;
end;
function TSubSubClass1.GetSomeValue: Integer;
begin
Result:=TMainClass(TSubClass1(Self.Owner).Owner).ValueFromAnySubClass;
end;
procedure TSubSubClass1.SetOwner(const Value: TObject);
begin
FOwner := Value;
end;
procedure TSubSubClass1.SetSomeValue(const Value: Integer);
begin
TMainClass(TSubClass1(Self.Owner).Owner).SetSomeValueFromMainClass(Value);
end;
{ TSubClass1 }
constructor TSubClass1.Create(AOwner: TObject);
begin
Owner:=AOwner;
FSubSubClass1:=TSubSubClass1.Create(Self);
end;
procedure TSubClass1.SetOwner(const Value: TObject);
begin
FOwner := Value;
end;
procedure TSubClass1.SetSubSubClass1(const Value: TSubSubClass1);
begin
FSubSubClass1 := Value;
FSubSubClass1.Owner:=Self;
end;
{ TMainClass }
constructor TMainClass.Create;
begin
FSubClass1:=TSubClass1.Create(Self);
end;
procedure TMainClass.SetSomeValueFromMainClass(Value: Integer);
begin
ValueFromAnySubClass := Value;
end;
procedure TMainClass.SetSubClass1(const Value: TSubClass1);
begin
FSubClass1 := Value;
FSubClass1.Owner:=Self;
end;
you must put the proper filename in uses part of implementation.
I am sure that I got a good answer to my previous question because I have previously had a great deal of help on other questions from the guys who posted there.
But I am obviously doing something wrong, because when I copy the example code what the object inspector shows me for the MyProp property is a single text input field. I was expecting to see something that looks like the Font property, with Pitch, font family, etc i.e I expect to see a tree structure but I don't see the Color, Height or Width properties of the MyProp property.
Any ideas? Again, I copied that code exactly.
Edit: I forgot to mention (in this question) that I am using TMS scripter pro, which allows users to design forms at run time and provides its own object inspector, but that is probably derived from standard Delphi stuff, I guess.
Anyway, it appears that I am too dumb to code Delphi as I simply can't get this to work.
Edit: TMS assure me that if the class with "sub-properties) is descended from TPresistent then it will appear in the object inspector with sub-properties, just like Font, Anchors, etc
When I use this code, the "Warning" property appears as a text field in the object inspector and has no sub-properties
unit IntegerEditBox;
// An edit box which only accepts integer values and warns if the value is not in a certain range
interface
uses
SysUtils, Classes, Controls, StdCtrls,
EditBox_BaseClass;
type
TWarning = Class(TPersistent)
private
FWarningBelowValue : Integer;
FWarningAboveValue : Integer;
FWarningEmailTo : String;
FWarningSmsTo : String;
published
property WarningBelowValue : Integer read FWarningBelowValue write FWarningBelowValue;
property WarningAboveValue : Integer read FWarningAboveValue write FWarningAboveValue;
property WarningEmailTo : String read FWarningEmailTo write FWarningEmailTo;
property WarningSmsTo : string read FWarningSmsTo write FWarningSmsTo;
end;
TIntegerEditBox = class(TEditBox_BaseClass)
private
FWarning : TWarning;
procedure WriteValue(const newValue : Integer);
protected
// The new property which w/e introduce in this class
FValue : Integer;
public { Public declarations }
Constructor Create(AOwner: TComponent); override; // This constructor uses defaults
property Text;
published { Published declarations - available in the Object Inspector at design-time }
property Hint;
// Now our own properties, which we are adding in this class
property Value : Integer read FValue write WriteValue;
property Warning : TWarning read FWarning write FWarning ;
end; // of class TIntegerEditBox()
procedure Register;
implementation
uses
Dialogs;
procedure Register;
begin
RegisterComponents('Standard', [TIntegerEditBox]);
end;
Constructor TIntegerEditBox.Create(AOwner: TComponent);
begin
inherited; // Call the parent Create method
Hint := 'Only accepts a number|Only accepts a number'; // Tooltip | status bar text
Mandatory := True;
Value := 0;
Text := IntToStr(Value);
end;
procedure TIntegerEditBox.WriteValue(const newValue : Integer);
begin
Text := IntToStr(newValue);
end;
end.
The original version of the demo code neglected to create an instance of the property object.
constructor TMyControl.Create(AOwner: TComponent)
begin
inherited;
FMyProp := TCustomType.Create;
end;
Don't forget to free it in the destructor.
Remy's comment on that answer points out that the property needs to be assigned differently. The property's write accessor shouldn't write directly to the field. Instead, it should have a setter method that works like this:
procedure TMyControl.SetMyProp(const Value: TCustomType);
begin
FMyProp.Assign(Value);
end;
That also highlights the requirement that the property class's Assign method be implemented, or else you'll get strange error messages like "Cannot assign a TCustomType to a TCustomType." A simple implementation could go like this:
procedure TCustomType.Assign(Source: TPersistent);
begin
if Source is TCustomType then begin
Color := TCustomType(Source).Color;
Height := TCustomType(Source).Height;
Width := TCustomType(Source).Width;
end else
inherited;
end;
I want to add aboutbox/dialogbox on my Custom component. how to make the small button[...] appear on the object inspector? just like the assigning a picure on the Timage component.
You must define a property similar to this:
//: InformaciĆ³n acerca del paquete de componentes
property AboutMe:TFAbout read FAboutG stored false;
TFAbout is a class, that define the form that you want to see (About form), when the user click on the property in "Object Inspector".
Additionally, you must register a "Property Editor", if you want see a buuton with the three point |...| in OI.
This is a sample unit:
unit UTAboutProp;
interface
uses
DesignIntf, DesignEditors;
type
TAboutGProp = class(TPropertyEditor)
public
procedure Edit(); override;
function GetValue(): string; override;
function GetAttributes(): TPropertyAttributes; override;
end;
implementation
uses
SysUtils, FormAbout, UConstantes;
procedure TAboutGProp.Edit();
begin
with TFAbout.Create(nil) do
try
ShowModal();
finally
Free();
end;
end;
function TAboutGProp.GetValue(): string;
begin
result := Format(GLIBSI_LBL,[GLIBSI_VERSION]);
result := '1.0';
end;
function TAboutGProp.GetAttributes(): TPropertyAttributes;
begin
result := [paDialog,paReadOnly];
end;
end.
Only rest to "register" this "property Editor" for work with your About property; This is important for "link" your property with your editor.
Where you have the code for register the component, add the code for register the property:
RegisterPropertyEditor(TypeInfo(TFAbout),nil,'',TAboutGProp);
Regards