wxpython communication between notebook and main frame - python

I have a notebook whose parent is the main frame of the application. The main frame also has a panel showing a chart to the side of the notebook, a menu bar and a status bar.
The notebook has a couple of pages and each page has some nested panels.
I'd like the callbacks for buttons in those panels to be able to talk to the main frame.
At the moment, that means a ridiculous chain of 'parents'. For example, to get to status bar from a panel on a notebook page I would do:
stat = self.parent.parent.parent.status_bar
The first parent is the notebook page, the second parent is the notebook and finally the last parent is the main frame.
This leads to very obtuse code...
Naturally you can see how this might get worse if I wanted to talk between elements on the panel adjacent to the notebook or nest the notebook in it's own panel..
Any tips?

There is a simple way to get your Main Frame.
Since you can get your app instance anywhere in your code with "wx.GetApp()", then you can set your Mainframe into your app instance, it would be easy to fecth.
Please try following simple sample:
import wx
class TestFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None)
btn = wx.Button(wx.Panel(self), label = "test")
self.Bind(wx.EVT_BUTTON, self.onButton)
def onButton(self, evt):
print "onButton"
app = wx.GetApp()
print app.Myframe
app = wx.App()
frame = TestFrame()
frame.Center()
frame.Show()
app.Myframe = frame
app.MainLoop()

If you need to get access to the top frame, you should be able to use wx.GetTopLevelParent(). Personally, I think pubsub is probably the easiest way to call various classes in wxPython and it's pretty clean too. Plus if you need to call multiple frames or panels or whatever, you can have them all "subscribe" to the same message name and then publish a message for all of them to pick up.
Here's a tutorial for pubsub: http://www.blog.pythonlibrary.org/2013/09/05/wxpython-2-9-and-the-newer-pubsub-api-a-simple-tutorial/

Related

wxPython - creating a GUI for an application that currently works as a CLI version - not sure where to start

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 :)

SearchCtrl in wxPython looks different

I am trying to get a searchctrl in wxPython. However I am not getting exactly what I want.
I get this:
But I would like to get the SearchCtrl like:
I know that there isn't a big difference, it is just for visual reasons.
I declare my SearchCtrl as:
self.searchControl = wx.SearchCtrl(panel, -1, style=wx.TE_PROCESS_ENTER)
Does anybody know how can I declare it in order to get the SearchCtrl as I want?
There are three differences that I can see between what you have and what you want:
The cancel button (X in a circle)
The menu indicator (the arrow next to the magnifying glass).
The location of the word "Search"
None of these three differences are affected by the declaration.
To get the cancel button to show up, call:
self.searchControl.ShowCancelButton(True)
To get the menu indicator to show up, call:
self.SetMenu(menu)
To get the text to appear in the right place, prevent the sizer from vertically resizing your control.
For example:
#!/usr/bin/env python
import wx
app = wx.App(False)
frame = wx.Frame(None, wx.ID_ANY, "Hello World")
sizer = wx.BoxSizer(wx.HORIZONTAL)
menu = wx.Menu()
menu.Append(wx.ID_ABOUT, 'About')
search = wx.SearchCtrl(frame)
search.ShowCancelButton(True)
search.SetMenu(menu)
sizer.Add(search, 0)
frame.SetSizer(sizer)
frame.SetAutoLayout(1)
sizer.Fit(frame)
frame.Show()
app.MainLoop()
yields this:

WxPython - How to hide the X and expand button on window

