Python Tkinter - Multiple entry box with same validation - python

I have created around 5 Entry boxes and binded them as well. Take this as model:
def makeEntry(self, master, data):
self.entry = Entry(master, width=8, textvariable=data)
self.entry.bind("<Leave>", lambda event, value=data: self.validate(event, value))
Now, I did also a validate method that check if the input was a string (and if so, the highlight background of the entry would change to red). The problem which is still taking me a lot of time is that I would need that the method should be able to check every entries, and if at least one of them has got a red background, then a final button should be disabled (button.configure(state=DISABLED)).
With just one entry it would be much easier, I would simply check if the background was red (status = str(self.myOneEntry.cget("highlightbackground"))), but what about with more entries?

If you want to check all of your entries, keep them in a list. Then, write a function that iterates over the list and sets the button state to disabled if any widget has a red background. You can then call this whenever something changes, such as within your validation function for each widget.
Example:
class Example(...):
def __init__(...):
self.entries = []
for data in ("one","two","three"):
entry = makeEntry(...)
self.entries.append(entry)
def _update_button(self):
for entry in self.entries:
if entry.cget("background") == "red":
self.button.configure(state="disabled")
return
self.button.configure(state="normal")
Note: you'll need to make sure that makeEntry(...) returns a reference to the created widget.
Also, you don't have to use makeEntry. You can create your widgets however you want. The point is to save references to the widgets in a data structure that you can iterate over. It doesn't matter how the widgets are created.

Related

Creating distinct Checkboxes in a For Loop that can be referenced separately

