PyQt Get specific value in ListWidget - python

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

Related

How to call function from other class with self?

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

How to connect QTreeWidget and QStackedWidget in PyQt4?

I'm sorry but just a beginner of Python.
I just want to change index of QStackedWidget by the item click of QTreeWidget. I searched for the tutorials of SIGNAL and SLOT online, but just cannot solve the problem.
The parameters in QTreeWidget signal and QStackedWidget slot are not fitted.
self.connect(qtree, QtCore.SIGNAL("itemClicked(QTreeWidgetItem*,int)"), stack, QtCore.SLOT("setCurrentIndex(int)"))
And I tried this:
qtree.itemClicked.connect(stack.setCurrentIndex)
It just showed the error:
TypeError: setCurrentIndex(self, int): argument 1 has unexpected type 'QTreeWidgetItem'
I think there may be a method, but I cannot find on the network.
Like this:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
class StockDialog(QDialog):
def __init__(self,parent=None):
super(StockDialog,self).__init__(parent)
mainSplitter=QSplitter(Qt.Horizontal)
treewidget = QTreeWidget(mainSplitter)
treewidget.setHeaderLabels(["Tree"])
treeroot = QTreeWidgetItem(treewidget, ["Stack"])
treeitem1 = QTreeWidgetItem(["WorkSpace"])
treeitem2 = QTreeWidgetItem(["About"])
treeroot.addChild(treeitem1)
treeroot.addChild(treeitem2)
stack=QStackedWidget(mainSplitter)
stack.setFrameStyle(QFrame.Panel|QFrame.Raised)
stackworkspace=StackWorkSpace()
stackabout=StackAbout()
stack.addWidget(stackworkspace)
stack.addWidget(stackabout)
closePushButton=QPushButton(self.tr("Close"))
self.connect(treewidget,
SIGNAL("itemClicked(int)"),
stack,SLOT("setCurrentIndex(int)"))
self.connect(closePushButton,
SIGNAL("clicked()"),
self,SLOT("close()"))
layout=QVBoxLayout(self)
layout.addWidget(mainSplitter)
layout.addWidget(closePushButton)
self.setLayout(layout)
class StackWorkSpace(QWidget):
def __init__(self,parent=None):
super(StackWorkSpace,self).__init__(parent)
widget1=QTextEdit(self.tr("WorkSpace"))
widget2=QTextEdit(self.tr("WorkSpace"))
layout=QGridLayout(self)
layout.addWidget(widget1,0,0)
layout.addWidget(widget2,0,1)
class StackAbout(QDialog):
def __init__(self,parent=None):
super(StackAbout,self).__init__(parent)
self.setStyleSheet("background: red")
app=QApplication(sys.argv)
main=StockDialog()
main.show()
app.exec_()
When change the QTreeWidget to the QListWidget in StockDialog class, it works.
class StockDialog(QDialog):
def __init__(self,parent=None):
super(StockDialog,self).__init__(parent)
mainSplitter=QSplitter(Qt.Horizontal)
listwidget=QListWidget(mainSplitter)
listwidget.insertItem(0,self.tr("WorkSpace"))
listwidget.insertItem(1,self.tr("About"))
stack=QStackedWidget(mainSplitter)
stack.setFrameStyle(QFrame.Panel|QFrame.Raised)
stackworkspace=StackWorkSpace()
stackabout=StackAbout()
stack.addWidget(stackworkspace)
stack.addWidget(stackabout)
closePushButton=QPushButton(self.tr("Close"))
self.connect(listwidget,
SIGNAL("currentRowChanged(int)"),
stack,SLOT("setCurrentIndex(int)"))
self.connect(closePushButton,
SIGNAL("clicked()"),
self,SLOT("close()"))
layout=QVBoxLayout(self)
layout.addWidget(mainSplitter)
layout.addWidget(closePushButton)
self.setLayout(layout)
Now, I want to do this with QTreeWidget, how can I do?
The strategy to solve this problem is to save the index information associated with each widget in the QTreeWidgetItem. QTreeWidgetItem has the setData() method that allows us to save information in the item and in this case we will save the index. The index is returned every time you add a widget to QStackedWidget through addWidget(), so in summary we will do the following:
treeitem1.setData(0, Qt.UserRole, stack.addWidget(stackworkspace))
treeitem2.setData(0, Qt.UserRole, stack.addWidget(stackabout))
After connecting the itemClicked signal of QTreeWidget, this returns the column and the item pressed, with this information we obtain the QStackedWidget index for it we recover the data saved through the function data():
treewidget.itemClicked.connect(lambda item, column: stack.setCurrentIndex(item.data(column, Qt.UserRole))
if item.data(column, Qt.UserRole) is not None else None)
The necessary code can be found in the following section:
class StockDialog(QDialog):
def __init__(self, parent=None):
super(StockDialog, self).__init__(parent)
mainSplitter = QSplitter(Qt.Horizontal)
treewidget = QTreeWidget(mainSplitter)
treewidget.setHeaderLabels(["Tree"])
treeroot = QTreeWidgetItem(treewidget, ["Stack"])
treeitem1 = QTreeWidgetItem(["WorkSpace"])
treeitem2 = QTreeWidgetItem(["About"])
treeroot.addChild(treeitem1)
treeroot.addChild(treeitem2)
stack = QStackedWidget(mainSplitter)
stack.setFrameStyle(QFrame.Panel | QFrame.Raised)
stackworkspace = StackWorkSpace()
stackabout = StackAbout()
treeitem1.setData(0, Qt.UserRole, stack.addWidget(stackworkspace))
treeitem2.setData(0, Qt.UserRole, stack.addWidget(stackabout))
closePushButton = QPushButton(self.tr("Close"))
treewidget.itemClicked.connect(lambda item, column: stack.setCurrentIndex(item.data(column, Qt.UserRole))
if item.data(column, Qt.UserRole) is not None else None)
layout = QVBoxLayout(self)
layout.addWidget(mainSplitter)
layout.addWidget(closePushButton)
self.setLayout(layout)

