Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. General and Desktop
  4. QTableWidget and QStyledItemDelegate
Forum Updated to NodeBB v4.3 + New Features

QTableWidget and QStyledItemDelegate

Scheduled Pinned Locked Moved Unsolved General and Desktop
5 Posts 2 Posters 459 Views 1 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • PerdrixP Offline
    PerdrixP Offline
    Perdrix
    wrote on last edited by
    #1

    The information in one column of a table widget is quite long (full file path), so I am trying to use a styled item delegate to force it to elide in the middle.

    //
    // The filename is quite long (full path), so use a subclass
    // of QStyledItemDelegate to handle the rendering for column one
    // the table with the text set to elide in the middle.
    //
    elidedTextDelegate = new ElidedTextDelegate(this);
    
    imageList->setItemDelegateForColumn(static_cast<int>(ImageListColumns::File), elidedTextDelegate);
    

    Header information for the delegate:

    class ElidedTextDelegate : public QStyledItemDelegate
    {
    	Q_OBJECT
    
    public:
    	using QStyledItemDelegate::QStyledItemDelegate;
    
    
    protected:
    	void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
    	//inline QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
    	//{
    	//	QSize result = QStyledItemDelegate::sizeHint(option, index);
    	//	result.setHeight(result.height() * 1.2);
    	//	return result;
    	//}
    
    	QString calculateElidedText(const QString& text, const QTextOption& textOption,
    		const QFont& font, const QRect& textRect, const Qt::Alignment valign,
    		Qt::TextElideMode textElideMode, [[maybe_unused]] int flags,
    		bool lastVisibleLineShouldBeElided, QPointF* paintStartPosition) const;
    
    };
    

    Implementation:

    	static QSizeF viewItemTextLayout(QTextLayout& textLayout, int lineWidth, int maxHeight = -1, int* lastVisibleLine = nullptr)
    	{
    		if (lastVisibleLine)
    			*lastVisibleLine = -1;
    		qreal height = 0;
    		qreal widthUsed = 0;
    		textLayout.beginLayout();
    		int i = 0;
    		while (true) {
    			QTextLine line = textLayout.createLine();
    			if (!line.isValid())
    				break;
    			line.setLineWidth(lineWidth);
    			line.setPosition(QPointF(0, height));
    			height += line.height();
    			widthUsed = qMax(widthUsed, line.naturalTextWidth());
    			// we assume that the height of the next line is the same as the current one
    			if (maxHeight > 0 && lastVisibleLine && height + line.height() > maxHeight) {
    				const QTextLine nextLine = textLayout.createLine();
    				*lastVisibleLine = nextLine.isValid() ? i : -1;
    				break;
    			}
    			++i;
    		}
    		textLayout.endLayout();
    		return QSizeF(widthUsed, height);
    	}
    
    QString ElidedTextDelegate::calculateElidedText(const ::QString& text, const QTextOption& textOption,
    	const QFont& font, const QRect& textRect, const Qt::Alignment valign,
    	Qt::TextElideMode textElideMode, [[maybe_unused]] int flags,
    	bool lastVisibleLineShouldBeElided, QPointF* paintStartPosition) const
    {
    	QTextLayout textLayout(text, font);
    	textLayout.setTextOption(textOption);
    
    	// In AlignVCenter mode when more than one line is displayed and the height only allows
    	// some of the lines it makes no sense to display those. From a users perspective it makes
    	// more sense to see the start of the text instead something inbetween.
    	const bool vAlignmentOptimization = paintStartPosition && valign.testFlag(Qt::AlignVCenter);
    
    	int lastVisibleLine = -1;
    	viewItemTextLayout(textLayout, textRect.width(), vAlignmentOptimization ? textRect.height() : -1, &lastVisibleLine);
    
    	const QRectF boundingRect = textLayout.boundingRect();
    	// don't care about LTR/RTL here, only need the height
    	const QRect layoutRect = QStyle::alignedRect(Qt::LayoutDirectionAuto, valign,
    		boundingRect.size().toSize(), textRect);
    
    	if (paintStartPosition)
    		*paintStartPosition = QPointF(textRect.x(), layoutRect.top());
    
    	QString ret;
    	qreal height = 0;
    	const int lineCount = textLayout.lineCount();
    	for (int i = 0; i < lineCount; ++i) {
    		const QTextLine line = textLayout.lineAt(i);
    		height += line.height();
    
    		// above visible rect
    		if (height + layoutRect.top() <= textRect.top()) {
    			if (paintStartPosition)
    				paintStartPosition->ry() += line.height();
    			continue;
    		}
    
    		const int start = line.textStart();
    		const int length = line.textLength();
    		const bool drawElided = line.naturalTextWidth() > textRect.width();
    		bool elideLastVisibleLine = lastVisibleLine == i;
    		if (!drawElided && i + 1 < lineCount && lastVisibleLineShouldBeElided) {
    			const QTextLine nextLine = textLayout.lineAt(i + 1);
    			const int nextHeight = height + nextLine.height() / 2;
    			// elide when less than the next half line is visible
    			if (nextHeight + layoutRect.top() > textRect.height() + textRect.top())
    				elideLastVisibleLine = true;
    		}
    
    		QString text1 = textLayout.text().mid(start, length);
    		if (drawElided || elideLastVisibleLine) {
    			if (elideLastVisibleLine) {
    				if (text1.endsWith(QChar::LineSeparator))
    					text1.chop(1);
    				text1 += QChar(0x2026);
    			}
    			QFontMetrics fontMetrics(font);
    			ret += fontMetrics.elidedText(text1, textElideMode, textRect.width());
    
    			// no newline for the last line (last visible or real)
    			// sometimes drawElided is true but no eliding is done so the text ends
    			// with QChar::LineSeparator - don't add another one. This happened with
    			// arabic text in the testcase for QTBUG-72805
    			if (i < lineCount - 1 &&
    				!ret.endsWith(QChar::LineSeparator))
    				ret += QChar::LineSeparator;
    		}
    		else {
    			ret += text1;
    		}
    
    		// below visible text, can stop
    		if ((height + layoutRect.top() >= textRect.bottom()) ||
    			(lastVisibleLine >= 0 && lastVisibleLine == i))
    			break;
    	}
    	return ret;
    }
    
    
    void ElidedTextDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
    {
    	Q_ASSERT(index.isValid());
    	QStyleOptionViewItem opt{ option };
    	initStyleOption(&opt, index);
    
    	QStyle* style = opt.widget ? opt.widget->style() : QApplication::style();
    	opt.textElideMode = Qt::ElideMiddle;
    
    	painter->save();
    
    	QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &opt);
    
    	//
    	// Draw the text as elided text
    	//
    	QFontMetrics fontMetrics(opt.font);
    	const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr) + 1;
    	textRect = textRect.adjusted(textMargin, 0, -textMargin, 0); // remove width padding
    	const bool wrapText = opt.features & QStyleOptionViewItem::WrapText;
    	QTextOption textOption;
    	textOption.setWrapMode(wrapText ? QTextOption::WordWrap : QTextOption::ManualWrap);
    	textOption.setTextDirection(opt.direction);
    	textOption.setAlignment(QStyle::visualAlignment(opt.direction, opt.displayAlignment));
    
    	QPointF paintPosition;
    	const QString newText = calculateElidedText(opt.text, textOption,
    		opt.font, textRect, opt.displayAlignment,
    		opt.textElideMode, 0,
    		true, &paintPosition);
    
    	QTextLayout textLayout(newText, opt.font);
    	textLayout.setTextOption(textOption);
    	viewItemTextLayout(textLayout, textRect.width());
    	textLayout.draw(painter, paintPosition);
    
    	painter->restore();
    }
    

    My problem is it is not eliding in the middle, but the end, and I'm also seeing this in the debug log:

    QPainter::begin: Paint device returned engine == 0, type: 2
    QPainter::setRenderHint: Painter must be active to set rendering hints
    QPainter::setRenderHint: Painter must be active to set rendering hints
    

    which don't appear to be from my code!

    Clearly I've messed it up, but how!?

    Thanks
    David

    PerdrixP 1 Reply Last reply
    0
    • PerdrixP Perdrix

      The information in one column of a table widget is quite long (full file path), so I am trying to use a styled item delegate to force it to elide in the middle.

      //
      // The filename is quite long (full path), so use a subclass
      // of QStyledItemDelegate to handle the rendering for column one
      // the table with the text set to elide in the middle.
      //
      elidedTextDelegate = new ElidedTextDelegate(this);
      
      imageList->setItemDelegateForColumn(static_cast<int>(ImageListColumns::File), elidedTextDelegate);
      

      Header information for the delegate:

      class ElidedTextDelegate : public QStyledItemDelegate
      {
      	Q_OBJECT
      
      public:
      	using QStyledItemDelegate::QStyledItemDelegate;
      
      
      protected:
      	void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
      	//inline QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
      	//{
      	//	QSize result = QStyledItemDelegate::sizeHint(option, index);
      	//	result.setHeight(result.height() * 1.2);
      	//	return result;
      	//}
      
      	QString calculateElidedText(const QString& text, const QTextOption& textOption,
      		const QFont& font, const QRect& textRect, const Qt::Alignment valign,
      		Qt::TextElideMode textElideMode, [[maybe_unused]] int flags,
      		bool lastVisibleLineShouldBeElided, QPointF* paintStartPosition) const;
      
      };
      

      Implementation:

      	static QSizeF viewItemTextLayout(QTextLayout& textLayout, int lineWidth, int maxHeight = -1, int* lastVisibleLine = nullptr)
      	{
      		if (lastVisibleLine)
      			*lastVisibleLine = -1;
      		qreal height = 0;
      		qreal widthUsed = 0;
      		textLayout.beginLayout();
      		int i = 0;
      		while (true) {
      			QTextLine line = textLayout.createLine();
      			if (!line.isValid())
      				break;
      			line.setLineWidth(lineWidth);
      			line.setPosition(QPointF(0, height));
      			height += line.height();
      			widthUsed = qMax(widthUsed, line.naturalTextWidth());
      			// we assume that the height of the next line is the same as the current one
      			if (maxHeight > 0 && lastVisibleLine && height + line.height() > maxHeight) {
      				const QTextLine nextLine = textLayout.createLine();
      				*lastVisibleLine = nextLine.isValid() ? i : -1;
      				break;
      			}
      			++i;
      		}
      		textLayout.endLayout();
      		return QSizeF(widthUsed, height);
      	}
      
      QString ElidedTextDelegate::calculateElidedText(const ::QString& text, const QTextOption& textOption,
      	const QFont& font, const QRect& textRect, const Qt::Alignment valign,
      	Qt::TextElideMode textElideMode, [[maybe_unused]] int flags,
      	bool lastVisibleLineShouldBeElided, QPointF* paintStartPosition) const
      {
      	QTextLayout textLayout(text, font);
      	textLayout.setTextOption(textOption);
      
      	// In AlignVCenter mode when more than one line is displayed and the height only allows
      	// some of the lines it makes no sense to display those. From a users perspective it makes
      	// more sense to see the start of the text instead something inbetween.
      	const bool vAlignmentOptimization = paintStartPosition && valign.testFlag(Qt::AlignVCenter);
      
      	int lastVisibleLine = -1;
      	viewItemTextLayout(textLayout, textRect.width(), vAlignmentOptimization ? textRect.height() : -1, &lastVisibleLine);
      
      	const QRectF boundingRect = textLayout.boundingRect();
      	// don't care about LTR/RTL here, only need the height
      	const QRect layoutRect = QStyle::alignedRect(Qt::LayoutDirectionAuto, valign,
      		boundingRect.size().toSize(), textRect);
      
      	if (paintStartPosition)
      		*paintStartPosition = QPointF(textRect.x(), layoutRect.top());
      
      	QString ret;
      	qreal height = 0;
      	const int lineCount = textLayout.lineCount();
      	for (int i = 0; i < lineCount; ++i) {
      		const QTextLine line = textLayout.lineAt(i);
      		height += line.height();
      
      		// above visible rect
      		if (height + layoutRect.top() <= textRect.top()) {
      			if (paintStartPosition)
      				paintStartPosition->ry() += line.height();
      			continue;
      		}
      
      		const int start = line.textStart();
      		const int length = line.textLength();
      		const bool drawElided = line.naturalTextWidth() > textRect.width();
      		bool elideLastVisibleLine = lastVisibleLine == i;
      		if (!drawElided && i + 1 < lineCount && lastVisibleLineShouldBeElided) {
      			const QTextLine nextLine = textLayout.lineAt(i + 1);
      			const int nextHeight = height + nextLine.height() / 2;
      			// elide when less than the next half line is visible
      			if (nextHeight + layoutRect.top() > textRect.height() + textRect.top())
      				elideLastVisibleLine = true;
      		}
      
      		QString text1 = textLayout.text().mid(start, length);
      		if (drawElided || elideLastVisibleLine) {
      			if (elideLastVisibleLine) {
      				if (text1.endsWith(QChar::LineSeparator))
      					text1.chop(1);
      				text1 += QChar(0x2026);
      			}
      			QFontMetrics fontMetrics(font);
      			ret += fontMetrics.elidedText(text1, textElideMode, textRect.width());
      
      			// no newline for the last line (last visible or real)
      			// sometimes drawElided is true but no eliding is done so the text ends
      			// with QChar::LineSeparator - don't add another one. This happened with
      			// arabic text in the testcase for QTBUG-72805
      			if (i < lineCount - 1 &&
      				!ret.endsWith(QChar::LineSeparator))
      				ret += QChar::LineSeparator;
      		}
      		else {
      			ret += text1;
      		}
      
      		// below visible text, can stop
      		if ((height + layoutRect.top() >= textRect.bottom()) ||
      			(lastVisibleLine >= 0 && lastVisibleLine == i))
      			break;
      	}
      	return ret;
      }
      
      
      void ElidedTextDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
      {
      	Q_ASSERT(index.isValid());
      	QStyleOptionViewItem opt{ option };
      	initStyleOption(&opt, index);
      
      	QStyle* style = opt.widget ? opt.widget->style() : QApplication::style();
      	opt.textElideMode = Qt::ElideMiddle;
      
      	painter->save();
      
      	QRect textRect = style->subElementRect(QStyle::SE_ItemViewItemText, &opt);
      
      	//
      	// Draw the text as elided text
      	//
      	QFontMetrics fontMetrics(opt.font);
      	const int textMargin = style->pixelMetric(QStyle::PM_FocusFrameHMargin, nullptr) + 1;
      	textRect = textRect.adjusted(textMargin, 0, -textMargin, 0); // remove width padding
      	const bool wrapText = opt.features & QStyleOptionViewItem::WrapText;
      	QTextOption textOption;
      	textOption.setWrapMode(wrapText ? QTextOption::WordWrap : QTextOption::ManualWrap);
      	textOption.setTextDirection(opt.direction);
      	textOption.setAlignment(QStyle::visualAlignment(opt.direction, opt.displayAlignment));
      
      	QPointF paintPosition;
      	const QString newText = calculateElidedText(opt.text, textOption,
      		opt.font, textRect, opt.displayAlignment,
      		opt.textElideMode, 0,
      		true, &paintPosition);
      
      	QTextLayout textLayout(newText, opt.font);
      	textLayout.setTextOption(textOption);
      	viewItemTextLayout(textLayout, textRect.width());
      	textLayout.draw(painter, paintPosition);
      
      	painter->restore();
      }
      

      My problem is it is not eliding in the middle, but the end, and I'm also seeing this in the debug log:

      QPainter::begin: Paint device returned engine == 0, type: 2
      QPainter::setRenderHint: Painter must be active to set rendering hints
      QPainter::setRenderHint: Painter must be active to set rendering hints
      

      which don't appear to be from my code!

      Clearly I've messed it up, but how!?

      Thanks
      David

      PerdrixP Offline
      PerdrixP Offline
      Perdrix
      wrote on last edited by
      #2

      @Perdrix I also tried:

      class ElidedTextDelegate : public QStyledItemDelegate
      {
      	Q_OBJECT
      
      public:
      	using QStyledItemDelegate::QStyledItemDelegate;
      
      protected:
      	void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const override
      	{
      		QStyledItemDelegate::initStyleOption(option, index);
      		option->textElideMode = Qt::ElideNone;
      	}
      };
      

      but that didn't seem to help either :(

      D.

      JoeCFDJ 1 Reply Last reply
      0
      • PerdrixP Perdrix

        @Perdrix I also tried:

        class ElidedTextDelegate : public QStyledItemDelegate
        {
        	Q_OBJECT
        
        public:
        	using QStyledItemDelegate::QStyledItemDelegate;
        
        protected:
        	void initStyleOption(QStyleOptionViewItem* option, const QModelIndex& index) const override
        	{
        		QStyledItemDelegate::initStyleOption(option, index);
        		option->textElideMode = Qt::ElideNone;
        	}
        };
        

        but that didn't seem to help either :(

        D.

        JoeCFDJ Offline
        JoeCFDJ Offline
        JoeCFD
        wrote on last edited by
        #3

        @Perdrix
        does this one help?
        https://stackoverflow.com/questions/64198197/how-to-prevent-too-aggressive-text-elide-in-qtableview

        PerdrixP 1 Reply Last reply
        0
        • JoeCFDJ JoeCFD

          @Perdrix
          does this one help?
          https://stackoverflow.com/questions/64198197/how-to-prevent-too-aggressive-text-elide-in-qtableview

          PerdrixP Offline
          PerdrixP Offline
          Perdrix
          wrote on last edited by
          #4

          @JoeCFD One thing is sure - the debug errors are nothing to do with that styled item delegate! I decided in the end to just display the filename in the table view so didn't need the elision in the middle (though it would be nice to know how it is supposed to be done).

          How to track down those debug messages:

          QPainter::begin: Paint device returned engine == 0, type: 2
          QPainter::setRenderHint: Painter must be active to set rendering hints
          QPainter::setRenderHint: Painter must be active to set rendering hints
          
          JoeCFDJ 1 Reply Last reply
          0
          • PerdrixP Perdrix

            @JoeCFD One thing is sure - the debug errors are nothing to do with that styled item delegate! I decided in the end to just display the filename in the table view so didn't need the elision in the middle (though it would be nice to know how it is supposed to be done).

            How to track down those debug messages:

            QPainter::begin: Paint device returned engine == 0, type: 2
            QPainter::setRenderHint: Painter must be active to set rendering hints
            QPainter::setRenderHint: Painter must be active to set rendering hints
            
            JoeCFDJ Offline
            JoeCFDJ Offline
            JoeCFD
            wrote on last edited by JoeCFD
            #5

            @Perdrix If you have logging code, use it to find out where these messages come out. Or use the debugger of qtcreator to go through your code

            1 Reply Last reply
            0

            • Login

            • Login or register to search.
            • First post
              Last post
            0
            • Categories
            • Recent
            • Tags
            • Popular
            • Users
            • Groups
            • Search
            • Get Qt Extensions
            • Unsolved