pyqt QbuttonGroup: Passing user data on buttonClicked - pyqt

I have defined my own class that inherits QbuttonGroup
class TupleButtonGroup(QtGui.QButtonGroup):
def __init__(self,parent, tuple_pair):
super(TupleButtonGroup, self).__init__(parent)
self.tuple_pair = tuple_pair
And on buttonclicked i want to access the user data tuple_pair and corresponding button clicked in the button group
button_group.buttonClicked[QtGui.QAbstractButton].connect(self.labelchanged)
def labelchanged(self, button):
print button.text()
The function receives the button clicked but i don't know how to access the user data in the callback

All buttons have a group() method that allows you to get a reference to whatever group (if any) that they have been added to. So your code can simply be:
button_group.buttonClicked.connect(self.labelchanged)
def labelchanged(self, button):
print button.text()
print button.group().tuple_pair
And note that you dont need to specifiy QAbstractButton when connecting the signal, because that is the default overload.

You could do it a couple of ways.
You could catch the buttonClicked signal in your subclass and emit your own signal that contains the tuple_pair.
class TupleButtonGroup(QtGui.QButtonGroup):
buttonClickedWithData = QtCore.pyqtSignal(tuple, QtGui.QAbstractButton)
def __init__(self,parent, tuple_pair):
super(TupleButtonGroup, self).__init__(parent)
self.tuple_pair = tuple_pair
self.buttonClicked[QtGui.QAbstractButton].connect(self.on_buttonClicked)
#QtCore.pyqtSlot(QtGui.QAbstractButton)
def on_buttonClicked(self, button):
self.buttonClickedWithData.emit(self.tuple_pair, button)
Then just connect to that new signal
button_group.buttonClickedWithData.connect(self.labelchanged)
#QtCore.pyqtSlot(tuple, QtGui.QAbstractButton)
def labelchanged(self, tuple_pair, button):
print tuple_pair
print button.text()
The other option is to use your existing class, signals and slots, and use the .sender() method to get a reference to the button group from within the slot method.
#QtCore.pyqtSlot(QtGui.QAbstractButton)
def labelchanged(self, button):
btngroup = self.sender()
print btngroup.tuple_pair
print button.text()

Related

How can I access the TextButton custom data property by clicking on the button in Python's Flet module?

I am using Flet module to create a simple GUI.
I want to access the custom data on the button. However, using the clicked method, it seems that the event handler of the button using the data property of it, returns empty string. Therefore, the method does not print anything.
Using flet 0.1.62, Python 3.10
import flet
from flet import UserControl, TextButton, Row, Page
class Test(UserControl):
def __init__(self):
super().__init__()
self.text_button = TextButton(text='test', data='test', on_click=self.clicked)
self.view = Row(controls=[self.text_button])
def clicked(self, e):
print(e.data, ' was clicked')
def build(self):
return self.view
def main(page: Page):
t = Test()
page.add(t)
app = flet.app(target=main)
It seems in the flet's tutorials for the Calculor example, that you can have multiple buttons using the same method for on_click argument.
this is a part from the example's code where it refers to Handling events:
https://flet.dev/docs/tutorials/python-calculator/
Now let's make the calculator do its job. We will be using the same event handler for all the buttons and use data property to differentiate between the actions depending on the button clicked. For each ElevatedButton control, specify on_click=self.button_clicked event and set data property equal to button's text, for example:
ft.ElevatedButton(
text="AC",
bgcolor=ft.colors.BLUE_GREY_100,
color=ft.colors.BLACK,
expand=1,
on_click=self.button_clicked,
data="AC",
)
Below is on_click event handler that will reset the Text value when "AC" button is clicked:
def button_clicked(self, e):
if e.data == "AC":
self.result.value = "0"
With similar approach, specify on_click event and data property for each button and add expected action to the button_clicked event handler depending on e.data value.
I am looking for a way to get the same result using the TextButton,
I'd appreciate your help!
I found the answer to my own question. Apparently in the version of the flet that I am using, the data property has to be looked up through the control instance attribute.
Therefore
I tried this in the code from my question:
def clicked(self, e):
print(e.control.data, ' was clicked')
instead of:
def clicked(self, e):
print(e.data, ' was clicked')
I tried it out of curiosity and it worked.

Reassigning the 'command' part of a range of tkinter buttons created using a class

Halfway through my program i want to change a range of button instances' "when clicked" commands. The buttons were created using the following class:
class GameBoardButtons:
def __init__(self, name, buttonText, buttonRow, buttonColumn, buttonState, frame, colour):
self.name = name
self.button = Button(frame, text=buttonText, fg=colour,
height=2, width=5, state=buttonState,
command=lambda: shipListAppendFunc(self))
self.button.grid(row=buttonRow, column=buttonColumn)
def GridPosAttackable(self):
self.button.config(command=lambda: whenAttacked(self.name))
And the function that i am calling to adjust the buttons is as follows:
def reconfigureButtons(grid):
children = grid.children
for c in children:
widget = children[c]
if isinstance(widget, (Button)):
widget.configure(command=lambda: whenAttacked(c.name))
Should i be instead calling the function within the class ("GridPosAttackable")? And if so, how would i do that? The current way i have it, all the buttons call the same "self.name", not their unique name.
I want to make it so when "whenAttacked(whatever)" is called, the field/'whatever' part it takes is the buttons unique ID/'self' label.
Many thanks to all forms of help!

Emit every signals for each n element in list of QpushButton object

I have list of QPushButton.i want to be able to make all button in list to emit signals in much more simpler way.
here's my code:
def btn_click(self):
self.menu_list[0].clicked.connect(lambda: self.add_cart(self.menu_list[0]))
self.menu_list[1].clicked.connect(lambda: self.add_cart(self.menu_list[1]))
self.menu_list[2].clicked.connect(lambda: self.add_cart(self.menu_list[2]))
self.menu_list[3].clicked.connect(lambda: self.add_cart(self.menu_list[3]))
self.menu_list[4].clicked.connect(lambda: self.add_cart(self.menu_list[4]))
self.menu_list[5].clicked.connect(lambda: self.add_cart(self.menu_list[5]))
def add_cart(self):
print(b.text())
You can do it in loop:
main
def btn_click(self):
for button in menu_list:
button.clicked.connect(self.add_cart)
def add_cart(self):
b = self.sender()
print(b.text())
alternative
def btn_click(self):
for button in self.menu_list:
button.clicked.connect(lambda btn=button: self.add_cart(btn))
Note that you need btn=button for making closure, otherwise all buttons woud refer to the last one.
ps:For more explanation on main way read #ekhumoro comment below

Need to call class method from different class without initialization of the first class or some other way around it

I have a small problem with my code.
There are two classes. First one creates a window with a Options button. Upon clicking the button, the second class is called and creates another window with an Ok button. Let's say there is also a checkbox, which changes the background color to black or something like that. After clicking the button, whatever changes were made in the options are stored into a file and the second window is closed.
All of this works fine. My problem is that now I need to call method update_init from the first class that will apply those changes to the MainWindow. The code below shows my first solution to this problem, but from what I understand, by using second mainloop I create second thread, which should be avoided.
class MainWindow:
def __init__(self, master):
self.master = master
self.options_btn = tk.Button(self.master, text="Options", command=self.open_options)
self.options_btn.pack()
self.options_window = None
def open_options(self):
options_master = tk.Toplevel()
self.options_window = OptionsWindow(options_master)
options_master.mainloop()
lst = meta_load() # loads changes from a file
self.update_init(lst)
def update_init(self, lst):
#code
class OptionsWindow:
def __init__(self, master):
self.master = master
self.ok_btn = tk.Button(self.master, text="OK", command=self.update_meta)
self.ok_btn.pack()
def update_meta(self):
meta_save(12) # saves changes into a file
self.master.destroy()
main_master = tk.Tk()
main_master.minsize(width=1280, height=720)
b = MainWindow(main_master)
main_master.mainloop()
My second solution was to just put both classes into one, but the code is quite messy if I do so.
Can I somehow call the method update_init (which is in the MainWindow class) from the OptionsWindow class without initializing new MainWindow class window? Or is there any other way to deal with this? I would appreciate any help.
I am sorry if this is too specific, I've tried to make it as general as possible, but it's a very specific problem and I couldn't find much information about it anywhere on the internet.
In general you can call a class method from anywhere you want and pass anything to it without initialisation of that class's instance, thanks to objective nature of python, but beware of self dependencies! Although, I don't think that's a good practice.
class A:
def __init__(self):
self.foo = 'foo'
def return_foo(self):
return self.foo
class B:
def __init__(self):
self.bar = 'bar'
print('Ha-ha Im inited!')
def return_bar(self):
try:
return self.bar
except AttributeError:
return 'bar'
def test():
a = A()
# b = B()
return_bar = getattr(B, 'return_bar', None)
if callable(return_bar):
print('%s%s' % (a.return_foo(), return_bar(None)))
test()
Links:
getattr
callable