Accessing property of dynamically created QStandardItems in PyQt5

I'm having a problem determining whether or not the checkboxes that are dynamically created have been checked or unchecked by the user in a simple GUI I've created.
I've adapted the relevant code and pasted it below. Although it may be easy to just create and name 4 QStandardItems, I'm dealing with many lists containing many different items that change quite a lot, so it isn't really feasible to create them myself.
Any help finding out how to access these properties would be much appreciated.
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
class Splash(QWidget):
def __init__(self):
super().__init__()
# imagine this is a very long list...
self.seasons = ['summer','autumn','winter','spring']
self.initUI()
def initUI(self):
layout = QVBoxLayout()
list = QListView()
model = QStandardItemModel()
list.setModel(model)
printbtn = QPushButton('print values')
printbtn.clicked.connect(self.print_action)
for season in self.seasons:
item = QStandardItem(season)
item.setCheckable(True)
model.appendRow(item)
model.dataChanged.connect(lambda: self.print_action(item.text()))
layout.addWidget(printbtn)
layout.addWidget(list)
self.setLayout(layout)
self.show()
def print_action(self, item):
print('changed', item)
if __name__ == '__main__':
import sys
app = QApplication(sys.argv)
ex = Splash()
sys.exit(app.exec_())
In short - I can detect when an item has been checked using model.dataChanged and connecting that to a function, but it cannot differentiate between the seasons.
If you keep a reference to the list (or the model), you can search for the items by their text, and then get their check-state:
def print_action(self):
model = self.list.model()
for text in 'summer', 'autumn', 'winter', 'spring':
items = model.findItems(text)
if items:
checked = items[0].checkState() == Qt.Checked
print('%s = %s' % (text, checked))
It seems you want to get notified when the checkState of a item has been changed.
In my opinion, there are possible two ways.
First way, QModel will emit "dataChanged" to refresh the view, so you can connect the signal which means the checkState of a item might be changed.
model.dataChanged.connect(self.test)
def test(self):
pass
Second way, use a timer to notify yourself and you check it by yourselves.
timer = QTimer()
timer.timeout.connect(self.test)
timer.start(1000)

Editing a dictionary with a QTreeView

