I have a working program in 1 class. I wish to convert it to MVC pattern.
I've implemented observer design as shown here and i opened 3 .py files for Model, View and Controller.
I'm having trouble understanding what goes where.
For example:
this code in the original class
helpMenu = wx.Menu()
helpMenu.Append(ID_ABOUT, "&About", "Display info")
self.Bind(event=wx.EVT_MENU, handler=self.OnHelpAbout, id=ID_ABOUT)
...
def OnHelpAbout(self,e):
title = self.GetTitle()
d = wx.MessageDialog(self, "About " + title, title, wx.ICON_INFORMATION | wx.OK)
d.ShowModal()
d.Destroy()
This menu code lines should be in the View, and the function OnHelpAbout should be implemented in the Controler. But how do I call it? should I have an instance of the Controller in the View? Where do I sign for the observer?
Also, if this function set a dialog = making changes in the view How do I do that? If I understood correctly I need to call another function in the View and put the code there?
I guess what i'm missing is a basic template to work with. I haven't found any source that explains how to do it with GUI & GUI Events.
The MVC entry in the wxPython wiki gives a good insight how the spearation of concerns could look like. The first (newer) example leverages pypubsub, the older example at the bottom deals with wx.Events alone.
While it may or may not conform to the strict MVC paradigma, it shows beautifully how the model is separated form view and controller.
In your example the creation of the menu (the first two lines) would go into the view. The controller would hold the event method (OnHelpAbout) and do the binding on the menu item. For the model your example would have no code. The frame title could be kept in the controller and put into the frame on object creation/after creation.
UPDATE/EDIT1
This is no exact science and can get messy as already pointed out. How about the following:
view.py
class myframe(wx.Frame):
def __init__(…
# code for menu creation goes here
self.helpmenu = …
controller.py
from view import myframe
class mycontroller(object):
def __init__(self, app):
self.title = 'mytitle'
self.frame = myframe(…
self.frame.SetTitle(self.title)
self.frame.helpmenu.Bind(…, handler=self.OnHelpAbout, …)
self.frame.Show()
def OnHelpAbout(self, evt):
title = self.title
# show message dialog
…
if __name__ == '__main__':
app = wx.App(…
ctrler = mycontroller(app)
app.MainLoop()
Wake me if you manage to find a "pure" solution …
Related
for a python program I created a gui with QtDesigner. Inside my program the gui is initiated and calls the .ui-file to implement content.
The gui class object is than given to a Controller, and here it gets tricky:
Instead of one Controller, there are a main controller and some sub-controller for different parts of the gui. The main Controller does one or two general things and than hands over different parts of the gui to different sub controller.
See the following example for better understanding:
import Controller # import the folder with the controller
def __init__(self, window):
self.window = window # self.window is the handed over gui
self.sub_controls()
def sub_controls(self):
Controller.Sub_Controller_1(window = self.window.part_1)
Controller.Sub_Controller_2(window = self.window.part_2)
Controller.Sub_Controller_3(window = self.window.part_3)
...
The sub-Controller is set up like this:
def __init__(self, window):
self.window = window
... # do stuff:
-----------------------------------------
by trying to call a widget in this Sub-Controller (lets say its a label called timmy), i get an error:
self.window.timmy.setVisible(False)
AttributeError: 'QWidget' object has no attribute 'timmy'
but by using the children()-Method, which returns a list of all children in the gui, I may access this label:
self.window.children()[1].setVisible(False)
This works well and hides timmy.
By trying to do this in the main Controller, it works fine as usual:
self.window.timmy.setVisible(False) # works fine here
I also tried to save the sub-controller object like this:
def sub_controls(self):
self.save_part_1 = Controller.Sub_Controller_1(window = self.window.part_1)
but this doesn't work.
Does any one have a suggestion, how I could solve this Problem?
Sure, I couldt access just all widgets with the children()-method-list, but this is laborious because of the great amount of widgets. Same thing applies to reassigning every child.
PS:
Unfortunately I cannot show you the original Code due to company guidelines.
Ok so I figured out, that the tree Structure of in each other nested widgets has nothing to do with the naming of the widgets. For example:
MainWindow
->centralWidget
->otherWidget
->Button
In this you can address "Button" with self.Button, because (how I believe) the name is saved in MainWindow-Level. If you cut out "otherWidget", the QPushButton in it still exists, but cannot addressed by name.
So it's not possible to just hand over a part of your Gui to a Handler, at least if it's build with QtDesigner.
My Solution for me to the original Problem is to hand over the complete Gui to every sub handler:
def __init__(self, window):
self.window = window # self.window is the handed over gui
self.sub_controls()
def sub_controls(self):
Controller.Sub_Controller_1(window = self.window)
Controller.Sub_Controller_2(window = self.window)
# Controller.Sub_Controller_3(window = self.window.part_3) # before
I know this is a super vague question, but I'm just getting into GUI development using wxPython and could use some guidance. I have a program that:
opens a modal dialog box where the user is to select a .csv file containing data to be analyzed
stores the data as a pandas DataFrame object
does some formatting, cleaning up, and calculation on the data
generates a new dataframe with the results of the calculations
plots the results (linear regressions) and displays the results tables, as well as saving both the plots and new tables to .png and .csv files, respectively.
I want a GUI such that, when launched, a simple window appears with some text and a single button in the middle "import csv to begin" or something (I was able to create this first window by subclassing wx.Frame, but the button currently doesn't do anything). On clicking the button, the modal dialog will open so the user can select the .csv data file. On clicking OK/Open/whatever the button is (long day, memory no work), the window/frame will change to a different layout (again, was able to piece together a class for this frame). My question is mainly how I should go about getting the data between frames WHILE ALSO changing the frame.
The method for switching between frames I found was to include, in the class definition, the method
def _NextFrame(self, event):
self.frame.Show()
self.Hide()
and then in the body of main() call it as
app = wx.App(redirect=True)
f1 = Frame("Frame1")
f2 = Frame("Frame2")
f1.frame = f2
f2.frame = f1
f1.Show()
app.MainLoop()
But this was for just switching between two instances of the same frame, not two different frames with different functions. Additionally, I think that this way will instantiate the frames all before running the app, so if I have the self.getcsv() function called in the __init__() of my second frame, the user will be prompted to open a file before they even click the button on the first frame (even though the second frame is as-yet invisible).
Can I use the code for the CLI version, build in the classes for the GUI, and handle all the calculations etc. outside of wxPython, using wx only to display what I want to display? I'm just pretty lost in general. Again, sorry for the vague question, but I didn't know where else to turn.
Finished the app. For other green GUI programmers, the way I handled this was to instantiate the next frame in an event handler bound to a logical button/control (such as a "Start" button, "Analyze" button, etc.). For example, after creating all the classes for the different frames, data handlers, and so on, I start the app with
def main():
app = wx.App()
frm = StartFrame(None)
frm.Show()
app.MainLoop()
if __name__ == "__main__":
main()
Within the StartFrame instance, there's a "Start" button bound to the handler:
def _OnStart(self,event):
frm2 = ParaFrame(None)
frm2.Show()
self.Destroy()
The ParaFrame frame has an "analyze" button which is a little more complex: it instantiates a (non-wx, custom) class DataHandler, sets various attributes according to user input in the ParaFrame instance, calls a DataHandler method which analyzes the data, then instantiates the result frame (which takes some of the results from DataHandler's analysis as __init__() parameters), shows it, deletes the DataHandler, and destroys itself:
def _analyze(self, event):
dhandler = DataHandler(self)
dhandler.path = self.path
dhandler.logconv = self.logbtn.GetValue()
dhandler.numTar = int(self.inputNum.GetValue())
dhandler.conc = self.inputcb.GetValue()
for idx, tar in enumerate(self.tarcbs):
dhandler.targets.append(self.tarcbs[idx].GetValue())
dhandler._analyzeData()
resfrm = ResultFrame(None, dhandler.targets, dhandler.dftables)
resfrm.Show()
del dhandler
self.Destroy()
From the ResultFrame instance, aside from just displaying the results, there are controls to either exit the app (bound to _OnExit, below) or restart the app from the beginning to run a new analysis (bound to _OnRestart):
def _OnExit(self, event):
"""Close frame & terminate app"""
self.Close(True)
def _OnRestart(self, event):
frm = StartFrame(None)
frm.Show()
self.Destroy()
This method also helped get around the problem with the example of switching frames I found; that example was suited to switching back and forth between two persistent frames, whereas I wanted a linear A --> B --> C approach, where once a frame was displayed, the previous frame should be destroyed.
Hopefully this will help someone in the future :)
I am creating a tkinter application using Python 3.4 that collects posts from an API, filter them and allow the user to review them and make a decision for each one (ignore, delete, share, etc.)
The user is expected to pick a date and some pages and then click on the 'Collect' button. The program then fetch the posts from the pages and stock them in 'wholeList'.
When the user clicks on the second button 'Review', the posts must be filtered and passed to the Reviewer.
My problem is that the Reviewer receives no posts at all, and neither does the Filterer. I have added some debugging print() statements at some places, notably to handlerCollect(), and the result baffled me, hence this post.
Instead of finishing the handlerCollect() callback method when I click on 'Collect', the program puts it on hold somewhere between "DEBUG->1" and "DEBUG->2". The main window does not freezes or anything, for I can click on 'Review' and have it print "DEBUG->4" and open up the Reviewer. When I close the main window, "DEBUG->0" "DEBUG->2" and "DEBUG->3" finaly print, along with the rest of the handlerCollect() method executing.
The same behavior happens with handlerChoosePage(), with "DEBUG->0" being delayed until the tkinter root (TK()) is destroyed. My knowledge of structural programming tells me it should be the very first one printed. Instead, it is the very last. My best conclusion is that I must not be ending my Toplevel mainloop()s correctly. I have to admit I have never encountered something like this before. I thought the proper way of ending mainloop()s on Toplevels was with destroy() and I am very confused as to why methods calling mainloop()s get put on hold until the Tk root is destroyed; not really practical.
from GUICollector import GUICollector as Collector
class Launcher(tk.Frame):
def __init__(self, *args, **kwargs):
...
self.allPagesCB = Checkbutton(self.dateFrame, text="Collect for all pages",
variable = self.allPagesVar, command=self.handlerChoosePage)
self.collectBtn = Button(self, text="Collect", command=self.handlerCollect)
self.reviewBtn = Button(self, text="Review", command=self.handlerReview)
def handlerChoosePage(self):
if self.allPagesVar.get() == 0:
child = tk.Toplevel(self)
selector = PageSelector(self.toCollect, child)
selector.pack(side="top", fill="both", expand=True)
selector.mainloop()
print("DEBUG->0")
def handlerCollect(self):
print("DEBUG->1")
self.collect()
print("DEBUG->4")
for post in self.collector.getPosts():
if post not in self.wholeList:
print("...")
self.wholeList.append(post.copy())
self.collector = None
print(len(self.wholeList), "posts in wholeList")
def collect(self):
window = tk.Toplevel(self)
self.collector = Collector(self.toCollect, self.sinceEpoch, window)
self.collector.grid(row=0,column=0)
self.collector.after(500, self.collector.start)
print("DEBUG->2")
self.collector.mainloop() # This is what seems to hang indefinetly
print("DEBUG->3")
def handlerReview(self):
print("DEBUG->5")
print(len(self.wholeList), "posts in wholeList")
filterer = Filterer(self.wholeList)
self.wholeList = filterer.done[:]
window = tk.Toplevel()
reviewer = Reviewer(self.wholeList[:], window)
reviewer.grid(row=0,column=0)
reviewer.mainloop()
The GUICollector module requires no interaction from the user at all.
This module seems to work perfectly: doing its job, displaying it is done and then closing after the specified delay.
Since the GuiCollector mainloop() seems to be the culprit of the hanging, here is how I end it:
class GUICollector(tk.Frame):
def __init__(self, pagesList, since, *args, **kwargs):
tk.Frame.__init__(self, *args, **kwargs)
def start(self, event=None):
if some_logic:
self.after(250,self.start)
else:
self.done() # Does get called.
def done(self):
# Some StringVar update to display we are done on screen
self.after(1250, self.validate)
def validate(self):
self.master.destroy()
The PageSelector module is destroyed with the same call on the press of a button: self.master.destroy()
Here is the revelant output of the program:
DEBUG->1
DEBUG->2
=> collected data of page [PageName]
=> Found 3 posts in page
DEBUG->5
0 posts in wholeList
[The main window (Launcher) is manually closed at this point]
DEBUG->3
DEBUG->4
...
...
...
3 posts in wholeList
DEBUG->0
The concept of mainloop assumes that you first create and initialize objects (well, at least these that are required at application start, i.e. not used dynamically), set event handlers (implement interface logic) and then go into infinite event handling (what User Interface essentially is), i.e. main loop. So, that is why you see it as it "hangs". This is called event-driven programming
And the important thing is that this event handling is done in one single place, like that:
class GUIApp(tk.Tk):
...
app = GUIApp()
app.mainloop()
So, the mainloop returns when the window dies.
Until I have some time to refactor my code, I solved the problem by adding the following line to my destroy() calls:
self.quit() # Ends mainloop
self.master.destroy() # Destroys master (window)
I understand this doesn't not solve the bad structure of my code, but it answers my specific question. destroy() doesn't end the mainloop of TopLevels, but quit() does. Adding this line makes my code execute in a predictable way.
I will be accepting #pmod 's answer as soon as I have refactored my code and verified his claim that the Tk() mainloop will cover all child TopLevels.
Hopefully this doesn't fall under "general discussion topic", since I'd like it to be more about resolving these issues in an efficient manner than a giant debate about which general approach to GUI programming is the absolute best.
So I've started some GUI programming with tkinter and long story short my code is getting pretty ugly pretty quickly. I'm trying to create a tile-based map editor for a video game. My main issues seem to be:
the inability of callbacks to return values.
the inability to transfer data between windows easily.
I assume that the reason I see these as issues is because I'm using functions a lot more than I'm using classes. For instance, my "load tileset" window is handled entirely functionally: Clicking the menu option in the main window calls the function that loads the new window. From within that window, I create an open file dialog when looking for the image, and modify the canvas displaying the image when I press the enter key (so that it draws the appropriate grid over the image). function function function.
What looks like really bad practice to me is the inclusion of extra arguments to compensate. For example, when I create a tileset, the instance of the TileSet class created should be sent back to the main window where the appropriate information can be displayed. I have a list of loaded tilesets as a global variable (even more bad practice: Everything dealing with my root window is in the global scope! yay!), and because callback functions don't return values, I pass that list as an argument to my "load tileset window" function, which then passes the argument to the create tileset function (called when you click the appropriate button in the window), where it's actually needed so that I can add my newly created tileset to the list. Passing arguments through a function 'hierarchy' like that seems like a horrible idea. It gets confusing, it's horrible for writing modular code, and just generally seems unnecessary.
My attempt at fixing the problem would be to write a class representing the whole GUI, and custom made window classes (that the GUI class can create and reference) that can actually store relevant data. That should take care of issues with transferring data between windows. Hopefully it would cut down on my gratuitous use of lambda functions in callbacks as well.
But I'm wondering: is this the best way? Or at least close? I'd rather not start rewriting and then end up with another system that's just sloppy and confusing in a different way. I know my methods are bad, but I don't really know what the best approach would be. I'm getting a lot of advice on how to do specific things, but none on how to structure the program as a whole. Any help would be greatly appreciated.
It sounds like you're trying to create a GUI that acts procedurally, which won't work. GUIs aren't procedural, their code doesn't run linearly where functions call callbacks which return values. What you're asking isn't unique to tkinter. This is the nature of event based GUI programming -- callbacks can't return anything because the caller is an event rather than a function.
Roughly speaking, you must use a global object of some sort to store your data. Typically this is called the "Model". It can be a global variable, or it might be a database, or it can be an object of some sort. In any case, it must exist "globally"; that is, it must be accessible to the whole GUI.
Often, this access is provided by a third component called a "Controller". It is the interface between the GUI (the "View") and the data (the "Model"). These three components make up what is called the model-view-controller pattern, or MVC.
The model, view and controller don't have to be three different objects. Often, the GUI and the controller are the same object. For small programs this works quite well -- the GUI components talk directly to your data model.
For example, you could have a class that represents a window which inherits from Tkinter.Toplevel. It can have an attribute that represents the data being edited. When the user selects "New" from a main window, it does something like self.tileset = TileSet(filename). That is, it sets the attribute named tileset of the GUI object named self to be an instance of the TileSet class specific to the given filename. Later functions that manipulate the data use self.tileset to access the object. For functions that live outside the main window object (for example, a "save all" function from the main window) you can either pass this object as an argument, or use the window object as the controller, asking it to do something to its tileset.
Here's a brief example:
import Tkinter as tk
import tkFileDialog
import datetime
class SampleApp(tk.Tk):
def __init__(self, *args, **kwargs):
tk.Tk.__init__(self, *args, **kwargs)
self.windows = []
menubar = tk.Menu(self)
self.configure(menu=menubar)
fileMenu = tk.Menu(self)
fileMenu.add_command(label="New...", command=self.new_window)
fileMenu.add_command(label="Save All", command=self.save_all)
menubar.add_cascade(label="Window", menu=fileMenu)
label = tk.Label(self, text="Select 'New' from the window menu")
label.pack(padx=20, pady=40)
def save_all(self):
# ask each window object, which is acting both as
# the view and controller, to save it's data
for window in self.windows:
window.save()
def new_window(self):
filename = tkFileDialog.askopenfilename()
if filename is not None:
self.windows.append(TileWindow(self, filename))
class TileWindow(tk.Toplevel):
def __init__(self, master, filename):
tk.Toplevel.__init__(self, master)
self.title("%s - Tile Editor" % filename)
self.filename = filename
# create an instance of a TileSet; all other
# methods in this class can reference this
# tile set
self.tileset = TileSet(filename)
label = tk.Label(self, text="My filename is %s" % filename)
label.pack(padx=20, pady=40)
self.status = tk.Label(self, text="", anchor="w")
self.status.pack(side="bottom", fill="x")
def save(self):
# this method acts as a controller for the data,
# allowing other objects to request that the
# data be saved
now = datetime.datetime.now()
self.status.configure(text="saved %s" % str(now))
class TileSet(object):
def __init__(self, filename):
self.data = "..."
if __name__ == "__main__":
app = SampleApp()
app.mainloop()
I'm making a program that fits the wizard concept ideally; the user is walked through the steps to create a character for a game.
However, I'm realizing that the limitations of the wizard are making it difficult to design "elegant" logic flow. For example, because all pages of the wizard are initalized at the same time, I can't have the values entered in one page available to the next one. I have to put a button on each page to get the values from a previous page rather than simply having fields auto-populated.
I've thought about alternatives to using the wizard. I think the best idea is to have some buttons on one panel that change the information on another panel, e.g. a splitter window.
However, I can't find any documentation in wxPython on how to dynamically change the panel. Everything I've found so far is really pretty static, hence the use of the wizard. Even the "wxPython in Action" book doesn't mention it.
Are there any tutorials for making "dynamic panels" or better management of a wizard?
Here is a simple example. This way you can make your "wizard" work like a finite state machine where states are different pages that are initialized on demand. Also, the data is shared between pages.
import wx
import wx.lib.newevent
(PageChangeEvent, EVT_PAGE_CHANGE) = wx.lib.newevent.NewEvent()
class Data:
foo = None
bar = None
class Page1(wx.Panel):
def __init__(self, parent, data):
wx.Panel.__init__(self, parent)
self.parent = parent
self.data = data
sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(sizer)
label = wx.StaticText(self, label="Page 1 - foo")
self.foo = wx.TextCtrl(self)
goto_page2 = wx.Button(self, label="Go to page 2")
for c in (label, self.foo, goto_page2):
sizer.Add(c, 0, wx.TOP, 5)
goto_page2.Bind(wx.EVT_BUTTON, self.OnPage2)
def OnPage2(self, event):
self.data.foo = self.foo.Value
wx.PostEvent(self.parent, PageChangeEvent(page=Page2))
class Page2(wx.Panel):
def __init__(self, parent, data):
wx.Panel.__init__(self, parent)
self.parent = parent
self.data = data
sizer = wx.BoxSizer(wx.VERTICAL)
self.SetSizer(sizer)
label = wx.StaticText(self, label="Page 2 - bar")
self.bar = wx.TextCtrl(self)
goto_finish = wx.Button(self, label="Finish")
for c in (label, self.bar, goto_finish):
sizer.Add(c, 0, wx.TOP, 5)
goto_finish.Bind(wx.EVT_BUTTON, self.OnFinish)
def OnFinish(self, event):
self.data.bar = self.bar.Value
wx.PostEvent(self.parent, PageChangeEvent(page=finish))
def finish(parent, data):
wx.MessageBox("foo = %s\nbar = %s" % (data.foo, data.bar))
wx.GetApp().ExitMainLoop()
class Test(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
self.data = Data()
self.current_page = None
self.Bind(EVT_PAGE_CHANGE, self.OnPageChange)
wx.PostEvent(self, PageChangeEvent(page=Page1))
def OnPageChange(self, event):
page = event.page(self, self.data)
if page == None:
return
if self.current_page:
self.current_page.Destroy()
self.current_page = page
page.Layout()
page.Fit()
page.Refresh()
app = wx.PySimpleApp()
app.TopWindow = Test()
app.TopWindow.Show()
app.MainLoop()
The wxPython demo has an example of a "dynamic" wizard. Pages override GetNext() and GetPrev() to show pages dynamically. This shows the basic technique; you can extend it to add and remove pages, change pages on the fly, and rearrange pages dynamically.
The wizard class is just a convenience, though. You can modify it, or create your own implementation. A style that seems popular nowadays is to use an HTML-based presentation; you can emulate this with the wxHtml control, or the IEHtmlWindow control if your app is Windows only.
You could try using a workflow engine like WFTK. In this particular case author has done some work on wx-based apps using WFTK and can probably direct you to examples.
I'd get rid of wizard in whole. They are the most unpleasant things I've ever used.
The problem that requires a wizard-application where you click 'next' is perhaps a problem where you could apply a better user interface in a bit different manner. Instead of bringing up a dialog with annoying 'next' -button. Do this:
Bring up a page. When the user inserts the information to the page, extend or shorten it according to the input. If your application needs to do some processing to continue, and it's impossible to revert after that, write a new page or disable the earlier section of the current page. When you don't need any input from the user anymore or the app is finished, you can show a button or enable an existing such.
I don't mean you should implement it all in browser. Make simply a scrolling container that can contain buttons and labels in a flat list.
Benefit: The user can just click a tab, and you are encouraged to put all the processing into the end of filling the page.
It should be noted that a Wizard should be the interface for mutli-step, infrequently-performed tasks. The wizard is used to guide the user through something they don't really understand, because they almost never do it.
And if some users might do the task frequently, you want to give those power users a lightweight interface to do the same thing - even if it less self explanatory.
See: Windows Vista User Experience Guidelines - Top Violations
Wizards
Consider lightweight alternatives first, such as dialog boxes, task
panes, or single pages. Wizards are
a heavy UI, best used for multi-step,
infrequently performed task. You don't
have to use wizards—you can provide
helpful information and assistance in
any UI.