On my first days as a programmer, I needed to create a GUI application for an automation project I was involved in.
I searched the web for help and came up with easygui.
Easygui gave me what I needed, the ability to invoke GUI without knowing anything at all about GUI creation, frames or Tk.
With easygui you just call a simple synchronous function and wait for the answer.
Here is an example
import easygui choice = easygui.buttonbox("body","title",["Yes","No"]) if choice == "Yes": ...
This is it! and here is what you get:
easygui project homepage: open in a new tab
Now I would like to point out a very annoying behavior of easygui, it invokes a new window for every single user input.
If you ever needed to get sequential inputs from a user with it, you have probably noticed this problem by yourself.
I'll explain the problem with an example. Let's say we have an external device we want to control, a robot. We want to be able to move it in all directions and to switch it on and off.
You're probably thinking of something like this:
import easygui choices = ["on", "off", "forward", "backward", "right", "left"] input= '' while input != "None": #happens when the user presses ESC input = easygui.buttonbox("controller","robot", choices) if input == "forward": ... elif input == "backward": ... ... ... elif input == "off": break
- User can't move the GUI around the screen because after every input, the window closes and a new one appears instead but in the original place.
- Flickering happens after every choice, till the next window opens.
- Every different window gets a new task-number or pid. This makes it harder to follow the window instances
Creating a callback mechanism in which we provide an easygui-function with a callback-function and the callback-function will be called for every user input.
The window should stay the same, in the same position with no flickering and with the same pid/task-number.
An example of how the new robot controller should look:
import easygui_callback def controller(user_input): if user_input == "forward": ... elif user_input == "backward": ... ... ... elif user_input == "off": return "terminate" #this terminates the callback loop choices = ["on", "off", "forward", "backward", "right", "left"] easygui_callback.buttonbox("controller","robot", choices, callback=controller)
The goal was to introduce the new ability without breaking the old API so everybody can continue enjoying their old code in the same way they have used it before, without worrying at all about the callback option.
Only programmers who are interested in such capability, will use it.
I've modified the code in order to enable the solution above.
This works wonderfully and it's totally backward-compatible.
Now the GUI can live forever and the logic applied to the options is being taken care separately.
Again, you can use both ways. You can put your logic in a separate function like in the example above but you can also use easygui-function (buttonbox for instance) the same way you used to do before. It supports both ways.
All three problems mentioned above are now solved, you can move the window around the screen and it won't flicker between user-inputs. The pid/task-number stays the same.
You can download the modified version here (please drop a short nice comment if you do :)
The modified version contains some bug fixes and it is based on easygui version 0.96
All modifications have been sent to the original developer and are being reviewed by him now in order to merge them with the next easygui formal version.For some of them it makes no sense to support it
Note! Not all the functions support callback!
Note! Not all the functions support callback!
- Functions supporting callback:
- Not supporting callback:
textbox (i'm considering adding callback to this one)
I recommend on viewing both files together, the original easygui and the modified version easygui_callback, with a diff program like WinMerge/Araxis
I've created a new generic function called "_buttonbox", it is based on the old function buttonbox
many other functions, including buttonbox (the original one) call it
most of the logic lies there now (in _buttonbox)
I'm gonna treat the current published version of easygui as the "old easygui" and my proposal "new easygui"
Because ynbox is by definition a choice between "yes" and "no", it makes more sense to not let the user specify
his own choices. If he wants to do so, he better uses the choicebox/boolbox and specify his choices there.
Added feature: you can now ALT+F4 or ESC instead of choosing between "yes" and "no", in this case None will be returned
Typo fixed "The returned value is calculated this way::" -> "The returned value is calculated this way:"
Bug fixed: in current easygui there is no validation of "choices" input parameter.
This is a serious bug because
- you can send more than 2 options to boolbox -> choosing the third or higher choice will always return 0.
- you can send only one parameter or empty tuple -> you get an unclear inner python trace
Added feature: you can now ALT+F4 or click on X instead of choosing between "yes" and "no", in this case None will be returned
The logic has been moved inside _buttonbox and we signify it by adding the parameter "index_only"
All old logic and Tk work has been moved to the new function _buttonbox
now it simply calls _buttonbox like other simple functions
Bug fixed: in old easygui there is no way to kill the GUI otherwise than choosing one of the choices
in the new one, you can kill it with 'X' button (need also to bind it to ESC)
_buttonbox: (the new function's logic comparatively to the old buttonbox)
- Default value, the initialization, is now set to None instead of the first choice. In the old easygui, you blocked the option of "X" button (windows manager close) so there is no need for default value anyway because the only way to make the GUI disappear is by choosing one of the choices. In the new one, this is the only way to close the GUI if we use callback. Anyway, this makes more sense for my opinion, it lets the GUI-user to say "i don't understand"or "i don't want either of the choices". The programmer should think about this option as well and we can't treat the "X" button the same as it was the first choice. This comment also explains the removal of the line "boxRoot.protocol('WM_DELETE_WINDOW', denyWindowManagerClose )"
- We split all cases to three, indexbox, boolbox and all other cases. We treat them separately, in each case we check if callback is needed. When it is, we call a "while" loop which ends in only two conditions. One is when the user closes the window with Windows-Manager's X button or ALT+F4 or ESC. The second is when the callback function returns "terminate". Each case(indexbox,boolbox and other) modifies the reply as needed (index for indexbox, 1 or 0 for boolbox or just raw reply for other cases)
- We destroy "root" only if the user chose a choice and the callback returned "terminate". Otherwise means the user closed the window with Windows-Manager's X button or ALT+F4 or ESC and therefore "root" is already destroyed
lowerbound and upperbound logic has been moved inside __fillablebox
we signify it to enterbox with an input parameter "integer_only=True"
inner function "__multfillablebox":
The stripping part is being taken care inside "__fillablebox"
Input parameter callback has been added
Input parameter strip has been added for enterbox
Input parameter integer_only has been added for integerbox
Input parameters upperbound and lowerbound have been added for integerbox option
We treat 2 cases in __fillablebox:
We have 3 options now
1) User canceled the GUI with "X", ALT+F4 or ESC
- In this case, we just terminate the GUI and return None
2) User chose too high or too low integer value or non-integer value
- In this case, we modify the entry with the appropriate error and reinvoke the GUI
3) User chose an appropriate value, integer between the specified boundries
- In this case we can return this int-value and terminate the GUI or, if callback is needed, call callback
We accept everything and we strip it if specified
a new function, it gets a value, entryWidget, upper and lower bounds for the integer
It checks whether the value is an integer and between the boundries if given
If one of the 2 conditions aren't met (not an integer, out of boundries), it sets an error
message inside the entryWidget and returns None. otherwise it returns a string saying "ok"
Bug fixed: in old easygui we focus and select the first choice. I've disabled it in the new one.
This causes a problem when using multchoicebox. In multchoicebox we click on every choice we want to choose and when we click on the "ok" button, all choices are sent back together as a list.
Well, when we defaultly choose the first choice for the user, s/he might not notice it.
if we have 5 options, we invoke the GUI with 1st choice already marked, the user chooses the 4th and the 5th.
We send back [1,4,5]. To avoid they need to click on the first choice to unmark it or click on "clear all" button.
Same would happen when user chooses nothing.
So we make users click on something they for sure don't want just to avoid it.