When to disable/enable menu items
-
wrote on 8 Jan 2020, 12:27 last edited by
I am asking for practical advice on when you experts disable/enable menu items in a decent sized program.
I have a main window
QMenuBar
with a number ofQMenus
on it, each with a number ofQActions
on them. Like other friendly applications, I think I should disable items which are not available in whatever the current "context" is (do you agree?).A particular example is I happen to be using is a
QGraphicsView
and allowZoom in
and "Zoom out" actions. I have a minimum/maximum zoom, so one of these should be disabled if the view is already at min/max zoom. But this is only one example, I'd like to apply a principle to all of them.As we all know, there are two possible approaches.
-
Code permanently disables/re-enables items at the instant something happens which should do so. This tends to be what all "small" examples show. It is efficient, in that you only do calculations when "appropriate". However, it means you have to dot your code all over with the necessary checks, and you have to allow for other routes which might affect enablement, such as any kind of "reset" etc. I'm not a great fan, as it's easy to overlook a code route, or get it stuck on disabled when it really should be enabled.
-
Code intercepts the menu show event/click, and dynamically calls functions for each item to decide whether it should be enabled/disabled just before showing. I tend to prefer this "functional" approach, rather than saving state, as you don't dot your code around and you don't get stuck in the wrong enablement if you don't notice some odd code route. However it is potentially "slow", as recalculation is done for every menu pull-down activity.
Now, I'm not completely stupid :) I realise that if recalculation for a particular action involves computation like "factoring large prime numbers :)" you don't want that dynamically each time, you'd be better with the state-based solution which only calculates once. However, let's assume my calculations are "modest", e.g. in the zoom case just retrieve the current zoom level and check that.
Note that if I also have right-click context menus I tend to have to go for solution #2, because I create the menus on the fly and don't keep them around so can't save state.
I don't know how much overhead you regard doing dynamic dis/enablement at menu-pull-down time imposes on the Qt infrastructure, whether you regard it as "costly".
Soooooo.... in practice, what/when do you guys tend to do for permanent pull-down menus, please?
-
-
wrote on 8 Jan 2020, 13:34 last edited by
Depends on the typical use of the program.
If the state changes frequently but the menu is used only once in a while then #2 is the way to go, if the program is set up to be controlled mostly by those menus then #1 is the best approach -
Depends on the typical use of the program.
If the state changes frequently but the menu is used only once in a while then #2 is the way to go, if the program is set up to be controlled mostly by those menus then #1 is the best approachwrote on 8 Jan 2020, 14:00 last edited by@VRonin
Lol, I appreciate what you're saying, but it's really "It depends, it's up to you". I wanted you guys to put me out of misery with a definitive answer :) Preferably "Yes #2 sounds much better from a design/coding POV, so use that" ;-)Let's put it this way: since you are prepared to accept the functional/dynamic approach #2, are you happy that doing my checks when the user starts to pull down the menu is an "OK" way to do it from Qt infrastructure?
-
I can confirm that the right answer is indeed "it depends" :)
If it helps I can offer a reason I found not to use #2. I often have a "collection" of actions that are reused in different places - menus, toolbars, context menus etc. They often have shortcuts assigned to them. If the action is enabled/disabled only on show/hide events of menus you can end up in a situation where a shortcut works/doesn't when it should/shouldn't, because no event happened that would update the state of the action.
As you said #1 can be costly. This is a real problem in large apps or apps that do heavy processing and update that state often as part of some workload.
Let me offer option #3 that worked a couple of times for me.
Each "actionable item" has a setter that marks in some way that a given item "changed" or otherwise requires UI update. This should be a lightweight operation e.g. adding a pointer to a set (the set is kept small). Then, at some point the set is checked and all UI elements corresponding to the changed items are updated (enabled, disabled, colored etc.). The question is what is the right time to do that. I'm working with game engines which are mostly frame-based so I just do that every frame, but I think a short interval timer would work ok too. -
I can confirm that the right answer is indeed "it depends" :)
If it helps I can offer a reason I found not to use #2. I often have a "collection" of actions that are reused in different places - menus, toolbars, context menus etc. They often have shortcuts assigned to them. If the action is enabled/disabled only on show/hide events of menus you can end up in a situation where a shortcut works/doesn't when it should/shouldn't, because no event happened that would update the state of the action.
As you said #1 can be costly. This is a real problem in large apps or apps that do heavy processing and update that state often as part of some workload.
Let me offer option #3 that worked a couple of times for me.
Each "actionable item" has a setter that marks in some way that a given item "changed" or otherwise requires UI update. This should be a lightweight operation e.g. adding a pointer to a set (the set is kept small). Then, at some point the set is checked and all UI elements corresponding to the changed items are updated (enabled, disabled, colored etc.). The question is what is the right time to do that. I'm working with game engines which are mostly frame-based so I just do that every frame, but I think a short interval timer would work ok too.wrote on 8 Jan 2020, 14:54 last edited by@Chris-Kawa
Oh blast! :)What I really like about #2 is that it's so easy to define one function which returns whether e.g. you can still zoom in, like
return !view.fullyZoomedIn()
, and then use it everywhere and relax, sure in the knowledge that function returns the right value. I write the function, and then never worry again about what the user is doing. This is a functional, non-state-based approach.What I really do not like about #1 is that I have to think of everywhere where the user/code might change the zoom. if for whatever reason our code does not use a single function to set zoom, or some programmer uses a Qt call directly, or resetting a Qt view (or blowing your nose) states somewhere in the docs that it resets the zoom, I have to know that and nail down all those routes to set my state flag, which is always the problem with state-based. Which is messy and I just know I'll miss something somewhere.
But I shall be gathering all the opinions here, so they are very welcome, thank you, keep them coming!
1/5