iagerogiannis
iagerogiannis

Reputation: 377

Column Alignment in QTreeView | PyQt5

How can I horizontally align a column (all items of this column) of a QTreeView? I am using the QStandardItemModel(). I have searched, but I have not found any relevant documentation. Thank you in advance!

Upvotes: 0

Views: 1731

Answers (2)

musicamante
musicamante

Reputation: 48231

Note: the proposed solution is only valid for basic cases as the one asked by the OP (a simple QStandardItemModel or any subclassed model that provides basic model data display); read more at the bottom of this answer

You can subclass the model and override the data() function in order to return a proper Qt.AlignmentFlag when the Qt.TextAlignmentRole is requested, which, by default, is None unless manually set with setData() or item.setTextAlignment(): when the returned value is None, the alignment is decided by the view, and is normally based on the system's layout direction.

In the following example, you can set an arbitrary default alignment for any column (or use None to restore it) which will be used unless the alignment has been explicitly set as written above.

class AlignModel(QtGui.QStandardItemModel):
    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.columnAlignments = {}

    def setColumnAlignment(self, column, alignment):
        if alignment:
            self.columnAlignments[column] = alignment
        elif column in self.columnAlignments:
            self.columnAlignments.pop(column)

    def data(self, index, role=QtCore.Qt.DisplayRole):
        if role == QtCore.Qt.TextAlignmentRole:
            return (super().data(index, role) or 
                self.columnAlignments.get(index.column()))
        return super().data(index, role)

# set the center alignment for the *second* column:
model = AlignModel()
model.setColumnAlignment(1, QtCore.Qt.AlignCenter)

Note that Qt.AlignCenter implies both Qt.AlignVCenter and Qt.AlignHCenter (through an OR operator), so if you want to align to the right or the left, you must also provide the vertical alignment: for example, Qt.AlignRight|Qt.AlignVCenter.

Some important notes:

As indicated by eyllanesc, the common suggestion is to use a delegate instead of subclassing the model. While my solution is fine for situations where a very simple solution is required (and a model can be actually used, which is not the case for higher the level classes, QTreeWidget, QTableWidget and QListWidget), the general rule is that the model should never return "changed" contents: the returned value from data() should always reflect the actual contents of the index, and any modifications (including displaying data such as text alignment) should happen using a proxy model or by changing the behavior of the delegate.
This is very important from the modular point of view typical of OOP, especially considering the MVC (or MV, for Qt) pattern: the model (the real data) and the view (how the data is shown) should be distinct and independent.

Take for instance this very case: text alignment. Let's suppose that the alignment should be towards the "beginning" of the first column: for right-to-left languages that would mean aligning the text on the right (since the first column is on the right edge); if the model is going to be serialized, the result is that alignment would be dependent on the source layout of the serialization, while the alignment should be completely dependent on the layout of the user viewing (unserializing) it.
So, that's what you should ask: is the text alignment important for the model (for example, the data could contain text in various languages with different writing directions), or for the view? If any of that doesn't really matter, then you have to think about what's the best solution, performance-wise: for instance, if you're going to have a very big model (with thousands of entries, and possibly hundreds of items shown at the same time), and you're sure that a certain column is going to always have the same alignment, then you could override data() or use a QIdentityProxyModel, which would be certainly faster than using a delegate and overriding the paint method.
In any other case, the delegate solution is a more correct and coherent one, as it also allows displaying the same model data in different ways, no matter what the contents are.

Upvotes: 2

eyllanesc
eyllanesc

Reputation: 243907

Solutions that do not involve inheriting from the model (which is sometimes a limitation such as QListWidget, QTableWidget or QTreeWidget) are:

  • Use a proxy model (QIdentityProxyModel for example).
  • Use delegates.

In this case I will use the last solution:

from functools import cached_property


class AlignmentDelegate(QStyledItemDelegate):
    @cached_property
    def alignment(self):
        return dict()

    def set_column_alignment(self, column, alignment):
        self.alignment[column] = alignment

    def initStyleOption(self, option, index):
        super().initStyleOption(option, index)
        alignment = self.alignment.get(index.column(), None)
        if alignment is not None:
            option.displayAlignment = alignment
delegate = AlignmentDelegate(self.view)
self.view.setItemDelegate(delegate)
delegate.set_column_alignment(1, QtCore.Qt.AlignCenter)

The advantage of the previous method is that it can be applied to any model and is easy to modify.

Upvotes: 3

Related Questions