QTextDocument (and QTextEdit) not updating their size? - c++

I've created a QTextEdit which initializes empty, after that the following code executes (TE being a pointer to the textedit):
TE->setText( "this is a test string, it should have a non zero height");
auto d(TE->document());
auto s(d->toPlainText());
auto _s(d->size());
auto _h(_s.height());
Through debugging I see that the string s is set correctly. However the size _s simply is 0. Why, and how to fix. Shouldn't this snipped work everywhere and in any place? I've also tried adding a TE->show() line in case the resizing only happens when showing the widget, but to no avail. What is causing this?

Changing the text in the document does not change the document width. From the docs for QTextDocument.
The size of the document can be changed either by setting a text width or setting an entire page size.
Perhaps calling d->adjustSize() will get you where you want to be? However, note that changing the size of the document won't change the size of the QTextEdit widget.

Related

Performantly appending (rich) text into QTextEdit or QTextBrowser in Qt

QTextEdit can be appended text to simply using append(). However, if the document is rich text, every time you append to the document, it is apparently reparsed. This seems like a bit of a trap in Qt.
If you're using the edit box as a log window and appending text in fast successions as a result of external signals, the appending can easily hang your app with no intermediate appends shown until each of the appends have completed.
How do I append rich text to a QTextEdit without it slowing down the entire UI?
If you want each append to actually show quickly & separately (instead of waiting until they've all been appended before they are shown), you need to access the internal QTextDocument:
void fastAppend(QString message,QTextEdit *editWidget)
{
const bool atBottom = editWidget->verticalScrollBar()->value() == editWidget->verticalScrollBar()->maximum();
QTextDocument* doc = editWidget->document();
QTextCursor cursor(doc);
cursor.movePosition(QTextCursor::End);
cursor.beginEditBlock();
cursor.insertBlock();
cursor.insertHtml(message);
cursor.endEditBlock();
//scroll scrollarea to bottom if it was at bottom when we started
//(we don't want to force scrolling to bottom if user is looking at a
//higher position)
if (atBottom) {
scrollLogToBottom(editWidget);
}
}
void scrollLogToBottom(QTextEdit *editWidget)
{
QScrollBar* bar = editWidget->verticalScrollBar();
bar->setValue(bar->maximum());
}
The scrolling to bottom is optional, but in logging use it's a reasonable default for UI behaviour.
Also, if your app is doing lots of other processing at the same time, appending this at the end of fastAppend, will prioritize actually getting the message displayed asap:
//show the message in output right away by triggering event loop
QCoreApplication::processEvents();
This actually seems a kind of trap in Qt. I would know why there isn't a fastAppend method directly in QTextEdit? Or are there caveats to this solution?
(My company actually paid KDAB for this advice, but this seems so silly that I thought this should be more common knowledge.)

Qt QWidget::minimumSizeHint delay (#2)

When i hide a widget in my application, the minimumSizeHint isn't updated immediatly.
I tried the QLayout::activate() solution suggested at this post, but it doesn't work for me because QLayout::activate() returns false.
I try to do it at MainWindow class like this:
ui.groupBox->setVisible(!ui.groupBox->isVisible());
qDebug() << this->layout()->activate();
qDebug() << this->minimumSizeHint();
this->resize(this->minimumSizeHint());
Any ideas why it's not working?
My current workaround is:
QTimer::singleShot(10, this, SLOT(on_resizeMin()));
but i noticed 10ms may not be enough on a slow system. Nasty workaround.
The size hints are meant to be used at appropriate moments by the layout system. You're not supposed to be using them elsewhere - that's why they appear to "not work".
You appear to want to constrain the size of the main widget (the window) to one of the size hints. You'll need to set an appropriate sizeConstrainton that widget's layout. There are two approaches:
Use the QLayout::SetFixedSize constraint. Reimplement the layout's sizeHint to return minimumSize, such that the QLayout::SetFixedSize will set the window's size to the minimum size hint, not the default size hint.
Patch Qt to implement QLayout::SetMinAndMaxToMinSize, meaning "The main widget's minimum and maximum size are set to minimumSize()"
The patch is small :)

QT QIcon properties for custom widget in designer

I have been working for a little while now on creating a QT custom designer widget for GUI menus. The idea being that you simply drag it into the designer, select the number of frames you'd like, how many buttons per frame, etc. and it generates and sizes everything for you.
The way the widget is structured there are properties to configure each button for the frame you are in. For example, you would use the button0Text field to enter text under Button0 while editing in frame 0, then use it again to edit Button0 which is in frame 1. Both buttons would retain the individual changes for each frame.
The Problem
Normally when I switch frames all of my properties are updated to reflect the status of the frame. The exception being QIcon. The correct icon is retained in the actual graphical representation and builds correctly, however the file path in the property list is always of the last edited for that property. I think this will be extremely confusing to an end user and I have found no way to fix it. So for example, if I set text and icons in frame 0 then switch to frame 1 the text in the property list will update to reflect the state of frame 1 but the icon path names will still show my last edit in frame 0 and not the actual icon in frame 1.
I have tried things as simple as:
setProperty("button0Icon", getButton0Icon());
That code works on properties like text, but not for the icon. I try executing it immediately after changing frames.
I have also tried:
#ifndef Q_WS_QWS
QDesignerFormWindowInterface *form = QDesignerFormWindowInterface::findFormWindow(this);
if(form){
QDesignerFormEditorInterface *editor = form->core();
QExtensionManager *manager = editor->extensionManager();
QDesignerPropertySheetExtension *sheet;
sheet = qt_extension<QDesignerPropertySheetExtension*>(manager, this);
int propertyIndex = sheet->indexOf("button0Icon");
sheet->setChanged(propertyIndex, true);
sheet->setProperty(propertyIndex, getButton0Icon());
}
#endif
And:
int propertyIndex = this->metaObject()->indexOfProperty("button0Icon");
QMetaProperty property = this->metaObject()->property(propertyIndex);
property.write(this, QIcon());
Nothing seems to update the property list in the designer.
I have all properties, including all QIcon properties properly declared in the header file with Q_PROPERTY and assigned getter and setter functions.
To be clear, the icon values are indeed retained through each frame when compiled. So it is functioning, just unclear for most users.
If anyone has any experience with this or any ideas please let me know. Thanks.
I have discovered that QIcon does not store file names/paths. The file names are only used for the creation of the QIcon. I think this is most likely the reason I do not get any feedback in the Property Browser for my QIcon properties.
Instead I have chosen to hide this property in the designer and add three new ones. Three QUrl properties, each of which is used to supply an image file. I use three because I want to construct a QIcon that contains Modes/States for normal, disabled, and pressed operations.
I take each of these QUrls and save them in QStringLists behind the scenes so their values are stored. I then construct my QIcon using the file names provided from the QUrls.
I would much prefer to be using the native QIcon in the designer for this, any thoughts or feedback are appreciated.

QTextEdit change font of individual paragraph/block

Using a QTextEdit, I need to change the font attributes of each paragraph individually. This is similar to how many word processors change the font of a paragraph when the user select a style from a menu (not a specific formatting).
Ideally, I would like to apply a QTextCharFormat (or equivalent) to a block (paragraph) just before it is laid out and rendered, but I would prefer that no font attribute be actually inserted in the text, as I don't want this information in the file but I need to preserve any bold/italic/underline attributes that the user might have set to words within paragraphs (I intend to save the needed information in a QTextBlock::userData). However, I can't figure where I would need to insert a function to perform this task.
I figured I could not change the QTextCharFormat of a paragraph from either QTextBlock nor QTextCursor as this only applies to new blocks, it doesn't affect blocks with existing text.
I checked out QTextLayout but I don't think my answer is there.
I have been looking for a solution to this problem for a few days now. I would be really gracious for any pointer in the right direction.
I have years of experience with C++, but I'm somewhat new to Qt. Using Qt 4.8.
Edit:
I added emphasize (bold) above to an important part of what I'm trying to do. In other word, what I'd really like to do is be able to apply the font attributes to the block of text (perhaps a temporary copy) just before it is displayed. I'm totally comfortable with deriving and modifying (even reimplement) any class that I need to in order to achieve that goal, but I need to be pointed to the right direction as to what I actually need to change. As a last resort, I could also modify some Qt class directly if that is necessary for the task, but again would need to know what class I need to touch. I hope this is clearer. I find it difficult to explain this without being allowed to tell you what the application will do exactly.
[Required Libraries]
#include <QTextEdit> // not needed if using the designer
#include <QTextDocument>
#include <QTextBlock>
#include <QTextCursor>
[Strategy]
QTextDocument
I need it to manage the blocks. The function QTextDocument::findBlockByNumber is quite handy to locate the previous blocks, and I think it is what you are after.
QTextBlock
Container for block texts. A nice and handy class.
QTextCursor
Surprisingly, there is no format-setter in QTextBlock class. Therefore I use QTextCursor as a workaround since there are four format-setters in this class.
[Code for formatting]
// For block management
QTextDocument *doc = new QTextDocument(this);
ui->textEdit->setDocument(doc); // from QTextEdit created by the Designer
//-------------------------------------------------
// Locate the 1st block
QTextBlock block = doc->findBlockByNumber(0);
// Initiate a copy of cursor on the block
// Notice: it won't change any cursor behavior of the text editor, since it
// just another copy of cursor, and it's "invisible" from the editor.
QTextCursor cursor(block);
// Set background color
QTextBlockFormat blockFormat = cursor.blockFormat();
blockFormat.setBackground(QColor(Qt::yellow));
cursor.setBlockFormat(blockFormat);
// Set font
for (QTextBlock::iterator it = cursor.block().begin(); !(it.atEnd()); ++it)
{
QTextCharFormat charFormat = it.fragment().charFormat();
charFormat.setFont(QFont("Times", 15, QFont::Bold));
QTextCursor tempCursor = cursor;
tempCursor.setPosition(it.fragment().position());
tempCursor.setPosition(it.fragment().position() + it.fragment().length(), QTextCursor::KeepAnchor);
tempCursor.setCharFormat(charFormat);
}
Reference:
How to change current line format in QTextEdit without selection?
[DEMO]
Building Environment: Qt 4.8 + MSVC2010 compiler + Windows 7 32 bit
The demo is just for showing the concept of setting the format on a specific block.
Plain text input
Format 1 (notice that it won't bother the current cursor in view)
Format 2
You can use QTextCursor to modify existing blocks.
Just get a cursor and move it to the beginning of the block. Then move it with anchor to create a selection.
Set this cursor to be the current cursor for the text edit and apply your changes.
QTextEdit accepts HTML so all you have to do is to format your paragraphs as HTML. See example below:
QString text = "<p><b>Paragraph 1</b></p><p><i>Paragraph 2</i></p>";
QTextCursor cursor = ui->textEdit->textCursor();
cursor.insertHtml(text);
That will create something like this:
Paragraph 1
Paragraph 2
Having said that, there is only a subset of HTML that is supported in Qt. See Supported HTML Subset

CListBox's Item size changed when changing the size of the list box even if I specify the size in MeasureItem() method?

I used a class which derives from CListBox, and create it with following:
style:WS_CHILD|WS_VISIBLE |LBS_OWNERDRAWFIXED | WS_VSCROLL | WS_HSCROLL
I expect the ListBox's item to be have a fixed size, not affected by the size of the list box. So I override the MeasureItem() method, in which I specify the item's size like below:
void CMyListBox::MeasureItem(LPMEASUREITEMSTRUCT lpMIS)
{
lpMIS->itemHeight = ALBUM_ITEM_HEIGHT;
lpMIS->itemWidth = ALBUM_ITEM_WIDTH;
}
But the item's size changes according to the List box's size changing. is there anything wrong with my approach?
What's not mentioned in the reference is that WM_MEASUREITEM is called every time the *_OWNERDRAWFIXED control is resized.
I don't know however, how official this behavior is and whether it should be relied on, but it has been verified at CodeGuru and several forum posts found on the Google thing.
If you don't want to process the message, then just set a private flag somewhere in the first OnMeasureItem() call and return from it as soon as you check that it's set next time.
If you look at the MSDN entry for CListBox::MeasureItem you'll see that it's only called once unless the LBS_OWNERDRAWVARIABLE (not LBS_OWNERDRAWFIXED) style is set. If I understand correctly then this would explain the behaviour you're seeing because MeasureItem would need to be called each time the control's size changes.
Also, have you considered the points made in MFC Technical Note 14 : Custom Controls?