6

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?

NaN
  • 8,596
  • 20
  • 79
  • 153

7 Answers7

12

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;
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • 2
    Why was this downvoted? @EASI asked for a flexible alternative. Using RTTI is a lot more flexible than what the other answers provided. They all rely on knowing the specific class types at compile-time. RTTI does not. – Remy Lebeau Jul 05 '12 at 09:05
  • I don't know who downvoted this, because I voted it up. Congratulations, it's just perfect! I told yoouu that a good flexible solution existed. Delphi is really great! – NaN Jul 05 '12 at 14:27
  • 1
    I'm not so sure this is such a great idea. Why do you need to abandon the type system? – David Heffernan Jul 05 '12 at 17:16
  • I agree with David. Although this is "flexible" there are other ways to do the same, for example, TControl.SetTextBuf is public. Also, using SetTextBuf you avoid the problem with properties names Caption vs Text, because it works for both. – GDF Jul 05 '12 at 18:12
  • 1
    You guys keep focusing on just the Text/Caption aspect, but EASI stated that this is going to be used for other things as well. – Remy Lebeau Jul 05 '12 at 19:59
  • Hey @Remy, is there a `SetProcedureProp` or something like to call a method of the Object? – NaN Jul 07 '12 at 00:00
  • The old-style RTTI system (the `TypInfo` unit) only has access to published properties, and thus cannot be used to call methods at run-time. The new-style RTTI system (the `Rtti` unit), on the other hand, has access to just about everything and can call methods via the `TRttiMethod.Invoke()` method. – Remy Lebeau Jul 07 '12 at 08:20
  • @RemyLebeau, you really should take a look at http://stackoverflow.com/questions/11728068/how-to-set-a-value-to-a-sub-property-item-using-typinfo-rtti-methods – NaN Jul 30 '12 at 19:15
10

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:='';
RRUZ
  • 134,889
  • 20
  • 356
  • 483
  • Yes, it is very useful! But I still see that we repeat a lot of code. Let's work on something even more clean! – NaN Jul 04 '12 at 22:10
  • 2
    What are you looking for? A control can't be both a `TEdit` and a `TCombo`! You could always use `THackedControl(Sender as TControl).Text := ...` – David Heffernan Jul 04 '12 at 22:15
  • Except in cases where some controls use `Text` and some use `Caption` instead. – Remy Lebeau Jul 05 '12 at 04:18
  • @remy Well, since Text is declared in TControl, the fact the some descendents expose it under a different name is immaterial – David Heffernan Jul 05 '12 at 06:11
  • 1
    And in cases where controls do not use either? The example provided by EASI focuses on TEdit and TComboBox, but the underlying issue is not limited to just them. EASI said as much. The more complex the code gets, the more unreliable this approach becomes. That is why EASI is looking for something far more flexible and manageable. – Remy Lebeau Jul 05 '12 at 09:10
5

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.

Deltics
  • 22,162
  • 2
  • 42
  • 70
  • +1 because it is a possible solution, though personally I do not like the absolute keyword. It makes sense to use it in this case, but it is very easy to misuse that keyword, as you state in your blog entry. – Guillem Vicens Jul 05 '12 at 09:34
  • Well, this is a great approach too! Now I don't know which is the best to vote... But @Remy's answer is more clean because the property used is set only one time in `SetStrProp(Sender, PropInfo, '')` and in your answer there are as much sets as there are object types. – NaN Jul 05 '12 at 14:37
  • A fair point, as long as you don't need to set both Caption and Text or in some cases set Text if it exists but not Caption and in others set Caption but not text. Or if on some other class you need to set yet another property, neither Caption nor Text. Instead of type-specific checks that safely identify and then treat each class according to it's type, you end up looking for the presence of properties which are in fact only a side-effect of an object being of a certain class. It may be more compact but it is unnecessarily "clever" and more fragile as a result, imho. – Deltics Jul 06 '12 at 04:31
2

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;
GDF
  • 830
  • 5
  • 11
  • It's very good, but the example at the question covers just the text/caption problem, but the big picture is to use the Sender for more than just that. There are lots of methods and properties that might be shared by different types of objects that sender does not accept the easy way, and we sometimes need to use it. – NaN Jul 05 '12 at 18:55
  • Didn't read the question as having to do with similarly named properties of random class types. Obv in the case you would need to use RTTI, but you are abandoning type checking. By removing type checking you may possibly end up with something much more complex down the line. However, if you are dealing with TControl based properties, creating a "TControlHack" class to cast with may work better. AT this point you are at least guaranteed that you are working with the TControl property that is shared, and avoiding the possibility of a non-shared but similarly named property. my $0.02 :) – GDF Jul 05 '12 at 19:13
1

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:)

Despatcher
  • 1,745
  • 12
  • 18
0

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;
NaN
  • 8,596
  • 20
  • 79
  • 153
-1

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.

Pieter B
  • 1,874
  • 10
  • 22