问题
示例
在
QTableWidget
上使用制表符“”会产生与 QLabel
文本不同的间距行为。
使用以下示例代码:
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
, m_qText1( nullptr )
, m_qText2( nullptr )
, m_qText3( nullptr )
, m_qTable( nullptr )
{
ui->setupUi(this);
m_qText1 = new QLabel(this);
m_qText1->setGeometry( 0, 100, 400, 100);
m_qText1->setText( "dafdsfsdf\te" );
m_qText2 = new QLabel(this);
m_qText2->setGeometry( 0, 200, 400, 100);
m_qText2->setText( "dafddsasddsafsdf\te" );
m_qText3 = new QLabel(this);
m_qText3->setGeometry( 0, 300, 400, 100);
m_qText3->setText( "dafasdassadasdsasadfsdf\te" );
m_qTable = new QTableWidget(this);
m_qTable->horizontalHeader()->setVisible(false);
m_qTable->verticalHeader()->setVisible(false);
m_qTable->setGeometry( 0, 0, 200, 100 );
for (int iRow = 0; iRow <= 2; ++iRow)
{
int rowCount = m_qTable->rowCount();
m_qTable->insertRow( rowCount );
}
m_qTable->insertColumn(0);
m_qTable->setColumnWidth( 0, 200);
QTableWidgetItem* myItem = new QTableWidgetItem( "dafdsfsdf\te" );
m_qTable->setItem( 0, 0, myItem);
myItem = new QTableWidgetItem( "dafddsasddsafsdf\te" );
m_qTable->setItem( 0, 1, myItem);
myItem = new QTableWidgetItem( "dafasdassadasdsasadfsdf\te" );
m_qTable->setItem( 0, 2, myItem);
}
还有标题
private:
QLabel* m_qText1;
QLabel* m_qText2;
QLabel* m_qText3;
QTableWidget* m_qTable;
结果
结果如下:
我们可以看到 QLabels 的选项卡“”的间距是固定的。
但是,QTableWidget 将选项卡固定为单元格宽度。我的猜测是,在单元格的一半之前,选项卡会添加空格,直到单元格的一半,在单元格的一半之后,直到单元格的末尾。
QTextEngine
FWIW:Qt 有此类 QTextEngine 且函数
QTextEngine::calculateTabWidth
给出:
QFixed QTextEngine::calculateTabWidth(int item, QFixed x) const
{
const QScriptItem &si = layoutData->items.at(item);
QFixed dpiScale = 1;
if (QTextDocumentPrivate::get(block) != nullptr && QTextDocumentPrivate::get(block)->layout() != nullptr) {
QPaintDevice *pdev = QTextDocumentPrivate::get(block)->layout()->paintDevice();
if (pdev)
dpiScale = QFixed::fromReal(pdev->logicalDpiY() / qreal(qt_defaultDpiY()));
} else {
dpiScale = QFixed::fromReal(fnt.d->dpi / qreal(qt_defaultDpiY()));
}
QList<QTextOption::Tab> tabArray = option.tabs();
if (!tabArray.isEmpty()) {
if (isRightToLeft()) { // rebase the tabArray positions.
auto isLeftOrRightTab = [](const QTextOption::Tab &tab) {
return tab.type == QTextOption::LeftTab || tab.type == QTextOption::RightTab;
};
const auto cbegin = tabArray.cbegin();
const auto cend = tabArray.cend();
const auto cit = std::find_if(cbegin, cend, isLeftOrRightTab);
if (cit != cend) {
const int index = std::distance(cbegin, cit);
auto iter = tabArray.begin() + index;
const auto end = tabArray.end();
while (iter != end) {
QTextOption::Tab &tab = *iter;
if (tab.type == QTextOption::LeftTab)
tab.type = QTextOption::RightTab;
else if (tab.type == QTextOption::RightTab)
tab.type = QTextOption::LeftTab;
++iter;
}
}
}
for (const QTextOption::Tab &tabSpec : std::as_const(tabArray)) {
QFixed tab = QFixed::fromReal(tabSpec.position) * dpiScale;
if (tab > x) { // this is the tab we need.
int tabSectionEnd = layoutData->string.size();
if (tabSpec.type == QTextOption::RightTab || tabSpec.type == QTextOption::CenterTab) {
// find next tab to calculate the width required.
tab = QFixed::fromReal(tabSpec.position);
for (int i=item + 1; i < layoutData->items.size(); i++) {
const QScriptItem &item = layoutData->items.at(i);
if (item.analysis.flags == QScriptAnalysis::TabOrObject) { // found it.
tabSectionEnd = item.position;
break;
}
}
}
else if (tabSpec.type == QTextOption::DelimiterTab)
// find delimiter character to calculate the width required
tabSectionEnd = qMax(si.position, layoutData->string.indexOf(tabSpec.delimiter, si.position) + 1);
if (tabSectionEnd > si.position) {
QFixed length;
// Calculate the length of text between this tab and the tabSectionEnd
for (int i=item; i < layoutData->items.size(); i++) {
const QScriptItem &item = layoutData->items.at(i);
if (item.position > tabSectionEnd || item.position <= si.position)
continue;
shape(i); // first, lets make sure relevant text is already shaped
if (item.analysis.flags == QScriptAnalysis::Object) {
length += item.width;
continue;
}
QGlyphLayout glyphs = this->shapedGlyphs(&item);
const int end = qMin(item.position + item.num_glyphs, tabSectionEnd) - item.position;
for (int i=0; i < end; i++)
length += glyphs.advances[i] * !glyphs.attributes[i].dontPrint;
if (end + item.position == tabSectionEnd && tabSpec.type == QTextOption::DelimiterTab) // remove half of matching char
length -= glyphs.advances[end] / 2 * !glyphs.attributes[end].dontPrint;
}
switch (tabSpec.type) {
case QTextOption::CenterTab:
length /= 2;
Q_FALLTHROUGH();
case QTextOption::DelimiterTab:
case QTextOption::RightTab:
tab = QFixed::fromReal(tabSpec.position) * dpiScale - length;
if (tab < x) // default to tab taking no space
return QFixed();
break;
case QTextOption::LeftTab:
break;
}
}
return tab - x;
}
}
}
QFixed tab = QFixed::fromReal(option.tabStopDistance());
if (tab <= 0)
tab = 80; // default
tab *= dpiScale;
QFixed nextTabPos = ((x / tab).truncate() + 1) * tab;
QFixed tabWidth = nextTabPos - x;
return tabWidth;
}
也许它是用来计算标签宽度的?但我想知道为什么它只发生在表格上而不是标签上......
经过一番研究,我找不到明确的答案,所以Qt制表符间距“”的规则是什么?和我可以以某种方式重写制表符计算吗?
您的测试不可靠,因为您使用的任意文本长度在实际应用制表符距离时无法正确显示。
让我们做一些更准确的例子。我们从 QLabel 开始,每个标签前都多一个字母:
o\to
、oo\to
等。
现在让我们用项目视图做同样的事情
如您所见,制表符间距始终受到尊重,它只是使用不同的宽度。为了进行比较,您可以并排查看两个示例,以及不同的制表符距离如何改变结果:
QLabel 实际上有两种布局(和绘制)文本的方式。
drawItemText()
渲染文本,默认情况下,它会调用使用字体规格的QPainterdrawText()
'horizontalAdvance()
字母
x
的 乘以 8 倍作为默认制表符宽度。
当使用富文本(Qt 的“HTML”子集)或设置文本交互标志时,它使用 QTextDocument 接口,默认制表符距离为 80 像素,除非通过 QTextOption
setTabStopDistance()
函数进行更改。一旦设置了该标志,您可能会看到选项卡宽度发生变化,遵循项目视图的行为,这是因为视图使用相同的 API 来显示文本。
无法设置默认的“全局”制表符距离,QLabel/QPainter 的不一致当然也无济于事:QPainter 仅基于给定字体的 8 个
x
字母的宽度,QTextDocument 始终使用 80像素。
如果不使用自定义委托并从
paint()
自行绘制文本,也没有直接的方法来更改项目视图的行为,这可以通过为文本创建 QTextDocument、设置不同的 来实现tabStopDistance()
为其默认 QTextOption,然后使用活动画家调用其 drawContents()
函数。
有趣的是,这对于标签来说有点简单,即使它有点“hacky”:当在标签上设置文本交互标志时,它会立即创建一个内部 QWidgetTextControl,它有自己的 QTextDocument,可以使用
访问它findChild()
。
为了简单起见,以下示例是用 Python 编写的(也是因为我无法编写正确的 C++ 代码),但清楚地展示了如何实现上述目标:
import sys
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
app = QApplication(sys.argv)
label1 = QLabel('def\tworld') # default behavior
label2 = QLabel('cus\tworld') # custom tab distance
label2.setTextInteractionFlags(Qt.TextSelectableByMouse)
# we don't actually need selection, it's only to ensure
# that the text control is created, therefore we disable
# mouse interaction
label2.setAttribute(Qt.WA_TransparentForMouseEvents)
# and prevent focus stealing
label2.setFocusPolicy(Qt.NoFocus)
doc = label2.findChild(QTextDocument)
opt = doc.defaultTextOption()
opt.setTabStopDistance(50)
doc.setDefaultTextOption(opt)
test = QWidget()
layout = QVBoxLayout(test)
layout.addWidget(label1)
# show the default tab distance for QLabel/QPainter
layout.addWidget(QLabel('xxxxxxxx'))
layout.addWidget(label2)
test.show()
sys.exit(app.exec())
这是结果: