I have a GUI made with wxPython that calls a function that I imported from a separate Python file when I press a button, and shows the output of that function in a text box. I want to improve it so that if the function asks for user input mid-execution (like a raw_input()), I want a new popup window to appear instead of the raw_input waiting in the text box. I've been looking through the wxPython documentation but can't seem to find anything that resembles what I want, so I was wondering if anyone here could give me any pointers.
GUI code:
import sys
import os
import re
import subprocess
import threading
import wx
import errno, os, stat, shutil
import extern_func
#this object redirects the external function output to the text box
class RedirectText(object):
def __init__(self,aWxTextCtrl):
self.out=aWxTextCtrl
def write(self,string):
self.out.WriteText(string)
#GUI code here
class progFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title="functionGUI", size=(800, 600), style=wx.DEFAULT_FRAME_STYLE ^ wx.RESIZE_BORDER)
panel = wx.Panel(self)
#more things....
self.runButton = wx.Button(panel, wx.ID_OK, "Run", pos=(200, 300))
self.out=wx.TextCtrl(panel, style=wx.TE_MULTILINE|wx.VSCROLL|wx.TE_READONLY, pos = (300, 50), size=(500, 200))
#Run button event
self.Bind(wx.EVT_BUTTON, self.OnRun, self.runButton)
#command prompt output to frame
redir=RedirectText(self.out)
sys.stdout=redir
self.Show()
def OnRun(self, event):
t=threading.Thread(target=self.__run)
t.start()
#external function call
def __run(self):
externFunc()
if __name__ == '__main__':
app = wx.App(False)
progFrame(None)
app.MainLoop()
External function code:
import sys
def externFunc():
print "Starting execution..."
#a bunch of code...
cont = raw_input("Something has gone wrong. Do you still want to continue?")
if(cont.lower() == "n")
sys.exit(0)
#more function code...
print "Success!"
I would call the external function via a button event. Instead of raw_input, I would just use a wx.MessageDialog with a yes or no button on it. You can check which button the user pressed and continue or not accordingly. Here are some links on that dialog and others:
http://wxpython.org/Phoenix/docs/html/MessageDialog.html
http://www.blog.pythonlibrary.org/2010/06/26/the-dialogs-of-wxpython-part-1-of-2/
http://zetcode.com/wxpython/dialogs/
If this piece of code you are running takes a long time (i.e. greater than a second), then it is probably going to block wx's mainloop and cause the application to become unresponsive. If that is the case, then you'll need to move this code into a thread. The following articles will help you with that course of action:
http://wiki.wxpython.org/LongRunningTasks
http://www.blog.pythonlibrary.org/2010/05/22/wxpython-and-threads/
Related
I'm kind of new with wxpython and python itself, sorry for the basic question.
I'm trying to organize my code in a more easy way to manage it. I created this simple example that kind of resume my problem. Basically it is just a window with a button that print a message. I separated it in three simple packages
ef_Main.py - This is the main packages it will import the UI and the application itself.
ef_Tool.py - it is the application that will run all the important code, right now it is just a print statment but will have all the application code.
ef_UI.py - a very basic interface using wxpython.
How it should work:
Run ef_Main.py it will import the interface (ef_UI.py) and the main code (ef_Tool.py). When something is click in the interface it will be ready by the ef_Main and send to ef_Tool to be executed.
My problem is:
I'm not sure how to use bind function to connect this three packages. I believe that it should be in the ef_Main but how it will get the info from the interface and send it to the ef_Tool.py.
And if I want to get some output from the ef_Tool and send it back to the interface. How should I do that.
Here is my code.
#ef_Main.py
import wx
import ef_UI as eU
import ef_Tool as eT
''' Here is where I don't know how to make it works,
if I should put a class here or not, and how to bind
this function with the impTool and impUI'''
#class MyMain(self):
def uiControls(self):
self.Bind(wx.EVT_BUTTON, eU.OnClick(self), eT.MyTools(self))
def main():
app = wx.App(False)
frame = eU.MyFrame()
frame.Show()
app.MainLoop()
if __name__ == "__main__":
main()
=======================
#ef_Tool.py
import wx
'''just a simple as possible function to be execute when it is called '''
class MyTools():
def OnClick(self, event):
#Value = self.MyTextCtrl.GetValue()
print "it is working! "
=======================
#ef_UI.py
import wx
''' very simple interface with only a button and a TextCtrl '''
class MyFrame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Menu Test")
self.panel = wx.Panel(self)
self.MyButton = wx.Button(self.panel, -1, "Button_test", (0, 0))
self.MyTextCtrl = wx.TextCtrl(self.panel, -1, value="just a test", pos=(100, 0))
Thanks in advance!
Emerson
Here is a simple example of fulfilling your requirements. Supposing all your application logic is done in ef_Tool.py and the input for these logic is from ef_UI and the output is also sent to ef_UI.py.
You need to call a method in ef_Tool.py when a button click event occurs in ef_UI. You can invoke this method from MyFrame's method. But you need an obj of MyTools to do this.
so first, create an obj for MyTools in ef_Main.py and pass this object to MyFrame
#ef_Main.py
import wx
import ef_UI as eU
import ef_Tool as eT
def main():
efToolObj = eT.MyTools() # object of MyTools class
app = wx.App(False)
frame = eU.MyFrame(efToolObj) # Pass this to MyFrame so that it can make use of it
frame.Show()
app.MainLoop()
if __name__ == "__main__":
main()
Store this MyTools's object in you MyFrame class. And then use this object to call the corresponding method inside MyTools
#ef_UI.py
import wx
''' very simple interface with only a button and a TextCtrl '''
class MyFrame(wx.Frame):
def __init__(self, efToolObj):
wx.Frame.__init__(self, None, title="Menu Test")
self.panel = wx.Panel(self)
self.efToolObj = efToolObj # save the MyTools object to be used later
self.MyButton = wx.Button(self.panel, -1, "Button_test", (0, 0))
self.MyButton.Bind(wx.EVT_BUTTON, self.onClickEvent) # Bind the click event to an event handling method
self.MyTextCtrl = wx.TextCtrl(self.panel, -1, value="just a test", pos=(100, 0))
def onClickEvent(self,event): #this is called when a button is clicked
res = self.efToolObj.OnClickPrinting(self.MyTextCtrl.GetValue()) #Use the mytools object to call its method to apply logic,also get result values
self.MyTextCtrl.SetValue(res) #use the result values in your UI
You can pass the info you want to send to the application logic in its arguments and get results as return values.
#ef_Tool.py
class MyTools:
def OnClickPrinting(self,textvalue):
#Value = self.MyTextCtrl.GetValue()
print "it is working! ",textvalue
resultstr = "test successful"
return resultstr
Hope this helped.
My wxPython GUI either quits with a Segmentation Fault or fails to quit at all using the standard options. The only successful quit option (no errors) is wx.Exit, which I understand is not a great practice. I've traced the issues down to a few factors, but I'm scratching my head as to why they are having this effect.
Using the wxPython inspector (wx.lib.inspection.InspectionTool()), I've been able to determine that a FigureFrameWxAgg is being created when I run certain pylab functions (pylab.xticks() is the function that creates it here, but I haven't tracked down every single function that has this effect). I don't know what this window is for. It's invisible and doesn't appear to do anything. However, this window totally messes up the shutdown of my GUI. If I use self.Destroy, Python doesn't shut down fully. If I use sys.exit, I get a Segmentation fault. I need to catch the wx.EVT_CLOSE so that I can prompt the user to save his/her work.
Here is the code for a simplified version of the GUI:
import matplotlib
matplotlib.use('WXAgg')
from matplotlib.backends.backend_wxagg import FigureCanvasWxAgg as FigCanvas
import wx
import wx.grid
import wx.lib.scrolledpanel
import wx.lib.inspection
import sys
import pylab
class my_frame(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, -1, 'Many Rows')
self.InitUI()
def InitUI(self):
self.panel = wx.Window(self, size=(200, 200))
hbox_all= wx.BoxSizer(wx.HORIZONTAL)
self.create_menubar()
self.fig1 = pylab.Figure((5, 5), dpi=100)
self.canvas1 = FigCanvas(self.panel, -1, self.fig1)
self.fig1.text(0.01,0.98,"Arai plot",{'family':'Arial', 'fontsize':10, 'style':'normal','va':'center', 'ha':'left' })
self.araiplot = self.fig1.add_axes([0.1,0.1,0.8,0.8])
self.araiplot.clear()
self.araiplot.plot(range(5),range(5),lw=0.75,clip_on=False)
xt = pylab.xticks()
grid = wx.grid.Grid(self.panel)
grid.ClearGrid()
grid.CreateGrid(100, 100)
grid.AutoSize()
hbox_all.AddSpacer(self.canvas1)
hbox_all.AddSpacer(20)
hbox_all.AddSpacer(grid)
hbox_all.AddSpacer(20)
self.panel.SetSizer(hbox_all)
hbox_all.Fit(self)
self.Centre()
self.Show()
def create_menubar(self):
"""
Create menu bar
"""
self.menubar = wx.MenuBar()
menu_file = wx.Menu()
menu_file.AppendSeparator()
m_exit = menu_file.Append(wx.ID_EXIT, "Quit", "Quit application")
self.Bind(wx.EVT_CLOSE, self.on_menu_exit)
self.menubar.Append(menu_file, "&File")
self.SetMenuBar(self.menubar)
def on_menu_exit(self, event):
self.Destroy() # this doesn't quit Python fully, unless I comment out 'matplotlib.use('WXAgg')'
#for w in wx.GetTopLevelWindows():
# if w.Title == 'Figure 1':
# w.Destroy() # if I pre-destroy the FigureFrameWxAgg window, I get a PyDeadObjectError when I run self.Destroy
# self.Destroy() #
# wx.Exit() # forces the program to exit, with no clean up. works, but not an ideal solution
#sys.exit() # program closes, but with segmentation error
#self.Close() # creates infinite recursion error, because we have a binding to wx.EVT_CLOSE
if __name__ == '__main__':
app = wx.PySimpleApp(redirect=False)
app.frame = my_frame()
if '-i' in sys.argv:
wx.lib.inspection.InspectionTool().Show()
app.MainLoop()
To add one more level of complexity, the Segmentation Fault with sys.exit() only happens with my brew installed Python. Sys.exit() works fine with Canopy Python.
My questions are: how can I fix this error? And, is using wx.Exit() really so bad?
There are several issues with your example:
Do not use pylab in GUI applications, because is brings its own mainloop (which will not quit when the wxPython mainloop quits). You had to kill pylab.
# both not required
# matplotlib.use('WXAgg')
# import pylab
# use instead
from matplotlib.figure import Figure
...
def __init__(# ...
...
self.fig1 = Figure((5, 5), dpi=100)
Your menu item "Close" does not work (at least not on Windows). wx.ID_EXIT is meant for buttons in dialogs. Do not ask me which predefined IDs are meant for menus.
ID_QUIT = wx.NewId()
menu_file.Append(ID_QUIT , "Quit", "Quit application")
#
self.Bind(wx.EVT_MENU, self.on_quit, id=ID_QUIT)
def on_quit(self, evt):
self.Close()
In this case it is not necessary to bind to wx.EVT_CLOSE. If you want to do something on the close event, you have to skip it. When you skip it, wxPython will deal with it on its own.
self.Bind(wx.EVT_CLOSE, self.on_close)
...
def on_close(self, evt):
# you can veto the close here or perform cleanup
evt.Skip()
If you change your code accordingly, wxPython will close everything properly.
I agree with the answer from #nepix32 on not using pylab but use the OOP approach of Matplotlib. However, in my case, I still need the matplotlib.use('WX') for the application to show.
I have the same problem of wxpython GUI application not terminated properly if a Matplotlib figure is displayed even if I'm not using pylab or pyplot.
My workaround is to pass the app object to the wx.Frame class and call app.Exit at EVT_CLOSE.
class my_frame(wx.Frame):
def __init__(self, app=None):
...
self.InitUI()
self.Bind(wx.EVT_CLOSE, lambda evt: app.Exit())
...
if __name__ == '__main__':
...
app.frame = my_frame(app)
...
I am up for anyone who can suggest cleaner solution. To be honest, I'm still new to wxpython.
I am new to python. I am trying to gather links from youtube based on search string. Assumption is the link I want will be the first result on my search results.
Problem I am facing is mouse clicks are not recognised on the gui. It just moves to the x,y cordinates but I cannot see that it is getting clicked. Please help me to understand why this clicks are not recognized.
Below is the code: I created a GUI in wxpython, and I am trying to click on search bar of youtube, then paste the search string, then click on search take the link address from the first result. This below code is sample code. In my real code search string will be passed from excel sheet.
import win32api, win32con
import win32com.client as win32
from win32com.client import Dispatch, Constants
import os
import time
import win32clipboard
import wx
import wx.html2
import sys
x_pad = 0
y_pad = 0
def leftClick():
wx.EVT_LEFT_DOWN
time.sleep(.1)
wx.EVT_LEFT_UP
print "Click." #completely optional. But nice for debugging purposes.
def rightClick():
wx.EVT_RIGHT_DOWN
time.sleep(.1)
wx.EVT_RIGHT_UP
print "Right Click." #completely optional. But nice for debugging purposes.
def mousePos(cord):
win32api.SetCursorPos((x_pad + cord[0], y_pad + cord[1]))
class URL(wx.Frame):
def __init__(self, *args, **kw):
super(URL, self).__init__(*args, **kw)
self.InitUI()
def InitUI(self):
pnl = wx.Panel(self)
sbtn = wx.Button(pnl, label='Start', pos=(850, 560))
cbtn = wx.Button(pnl, label='Close', pos=(850, 610))
stdot = wx.TextCtrl(pnl, pos =(400,580),size=(400,80), style=wx.TE_MULTILINE|wx.TE_READONLY|wx.HSCROLL)
sys.stdout = stdot
sys.stderr = stdot
sbtn.Bind(wx.EVT_BUTTON, self.OnStart)
cbtn.Bind(wx.EVT_BUTTON, self.OnClose)
brwser = wx.html2.WebView.New(pnl, size = (1280,550), pos = (0,0), url =("http://www.youtube.com/results?search_query=sarabhai+vs+sarabhai+episode+25&sm=3"))
self.SetSize((1280, 720))
self.SetTitle('YouTube URL Grab')
self.SetPosition((0,0))
self.Show(True)
def OnStart(self,e):
win32clipboard.OpenClipboard()
win32clipboard.EmptyClipboard()
win32clipboard.SetClipboardText("Sarabhai vs Sarabhai episode 31") #Sample string for search
win32clipboard.CloseClipboard()
mousePos((252, 53))
leftClick()
leftClick()
leftClick()
rightClick()
time.sleep(5)
mousePos((280, 144))
leftClick()
time.sleep(5)
mousePos((755, 53))
leftClick()
time.sleep(7)
mousePos((467, 140))
rightClick()
time.sleep(5)
mousePos((514, 321))
leftClick()
def OnClose(self, e):
self.Close(True)
def main():
ex = wx.App()
URL(None)
ex.MainLoop()
if __name__ == '__main__':
main()
The names wx.EVT_LEFT_DOWN and wx.EVT_LEFT_UP are actually just numbers for the event identifies that you need to bind to they do not cause the events to happen! Look at the examples in the docs and demos package.
The basic process is:
During frame intitialisation bind handlers to the events you need to handle, these should be of type fn(self, evt) and by convention are named onNameOfEvent e.g. def onLeftBtnDown(self, evt) these contain the events that you need to react to and the evt parameter gives you an event object the members of which depend on the event type. The event binding uses the format self.Bind(wx.EVT_NAME_OF_EVENT, onNameOfEvent) and is normally in the windows __init__ method, (this is what the vast majority of GUI code does, including non wx GUIs).
If, (much more rarely), you need to generate events from your code rather than just reacting to the events you still need to do the above then where you need to create the event by declaring the appropriate type of event object, e.g. wx.CommandEvent, (note that you can create your own event types), populating with the required values and then use 'wx.PostEvent' to add it to the event queue for the window or control that it applies to. Sometime later, when it gets to the top of the queue, your event handler will be called with this event.
I'm trying to run a simple Tkinter program that opens a program when you click a button. The code is listed below. I use a command to call a program that then calls a fortran program. However, when I click on the button, it opens the program but the menu of the program i'm calling goes into an infinite loop......the offending code seems to be in the button1Click module.
Any help is greatly appreciated.
Thanks
from Tkinter import *
import os, sys
from win32com.client import Dispatch
xlApp=Dispatch('Excel.Application')
_PSSBINPATH=r"C:\Program Files\PTI\PSSE32\PSSBIN"
os.environ['PATH']=_PSSBINPATH+';'+os.environ['PATH']
sys.path.insert(0,_PSSBINPATH)
import redirect; redirect.psse2py()
import psspy
class MyApp:
def __init__(self, parent):
self.myParent = parent ### (7) remember my parent, the root
self.myContainer1 = Frame(parent)
self.myContainer1.pack()
self.button1 = Button(self.myContainer1)
self.button1.configure(text="OK", background= "green")
self.button1.pack(side=LEFT)
self.button1.bind("<Button-1>", self.button1Click) ### (1)
self.button2 = Button(self.myContainer1)
self.button2.configure(text="Cancel", background="red")
self.button2.pack(side=RIGHT)
self.button2.bind("<Button-1>", self.button2Click) ### (2)
def button1Click(self,event): ### (3)
psspy.runiplanfile(r"C:\MNTACT\Contingency Program\work\contingency-31-4.irf")
if self.button1["background"] == "green": ### (4)
self.button1["background"] = "yellow"
else:
self.button1["background"] = "green"
def button2Click(self, event): ### (5)
self.myParent.destroy() ### (6)
root = Tk()
myapp = MyApp(root)
root.mainloop()
What makes you think there's an infinite loop happening? I see no loop in button1Click, unless the loop is in runiplanfile. Are you using "infinite loop" to mean simply that the GUI has stopped responding?
Tkinter is single threaded and cannot process events except via the event loop. If one event takes a long time to process, the GUI will hang until the processing of that event is completed. If you're exec'ing an external process and waiting for it to complete, your GUI will appear to be frozen until that process finishes.
Is it possible to run a wxApp from another wxApp?
I am trying to simply call a program I wrote (called DataDeck) from a method of another wxApp, like it was a plugin.
something like:
def on_datadeck_btn_click(self, event):
import datadeck.main
datadeck.main.run()
event.Skip()
where datadeck.main.run() is a classic start of a wxApp:
def run():
app = DataDeck(0)
app.SetAppName("DataDeck")
app.MainLoop()
Right now, it correctly opens DataDeck the first time and it works, but it won't reopen DataDeck a second time after I close it. This would freeze everything.
Update: based on #Mike Driscoll answer, I documented myself more and came to the following solution:
I added an "entry point" in datadeck
def run_as_plugin():
#[do some stuff related to XRC layouts and sysout redirection]
MainGUI = datadeck.gui.maingui.MainGUI()
Where the constructor of MainGUI() automatically shows the wxFrame. Now my application behaves like it was a component of the caller wxApp.
Therefore, I modify the application method as follows:
def on_datadeck_btn_click(self, event):
import datadeck.main
datadeck.main.run_as_plugin()
event.Skip()
It was very simple, indeed! I just had to modify my objects that deal with stdout redirection (not part of this question, I omit the details), and everything worked fine.
There should only be on wx.App. From what I've read online, you can't have two wx.App objects running in one script. You could probably do it using the subprocess module to open a new process though. Take a look at Editra to see some examples for how to do plugins. It is included with wxPython or you can download it separately.
It is perfectly feasible. Not sure why it doesnt work for you.
This example works perfectly:
--main.py--
import wx
class MainFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title='Main', size=(353,270))
button= wx.Button(self, -1, 'call app', pos=(10,10), size=(-1,30))
self.Bind(wx.EVT_BUTTON, self.capp, button)
def capp(self, event):
import datadeck
datadeck.run()
if __name__ == '__main__':
app = wx.App(0)
frame = MainFrame(None)
frame.Show()
app.MainLoop()
--datadeck.py--
import wx
class DDFrame(wx.Frame):
def __init__(self, parent):
wx.Frame.__init__(self, parent, title='DDFrame', size=(353,270))
button = wx.Button(self, -1, 'print something', pos=(100,100), size=(-1,30))
self.Bind(wx.EVT_BUTTON, self.say_hello, button)
def say_hello(self, event):
print 'something'
class DataDeck(wx.App):
def OnInit(self):
frame = DDFrame(None)
frame.Show()
return True
def run():
app = DataDeck(1)
app.SetAppName("DataDeck")
app.MainLoop()
if you press the 'call app' button you get the new frame open. And you can open as many as you want.
Created aplications/frames are independent of each other. You can close any of them without affecting the others. And the system doesnt freeze.