I had two classes - main and sub interface
There is a pushbutton which will calls out the sub interface and I am trying to get the output of the sub tool interface directly (or almost immediately) so that it can be use within the push button function.
In my code, if I did the following:
hit on 'Click Me'
checked 2 options and hit the 'Apply to selected item' in the sub interface
the print line of 'my dict values' is still empty
Unless I create another function get_results in which then self.my_dict will be shown correctly.
As such, how can I code it in a way that once the 'Apply...' button is hit, self.my_dict will be updated without the need of creating another function? Or am I just overthinking things?
class SubMenuWindow(QtGui.QWidget):
def __init__(self, menu_items, parent=None):
super(SubMenuWindow, self).__init__(parent)
self.my_lyt = QtGui.QVBoxLayout()
self.checked_options = []
self.sel = {}
for menu_name, submenu_name in menu_items.items():
# Set the main menu item name
self.groupbox = QtGui.QGroupBox(self)
self.groupbox.setTitle(menu_name)
self.groupbox.setLayout(QtGui.QVBoxLayout())
self.my_lyt.addWidget(self.groupbox)
if submenu_name:
sub_txt = [action for action in submenu_name]
for s in sub_txt:
sub_chk = QtGui.QCheckBox(s)
self.checked_options.append(sub_chk)
self.groupbox.layout().addWidget(sub_chk)
apply_tag_btn = QtGui.QPushButton('Apply to selected item')
apply_tag_btn.clicked.connect(self.get_checked_options)
self.my_lyt.addWidget(apply_tag_btn)
self.my_lyt.addStretch()
self.setLayout(self.my_lyt)
self.show()
def get_checked_options(self):
for f in self.checked_options:
if f.isChecked():
self.sel[f.parent().title()] = f.text()
class MainWin(QtGui.QWidget):
def __init__(self, parent=None):
super(MainWin, self).__init__(parent)
self.my_dict = {}
btnA = QtGui.QPushButton('Click Me')
btnA.clicked.connect(self.get_options)
btnB = QtGui.QPushButton('Get results')
btnB.clicked.connect(self.get_results)
layout = QtGui.QVBoxLayout()
layout.addWidget(btnA)
layout.addWidget(btnB)
self.setLayout(layout)
def get_options(self):
sample_dict = {'GrpA' : ['John', 'Zack'], 'GrpB' : ['Alice', 'Phan']}
self.subWin = SubMenuWindow(sample_dict)
# I had want to get the values from subWin as soon as User has hit on
# the 'Apply to selected item' button
self.my_dict = self.subWin.sel
print ">>> my dict values : ", self.my_dict
# do something else from here thereafter...
def get_results(self):
print self.subWin.sel
Creating the new window will not block, so your print statements will be executed before the user has anything selected. You could pass in a callback to notify the calling widget when the user changes the selection.
for example:
class SubMenuWindow(QtWidgets.QWidget):
def __init__(self, menu_items, parent=None, callback=None):
super(SubMenuWindow, self).__init__(parent)
self.callback = callback
[...]
def get_checked_options(self):
for f in self.checked_options:
if f.isChecked():
self.sel[f.parent().title()] = f.text()
if self.callback:
self.callback()
and pass in the callback:
def get_options(self):
sample_dict = {'GrpA' : ['John', 'Zack'], 'GrpB' : ['Alice', 'Phan']}
self.subWin = SubMenuWindow(sample_dict, callback=self.get_results)
[...]
this way your get_result method will be called whenever the user clicks the apply button in the SubMenuWindow.
Related
I'm designing this thing on Qt Designer using PyQt. So I have a QMainWindow from where the user can open a QDialog to Input some data I'm saving in a Json file (AddUser function below)
What I want is when the AddUser push button is clicked, from the AddUser function, How can I Add a new Item in a Combobox that is defined in the MainWindow Class ?
Here is the code of the two classes
import ui.mainwindow as MnWindow
import ui.AddUserDialog as AddUserDialog
#First GUI
class MainWindow(QMainWindow,MnWindow.Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
#Second GUI
class AddUserDialog(QDialog,AddUserDialog.Ui_Dialog):
def __init__(self,parent=None):
super(AddUserDialog,self).__init__(parent)
self.setupUi(self)
self.pushButtonAddUser.clicked.connect(self.AddUser)
def AddUser(self):
DateDeNaissance = self.dateEditDateDeNaissance.date().toString(Qt.ISODate)
DateDeSortie = self.dateEditDateDeSortie.date().toString(Qt.ISODate)
new_user = {
'firstname' : self.lineEditPrenom.text(),
'lastname' : self.lineEditNom.text(),
'DateDeNaissance' : DateDeNaissance[-2:] + DateDeNaissance[5:7] + DateDeNaissance[:4],
'LieuDeNaissance' : self.lineEditLieuNaissance.text(),
'Adresse' : self.lineEditAdresse.text(),
'Ville' : self.lineEditVille.text(),
'CodePostal' : self.lineEditCodePostal.text(),
'DateDeSortie' : DateDeSortie[-2:] + DateDeSortie[5:7] + DateDeSortie[:4],
'Heure' : str(self.timeEditSortie.time().hour()),
}
with open('TestJson.json','r') as f:
data = json.load(f)
data['users'].append(new_user)
with open('TestJson.json','w') as f:
json.dump(data,f,indent=3)
MainWindow.UserComboBox.addItem(new_user['firstname'] + ' ' + new_user['lastname'])
The last line the incorrect of course, How can I do that properly ?
PS: I've read that I need to inherit from MainWindow Class, but I've been trying that with no success.
There are two possible ways to do so.
The simplest way is to access to the properties (or properties of widgets) from the reference to the instance.
class MainWindow(QMainWindow,MnWindow.Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.someButton.clicked.connect(self.addUser)
def addUser(self):
dialog = AddUserDialog(self)
if dialog.exec_():
firstname = dialog.lineEditPrenom.text()
lastname = dialog.lineEditNom.text()
self.UserComboBox.addItem('{} {}'.format(firstname, lastname))
Alternatively, you can overwrite the exec_() method of the dialog and return the values if the dialog is accepted:
class MainWindow(QMainWindow,MnWindow.Ui_MainWindow):
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
self.setupUi(self)
self.addUserButton.clicked.connect(self.addUser)
def addUser(self):
res = AddUserDialog(self).exec_()
if res:
self.UserComboBox.addItem(res)
class AddUserDialog(QDialog,AddUserDialog.Ui_Dialog):
def exec_(self):
if super().exec_():
firstname = self.lineEditPrenom.text()
lastname = self.lineEditNom.text()
return '{} {}'.format(firstname, lastname)
Both solutions work with the assumption that the user is accepting the dialog, which normally happens by pressing an "Ok" button. If you want to use a custom button, you should also call self.accept() at the end of the AddUser() function.
Consider that Qt provides the QDialogButtonBox for this purpose, and you don't need to add your own buttons. I believe you're probably using a QPushButton because the default buttons of QDialogButtonBox have fixed labels and none of them has the "Add" text, but those buttons can be renamed as you wish. Assuming you have a QDialogButtonBox on your dialog named buttonBox (the default for dialogs with buttons) with an "Ok" button, you can implement the json writing directly in the accept() function:
class AddUserDialog(QDialog,AddUserDialog.Ui_Dialog):
def __init__(self,parent=None):
super(AddUserDialog,self).__init__(parent)
self.setupUi(self)
self.buttonBox.button(self.buttonBox.Ok).setText('Add user')
def accept(self):
DateDeNaissance = self.dateEditDateDeNaissance.date().toString(Qt.ISODate)
DateDeSortie = self.dateEditDateDeSortie.date().toString(Qt.ISODate)
new_user = {
'firstname' : self.lineEditPrenom.text(),
'lastname' : self.lineEditNom.text(),
'DateDeNaissance' : DateDeNaissance[-2:] + DateDeNaissance[5:7] + DateDeNaissance[:4],
'LieuDeNaissance' : self.lineEditLieuNaissance.text(),
'Adresse' : self.lineEditAdresse.text(),
'Ville' : self.lineEditVille.text(),
'CodePostal' : self.lineEditCodePostal.text(),
'DateDeSortie' : DateDeSortie[-2:] + DateDeSortie[5:7] + DateDeSortie[:4],
'Heure' : str(self.timeEditSortie.time().hour()),
}
with open('TestJson.json','r') as f:
data = json.load(f)
data['users'].append(new_user)
with open('TestJson.json','w') as f:
json.dump(data,f,indent=3)
super().accept()
Note that Designer automatically connects the accepted and rejected signals of the button box to the relative accept and reject slots of the dialog. If you don't want to rebuild the whole GUI and still add a button box, just create a new dialog with buttons and ctrl-drag the buttonbox to your existing interface, but you have to recreate the connections manually; you can do that using the signal/slot edit mode of Designer (Edit menu -> Edit Signals/Slots, or F4) or within your code:
class AddUserDialog(QDialog,AddUserDialog.Ui_Dialog):
def __init__(self,parent=None):
super(AddUserDialog,self).__init__(parent)
self.setupUi(self)
self.buttonBox.button(self.buttonBox.Ok).setText('Add user')
self.buttonBox.accepted.connect(self.accept)
self.buttonBox.rejected.connect(self.reject)
# ...
You can pass the combo box as an argument when you create an instance of AddUserDialog:
class AddUserDialog(QDialog,AddUserDialog.Ui_Dialog):
def __init__(self,parent=None, combobox= None):
super(AddUserDialog,self).__init__(parent)
self.setupUi(self)
self.combobox = combobox
And in AddUser method, use self.combobox:
# MainWindow.UserComboBox.addItem(new_user['firstname'] + ' ' + new_user['lastname'])
self.combobox.addItem(new_user['firstname'] + ' ' + new_user['lastname'])
Alternatively, you already know the parent of the dialog when you make an instance. I think the super(AddUserDialog,self).__init__(parent) line will set self.parent for the dialog. Then in AddUser method, use this statement:
self.parent.UserComboBox..addItem(new_user['firstname'] + ' ' + new_user['lastname'])
One post right before mine, I found some Code I would like to use. There is an ComboPopup which has checkboxes in it. If one of These checkboxes is activated, I want to pass the selected text back to my class (i.e. MyForm). There is an StaticText called self.text. I want to Change the Label with the choosen Text of the ComboPopup.
I tried it with:
test = MyForm()
MyForm.OnUpdate(test,item.GetText())
as I thought that self.text is parent from MyForm(). But that doesn't work. No errors, but also no changes of the text.
What is self in this case? Is there a good way to find out what self is ? Like print the Name or anything :-)
My Code:
import wx
import wx.stc
from wx.lib.mixins.listctrl import CheckListCtrlMixin, ListCtrlAutoWidthMixin
class CheckListCtrl(wx.ListCtrl, CheckListCtrlMixin, ListCtrlAutoWidthMixin):
def __init__(self, parent):
wx.ListCtrl.__init__(self, parent, wx.ID_ANY, style=wx.LC_REPORT |
wx.SUNKEN_BORDER)
CheckListCtrlMixin.__init__(self)
ListCtrlAutoWidthMixin.__init__(self)
self.SetSize(-1, -1, -1, 50)
def OnCheckItem(self, index, flag):
item = self.GetItem(index)
if flag:
what = "checked"
else:
what = "unchecked"
print(f'{item.GetText()} - {what}')
test = MyForm()
MyForm.OnUpdate(test,item.GetText())
class ListViewComboPopup(wx.ComboPopup):
def __init__(self):
wx.ComboPopup.__init__(self)
self.lc = None
def AddItem(self, txt):
self.lc.InsertItem(0, txt)
def Init(self):
self.value = -1
self.curitem = -1
def Create(self, parent):
self.lc = CheckListCtrl(parent)
self.lc.InsertColumn(0, '', width=90)
return True
def GetControl(self):
return self.lc
def OnPopup(self):
wx.ComboPopup.OnPopup(self)
def GetAdjustedSize(self, minWidth, prefHeight, maxHeight):
return wx.ComboPopup.GetAdjustedSize(
self, minWidth, 110, maxHeight)
class MyForm(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title="Popup Menu")
self.panel = wx.Panel(self)
vsizer = wx.BoxSizer(wx.VERTICAL)
comboCtrl = wx.ComboCtrl(self.panel, wx.ID_ANY, "Select Text")
popupCtrl = ListViewComboPopup()
comboCtrl.SetPopupControl(popupCtrl)
popupCtrl.AddItem("Text One")
self.txt = wx.StaticText(self.panel,-1,style = wx.ALIGN_LEFT)
self.txt.SetLabel("Startup Text")
vsizer.Add(comboCtrl,1,wx.EXPAND)
vsizer.Add(self.txt,1,wx.EXPAND)
self.panel.SetSizer(vsizer)
def OnUpdate(self, txt):
self.txt.SetLabel(txt)
if __name__ == "__main__":
app = wx.App(False)
frame = MyForm().Show()
app.MainLoop()
Your wx.Frame subclass instance does not have a parent. You explicitly create it without one:
wx.Frame.__init__(self, None, title="Popup Menu")
You create an instance of MyForm in your __name__ == '__main__' block:
frame = MyForm().Show()
# Note: your name 'frame' holds the return value of the method Show(), i.e. a boolean
# This probably should rather read:
# frame = MyForm()
# frame.Show()
This is the MyForm instance you show in your app.
What you do here:
test = MyForm()
is creating a new instance of MyFrame (that has nothing to do with the one your app shows). You then call onUpdate on that new instance of your MyForm class
MyForm.OnUpdate(test,item.GetText())
Since you never Show() that new instance, you can't see the effect of your operation. However, you probably don't want/need that new instance anyway.
You need your instance from the main block.
There is a parent argument on the CheckListCtrl initializer. This might contain a chain of objects which you probably can ascend until you reach your MyForm instance.
I can't tell for sure, since it is not visible where and how this is called in the ListViewComboPopup:
def Create(self, parent):
self.lc = CheckListCtrl(parent)
Do a print(self.Parent) in OnCheckItem to see what it contains and then add another .Parent to self.Parent until you hopefully end up on a <__main__.MyForm instance [...]>. This is where you want to call the onUpdate Method. That should look similar to this:
self.Parent.Parent.Parent.OnUpdate(item.GetText())
# the number of '.Parent' my vary, based on where in the chain you find your MyForm instance
Edit
As per the OP's comment, the parent attribute on wx objects is spelled with a capital P. The respective code snippets have been updated accordingly.
I don't know what wx library does but there is a way to check where .text is.
You want vars() mixed with pprint():
from pprint import pprint
pprint(vars(your_object))
pprint(your_object) # this is OK too
Suggestion 2
type(x).__name__
This gets you the class name of an instance. You could insert this line before self.text. And give self as argument instead of x.
Original: Link
I am new to python and pyqt/pyside ...
i make customwidget class consist of 2 label (title & desc) which is example instance to add to Listwidget later ...
here is the complete clean code (pyside maya)
import PySide.QtCore as qc
import PySide.QtGui as qg
class CustomQWidget (qg.QWidget):
def __init__ (self, parent = None):
super(CustomQWidget, self).__init__(parent)
self.textQVBoxLayout = qg.QVBoxLayout()
self.titleLabel = qg.QLabel()
self.description = qg.QLabel()
self.textQVBoxLayout.addWidget(self.titleLabel)
self.textQVBoxLayout.addWidget(self.description)
self.setLayout(self.textQVBoxLayout)
def setTitle (self, text):
self.titleLabel.setText(text)
def setDescription (self, text):
self.description.setText(text)
class example_ui(qg.QDialog):
def __init__(self):
qg.QDialog.__init__(self)
self.myQListWidget = qg.QListWidget(self)
self.myQListWidget.currentItemChanged.connect(self.getTitleValue)
self.myQListWidget.setGeometry(qc.QRect(0,0,200,300))
# make instance customwidget item (just one)------
instance_1 = CustomQWidget()
instance_1.setTitle('First title')
instance_1.setDescription('this is a sample desc')
myQListWidgetItem = qg.QListWidgetItem(self.myQListWidget)
myQListWidgetItem.setSizeHint(instance_1.sizeHint())
self.myQListWidget.addItem(myQListWidgetItem)
self.myQListWidget.setItemWidget(myQListWidgetItem, instance_1)
def getTitleValue(self,val):
# i make assume something like below but didnt work
# print (self.myQListWidget.currentItem.titleLabel.text()
return 0
dialog = example_ui()
dialog.show()
now at getTitleValue function how do i get Title and desc value when i change selection ?
You should remember that the list items and corresponding widgets are not the same. Luckily, QListWidget tracks them and gives you access to the displayed widget if you provide the list item:
class example_ui(qg.QDialog):
def getTitleValue(self,val):
# parameter val is actually the same as self.myQListWidget.currentItem
selected_widget = self.myQListWidget.itemWidget(val)
print selected_widget.titleLabel.text()
return 0
Side note: I had to add a main loop in order for the app to be executed at all:
import sys # to give Qt access to parameters
# ... class definitions etc. ...
app = qg.QApplication(sys.argv)
dialog = example_ui()
dialog.show()
exec_status = app.exec_() # main loop
I am trying to create a look up table to connect wxTreeItem to objects. Upon selecting or double clicking on the item an action should be taken on this object.
Mysteriously, I found that item instance returned after AppendItem is either a copy of the real item appended to the tree or self.tree.GetSelection() and event.GetItem() return a copy of the item in question.
import wx
class RandomObj(object):
def __init__(self, name):
self.name = name
class TreeExample(wx.Frame):
def __init__(self):
wx.Frame.__init__(self, None, title='Tree Example', size=(200, 130))
self.tree = wx.TreeCtrl(self, size=(200, 100))
root = self.tree.AddRoot('root')
self.itemLUT = {}
for obj in [RandomObj('item1'), RandomObj('item2'), RandomObj('item3')]:
item = self.tree.AppendItem(root, obj.name)
print item
self.itemLUT[id(item)] = obj
self.itemLUT[id(obj)] = item
self.Bind(wx.EVT_TREE_ITEM_ACTIVATED, self.OnActivated, self.tree)
self.tree.Expand(root)
def OnActivated(self, event):
item = event.GetItem()
print 'Double clicked on', self.tree.GetItemText(item)
print id(item) in self.itemLUT.keys()
print self.tree.GetSelection()
print item
app = wx.PySimpleApp(None)
TreeExample().Show()
app.MainLoop()
Any suggestions? is there any proper way to connect and access an object upon an action (mouse or keyboard) on an tree item.
The best way is just to put your data into the item with SetItemData:
item = self.tree.AppendItem(root, obj.name)
self.tree.SetItemData(item,obj)
Then later, you can use GetItemData to extract the data back out of the item. You can put just about anything in there.
a good way to do it is
item = self.tree.AppendItem(root, obj.name)
self.tree.SetItemData(item, wx.TreeItemData(obj))
and in the event method
def OnActivated(self, event):
item = event.GetItem()
itemObject = self.tree.GetItemData(item).GetData()
I have a simple PyQt4 program that takes in 3 Particulars (Name,Gender & Address) whenever i clicked on OK button and save it as a binary file (3 particulars are hard coded in program for testing purpose). Then later will load that information back and display it in QTableWidget.
This is the layout of my program:
It has 2 scripts: DContainer.py and Data_Main.py
Dcontainer.py
import bisect
from PyQt4 import QtCore
class Person(object):
def __init__(self, Name = None, Gender = None , Address = None ):
self.Name = Name
self.Gender = Gender
self.Address = Address
class PersonContainer(object):
def __init__(self):
self.__fname = QtCore.QString("mydatabase.mqb")
self.__persons = []
self.__personFromId = {}
def __iter__(self):
for pair in iter(self.__persons):
yield pair[1]
def __len__(self):
return len(self.__persons)
def Clear(self):
self.__persons = []
self.__personFromId ={}
def add(self,person):
if id(person)in self.__personFromId:
return False
key = person.Name
bisect.insort_left(self.__persons, [key,person])
self.__personFromId[id(person)] = person
return True
def save(self):
fh = QtCore.QFile(self.__fname)
if not fh.open(QtCore.QIODevice.WriteOnly):
raise IOError , unicode(fh.errorString())
stream = QtCore.QDataStream(fh)
for key, person in self.__persons:
stream << person.Name << person.Gender << person.Address
def load(self):
fh = QtCore.QFile(self.__fname)
if not fh.open(QtCore.QIODevice.ReadOnly):
raise IOError , unicode(fh.errorString())
stream = QtCore.QDataStream(fh)
while not stream.atEnd():
Name = QtCore.QString()
Gender = QtCore.QString()
Address = QtCore.QString()
stream >> Name >> Gender >> Address
self.add(Person(Name,Gender,Address))
Data_Main.py
import sys
from PyQt4 import QtCore,QtGui
import DContainer
class MainDialog(QtGui.QDialog):
def __init__(self, parent = None):
super(MainDialog,self).__init__(parent)
self.InitGui()
self.persons = DContainer.PersonContainer()
self.Update()
def InitGui(self):
buttonbox = QtGui.QDialogButtonBox(QtGui.QDialogButtonBox.Ok|QtGui.QDialogButtonBox.Cancel)
self.table = QtGui.QTableWidget()
layout = QtGui.QVBoxLayout()
layout.addWidget(self.table)
layout.addWidget(buttonbox)
self.setLayout(layout)
self.connect(buttonbox.button(buttonbox.Ok), QtCore.SIGNAL("clicked()"),self.OK)
def OK(self):
NewPerson = DContainer.Person(QtCore.QString('This is another test'),QtCore.QString('Male'),QtCore.QString('Strand Road'))
self.persons.add(NewPerson)
self.persons.save()
self.Update()
def Update(self):
self.table.clear()
self.persons.load()
self.table.setRowCount(len(self.persons))
self.table.setColumnCount(3)
for row,person in enumerate(self.persons):
item = QtGui.QTableWidgetItem(person.Name)
self.table.setItem(row,0,item)
def Main():
app = QtGui.QApplication(sys.argv)
dialog = MainDialog()
dialog.show()
app.exec_()
if __name__ == "__main__":
Main()
My Problem is whenever i clicked on OK button, it create multiple table entries
After second click
It should not create multiple table entries as i have used
if id(person)in self.__personFromId:
return False
in my Add method in Dcontainer.py.
Rightfully, it should only show one item in the table unless i give the new person object with different name.
What is causing the problem?
The PersonContainer.add method is called twice when you click the OK button:
Directly from the MainDialog.OK method
Indirectly from the MainDialog.Update method, with self.persons.load()
You can add an optional argument to the Update method to trigger the call to load:
def Update(self, load=False):
self.table.clear()
if load:
self.persons.load()
And call this method with load set to True in the __init__ method:
def __init__(self, parent = None):
super(MainDialog,self).__init__(parent)
self.InitGui()
self.persons = DContainer.PersonContainer()
self.Update(True)
By the way, the old style signal/slot is no longer supported with PyQt5. This is how to write in the new style:
buttonbox.accepted.connect(self.OK)
buttonbox.rejected.connect(self.reject)