Signal/Slot help-- setting a signal to a slot outside of the current class

I'm trying to populate a table (present in the main window) from a slider that's located in a widget in a separate class. I can't seem to get it to work...what's the best way to go about doing this?
Here's my current code:
class Widget(QWidget):
def __init__(self,filename,parent=None):
super(Widget,self).__init__(parent)
self.resize(900,900)
self.layout=QVBoxLayout(self)
frame=Frame(filename)
self.image=pg.ImageView()
self.image.setImage(frame.data)
self.image.setCurrentIndex(0)
fileheader=FileHeader(filename)
self.slider=QSlider(self)
self.slider.setOrientation(Qt.Horizontal)
self.slider.setMinimum(1)
self.slider.setMaximum(fileheader.numframes)
self.slider.sliderMoved.connect(self.sliderMoved)
self.layout.addWidget(self.image)
self.layout.addWidget(self.slider)
def sliderMoved(self,val):
print "slider moved to:", val
fileheader=FileHeader(filename)
idx=val
frame=fileheader.frameAtIndex(idx)
self.image.setImage(frame.data)
class MainWindow(QMainWindow):
def __init__(self, filename, parent=None):
super(MainWindow,self).__init__(parent)
self.initUI(filename)
def initUI(self,filename):
self.filetable=QTableWidget()
self.frametable=QTableWidget()
self.imageBrowser=Widget(filename)
self.imagesplitter=QSplitter(Qt.Horizontal)
self.tablesplitter=QSplitter(Qt.Horizontal)
self.imagesplitter.addWidget(self.imageBrowser)
self.tablesplitter.addWidget(self.imagesplitter)
self.tablesplitter.addWidget(self.filetable)
self.tablesplitter.addWidget(self.frametable)
self.setCentralWidget(self.tablesplitter)
exitAction=QAction(QIcon('exit.png'),'&Exit',self)
exitAction.setShortcut('Ctrl+Q')
exitAction.triggered.connect(qApp.quit)
openAction=QAction(QIcon('open.png'),'&Open',self)
openAction.setShortcut('Ctrl+O')
menubar=self.menuBar()
fileMenu=menubar.addMenu('&File')
fileMenu.addAction(exitAction)
fileMenu.addAction(openAction)
self.fileheader=FileHeader(filename)
self.connect(self.frametable,
SIGNAL("Widget.sliderMoved(idx)"),
self.fileheader.frameAtIndex(idx))
self.frameheader=self.fileheader.frameAtIndex(0)
self.populate()
def populate(self):
self.filetable.setRowCount(len(self.fileheader.fileheader_fields))
self.filetable.setColumnCount(2)
self.filetable.setHorizontalHeaderLabels(['File Header','value'])
for i,field in enumerate(self.fileheader.fileheader_fields):
name=QTableWidgetItem(field)
value=QTableWidgetItem(unicode(getattr(self.fileheader,field)))
self.filetable.setItem(i,0,name)
self.filetable.setItem(i,1,value)
self.frametable.setRowCount(len(self.frameheader.frameheader_fields))
self.frametable.setColumnCount(2)
self.frametable.setHorizontalHeaderLabels(['Frame Header','Value'])
for i,fields in enumerate(self.frameheader.frameheader_fields):
Name=QTableWidgetItem(fields)
Value=QTableWidgetItem(unicode(getattr(self.frameheader,fields)))
self.frametable.setItem(i,0,Name)
self.frametable.setItem(i,1,Value)
I know the "connect" is wrong-- I'm very new to PyQt and Python in general, so I'm not quite sure where to start.
Since self.imageBrowser is your Widget class, it will have the slider attribute which has the sliderMoved signal. You just need a few more dots.
self.imageBrowser.slider.sliderMoved.connect(self.fileheader.frameAtIndex)
The way you have it organized is correct though. Your main window composes your custom widgets and binds the connections together.
Though because you have a data source, and also a QTableWidget that will need to be updated, you probably need to wrap the steps up into a little method:
def initUI(self,filename):
...
self.imageBrowser.slider.sliderMoved.connect(self._handle_slider_moved)
# initialize it the first time during the window set up
self._handle_slider_moved(0)
def _handle_slider_moved(self, val):
# update the data source
self.fileheader.frameAtIndex(val)
# update the second data source
self.frameheader=self.fileheader.frameAtIndex(0)
# now refresh the tables
self.populate()

Resources