Jump to content
Incus J

How to separate UI and Code, while maintaining rapid development?

Recommended Posts

I’d like to assign event handlers to visual components on a form at design time as usual (for example by double-clicking a popup menu item’s OnClick event, or selecting an Action via the Object Inspector), but I don’t want the IDE to create the handler in the form unit itself.  I find the form unit quickly becomes cluttered and difficult to maintain.  Instead I think I’d like the IDE to create the handler in a separate controller class in another unit.

 

Basically I’m trying to separate UI from code, but still be able to assign the event handlers rapidly at design time.  Can't quite figure it out.  I’m happy to use Actions and ActionLists if that helps - in fact that might be preferable.

 

In the past I’ve ended up with reams of tiny procedures in the form unit that simply hand off to a controller to perform the actual task (and quite often the controller class itself then simply passes-the-buck on to the model) :

procedure TForm1.SomeAction(Sender: TObject);
begin
  TController.ActuallyPerformThisAction;
end;

…elsewhere…

class procedure TController.ActuallyPerformThisAction;
begin
  TModel.WellReallyThisIsYourJob;
  UpdateTheUIToReflectModel;
end;

In complex apps I can create multiple controller classes to handle different aspects of the app and keep things organised, which is good.  But writing multiple handlers that simply pass-the-buck seems inefficient - I feel like my time could be better spent somehow, and my code also becomes cumbersome to navigate through:  Getting from a UI control to some code that actually does something involves several ‘Find Declaration’ steps.

 

I’m not sure I’ve explained this well, but assuming it is possible to fathom what I’m driving at, what’s a better/good way to achieve UI/code separation while maintaining rapid development in the IDE?

Share this post


Link to post

I use actions a lot. In fact, sometimes I group related sets of actions into data modules, then use the data modules in the forms.

 

Instead of double-clicking event handlers and calling procedures to do the work (or Execute methods of actions), just hook the actions directly up to the controls. 

 

The caption of the TAction becomes the caption of the menu or button; the Execute event of the TAction becomes the OnClick event handler of the Menu or Button. You can also provide icons for the associated controls right within a TActionList as well.

 

Later, if you decide to change the user interface from using menus to using buttons (for example), you don't have to change the captions or event handlers but just assign the TAction!

 

There are many other ways and probably some more "pure MVC or MVVM" but this has worked really well for me.

  • Like 1
  • Thanks 1

Share this post


Link to post
1 hour ago, Incus J said:

I find the form unit quickly becomes cluttered and difficult to maintain.  Instead I think I’d like the IDE to create the handler in a separate controller class in another unit.

Now the "another unit" is cluttered with the form's stuff. Can't see any benefits.

 

Share this post


Link to post

Invert the scheme have the controllers on top and have them carry their own forms for additional display or tuning.  Just set the properties the controller "looks at," drop on a form and use. 

Share this post


Link to post

Thank you David - that approach sounds very promising.  I've never considered data modules before (I probably made an assumption it was something to do with databases) - so I'll go and have a play around with that idea.  I'm guessing from your description that the Object Inspector will become aware of, and let me select actions I've defined in a separate data module - providing I add the data module to the main form's 'uses' section?

 

Attila, yes you're right - I think it's that the form unit interface section is already populated with numerous entries for all the components I've added to the form.  If I start adding my code or classes in this unit, it feels cluttered - but that's maybe just a perception on my part.  (I know that when I came to convert an older VCL project to FMX, application code written directly in the VCL form handlers proved time consuming to extract.)  I was kind of hoping for a non-visual TController organisational component that I could then assign to controls to indicate to the IDE "create your event handlers in here instead".  David's suggestion above, using a separate data module and action list, sounds like it might give me something close to that.

 

Pat, do you mean create my controller classes on a secondary form (instead of in a plain unit)?   I'm not sure whether I've understood you fully - could you elaborate?

Edited by Incus J

Share this post


Link to post
57 minutes ago, Incus J said:

I'm guessing from your description that the Object Inspector will become aware of, and let me select actions I've defined in a separate data module - providing I add the data module to the main form's 'uses' section?

Yes, it does. In one project I did a while back, I separated the functionality into three different data modules, each with a TActionList and the main form used all three data modules. When selecting an action on a control, all actions from all data modules were available, easily identified by <Unit>.<Action>.

Share this post


Link to post

You can also add popup menu's to the data module and they will be visible in the form as well. I use David's suggestion with global data module and popup connected to action list for my data grids, then assign the grids popup property at runtime depending on the grid shown. If I want to change or add functionality it's done in a single place.

  • Like 1

Share this post


Link to post

These show my idea of controllers. Each controller is the dashboard of it's controlled domain.

 

Better example would be a spinbutton changing a calendar view to start each week on say Sunday verses Monday.  Another spinbutton changes the month the calendar view is looking at. The hours worked and leave taken each day is retrieved from DB.  

This Viewer was made to show a vendor that there was too much "separation of concerns" when they put vacation leave on one view and hours worked on another view. Viewer example loaded leave and hours worked on same view so adjusting for a consistent 40 hours each week when mixing benefits with hours worked was much easier.  In time the vendor merged the views.      

Rather than actions or using TApplicationEvents. I make the DM UI aware by adding Forms and Controls to its user cause.  When the application forms are created, editboxes, labels and panels are added to lists in the Event module. Note the mainform or other forms are not added to uses clause. The controls added in EM Eventmodule parent is set to panel or form passed in. but control onevent is direct consumed by the model.  That makes it easier to debug IMHO.

 

constructor TEventBoss.Create(AOwner: TComponent);
begin
 inherited;
      Headings := TStringList.Create;
        Panels := TList<Tpanel>.create;     //so controls can be added at run time
          Jobs := Tlist<TProc>.create;      //needs empty parens to work Job()
         Edits := TList<TtextPair>.create;  //TtextPair = Tpair<Tcomponent, Tcomponent>;

         Jobs.add(checkforWhiteSpace);      //in the Edits list only states white space does not "change"                                               //the value.   

  

 

 

Controllers.png

  • Thanks 1

Share this post


Link to post

Thank you David, Gary, Pat for the detailed replies - there are lots of ideas for me to try out there.  I do like that idea of moving other non-visual controls (e.g. popup menus) off the main form onto a data module too.

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

×