I'm trying to extend the collection "Buttons" of type "TcxEditButtons". The purpose is to add an "OnClick" event and a "Shortcut" property on all buttons of a legacy TcxButtonEdit component. I started by overwriting the "Properties" property with the code below:
type
TMycxDBButtonEditProperties = class(TcxCustomButtonEditProperties)
private
FButtons: TMycxEditButtons;
procedure SetButtons(const Value: TMycxEditButtons);
function GetButtons: TMycxEditButtons;
public
constructor Create(AOwner: TPersistent); override;
procedure AfterConstruction; override;
published
property Buttons: TMycxEditButtons read GetButtons write SetButtons;
end;
...
in my component i do this
...
type
TMycxDBButtonEdit = class(TcxCustomButtonEdit)
FProperties: TMycxDBButtonEditProperties;
published
property Properties: TMycxDBButtonEditProperties read FProperties write SetProperties;
The problem is somewhat obvious: the collection is available for editing, but does not reflect the actual ancestral property "Buttons". The question is: How do I make my collection affect the buttons on the component?
I tried to understand and apply what is described in the links below the support of Dev Express, but without success (incompetence)
https://www.devexpress.com/Support/Center/Question/Details/Q136143/creating-custom-tcxbuttonedit
https://www.devexpress.com/Support/Center/Question/Details/Q35461/do-you-have-information-on-creating-own-tcxcustomedit-descendant
https://www.devexpress.com/Support/Center/Question/Details/A483/how-to-hide-default-button-s-in-a-dropdown-editor-or-add-extra-buttons
Based on the #nil comment, I got the expected result. Below is the code snippet for those with the same type of need.
type
TZcxEditButton = class (TcxEditButton)
...
published
property Shortcut: TShortCut read FShortcut write SetShortcut;
property OnClick: TNotifyEvent read FOnClick write SetOnClick;
end;
type
TZcxEditButtons = class(TcxEditButtons)
public
class function GetButtonClass: TcxEditButtonClass; override;
end;
type
TZcxButtonEditProperties = class(TcxButtonEditProperties)
public
class function GetButtonsClass: TcxEditButtonsClass; override;
end;
type
TZcxButtonEdit = class(TcxButtonEdit)
public
class function GetPropertiesClass: TcxCustomEditPropertiesClass; override;
end;
implementation
class function TZcxEditButtons.GetButtonClass: TcxEditButtonClass;
begin
Result := TZcxEditButton;
end;
class function TZcxButtonEditProperties.GetButtonsClass: TcxEditButtonsClass;
begin
Result := TZcxEditButtons;
end;
class function TZcxButtonEdit.GetPropertiesClass: TcxCustomEditPropertiesClass;
begin
Result := TZcxButtonEditProperties;
end;
Note: I accept suggestions for improvement
You should extend your new class from main class TcxEditButton .
It is not a good way to extend Property Class TcxCustomButtonEditProperties.
So create a new class, extended from TcxEditButton and add your new Methods and property to it.
Related
I'm still a bit fuzzy with generics in Delphi, but have been using TObjectList<> quite widely. Now I have a situation where I have a base class with such a private field, but needs to be created for an arbitrary class, also inherited from another base.
To clarify, I have two base classes:
type
TItem = class;
TItems = class;
TItemClass = class of TItem;
TItem = class(TPersistent)
private
FSomeStuffForAllIneritedClasses: TSomeStuff;
end;
TItems = class(TPersistent)
private
FItems: TObjectList<TItem>;
FItemClass: TItemClass;
public
constructor Create(AItemClass: TItemClass);
destructor Destroy; override;
function Add: TItem;
...
end;
This pair of classes is then further inherited into more specific classes. I'd like the object list to be shared for all of them, while each holds actually a different type internally.
type
TSomeItem = class(TItem)
private
FSomeOtherStuff: TSomeOtherStuff;
...
end;
TSomeItems = class(TItems)
public
function Add: TSomeItem; //Calls inherited, similar to a TCollection
procedure DoSomethingOnlyThisClassShouldDo;
...
end;
Now the problem is when it comes to creating the actual object list. I'm trying to do it like this:
constructor TItems.Create(AItemClass: TItemClass);
begin
inherited Create;
FItemClass:= AItemClass;
FItems:= TObjectList<AItemClass>.Create(True);
end;
However, the code insight complains about this:
Undeclared Identifier AItemClass
Even more, the compiler has yet a different complaint:
Undeclared Identifier TObjectList
Where, I do in fact have System.Generics.Collections used in this unit.
What am I doing wrong here, and how should I do this instead?
Make TItems generic:
TItems<T: TItem, constructor> = class(TPersistent)
private
FItems: TObjectList<T>;
public
constructor Create;
destructor Destroy; override;
function Add: T;
...
end;
constructor TItems.Create;
begin
inherited Create;
FItems:= TObjectList<T>.Create(True);
end;
function TItems<T>.Add: T;
begin
Result := T.Create;
FItems.Add(Result);
end;
If you inherit, simply put the correct generic parameter:
TSomeItems = class(TItems<TSomeItem>)
public
procedure DoSomethingOnlyThisClassShouldDo;
...
end;
The TObjectList is not meant to be used in that manner. The fact that it was originally defined as TObjectList<TItem> means that it will expect you to create it this way as well. It needs to be defined with the precise class you intend to create it as.
Instead, just create it with TItem, and then whenever you create a new item which is supposed to be added to this list, then you create it using the class type. Any time you need to access the items in this list, just cast them on the fly.
For example...
Result:= FItemClass.Create;
FItems.Add(Result);
...can be the contents of your Add function.
I have a class that contains a TObjectList<T> list of objects of another class.
TMyElementClass = class (TPersistent)
private
....
public
....
end;
TMyElemContainerClass = class (TPersistent)
private
fElemList: TObjectList<TMyElementClass>;
...
published
ElemList: TObjectList<TMyElementClass> read fElemList write fElemList;
end;
var
Elements: TMyElemContainerClass;
I register both classes:
System.Classes.RegisterClass (TMyElemContainerClass);
System.Classes.RegisterClass (TMyElementClass);
The problem is, when the Elements object is "saved" to a stream, all of the published fields are correctly saved, but the list itself isn't.
What's wrong?
TObjectList is not a streamable class. Just because you use it in a published property doesn't mean the streaming system automatically knows how to stream it. If you are using the DFM streaming system, only classes that derive from TPersistent are streamable, but TObjectList does not. You will have to implement custom streaming logic for it.
Consider changing your design to use TCollection and TCollectionItem instead, eg:
TMyElementClass = class (TCollectionItem)
private
...
public
...
published
...
end;
TMyElemCollectionClass = class (TCollection)
private
function GetElem(Index: Integer): TMyElementClass;
procedure SetElem(Index: Integer; Value: TMyElementClass);
public
constructor Create; reintroduce;
function Add: TMyElementClass; reintroduce;
function Insert(Index: Integer): TMyElementClass; reintroduce;
property Elements[Index: Integer]: TMyElementClass read GetElem write SetElem; default;
end;
TMyElemContainerClass = class (TPersistent)
private
fElemList: TMyElemCollectionClass;
procedure SetElemList(Value: TMyElemCollectionClass);
...
public
constructor Create;
destructor Destroy; override;
...
published
ElemList: TMyElemCollectionClass read fElemList write SetElemList;
end;
...
constructor TMyElemCollectionClass.Create;
begin
inherited Create(TMyElementClass);
end;
function TMyElemCollectionClass.GetElem(Index: Integer): TMyElementClass;
begin
Result := TMyElementClass(inherited GetItem(Index));
end;
procedure TMyElemCollectionClass.SetElem(Index: Integer; Value: TMyElementClass);
begin
inherited SetItem(Index, Value);
end;
function TMyElemCollectionClass.Add: TMyElementClass;
begin
Result := TMyElementClass(inherited Add);
end;
function TMyElemCollectionClass.Insert(Index: Integer): TMyElementClass;
begin
Result := TMyElementClass(inherited Insert(Index));
end;
constructor TMyElemContainerClass.Create;
begin
inherited;
fElemList := TMyElemCollectionClass.Create;
end;
destructor TMyElemContainerClass.Destroy;
begin
fElemList.Destroy;
inherited;
end;
procedure TMyElemContainerClass.SetElemList(Value: TMyElemCollectionClass);
begin
fElemList.Assign(Value);
end;
Remy gave you one avenue to try.
Another possible way would be implement this list streaming manually.
You would have to
derive your TMyElemContainerClass from TComponent
remove ElemList from published properties
override DefineProperties method that would declare some virtual, not-existing published property to be streamed in and out. You may even name it ElemList or by any other identifier you would see fit. It would be used by Delphi instead your TObjectList object.
implement stream-oriented reader and writer methods for the said virtual property, they should iterate through all the items and save/load them.
Explore documentation from here: http://docwiki.embarcadero.com/Libraries/Berlin/en/System.Classes.TComponent.DefineProperties
See one of many (and there really are many) examples (using arrays rather than lists, but the idea is the same) at How to use DefineProperties in a custom Class Object for Arrays - Delphi
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
Suppose I have the following as an example:
TDelphiIDECompatibility = (
Delphi1,
Delphi2,
Delphi3);
From a class, how could I implement the above correctly as a property?
The idea is that in my component I want to have a field that will allow you to select True or False for certain elements in a Set.
I tried to declare like this without much luck:
TMyClass = class
private
FIDECompatibility: Set of TDelphiIDECompatibility;
public
constructor Create;
destructor Destroy; override;
property IDECompatibility: TDelphiIDECompatibility read
FIDECompatibility write FIDECompatibility;
end;
The error message been:
Incompatible types: 'TDelphiIDECompatibility' and 'Set'
The quick way I know is to just declare them as regular booleans, like so:
private
FDelphi1Compatible: Boolean;
FDelphi2Compatible: Boolean;
FDelphi3Compatible: Boolean;
public
constructor Create;
destructor Destroy; override;
property Delphi1Compatible: Boolean read
FDelphi1Compatible write FDelphi1Compatible;
end;
But I don't really like having it like that when I can have them defined in a Set/Enumeration?
What should I be doing to declare it properly instead?
Thank you.
From TLama's comment I looked at the Delphi source for Anchors and came up with the solution here:
TDelphiIDECompatibilityKind = (
Delphi1,
Delphi2,
Delphi3);
TDelphiIDECompatibility = set of TDelphiIDECompatibilityKind;
And the class:
private
FIDECompatibility: TDelphiIDECompatibility;
public
constructor Create;
destructor Destroy; override;
property IDECompatibility: TDelphiIDECompatibility read
FIDECompatibility write FIDECompatibility;
end;
Can you have a class function that creates an instance of a class:
TMyClass = class(TSomeParent)
public
class function New(AValue : integer) : TMyClass;
end;
TDerivedClass = class(TMyClass)
public
function Beep;
end;
and then use it as follows
...
var
myList : TList<T>;
item : TDerivedClass;
begin
myList.Add(TDerivedClass.New(1))
myList.Add(TDerivedClass.New(3))
myList.Add(TDerivedClass.New(5))
for item in myList do
item.Beep; //times the count in the class function
...
And if so, what does that function code look like? Do you use TObject's NewInstance method and do you re-implement every-time for every derived class? Is it saver/better to use the Constructor?
The goal is to use this approach in a command pattern and load the command list with class types and a receiver e.g:
//FYI: document is an instance of TDocument
commandList.Execute(TOpenDocument(document));
commandList.Execute(TPasteFromClipboard(document));
//... lots of actions - some can undo
commandList.Execute(TPrintDocument(document));
commandList.Execute(TSaveDocument(document));
And the reason for this is that some commands will be specified via text/script and will need to be resolved at runtime.
What you're looking for is called the factory pattern. It can be done in Delphi; it's how the VCL deserializes forms, among other things. What you're missing is the registration/lookup part of the system. Here's the basic idea:
Somewhere, you set up a registration table. If you're on Delphi XE, you can implement this as a TDictionary<string, TMyClassType>, where TMyClassType is defined as class of TMyClass. This is important. You need a map between class names and class type references.
Put a virtual constructor on TMyClass. Everything that descends from it will use this constructor, or an override of it, when the factory pattern creates it.
When you create a new descendant class, have it call a method that will register itself with the registration table. This should happen at program startup, either in initialization or in a class constructor.
When you need to instantiate something from a script, do it like this:
class function TMyClass.New(clsname: string; [other params]): TMyClass;
begin
result := RegistrationTable[clsName].Create(other params);
end;
You use the registration table to get the class reference from the class name, and call the virtual constructor on the class reference to get the right type of object out of it.
Yes, it is technically possible to create an instance from a class method, simply call the actual constructor and then return the instance it creates, eg:
type
TMyClass = class(TSomeParent)
public
constructor Create(AValue : Integer); virtual;
class function New(AValue : integer) : TMyClass;
end;
TDerivedClass = class(TMyClass)
public
constructor Create(AValue : Integer); override;
function Beep;
end;
constructor TMyClass.Create(AValue : Integer);
begin
inherited Create;
...
end;
function TMyClass.New(AValue : integer) : TMyClass;
begin
Result := Create(AValue);
end;
constructor TDerivedClass.Create(AValue : Integer);
begin
inherited Create(AValue);
...
end;
var
myList : TList<TMyClass>;
item : TMyClass;
begin
myList.Add(TDerivedClass.New(1))
myList.Add(TDerivedClass.New(3))
myList.Add(TDerivedClass.New(5))
for item in myList do
TDerivedClass(item).Beep;
In which case, you are better off just using the constructor directly:
type
TMyClass = class(TSomeParent)
end;
TDerivedClass = class(TMyClass)
public
constructor Create(AValue : Integer);
function Beep;
end;
var
myList : TList<TDerivedClass>;
item : TDerivedClass;
begin
myList.Add(TDerivedClass.Create(1))
myList.Add(TDerivedClass.Create(3))
myList.Add(TDerivedClass.Create(5))
for item in myList do
item.Beep;
Can you have a class function that creates an instance of a class.
Is it saver/better to use the Constructor?
Constructor is a class function that creates an instance of class.
Just put:
constructor New(); virtual;
And you are good to go.
The virtual; part will let you call same New() constructor for all descendant classes.
Another option is to use RTTI. The code below runs as a normal method in my class as a way to get a new instance of the object with a subset of items, but as the items (along with the list object itself) are probably of descendent objects, creating an instance of the object in which the method is defined isn't good enough as it needs to be of the same type of the instance.
i.e.
TParentItem = Class
End;
TParentList = Class
Items : TList<TParentItem>;
Function GetSubRange(nStart,nEnd : Integer) : TParentList;
End;
TChildItem = Class(TParentItem)
end
TChildList = Class(TParentList)
end
List := TChildList.Create;
List.LoadData;
SubList := List.GetSubRange(1,3);
The implementation if GetSubRange would be something like...
Function TParentList.GetSubRange(nStart,nEnd : Integer) : TParentList;
var
aContext: TRttiContext;
aType: TRttiType;
aInsType : TRttiInstanceType;
sDebug : String;
begin
aContext := TRttiContext.Create;
aType := aContext.GetType(self.ClassType);
aInsType := aType.AsInstance;
Result := aInsType.GetMethod('Create').Invoke(aInsType.MetaclassType,[]).AsType<TParentList>;
sDebug := Result.ClassName; // Should be TChildList
// Add the items from the list that make up the subrange.
End;
I appreciate for some things it may be a bit OTT, but in the design above, it works and is another alternative, although I appreciate, its not a class method.
You should use a constructor (a special "kind" of class function). TObject.NewInstance is not a suitable option, unless you require special memory allocation.
And regarding the Execute routine of the command list: the action involved now depends on the type of the object. Imagine a document being able to open, print, paste and save at the same time (not a weird assumption), that would be difficult to implement in this structure. Instead, consider to add interfaces (IOpenDocument, IPasteFromClipboard, IPrintable, ISaveDocument) which indeed could all be actions of one document instance.