How to retrieve value from widgets put in a fixed container Gtk - python

Whenever i retrieve values from combobox / entry i always get the first value / returns null.
I can't seem to find a workaround. I think the problem seems to lie in using a fixed container? The only solution i can think of is to replace it with a grid layout but that means i will have to redo my entire code from the start.
creating the fixed container
class MainWindow(Gtk.Window):
def __init__(self): #constructor
Gtk.Window.__init__(self, title="dawadaw")
self.set_size_request(900, 700)
container = Gtk.Fixed()
self.add(container)
# Stack
main_area = Gtk.Stack()
#stackwindow1
sw1 = Gtk.Fixed()
Combobox and entry
list = Gtk.ComboBoxText()
list.append_text("string1")
list.append_text("string2")
list.append_text("string3")
list.set_active(0)
sw1.put(list, 180, 167)
Entry1 = Gtk.Entry()
Entry1.set_width_chars(10)
sw1.put(Entry1, 180, 297)
button to retrieve data
button = Gtk.Button(label="retrieve values")
button.connect("clicked", self.button_clicked,
Entry1.get_text(), sw1.get_active_text())
sw1.put(button, 370, 600)
main_area.add_titled(sw1, "Stack", "stack1")
Button_Clicked funtcion
def button_clicked(self, button, *data):
print(data)
I want to print the selected combobox value and the value i entered in the entry box. However, the combobox only returns the string1, regardless of what i select and the entry widget does not return anything.
I'm stuck and i think the problem lies with me using a fixed container. But i don't want to redo everything if i don't have to.
Help is greatly appreciated!

Related

How to dynamically control multiple Listboxes simultaneously in tkinter?

So, I have 5 listboxes in which I need to control at the same time, almost as if they were one listbox with columns.
I am trying to find a way in which when I select an item from any one of the listboxes and delete them, it will highlight and delete the other items in the corresponding index.
so far I am only able to delete the other indexed items only when I invoke curselection() on Listbox1, but if a user selects an item on listbox2 and calls the same, it'll throw an error because the variable is looking for listbox1.
I can't seem to find any documentation or examples of how to control multiple listboxes simultaneously anywhere.
Is it possible to have a self.listbox[0, 1, 2, 3].curselection() type of thing? or even an if statement that allows me to check if self.listbox1.curselection() == True: and then execute according.
This is the function anyway:
def removeSeq(self, event=None):
index = self.listbox1.curselection()[0]
print(index)
## self.listbox1.selection_set(1)
## selectedItem = self.listbox2.curselection()
## print(selectedItem)
## self.listbox1.delete(selectedItem)
## self.listbox2.delete(selectedItem)
## self.listbox3.delete(selectedItem)
## self.listbox4.delete(selectedItem)
## self.listbox5.delete(selectedItem)
pass
I've commented most of it out for test purposes, any help would be massively appreciated.
In your binding you can use event.widget to know which widget was clicked on. Then it's just a matter of getting the selection from that widget and applying it to the other listboxes.
Here's a simple example. To delete a row, double-click in any listbox:
import tkinter as tk
class MultiListbox(tk.Frame):
def __init__(self, parent):
super().__init__(parent)
for i in range(5):
lb = tk.Listbox(self, height=10, exportselection=False)
lb.pack(side="left", fill="y")
for j in range(10):
lb.insert("end", f"Listbox {i+1} value {j+1}")
lb.bind("<Double-1>", self.removeSeq)
def removeSeq(self, event):
lb = event.widget
curselection = lb.curselection()
index = curselection[0] if curselection else None
for listbox in self.winfo_children():
listbox.delete(index)
root = tk.Tk()
mlb = MultiListbox(root)
mlb.pack(side="top", fill="both", expand=True)
root.mainloop()

Updating Entry and Label widget in Tkinter

i have defined a GUI class that creates a tkinter window with a couple of entries. I would like that every time that the user overwrites the Entries and press Enter, there is some operation done in the background. In addition, i would like that the entries are checking regularly certain values and updating them, so the user could see if they changed; In the example below i use a static dictionary, but normally those parameters are obtained from a camera and could fluctuate.
However, i am not even able to get the correct value printed in the label. I am not a tkinter expert so any idea would be appreciated
from tkinter import *
class GUI():
def __init__(self, window, window_title,input_dict):
self.window = window
self.window.title(window_title)
self.window.geometry('400x200')
top_frame = Frame(self.window)
top_frame.pack(side=TOP, pady=5)
Label(top_frame, text="Frame rate (fps)").grid(row=0)
Label(top_frame, text="Exposure time (ms)").grid(row=2)
self.labeling=Label(top_frame, text="Result").grid(row=3)
self.e1_var = StringVar() # or StringVar(top)
self.e1_var.set(str(round(input_dict['frameRate'])))
self.e2_var = StringVar() # or StringVar(top)
# print(type(self.e2_var))
self.e2_var.set(str(round(input_dict['Exp_time'])))
self.fps_entry = Entry(top_frame,textvariable=self.e1_var)
self.exptime_entry = Entry(top_frame,textvariable=self.e2_var)
self.fps_entry.bind("<Return>",self.my_tracer)
self.exptime_entry.bind("<Return>",self.my_tracer)
self.fps_entry.grid(row=0, column=1)
self.exptime_entry.grid(row=2, column=1)
self.window.mainloop()
def my_tracer(self,event):
val1=int(self.e1_var.get())
val2=int(self.e2_var.get())
self.labeling.configure(text=str(val1*val2))
input_dict = {
'frameRate': 50,
'Exp_time': 5000}
video_object=GUI(Tk(),"Test",input_dict)
The error your code produces is AttributeError: 'NoneType' object has no attribute 'configure', right?
Look at this line:
self.labeling=Label(top_frame, text="Result").grid(row=3)
self.labeling will be None because grid() returns None. It is indeed bad practice to 'chain' a geometry manager to the creation of a widget. Change to:
self.labeling=Label(top_frame, text="Result")
self.labeling.grid(row=3)
Now the labels are updating when the user enters a new value.

