I'm using Delphi's GetObjectProp function to get the properties of the form components, I get all the properties of several components, but I can not get the TextSettings.Font.Style (Bold, Italic, ...) property of components like TLabel for example. I need to know if the component text is bold or italic. The procedure I am working on trying to get these properties follows below:
procedure Tfrm1.aoClicarComponente(Sender: TObject);
var
TextSettings: TTextSettings;
Fonte: TFont;
Estilo: TFontStyle;
Componente_cc: TControl;
begin
Componente_cc := TControl(Label1);
if IsPublishedProp(Componente_cc, 'TextSettings') then
begin
TextSettings := GetObjectProp(Componente_cc, 'TextSettings') as TTextSettings;
if Assigned(TextSettings) then
Fonte := GetObjectProp(TextSettings, 'Font') as TFont;
if Assigned(Fonte) then
Estilo := GetObjectProp(Fonte, 'Style') as TFontStyle; // <-- error in this line
if Assigned(Estilo) then
Edit1.text := GetPropValue(Estilo, 'fsBold', true);
end
end;
The error displayed on the line where I marked above is.
[dcc64 Error] uPrincipal.pas(1350): E2015 Operator not applicable to this operand type
What am I doing wrong?
GetObjectProp(Fonte, 'Style') will not work since Style is not an object-based property to begin with, it is a Set-based property. And GetPropValue(Estilo, 'fsBold', true) is just plain wrong (not that you would get far enough to call it anyway), because fsBold is not a property, it is a member of the TFontStyle enum. To retreive the Style property value, you would have to use GetOrdProp(Fonte, 'Style'), GetSetProp(Fonte, 'Style'), or GetPropValue(Fonte, 'Style') instead (as an integer, string, or variant, respectively).
That being said, once you have retrieved the TextSettings object, you don't need to use RTTI at all to access its Font.Style property, just access the property directly.
Try this instead:
procedure Tfrm1.aoClicarComponente(Sender: TObject);
var
Componente_cc: TControl;
TextSettings: TTextSettings;
begin
Componente_cc := ...;
if IsPublishedProp(Componente_cc, 'TextSettings') then
begin
TextSettings := GetObjectProp(Componente_cc, 'TextSettings') as TTextSettings;
Edit1.Text := BoolToStr(TFontStyle.fsBold in TextSettings.Font.Style, true);
end;
end;
A better (and preferred) solution is to not use RTTI at all. FMX classes that have a TextSettings property also implement the ITextSettings interface for exactly this situation, eg:
procedure Tfrm1.aoClicarComponente(Sender: TObject);
var
Componente_cc: TControl;
Settings: ITextSettings;
begin
Componente_cc := ...;
if Supports(Componente_cc, ITextSettings, Settings) then
begin
Edit1.Text := BoolToStr(TFontStyle.fsBold in Settings.TextSettings.Font.Style, true);
end;
end;
Read Embarcadero's documentation for more details:
Setting Text Parameters in FireMonkey
Related
In Delphi 10.4, in a VCL Application, using the OnMessage event-handler of a TApplicationEvents component, I increase the font-size of the right-clicked Control:
procedure TformMain.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
var
ThisControl: TControl;
begin
if (Msg.Message = WM_RBUTTONDOWN) then
begin
ThisControl := FindDragTarget(Mouse.CursorPos, True);
CodeSite.Send('TformMain.ApplicationEvents1Message: RIGHTCLICK!', ThisControl.Name);
if ThisControl is TLabel then
TLabel(ThisControl).Font.Size := TLabel(ThisControl).Font.Size + 1
else if ThisControl is TCheckBox then
TCheckBox(ThisControl).Font.Size := TCheckBox(ThisControl).Font.Size + 1;
// ETC. ETC. ETC.! :-(
end;
end;
This is an extremely INEFFICIENT way to make this work for all Control Types because I would have to enumerate all existent Control Types, as TControl does not have a TFont property.
A better way would be to get the TFont property of the control without having to ask for the TYPE and then having to TYPECAST the Control.
But HOW?
If you redeclare the type, you get access to the protected properties of the class.
Nowadays you do that with an interposer class, but I'm still used to the old ways.
You might have to add a check, if it turns out that a particular control bombs, when you do something with the font. It has always worked for me so far.
type
TCrackControl = class(TControl);
procedure TformMain.ApplicationEvents1Message(var Msg: tagMSG; var Handled: Boolean);
var
ThisControl: TCrackControl;
begin
if (Msg.Message = WM_RBUTTONDOWN) then
begin
ThisControl := TCrackControl(FindDragTarget(Mouse.CursorPos, True));
CodeSite.Send('TformMain.ApplicationEvents1Message: RIGHTCLICK!', ThisControl.Name);
If assigned(ThisControl.Font) then
ThisControl.Font.Size := ThisControl.Font.Size + 1;
end;
end;
I want to use findclass and findcomponent to be able to pass the sender component as parameter in a procedure.
Thank you for reading.
Edit: I use Delphi 2005
[Error]: E2003 Undeclared identifier: 'text'
TestMemo.Text := (FindComponent(VonKomponente.name) as
(Findclass(vonkomponente.ClassType.ClassName))).text; -> does not work
TestMemo.Text := (FindComponent(VonKomponente.name) as TEdit).text; -> works
procedure TFormTest.Edit7DblClick(Sender: TObject);
begin
MemoEdit((Sender as TComponent),'table','row');
end;
procedure TFormTest.MemoEdit(VonKomponente :TComponent;table,row : String);
begin
FormTestMemo.Max_Textlaenge := get_length(table,row);
FormTestMemo.Text := (FindComponent(VonKomponente.name) as
(Findclass(vonkomponente.ClassType.ClassName))).text;
If FormTestMemo.Showmodal = MrOk then
begin
...
end;
end;
What you are trying to do is not possible. You cannot pass a metaclass type determined at runtime to the as operator.
For what you are trying to do, you will have to resort to using old-style RTTI via the TypInfo unit, in this case the TypInfo.GetStrProp() function, eg:
uses
..., TypInfo;
FormTestMemo.Text := GetStrProp(VonKomponente, 'Text');
Note that not all text-based components have a Text property, some have a Caption property, eg:
uses
..., TypInfo;
var
prop: PPropInfo;
prop := GetPropInfo(VonKomponente, 'Text');
if prop = nil then
prop := GetPropInfo(VonKomponente, 'Caption');
if prop <> nil then
FormTestMemo.Text := GetStrProp(VonKomponente, prop)
else
FormTestMemo.Text := '';
I have a component with a lot of properties, many of which are types.
For example:
BackgroundStyle = [bsSolid, bsGradient, bsNone]
BorderStyle = [bsNone, bsSingle, bsWide]
I am building a form to allow the user to configure these properties at runtime and I would like to populate some dropdown lists dynamically, rather than having to type all of them in by hand.
Is this possible? Thanks!
Use RTTI for that. Specifically, look at the GetPropInfo() and GetEnumName() functions in the TypInfo unit.
Remy is on the ball with this one. Lately I just happen to do something similar and a bit of refactoring (within a text editor, so you mileage may vary with the complier):
class function TEnumerationRoutines.TitleCaseDescriptionFromOptions<T>: TStrings;
var
LRttiContext : TRttiContext;
LRttiEnumerationType: TRttiEnumerationType;
LTypeInfo : Pointer;
LPTypeInfo : PTypeInfo;
lp: Integer;
begin
LTypeInfo := TypeInfo(T);
LPTypeInfo := PTypeInfo(LTypeInfo);
if LPTypeInfo^.Kind <> tkEnumeration then
raise Exception.Create('Type is not an enum');
Result := TStringList.Create;
LRttiEnumerationType := LRttiContext.GetType(LTypeInfo) as TRttiEnumerationType;
for lp := LRttiEnumerationType.MinValue to LRttiEnumerationType.MaxValue do
Result.Add(GetEnumName(LTypeInfo, Ord(lp)));
end;
and call it with:
MyStrings := TEnumerationRoutines.TitleCaseDescriptionFromOptions<BackgroundStyle>;
or
MyStrings := TEnumerationRoutines.TitleCaseDescriptionFromOptions<BorderStyle>;
In Delphi, sometimes we need to do this...
function TForm1.EDIT_Click(Sender: TObject);
begin
(Sender As TEdit).Text := '';
end;
...but sometimes we need to repeat the function with other object class like...
function TForm1.COMBOBOX_Click(Sender: TObject);
begin
(Sender As TComboBox).Text := '';
end;
...because the operator As does not accept flexibility. It must know the class in order to allow the .Text that come after the ().
Sometimes the code gets full of similar functions and procedures because we need to do the same thing with similar visual controls that we can't specify.
This is only an case of use example. Generally I use these codes on more complex codes to achieve a standard objective on many controls and other kind of objects.
Is there an alternative or trick to make these tasks more flexible?
Use RTTI to perform common tasks on similarly-named properties of unrelated classes, eg:
Uses
..., TypInfo;
// Assigned to both TEdit and TComboBox
function TForm1.ControlClick(Sender: TObject);
var
PropInfo: PPropInfo;
begin
PropInfo := GetPropInfo(Sender, 'Text', []);
if Assigned(PropInfo) then
SetStrProp(Sender, PropInfo, '');
end;
In some cases, some controls use Text and some use Caption instead, eg;
function TForm1.ControlClick(Sender: TObject);
var
PropInfo: PPropInfo;
begin
PropInfo := GetPropInfo(Sender, 'Text', []);
if not Assigned(PropInfo) then
PropInfo := GetPropInfo(Sender, 'Caption', []);
if Assigned(PropInfo) then
SetStrProp(Sender, PropInfo, '');
end;
you can use the is operator, try this sample
if Sender is TEdit then
TEdit(Sender).Text:=''
else
if Sender is TComboBox then
TComboBox(Sender).Text:='';
You can eliminate the messy type-casting by using the absolute keyword which allows you to declare variables of different types occupying the same memory location, in this case the same location as the event parameter.
You still need to perform the type checking using "is" but in other respects this approach is a bit cleaner but just as safe.
procedure TMyForm.ControlClick(Sender: TObject);
var
edit: TEdit absolute Sender;
combo: TComboBox absolute Sender;
:
begin
if Sender is TEdit then
edit.Text := ''
else if Sender is TComboBox then
combobox.Text := ''
else
:
end;
I wrote in more detail about using this language feature in my blog almost 3 years ago.
I'm posting my comment as an answer because I don't see any answer here that mentions this. SetTextBuf is a public method of TControl. This method is utilized to populate the internal text data member via the SetText windows message. This is how the a TControl descendant updates both the Caption and Text properties. So all TControl descendants, such as TButton, TEdit, TComboBox will work using the following type of code. And you don't have to use RTTI.
function TForm1.EDIT_Click(Sender: TObject);
begin
(Sender as TControl).SetTextBuf('Text or Caption'); // will work for both the Caption and text property
end;
I don't know if you are using the tag property for anything but it can be useful for these situations. Setting the tag of all Tedits to say 1 and the tag of all Tcomboboxes to 2 etc could let you do:
if Sender is TControl then
Case TControl(Sender).tag of
1: TEdit(sender).text := '';
2: Tcombobox(sender).text := '';
3....etc
end;
Just a thought and it looks neater and easier to read/debug:)
Thanks to you people, specially #RemyLebeau, I could make this universal function that applies do any kind of Win Control or Data Base Control. It turns the control in Red (or whatever color you want) if it's Required but empty, if it has repeated information on the Data Base, or whatever other condition we want to check. It return numbers instead of true or false, so we can send only one message at the end of many checks and tell the user how many error did he/she made.
function CheckInput(Control: TWinControl; Condition: Boolean; EmptyState: Integer; Msg: String): Integer;
var
PropInfo: PPropInfo;
begin
{ os controles que precisam passar por condições para que seu conteúdo seja aceito }
Result := 0;
if EmptyState = ciNotEmpty then
begin
PropInfo := GetPropInfo(Control, 'Text', []);
if Assigned(PropInfo) then
begin
if GetStrProp(Control, PropInfo) = '' then
begin
Condition := False;
Msg := ciEmptyMsg;
end;
end;
end;
if not Condition then
begin
Result := 1;
PropInfo := GetPropInfo(Control, 'Color', []);
if Assigned(PropInfo) then SetPropValue(Control, PropInfo, ciErrorColor);
if Msg <> '' then ShowMessage(Msg);
end
else
begin
PropInfo := GetPropInfo(Control, 'Color', []);
if Assigned(PropInfo) then SetPropValue(Control, PropInfo, ciNormalColor);
end;
end;
If you go all the way down, you'll notice that both TEdit and TCombobox descend from TControl. If you look which method they use to set their text then you'll see it's the method implemented by TControl. That's why you can do something ugly like:
if (sender is TEdit) or (sender is TComboBox) then
TEdit(sender).Text:='test';
You have to make sure that all objects you put in here use the same method internally or your application will break in mysterious ways.
after reading the post How to set event handlers via new RTTI?, I wonder if it is possible to solve this more dynamically. For example I want to set ALL event handlers of any component to nil.
Using TValue.From <TNotifyEvent> (SomeMethod) does not work for two reasons:
1. The type is unknown (could be TNotifyEvent, TMouseEvent etc.)
2. I cannot set 'SomeMethod' to nil (invalid cast)
In old RTTI style I would do something like:
var
NilMethod: TMethod;
begin
[...]
NilMethod.Data := nil;
NilMethod.Code := nil;
SetMethodProp (AComponent,PropertyName,NilMethod);
The following code ought to work:
procedure NilAllEventHandlers(myObject: TObject);
var
context: TRttiContext;
rType: TRttiType;
field: TRttiField;
value: TValue;
nilMethod: TMethod;
begin
nilMethod.Code := nil;
nilMethod.Data := nil;
context := TRttiContext.Create;
rType := context.GetType(TButton);
for field in rType.GetFields do
begin
if field.FieldType.TypeKind = tkMethod then
begin
TValue.Make(#nilMethod, field.FieldType.Handle, value);
field.SetValue(myObject, value);
end;
end;
end;
But it doesn't because there's a bug in TValue.TryCast when working with a TMethod value whose .Code parameter is nil. I'll report it to QC. Hopefully it'll get fixed in D2011 or an update. Until then, try the old style.
EDIT: Reported as QC# 81416. Vote it up if you want to see it fixed.