I'm creating a show tracker, and on the actual tracker page I want to load a scrolling ScrolledText box with a checkbox for each episode retrieved (I have chosen a ScrolledText box as it allows me to scroll if the amount of episodes fills over the screen). The code below does this perfectly, however each checkbox is referred to as the same variable, and thus every time I click one episode number, it selects all of them.
for alignment in range(1,int(showDisplay_episodesAmount)+1):
# create a radio button
self.showDisplay.episodeCheckbox = Checkbutton(self.showDisplay.episodesScrollBox, text=alignment,variable=var1, onvalue=1, offvalue=0)
self.showDisplay.episodeCheckbox.grid(column=0, row=grid_column)
self.showDisplay.episodesScrollBox.window_create('end', window=self.showDisplay.episodeCheckbox)
self.showDisplay.episodesScrollBox.insert('end', '\n')
grid_column += 1
I would like each checkbox to correspond to a separate episode number (such as checkbox 1 referring to episode 1, etc.), created in a for loop, as I cannot predetermine the amount of episodes for a wide variety of shows.I have tried the list approach, making this code, which both doesn't create separate checkboxes, and disables the scrollbar:
for alignment in range(1,int(showDisplay_episodesAmount)+1):
# create a radio button
self.showDisplay.episodeCheckbox[alignment] = {}
self.showDisplay.episodeCheckbox[alignment]['name'] = alignment
self.showDisplay.episodeCheckbox[alignment]['checkbox'] = Checkbutton(self.showDisplay.episodesScrollBox, text=alignment,variable=var1, onvalue=1, offvalue=0).grid(column=0, row=grid_column)
#self.showDisplay.episodesScrollBox.window_create('end', window=self.showDisplay.episodeCheckbox[alignment])
self.showDisplay.episodesScrollBox.insert('end', '\n')
grid_column += 1
How could I make each checkbox generated distinct and not linked to each other?
You can use the simple version of Jhonatan but you can also do something more sophisticated like creating a dictionary in which the key is the progressive counter of episode (your alignment) and the value is the checkboxed itself, from which you can access the state.
I don't use plain tk checkboxes but rather ttk, so here it's an example with ttk:
from tkinter import ttk
checkboxes_dictionary={}
for alignment in range(1,int(showDisplay_episodesAmount)+1):
checkboxes_dictionary[alignment]=ttk.Checkbutton(self.showDisplay.episodesScrollBox, text=alignment)
checkboxes_dictionary[alignment].pack()
Here I'm assuming you are interested in only showing the state of the checkboxes so I omitted the var.
You can access the state of your checkboxes with this line:
checkboxes_dictionary[my_episode].state()
Where of course my_episode is an integer between 0 and showDisplay_episodesAmount. You can also sweep this dictionary to print the status of all the episodes (checked / unchecked).
Final note, in ttk the return of the state() method is not binary, rather is a string that has many values ("selected", "focus" and many others, and two or more of them can coexist so the output will be a list of these string. Look at the documentation
I haven't check my code so please review it
I think you can use a list instead of the variable.
Then you can set each episode to one item in the list. like this:
listofvar = []
for alignment in range(1,int(showDisplay_episodesAmount)+1):
listosvar.append(tk.StringVar())
# create a radio button
self.showDisplay.episodeCheckbox = Checkbutton(self.showDisplay.episodesScrollBox, text=alignment,variable=listofvar[alignment-1], onvalue=1, offvalue=0)
self.showDisplay.episodeCheckbox.grid(column=0, row=grid_column)
self.showDisplay.episodesScrollBox.window_create('end', window=self.showDisplay.episodeCheckbox)
self.showDisplay.episodesScrollBox.insert('end', '\n')
grid_column += 1
Now each checkbox will define one item in the list ...
Hope I helped.
Jonathan

Tkinter optionmenu won't allow me to pass the frame of the object I want to update depending on the choice made

I have a list of frames that each have an optionmenu with the same list of choices. When a choice is made in that specific optionmenu, I've only been able to get the last entry widget to change, not the corresponding one. In other widgets I've been able to use something like "lambda F=F:function(args)" but that isn't working here.
I've tried a trace on the variable in the option menu, I've tried wrapper functions, I've tried every combination of a lambda in the command section of the optionmenu widget. Most approaches create errors, some, like the one attached, modify the bottom frame/entry but not the correct corresponding one.
This doesn't seem like it should be too hard. If the option for the top frame selected is "Continuous" or "Discrete", the entry next to it should be 'normal' state with "?..?" in the box, if it is categorical, it should change to be 'disabled' with no contents. I could do this easily if I could somehow pass the Frame dictionary key to the "updateOnChange" function, but I can't, it only allows a single argument to be passed and that is the string value of mType[F].
from tkinter import *
def updateOnChange(type):
print(type)
if type.upper()=='CATEGORICAL':
rangeEntry[F].delete(0,END)
rangeEntry[F].config(state='disabled')
print("runCat")
else:
rangeEntry[F].config(state='normal')
rangeEntry[F].delete(0,END)
rangeEntry[F].insert(0,'?..?')
print("runCont")
mType={}
frame={}
om={}
rangeEntry={}
root=Tk()
Frames=['FrameOne','FrameTwo']
miningTypes=['Continuous','Categorical','Discrete']
for F in Frames:
mType[F]=StringVar(root)
if F=='FrameOne':
mType[F].set("Continuous")
else:
mType[F].set("Categorical")
frame[F]=Frame(root,borderwidth=3,relief=SUNKEN)
frame[F].pack(side=TOP,fill=X)
rangeEntry[F]=Entry(frame[F],width=20,font=("Arial",12))
om[F]=OptionMenu(frame[F],mType[F],*miningTypes,command=updateOnChange)
om[F].pack(side=LEFT)
rangeEntry[F].pack(side=LEFT)
mainloop()
``
Your updateOnChange function hard-coded the entry to be changed as rangeEntry[F], which points to the last Entry widget created in your for loop. To properly associate each entry, you should pass the widget as a parameter:
def updateOnChange(type, entry):
if type.upper()=='CATEGORICAL':
entry.delete(0,END)
entry.config(state='disabled')
print("runCat")
else:
entry.config(state='normal')
entry.delete(0,END)
entry.insert(0,'?..?')
print("runCont")
And then pass the parameter in your command:
om[F]= OptionMenu(frame[F],mType[F],*miningTypes,command=lambda e, i=rangeEntry[F]: updateOnChange(e, i))

PyQt: removing custom widgets from a QListWidget

I am loading in a picture into a QGraphicsScene then when you click on the photo it will place circles in the area that is clicked. Then I am adding a custom widget that will keep a list of the points. This widget has a couple of buttons. one of them will be able to move the circle and the other will be to delete it. I am currently stuck on the delete part.
Problem
I can delete the circle just fine and I can delete the widget from the list. The problem is there is still a space in the list from where the widget once was and since I'm using the button from the widget and not selecting the item I'm not sure how to delete that spot. Also if I delete a bunch and then try and add some Python its self will crash and I have no idea why.
I'm not sure if what I want can be done since there really is no reference or if I'm better off moving the buttons to the main window and removing them from the custom widget. If it is possible I would like to keep it the way I have it.
There are a few files so I will put it on GitHub so I am not to take up a ton of space. Any help is much appreciated.
GitHub Link
Link to GitHub Project
The relevant code (from Main.py):
class MainWindow(QMainWindow, Ui_MainWindow):
...
def Add_History(self,pos):
self.HistoryWidget = HistoryList()
self.HistoryWidget.setObjectName("HistoryWidget_"+ str(Constents.analysisCount))
myQListWidgetItem = QListWidgetItem(self.History_List)
myQListWidgetItem.setSizeHint(self.HistoryWidget.sizeHint())
self.History_List.addItem(myQListWidgetItem)
self.History_List.setItemWidget(myQListWidgetItem, self.HistoryWidget)
self.HistoryWidget.buttonPushed.connect(self.deleteObject)
self.HistoryWidget.setXpoint(str(pos.x()))
self.HistoryWidget.setYpoint(str(pos.y()))
self.HistoryWidget.setHistoryName("Point "+ str(Constents.analysisCount))
Constents.analysisCount = Constents.analysisCount + 1
def deleteObject(self):
sender = self.sender() #var of object that sent the request
row_number = sender.objectName().split("_")
number = row_number[1]
x,y = Constents.analysisDetails[str(number)]# getting xy for object
self.loadPicture.findAndRemoveAnalysisPoint(x,y) #calling the class with the scense to delete it
Constents.analysisDetails.pop(str(number)) # get rid of the object in the variables
HistoryWidget = self.findChildren(HistoryList, "HistoryWidget_"+number)[0] #find the actual object
HistoryWidget.deleteLater() #delete that object
#Simport pdb; pdb.set_trace()
#self.History_List.takeItem(HistoryWidget)
Pictures
You must delete both the item-widget and the item itself. To do that, a method is required for getting an item from an item-widget (or one of its child widgets). A clean way to do this is to use the list-widget's itemAt method, which can get an item from a point on the screen. The major benefit of doing it this way is that it does not require knowledge of the item's index, which can of course change when other items are deleted. It also means the item-widgets don't need to know anything about the specific list-widget items they are associated with.
Here is a re-write of your deleteObject method, which implements that:
def deleteObject(self):
sender = self.sender() #var of object that sent the request
row_number = sender.objectName().split("_")
number = row_number[1]
x,y = Constents.analysisDetails[str(number)]# getting xy for object
self.loadPicture.findAndRemoveAnalysisPoint(x,y) #calling the class with the scense to delete it
Constents.analysisDetails.pop(str(number)) # get rid of the object in the variables
# get the viewport coords of the item-widget
pos = self.History_List.viewport().mapFromGlobal(
sender.mapToGlobal(QtCore.QPoint(1, 1)))
if not pos.isNull():
# get the item from the coords
item = self.History_List.itemAt(pos)
if item is not None:
# delete both the widget and the item
widget = self.History_List.itemWidget(item)
self.History_List.removeItemWidget(item)
self.History_List.takeItem(self.History_List.row(item))
widget.deleteLater()

Make Tkinter.Listbox selection persist

I have a program where I need to take a selection from Tkinter.Listbox and an entry field and do something with that data. However, if I highlight any text within the entry field (i.e., to delete previous entry), the Listbox selection is cleared. How can I overcome it so that the Listbox selection persists?
import Tkinter as tk
master = tk.Tk()
listbox = tk.Listbox(master)
listbox.grid(row=0, column=0)
items = ['a', 'b', 'c']
for item in items:
listbox.insert(tk.END, item)
efield = tk.Entry(master)
efield.grid(row=1, column=0)
tk.mainloop()
Steps to reproduce:
Type something in the entry field.
Select something in the listbox.
Highlight whatever you entered in the entry field => selection in the listbox gets cleared.
This related question with similar issue How to select at the same time from two Listbox? suggests to use exportselection=0, which doesn't seem to work for me. In such case selection = listbox.selection_get() throws an error while the right line is still highlighted.
I know this is an old question, but it was the first google search result when I came across the same problem. I was seeing odd behavior using 2 list boxes when using selection_get() and also the selection persistence issue.
selection_get() is a universal widget method in Tkinter, and was returning selections that were last made in other widgets, making for some really strange behavior. Instead, use the ListBox method curselection() which returns the selected indices as a tuple, then you can use the ListBox's get(index) method to get the value if you need.
To solve the persistence issue, set exportselection=0 when instantiating the ListBox instance.
list_box = tk.Listbox(master, exportselection=False)
...
selected_indices = list_box.curselection()
first_selected_value = list_box.get(selected_indices[0])
As for now, I wasn't able to cleanly overcome the problem. One way around is to create a variable which will store the selected list value on click:
selected = None
def onselect(e):
global selected
selected = listbox.selection_get()
listbox.bind('<<ListboxSelect>>', onselect)
This doesn't keep the highlight, but the selection is now stored in a variable and can be used further.

Tkinter - Why is my background/frame not re-painting?

I am not sure if I am on the right track here-- but I started GUI programming with Python.
I have all of my buttons and entries worked out. The main problem I am having is with the method that rolls my die and places the result.
def roll(self):
self.die = Die(int(self.sides.get())) # gets from label
t = self.die.roll()
t += int(self.mod.get()) # gets from label
self.result = Label(self.root, text=t).grid(row=2, column=1, sticky=W)
Is my problem the fact that I am re-instantiating a Label over the old one? Shouldn't the old Label's text be destroyed and the frame should only show the new label in its place?
It seems to me that you're not using objects at their best values. You should modify you code in this way:
each time you need a new roll, you instantiate a new Die. Why not keeping the same instance?
each time you want to display the roll, you instantiate a new Label. Maybe you're not aware of this, but you can update the label text (and any Tkinter widget), using its configure() method. This would mean that you need to grid the instance only the first time.
By the way, .grid returns None. If you want to keep reference of the result label, you have to use two lines for Label instantiation:
self.result = Label(self.root, text=t) # first creating instance...
self.result.grid(row=2, colum=1, sticky=W) # ... and placing it in self.root
Try to update your code like this. You will certainly feel the need to move some of this code to the __init__() function of self, so write it in your question as well.

Categories

Resources