问题
- Qt Tab 键的规则是什么?
- 我可以以某种方式覆盖选项卡计算吗?
例子
使用制表符“\t”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 具有固定的制表符“\t”间距。
但是,QTableWidget 将选项卡固定为单元格宽度。我的猜测是,在单元格的一半之前,选项卡会添加空格,直到单元格的一半,在单元格的一半之后,直到单元格的末尾。
Q文本引擎
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制表符间距“\t”的规则是什么?我可以以某种方式覆盖选项卡计算吗?
您的测试不可靠,因为您使用的任意文本长度在实际应用制表符距离时无法正确显示。
让我们做一些更准确的例子。我们从 QLabel 列表开始,每个 QLabel 的选项卡前多一个字母:
o\to
、oo\to
等。现在让我们用项目视图做同样的事情:
正如您所看到的,制表符间距始终受到尊重,它只是使用不同的宽度。为了进行比较,您可以并排查看两个示例,以及不同的制表符距离如何改变结果:
QLabel 实际上有两种布局(和绘制)文本的方法。
当它使用纯文本并且没有设置文本交互标志时,它直接使用标签样式渲染文本,默认情况下,它调用使用字母字体规格乘以 8 倍作为默认制表符宽度的
drawItemText()
QPainter 。drawText()
horizontalAdvance()
x
当使用富文本(Qt 的“HTML”子集)或设置文本交互标志时,它使用 QTextDocument 接口,该接口默认制表符距离为 80 像素,除非通过 QTextOption 函数进行更改
setTabStopDistance()
。一旦设置了文本交互标志,您可能会看到选项卡宽度发生变化,遵循项目视图的行为,这是因为视图使用相同的 API 来显示文本。没有办法设置默认的“全局”制表符距离,QLabel/QPainter 的不一致当然也无济于事:QPainter 仅基于给
x
定字体的 8 个字母的宽度,QTextDocument 始终使用 80 像素。如果不使用自定义委托并通过重写自行绘制文本,也没有直接的方法来更改项目视图的行为,
paint()
这可以通过为文本创建 QTextDocument,tabStopDistance()
为其默认 QTextOption设置不同的值来实现,然后drawContents()
使用活动画家调用其函数。有趣的是,这对于标签来说有点简单,即使它有点“hacky”:当在标签上设置文本交互标志时,它会立即创建一个内部 QWidgetTextControl,它有自己的 QTextDocument,可以使用
findChild()
.为了简单起见,以下示例是用 Python 编写的(也是因为我无法编写正确的 C++ 代码),但清楚地展示了如何实现上述目标:
这是结果: