Bit of a beginner here :)
So for the past couple of days I've been working on a relatively simple web scraper, but with a special goal in mind. Namely, I wanted to get better with tkinter.
So, I've made a devlog window, which is simply going to be a separate console kind of thing. But the problem is that I want to insert text "live" in to the devlog window, and the text comes from a different script in a different thread. How could I implement this feature? This devlog window is ALSO run in a thread, as well as 3 other scripts along side it. This is the code I have so far:
import Manager as AM
import tkinter as tk
from time import sleep # For some simple timing algorithms
Width = 700
Height = 500
FrameGeometry = str(Width) + "x" + str(Height)
# COLORS
LightGrey = "#D9D9D9"
DarkGrey = "#AAAAAA"
# Global variables.
ThreadsList = list()
def CreateDevLogWindow():
global Width
global Height
ThreadTemp_ = str(AM.FileManager(ReadData=2))
devlog = tk.Tk() # Secondary Devlog Interface.
FrameInfo = tk.Frame(master=devlog)
FrameText = tk.Frame(master=devlog)
ThreadText = tk.Label(master=FrameInfo, text="Total Threads: " + ThreadTemp_, bg="white")
ThreadTextActive = tk.Label(master=FrameInfo, text="Active Threads: " + ThreadTemp_, bg="white")
InfoText = tk.Text(FrameText, border=1, highlightthickness=1)
devlog.title("Devlog: YourOptimalScrapper")
devlog.geometry(str(Width) + "x" + str(Height))
devlog.config(bg="white")
FrameInfo.config(bg="white")
FrameText.config(bg="white", padx=5, pady=5)
FrameInfo.pack(side=tk.TOP, fill=tk.BOTH)
FrameText.pack(side=tk.BOTTOM, expand=True, fill=tk.BOTH)
ThreadText.grid(row=0)
ThreadTextActive.grid(row=1)
InfoText.pack(expand=True, fill=tk.BOTH)
while True:
if ThreadTemp_ != AM.FileManager(ReadData=2):
ThreadTemp_ = str(AM.FileManager(ReadData=2))
ThreadText.config(text="Total Threads: " + ThreadTemp_)
ThreadTextActive.config(text="Active Threads: " + ThreadTemp_)
devlog.update()
Any and all help is appreciated, and NOTE: There is more code under this, but I don't think its particularly necessary :)
First of all you can insert text in your TextBox with just a few lines.
def update_textbox(textbox, data):
textbox.config(state=tk.NORMAL) # unlock the textbox
textbox.insert(tk.END, "\n"+str(data)) # add newline and append the data
textbox.config(state=tk.DISABLED) # lock back the textbox to readonly
textbox.see(tk.END) # scroll to the bottom to see the last line
With that if your script is in a different thread, then you can create a file that will be the "carrier" of your data.
Whenever you need to write data to the textbox, simply write data to the file with your text in any thread and update the while loop in the above code to read this file and update the textbox if the file isn't empty.
EDIT: You don't need to call this function outside of this thread. Just check in your while loop whether new data has to be added to the log
...
while True:
with open("carrier.txt", 'r') as f:
content = f.read() # read carrier data
if content: # if file isn't empty
update_textbox(InfoText, content) # update the textbox with the data in the file
open("carrier.txt", 'w').close() # clear the file to prevent reading the data twice
if ThreadTemp_ != AM.FileManager(ReadData=2):
ThreadTemp_ = str(AM.FileManager(ReadData=2))
ThreadText.config(text="Total Threads: " + ThreadTemp_)
ThreadTextActive.config(text="Active Threads: " + ThreadTemp_)
devlog.update()
And in your other thread
def update_log(data):
with open("carrier.txt", 'a') as f: # append to the file
f.write(str(data))
Related
I'm not a programmer so admittingly my title might be a bit off.
I have a program where I select an IFC-file (used in the construction industry, which contains data about the geometry and data connected to the objects in the 3D drawing) and then a dialog opens where I can add a GUID (an identifier) and search for all the information related to that object in the IFC-file, which I then print in the command prompt.
A minor issue which annoys me a bit is that the when is select the file in a more flexible way, using askopenfilename, the function seems to stay active or in a loop after I close the later opening dialog, so that I have to end the process using CTRL+C.
I post the entire code as I don't know if there is something else which causes it:
#imports to search for files
import os
# imports tkinter for selection purposes, so that one can select the files instead of reading in them automaticall from a directory
import tkinter as tk
from tkinter.filedialog import askopenfilename
#importing the counter to counte the occurences in a list
from collections import Counter
#importing regex functionality to check values
import re
#this is the numbering, for example #433, #4781 etc, that we either want to look for or change - now it looks for up to a number of length 7
regexIFCnumbers = '#\d{1,7}'
tk.Tk().withdraw() # we don't want a full GUI, so keep the root window from appearing
file1 = askopenfilename(initialdir=os.getcwd()) # show an "Open" dialog box and return the path to the selected file - this in the current directory where we'll start looking
file1Name = (os.path.basename(file1)) #only the file's name
#read files
def readFile(file):
x = []
f = open(file, 'r')
x = f.readlines()
f.close()
return(x)
x1 = readFile(file1)
#checks the GUIDs
def checkGUID(GUID, IFC):
A = []
for row in IFC:
if GUID in row:
#print(re.findall(regexIFCnumbers, row))
A.extend(re.findall(regexIFCnumbers, row))
return(A)
#the numbering is not perfectly ordered and varies in some places, so need to index it all to find the correct lines
def indexIFC(IFC):
A = []
for index, row in enumerate(IFC):
if re.findall('^#', row): #starts with a hash #
B = re.findall(r'\#\w+', row)[0]
A.append([B, index])
return(A)
def recurseThrough(inputList, IFC, checkedList, fullList, indexednumbersList):
for item in inputList:
for hashvalueList in indexednumbersList:
if item == hashvalueList[0]:
positionInIFC = hashvalueList[1]
if re.search('^'+item, IFC[positionInIFC]) and item not in checkedList: #if the row begins with the number item in the list
checkedList.append(item)
fullList.append(IFC[positionInIFC])
recurseThrough(re.findall(regexIFCnumbers, IFC[positionInIFC])[1:], IFC, checkedList, fullList, indexednumbersList) #recurses through the next list
return(fullList)
from os import system, name
def clear():
if name == 'nt':
_ = system('cls')
def runTheGUIDCheck(setValue):
inputValue = str(setValue)
print(inputValue)
clear()
try:
B1 = checkGUID(inputValue, x1) #This returns a list with for example [#122, #5, #7889]
checkedList = [] #the list of already checked items
fullList = []
indexedIFClist1 = indexIFC(x1)
#run the function with the initial input, sending in empty array as well as they should be empty/none at the start
outList1 = recurseThrough(B1, x1, [], [], indexedIFClist1)
for index, item in enumerate(outList1):
print(index, item.strip())
return None
except:
print("inserted GUID not found or didn't work")
#dialog
dialog = tk.Tk()
dialog.geometry('500x200')
t1 = tk.Label(dialog, text = 'Check GUID')
t1.grid(row = 0, column = 0, sticky = 'w')
testGUIDAttributes = tk.Entry(dialog, width = 40)
testGUIDAttributes.grid(row = 0, column = 1, columnspan = 50)
button = tk.Button(dialog, text='Run GUID', command = lambda: runTheGUIDCheck(testGUIDAttributes.get()))
button.grid(row = 5, column = 0, sticky = 'w')
dialog.mainloop()
If I select the file directly like this...
file1 = 'thefile.ifc'
instead of with the above this...
tk.Tk().withdraw() # we don't want a full GUI, so keep the root window from appearing
file1 = askopenfilename(initialdir=os.getcwd()) # show an "Open" dialog box and return the path to the selected file - this in the current directory where we'll start looking
then this issue doesn't come up and the command prompt will accept new commands after I close the dialog which is "created" at the end.
Since you have created two instances of Tk(): one in the line tk.Tk().withdraw() and one in the line dialog = tk.Tk(), so the line dialog.mainloop() will not return until both windows are closed. However you cannot close the withdrawn window because it is invisible.
You should create only one instance of Tk(), hide it and then show the file dialog. Then show it back and proceed as normal:
...
# create the main window and hide it initially
dialog = tk.Tk()
dialog.withdraw()
file1 = askopenfilename(initialdir=os.getcwd()) # show an "Open" dialog box and return the path to the selected file - this in the current directory where we'll start looking
...
# show back the main window
dialog.deiconify()
dialog.geometry('500x200')
...
dialog.mainloop()
I'm writing a script and I'd like to use Tkinter GUI (or any other module which could do this) to show how many times in total this script has been run, store the number in file and update GUI dynamically from this file. But each time I call label.mainloop() nothing past this line gets executed, it just freezes there.
import tkinter
import time
def tracker():
f = open('howmanytimes.txt', 'r')
thenumber = f.read()
f.close()
tracker = 'Script has run this many times: ' + (str(thenumber))
label = tkinter.Label(text=tracker, font=(
'Times', '20'), fg='red', bg='black')
label.master.overrideredirect(True)
label.master.geometry("+0+0")
label.pack()
label.master.wm_attributes("-topmost", True)
label.mainloop()
tracker() # <- Nothing past this line gets executed
time.sleep(2)
i = 0
while i < 20:
print('Script has been run')
time.sleep(3)
f = open('howmanytimes.txt', 'w+')
thenumber = f.read()
g = int(thenumber) + 1
f.write(str(g))
f.close()
tracker()
i+=1
Here's a way to implement what you want. I put all the tkinter related code into a class to better encapsulate what it does and allow it to be put into a separate thread. It uses the universal widget method after() to periodically "poll" the file for changes without interfering with tkinter's mainloop() — which is a very common way to do that sort of thing.
I also had to change your file handling quite a bit to get it working and handle all the possible cases of it not existing yet or being empty — as well as deleting it at the end so its existence can't affect a subsequent run of the same program.
import os
from threading import Lock, Thread
import tkinter as tk
import time
class Tracker:
def __init__(self):
self.root = tk.Tk()
self.root.overrideredirect(True)
self.root.geometry("+0+0")
self.root.wm_attributes("-topmost", True)
# Create empty label widget to be updated.
self.label = tk.Label(self.root, font=('Times', '20'), fg='red', bg='black')
self.label.pack()
self.update() # Start polling file.
self.root.mainloop()
def update(self):
try:
with open(FILE_PATH, 'r') as f:
thenumber = next(f)
except (FileNotFoundError, StopIteration):
thenumber = '0'
self.label.config(text=f'Script has run this many times: {thenumber}')
with running_lock:
if running:
self.label.after(1000, self.update)
else:
self.root.quit() # Stop mainloop.
FILE_PATH = 'howmanytimes.txt'
running_lock = Lock()
running = True
bkg_thread = Thread(target=Tracker)
bkg_thread.start()
for i in range(5):
try: # Create or update file.
with open(FILE_PATH, 'r+') as f:
thenumber = f.read()
try:
g = int(thenumber) + 1
except ValueError: # Empty file.
g = 1
f.seek(0) # Rewind.
f.write(str(g))
except FileNotFoundError:
with open(FILE_PATH, 'w') as f:
g = 1
f.write(str(g))
print('Script has been run')
time.sleep(3)
with running_lock:
running = False # Tell background thread to stop.
try:
os.remove(FILE_PATH) # Clean-up.
except Exception:
pass
I'm trying to build a program that will read in a list of files, add a prefix and a suffix to the filename, and then copy the file to a new folder with the new file name. So, for example, a file named "Reports.pdf" would become "PBC_Reports_V1.pdf"
The problem I have is when either the source or destination directory have a space in their name, the shutil module can't find the directory. I'm not sure what I need to do to have shutil recognize the directory, and could use some help.
code is as follows:
## This program is intended to allow a batch of files to have their name changed to standard IA formats
import os, shutil, datetime
from tkinter import *
strFolderName = "\Test"
#strNewPath = basePath+strFolderName
root = Tk()
root.title("Bulk File Renaming")
#root.iconbitmap(r"C:\Coding\FlagIcon.bmp")
############################################################################################################################################
## This section sets up the program's GUI. The buttons have to come after the modules, but this has to come at the beginning of the code
## because, of course, putting all the GUI stuff together would make too much sense. Oh well. GUI until the next line of hashtags.
strF = os.getcwd() #Finds the current file path
strFileIn = StringVar() #This is where we'll store the input path
strFileIn.set(strF) #Puts the current directory in the box
strFileOut = StringVar() #This variable is the output path
strFileOut.set(strF+r"\ConvertedFiles")
str1 = StringVar() #This variable holds the radiobutton output, which represents the file prefix
str1.set("\PBC_") #For some reason, if you try to set a variable in the same line you declare it, it stops working.
strPrefixes = [("PBC (Prepared by Client)", "\PBC_"), #A list (tuple?) of the different prefixes and their meaning.
("WP (Working Paper)", "\WP_"), #To be called futher down when we create the radiobuttons
("COM (Communications)", "\COM_"),
("MIN (Minutes)", "\MIN_"),
("PM (Project Management)", "\PM_"),
("DOC (Anything not created by IA or the OPI)", "\DOC_")]
inRow = 5
inCol = 0
for strPref, val in strPrefixes:
Radiobutton(root, text=strPref, variable=str1, value=val).grid(row =inRow, column=inCol, sticky=(W))
inCol=inCol+1
if inCol>2:
inCol=0
inRow=6
#First, labels
myLabelSource = Label(root, text="Source Folder").grid(row=0, column=0, sticky=(E))
myLabelDestn = Label(root, text="Destination Folder").grid(row=1, column=0, sticky=(E))
myLabelPrefix = Label(root, text="Select Desired Prefix").grid(row=3, column=0, sticky=(W))
#Next, input boxes
#Source files' location
enSource = Entry(root, textvariable=strFileIn).grid(row=0, column = 1, columnspan=3, sticky=(W,E))
#Destination files' location
enDestn = Entry(root, textvariable=strFileOut).grid(row=1, column = 1, columnspan =3, sticky=(W,E))
#################################################################################################################################################
#This module shuts down the program
def progExit():
root.quit() #Closes all the program's stuff
root.destroy() #Actually shuts things down.
#This module gets the date the file was last modified
def getDate(strA):
# Depending on how the file was created/modified, the creation date may be placed in the date modified field and vice versa.
# Here, we take both the modified and created date, see which one is earlier, format it in a YYYY-MM-DD format, and
# return it to the user.
strC = os.path.getctime(strA) #Gets the created date, as time since the epoch
strB = os.path.getmtime(strA) #Gets the modified date, as time since the epoch
if strB<strC:
strB = datetime.datetime.fromtimestamp(strB) #Converts the date into a readable string
strB = str(strB)
strB=strB[0:10] #Leaves just the YYYY-MM-DD fields
return strB
else:
strC = datetime.datetime.fromtimestamp(strC)
strC=str(strC)
strC=strC[0:10]
return strC
#This module determines the location of the '.' at the end of the filename, before the file type indicator.
def fileTypeLength(strName):
inI = len(strName)-1 #Since the first letter in the string is in the 0 position, we subtract 1 from the length to make the loop work
while inI>-1:
if strName[inI] == chr(46): #The '.' char has an ascii value of 46. It's easier for python to understand what we're looking for this way.
return inI
else:
inI=inI-1 #We go from the end towards the front so that if there are any '.' in the file name it won't screw up the program
if inI<0:
outputLabel=Label(root, text="Error finding file suffix for file "+strName).grid(row=8, column = 1)
root.quit()
#This module is where the program actually copies the file, and pastes together everything else.
def copyAllFiles():
inA=0
strPrefix=str1.get()
strNewPath=os.path.abspath(strFileOut.get())
strEntry=os.path.abspath(strFileIn.get())
listOfFiles = os.listdir(strFileIn.get())
if os.path.isdir(strNewPath) == False:
os.mkdir(strNewPath)
for entry in listOfFiles:
if os.path.isfile(os.path.join(strEntry, entry)): # Verify that file in question is a file, not a folder
entryEd=entry # entryEd holds the file name
entry=(os.path.join(strEntry, entry)) # entry holds the file location
strDate = getDate(entry) # Gets the file's creation date
inL = len(entryEd)
inS = fileTypeLength(entryEd) #Finds the position of the '.' at the end of the file name
strFT = entryEd[inS:inL] #Here we remove the file type idenifier, so that it can be moved to the end of the copied filename
entryEd=entryEd[0:inS]
strOutputFile = strNewPath+strPrefix+entryEd+"_"+str(strDate)+"_V1"+str(strFT)
shutil.copy2(entry, strOutputFile) #This copies the file, and all its attributes
inA = inA+1
outputLabel=Label(root, text=str(inA)+" files successfully processed").grid(row=8, column = 1)
##### These are the buttons that the GUI uses to launch the program or shut it down.
buttonExit=Button(root, text="Click here to exit", padx=50, command=progExit).grid(row=7, column=2)
buttonProcess=Button(root,text="Rename Files", padx=50, command=copyAllFiles).grid(row=7, column=1)
#The last thing we have to do is name the GUI that we started building on line 9. Tkinter is a funny module.
root.mainloop()
I can verify that the variables have two slashes for all the folder dividers, so `strOutputFile = C:\\Example Folder\\Example Output\\PBC_Reports_V1.pdf' .
Anybody have a suggestion?
So i am trying to write a Stroop experiment from scratch.
Ideally this is how i could like the experiment to be set up:
Enter participant information
Instruction pages (click x to continue)
Instruction page 2 (click x to continue)
Experiment Start
Break between trial
Experiment trial 2
End
(there will be more than 2 trials but for testing just 2 will be used)
I'm having difficulty writing the data to a text file. The second trial records perfectly with the different values per each loop. However the first trial shows up as duplicates and each trial has the same values in the text file.
In addition, i can't figure out how to write the data from the pop-up into my text file. (ie. subject name, age, id)
Also is there a way I can input the file name each time? Without changing code? -perhaps like a popup to choose the path and file name?
Thank you!
from psychopy import visual, core
import random
import time
import datetime
import sys
from psychopy import gui
from psychopy import event
#Write to file, need to figure out how to choose file name in each instance
file = open ("Test Output.txt", 'w')
#Pop up subject information - need to figure out how to output this data
myDlg = gui.Dlg(title="TEST TEXT BOX")
myDlg.addText('Subject info')
myDlg.addField('Name:')
myDlg.addField('Age:', )
myDlg.addText('Experiment Info')
myDlg.addField('Subject ID', "#" )
myDlg.addField('Group:', choices=["Test", "Control"])
ok_data = myDlg.show()
if myDlg.OK:
print(ok_data)
else:
print('user cancelled')
#opens up window w/ text,
win = visual.Window([800,800],monitor="testmonitor", units="deg")
msg = visual.TextStim(win, text="Hello")
msg.draw()
win.flip()
event.waitKeys(maxWait=10, keyList=None, timeStamped=False) #page remains until keyboard input, or max of 10 seconds
#with keyboard input, second screen will come up
msg = visual.TextStim(win, text="Instructions 1")
msg.draw()
win.flip()
event.waitKeys(maxWait=10, keyList=None, timeStamped=False)
#3rd screen will pop up with keyboard input
msg = visual.TextStim(win, text="Trial 1")
msg.draw()
win.flip()
event.waitKeys(maxWait=10, keyList=None, timeStamped=False)
#Trial starts,
for frameN in range(5):
MyColor = random.choice(['red','blue','green','yellow'])
Phrase = random.choice(["Red","Green", "Blue", "Yellow"])
time = str(datetime.datetime.now())
key = str(event.getKeys(keyList=['1','2','3','4','5'], ))
pause = random.randint(1200,2200)/1000.0
length = str(pause)
msg = visual.TextStim(win, text=Phrase,pos=[0,+1],color=MyColor)
msg.draw()
win.flip()
core.wait(pause)
msg = visual.TextStim(win, text="Break between trial")
msg.draw()
win.flip()
event.waitKeys(maxWait=10, keyList=None, timeStamped=False)
#trial 2
for frameN in range(5):
MyColor2 = random.choice(['red','blue','green','yellow'])
Phrase2 = random.choice(["Red","Green", "Blue", "Yellow"])
time2 = str(datetime.datetime.now())
key2 = str(event.getKeys(keyList=['1','2','3','4','5'], ))
pause2 = random.randint(1200,2200)/1000.0
length2 = str(pause2)
msg = visual.TextStim(win, text=Phrase2,pos=[0,+1],color=MyColor2)
msg.draw()
win.flip()
core.wait(pause2)
#specifying which data will be recorded into the file
data = "Stimuli:"+ MyColor + ',' + Phrase + ','+ time + ',' + key + ',' + length + MyColor2 + ',' + Phrase2 + ','+ time2 + ',' + key2 + ',' + length2
file.write(data + '\n')
#Jessica's Code.
You should really consider using the TrialHandler and/or ExperimentHandler classes that are built into PsychoPy: they have solved this (and many more issues) for you already. You don't need to re-invent the wheel.
i.e. define the trial parameters (in your case, colours and phrases) and feed them to the TrialHandler when it is created. It will then automatically cycle through each trial (in sequence or randomly, as required), and handle saving the data for you in structured files automatically. Data gathered from the experiment info dialog is saved with the data, as the dictionary of info gathered from the dialog can be passed as the extraInfo parameter when a TrialHandler or ExperimentHandler is created.
The PsychoPy data API is here: http://www.psychopy.org/api/data.html and there are examples of using the TrialHandler and ExperimentHandler under the Demos → exp control menu. Or examine any simple Builder-generated code for an experiment which contains a loop. For example, the Builder Stroop demo ;-) Builder code is quite verbose, but just look at the part where the Trial/Experiment handlers are created and how the experimental loop is controlled.
Have you considered using command line arguments? This would let you pass in file names at the start of your script. For example:
python myscript.py InputFile1 InputFile2 OutputFile1 OutputFile2
There is a really nice module that does a lot of the heavy lifting for you called argparse:
https://docs.python.org/3/library/argparse.html
Here is a tutorial that the docs provide if you are a little intimidated:
https://docs.python.org/3/howto/argparse.html
If you need Python 2 documentation, you can just change the 3 into a 2 in the URL. Here is a little code sample to show you what you can do with it as well:
import argparse
ap = argparse.ArgumentParser()
ap.add_argument("-i", "--input", required = True, help = "Path to input file")
ap.add_argument("-o", "--output", required = True, help = "Path to output file")
args = vars(ap.parse_args())
print(args["input"])
print(args["output"])
You then can call this from your terminal to pass your file locations (or whatever else you want to pass):
python myscript.py -i File1.txt -o File2.txt
You will then get the following output from the two print statements in the code above:
File1.txt
File2.txt
So you can now use args["input"] and args["output"] to tell your program where it needs to get its input and output from without directly putting it in your code.
I've been playing around with this quite a bit and I feel like I'm making a mess of it.
I need an interface where if someone enters a word in the text field, it'll print any lines within a .txt file containing that word in the box next to it.
Here's what I have so far:
import Tkinter as tk
from Tkinter import *
from scipy import *
import math
def cbc(id, tex):
return lambda : callback(id, tex)
def callback(id, tex):
t = Search(id)
tex.insert(tk.END, t)
tex.see(tk.END)
#def retrieve_input():
# input = self.entn.get("0.0",END)
def Search(id):
with open("file.txt", "r") as s:
searchlines = s.readlines()
for i, line in enumerate(searchlines):
if entn.get() in line:
for line in searchlines[i:i+1]: print line,
print
top = tk.Tk()
tex = tk.Text(master=top)
tex.pack(side=tk.RIGHT)
bop = tk.Frame()
bop.pack(side=tk.LEFT)
entn = tk.Entry()
entn.pack(side=tk.TOP, fill=Y)
tb = "Search"
b = tk.Button(bop, text=tb, command=cbc(id, tex))
b.pack()
tk.Button(bop, text='Exit', command=top.destroy).pack()
top.mainloop()
I'm pretty new to this, as you can probably tell - so any help will be really appreciated.
Your callback is inserting the result of Search(id) but that function isn't returning anything. You need to add a return statement in that function to return the strings you want to show in the text widget.
A better way might be to pass in a reference to tne text widget to Search so that you can directly insert each match as you find them.