I'm recently working on an Dart-Counter.
I have a Dartboard presented on a TImage and for every counting position there is a TShape.
Is it possible to assigne a value to a TShape, because the TShape does not like integer?
Thanks.
You could use the Tag property to store your integer value, if nothing else is using that property.
Tag has no predefined meaning. The Tag property can store any additional integer value for the convenience of developers.
Another way would be to derive a sub-class of TShape and add a property to store the information. This would allow you to give the property a more meaningful name, and make the intent clear.
I would comment though that building up a complex visual user interface out of TShape objects is perhaps an inefficient and error prone way to go about your task. Usually for a task like this is would be better to make a custom control that painted itself appropriately, without the use of sub-components.
Related
I'm writing a component which requires properties of type Date, Time, and Date/Time. I would like these properties to be visible in the Object Inspector, with an option to use a popup property editor.
I have tried TDate as a published property, and this gives me the results I need for just Date alone. However I need the same thing for TTime and TDateTime but they don't come with a property editor, and in fact it won't even accept any value I type in there either.
I have found the TDateTimeProperty which can be used as a property editor, or so I understand anyway. I have done the necessary implementation when registering this component. This property I need to apply it to is actually a TCollectionItem descendant, not necessarily a part of the component but within it.
This is how I'm registering it...
RegisterComponents('My Page', [TMyComponent]);
RegisterPropertyEditor(TypeInfo(TDateTime), TMyCollectionItem, 'MyPropName', TDateTimeProperty);
Although this compiles, when I install it, there is no property editor on this property. I have tried using my component's class name in place of TMyCollectionItem but same issue.
What am I doing wrong here to show this property editor?
You don't need to register the built-in property editors for TDateTime, TDate and TTime. They are already registered. That's why your attempts to register them have no impact.
The built-in property editors for these types simply convert between the underlying floating point value and a string representation. They don't implement date time pickers or anything like that.
You say:
However I need the same thing for TTime and TDateTime but they don't come with a property editor, and in fact it won't even accept any value I type in there either.
That is in fact incorrect. They do come with property editors. They are the same built-in property editors that you named in your question. And they do accept values. They don't accept the values you provided because you provided invalid values.
If you want to register a property editor that does provide a visual date time picker, then you will have to write the property editor yourself.
I have a Delphi 6 class object that contains an array of 30 Variants, each of which is exposed via a different indexed property. For example:
property responseCode: integer
Index 7 read getIndexedProperty_integer write setIndexedProperty_integer;
I did this to make using the array of Variants easier (helps the IDE's auto-complete) and to provide type safety. It works fine but now I have a wrinkle. The array of Variants are initialized to NULL when the class that wraps it is constructed, so I can tell if a particular variant has ever been instantiated with a value. A consequence of this is if only some of the Variants are instantiated (given valid values), any attempt to access a property that currently represents a NULL Variant will cause a Variant conversion error when Delphi tries to convert the variant to the type declared by the indexed property.
I would much rather not declare an "isValid" property for each indexed property. I was wondering if there was a way to use the TypeInfo library to get the raw value of the underlying Variant without having to access the indexed property directly and thus triggering the conversion Exception. Then I could write code like (using the example property above):
isValidProperty(responseCode);
and that function would return TRUE if the Variant underlying the responseCode property is not NULL and FALSE if it is.
I know I can walk the PPropList property list for the class and access the properties by name, but then I would have to use code like:
isValidProperty('responseCode');
and pass the property name in string form instead of passing in the property directly like the first isValidProperty() above. Is there a way to do this?
So you want "to get the raw value of the underlying Variant without having to access the indexed property directly and thus triggering the conversion Exception". So long as you can access the underlying Variant itself, yes, you can. You will need to change the container class itself most likely.
From the Delphi XE2 help page on variant types:
The standard function VarType returns a variant's type code. The
varTypeMask constant is a bit mask used to extract the code from
VarType's return value, so that, for example,
VarType(V) and varTypeMask = varDouble
returns True if V contains a Double or an
array of Double. (The mask simply hides the first bit, which indicates
whether the variant holds an array.) The TVarData record type defined
in the System unit can be used to typecast variants and gain access to
their internal representation.
You should be able to use a combination of the methods and records mentioned here to find out anything you want about the internal data inside the variant, including if it's a NULL variant, as well as getting direct access to it.
(This system seems slightly dodgy design to me: it doesn't seem a very type safe implementation... see my comment above. I think a design based on the actual types of the values you are expecting might be safer. But, this will let you achieve your goal.)
ShowMessage(TRttiContext.Create.GetType(TStringList)
.GetProperty('Strings').ToString);
Above code fails as .GetProperty returns nil on properties like "Strings", "Objects", "Values" (ones with indexers). I assume this is a known limitation and the question is if there's any way to access those indexed properties (preferably without falling back to the old RTTI utils).
Indexed properties don't have RTTI, but the underlying fields do. So you can access TStringList.FList directly through RTTI. Be careful, though, as this involves raw pointers, and make sure you don't go beyond the Count property. You can do similar things with other classes.
There are gaps in the RTTI. Indexed properties are one.
But when you don't get the property name, why you try to access them? ;-) When you know there is such a property you can try a cast instead.
You don't get RTTI for method parameters of the typ
procedure MyProc(const AParam: array of AType)
also.
Anybody knowing more elements were we can't get RTTI?
i have a property editor (descendant of TPropertyEditor) that is used to edit a property.
When it comes time to edit my property, how do i know what property of what object i'm editing? If i'm going to edit a property, i have to know what property i'm editing.
i've been pulling my hair out, sifting through the Delphi help, the online help, and the TPropertyEditor and descendant source code, and i can't find the answer.
i expected something like:
TPropertyEditor = class(...)
public
procedure Initialize(TheObject: TObject; ThePropertyName: string);
end;
As far as i can tell, my property editor is created, and i will be told to "Edit", and i just have to divine what property they wanted me to edit.
From the help:
Editing the property as a whole
You can optionally provide a dialog
box in which the user can visually
edit a property. The most common use
of property editors is for properties
that are themselves classes. An
example is the Font property, for
which the user can open a font dialog
box to choose all the attributes of
the font at once.
To provide a
whole-property editor dialog box,
override the property-editor class’s
Edit method.
Edit methods use the same
Get and Set methods used in writing
GetValue and SetValue methods. In
fact, an Edit method calls both a Get
method and a Set method. Because the
editor is type-specific, there is
usually no need to convert the
property values to strings. The editor
generally deals with the value “as
retrieved.”
When the user clicks the ‘...’ button
next to the property or double-clicks
the value column, the Object Inspector
calls the property editor’s Edit
method.
Within your implementation of
the Edit method, follow these steps:
Construct the editor you are using
for the property.
Read the current
value and assign it to the property
using a Get method.
When the user
selects a new value, assign that value
to the property using a Set method.
Destroy the editor.
Answer
It's tucked away, and not documented, but i found out how. The property i'm editing that i edit:
TheCurrentValue := TMyPropertyThing(Pointer(GetOrdValue));
Now that i have the value, i can edit it. If i want to replace the property with some other object:
SetOrdValue(Longint(TheNewValue));
The full code:
Create a property editor that descends from TClassProperty:
TMyPropertyEditor = class(TClassProperty)
public
procedure Edit; override;
function GetAttributes: TPropertyAttributes; override;
end;
First is the housekeeping, telling Delphi's object inspector that my property editor will display a dialog box, this will make a "..." appear next to the property:
function TMyPropertyEditor.GetAttributes: TPropertyAttributes;
begin
//We show a dialog, make Object Inspector show "..."
Result := [paDialog];
end;
Next is the actual work. When the user clicks the "..." button, the object inspector calls my Edit method. The trick that i was missing is that i call my GetOrdValue method. Even though my property isn't an ordinal, you still use it, and just cast the resulting thing to an object:
procedure TMyPropertyEditor.Edit;
var
OldThing: TMyPersistentThing;
NewThing: TMyPersistentThing;
begin
//Call the property's getter, and return the "object" i'm editing:
OldThing:= TMyPersistentThing(Pointer(GetOrdValue));
//now that i have the thing i'm editing, do stuff to "edit" it
DoSomeEditing(OldThing);
//i don't have to, but if i want to replace the property with a new object
//i can call the setter:
NewThing := SomeVariant(OldThing);
SetOrdValue(Longint(NewThing));
end;
If I understand your question right, you're wondering how you're supposed to actually find the value you need to be editing, especially if the object in question contains more than one of them. The answer is that the IDE sets that up for you and the property editor comes "preloaded" by the time Edit is called. TPropertyEditor comes with a bunch of GetValue methods that your Edit function can use to retrieve the value. Or if it's not one of those types, (if it's an object descended from TPersistent, for example,) then you can call GetOrdValue and cast the result to a TPersistent.
Also, you might want to check out TJvPersistentPropertyEditor in the JvDsgnEditors unit of the JVCL to use as a base class. It provides some of the functionality for you.
BTW if you really need it, you can use the GetName method, which will give you the name of the property, but you usually shouldn't have to. And be careful if you're inheriting from something other than TPropertyEditor itself, as GetName is virtual and can be overridden.
A property editor keeps the information about which objects and properties it's editing in the private FPropList variable. The IDE fills that in by calling your editor's SetPropEntry method. You're then generally supposed to call the various methods of TPropertyEditor to find out the properties' values.
It's not really supposed to matter which property you were asked to edit. Your property editor edits properties of a particular type. For in-place editing, your editor provides an implementation of SetValue that translates the string from the Object Inspector into a value of the proper type for the property, and then you call the appropriate Set function, such as SetOrdValue or SetIntfValue. For whole-property editing, the IDE won't call SetValue. Instead, it will call Edit, and you're expected to call GetOrdValue or GetIntfValue (for example) yourself, since your editor already knows what type of property it's designed to edit.
Remember that property editors, in general, can edit multiple properties simultaneously. The name of the property will be the same for all of them, but the type of component they belong to may vary, and thus so may their getters and setters. Call your property editor's GetName method to find out the name. To get the objects, call GetComponent for each index from 0 to PropCount - 1. (Be careful; there's no range checking in those Get functions.) You can also check whether a specific object is in the list by calling HasInstance. The GetPropInfo method will tell you the PPropInfo pointer for the first property, but I don't think that will necessarily be equal to the pointers of all the other properties. Aside from that, you don't get direct access to the PPropInfo data, but again, it really shouldn't matter. If you think you need that for your editor, you're probably doing something wrong; post a new question with more specific information about your underlying task.
Property editors only care about (and are registered for) the type of the property, not the specific property itself.
To avoid singletons and global variables I'd like to be able to pass parameters to a TFrame component. However since a TFrame normally is included on form at design time it is only possible to use the default constructor.
The parent form can of course set some properties in the OnCreate callback after the TFrame has been created. However this does not ensure that a property is not forgotten, and the dependencies are not as clear as using a constructor.
A nice way would be if it was possible to register a factory for creating components while the dfm file is being read. Then the required parameters could be passed to the TFrame constructor when created by the factory. Is there a way of accomplishing this?
Or does anyone have a better solution on how to pass parameters to a TFrame?
All components, including descendants of TFrame, need to be able to be constructed using the constructor inherited from TComponent. Otherwise, they can't be used properly at design time. If the restriction of design-time use is acceptable to you, then you could override that constructor and raise an exception. That would prevent the component from being placed on a form at design time. Simply provide some other constructor that requires other parameters.
Because of the design-time requirement, all components need to be able to exist with some or all of their properties still at their default values. That doesn't mean the components have to do useful things while they're in that state, but they do need to be able to stay in that state indefinitely. It should be OK, for example, to place a component on a form, save the form, and close Delphi, with the intention of resuming the form-designing at a later time. The component should allow itself to be saved and restored, even if all its properties haven't been set up for final use yet.
My preferred option is to enforce the component's rules only at run time. Check that all the properties are set to sensible values before you allow them to be used. You can use assertions to enforce the proper use of your components. Consumers of your classes will learn very quickly if they haven't finished setting up your components on their forms.
I would normally add a public, non-virtual "Initialise" or (Initialize to Americans) procedure which requires all parameters to be provided. This will then set the properties.
Make the properties protected or private if possible, so the only way they can be set is from calling Initialise(AFoo, ABar : integer).
Then in TFormXXX.FormCreate or TformXXX.Create, have:
inherited;
Initialise(foo, bar);
could you create/registercomponent your own tFrame component and
place that on the form - it's create could have anything passed to it.
If a factory could provide the parameters that you need, why don't you just override the default constructor for your frame, and ask the factory-class for parameters?
I usually make my own constructor. I don't like to create frames at designtime anyway.
a) a frame can be created dynamically when required and destroyed when not needed
b) give the frame a public property with either the parameter data type or a data structure and pass the values to the form through the property.
Example:
TAddress - a class to hold the usual elements of an address.
TAddressFra - a frame with the visual controls to display the address
populate an instance of TAddress with values
create an instance of TAddressFra
assign the TAddressFra.address property with the TAddress instance
use the procedure setAddress(o_address : TAddress) to assign the values of the TAddress attributes to the corresponding visual components on the TAddressFra