I have a 2 scripts:
script_1: performs some calculations and and prints the answers in the command line.
script_2: imports script_1 and then connects some of the functions in script_1 to pyqt4 slots.
script_1 contains no PyQt4 code
script_2 contains any relevant PyQt4 code
The reason I have my code in two scripts is so that I can modify all the "algorithms" and calculations in script_1 and without worrying about script_2 which is always changing (adding colors, buttons, and other widgets.)
When I am running script_1 it is useful to view all the output in the command line, but when I am running script_2, I am looking at a PyQt gui which has a QTextEdit widget that is blank. I would like all the text that appears in the command line to go into the QTextEdit box but I can't find a way to do that.
Is there an easy or any way to accomplish this?
You don't need to fully redirect input to the QTextEdit, just try to catch printing and append message to the QTextEdit:
class Log(object):
def __init__(self, edit):
self.out = sys.stdout
self.textEdit = edit
def write(self, message):
self.out.write(message)
self.textEdit.append(message)
def flush(self):
self.out.flush()
#...
if __name__ == '__main__':
app = QApplication(sys.argv)
print("will not be in textedit")
edit = QTextEdit()
edit.show()
sys.stdout = Log(edit)
print("will be in textedit")
Related
OS is Win7 64bit, Python is 2.7.16 64bit. I have a simple Tkinter GUI: Root containing a Notebook with two tabs. First tab contains a Frame which contains a Button. Second tab contains a Frame which contains a Text. The command bound to the Button spawns a thread with sets the content of the Text.
import Tkinter
import ttk
import threading
r = Tkinter.Tk()
n = ttk.Notebook(r)
n.pack(expand=1, fill="both")
control = ttk.Frame(n)
info = ttk.Frame(n)
tInfo = Tkinter.Text(info)
tInfo.pack(expand=1, fill="both")
n.add(control, text='Control')
n.add(info, text='Info')
infoMutex = threading.Lock()
def doGuiTest():
try:
infoMutex.acquire()
tInfo.config(state='normal')
tInfo.delete('1.0', Tkinter.END)
tInfo.insert(Tkinter.END, 'Test')
tInfo.config(state='disabled')
finally:
infoMutex.release()
def workerThread():
doGuiTest()
def execute():
worker=threading.Thread(target=workerThread)
worker.start()
bExecute=Tkinter.Button(control, text='Execute', command=execute)
bExecute.pack()
r.mainloop()
Expected result: The Text is reliably visible with the set content after the Button is clicked.
Actual result: The Text is only visible when the tab containing the Text has been manually brought to the foreground before the Button is clicked.
When I set the content of the Text directly from the Button's command everything works as expected. Sadly, in the real application I am working on the functionality triggered by the Button will be running for several minutes so using another thread is a must.
What am I missing to achieve a consistent behavior?
This question is about programming in Python 2.7.x
I wanted to code a programme where there are two functions exist: one of those is a method to get input from the user, and the other one is to show the input. Both are supposed to be done in GUI. Let's call the first function as GET TEXT function, and the second as SHOW TEXT function; my strategy is to open a GUI, show a text box, and put a button to go to SHOW TEXT function. Then, the first line of the SHOW TEXT function is to close the window opened by the GET TEXT function, get the value of the input text, and print it in another GUI.
So, I tried doing this,
from Tkinter import *
import tkMessageBox
def texttobeenteredhere():
application = Tk()
textbox = Text(application)
textbox.pack()
submitbutton = Button(application, text="OK", command=showinputtext)
submitbutton.pack()
application.mainloop()
def showinputtext():
application.quit()
thetext = textbox.get()
print "You typed", thetext
texttobeenteredhere()
I got errors that I could not comprehend, but I hope you get my idea even though my explanation could be really bad. Please suggest a solution to my problem, where the GET TEXT function and SHOW TEXT function have to exist separately in the code.
EDIT:
Thanks Josselin for introducing the syntax class in python. What I actually wanted to say was, I want the programme to open a window to get input from the user, and then close the window, and finally open another window to show the input text. I am honestly new to this, but through my prior knowledge and guessing, I tried to modify the code to meet my expectation.
import Tkinter as tk
global passtext
class application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.textbox = tk.Text(self)
self.textbox.pack()
self.submitbutton = tk.Button(self, text="OK", command=self.showinputtext)
self.submitbutton.pack()
self.mainloop()
def showinputtext(self):
self.thetext = self.textbox.get("1.0", "end-1c")
print "You typed:", self.thetext
self.destroy()
class showtext(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.setthetext = tk.StringVar()
self.setthetext.set(passtext)
self.showthetext = tk.Label(self, textvariable=self.setthetext)
self.showthetext.pack()
self.submitbutton = tk.Button(self, text="OK", command=self.destroy)
self.submitbutton.pack()
self.mainloop()
# Launch the GUI
app = application()
# Access the entered text after closing the GUI
passtext = app.thetext
text = showtext()
My English can sometimes be not understandable, but this question is answered. Thank you very much.
There are 2 main problems in your code:
First, in your showinputtext function, you want to access elements of your GUI, but they are not defined within the scope of the function.
Second, when reading the content of a tk.Text widget, the .get() method takes 2 arguments (see this link).
To fix the first problem, the best is to define your application as a class, with an inner function taking the class instance self as input argument, such that application widgets can be called within the function.
Code:
import Tkinter as tk
class application(tk.Tk):
def __init__(self):
tk.Tk.__init__(self)
self.textbox = tk.Text(self)
self.textbox.pack()
self.submitbutton = tk.Button(self, text="OK", command=self.showinputtext)
self.submitbutton.pack()
self.mainloop()
def showinputtext(self):
self.thetext = self.textbox.get("1.0", "end-1c")
print "You typed:", self.thetext
self.destroy()
# Launch the GUI
app = application()
# Access the entered text after closing the GUI
print "you entered:", app.thetext
I have the following problem with this easy script:
from Tkinter import *
root = Tk()
while 1:
pass
I think, after the 2nd line everyone would expect a Tkinter window would appear. But it does not!
If I put this line into the Python console (without the endless-while-loop), it works.
[I wanted to add an image here, but as I'm new I'm to allowed to :-(]
But running the script (double-clicking on the *.py file in the Windows Explorer) results only in an empty Python console!
Background:
Actually I want to use Snack for Python. This is based on Tkinter. That means I have to create an Tk() instance first. Everything works fine in the Python console. But I want to write a bigger program with at least one Python script thus I cannot type the whole program into the console everytime :-)
I have installed Python 2.7 and Tcl/Tk 8.5 (remember: it works in the console)
EDIT: So here's my solution:
First, I create a class CSoundPlayer:
from Tkinter import*
import tkSnack
class CSoundPlayer:
def __init__(self, callbackFunction):
self.__activated = False
self.__callbackFunction = callbackFunction
self.__sounds = []
self.__numberOfSounds = 0
self.__root = Tk()
self.__root.title("SoundPlayer")
tkSnack.initializeSnack(self.__root)
def __mainFunction(self):
self.__callbackFunction()
self.__root.after(1, self.__mainFunction)
pass
def activate(self):
self.__activated = True
self.__root.after(1, self.__mainFunction)
self.__root.mainloop()
def loadFile(self, fileName):
if self.__activated:
self.__sounds.append(tkSnack.Sound(load=fileName))
self.__numberOfSounds += 1
# return the index of the new sound
return self.__numberOfSounds - 1
else:
return -1
def play(self, soundIndex):
if self.__activated:
self.__sounds[soundIndex].play()
else:
return -1
Then, the application itself must be implemented in a class thus the main() is defined when handed over to the CSoundPlayer() constructor:
class CApplication:
def __init__(self):
self.__programCounter = -1
self.__SoundPlayer = CSoundPlayer(self.main)
self.__SoundPlayer.activate()
def main(self):
self.__programCounter += 1
if self.__programCounter == 0:
self.__sound1 = self.__SoundPlayer.loadFile("../mysong.mp3")
self.__SoundPlayer.play(self.__sound1)
# here the cyclic code starts:
print self.__programCounter
CApplication()
As you can see, the mainloop() is called not in the constructor but in the activate() method. This is because the CApplication won't ever get the reference to CSoundPlayer object because that stucks in the mainloop.
The code of the class CApplication itself does a lot of overhead. The actual "application code" is placed inside the CApplication.main() - code which shall be executed only once is controlled by means of the program counter.
Now I put it to the next level and place a polling process of the MIDI Device in the CApplication.main(). Thus I will use MIDI commands as trigger for playing sound files. I hope the performance is sufficient for appropriate latency.
Have you any suggestions for optimization?
You must start the event loop. Without the event loop, tkinter has no way to actually draw the window. Remove the while loop and replace it with mainloop:
from Tkinter import *
root = Tk()
root.mainloop()
If you need to do polling (as mentioned in the comments to the question), write a function that polls, and have that function run every periodically with after:
def poll():
<do the polling here>
# in 100ms, call the poll function again
root.after(100, poll)
The reason you don't need mainloop in the console depends on what you mean by "the console". In IDLE, and perhaps some other interactive interpreters, tkinter has a special mode when running interactively that doesn't require you call mainloop. In essence, the mainloop is the console input loop.
I am new to Python and Qt4 and am running into some problems with taking user entered/selected information and then using them as arguments for other python files. Here's the two situations & code:
Users enter an ID # into a lineEdit box, and on button click, program will run a script with the ID # entered as an argument. e.g., ID # = 11503, on button click: programname.py 11503 will run. This sort of works, but puts spaces between each number in the ID and I don't know if it's the cleanest way to do what I want.
ID # process button SIGNAL:
QtCore.QObject.connect(self.ui.pushButton_2, QtCore.SIGNAL('clicked()'), self.processID)
SIGNAL calls this:
def processID(self):
import subprocess
from PyQt4 import QtCore, QtGui
rawID = (self.ui.lineEdit.text())
idList = []
for x in rawID:
idList.append(str(x))
subprocess.call(["Python" "programname.py"] + idList, shell=True)
Pretty much the same situation as above, but one of the arguments needed for the "programname.py" script is a file directory. I have a comboBox populating with the names of the directories, but can't get it to take the selection and print it as an argument. Here's the code for that:
Combobox Directory population
import glob, os
myList = sorted(glob.glob('C:\\Python27\\test_directories\\*'))
new_myList = []
for x in myList:
new_myList.append(os.path.basename(x))
self.ui.comboBox_4.addItems(new_myList)
Directory combobox SIGNAL
self.ui.comboBox_4.activated[str].connect(self.Directory)
I connected a different button to the comboBox and tried to replicate what I did with the lineEdit, but it just won't work at all. However, I CAN print the user selection with the code below, so it's functional, just not in the way I want.
Directory comboBox selection print test
def Directory(self, item):
print(item)
Any help would be greatly appreciated. (And if you make it all the way through this long post, thank you!)
I'm doing my own tweak on the technique from this post, using a canvas vs. a text widget to get some finer control over the scrolling behavior. My code appears below.
All is working as I want it but for some reason the trace I'm using to track the checkbutton values isn't working. No errors of any kind show up in the console window. But I don't get the expected printed message (from _cbWasClicked) when I click one any of the checkbuttons. As best I can tell the method is just never invoked.
I know it's got to be a simple and obvious bug but I'm stumped. I've used print statements to confirm that the 100 IntVars get instantiated as expected. Then I deliberately misspelled the method name in the .trace and this time it generated an error. So when I yank those diagnostic tweaks all should be working.... it just isn't. Can someone tell me what I'm missing?
Environment is Python 2.7 on Windows 7.
import Tkinter as tk
class myCheckList(tk.Frame):
def __init__(self, root, *args, **kwargs):
tk.Frame.__init__(self, root, *args, **kwargs)
self.root = root
self.vsb = tk.Scrollbar(self, orient="vertical")
self.canvas = tk.Canvas(self, width=200, height=290,
relief=tk.GROOVE,bd=3,
scrollregion=(0,0,0,2020),
yscrollcommand=self.vsb.set,
yscrollincrement=20)
basecolor = self.canvas.cget('background')
self.vsb.config(command=self.canvas.yview)
self.canvas.grid(row=0,column=0,sticky=tk.NSEW,padx=(0,0),pady=0)
self.vsb.grid(row=0,column=1,sticky=tk.NS,padx=(0,0),pady=0)
for i in range(100):
cbv = tk.IntVar()
cbv.trace('w',self._cbWasClicked)
cb = tk.Checkbutton(self, background=basecolor,
variable=cbv,
text="Checkbutton #%s" % i)
self.canvas.create_window(5,20*i+5,anchor=tk.NW,window=cb)
self.canvas.bind_all('<MouseWheel>',
lambda event: self.canvas.yview_scroll(-1*event.delta/120, tk.UNITS))
def _cbWasClicked(self,*args):
print 'checkbox clicked'
if __name__ == "__main__":
root = tk.Tk()
myCheckList(root).grid(row=0,column=0,sticky=tk.W,padx=0,pady=0)
root.mainloop()
Found it, after much wrestling and experimenting. It turns out that the trace works perfectly when I add a couple of lines to the class's __init__:
self.status = []
...and then, inside the loop...
self.status.append((cb,cbv))
...which tells me that garbage collection is the culprit. By creating a list and storing the object references in it, they couldn't be garbage-collected and so the .trace remains effective.
First off, you should prepend self. to cbv and cb within the FOR cycle.
Secondly, even then it is going to work only for the very last checkbox, because with each iteration you overwrite the variable cbv again and again.
As a workaround I used a list of vaiables (self.li) generated one step before the cycle. This way you can link each checkbox to its own variable:
self.li = ['cbv' + str(i) for i in range(100)]
for i in range(100):
self.li[i] = tk.IntVar()
self.cb = tk.Checkbutton(self, background=basecolor,
variable=self.li[i],
text="Checkbutton #%s" % i)
self.li[i].trace('w', self._cbWasClicked)
self.canvas.create_window(5,20*i+5,anchor=tk.NW,window=self.cb)
...
This code worked fine for me.
You will then need to identify each checkbox somehow. You can do it using the internal variable name which is passed as the first param to the callback function in the trace method (What are the arguments to Tkinter variable trace method callbacks?):
def _cbWasClicked(self, name, *args):
print('checkbox %s clicked:' % name)
In the output you'll get something like this:
checkbox PY_VAR10 clicked:
checkbox PY_VAR99 clicked:
checkbox PY_VAR0 clicked: