Jump to content
Mike Torrettinni

Refer to Form Control without using the Form unit?

Recommended Posts

I'm trying to resolve unit dependencies, cycling. I have a global unit where in some cases I need to reference Form Control - so I need the Form units used in uses:

 

TranslatorUnit - custom translation unit that translates all forms, controls, message...

 

I run TranslateProject method that has this:

FormsList[0] := MainForm; // <- REFERENCE TO FORM
FormsList[1] := CustomDialog; // <- REFERENCE TO FORM
...
for i:= 0 to High(FormsList) do
begin
  if FormsList[i] = MainForm then  // <- REFERENCE TO MAINFORM
  begin
    for j := 0 to FormsList[i].ComponentCount - 1 do
    if FormsList[i].Components[j] = CustomButton then  // <- REFERENCE TO CONTROL
     ...
  end
  else
  	// common translation of Form and Controls
end;

 

I know I can loop Forms in Application and refer them by 'MainForm' name and same for Controls, I can refer to them by name like FindComponent('CustomButton'), but I don't like it since it doesn't validate names, like compiler does when I refer to them as objects.

 

Is there any way I can refer to Forms and  Form Controls without using the units in uses, but will still give me some way to verify I don't misspell the names or when I change a name that translation will still work as needed?

 

Any suggestion how to solve this?

 

 

 

Share this post


Link to post
40 minutes ago, Mike Torrettinni said:

FindComponent('CustomButton'), but I don't like it since it doesn't validate names

 

40 minutes ago, Mike Torrettinni said:

will still give me some way to verify I don't misspell the names

 FindComponent returns nil if you misspell the name, so just check for nil?
 

Share this post


Link to post
Posted (edited)

So you want the compiler to check all the references for you without giving it the references to check? Not possible.

You could put all the form variables into a global unit, typed as TForm and only use that unit.

 

Unit AllMyForms;

 

Interface

 

Uses Forms;

 

Var

  TheMainForm: TForm;

  ASecondaryForm: TForm;

 

Implementation

End.

 

Unit TheMainFormUnit;

 

Interface

 

Type

   TTheMainForm = class (TForm)

  // stuff here

  end;

 

// remove the variable declaration here

// Var

//  TheMainForm : TTheMainForm;

 

I'm not sure this would work. It might wreak havoc with the IDE.

 

