views:

1324

answers:

9

Say you have a menu item and a button that do the same task. Why is it bad practice to put the code for the task into one control's action event and then make a call to that event from the other control? Delphi allows this as does vb6 but realbasic doesn't and says you should put the code into a method that is then called by both the menu and the button

+10  A: 

Beacause you should separate internal logic to some other function and call this function

  1. from both even handlers
  2. separately from code if you need to.

This is more elegant solution and much easier to maintain.

smok1
IMO this isn't an answer to the question. I asked why you cant do A rather than B and this answer just says because B is better!
jjb
BTW I don't mean that in a rude sense it's just my observation, I think Gerald hit the nail on the head with his answer
jjb
The answer that B is more elegant solution and is easier to maintain, comes from my own personal experience. The own personal experience in fact is not a think you can prove using hard data, this is the difference between experiencing something and scientifically proving it. And when talking about elegance.. you can't define it, you can only feel it... Eventually refer to "Code Complete" by Steve McConnell, he is has quite good coverage of such issues.
smok1
Fair point but I'd say that using personal experience as argument requires examples if it is to carry weight.
jjb
Ok, I will search my code archives and put some code as an example.
smok1
@jjb - I have added a detailed example as another answer.
smok1
You should give examples why your method is better (i.e. easier to maintain) (Which Rob's answer did, so he wins)
Ian Boyd
+4  A: 

It is neater obviously. But ease of use and productivity is of course also always important.

In Delphi I generally refrain from it in serious apps, but I call eventhandlers in small stuff. If small stuff somehow morphes into something bigger, I clean it up, and usually at the same time increase logic-UI separation.

I do know though that it won't matter in Lazarus/Delphi. Other languages might have more special behaviour attached to eventhandlers.

Marco van de Voort
Sounds like a pragmatic policy
jjb
+7  A: 

Separation of concerns. A private event for a class should be encapsulated within that class and not called from external classes. This makes your project easier to change down the road if you have strong interfaces between objects and minimize the occurences of multiple entry points.

Chris Ballance
I agree with encapsulation and separation, but click/dbclick events on vb6 controls are never private. And if they were not made private, it's because someone considered that the harm would be minimal.
jpinto3912
Neither in Delphi/Lazarus, they are published (RTTI'd)
Marco van de Voort
@jpinto3912 - in fact VB6 event handlers are private by default.
MarkJ
This isn't an event, it's an event sink. And not even really the sink itself but the logic invoked by the compiler generated sink.By most of the logic seen in this thread a VB6 event handler would never have any code in it at all besides a call to yet another (redeundant) procedure!Frankly I don't buy it, and occurrences should be rare enough anyway. If one is paranoid, the handler implementing the logic could be grouped with those calling it and elaborate comments emplaced to guide future maintainers.
Bob
@jpinto3912: Events are public, but handlers are private. Events are actually methods on a (hidden, but public) event sink interface. The (private) event handler methods are implementations of methods on the (public) event sink interface. Similar to how implementing an interface with the `Implements` keyword creates `Private` methods for the implementation by default, except that events and event handlers are treated specially (i.e. you don't have to implement handlers for all the events exposed by a class, the compiler inserts empty event handlers at compile-time).
Mike Spross
Of course, you can choose to mark your event handlers as `Public`, but that would go against best practice. The IDE marks the methods as `Private` to maintain consistency with the COM programming model: i.e. since they are implementing an interface, they should be private within the class itself, since they are not part of the class's own interface, but rather implementations of an interface that is implemented by the class.
Mike Spross
+6  A: 

Suppose at some point you decide that the menu item no longer makes sense, and you want to get rid of the menu item. If you just have one other control pointing to the menu item's event handler, that might not be a big problem, you can just copy the code into the button's event handler. But if you have several different ways the code can be invoked, you'll have to do a lot of changing.

Personally I like the way Qt handles this. There is a QAction class with it's own event handler that can be hooked, and then the QAction is associated with any UI elements that need to perform that task.

Gerald
OK this is logical to me, when you delete the button you have nothing to tell you that other controls are referring to it.Are there other reasons?
jjb
Delphi can do the same. Assign an action to the menuitem and the button - I do this all the time for toolbar buttons that mirror menu functionality.
TheArtTrooper
Another reason is that maybe you want to do some kind of user interface update when a menu item is chosen that doesn't apply when the button is chosen. There's nothing intrinsically bad about doing what you say in most cases, but it's just a questionable design decision that limits flexibility.
Gerald
+2  A: 

Why is it bad practice? Because it is much easier to reuse code when it is not embedded into UI controls.

Why can't you do it in REALbasic? I doubt there's any technical reason; it's likely just a design decision they made. It certainly does enforce better coding practices.

Paul Lefebvre
Is that an argument for not allowing anything except calls in events. It would always take an extra bit of looking to find code if you first have to look in the event to find the name of the method where the code is. Also it gets very tedious having to think up meaningful names for endless numbers of methods.
jjb
No, it is an argument for not attempting to reuse code that is in events. If the code is only applicable for the event, then I would put it in the event. But if I need to call it from anywhere else, I refactor it into its own method.
Paul Lefebvre
Yes that approach seems to make a lot of sense. Thanks
jjb
A: 

Suppose at some time you decided that the menu should do something slightly differently. Perhaps this new change only happens under some specific circumstances. You forget about the button, but now you have changed its behaviour as well.

On the other hand if you call a function, you are less likely to change what it does, since you (or the next guy) knows that this will have bad consequences.

tomjen
I disagree with your logic. If you have a menu item and a button to do the same thing, they should *do the same thing*, not function differently. IOW, if you have a menu item that allows you to edit the current row in a database, and a button that allows you to edit the current row in a database, both should do the same thing; if not, they shouldn't both be called "Edit".
Ken White
@Ken There might be good reasons for the menu and button to do different things. For instance in VB6 when the user clicks a menu item, it doesn't fire a lost focus event on the control with the focus. When the user clicks a button, it does fire lost focus events. If you are relying on lost focus events (for instance to do validation) you might need special code in the menu click event to trigger a lost focus and abort if validation errors are found. You wouldn't need this special code from a button click.
MarkJ
+38  A: 

It's a question of how your program is organized. In the scenario you've described, the menu item's behavior will be defined in terms of the button's:

procedure TJbForm.MenuItem1Click(Sender: TObject);
begin
  // Three different ways to write this, with subtly different
  // ways to interpret it:

  Button1Click(Sender);
  // 1. "Call some other function. The name suggests it's the
  //    function that also handles button clicks."

  Button1.OnClick(Sender);
  // 2. "Call whatever method we call when the button gets clicked."
  //    (And hope the property isn't nil!)

  Button1.Click;
  // 3. "Pretend the button was clicked."
end;

Any of those three implementations will work, but why should the menu item be so dependent on the button? What's so special about the button that it should define the menu item? If a new UI design did away with buttons, what would happen to the menu? A better way is to factor out the event handler's actions so it's independent of the controls it's attached to. There are a few ways to do that:

  1. One is to get rid of the MenuItem1Click method altogether and assign the Button1Click method to the MenuItem1.OnClick event property. It's confusing to have methods named for buttons assigned to menu items' events, so you'll want to rename the event handler, but that's OK, because unlike VB, Delphi's method names do not define what events they handle. You can assign any method to any event handler as long as the signatures match. Both components' OnClick events are of type TNotifyEvent, so they can share a single implementation. Name methods for what they do, not what they belong to.

  2. Another way is to move the button's event-handler code into a separate method, and then call that method from both components' event handlers:

    procedure HandleClick;
    begin
      // Do something.
    end;
    
    
    procedure TJbForm.Button1Click(Sender: TObject);
    begin
      HandleClick;
    end;
    
    
    procedure TJbForm.MenuItem1Click(Sender: TObject);
    begin
      HandleClick;
    end;
    

    This way, the code that really does stuff isn't tied directly to either component, and that gives you the freedom to change those controls more easily, such as by renaming them, or replacing them with different controls. Separating the code from the component leads us to the third way:

  3. The TAction component, introduced in Delphi 4, is designed especially for the situation you've described, where there are multiple UI paths to the same command. (Other languages and development environments provide similar concepts; it's not unique to Delphi.) Put your event-handling code in the TAction's OnExecute event handler, and then assign that action to the Action property of both the button and the menu item.

    procedure TJbForm.Action1Click(Sender: TObject);
    begin
      // Do something
      // (Depending on how closely this event's behavior is tied to
      // manipulating the rest of the UI controls, it might make
      // sense to keep the HandleClick function I mentioned above.)
    end;
    

    Want to add another UI element that acts like the button? No problem. Add it, set its Action property, and you're finished. No need to write more code to make the new control look and act like the old one. You've already written that code once.

    TAction goes beyond just event handlers. It lets you ensure that your UI controls have uniform property settings, including captions, hints, visibility, enabledness, and icons. When a command isn't valid at the time, set the action's Enabled property accordingly, and any linked controls will automatically get disabled. No need to worry about a command being disabled through the tool bar, but still enabled through the menu, for example. You can even use the action's OnUpdate event so that the action can update itself based on current conditions, instead of you needing to know whenever something happens that might require you to set the Enabled property right away.

Rob Kennedy
Great answer, Thanks. I am especially impressed by the TAction approach which I hadn't been aware of before but which sounds like the best way of approaching this. Actually Delphi seems to have this area well covered, allowing all approaches.BTW You mention that TAction allows the automatic disabling of associated controls. One change in attitude to style lately that I do like is the trend not to disable controls when an action is unavailable but instead to allow the user to click on the control and then give them a message that explains why the action is not happening.
jjb
Some of the TAction approach's advantage over the other ways is rendered irrelevant if this style is used I think.
jjb
@jjb: Not disabling controls even though their actions are not available ATM makes for a very confusing user interface IMHO. But since disabled controls do indeed make the UI less discoverable there should be some indication of the cause, like tool tips or status bar help messages when the mouse hovers over a disabled control. I prefer that approach much over a UI that gives no indication of the state it's in.
mghie
@mghie yes that sounds like the best of both worlds.
jjb
<sigh>. What you do with the TAction isn't the point. The point is that it lets you ensure that everything works the same way.
Rob Kennedy
+7  A: 

This is an extension answer, as promised. In 2000 we have started to write an application using Delphi. This was one EXE and few DLL’s containing logic. This was movie industry, so there was customers DLL, booking DLL, box office DLL and billing DLL. When user wanted to do billing, he opened appropriate form, selected customer from a list, then OnSelectItem logic loaded customers theaters to next combo box, then after selecting theater next OnSelectItem event filled third combo box with information about the movies, that has not been billed yet. Last part of the process was pushing the button “Do Invoice”. Everything was done as an event procedures.

Then someone decided we should have extensive keyboard support. We have added calling event handlers from another even handlers.. The workflow of event handlers begun to complicate.

After two years someone decided to implement another feature – so that user working with customer data in another module (customers module) should be presented with a button titled “Invoice this customer”. This button should fire the invoice form and present it in such a state, like it was user who have been manually selecting all the data (the user was to be able to look at, make some adjustments, and press magic “Do Invoice” button). Since customer data was one DLL and billing was another, it was EXE that was passing messages. So the obvious idea was that customer data developer will have single routine with single ID as a parameter, and that all this logic will be inside billing module.
Imagine what happened. Since ALL logic was inside event handlers, we spent huge amount of time, trying actually not implement logic, but trying to mimic user activity – like choosing items, suspending Application.MessageBox inside event handlers using GLOBAL variables, and so one. Imagine – if we had even simple logic procedures called inside event handlers, we would have been able to introduce DoShowMessageBoxInsideProc Boolean variable to the procedure signature. Such a procedure could have been called with true parameter if called from event handler, and with FALSE parameters when called from external place.

So this is what have taught me not to put logic directly inside GUI event handlers, with a possible exception of small projects.

smok1
Thanks for putting this up. I think it clearly illustrates the point you were making. I like the idea of the boolean parameter to allow for different behaviour when the event actually happened as opposed to being done via code.
jjb
Different behaviour you can have if you pass nil as sender ;)
inzKulozik
@jjb: I think this is even wider subject of having similar logic in two different procedures. When you have such situation, it is always better to provide third procedure with actual logic and turn those two similar procedures into wrappers for new logic containing proc. The differences in behaviour can be done by control params. Many components that have two or more overload methods like Open. Those open methods are usually wrappers for some kind of private InternalOpen procedure with boolean parameters for some small adjustments.
smok1
@inzKulozik: yeah, steering logic using UI logic and in fact using niled Sender as a boolean control variable... I think it is even better than declaring var a,b,c,d,e,f,g : integer just in case ;)
smok1
+3  A: 

Another big reason is for testability. When event handling code is buried in the UI, the only way to test this is via either manual testing or automated testing that is heavily tied to the UI. (e.g. Open menu A, Click button B). Any change in the UI naturally can then break dozens of tests.

If the code is refactored into a module that deals exclusively with the job it needs to perform, then testing become a whole lot easier.

gommo
I think this is a very good point
jjb