Multi-Line Combobox in Tkinter

Is it possible to have a multi-line text entry field with drop down options?
I currently have a GUI with a multi-line Text widget where the user writes some comments, but I would like to have some pre-set options for these comments that the user can hit a drop-down button to select from.
As far as I can tell, the Combobox widget does not allow changing the height of the text-entry field, so it is effectively limited to one line (expanding the width arbitrarily is not an option). Therefore, what I think I need to do is sub-class the Text widget and somehow add functionality for a drop down to show these (potentially truncated) pre-set options.
I foresee a number of challenges with this route, and wanted to make sure I'm not missing anything obvious with the existing built-in widgets that could do what I need.
Terry's feedback made it clear that there was no simple way to solve this, so I created a custom class which wraps a Text and a Button into a frame, with a Toplevel containing a Listbox spawned by the button's callback function. I added a couple of "nice-to-have" features, like option highlighting within the Listbox, and I mapped bindings of the main widget onto the internal Text widget to make it easier to work with. Please leave a comment if there's any glaring bad practices here; I'm definitely still pretty inexperienced! But I hope this helps anybody else who's looking for a multi-line combobox!
class ComboText(tk.Frame):
def __init__(self, parent=None, **kwargs):
super().__init__(parent)
self.parent = parent
self._job = None
self.data = []
self['background'] = 'white'
self.text = tk.Text(self, **kwargs)
self.text.pack(side=tk.LEFT, expand=tk.YES, fill='x')
symbol = u"\u25BC"
self.button = tk.Button(self,width = 2,text=symbol, background='white',relief = 'flat', command = self.showOptions)
self.button.pack(side=tk.RIGHT)
#pass bindings from parent frame widget to the inner Text widget
#This is so you can bind to the main ComboText and have those bindings
#apply to things done within the Text widget.
#This could also be applied to the inner button widget, but since
#ComboText is intended to behave "like" a Text widget, I didn't do that
bindtags = list(self.text.bindtags())
bindtags.insert(0,self)
self.text.bindtags(tuple(bindtags))
def showOptions(self):
#Get the coordinates of the parent Frame, and the dimensions of the Text widget
x,y,width,height = [self.winfo_rootx(), self.winfo_rooty(), self.text.winfo_width(), self.text.winfo_height()]
self.toplevel = tk.Toplevel()
self.toplevel.overrideredirect(True) #Use this to get rid of the menubar
self.listbox = tk.Listbox(self.toplevel,width=width, height =len(self.data))
self.listbox.pack()
#Populate the options in the listbox based on self.data
for s in self.data:
self.listbox.insert(tk.END,s)
#Position the Toplevel so that it aligns well with the Text widget
list_height = self.listbox.winfo_reqheight()
self.toplevel.geometry("%dx%d+%d+%d" % (width, list_height, x, y+height))
self.listbox.focus_force()
self.listbox.bind("<Enter>", self.ListboxHighlight)
self.listbox.bind("<Leave>",self.stopListboxHighlight)
self.listbox.bind("<Button-1>",self.selectOption)
self.toplevel.bind("<Escape>", self.onCancel)
self.toplevel.bind("<FocusOut>", self.onCancel)
def ListboxHighlight(self,*ignore):
#While the mouse is moving within the listbox,
#Highlight the option the mouse is over
x,y = self.toplevel.winfo_pointerxy()
widget = self.toplevel.winfo_containing(x,y)
idx = self.listbox.index("#%s,%s" % (x-self.listbox.winfo_rootx(),y-self.listbox.winfo_rooty()))
self.listbox.selection_clear(0,100) #very sloppy "Clear all"
self.listbox.selection_set(idx)
self.listbox.activate(idx)
self._job = self.after(25,self.ListboxHighlight)
def stopListboxHighlight(self,*ignore):
#Stop the recurring highlight function.
if self._job:
self.after_cancel(self._job)
self._job = None
def onCancel(self,*ignore):
#Stop callback function to avoid error once listbox destroyed.
self.stopListboxHighlight()
#Destroy the popup Toplevel
self.toplevel.destroy()
def selectOption(self,event):
x,y = [event.x,event.y]
idx = self.listbox.index("#%s,%s" % (x,y))
if self.data:
self.text.delete('1.0','end')
self.text.insert('end',self.data[idx])
self.stopListboxHighlight()
self.toplevel.destroy()
self.text.focus_force()
def setOptions(self,optionList):
self.data = optionList
#Map the Text methods onto the ComboText class so that
#the ComboText can be treated like a regular Text widget
#with some other options added in.
#This was necessary because ComboText is a subclass of Frame, not Text
def __getattr__(self,name):
def textMethod(*args, **kwargs):
return getattr(self.text,name)(*args, **kwargs)
return textMethod
if __name__ == '__main__':
root = tk.Tk()
ct = ComboText(root, width = 50, height = 3)
ct.pack()
ct.setOptions(['Option %d' % i for i in range (0,5)])
root.mainloop()
I don't think you are missing anything. Note that ttk.Combobox is a composite widget. It subclasses ttk.Entry and has ttk.Listbox attached.
To make multiline equivalent, subclass Text. as you suggested. Perhaps call it ComboText. Attach either a frame with multiple read-only Texts, or a Text with multiple entries, each with a separate tag. Pick a method to open the combotext and methods to close it, with or without copying a selection into the main text. Write up an initial doc describing how to operate the thing.

