PyQt: reduce boilerplate for computational module GUI - python

I have a module for a physical process simulation. Now I want to do a GUI for it. I've used PyQt to create application, which allows to control calculation, its parameters and graphical output. But there is a problem - if I want to add some new feature to simulation, then in addition to coding it in computational module, I need to create wrapper function for calling it in GUI module, GUI element for calling it, set up GUI representation and bind it to the function I need. That's annoying, because I need to write an amount of obvious code instead of working on logic. I have a few ideas on automatization of process, but first I want to ask: are there some recommended ways to reduce this boilerplate work?

Welcome to GUI programming! :)
In PyQt, you can slightly reduce the amount of boilerplate by autoconnecting slots by name.
You can also create any GUI element at runtime, without resorting to QtDesigner, so you can do things like generate a list of checkboxes in a for loop.

Related

Tkinter with or without OOP

Studying Tkinter and I've only found tutorials on Tkinter without OOP, but looking at the Python.org documentation it looks like it's all in OOP. What's the benefit of using classes? It seems like more work and the syntax looks night and day from what I've learned so far.
This is going to be a really generic answer and most of the answers to this will be opinionated anyways. Speaking of which,the answer will likely be downvoted and closed because of this.
Anyways... Let's say you have a big GUI with a bunch of complicated logic sure you could write one huge file with hundreds, if not thousands of lines, and proxy a bunch of stuff through different functions and make it work. But, the logic is messy.
What if you could compartmentalize different sections of the GUI and all the logic surrounding them. Then, takes those components and aggregate them into the sum which makes the GUI?
This is exactly what you can use classes for in Tkinter. More generally, this is essentially what you use classes for - abstracting things into (reusable - instances) objects which provide a useful utility.
Example:
An app I built ages ago with Tkinter when I first learned it was a file moving program. The file moving program let you select the source / destination directory, had logging capabilities, search functions, monitoring processes for when downloads complete, and regex renaming options, unzipping archives, etcetera. Basically, everything I could think of for moving files.
So, what I did was I split the app up like this (at a high level)
1) Have a main which is the aggregate of the components forming the main GUI
Aggregates were essentially a sidebar, buttons / labels for selection various options split into their own sections as needed, and a scrolled text area for operation logging + search.
So, the main components were split like this:
2) A sidebar which had the following components
Section which contained the options for monitoring processes
Section which contained options for custom regular expressions or premade ones for renaming files
Section for various flag such as unpacking
3) A logging / text area section with search functionality build in + the options to dump (save) log files or view them.
That's a high level description of the "big" components which were comprised from the smaller components which were their own classes. So, by using classes I was able to wrap the complicated logic up into small pieces that were self contained.
Granted, you can do the same thing with functions, but you have "pieces" of a GUI which you can consider objects (classes) which fit together. So, it just makes for cleaner code / logic.
Like what pythonista just said...
OOP makes your GUI code more organized and if you need to create new windows eg.toplevel() you will find it extremely useful because you won't need to write all that code again and again and again... Plus if you have to use variables that are inside another function you will not need to declare it as a global. OOP with Tkinter is the best approach

While using tkinter, why should I use "indirect" callbacks?

