I want to create a menu with a checkable list. To prevent the menu from closing when the action is clicked, I'm setting the DefaultWidget to be a QCheckBox. The problem is when I'm trying to get isClicked from the action - it doesn't seem to be synced to the checkbox. How do I get the value of the action to change when the checkbox is clicked?
tool_button = QtWidgets.QToolButton()
menu = QtWidgets.QMenu()
check_box = QtWidgets.QCheckBox(menu)
check_box.setText("abc")
check_box.setChecked(True)
action_button = QtWidgets.QWidgetAction(menu)
action_button.setDefaultWidget(check_box)
menu.addAction(action_button)
tool_button.setMenu(menu)
print(check_box.text()) # returns abc
print(check_box.isChecked()) # returns True
print(action_button.isChecked()) # returns False - it's not picking up the values from check_box
Since QWidgetAction acts as some sort of container for any kind of widget, it has no way to know if its defaultWidget could even have any support for a bool state like "isChecked", so you have to provide it.
The simplest way is to subclass a specific QWidgetAction class for that action, and override its isChecked() method so that it returns the checked value based on its default widget.
from PyQt5 import QtWidgets
class CheckableWidgetAction(QtWidgets.QWidgetAction):
def setDefaultWidget(self, widget):
super().setDefaultWidget(widget)
try:
# if the widget has the toggled signal, connect that signal to the
# triggered signal
widget.toggled.connect(self.triggered)
except:
pass
def isChecked(self):
try:
return self.defaultWidget().isChecked()
except:
# failsafe, in case no default widget has been set or the provided
# widget doesn't have a "checked" property
return super().isChecked()
class Test(QtWidgets.QWidget):
def __init__(self):
super().__init__()
layout = QtWidgets.QVBoxLayout(self)
tool_button = QtWidgets.QToolButton()
layout.addWidget(tool_button)
menu = QtWidgets.QMenu()
check_box = QtWidgets.QCheckBox(menu)
check_box.setText("abc")
check_box.setChecked(True)
self.action_button = CheckableWidgetAction(menu)
self.action_button.setDefaultWidget(check_box)
self.action_button.triggered.connect(self.action_triggered)
menu.addAction(self.action_button)
tool_button.setMenu(menu)
controlButton = QtWidgets.QPushButton('is checked?')
layout.addWidget(controlButton)
controlButton.clicked.connect(self.is_checked)
def is_checked(self):
print('checked is {}'.format(self.action_button.isChecked()))
def action_triggered(self, state):
print('triggered {}'.format(state))
if __name__ == '__main__':
import sys
a = QtWidgets.QApplication(sys.argv)
test = Test()
test.show()
sys.exit(a.exec_())
I have an existing application that I am polishing off and I want to add some animation to a few of the widgets. Animating widgets with QPropertyAnimation outside of layouts is easy and fun, however when they are in a layout I am having various difficulties. The current one giving me a headache is that when I animate the size of a widget, the layout does not adjust to it's new size.
So lets say I have a QVBoxLayout with three widgets: a label which should expand to all available space, a treeview, and a button. When I click the button I want the tree to collapse and the label to take over it's space. Below is this example in code, and as you can see while the tree animates it's size nothing happens, and then when I hide it at the end of the animation the label pops to fill the now vacant space. So it seems that during the animation the layout does not "know" the tree is resizing. What I would like to happen is that AS the tree shrinks, the label expands to fill it.
Could this could be done not by absolute sizing of the label, but by calling a resize on the layout or something like that? I ask because I want to animate several widgets across my application and I want to find the best way to do this without having to make too many widgets interdependent upon each other.
Example code:
import sys
from PyQt4 import QtGui, QtCore
class AnimatedWidgets(QtGui.QWidget):
def __init__(self):
super(AnimatedWidgets, self).__init__()
layout1 = QtGui.QVBoxLayout()
self.setLayout(layout1)
expanding_label = QtGui.QLabel("Expanding label!")
expanding_label.setStyleSheet("border: 1px solid red")
layout1.addWidget(expanding_label)
self.file_model = QtGui.QFileSystemModel(self)
sefl.file_model.setRootPath("C:/")
self.browse_tree = QtGui.QTreeView()
self.browse_tree.setModel(self.file_model)
layout1.addWidget(self.browse_tree)
shrink_tree_btn = QtGui.QPushButton("Shrink the tree")
shrink_tree_btn.clicked.connect(self.shrink_tree)
layout1.addWidget(shrink_tree_btn)
#--
self.tree_size_anim = QtCore.QPropertyAnimation(self.browse_tree, "size")
self.tree_size_anim.setDuration(1000)
self.tree_size_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuart)
self.tree_pos_anim = QtCore.QPropertyAnimation(self.browse_tree, "pos")
self.tree_pos_anim.setDuration(1000)
self.tree_pos_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuart)
self.tree_anim_out = QtCore.QParallelAnimationGroup()
self.tree_anim_out.addAnimation(self.tree_size_anim)
self.tree_anim_out.addAnimation(self.tree_pos_anim)
def shrink_tree(self):
self.tree_size_anim.setStartValue(self.browse_tree.size())
self.tree_size_anim.setEndValue(QtCore.QSize(self.browse_tree.width(), 0))
tree_rect = self.browse_tree.geometry()
self.tree_pos_anim.setStartValue(tree_rect.topLeft())
self.tree_pos_anim.setEndValue(QtCore.QPoint(tree_rect.left(), tree_rect.bottom()))
self.tree_anim_out.start()
self.tree_anim_out.finished.connect(self.browse_tree.hide)
def main():
app = QtGui.QApplication(sys.argv)
ex = AnimatedWidgets()
ex.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
The layouts handle the geometry() of the widgets so that when wanting to change the pos property these are interfacing with their handles so it is very common that you get that type of behavior, a better option is to use a QVariantAnimation to establish a fixed height:
import sys
from PyQt4 import QtGui, QtCore
class AnimatedWidgets(QtGui.QWidget):
def __init__(self):
super(AnimatedWidgets, self).__init__()
layout1 = QtGui.QVBoxLayout(self)
expanding_label = QtGui.QLabel("Expanding label!")
expanding_label.setStyleSheet("border: 1px solid red")
layout1.addWidget(expanding_label)
self.file_model = QtGui.QFileSystemModel(self)
self.file_model.setRootPath(QtCore.QDir.rootPath())
self.browse_tree = QtGui.QTreeView()
self.browse_tree.setModel(self.file_model)
layout1.addWidget(self.browse_tree)
shrink_tree_btn = QtGui.QPushButton("Shrink the tree")
shrink_tree_btn.clicked.connect(self.shrink_tree)
layout1.addWidget(shrink_tree_btn)
#--
self.tree_anim = QtCore.QVariantAnimation(self)
self.tree_anim.setDuration(1000)
self.tree_anim.setEasingCurve(QtCore.QEasingCurve.InOutQuart)
def shrink_tree(self):
self.tree_anim.setStartValue(self.browse_tree.height())
self.tree_anim.setEndValue(0)
self.tree_anim.valueChanged.connect(self.on_valueChanged)
self.tree_anim.start()
def on_valueChanged(self, val):
h, isValid = val.toInt()
if isValid:
self.browse_tree.setFixedHeight(h)
def main():
app = QtGui.QApplication(sys.argv)
ex = AnimatedWidgets()
ex.show()
sys.exit(app.exec_())
if __name__ == "__main__":
main()
I have created a window with QTableWidget having a cell with 2 buttons.
Buttons are created in seperate class where i am passing QTableWidget instance from main procedure.
I am not able to get the button events, which are connected in button Creation class. My code snippet is as below
class Buttons():
def __init__(self,tab):
buttonLayout = QtGui.QHBoxLayout()
buttonLayout.setContentsMargins(0,0,0,0)
self.saveButtonItem = QtGui.QPushButton('Save')
self.deleteButtonItem = QtGui.QPushButton('Delete')
buttonLayout.addWidget(self.saveButtonItem)
buttonLayout.addWidget(self.deleteButtonItem)
cellWidget = QtGui.QWidget()
cellWidget.setLayout(buttonLayout)
tab.insertRow(tab.rowCount())
tab.setCellWidget(tab.rowCount() - 1,0,cellWidget)
self.setconncection()
def setconncection(self):
self.saveButtonItem.clicked.connect(self.btnSaveClicked)
self.deleteButtonItem.clicked.connect(self.btnDeleteClicked)
print 'connections are set'
def btnSaveClicked(self):
print 'save clicked'
def btnDeleteClicked(self):
print 'delete clicked'
class testing(QtGui.QTableWidget):
def __init__(self):
super(testing,self).__init__()
self.setColumnCount(1)
for i in xrange(3):
self.r = Buttons(self)
if __name__ == "__main__" :
import sys
app = QtGui.QApplication (sys.argv)
win = testing ()
win.show()
sys.exit(app.exec_())
My window at run time is as below
After the __init__ of testing, the reference to Buttons instance is lost and the object is destroyed. (Variable r is affected but not used.)
Keeping a link to it (see last line in following code snippet) makes it work.
class testing(QtGui.QTableWidget):
def __init__(self):
super(testing,self).__init__()
self.setColumnCount(1)
self.setRowCount(1)
self.buttons = []
for i in xrange(3):
self.buttons.append(Buttons(self))
I have researched heavily on how to get the variables from a different class in wxPython without much luck. My problem is that I want to update a combobox in the main window after the user closes the second window. I thought the best way to do this was to try to get the main window combobox variable somehow. Example:
import wx
class oranges(wx.Frame):
#----------Main Window---------#
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,'Test',size=(1024,768))
self.frame=wx.Panel(self)
self.tickers=['Apples','a','1234']
self.dropdown=wx.ComboBox(self.frame,choices=self.tickers,pos=(750,62),style=wx.CB_READONLY)
self.Bind(wx.EVT_COMBOBOX, self.get_stuff,self.dropdown)
apples=wx.Button(self.frame,label='Click here',pos=(300,300),size=(100,100))
self.Bind(wx.EVT_BUTTON, self.plus,apples)
def get_stuff(self,event):
pass
def plus(self,event):
class orange(wx.Frame):
#----------Second Window---------#
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,'Testing',size=(500,500))
self.frames=wx.Panel(self)
apples=wx.Button(self.frames,label='Collect Info',pos=(300,300),size=(100,100))
self.Bind(wx.EVT_BUTTON, self.click,apples)
self.what=wx.TextCtrl(self.frames,-1,'12',pos=(200,48))
def click(self,event):
asdf=self.what.GetValue()
self.tickers=[]
self.tickers.append(asdf)
self.dropdown.Clear()
self.dropdown.AppendItems(self.tickers)
#Need to update dropdown#
self.Destroy()
if __name__ =='__main__':
apps = wx.PySimpleApp()
windows = orange(parent=None,id=-1)
windows.Show()
apps.MainLoop()
if __name__ =='__main__':
app = wx.PySimpleApp()
window = oranges(parent=None,id=-1)
window.Show()
app.MainLoop()
I am really confused on how to go about fixing this problem. Thanks in advance! I am looking forward to the answers!
in your parent Frame, add a method called UpdateComboBox(self, newVal) that takes in the new string... then since your popup is a child of that, just before you call self.Destroy in the child, call self.GetParent().UpdateComboBox(asdf) (where asdf is from your example, i.e. the string you want to pass back)
import wx
class SomeUserForm(wx.Dialog):
def __init__(self):
wx.Dialog.__init__(self,None,-1,"Enter User Info In this Frame")
self.txt = wx.TextCtrl(self,-1,pos=(50,50))
self.txt2 = wx.TextCtrl(self,-1,pos=(50,100))
self.ok = wx.Button(self,wx.ID_OK,pos=(50,125))
def GetValue(self):
return self.txt.GetValue() + "::"+ self.txt2.GetValue()
class oranges(wx.Frame):
#----------Main Window---------#
def __init__(self,parent,id):
wx.Frame.__init__(self,parent,id,'Test',size=(1024,768))
self.frame=wx.Panel(self)
self.tickers=['Apples','a','1234']
self.dropdown=wx.ComboBox(self.frame,choices=self.tickers,pos=(750,62),style=wx.CB_READONLY)
self.Bind(wx.EVT_COMBOBOX, self.get_stuff,self.dropdown)
apples=wx.Button(self.frame,label='Click here',pos=(300,300),size=(100,100))
self.Bind(wx.EVT_BUTTON, self.plus,apples)
def get_stuff(self,event):
pass
def plus(self,evt):
dlg = SomeUserForm()
if dlg.ShowModal() != wx.ID_OK:
wx.MessageBox("User Cancelled Add!","Cancelled!")
return
self.dropdown.Append(dlg.GetValue())
if __name__ =='__main__':
app = wx.PySimpleApp()
window = oranges(parent=None,id=-1)
window.Show()
app.MainLoop()
may do what you are asking for ...
I have a code which consists of 3 classes. The classes widget1 and widget2 inherit from QFrame class and consists of several lineedit and combobox for the user to enter information.
So, what I want is: when the program is launched, it will firstly bring up the QMainWindow class with widget1 set as the central widget.
The widget1 has a Check function which is connected to a button on it. The check button will check whether a condition is true based on the data entered by the user on the page.
If the condition is true. I want the widget2 to set it as central wiget of MainWindow to replace existing central widget, the widget1.
My question is, how do I set the widget2 as the central widget of existing MainWidnow class instance?
This is the format of my code:
class widget1(QtGui.QFrame):
def __init__(self,parent = None):
......
......
def Check(self):
if (condition):
#set widget2 as central widget on MainWindow
class widget2(QtGui.QFrame):
def __int__(self,parent = None):
.....
.....
class MainWindow(QtGui.QMainWindow):
def __init__(self,parent = None):
QtGui.QMainWindow.__init__(self,parent)
....
mywidgetone = widget1()
self.setCentralWidget(mywidgetone)
if __name__ == '__main__':
app = QtGui.QApplicaiton(sys.argv)
main = MainWindow()
main.show()
app.exec_()
I'd do the following for MainWindow:
class MainWindow(QtGui.QMainWindow):
def __init__(self,parent = None):
QtGui.QMainWindow.__init__(self,parent)
....
self.setMyCentral(widget1)
def setMyCentral(self, widgetClass):
mywidget = widgetClass(self)
self.setCentralWidget(mywidget)
and then, in widget1:
def Check(self):
if (condition):
self.parent().setMyCentral(widget2)
Now, please, follow the conventions: classes start in capital letters (Widget1, Widget2) and methods don't (check instead of Check)
Another option is a panel creates all 3 widgets but set widget2 and 3 visible = false. Then when time to change, set widget1 visible=false, and widget2 visible. Etc. You set the panel as central widget, this never changes. Just the panel decides which one of its three children to show.