在 QTableWidget 上使用选项卡“”可提供与 QLabel 文本不同的间距行为

问题描述 投票:0回答:1

问题

  • Qt Tab 键的规则是什么?
  • 我可以以某种方式覆盖选项卡计算吗?

示例

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制表符间距“”的规则是什么?我可以以某种方式重写制表符计算吗?

c++ qt qwidget
1个回答
0
投票

您的测试不可靠,因为您使用的任意文本长度在实际应用制表符距离时无法正确显示。

让我们做一些更准确的例子。我们从 QLabel 开始,每个标签前都多一个字母:

o\to
oo\to
等。

现在让我们用项目视图做同样的事情

如您所见,制表符间距始终受到尊重,它只是使用不同的宽度。为了进行比较,您可以并排查看两个示例,以及不同的制表符距离如何改变结果:

QLabel 实际上有两种布局(和绘制)文本的方式。

当使用纯文本并且未设置文本交互标志时,它直接使用标签样式的

drawItemText()
渲染文本,默认情况下,它会调用使用字体规格的QPainter
drawText()
'
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())

这是结果:

© www.soinside.com 2019 - 2024. All rights reserved.