MVC pattern in desktop GUI with python - python

My problem is the following. Coming from a web background, I did not problems to do this, but in a Python desktop application I can´t really see what is the best way to organize the code according to a MVC pattern.
I want to create a window that according to user input, when a button is pressed, it shows similar entries that are available in the database. The window is my view.
So basically these are the relations:
1) Communication controller --> view
The controller has an instance of the view, and can use its exposed methods, as for example view.show_data(). I think this is the way to go.
# Controller
my_view = View()
...
my_view.show_data(whatever_data)
2) Communication view --> controller
When the user inserts some text, a method in the controller has to be fired so that it can ask the model for the necessary data in the database. The problem is that I don't know what is the best way for the view to tell the controller that it has to fire that very method.
My first idea is to pass a reference of the controller to the view, and bind the events on the view, something like this:
# Controller
my_view = View(self)
my_model = Model()
...
def on_user_input(self):
# process the input
user_input = ...
self.my_model.method_to_get_info(user_input)
And the view:
# View
def __init__(self, controller):
self.controller_reference = controller
self.launch_gui()
self.config_binds()
def launch_gui(self):
# ... configure all the GUI itself
self.button = ...
def config_binds(self):
self.button.Bind(wx.EVT_BUTTON, self.controller_reference.on_user_input())
But I think this "closed circle" relation is not a very clean solution. The view is referenced in the controller, and the controller in the view. I think it creates a tight relationship between the view and the controller.
What´s the way to do this?

The view is supposed to fire events. Normally you are not sending events directly to the controller, but the the GUI's frameworks generic event handler. The events in question are typically such event like "a key has been pressed", and "somebody moved the mouse".
A more "higher-level" MVC as in your case, can very well let the parts know of each other. That you tell the view what object is it's controller is perfectly fine. The important part is that all parts are using a well-defined interface, so that the parts are exchangeable.
As you see, this is quite different from the common web concept of model-view-controller which really isn't model-view-controller at all, but a misnomer and should be called model-view-template.

Related

Is tkinter mainloop responsible? Separating UI from logic

I'm still trying to figure out how to separate my UI from my programme logic. I'm fairly new to Python and maybe you can help me out.
I was trying to separate my UI from my programme hoping that I might be able to switch from tkinter to some other GUI later on. So I was thinking to have my main programme doing stuff and calling a tk Frame for displaying my data.
So, this is my class for displaying and it works fine:
import tkinter as tk
class TestClass(tk.Frame):
def __init__(self, master=None):
super().__init__(master)
self.label1 = tk.Label(text="Label1")
self.label1.pack()
self.changeLabel1("change from within") # <-- this works
def changeLabel1(self, changeText):
self.label1.config(text=changeText)
self.update_idletasks()
def writeSomething(self, outputText):
print(outputText)
And I made myself some test programme to instantiate the class:
# starter for TestClass
import TestClass
x = TestClass.TestClass()
x.mainloop()
x.writeSomething("Test") <-- nothing happens
x.changeLabel1("Test") <-- nothing happens
I put those calls to the functions after the mainloop just to show that I'm not able to change something after the mainloop has been called.
When I'm trying to change the label1 with the changeLabel1 function it works from within the class but not from my starter class. I get some output from writeSomething(text) but only when I close the window. And there is an error message which reads:
_tkinter.TclError: invalid command name ".!label"
(It is actually longer, only the last line of the traceback.)
Searching brought me to the conclusion that it might have something to do with the mainloop() but I'm not sure how to handle this problem.
What is best practice? Do I call the mainloop in my tkinter class or in my test file which calls the tkinter class?
Is this a way to separate UI and logic or am I getting something wrong and should it be done the other way around (calling the programme logic from within the UI class)?
I hope I made myself clear enough...
In a typical GUI the main loop is responsible for handling user events (by dispatching them to your event handler callbacks). In your snippet, code after the call to x.mainloop() is only executed when the main window is closed and the loop exited.
So yes, the obvious answer is that it's the UI code (the event handlers callbacks) that is responsible for calling the "logic" code (which would be better named "domain model" code - there is some "logic" in the UI too) - which is mostly obvious when you think about it: your domain model code should know nothing about the UI so you can use it with different UIs (GUI, text based, web, command line script etc).
If you want your UI to update itself in reaction to things happening in your domain model layer (which is a common requirement for any non-trivial GUI app), your best bet is the good old Model-View-Controller pattern (the real thing, not the web derivative), where the "model" is your domain model (or some wrapper around it), the "controler" are your event handlers callbacks and the "view" is your UI components (or some wrapper around them). The key here is the use of the Observer pattern which let the UI subscribe to events sent by the model.

What would these GUI elements represent in MVC terms?