Remove the highlight from combobox in readonly mode (python)

I would like to remove the highlighting from my combobox widget when the user selects an option. But the method select_clear() doesn't seem to be working :(
Any clues why ?
from tkinter import *
from tkinter.ttk import Combobox
class hey(Frame):
def __init__(self):
Frame.__init__(self)
self.comboboxVariable = StringVar()
values = (1, 2, 3, 4, 5)
self.comboBox = Combobox(self, textvariable = self.comboboxVariable, values = values, state = 'readonly')
self.comboBox.pack()
self.comboBox.bind("<<ComboboxSelected>>", self.updateData)
def updateData(self, event =None):
self.comboBox.select_clear()
myClass = hey()
myClass.pack()
root = myClass.master
root.mainloop()
What you are trying to do and what your script is attempting to do are two different things. First, you have event = None, which causes the function to be called when you first initialize an object and then never again. Instead, leave the function attribute just as 'event' so that it runs when the specified event takes place.
To remove the selection made, your updateData function would look like the following:
def updateData(self, event):
self.comboBox.set("")
However, to remove the highlighting from the chosen value, you would need to change the focus from the combobox to the root window:
def updateData(self, event):
self.focus_set()
Hope this helps.

Tkinter: Integers as labels overwrite

I am creating labels in a for loop that display integers every time I fire an event (a mouse click) on my application. The problem is that old labels don't get erased and the new ones come on top of them causing a big mess.
Here is the working code that you can try out:
import numpy as np
import Tkinter as tk
class Plot(object):
def __init__(self, win):
self.win = win
self.bu1 = tk.Button(win,text='Load',command=self.populate,fg='red').grid(row=0,column=0)
self.listbox = tk.Listbox(win, height=5, width=5)
self.listbox.grid(row=1,column=0)#, rowspan=10, columnspan=2)
self.listbox.bind("<Button-1>", self.print_area)
def populate(self):
"""Populate listbox and labels"""
self.time = [1,2,3]
self.samples = ['a','b','c']
for item in self.time:
self.listbox.insert(tk.END,item)
for i,v in enumerate(self.samples):
tk.Label(self.win, text=v).grid(row=2+i,column=0,sticky=tk.W)
self.lbl_areas = []
for i in range(0, len(self.samples)):
self.lbl=tk.IntVar()
self.lbl.set(0)
self.lbl_areas.append(tk.Label(self.win,textvariable=self.lbl).grid(row=2+i,column=1,sticky=tk.W))
def print_area(self, event):
"""Prints the values"""
widget = event.widget
selection=widget.curselection()
value = widget.get(selection[0])
#Here is the dictionary that maps time with values
self.d = {1:[('a',33464.1),('b',43.5),('c',64.3)],
2:[('a',5.1),('b',3457575.5),('c',25.3)],
3:[('a',12.1),('b',13.5),('c',15373.3)]}
lbl_val = []
for i in range(0, len(self.samples)):
lbl_val.append(self.d[value][i][1])
for i in range(0, len(self.samples)):
self.lbl=tk.IntVar()
self.lbl.set(lbl_val[i])
tk.Label(self.win,textvariable=self.lbl).grid(row=2+i,column=1,sticky=tk.W)
def main():
root = tk.Tk()
app = Plot(root)
tk.mainloop()
if __name__ == '__main__':
main()
If You try to run this code and click on LOAD you will see the numbers appearing in the listbox and labels a,b,c with values set to zero at the beginning. If you click on the number in the listbox the values (mapped into the dictionary d) will appear but you will see the overwrite problem. How can I fix that?
How can I overcome this problem? Thank you
Don't create new labels. Create the labels once and then update them on mouse clicks using the configure method of the labels.
OR, before creating new labels delete the old labels.If you design your app so that all of these temporary labels are in a single frame you can delete and recreate the frame, and all of the labels in the frame will automatically get deleted. In either case (destroying the frame or destroying the individual labels) you would call the destroy method on the widget you want to destroy.

Categories

Resources