I have a reduced code sample in which a window is created with just one Button.
Pressing it will pop a Qt dialog TestDialog which takes as parameter a Python dictionary. This dictionary is displayed in an editable QTreeView inside the dialog.
After changing some values you can click on Ok or Cancel to either accept or discard the changes. Once the dialog is closed my intention is to retrieve from the main window the modified dictionary calling dialog.get_data() which, right now only returns the original unmodified dictionary. After clicking the Ok button, the retrieved dictionary is printed to stdout.
My question is, how can I modify the dictionary when the tree view is modified? Is there a way to automatically attach a function to be executed on each item modification? So that when editing, for example, a float in the tree view, then the corresponding value will be updated as float in the dictionary?
The dictionary does not have a fixed size and the types on it may change. The list of types is limited and known though and, for this example, could be reduced to {int, str, float, Other}. It can be assumed as well that the parents are not supposed to be editable and the children are only editable in the second column, just as it is shown in the example bellow.
Here is the code I have:
import sys
from PyQt4 import QtGui, QtCore, uic
from copy import deepcopy
class TestDialog(QtGui.QDialog):
def __init__(self, data):
super(TestDialog, self).__init__()
self.data = deepcopy(data)
# Layout
btOk = QtGui.QPushButton("OK")
btCancel = QtGui.QPushButton("Cancel")
self.tree = QtGui.QTreeView()
hbox = QtGui.QHBoxLayout()
hbox.addStretch(1)
hbox.addWidget(btOk)
hbox.addWidget(btCancel)
vbox = QtGui.QVBoxLayout()
vbox.addLayout(hbox)
vbox.addWidget(self.tree)
self.setLayout(vbox)
self.setGeometry(300, 300, 600, 400)
# Button signals
btCancel.clicked.connect(self.reject)
btOk.clicked.connect(self.accept)
# Tree view
self.tree.setModel(QtGui.QStandardItemModel())
self.tree.setAlternatingRowColors(True)
self.tree.setSortingEnabled(True)
self.tree.setHeaderHidden(False)
self.tree.setSelectionBehavior(QtGui.QAbstractItemView.SelectItems)
self.tree.model().setHorizontalHeaderLabels(['Parameter', 'Value'])
for x in self.data:
if not self.data[x]:
continue
parent = QtGui.QStandardItem(x)
parent.setFlags(QtCore.Qt.NoItemFlags)
for y in self.data[x]:
value = self.data[x][y]
child0 = QtGui.QStandardItem(y)
child0.setFlags(QtCore.Qt.NoItemFlags |
QtCore.Qt.ItemIsEnabled)
child1 = QtGui.QStandardItem(str(value))
child1.setFlags(QtCore.Qt.ItemIsEnabled |
QtCore.Qt.ItemIsEditable |
~ QtCore.Qt.ItemIsSelectable)
parent.appendRow([child0, child1])
self.tree.model().appendRow(parent)
self.tree.expandAll()
def get_data(self):
return self.data
class Other(object):
def __init__(self, x, y):
self.x = x
self.y = y
def __repr__(self):
return '(%s, %s)' % (self.x, self.y)
class Example(QtGui.QWidget):
def __init__(self):
super(Example, self).__init__()
btn = QtGui.QPushButton('Button', self)
btn.resize(btn.sizeHint())
btn.clicked.connect(self.show_dialog)
self.data = {}
# This example will be hidden (has no parameter-value pair)
self.data['example0'] = {}
# A set example with an integer and a string parameters
self.data['example1'] = {}
self.data['example1']['int'] = 14
self.data['example1']['str'] = 'asdf'
# A set example with a float and other non-conventional type
self.data['example2'] = {}
self.data['example2']['float'] = 1.2
self.data['example2']['other'] = Other(4, 8)
def show_dialog(self):
dialog = TestDialog(self.data)
accepted = dialog.exec_()
if not accepted:
return
self.data = deepcopy(dialog.get_data())
print self.data
if __name__ == '__main__':
app = QtGui.QApplication(sys.argv)
ex = Example()
ex.show()
sys.exit(app.exec_())
You can connect to the model's itemChanged signal:
self.tree.model().itemChanged.connect(self.handleItemChanged)
The handler would look something like this:
def handleItemChanged(self, item):
parent = self.data[item.parent().text()]
key = item.parent().child(item.row(), 0).text()
parent[key] = type(parent[key])(item.text())
Note that the conversion of values using type won't necessarily work for custom classes like Other. So you will have to either ensure that the constructor for such classes can convert a string representation, or parse the string into the appropriate arguments before passing them to the constructor.
Also note that I haven't bothered to deal with QString values in the above example code. If you use Python 3, this is not an issue, because they are automatically converted to/from Python strings by default. But for Python 2, you can switch this behaviour on by doing the following:
import sip
sip.setapi('QString', 2)
from PyQt4 import QtGui, QtCore, uic
For more details on this, see Selecting Incompatible APIs in the PyQt4 Docs.

PyQt4 QDialog connections not being made