I am trying improve my Python and tkinter skills. At this moment I am reading a book on tkinter. I noticed that in an object oriented GUI application the author defines the button callbacks this way:
def on_fast_forward_button_clicked(self):
self.player.fast_forward()
def on_rewind_button_clicked(self):
self.player.rewind()
So, if I understand correctly, we are writing to methods for each button in the application class. One with the "indirect" callback, called but the event, and other with the intended action itself, called by the previous one. Why should we write code like this, instead of just using a single method?
There's no specific reason, and in the example you give I absolutely wouldn't do it that way.
It's really quite simple: in general terms, every button should be associated with a function. That's it. That's the rule. What that function does is totally up to you.
If all it ever does is a single thing, there's no reason it can't directly call that single thing. If it needs to do more than one thing, create a special purpose function that does everything the button should do, and have that button call that function.
That being said, there's a conceptual difference between "responding to a button" and "performing an action". For example, you might have two or three ways to do the fast-forward function: you might have a command on the menubar, a command on a right-click menu, a dedicated fast-forward button, and a keyboard accelerator.
Depending on how these are implemented, the functions they call may or may not require extra arguments (eg: a function bound to an event always gets an event object). In that case it is often easier to have a special purpose function that is designed to respond to the event, and that special purpose function call the more general-purpose fast_forward function.
At the end of the day, it's all about clarity. If you think it makes the program easier to understand when you have a single function perform two roles (fastforward, and respond to a button click), by all means do it that way.
Think about it like this: currently, all the button does is have the player fast forward. What if in the future however you also wanted the clock, or some other entity to fast forward with it? Would it make any sense to stick those fast forward calls inside player.fast_forward()? Should the player's methods really have access to information like that?
on_fast_forward_button_clicked is acting as a wrapper to encompass everything that may need to be fast forwarded. If you ever need to add any functionality, all you need to do is make a minor modification:
def on_fast_forward_button_clicked(self):
self.player.fast_forward()
self.clock.fast_forward()

Python TKinter for real time GUIs

I have written a monitoring program for the control system at our plant. It is basically a GUI which lets the operator see the current status of the lock of the closed loop system and aware the operator in case the lock/loop breaks.
Now, the operation is heavily dependent on the responses of the GUI. My seniors told me that they prefer just the console prints instead of using TKinter based GUI as TKinter has lags while working in real time.
Can anyone please comment on this aspect?
Can this lag be checked and corrected?
Thanks in advance.
I would say that if your program is simply accessing data and not interacting with the data, then a GUI seems to be a bit of overkill. GUI's are guided user interfaces, as you know, and are made for guiding a user through an interface. If the interface is just a status, as you indicated, then I see nothing wrong with a console program.
If, however, your program also interacts with data in a way that would be difficult without a GUI, then the GUI is likely the right choice.
Have you considered a GUI in another programming language? Python is known to be a bit slow, even in console. In my experience, C++ is faster in terms of viewing data. Best of luck!
Python / tkinter in general
In a tkinter program, your code falls in one of four categories;
Initialization code that runs before the mainloop is started.
Callbacks that are run from within the mainloop.
Code running in other threads.
Code running in different processes.
In the first case, the time the code takes only influences the startup time, which for a long-running program is probably not all that relevant.
Concerning the second case, well-written callbacks should not take that long to run. In the order of tens of milliseconds, maybe up to 100 ms. If they take longer, they will render the GUI unresponsive. So unless you notice a sluggish GUI (without threads; see below) this should not be a problem.
One pitfall here are after callbacks, that is functions that will be scheduled to run after a certain time. If you launch them too often, this will also starve the GUI of time.
Another possible problem might be the manipulation of a Canvas with lots and lots of items in it.
As of Python 3.x, tkinter is thread-safe to the best of my understanding. However, in the reference implementation of Python, only one thread at a time can be executing Python bytecode. So doing heavy calculations in a second thread would slow down the GUI.
If you GUI uses multiprocessing to run calculations in another process, that should not influence the speed of your GUI much, unless you do things wrong when communicating with that other process.
Your monitoring program
What is too slow depends on the situation. In general Python is not considered a language suitable for hard real-time programs. To do hard real-time one also needs a suitable operating system.
So the question then becomes what is the acceptable lag in your system specification? Without knowing that it is impossible to precisely answer your question.
It seems that your GUI is just displaying some system status. That should not cause too much of a load, provided that you don't read/check the data too often. As described in the callbacks paragraph above it is possible to starve your GUI of CPU cycles with callbacks that run too often. From what you've written, I gather that the GUI's task is just to inform the human operator.
That leads me to believe that the task is not hugely time critical; a system that requires millisecond intervention time should not rely on a human operator.
So based on your information I would say that a competently written GUI should probably not be too slow.

Optimizing your PyQt applications

