How should I proceed if I want to get an especific widget from a layout in python Qt?
What I have done so far:
for i in range(self.ui.horizontalLayout_14.count()):
#here it does fail
name = self.ui.horizontalLayout_14.itemAt(i).objectName()
#if the above would had worked, then I could do something like this for example
if "button" in name:
self.ui.horizontalLayout_14.itemAt(i).widget().close()
Note that, for the example, I am using button but It might be whatever widget inside the layout, lineEdit, or comboBox, labels, etc etc, but not all of them though.
The problem is that itemAt() function returns the QLayoutItem and not a widget. So you have to call the QLayoutItem::widget() function to get the containing widget, i.e.:
name = self.ui.horizontalLayout_14.itemAt(i).widget().objectName()
UPDATE
However, you can do the same much easier with using the QObject::findChild() function. I.e.:
widget = self.ui.findChild(QPushButton, "button")
# Check whether the returned widget is null...
Related
I'm trying to build a listbox using Tkinter and receive the selected option by clicking it.
import Tkinter as tk
from Tkinter import *
root = tk.Tk()
lst=Listbox(root, height=30, width=50)
lst.insert(1, "hy")
lst.insert(2, "hello")
lst.insert(3, "hey")
lst.pack()
sel = lst.curselection()
print sel
root.mainloop()
However, when I run the code it prints me an empty tuple before I pressed any choise.
Does someone know how to get the selected choise after I press one and not right after I run it?
Thanks a lot :)
You are getting the selection about a millisecond after creating the widget, well before the user has a chance to see the UI much less interact with it.
GUI programs are event based, meaning that things happen in response to events. Events are things like clicking buttons, inserting data into input widgets, and selecting items from listboxes.
You need to do one of two things: create a button or other widget which will get the selected item, or configure it so that a function is called whenever an item is selected.
No matter which solution you use, you will need a function that ultimately calls the curselection method of the listbox to get a list of indices. You can then call the get method to get the selected item or items.
Here's a function definition that will print the selected item, or print "no selection" if nothing is selected. So that it can be resused without modification. we'll define it to take the listbox as an argument.
Note: this example assumes the widget only supports a single select, to keep it simple:
def print_selection(listbox):
selection = listbox.curselection()
if selection:
print(f"selected item: {listbox.get(selection[0])}")
else:
print("nothing is selected")
Using a button
To call this from a button is straight-forward. We just create a button after we create the listbox, and use the command attribute to call the function. Since the function we wrote earlier needs a parameter, we'll use lambda to create a temporary function for the button.
button = tk.Button(root, text="Print Selected Item", command=lambda: print_selection(lst))
button.pack()
Calling the function when the selection is made
To call the function whenever the user changes the selection, we can bind a function to the <<ListboxSelect>> event. We'll create a separate function for this, and then pull the widget from the event object that is automatically passed to the function.
def print_callback(event):
print_selection(event.widget)
lst.bind("<<ListboxSelect>>", print_callback)
First of all, the reason you are getting an empty tuple is because you have executed the statements:
sel = lst.curselection()
print(sel)
before you have executed the root.mainloop()
Secondly, your setup for listbox fails to include a StringVar variable to hold your list.
Once the variable has been defined, you should be able to use the .insert statements to add your list items one at a time, or you can initialize the StringVar variable using a .set('hy', 'hello', 'hey') command.
To provide a return of a selected variable, you must incorporate an event handler to determine the list position selected onclick or some other triggering method.
For a pretty clear explanation of these characteristics check here
I am new in Python and in tkinter so the question may seems naive: is it ok to create and place widgets at the same time if I don't need to change them?
It works but is it a good practice? And if not why?
An example of what I mean:
import tkinter as tk
window=tk.Tk()
tk.Label(window,text='Lost Label').pack()
window.mainloop()
To expand upon #Skynet's answer....
Whenever you do Widget(*args, **kwargs).pack() the pack() method returns None as would other geometry managers, so if you tried to assign this to a variable the variable would be None.
In this case then probably not, since you probably actually want to be storing the reference to the widget.
If you don't need a reference then there's not really a problem with it. As the other answer notes you don't need a definitve reference to every single widget in your GUI unless you plan to use this reference in some way. Unless I plan on changing the label text / modifying it in someway then I typically use your method to save some space. No need to write more code than you have to!
For example you're creating a Button widget.
btn = Button(blabla, text="Button1")
This returns a button object and if you need later to configure it or get information about it you can do it by through the btn variable.
But if you use something like btn = Button(blabla, text="Button1").pack() it returns None and not a button object so you won't be able to change anything about the button or get information about it later.
Another example is with the Entry widget
entry = Entry(blabla)
Using that later you can do entry.get() to get the text inside the entry
but you won't be able to do it if you use entry = Entry(blabla).pack() since it doesn't return an entry object, it just packs the widget and you won't be able to access it for later use.
There is nothing wrong with that approach and I have already seen it quite a few times. You don't have to keep a reference to every widget in your GUI.
Using the following code, after I input the information into the text boxes in my app, the offset_back returns a list and self.main, the id used in my .kv file to create a scrollview
adds the layout but when I try to re-enter data a second time this happens: "raise Exception('ScrollView accept only one widget')"
Do I need to reset self.main some way and if so how can I achieve this?
Thanks.
def set_back_offset(self):
layout = GridLayout(cols=1, spacing=10, size_hint_y=None)
layout.bind(minimum_height=layout.setter('height'))
# catch empty box input which is a str, as Decimal will throw error.
if self.initial_bet.text == str() or self.initial_odds.text == str():
pop_warning().open()
#make sure entered unicode text is numeric, if not throw pop-up warning
elif not unicode.isnumeric(self.initial_bet.text) or not unicode.isnumeric(self.initial_odds.text):
pop_warning().open()
else:# all is good, create widgets
for details in offset_back(Decimal(self.initial_bet.text), Decimal(self.initial_odds.text)):
btn = Button(text=str(details[0]), size_hint_y=None, height=40,background_color= (255,0,0,1))
layout.add_widget(btn)
self.main.add_widget(layout)
`
I assume the ScrollView is self.main? The problem is presumably what it says on the tin, that it only accepts one widget and it already has a child when you try to add another one.
A simple fix would be to just first remove the existing widget with something like self.main.remove_widget(self.main.children[0]). However, if you want to replace them with something similar-looking you might be better off changing some properties of the existing widgets.
Either way, the solution is to remove the existing ScrollView child before adding a new one.
Im making a simplish widget that can act as a container for other widgets. One of the features of the widget is that you can expand/collapse it by clicking on it. My current method is basically looking up all child widgets of the layout and hiding them. I'm looking for any help on how to handle this properly - my current implementation has at least one serious caveat: that you can't add widgets while it's collapsed (they're added in an 'unhidden' state)
heres the setCollapsed method that is run when the widget is clicked
def collapsed(self):
return self._isCollapsed
def setCollapsed(self, collapseBool):
self._isCollapsed = collapseBool
if self.layout()!=None:
childWidgets = [self.layout().itemAt(i).widget() for i in range(self.layout().count())]
for w in childWidgets:
if isinstance(w,QtGui.QWidget):
w.setHidden(collapseBool)
if collapseBool:
self._cachedMargin = self.layout().margin()
self.layout().setMargin(0)
else:
self.layout().setMargin(self._cachedMargin)
Rather than hiding all child widgets individually, I would just hide a single parent item.
I have dialog:
It contains many flat QPushButtons, QTextEdit and another QPushButton. After click on 'Get list' we can see list of checked buttons in QTextEdit.
My question is how to get this functionality in some smart way. Right now I'm checking every button:
if self.ui.bq6o.isChecked():
cards.append("Q6o")
if self.ui.bk2o.isChecked():
cards.append("K2o")
if self.ui.bq3o.isChecked():
cards.append("Q3o")
if self.ui.bt7s.isChecked():
cards.append("T7s")
if self.ui.bq4o.isChecked():
cards.append("Q4o")
if self.ui.bt4s.isChecked():
cards.append("T4s")
if self.ui.b98o.isChecked():
cards.append("98o")
if self.ui.bjto.isChecked():
cards.append("JTo")
if self.ui.btt.isChecked():
cards.append("TT")
if self.ui.bq7o.isChecked():
cards.append("Q7o")
[...]
Obviously I can't like code like that. I was looking for some widget "button matrix" like, but without luck. I will be grateful for advises.
All the buttons should be children of the same widget, probably the dialog itself. Just get a handle to that widget to get all the child buttons, then loop through them and if they're checked, included their text.
parent = dialog # or whatever
cards = [widget.text() for widget in parent.children() if isinstance(widget, QPushButton) and widget.isChecked()]
You may need to include some code in the if statement to exclude the "Get List" button, or any other pushbuttons in your dialog that could be set to "checked" but shouldn't be included in cards list.
As #Brendan suggested in the other question, you could loop through them in a single list comprehension. But one other approach is to connect each buttons toggled signal to a slot that allows them to register when they are checked.
# somewhere in your class
self.checkedList = set()
def buttonChecked(self, checked):
button = self.sender()
if checked:
self.checkedList.add(button)
else:
if button in self.checkedList:
self.checkedList.remove(button)
# when you create a new button
button.toggled.connect(self.buttonChecked)
This would let you always have a set of just the checked buttons, which are self reporting. Otherwise, you would have to track them under their parent and loop to find out which are checked each time.
Update
Here is a another version that combines #Brendans loop and my signal suggestion. This might help in a situation where your buttons are a bit more spread out across your UI as opposed to be all under a single parent... but first assuming them all under a parent...
parent = dialog
for widget in parent.children():
if isinstance(widget, QPushButton):
widget.toggled.connect(self.buttonChecked)
You could repeat this in your __init__() for all the locations of your buttons and get them all registered to the slot.