I m getting problem to add modelEditor to QFrame
So plz help me if you can.Thank you
=>Here is my UI created in Qt Designer
http://farm3.staticflickr.com/2944/15230904617_a2b8608d2d_b.jpg
And here is code to load UI & add functionality
import maya.OpenMayaUI as mui
import sip
from PyQt4 import QtGui, QtCore, uic
import maya.cmds as mc
import maya.mel as mm
def getMayaWindow():
# ‘Get the maya main window as a QMainWindow instance’
ptr = mui.MQtUtil.mainWindow()
return sip.wrapinstance(long(ptr), QtCore.QObject)
def toQtObject(mayaName):
”’
Given the name of a Maya UI element of any type,
return the corresponding QWidget or QAction.
If the object does not exist, returns None
”’
ptr = apiUI.MQtUtil.findControl(mayaName)
if ptr is None:
ptr = apiUI.MQtUtil.findLayout(mayaName)
if ptr is None:
ptr = apiUI.MQtUtil.findMenuItem(mayaName)
if ptr is not None:
return sip.wrapinstance(long(ptr), QtCore.QObject)
uiFile = (‘D:/modEditorTestUI.ui’)
form_class, base_class = uic.loadUiType(uiFile)
class myUIClass(form_class, base_class):
def __init__(self, parent=getMayaWindow()):
super(myUIClass, self).__init__(parent)
self.setupUi( self )
self.snapView = mc.modelEditor(displayAppearance=’smoothShaded’, displayTextures=True, wos=False, camera=’persp’)
qtObj = toQtObject(self.snapView)
print (‘qtObj: ‘ + str(qtObj))
#methods
self.connectSignals()
def connectSignals(self):
“”"Connect all the UI signals”"”
print “Connect signals”
def runUI():
global app
global win
app=QtGui.qApp
win = myUIClass()
win.show()
runUI()
From the code snippet you have provided, it looks like the method toQtObject wouldn't be returning anything useful because of the indentation of the last if and return statements. Here's that fixed:
def toQtObject(mayaName):
"""
Given the name of a Maya UI element of any type,
return the corresponding QWidget or QAction.
If the object does not exist, returns None
"""
ptr = apiUI.MQtUtil.findControl(mayaName)
if ptr is None:
ptr = apiUI.MQtUtil.findLayout(mayaName)
if ptr is None:
ptr = apiUI.MQtUtil.findMenuItem(mayaName)
if ptr is not None:
return sip.wrapinstance(long(ptr), QtCore.QObject)
Related
how can I make progress bar for cloning git repository in pyqt5
git.Repo.clone_from('https://github.com/addddd123/Osdag', '_new_update')
You have to execute the task in another thread, connect to the callback and send the progress information through signals:
import threading
import sys
from dataclasses import dataclass, field
from typing import List, Optional, Any, Mapping, Dict
from PyQt5 import QtCore, QtWidgets
import git
class GitReply(QtCore.QObject):
pass
#dataclass
class GitCloneReply(GitReply):
progress_changed = QtCore.pyqtSignal(int)
started = QtCore.pyqtSignal()
finished = QtCore.pyqtSignal()
url: str
path: str
env: Optional[Mapping[str, Any]] = None
multi_options: Optional[List[str]] = None
kwargs: Dict[str, Any] = field(default_factory=dict)
def __post_init__(self):
super().__init__()
def start(self):
threading.Thread(target=self._execute, daemon=True).start()
def _execute(self):
self.started.emit()
repo = git.Repo.clone_from(
self.url,
self.path,
self.callback,
self.env,
self.multi_options,
**self.kwargs
)
self.finished.emit()
def callback(self, op_code, cur_count, max_count=None, message=""):
self.progress_changed.emit(int((cur_count / max_count) * 100))
#dataclass
class RepoManager(QtCore.QObject):
_replies: List[GitReply] = field(init=False, default_factory=list)
def __post_init__(self):
super().__init__()
def clone_from(self, url, path, env=None, multi_options=None, **kwargs):
reply = GitCloneReply(url, path, env, multi_options, kwargs)
reply.finished.connect(self.handle_finished)
reply.start()
self._replies.append(reply)
return reply
def handle_finished(self):
reply = self.sender()
if reply in self._replies:
self._replies.remove(reply)
def main():
app = QtWidgets.QApplication(sys.argv)
progressbar = QtWidgets.QProgressBar()
progressbar.show()
manager = RepoManager()
reply = manager.clone_from("https://github.com/addddd123/Osdag", "_new_update")
reply.progress_changed.connect(progressbar.setValue)
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main()
```
I've noticed that QFormLayout in Pyside2 does not have the takeRow method like its PyQt5 counterpart. I've attempted to subclass QFormLayout to incorporate a similar method, but I've run into Runtime Errors, as the removal behavor of the LabelRole item is different than the FieldRole item. Another issue being that the LabelRole item does not actually get taken off the row even when the row itself is removed.
The following is the test sample I've been working with using Python 3.8.6:
from PySide2.QtWidgets import *
import sys
class MyFormLayout(QFormLayout):
def __init__(self, *args, **kwargs):
super(MyFormLayout, self).__init__(*args, **kwargs)
self.cache = []
print(f"Formlayout's identity: {self=}\nwith parent {self.parent()=}")
def takeRow(self, row: int):
print(f"Called {self.takeRow.__name__}")
print(f"{self.rowCount()=}")
label_item = self.itemAt(row, QFormLayout.LabelRole)
field_item = self.itemAt(row, QFormLayout.FieldRole)
print(f"{label_item=}\n{field_item=}")
self.removeItem(label_item)
self.removeItem(field_item)
self.removeRow(row) ## <-- This seems necessary to make the rowCount() decrement. Alternative?
label_item.widget().setParent(None) ## <-- Runtime Error Here?
field_item.layout().setParent(None)
self.cache.append(label_item.widget(), field_item)
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
print(self.cache[0])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
return label_item, field_item
def restoreRow(self, insert_idx: int):
print(f"Called {self.restoreRow.__name__}")
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
to_insert = self.cache.pop()
self.insertRow(insert_idx, to_insert[0], to_insert[1])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
class MyWindow(QWidget):
def __init__(self):
super(MyWindow, self).__init__()
self.mainlay = MyFormLayout(self)
self.cmb = QComboBox()
self.cmb.addItems(["Placeholder", "Remove 1 and 2"])
self.cmb.currentTextChanged.connect(self.remove_rows_via_combo)
self.current_text = self.cmb.currentText()
self.hlay1, self.le1, self.btn1 = self.le_and_btn(placeholderText="1")
self.hlay2, self.le2, self.btn2 = self.le_and_btn(placeholderText="2")
self.hlay3, self.le3, self.btn3 = self.le_and_btn(placeholderText="3")
self.hlay4, self.le4, self.btn4 = self.le_and_btn(placeholderText="4")
self.remove_btn = QPushButton("Remove", clicked=self.remove_row_via_click)
self.restore_btn = QPushButton("Restore", clicked=self.restore_a_row_via_click)
self.mainlay.addRow("Combobox", self.cmb)
for ii, hlayout in zip(range(1, 5), [self.hlay1, self.hlay2, self.hlay3, self.hlay4]):
self.mainlay.addRow(f"Row {ii}", hlayout)
self.mainlay.addRow(self.remove_btn)
self.mainlay.addRow(self.restore_btn)
#staticmethod
def le_and_btn(**kwargs):
hlay, le, btn = QHBoxLayout(), QLineEdit(**kwargs), QPushButton()
hlay.addWidget(le)
hlay.addWidget(btn)
return hlay, le, btn
def remove_row_via_click(self):
self.mainlay.takeRow(1)
def restore_a_row_via_click(self):
self.mainlay.restoreRow(1)
def remove_rows_via_combo(self, text):
print(f"{self.remove_rows_via_combo.__name__} received the text: {text}")
if text == "Remove 1 and 2":
self.mainlay.takeRow(1)
self.mainlay.takeRow(1)
if __name__ == '__main__':
app = QApplication(sys.argv)
win = MyWindow()
win.show()
sys.exit(app.exec_())
I would like to understand why the behavior of the role items is different and how the method may be properly re-implemented.
The problem is that the label was created internally by Qt from a string, rather than by explicitly creating a QLabel in Python. This means that when the row is removed, the last remaining reference is also removed, which deletes the label on the C++ side. After that, all that's left on the Python side is an empty PyQt wrapper - so when you try to call setParent on it, a RuntimeError will be raised, because the underlying C++ part no longer exists.
Your example can therefore be fixed by getting python references to the label/field objects before the layout-item is removed:
class MyFormLayout(QFormLayout):
...
def takeRow(self, row: int):
print(f"Called {self.takeRow.__name__}")
print(f"{self.rowCount()=}")
label_item = self.itemAt(row, QFormLayout.LabelRole)
field_item = self.itemAt(row, QFormLayout.FieldRole)
print(f"{label_item=}\n{field_item=}")
# get refs before removal
label = label_item.widget()
field = field_item.layout() or field_item.widget()
self.removeItem(label_item)
self.removeItem(field_item)
self.removeRow(row)
label.setParent(None)
field.setParent(None)
self.cache.append((label, field))
print(f"{self.rowCount()=}")
print(f"{self.cache=}")
print(self.cache[0])
print("&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&&")
return label, field
I have a list of items in a QTreeView. Each item holds a QImage object. If I try to drag and drop the item, the program freezes. But when I comment out the line objMod._Image = QImage(flags = Qt.AutoColor), the program runs fine.
How can I drag and drop the items with the QImage object? The QImage holds an image which is rendered. The rendering process takes a while, so it would be nice to keep the QImage object.
import sys
import os
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtUiTools import *
from PIL import Image, ImageCms, ImageQt
class ObjModel:
def __init__(self):
self._Image = None
class DragMoveTest(QMainWindow):
def __init__(self):
super(DragMoveTest,self).__init__()
self.initGUI()
self.show()
def initGUI(self):
self.treeView = QTreeView()
modelTreeView = QStandardItemModel()
self.treeView.setModel(modelTreeView)
for i in range(0, 4):
objMod = ObjModel()
objMod._Image = None
objMod._Image = QImage(flags = Qt.AutoColor)
item = QStandardItem('Test: %s' % str(i))
item.setData(objMod, Qt.UserRole + 1)
modelTreeView.invisibleRootItem().appendRow(item)
self.treeView.setDragDropMode(QAbstractItemView.InternalMove)
self.setCentralWidget(self.treeView)
def main(args):
app = QApplication(sys.argv)
qt_main_wnd = DragMoveTest()
ret = app.exec_()
sys.exit(ret)
if __name__ == "__main__":
main(sys.argv)
This is caused by a bug in PySide. During a drag and drop operation, the data in the dragged item must be serialized. This will be handled by Qt for most data-types, but for types that are specific to Python, special handling is required. This special handling seems to be broken in PySide. If your example is converted to PyQt, a TypeError is raised when trying to drag items, but the program does not freeze.
The source of the problem is that you are storing data using a custom Python class. PyQt uses pickle to serialize custom data-types, but it is not possible to also pickle the QImage that is stored in its __dict__, so the operation fails. I assume PySide must attempt something similar, but for some reason it does not raise an error when it fails. Qt grabs the mouse whilst dragging, so if the operation fails abnormally, it won't be released again, and the program will appear to freeze.
The simplest way to fix this is to avoid using a custom class to hold the QImage, and instead store the image directly in the item:
image = QImage()
item = QStandardItem('Test: %s' % i)
item.setData(image, Qt.UserRole + 1)
To store more data items, you can either use a different data-role for each one, or use a dict to hold them all:
data = {'image': QImage(), 'title': 'foo', 'timestamp': 1756790}
item.setData(data, Qt.UserRole + 1)
However, if you do this, you must always use string keys in the dict, otherwise you will face the same problems as before. (Using string keys means the dict can be converted into a QMap, which Qt knows how to serialize).
(NB: if you want to know whether a Qt class can be serialized, check the docs to see whether it defines the datastream operators).
I come up with a different solution. It is easier to have a object that holds a io.BytesIO. Your store the ImageData into the bytesIO variable. Upon on your image library you can open the image from the bytesIO variable.
In the demo the class ObjModel can handle QImage and Image from Pillow/PIL. If you use the set methods the image object will be converted into a bytesIO.
In short here a working example:
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
import sys
import os
import io
from PySide.QtCore import *
from PySide.QtGui import *
from PySide.QtUiTools import *
from PIL import Image, ImageCms, ImageQt
########################################################################
class ObjModel:
""""""
#----------------------------------------------------------------------
def __init__(self):
"""Constructor"""
self._ImageByteIO = None
#----------------------------------------------------------------------
def getObjByte(self):
""""""
return self._ImageByteIO
#----------------------------------------------------------------------
def getQImage(self):
""""""
try:
self._ImageByteIO.seek(0)
qImg = QImage.fromData(self._ImageByteIO.getvalue())
return qImg
except:
return None
#----------------------------------------------------------------------
def getPILImage(self):
""""""
try:
self._ImageByteIO.seek(0)
img = Image.open(tBytesIO)
return img
except:
return None
#----------------------------------------------------------------------
def setObjByte(self, fileName):
""""""
try:
tBytesIO = io.BytesIO()
f = open (fileName, 'rb')
tBytesIO.write(f.read())
f.close()
self._ImageByteIO = tBytesIO
except:
self._ImageByteIO = None
#----------------------------------------------------------------------
def setQImage(self, qImg):
""""""
try:
tBytesIO = io.BytesIO()
qByteArray = QByteArray()
qBuf = QBuffer(qByteArray)
qBuf.open(QIODevice.ReadWrite)
qImg.save(qBuf, 'PNG')
tBytesIO = io.BytesIO()
tBytesIO.write(qByteArray.data())
self._ImageByteIO = tBytesIO
except:
self._ImageByteIO = None
#----------------------------------------------------------------------
def setPILImage(self, pImg):
""""""
tBytesIO = io.BytesIO()
pImg.save(tBytesIO, 'png')
self._ImageByteIO = tBytesIO
#----------------------------------------------------------------------
class DragMoveTest(QMainWindow):
def __init__(self):
""""""
super(DragMoveTest,self).__init__()
self.initGUI()
self.show()
#----------------------------------------------------------------------
def initGUI(self):
""""""
self.treeView = QTreeView()
modelTreeView = QStandardItemModel()
self.treeView.setModel(modelTreeView)
for i in range(0, 4):
objMod = ObjModel()
objMod.setQImage(QImage(flags = Qt.AutoColor))
item = QStandardItem('Test: %s' % str(i))
item.setData(objMod, Qt.UserRole + 1)
modelTreeView.invisibleRootItem().appendRow(item)
self.treeView.setDragDropMode(QAbstractItemView.InternalMove)
self.setCentralWidget(self.treeView)
#----------------------------------------------------------------------
def main(args):
app = QApplication(sys.argv)
qt_main_wnd = DragMoveTest()
ret = app.exec_()
sys.exit(ret)
#----------------------------------------------------------------------
if __name__ == "__main__":
main(sys.argv)
Please axplain how to enable and show a tooltip for each item in QTreeView. I found a sample of code class TreeModel(QAbstractItemModel) but due to my beginner's level I can't understand how to apply it to my needs.
Data for tooltip should be taken from value of key "note" in dictionary data_for_tree.
#!/usr/bin/env python -tt
# -*- coding: utf-8 -*-
from PyQt5.QtGui import *
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
import sys
reload(sys)
sys.setdefaultencoding('utf8')
data_for_tree = {"tomato":{"color":"red","ammount":"10", "note":"a note for tomato"},"banana":{"color":"yellow","ammount":"1", "note":"b note for banana"}, "some fruit":{"color":"unknown","ammount":"100", "note":"some text"}}
class TreeModel(QAbstractItemModel):
def data(self, index, role=Qt.DisplayRole):
#...
if role == Qt.ToolTipRole:
return 'ToolTip'
def flags(self, index):
if not index.isValid():
return Qt.NoItemFlags # 0
return Qt.ItemIsSelectable # or Qt.ItemIsEnabled
class ProxyModel(QSortFilterProxyModel):
def __init__(self, parent=None):
super(ProxyModel, self).__init__(parent)
def lessThan(self, left, right):
leftData = self.sourceModel().data(left)
rightData = self.sourceModel().data(right)
try:
return float(leftData) < float(rightData)
except ValueError:
return leftData < rightData
class MainFrame(QWidget):
def __init__(self):
QWidget.__init__(self)
self.MyTreeView = QTreeView()
self.MyTreeViewModel = QStandardItemModel()
self.MyTreeView.setModel(self.MyTreeViewModel)
self.most_used_cat_header = ['Name', "ammount", "color"]
self.MyTreeViewModel.setHorizontalHeaderLabels(self.most_used_cat_header)
self.MyTreeView.setSortingEnabled(True)
self.MyTreeView_Fill()
MainWindow = QHBoxLayout(self)
MainWindow.addWidget(self.MyTreeView)
self.setLayout(MainWindow)
def MyTreeView_Fill(self):
for k in data_for_tree:
name = QStandardItem(k)
ammount = QStandardItem(data_for_tree[k]["ammount"])
note = QStandardItem(data_for_tree[k]["color"])
tooltip = data_for_tree[k]["note"]
item = (name, ammount, note)
self.MyTreeViewModel.appendRow(item)
self.MyTreeView.sortByColumn(1, Qt.DescendingOrder)
proxyModel = ProxyModel(self)
proxyModel.setSourceModel(self.MyTreeViewModel)
self.MyTreeView.setModel(proxyModel)
c = 0
while c < len(self.most_used_cat_header):
self.MyTreeView.resizeColumnToContents(c)
c=c+1
if __name__ == "__main__":
app = QApplication(sys.argv)
main = MainFrame()
main.show()
main.move(app.desktop().screen().rect().center() - main.rect().center())
sys.exit(app.exec_())
As you are using the QStandardItem and QStandardItemModel classes (which is what I would recommend!) you don't need to bother with the TreeModel class you have found. Creating your own model is rarely necessary, but for some reason tutorials often encourage you to do so. If you find something encouraging you to subclass QAbstractItemModel, I suggest you check on stack overflow first to see if there is a simpler way to do it! In this case, there is a very simple way to add your tooltips.
If you look at the C++ documentation (which I often find more useful than the PyQt documentation for finding out what methods are available), you will see that QStandardItem has a method called setToolTip().
So all you need to do is call this method on each of the items you add to the model. For example, inside the loop in the MyTreeView_Fill method:
name = QStandardItem(k)
ammount = QStandardItem(data_for_tree[k]["ammount"])
note = QStandardItem(data_for_tree[k]["color"])
tooltip = data_for_tree[k]["note"]
name.setToolTip(tooltip)
ammount.setToolTip(tooltip)
note.setToolTip(tooltip)
Here I've set the tooltip to be the same for every cell in the row (name, amount and note) but you could easily change this to have a different tooltip for one of the cells (hopefully it is obvious how to do that)
Can anyone tell me why when I push test button, __get__ isnt called? I tried many things but I can't achieve desired behavior:
when lt or gt attribute accessed the value of appropriate QlineEdit must be returned if checkbox is checked
#!/usr/bin/python3
# -*- coding: utf-8 -*-
import sys
from PyQt5.QtWidgets import QApplication
from PyQt5.QtWidgets import QVBoxLayout, QRadioButton, QGroupBox, QLineEdit, QCheckBox, QGridLayout, QLabel, QFrame, QPushButton
from PyQt5.QtCore import Qt
from PyQt5.QtGui import QIntValidator
from PyQt5.QtWidgets import QMainWindow
class LineDescriptor:
def __init__(self, row=1, label=None):
self.label = label
self.row = row
self.__name = None
def __get__(self, instance, owner=None):
print('get_called')
if instance is None:
print('is none')
return self
else:
private_name = '_{0}__{1}'.format(type(instance).__name__,
self.__name)
check_name = '_{0}__{1}'.format(type(instance).__name__,
self.__name)
if getattr(instance, check_name).isChecked():
return getattr(instance, private_name).text()
else:
return None
class CheckSelector(QGroupBox):
def __init__(self, name):
self.__dictionary = []
super(CheckSelector, self).__init__()
layout = QGridLayout()
layout.setAlignment(Qt.AlignTop)
name = QLabel(name)
layout.addWidget(name, 0, 0, 1, 1)
self.__check = QCheckBox()
layout.addWidget(self.__check, 0, 1, 1, 1)
self.setLayout(layout)
for name, attribute in type(self).__dict__.items():
if isinstance(attribute, LineDescriptor):
row = attribute.row
if attribute.label is not None:
label = QLabel(attribute.label)
self.layout().addWidget(label, row, 0, 1, 1)
int_validator = QIntValidator()
field = QLineEdit()
field.setValidator(int_validator)
layout.addWidget(field, row, 1, 1, 1)
private_name = '_{0}__{1}'.format(type(self).__name__, name)
setattr(attribute, '_LineDescriptor__name', name)
setattr(self, name, attribute)
setattr(self, private_name, field)
print(attribute)
print(attribute.__dict__)
class GtCheck(CheckSelector):
gt = LineDescriptor(1, '>')
class BothCheck(CheckSelector):
gt = LineDescriptor(1, '>')
lt = LineDescriptor(2, '<')
def checker():
print(test.lt)
app = QApplication(sys.argv)
win = QMainWindow()
wid = QFrame()
lay = QVBoxLayout()
test = BothCheck(name='слов')
lay.addWidget(test)
but = QPushButton()
but.clicked.connect(checker)
lay.addWidget(but)
wid.setLayout(lay)
win.setCentralWidget(wid)
win.show()
sys.exit(app.exec_())
Here's the offending line:
setattr(self, name, attribute)
Your descriptor is a non-data descriptor (it does not implement __set__()). Thus, this sets the attribute in self.__dict__, creating an instance attribute. Instance attributes take precedence over non-data descriptors. If you immediately did this on the next line:
getattr(self, name)
...then the descriptor would not be invoked; it would just fetch attribute back out of self.__dict__ and return it directly. Since foo.bar is exactly the same as getattr(foo, "bar"), the foo.bar syntax won't invoke the descriptor either.
Data descriptors (descriptors which implement __set__()) take precedence over instance attributes. If you changed LineDescriptor to implement __set__() as well as __get__(), it would not exhibit this behavior. But then you would have to figure out how to implement __set__(). That implementation will depend on what you are trying to accomplish:
You could raise AttributeError to indicate the descriptor is read-only. Then you'd have to remove the setattr() call, since you can't set a read-only descriptor.
You could implement __set__() to do the reverse of __get__(). Given the complexity of your __get__() method, I'm not entirely sure how to do that. It'll also change the effect of your setattr() call.
If you don't want to change or remove the setattr() call, you can always manually set an instance attribute:
self.__dict__[name] = attribute
This bypasses the data descriptor, and does exactly what the setattr() call is currently doing. However, the attribute probably won't match up with __get__()'s return value. In my opinion, that's poor design, but it's not strictly wrong.