Im making a python program and in some functions it needs to hide the X and expand window buttons, how would i do it? Im using WxPython, how would I put this in?
The widgets in the window frame are defined as part of the window's style: CLOSE_BOX, MINIMIZE_BOX, and MAXIMIZE_BOX.
So, when you create the window, just leave those styles out.
If you're using a wx.Frame subclass, note that DEFAULT_FRAME_STYLE includes these values, so you will have to mask them out:
style = wx.DEFAULT_FRAME_STYLE & (~wx.CLOSE_BOX) & (~wx.MAXIMIZE_BOX)
super().__init__(whatever, args, you, use, style=style)
If you want to change them after creation, you use SetWindowStyle:
style = self.GetWindowStyle()
self.SetWindowStyle(style & (~wx.CLOSE_BOX) & (~wx.MAXIMIZE_BOX))
self.Refresh()
However, notice that the documentation of that function says:
Please note that some styles cannot be changed after the window creation and that Refresh() might need to be called after changing the others for the change to take place immediately.
And, from what I can tell, on Windows, if you create a window with a close box and then remove it later in this way, it doesn't actually go away. It does disable, which may be good enough. But if not, there's probably no way to do what you want without either reaching underneath wx to the native Windows API (which gets very tricky), or drawing the widgets on the frame manually (which gets even more tricky, especially if you care about looking right on different versions of Windows—not to mention porting to other platforms).
I wrote about Frame styles a while ago on my blog. To remove all the buttons, you could do this:
import wx
########################################################################
class NoSystemMenuFrame(wx.Frame):
"""
There is no system menu, which means the title bar is there, but
no buttons and no menu when clicking the top left hand corner
of the frame
"""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
no_sys_menu = wx.CAPTION
wx.Frame.__init__(self, None, title="No System Menu", style=no_sys_menu)
panel = wx.Panel(self)
self.Show()
#----------------------------------------------------------------------
if __name__ == "__main__":
app = wx.App(False)
frame = NoSystemMenuFrame()
app.MainLoop()
I tried setting the style to wx.DEFAULT_FRAME_STYLE & (~wx.CLOSE_BOX) & (~wx.MAXIMIZE_BOX) and to wx.DEFAULT_FRAME_STYLE^(wx.CLOSE_BOX|wx.MAXIMIZE_BOX), but both of those seem to only remove the Close box. For some reason, the Maximize button is still there on my Xubuntu machine.

Is it possible to inset a panel into a BoxSizer in wxPython

I am trying to make something that looks like an installer without actually installing anything ( I am using it to explore wxPython) .And right now I'm trying to put a panel on another panel, but when ever I do that the window shows up grey and only the corner of one button shows .
import wx
class PanelOne(wx.Panel):
def __init__(self,*args,**kwargs):
wx.Panel.__init__(self,*args,**kwargs)
def initUI(self):
vbox=wx.BoxSizer(wx.VERTICAL)
vbox.Add(wx.StaticText(self,-1,'This is Panel One'),0,wx.ALIGN_CENTER_HORIZONTAL|wx.ALIGN_CENTER_VERTICAL)
vbox.SetSizeHints(self)
self.SetSizer(vbox)
self.Show(True)
class Gui(wx.Frame):
def __init__(self,*args,**kwargs):
wx.Frame.__init__(self,*args,**kwargs)
self.initUI()
self.tTips()
def initUI(self):
panel=wx.Panel(self)
self.vbox=wx.BoxSizer(wx.VERTICAL)
panel.SetSizer(self.vbox)
##This is what im having trouble with
p1=PanelOne(self)
self.vbox.Add(p1,3,wx.ALL,10)
##
btnBox=wx.BoxSizer(wx.HORIZONTAL) #This Sizer is for the box that will hold the next and back buttons.
backBtn=wx.Button(panel,-1,'&Back')# back button
backBtn.Bind(wx.EVT_BUTTON,self.onBack)
btnBox.Add(backBtn,0,wx.ALL,10)
nextBtn=wx.Button(panel,-1,'&Next')# next button
nextBtn.Bind(wx.EVT_BUTTON,self.onNext)
btnBox.Add(nextBtn,0,wx.ALL,10)
self.vbox.Add(btnBox)
self.vbox.SetSizeHints(self)
self.Show(True)
def onNext(self,e):
pass
def onBack(self,e):
pass
def tTips(self):
pass
if __name__ == '__main__':
app=wx.App()
gui=Gui(None,-1,'Title Yo')
app.MainLoop()
I would recommend using the wx.wizard.Wizard widget as it basically already has everything you need builtin. You can read about it here or you can see an example in the wxPython demo (which can be downloaded from the wxPython website). Of course, rolling your own wizard is a lot more flexible. I created a skeleton to show one way to do it on my blog. That should get you started.
Your problem is that you don't add the wx.EXPAND flag when you add the panel to the sizer and you don't call SetSizer for the panel or the frame.

Alternatives to a wizard

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.

Categories