How can I link Entry and Button widgets in Tkinter with classes? - python

Ï'm trying to make a simple code that can automatically perform a sequence of smaller tasks on the click of a button. There's some information that currently needs to be punched into several word and excel sheets and webpages at work; it's time consuming and boring. My idea has been to create a tkinter window that allows the user to enter the necessary information, which is subsequently stored in a .csv file. I made it work without the use of classes, but when researching Tkinter, I realised that it's good practise to use classes. Problem is, I can't make it work when I put the entry widget into a class. This code works, but doesn't apply class(es):
def newprojectinput():
task_number_s = task_number_e.get()
#code is written to .csv in this method
my_window = Tk()
generate_b = Button(my_window, text="Make new project", command=newprojectinput)
generate_b.grid(row=12,column=1)
task_number_e = Entry(my_window)
task_number_e.grid(row=0,column=1)
my_window.mainloop()
This doesn't work:
def newprojectinput():
task_number_s = task_number_e.get()
#code is written to .csv in this method
class Toplevel_new_project:
def __init__(self, top=None):
self.Frame1 = tk.Frame(top)
self.task_number_e = tk.Entry(self.Frame1)
self.generate_b = tk.Button(self.Frame1, command=newprojectinput)
I would appreciate it if someone could take the time to provide optimal code and maybe briefly explain why the previous code doesn't work.

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

How does one update a field from outside the __init__() function with pyqt5

I am reading a sensor and want to display its output as a decimal number in a GUI using PyQt5. I have found a number of tutorials that point out the label.setText('myStr') function. This does not work for my setup, however, because I need to update the field based on the input from another function. I'm not very familiar with PyQt5 yet, and I would appreciate any insight into how this problem ought to be approached.
Note: (I am using LCM to acquire data from a Raspberry Pi. I'm not sure that that is relevant to the problem, but it helps explain my code below.)
Here is what I am trying to do:
class Home_Win(QMainWindow):
def __init__(self):
QMainWindow.__init__(self)
loadUi("sensor_interface.ui", self)
self.label_temp.setText('temperature') #Just to verify that I can change it from here
def acquire_sensors(self):
temp = 0 #Make variable available outside nested function
def listen(channel, data):
msg=sensor_data.decode(data)
temp = msg.temperature
lc = lcm.LCM()
subscription = lc.subscribe("sensor_data_0", listen)
while True:
lc.handle()
self.label_temp.setText(str(temp))
Any thoughts on how I can update the GUI to display the readings I am getting from my sensors?
Thanks!
You're almost there. All you need to do is to save the ui in an instance variable in __init__:
self.ui = loadUi("sensor_interface.ui", self)
Then, assuming label_temp is the name of your QLabel widget, just do:
self.ui.label_temp.setText(str(temp))
It turned out that I needed to add repaint(). I also switched to a QLineEdit as this seemed to work better for me. So inside the while loop I now have:
self.ui.lineEdit_temp.setText(str(temp))
self.ui.lineEdit_temp.repaint()
This now outputs live updates to the GUI while reading the data stream.

how to send information to a define with a tkinter button

I'm trying to create a menu for a school project and wanted to make it so that it reads the files in a given directory and creates buttons on the screen based on what files are there. My main problem at the moment is that I can't get the Button to send the information i need.
I've tried to use the functions in a different way but this is the closest I can get to what I want.
class Menu(Frame):
def __init__(self, master=None):
#working out how big to make the window
global files_in_area
files_in_area = []
files_in_area = os.listdir(menu_folder_loc)
print files_in_area
a = len(files_in_area)
b = 3
rows = a / b
rows = rows + 1
window_height = rows * 56
root.geometry('%dx%d+%d+%d' % (450, window_height, 0, 0))
Frame.__init__(self, master)
self.master = master
self.init_menu()
def init_menu(self):
self.master.title("Menu")
self.pack(fill=BOTH, expand=1)
for i in range(0, len(files_in_area)):
#button placment
a = i
b = 3
row_no = a / b
row_no = row_no + 1
column_no = a % b
global file_name
file_name = str(files_in_area[i])
b1 = Button(self, text=file_name, bg= "red", height=3, width=20, command=self.client_print_file).grid(row=row_no, column=column_no, sticky=W)
def client_print_file(self):
print file_name
I've got the code to work when finding the files and putting them in a tkinter window, and when I click on the button, it should open the file (I'm just printing the file name at the moment to make sure it works). However it just prints the last file in the list.
Sorry if this question is a bit confusing, and thanks in advance.
This is not a problem about tkinter but about scopes and oop in python. I assume you got started with python by reading online stuff or got a fast introduction. My first advice is: do not use/copy from someone else's code something if you do not understand it. If you copy, try to dig in yourself and MAKE it your own code. One good way of reading up stuff is the very good online documentation of python.
Some exercises to get you started: 1) Research about python's scoping of variables.
2) More importantly: read up on classes, i.e. object oriented programming.
To the code: 1) Ditch the use of global alltogether. No need here. If you want to use the variable files_in_area in all methods of the class simply make it an attribute of the class, just like you did with master which you then go on to use in another method where it was not defined! Now to the variable filename: Again, ditch global. Then filename lives in the scope of the for loop which is exactly what you want here.
See it like this: if you had not used the variable filename at all but simply put text=str(files_in_area[i]) inside the Button-constructor as argument, you wouldnt have run into this problem here - at its core this really is a problem about thinking for yourself and not copying too much code from someone else - I know it because this was a big issue for me for long time.
The hardest thing here is how to pass a command depending on the i of the for loop to each button. You can find a lot here on stackoverflow. This again is about variables and their scope but also about their binding. In each loop pass, we want to generate a value of command depending on i. One way to use this is using the (at first mystical) lambda: command=lambda i=i: self.client_print_file(i). And define def client_print_file(self, i): ... with an additional argument i. The critical thing here is the binding. What would not work is command=lambda i: self.client_print_file(i).
(Another way instead of using lambda would be using partial from functools...)
Assignment: read up on lambda and what it is about and about name binding and variables in python :)

How to correctly use Control Variable in Tkinter?

I just started to look into tkinter to develop some simple GUI applications. However, I had a hard time figuring out how the control variables are used. I simply want to retrieve the status of a checkbutton(whether it is checked) and the text of an entry, but using the control variable seems not doing the job. I know my_entry.get() can also be used to get the text, but since most of the tutorials use control variables, I believe it is worth learning to use it correctly... Really appreciate your help!
here is the sample code I had:
import tkinter as tk
def checkbtn_callback():
print(btn_state.get())
print(entry_text.get())
top = tk.Tk()
btn_state = tk.IntVar()
checkbtn=tk.Checkbutton(top,text='testbutton',variable=btn_state,command=checkbtn_callback)
checkbtn.grid()
entry_text = tk.StringVar()
entry = tk.Entry(top, text='text entry',textvariable=entry_text)
entry.grid()
tk.mainloop()

tkinter and GUI programming methods

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

Categories