I am working on an application using PyQt4 and the designer it provides. I have a main window application that works fine, but I wanted to create custom message dialogs. I designed a dialog and set up some custom signal/slot connections in the __init__ method and wrote an if __name__=='__main__': and had a test. The custom slots work fine. However, when I create an instance of my dialog from my main window application, none of the buttons work. Here is my dialog:
from PyQt4.QtGui import *
from PyQt4.QtCore import *
import sys
import encode_dialog_ui
# Ui_EncodeDialog is the python class generated by pyuic4 from the Designer
class EncodeDialog(encode_dialog_ui.Ui_EncodeDialog):
def __init__(self, parent, in_org_im, txt_file, in_enc_im):
self.qd = QDialog(parent)
self.setupUi(self.qd)
self.qd.show()
self.message = (txt_file.split("/")[-1] + " encoded into " +
in_org_im.split("/")[-1] + " and written to " +
in_enc_im.split("/")[-1] + ".")
QObject.connect(self.view_image_button, SIGNAL("clicked()"),
self.on_view_image_button_press)
self.org_im = in_org_im
self.enc_im = in_enc_im
self.encoded_label.setText(self.message)
def on_view_image_button_press(self):
print "hello world"
if __name__ == '__main__':
app = QApplication(sys.argv)
tmp = QMainWindow()
myg = EncodeDialog(tmp,'flower2.png','b','flower.png')
app.exec_()
If I run this class it works fine, and pressing the view_image_button prints hello world to the console. However when I use the call
#self.mw is a QMainWindow, the rest are strings
EncodeDialog(self.mw, self.encode_image_filename,
self.encode_txt_filename,
self.encode_new_image_filename)
in my main window class, the dialog displays correctly but the view_image_button does nothing when clicked. I have googled for a solution, but couldn't find anything useful. Let me know if you need any more information. Any help on this would be appreciated!
As requested below is some more code from my main window class for brevity's sake I have added ellipses to remove code that seemed irrelevant. If no one can think of anything still, I will add more. (If indenting is a little off, it happened in copy-pasting. The orignal code is correct)
class MyGUI(MainWindow.Ui_MainWindow):
def __init__(self):
self.mw = QMainWindow()
self.setupUi(self.mw)
self.mw.show()
self.encode_red_bits = 1
self.encode_blue_bits = 1
self.encode_green_bits = 1
self.decode_red_bits = 1
self.decode_blue_bits = 1
self.decode_green_bits = 1
self.encode_image_filename = ""
self.encode_new_image_filename = ""
self.encode_txt_filename = ""
self.decode_image_filename = ""
self.decode_txt_filename = ""
# Encode events
...
QObject.connect(self.encode_button, SIGNAL("clicked()"),
self.on_encode_button_press)
# Decode events
...
# Encode event handlers
...
def on_encode_button_press(self):
tmp = QErrorMessage(self.mw)
if (self.encode_image_filename != "" and
self.encode_new_image_filename != "" and
self.encode_txt_filename != ""):
try:
im = Steganography.encode(self.encode_image_filename, self.encode_txt_filename,
self.encode_red_bits, self.encode_green_bits,
self.encode_blue_bits)
im.save(self.encode_new_image_filename)
encode_dialog.EncodeDialog(self.mw, self.encode_image_filename,
self.encode_txt_filename,
self.encode_new_image_filename)
except Steganography.FileTooLargeException:
tmp.showMessage(self.encode_txt_filename.split("/")[-1] +
" is to large to be encoded into " +
self.encode_image_filename.split("/")[-1])
else:
tmp.showMessage("Please specify all filenames.")
# Decode event handlers
...
if __name__ == '__main__':
app = QApplication(sys.argv)
myg = MyGUI()
app.exec_()
It feels like the signal is just not getting passed from the parent down to your child QDIalog.
Try these suggestions:
Use the new method for connecting signals
Instead of extending the classes pyuic created, extend the actual QT classes and call the ones generated by pyuic
Your new code will look something like this:
class MyGUI(QMainWindow):
def __init__(self, parent=None):
QMainWindow.__init__(self, parent)
self.mw = MainWindow.Ui_MainWindow()
self.mw.setupUi(self)
self.mw.show()
...
self.encode_button.clicked.connect(self.on_encode_button_press)
...
class EncodeDialog(QDialog):
def __init__(self, parent, in_org_im, txt_file, in_enc_im):
QDialog.__init__(self, parent)
self.qd = encode_dialog_ui.Ui_EncodeDialog()
self.qd.setupUi(self)
self.qd.show()
...
self.view_image_button.clicked.connect(self.on_view_image_button_press)
...

Categories

Resources