(Sorry for the source code mess. I'm on mobile and for whatever reason there is no insert source code button.)

Edited by dummzeuch
  • Thanks 1

Share this post


Link to post
7 hours ago, Mike Torrettinni said:

I'm trying to resolve unit dependencies, cycling. I have a global unit where in some cases I need to reference Form Control - so I need the Form units used in uses:

 

TranslatorUnit - custom translation unit that translates all forms, controls, message...

 

I run TranslateProject method that has this:


FormsList[0] := MainForm; // <- REFERENCE TO FORM
FormsList[1] := CustomDialog; // <- REFERENCE TO FORM
...
for i:= 0 to High(FormsList) do
begin
  if FormsList[i] = MainForm then  // <- REFERENCE TO MAINFORM
  begin
    for j := 0 to FormsList[i].ComponentCount - 1 do
    if FormsList[i].Components[j] = CustomButton then  // <- REFERENCE TO CONTROL
     ...
  end
  else
  	// common translation of Form and Controls
end;

 

I know I can loop Forms in Application and refer them by 'MainForm' name and same for Controls, I can refer to them by name like FindComponent('CustomButton'), but I don't like it since it doesn't validate names, like compiler does when I refer to them as objects.

 

Is there any way I can refer to Forms and  Form Controls without using the units in uses, but will still give me some way to verify I don't misspell the names or when I change a name that translation will still work as needed?

 

Any suggestion how to solve this?

 

 

 

You cannot do it this way without using the form units. It is not a good way to translate an application anyway.  Have you looked at the IDE Translation Manager?

  • Thanks 1

Share this post


Link to post

Thanks, I had a feeling this would not be possible. This unit cycling is so easy to occur, it's annoying 🙂

 

7 hours ago, Dave Nottage said:

 

 FindComponent returns nil if you misspell the name, so just check for nil?
 

The check is for compile time, not runtime.

 

4 hours ago, dummzeuch said:

...

I'm not sure this would work. It might wreak havoc with the IDE.

 

Thanks, but as you predicted possible issues, I don't want to go somewhere unknown 🙂

 

53 minutes ago, PeterBelow said:

You cannot do it this way without using the form units. It is not a good way to translate an application anyway.  Have you looked at the IDE Translation Manager?

Thanks, I will have to find another way.

 

Share this post


Link to post
On 7/26/2019 at 5:26 AM, Mike Torrettinni said:

I'm trying to resolve unit dependencies, cycling. I have a global unit where in some cases I need to reference Form Control - so I need the Form units used in uses:

 

TranslatorUnit - custom translation unit that translates all forms, controls, message...

 

...

 

I know I can loop Forms in Application and refer them by 'MainForm' name and same for Controls, I can refer to them by name like FindComponent('CustomButton'), but I don't like it since it doesn't validate names, like compiler does when I refer to them as objects.

 

Is there any way I can refer to Forms and  Form Controls without using the units in uses, but will still give me some way to verify I don't misspell the names or when I change a name that translation will still work as needed?

 

Any suggestion how to solve this?

You of course know, that you can break some cycles by moving parts of uses to implementation section, right?

Share this post


Link to post
4 minutes ago, Микола Петрівський said:

you can break some cycles by moving parts of uses to implementation section

That is not breaking cycles, but actually creating them. Cyclic dependencies in the interface simply don't even compile. Therefore cyclic dependencies are only possible in the implementation section.

  • Like 4

Share this post


Link to post
On 7/26/2019 at 2:15 AM, dummzeuch said:

So you want the compiler to check all the references for you without giving it the references to check? Not possible.

Yes, that was the idea. Unfortunately you are right, not possible, I guess.

Share this post


Link to post
Posted (edited)
4 hours ago, Микола Петрівський said:

You of course know, that you can break some cycles by moving parts of uses to implementation section, right?

As Uwe said, this it not correct.

 

If you have both units referenced in interface, it doesn't even compile:

 

unit Unit1;

interface
uses Unit2;

implementation

end.



unit Unit2;

interface
uses Unit1;

implementation

end.

 

You need to move one reference to implementation, to actually compile - whichever one you can/need:

 

unit Unit2;

interface

implementation
uses Unit1;

end.

 

But this is unit dependency cycle.

 

I have plenty of such cases in my code, and not all cases are solvable the same. But if you don't care about it, it works.

Edited by Mike Torrettinni

Share this post


Link to post

Cyclic dependencies are not necessarily bad. Sometimes they are essential. Try writing a linked list without a cyclic dependency. 

Share this post


Link to post
1 hour ago, David Heffernan said:

Cyclic dependencies are not necessarily bad. Sometimes they are essential. Try writing a linked list without a cyclic dependency. 

OK, good to know not to force it for every situation.

Share this post


Link to post

I think you need something like this procedure. And every unit with a form will use the unit where this procedure is located.

Every form will use this procedure directly or will register itself for later translation.

procedure TranslateForm(Form: TForm);
var
  i, j: Integer;
  Frame: TFrame;
begin
  for i := 0 to Form.ComponentCount - 1 do begin
    if (Form.Components[i] is TFrame) then begin
      Frame := TFrame(Form.Components[i]);
      for j := 0 to Frame.ComponentCount - 1 do begin
        if (Frame.Components[j] is TControl) and (TControl(Frame.Components[j]).Action = nil) then
          TranslateTControl(TControl(Frame.Components[j]))
        else if (Frame.Components[j] is TAction) then
          TranslateTAction(TAction(Frame.Components[j]));
      end;
    end
    else if (Form.Components[i] is TControl) and (TControl(Form.Components[i]).Action = nil) then
      TranslateTControl(TControl(Form.Components[i]))
    else if (Form.Components[i] is TMenuItem) and (TMenuItem(Form.Components[i]).Action = nil) then
      TranslateTMenuItem(TMenuItem(Form.Components[i]))
    else if (Form.Components[i] is TAction) then
      TranslateTAction(TAction(Form.Components[i]));
  end;
end;

 

  • Like 1

Share this post


Link to post

@Cristian Peța That looks like a perfect candidate for the Visitor explained in this article: The Visitor Pattern – Part 4 avoiding all the is type calls and type casting.

 

The above code would break down to implementing a TTranslator class like this:

type
  TTranslator = class(TVisitor)
  protected
    procedure IterateComponents(Instance: TComponent);
  public
    procedure VisitAction(Instance: TAction);
    procedure VisitComponent(Instance: TComponent);
    procedure VisitControl(Instance: TControl);
    procedure VisitCustomForm(Instance: TCustomForm);
    procedure VisitCustomFrame(Instance: TCustomFrame);
    procedure VisitMenuItem(Instance: TMenuItem);
  end;
  
  procedure TTranslator.IterateComponents(Instance: TComponent);
var
  I: Integer;
begin
  for I := 0 to Instance.ComponentCount - 1 do
    Visit(Instance.Components[I]);
end;

procedure TTranslator.VisitAction(Instance: TAction);
begin

end;

procedure TTranslator.VisitComponent(Instance: TComponent);
begin
  IterateComponents(Instance);
end;

procedure TTranslator.VisitControl(Instance: TControl);
begin
  if Instance.Action = nil then begin

  end;
end;

procedure TTranslator.VisitCustomForm(Instance: TCustomForm);
begin
  {
    translate form parts
    ...
  }

  { translate components }
  IterateComponents(Instance);
end;

procedure TTranslator.VisitCustomFrame(Instance: TCustomFrame);
begin
  {
    translate frame parts
    ...
  }

  { translate components }
  IterateComponents(Instance);
end;

procedure TTranslator.VisitMenuItem(Instance: TMenuItem);
begin
  if Instance.Action = nil then begin

  end;
end;

Instead of calling

TranslateForm(MyForm)

you would call

Translator.Visit(MyForm)

 

  • Like 3

Share this post


Link to post

I had a similar problem where a lot of code was referring to the class name of forms to do navigation, and hence including the various form classes all over the place to get the class name.

 

I ended up making a unit, something like this:

unit NavigationHelp;
interface
type
  TNavLink = record
     MainForm: string;
     ReportForm: string;
     QueryForm: string;
  end;

const
  NameNotSet = 'Name not set';
{$WRITEABLECONST ON}
const
  NavLink: TNavLink = (
    MainForm: NameNotSet;
    ReportForm: NameNotSet;
    QueryForm: NameNotSet
  );

implementation
end.

 

and in the init section of my various form units, I updated the constant to the actual class name

unit MainFormU;
interface
uses
  NavigatorHelp;
type
  // ...
implementation
  // ...
initialization
  NavLink.MainForm := TMainForm.ClassName;
end.

 

You could apply the same pattern to form and control handles and set the handles in the respective OnFormShow routines.

 

type
  THandleRef = record
    MainForm: THandle;
    CustomButton: THandle;
  end;

const
  HandleRef: THandleRef = (
    MainForm: 0;
    CustomButton: 0
  );

...

TMainForm.FormShow(Sender: TObject);
begin
  HandleRef.MainForm := Self.Handle;
  HandleRef.CustomButton := CustomButton.Handle;
end;

That is a fairly simple "brute force" method to eliminate circular refs.

  • Like 2

Share this post


Link to post
Posted (edited)

Thanks @Lars Fosdal, interesting solution. That works because you only deal with a few of different ClassNames, right? If you needed 'navigation' through multiple Forms, that have same ClassName, this would be more tricky... or not, you could then go by another Form identificator... OK.

Edited by Mike Torrettinni

Share this post


Link to post

Am I right that you in your TranslateProject  method you have mentions of all translatable components from all forms in a project?

Share this post


Link to post

I don't get it. Even if you have a translator (which I would make with rtti), how would you handle different instances of a specific form?

Share this post


Link to post
3 hours ago, Fr0sT.Brutal said:

Am I right that you in your TranslateProject  method you have mentions of all translatable components from all forms in a project?

No, just forms and only a few customized controls - where translation is customized.

 

3 hours ago, Attila Kovacs said:

I don't get it. Even if you have a translator (which I would make with rtti), how would you handle different instances of a specific form?

I don't have many situations like these. The ones where I do, I specifically control the creation of such forms and also translation at that point.

Share this post


Link to post

Well, I don't know your needs but I'd move special translation to the form itself so that translator wouldn't know anything specific about controls. Like
 

Form.OnShow

  TranslateCommon(Self);
  TranslateSpecial1([Spec1Button, Spec1Label, ...]);
  TranslateSpecial2([Spec2Button, Spec2Combo, ...]);

 

Share this post


Link to post
32 minutes ago, Fr0sT.Brutal said:

Well, I don't know your needs but I'd move special translation to the form itself so that translator wouldn't know anything specific about controls. Like
 


Form.OnShow

  TranslateCommon(Self);
  TranslateSpecial1([Spec1Button, Spec1Label, ...]);
  TranslateSpecial2([Spec2Button, Spec2Combo, ...]);

 

I know this is one option, but then it's not all in one Translator unit. But good suggestion, thanks!

Share this post


Link to post

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now

×