The code below creates a simple QComboBox. But instead of using a "traditional" .addItem('myItemName') method it creates QStandardItem first and then adds it via QComboBox's .model().appendRow(). Since now I can access each QStandardItem individually I wonder if there is a way to assign CSS to each of them (to each QStandardItem) individually. The goal is to customize each item displayed in ComboBox pulldown menu. So far I am only able to assign a single CSS style to entire ComboBox globally.
from PyQt4 import QtCore, QtGui
app = QtGui.QApplication([])
class Combo(QtGui.QComboBox):
def __init__(self, *args, **kwargs):
super(Combo, self).__init__()
for each in ['Item_1','Item_2','Item_3','Item_4','Item_5']:
item=QtGui.QStandardItem(each)
self.model().appendRow(item)
tree=Combo()
sys.exit(app.exec_())
It looks like this class has no setStyleSheet method, but you can use setBackground, setForeground and setTextAlignment methods. With QBrush you be able to customize elements. Of course it isn't so powerful as styleSheets but better than nothing.
http://pyqt.sourceforge.net/Docs/PyQt4/qstandarditem.html
Related
I'm trying to add custom AxisItem in pyqtgraph to existing PlotWidget that was generated by Qt Designer. There is related topic here, but there is no exact answer with code example and I cannot comment, so I've created a new topic.
This is my custom AxisItem (based on this code):
import pyqtgraph as pg
import datetime
def int2td(ts):
return(datetime.timedelta(seconds=float(ts)/1e6))
class TimeAxisItem(pg.AxisItem):
def __init__(self, *args, **kwargs):
super(TimeAxisItem, self).__init__(*args, **kwargs)
def tickStrings(self, values, scale, spacing):
return [int2dt(value).strftime("%H:%M:%S") for value in values]
This is my main QtPlotter class:
from pyqtgraph.Qt import QtGui
from template_pyqt import Ui_Form # Ui_Form is generated by Qt Designer
class QtPlotter:
def __init__(self):
self.app = QtGui.QApplication([])
self.win = QtGui.QWidget()
self.ui = Ui_Form()
self.ui.setupUi(self.win)
self.win.show()
self.ui_plot = self.ui.plot
self.ui_plot.showGrid(x=True, y=True)
And then I'm trying to add my custom AxisItem:
self.ui_plot.getPlotItem().axes['bottom']['item'] = TimeAxisItem(orientation='bottom')
I have no errors, but this does not give any effect.
It was so easy. The PlotItem class does not have a setAxis method. Instead of that there is a method named setAxisItems. this code worked for me.
date_axis = pg.graphicsItems.DateAxisItem.DateAxisItem(orientation = 'bottom')
self.mainSoundPlot.setAxisItems(axisItems = {'bottom': date_axis})
I know this is an old post but I am encountering a similar issue and came across a solution in this gist example, which is an AxisItem subclass that implements an attachToPlotItem method.
So regarding the original post, instead of
self.ui_plot.getPlotItem().axes['bottom']['item'] = TimeAxisItem(orientation='bottom')
you need the following method (copied from the example linked above) in your TimeAxisItem subclass:
def attachToPlotItem(self, plotItem):
"""Add this axis to the given PlotItem
:param plotItem: (PlotItem)
"""
self.setParentItem(plotItem)
viewBox = plotItem.getViewBox()
self.linkToView(viewBox)
self._oldAxis = plotItem.axes[self.orientation]['item']
self._oldAxis.hide()
plotItem.axes[self.orientation]['item'] = self
pos = plotItem.axes[self.orientation]['pos']
plotItem.layout.addItem(self, *pos)
self.setZValue(-1000)
It even retains the original AxisItem so that it could be re-instated if needed.
Then in your QtPlotter constructor, you'd add
axis = TimeAxisItem(orientation='bottom')
axis.attactToPlotItem(self.ui_plot.getPlotItem())
The PlotItem class does not have a setAxis method, only a getAxis method to get the current axis. You can specify a dictionary with axis items in the axisItems parameter of the PlotItem constructor, but it doesn't seem possible to update an AxisItem of an existing PlotItem.
Even though the PlotItem.axes dictionary is a public attribute, it is undocumented and just using it is therefore a bit risky. There is a possibility that the author of PyQtGraph will alter its behavior, or rename it (although the chance of this happening is small). IMHO it should have been made a private attribute.
In any case, by looking at the source of the PlotItem constructor you can see how the axis items from axisItem parameter are added to the plot item:
## Create and place axis items
if axisItems is None:
axisItems = {}
self.axes = {}
for k, pos in (('top', (1,1)), ('bottom', (3,1)), ('left', (2,0)), ('right', (2,2))):
if k in axisItems:
axis = axisItems[k]
else:
axis = AxisItem(orientation=k, parent=self)
axis.linkToView(self.vb)
self.axes[k] = {'item': axis, 'pos': pos}
self.layout.addItem(axis, *pos)
axis.setZValue(-1000)
axis.setFlag(axis.ItemNegativeZStacksBehindParent)
Perhaps you can make it work by looking at the code above. Again, this is undocumented behavior, use at your own risk! Furthermore, you should properly remove and unlink the old AxisItem when setting a new one to prevent memory leaks. Perhaps this is tricky and this could be the reason why the PlotItem.setAxis method does not exist.
I looked into the source of the PlotItem constructor for a complete day, but I couldn't get it to work.
In the end, I found that QtDesigner/QtCreator only outputs three lines on the PlotWidget. Instead of trying to add a TimeAxisItem to the existing PlotWidget, it is easier (but definitely not nicer) to just delete the existing PlotWidget and then make a new one with a TimeAxisItem, as described here.
from PyQt5 import QtCore
import pyqtgraph as pg
parent = self.ui_plot.parent()
geom_object = self.ui_plot.frameGeometry()
geometry = QtCore.QRect(geom_object.left(), geom_object.top(), geom_object.width(), geom_object.height())
object_name = self.ui_plot.objectName()
del self.ui_plot
time_axis = TimeAxisItem(orientation='bottom')
self.ui_plot = pg.PlotWidget(parent, axisItems={'bottom': time_axis})
self.ui_plot.setGeometry(geometry)
self.ui_plot.setObjectName(object_name)
Since there are people that try to find something here, I will post my simple but not elegant workaround.
After converting my template generated by Qt Designer to python template file with pyuic, I'm adding my custom AxisItem directly to python template file by replacing corresponding PlotWidget. This all can be done with simple bash script:
pyuic4 template.ui -o template.py
match="from PyQt4 import QtCore, QtGui"
insert_class="from timeaxisitem_class import TimeAxisItem"
match_widget="self.plot = PlotWidget(Form)"
insert_timeaxis="self.plot = PlotWidget(Form, axisItems={'bottom': TimeAxisItem(orientation='bottom')})"
file="template.py"
sed -i "s/$match/$match\n$insert_class/" $file
sed -i "s/$match_widget/$insert_timeaxis/" $file
If I've created a QMainWindow instance, and assign the central widget and dock widgets for it:
class MyWindow(QtGui.QMainWindow):
def __init__(self, MyCentralWidget, MyDockWidget):
super(MyWindow,self).__init__()
self.setCentralWidget(MyCentralWidget)
self.addDockWidget(Qt.LeftDockWidgetArea,MyDockWidget)
self.show()
Instance=MyWindow(A,B)
Then I think I can get access to the center widget A by Instance.centralWidget(), but how can I get access to the dockwidget in the LeftDockWidgetArea similarly? (for example, I want to modify the properties of the Widget in that dockwidget after instance is created)
try:
self.dockWidgetContents.your_widget...
I want to improve my code but currently have not much idea how.
So I used Qt Designer and created a main window plus 3 dialogs which can be opened from main window. Converted .ui files to .py files and created the MainWindow class which manages all.
Everything works fine, but for me this looks wrong:
class MainWindow(QMainWindow, Ui_MainWindow):
# init and else
[...]
def open_add_dialog(self):
self.dialog = AddDialog()
self.dialog.show()
def open_edit_dialog(self):
self.dialog = EditDialog()
self.dialog.show()
def open_about_dialog(self):
self.dialog = AboutDialog()
self.dialog.show()
def assign_widgets(self):
self.actionAdd.triggered.connect(self.open_add_dialog)
self.actionEdit.triggered.connect(self.open_edit_dialog)
self.actionAbout.triggered.connect(self.open_about_dialog)
Code is simplified.. So as you see I've 3 almost equal methods. So the question comes to my mind is it possible to merge all into one? What I want is something like this:
def open_dialog(self):
sender = self.sender()
sender.show()
I think you should never use the sender method of Qt because it makes calling the method from another function impossible, you can then only use it via the signal/slot mechanism. It therefore says in the documentation that: "This function violates the object-oriented principle of modularity". Using it during debugging is fine, of course.
In your case the methods are quite small. You could use lambdas in the connect statement so that you don't have to make separate methods. Or you could create the dialogs in the constructor and only connect to the show methods. Like this:
class MainWindow(QMainWindow, Ui_MainWindow):
def __init__(self):
self.add_dialog = AddDialog()
self.edit_dialog = EditDialog()
self.about_dialog = AboutDialog()
def assign_widgets(self):
self.actionAdd.triggered.connect(self.add_dialog.show)
self.actionEdit.triggered.connect(self.edit_dialog.show)
self.actionAbout.triggered.connect(self.about_dialog.show)
Im making a QTableWidget in Pyqt and ran into a bit of an annoying hiccup.
I need to use widgets in my table for its functionality, so im using setCellWidget to add them to the table. However, widgets dont have the same methods available as QTableWidgetItem's do (especially regarding selection in the table).
Im wondering if its possible to do something subclassing both items, so i can have the methods of both, and how i woulda dd that to the table.
Something like:
class TableItem(QtGui.QTableWidgetItem, QtGui.QWidget):
def __init__(self, parent=None):
super(TableItem, self).__init__(parent)
self.check = QtGui.QCheckBox()
self.label = QtGui.QLabel('Some Text')
self.h_box = QtGui.QHBoxLayout()
self.h_box.addWidget(self.check)
self.h_box.addWidget(self.label)
and then somehow add that to my table as a TableWidgetItem so it displays widgets and also has selection methods available.
Any ideas here?
For reference:
setCellWidget: http://pyqt.sourceforge.net/Docs/PyQt4/qtablewidget.html#setCellWidget
QWidget: (easy to find, i cant post more than 2 links)
-Which doesnt have the nice methods for a table
QTableWidgetItem: http://pyqt.sourceforge.net/Docs/PyQt4/qtablewidgetitem.html#type
with isSelected and setSelected (Methods not avialble from a widget used in setCellWidget.
To return the widget in a cell you can use table.cellWidget(row, column) and then use your widgets methods on that. But beacuse setSelected and isSelected arent methods of a widget, you cant check for selection. I was hoping to subclass the two together to allow for both
--Basically I need to know how to get my class to 'return' the proper type when i call it to add to the table with setItem
I am not sure what you want to do but you could "inject" a method like:
class TableWidgetItem(QtGui.QTableWidgetItem):
def __init__(self, parent=None):
QtGui.QTableWidgetItem.__init__(self)
def doSomething(self):
print "doing something in TableWidgetItem"
class Widget(QtGui.QWidget):
def __init__(self, parent=None):
QtGui.QWidget.__init__(self)
tableWidgetItem = TableWidgetItem()
widget = Widget()
def widgetFunction(self):
tableWidgetItem.doSomething()
# as an instance method
settatr(widget, "widgetFunction", MethodType(widgetFunction, widget, type(widget)))
# or as a class method
settatr(widget, "widgetFunction", widgetFunction)
Then you can:
>>>widget.widgetFunction()
doing something in TableWidgetItem
(not tested)
I am using Qt Designer to manage a large UI and Pyside for the code. I'm looking to display a ComboBox in my Table.
The following answer gives a pretty detailed description of how to accomplish this when managing the UI yourself:
PyQt - How to set QComboBox in a table view using QItemDelegate
My question is: How would this work when the UI is already created for me by pyside-uic? The code in the link includes, more or less:
class TableView(QtGui.QTableView):
def __init__(self, *args, **kwargs):
QtGui.QTableView.__init__(self, *args, **kwargs)
# Set the delegate for column 0 of our table
self.setItemDelegateForColumn(0, ComboDelegate(self))
[...]
class Widget(QtGui.QWidget):
self._tm=TableModel(self)
self._tv=TableView(self)
self._tv.setModel(self._tm)
Does this mean that if I have a UI file, I still need to write out, then call something like the TableView object myself? Should I create a TableView in Qt Designer, then overwrite it with one I've created that inherits from QTableView and adds setItemDelegate?
Thanks in advance!
The subclass in the example is redundant, because it doesn't re-implement or add any methods. It could be re-written like this:
from PySide import QtGui, QtCore
from mainwindowui import Ui_MainWindow
class MainWindow(QtGui.QMainWindow, Ui_MainWindow):
def __init__(self):
QtGui.QMainWindow.__init__(self)
self.setupUi(self)
self._model = TableModel(self)
self._delegate = ComboDelegate(self)
self.tableView.setModel(self._model)
self.tableView.setItemDelegateForColumn(0, self._delegate)
Thus, the table-view is accessed as an attribute of the object that is passed to the setupUi method of the UI class generated by pyside-uic.
But what if you did need to subclass one of the classes from Qt Designer? The way to do that is to promote it to a custom class that will replace it when the python UI module is imported.
If you right-click a widget in Qt Designer and select "Promote to...", it will bring up a dialog. In the dialog, you can set a custom class name (say, "TableView"), and also set the header file to the python module it should be imported from (say, "mypkg.mainwindow"). If you then click "Promote", Qt Designer will show the classname as TableView rather than QTableView.
After re-generating the UI module, you'll then see an extra import line at the bottom of the file that looks like this:
from mypkg.mainwindow import TableView
So the custom TableView class will be used instead of a QTableView, and get all the settings from Qt Designer - plus whatever re-implemented stuff you want to add yourself.