For those of you who have written fairly complex PyQt applications, what tips and tricks would you offer for speeding up your applications? I have a few examples of where my program begins to slow down as it grows larger:
I have a 'dashboard' written that is destroyed and re-created when a user clicks on an item in a TreeWidget. What would be a better way to have a modular interface where clicking an item in the TreeWidget changes the dashboard, but doesn't require destroying a widget and recreating it.
Each dashboard also loads an image from a network location. This creates some slowdown as one navigates around the application, but after it's loaded into memory, 'going back to that same dash' is faster. Is there a good method or way to run a thread on program load that maybe pre-loads the images into memory? If so, how do you implement that?
When you have a large variety of dashboard items and data that gets loaded into them, do you guys normally thread the data load and load it back in which each thread completes? Is this viable when somebody is browsing around quickly? Would implementing a kill-switch for the threads such that when a user changes dashboards, the threads die work? Or would the constant creation and killing of threads cause some sort of, well, meltdown.
Sorry for the huge barrage of questions, but they seemed similar enough to warrant bundling them together.
I'm not sure if this is exactly the same thing you are doing, but it sounds similar to something I have in some apps, where there is some list of custom widget. And it does significantly slow down when you are creating and destroying tons of widgets.
If its an issue of lesser amounts of total widgets, but just being created and deleted a lot, you can just create the widgets once, and only change the data of those widgets as information needs to be updated... as opposed to creating new widgets each time the information changes. That way you can even change the data from threads without having to worry about creating widgets.
Another situation is where you are displaying a list with custom widgets and there are a TON of results. I notice it always slows down when you have 1000's of custom widgets in a list. A trick my co-worker came up with was to have a fake kind of list where it is using a static number of slots in the display. Say, it shows 10 slots in the view. The scrollbar doesn't really scroll down across MORE widget...what it does is scrolls the DATA through the 10 visible widgets. You can a crazy performance increase doing that. But only if it is an acceptable display style for your application.
Are you using QNetworkAccessManager to load you images? It has cache support. Also it loads everything in background with finishing callbacks.
I don't really understand what your dashboard is doing. Have you think about using QWebkit? Maybe your dashboard content is easy to be implemented in HTML?
PS. I don't like threads in Python and don't think they are good idea. Deferred jobs delegated to Qt core is better.

Implementing a macro recorder for a python gui?

I'm wondering how to go about implementing a macro recorder for a python gui (probably PyQt, but ideally agnostic). Something much like in Excel but instead of getting VB macros, it would create python code. Previously I made something for Tkinter where all callbacks pass through a single class that logged actions. Unfortunately my class doing the logging was a bit ugly and I'm looking for a nicer one. While this did make a nice separation of the gui from the rest of the code, it seems to be unusual in terms of the usual signals/slots wiring. Is there a better way?
The intention is that a user can work their way through a data analysis procedure in a graphical interface, seeing the effect of their decisions. Later the recorded procedure could be applied to other data with minor modification and without needing the start up the gui.
You could apply the command design pattern: when your user executes an action, generate a command that represents the changes required. You then implement some sort of command pipeline that executes the commands themselves, most likely just calling the methods you already have. Once the commands are executed, you can serialize them or take note of them the way you want and load the series of commands when you need to re-execute the procedure.
Thinking in high level, this is what I'd do:
Develop a decorator function, with which I'd decorate every event-handling functions.
This decorator functions would take note of thee function called, and its parameters (and possibly returning values) in a unified data-structure - taking care, on this data structure, to mark Widget and Control instances as a special type of object. That is because in other runs these widgets won't be the same instances - ah, you can't even serialize a toolkit widget instances, be it Qt or otherwise.
When the time comes to play a macro, you fill-in the gaps replacing the widget-representating object with the instances of the actually running objects, and simply call the original functions with the remaining parameters.
In toolkits that have an specialized "event" parameter that is passed down to event-handling functions, you will have to take care of serializing and de-serializing this event as well.
I hope this can help. I could come up with some proof of concept code for that (although I am in a mood to use tkinter today - would have to read a lot to come up with a Qt4 example).
An example of what you're looking for is in mayavi2. For your purposes, mayavi2's "script record" functionality will generate a Python script that can then be trivially modified for other cases. I hear that it works pretty well.

Categories

Resources