Mike Torrettinni 198 Posted July 26, 2019 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
Dave Nottage 557 Posted July 26, 2019 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
dummzeuch 1505 Posted July 26, 2019 (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 July 26, 2019 by dummzeuch 1 Share this post Link to post
PeterBelow 238 Posted July 26, 2019 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? 1 Share this post Link to post
Mike Torrettinni 198 Posted July 26, 2019 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
David Schwartz 426 Posted July 28, 2019 I don't know if this will help or not, but you might find it interesting. It's a talk I did for CodeRage 9: http://w5a.com/u/coderage-9-talk 1 Share this post Link to post
Микола Петрівський 10 Posted July 28, 2019 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
Uwe Raabe 2057 Posted July 28, 2019 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. 4 Share this post Link to post
Mike Torrettinni 198 Posted July 28, 2019 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
Mike Torrettinni 198 Posted July 28, 2019 (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 July 28, 2019 by Mike Torrettinni Share this post Link to post
David Heffernan 2345 Posted July 28, 2019 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
Mike Torrettinni 198 Posted July 29, 2019 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
Cristian Peța 103 Posted July 29, 2019 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; 1 Share this post Link to post
Uwe Raabe 2057 Posted July 29, 2019 @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) 3 Share this post Link to post
Lars Fosdal 1792 Posted July 31, 2019 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. 2 Share this post Link to post
Mike Torrettinni 198 Posted July 31, 2019 (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 July 31, 2019 by Mike Torrettinni Share this post Link to post
Lars Fosdal 1792 Posted July 31, 2019 TForm.QualifiedClassName would be an option, @Mike Torrettinni Share this post Link to post
Mike Torrettinni 198 Posted August 1, 2019 4 hours ago, Lars Fosdal said: TForm.QualifiedClassName would be an option, @Mike Torrettinni Good option, 'Self.QualifiedClassName' shows 'Unit1.TForm1' - should be unique. Share this post Link to post
Fr0sT.Brutal 900 Posted August 8, 2019 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
Attila Kovacs 629 Posted August 8, 2019 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
Mike Torrettinni 198 Posted August 8, 2019 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
Fr0sT.Brutal 900 Posted August 8, 2019 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
Mike Torrettinni 198 Posted August 8, 2019 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