I have a Listbox in tkinter in this Listbox. We can choose an image, all images are in a array in the model, but I don't known if the Listbox corresponds to MVC "View" or a "Controller", and if it's a Controller, how to add an image that we have just imported to it in accordance with MVC.
The Listboxor any other widget in tkinter is both part of the view and part of the controller, in the sense that it is a view (obviously) but it is also acting as a controller when you register callbacks in your code, and the widget catches relevant user interactions (events) and directs them to the registered code (with the help of tkinter event mainloop).
What you can do if you like, is to put your business code in a separated module (your model), make your tkinter registered callbacks call the functions in the model, and if the model changes because of that, update the widgets (the view) by notification, using the observer pattern (see a Python example) here. Essentially you keep a list of observers in the model, and when the model changes you'll call an update() or notify() method of them.
That will require you more work, which is a drawback in many cases, but has the power of updating your UI automatically anytime a change in the model affects some widget.
Note that I am not recommending all that work for simple cases (like using a few controls), unless you have good reasons for it. Wanting to use MVC looks like a desire to really separate concerns, but evaluate the effort first...

tkinter Model-View-Controller: deeply-nested widgets calling the controller

I'm refactoring a tkinter GUI along a MVC design pattern. The Controller module imports a View class, and the View class makes requests to the Controller. I have a few ways of registering a change to an entry widget (StringVar trace, binding, etc.), but what if the widget is deeply nested and the callback is at a much higher level? What is the best way to have a request propagate upwards? For example, what if the View has a Frame that has a Frame that has a Widget that changes status?
The two solutions that occur to me are:
Russian Doll approach: Every inner layer is instantiated with either a reference to the View or the Controller, so the widget can directly make a request (e.g. to ViewReference.call_controller(input), or ControllerAlias.request(input). This leaves a trail of breadcrumbs from View to widget.
Family Tree approach: recursively call the parents and ask if they have an "ask controller to do thing" method. If not, ask their parents. This seems more fragile and awkward.

Qt - Temporarily disable all events or window functionality?

I have a Qt program with many buttons, user-interactable widgets, etc.
At one stage in the program, I would like all the widgets to temporarily 'stop working'; stop behaving to mouse clicks and instead pass the event on to one function.
(This is so the User can select a widget to perform meta operations. Part explanation here: Get variable name of Qt Widget (for use in Stylesheet)? )
The User would pick a widget (to do stuff with) by clicking it, and of course clicking a button must not cause the button's bound function to run.
What is the correct (most abstracted, sensible) method of doing this?
(which doesn't involve too much new code. ie; not subclassing every widget)
Is there anything in Qt designed for this?
So far, I am able to retrieve a list of all the widgets in the program (by calling
QObject.findChildren(QtGui.QWidget)
so the solution can incorporate this.
My current horrible ideas are;
Some how dealing with all the applications events all the time in one
function and not letting through the events when I need the
application to be dormant.
When I need dormancy, make a new transparent widget which recieves
mouse clicks and stretch it over the entire window. Take coordinates
of click and figure out the widget underneath.
Somehow create a new 'shell' instance of the window.
THANKS!
(Sorry for the terrible write-up; in a slight rush)
python 2.7.2
PyQt4
Windows 7
You can intercept events send to specific widgets with QObject::installEventFilter.
graphite answered this one first so give credit where credit is due.
For an actual example in PySide, here's an example you might draw some useful code from:
my_app.py
from KeyPressEater import KeyPressEater
if __name__ == "__main__":
app = QApplication(sys.argv)
eater = KeyPressEater()
app.installEventFilter(eater)
KeyPressEater.py
class KeyPressEater(QObject):
# subclassing for eventFilter
def eventFilter(self, obj, event):
if self.ignore_input:
# swallow events
pass
else:
# bubble events
return QObject.eventFilter(self,obj,event)

How to control a frame from another frame?

I'm writing a small app which has 2 separate frames.
The first frame is like a video player controller. It has Play/Stop/Pause buttons etc. It's named controller.py.
The second frame contains OpenGL rendering and many things inside it, but everything is wrapped inside a Frame() class as the above. It's named model.py.
I'm up to the final part where I have to "join" these two together.
Does anyone know how to control a frame (model.py) from another frame (controller.py)?
I would like to do something like a movie player when you clicks play it pops up a new window and play until the stop button is clicked.
If you know how, please let me know. (Just tell me in general no need to specific).
Theres not much too it, you create an instance of your model class in your controller and call its methods. So for example when you click the models stop button its handler calls the appropriate method of your model class to stop playback.
If you would like your frames to be decoupled somewhat, you could use pubsub, and simply setup some listeners in your model for messages from your controller.
Here's a tutorial I just found on communciating between two frames using pubsub, it's not exactly what you want to do, but it should be enough to get you started in the right direction if you decide to use pubsub.
I'd definitely use PubSub as it's probably the cleanest way I can think of to do it. You can also do it with wx.PostEvent or use a modal frame.

Categories

Resources