#include "faulttable.h" #include #include #include #include #include #include #include #include "global.h" FaultTable::FaultTable(QWidget* parent) : QWidget(parent) { QVBoxLayout* lay = new QVBoxLayout(this); lay->setContentsMargins(0, 0, 0, 0); lay->setSpacing(10); // 分页行(上一页 / 页码 / 下一页)——水平居中,字号与按钮加大 QHBoxLayout* nav = new QHBoxLayout(); nav->setSpacing(50); nav->addStretch(1); m_prev = new QPushButton("上一页", this); m_prev->setFixedSize(240, 100); m_prev->setStyleSheet("font-size:50px;"); connect(m_prev, &QPushButton::clicked, this, [this]() { if (m_page > 0) { setPage(m_page - 1); emit pageChanged(m_page); } }); m_pageLabel = new QLabel("1/1", this); m_pageLabel->setAlignment(Qt::AlignCenter); m_pageLabel->setStyleSheet(QString("color:%1;font-size:40px;").arg(C::TEXT)); m_pageLabel->setFixedWidth(180); m_next = new QPushButton("下一页", this); m_next->setFixedSize(240, 100); m_next->setStyleSheet("font-size:50px;"); connect(m_next, &QPushButton::clicked, this, [this]() { if (m_page < pageCount() - 1) { setPage(m_page + 1); emit pageChanged(m_page); } }); nav->addWidget(m_prev); nav->addWidget(m_pageLabel); nav->addWidget(m_next); nav->addStretch(1); // 尾部也留弹性 → 整行居中 lay->addLayout(nav); // 表格 m_table = new QTableWidget(this); m_table->setColumnCount(3); m_table->setHorizontalHeaderLabels(QStringList() << "序号" << "故障时间" << "故障名称"); m_table->setEditTriggers(QAbstractItemView::NoEditTriggers); m_table->setSelectionBehavior(QAbstractItemView::SelectRows); m_table->setSelectionMode(QAbstractItemView::SingleSelection); m_table->setTabKeyNavigation(false); // 关掉视图自带的 Tab 导航,改由下方 eventFilter 按行切换 m_table->verticalHeader()->setVisible(false); m_table->horizontalHeader()->setFocusPolicy(Qt::NoFocus); // 表头不参与 Tab m_table->verticalHeader()->setFocusPolicy(Qt::NoFocus); m_table->setAlternatingRowColors(true); m_table->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); m_table->horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); m_table->setColumnWidth(0, 120); m_table->horizontalHeader()->setSectionResizeMode(2, QHeaderView::Stretch); m_table->horizontalHeader()->setFixedHeight(kHeaderHeight); // 固定表头高度(供对齐) // 每页 6 行等分填满表格可用高度,行高由视口高度直接算出(见 updateRowHeights)。 // 不能用 Stretch:它不会把行压到 minimumSectionSize 之下,而该值默认取自字体度量, // 主板上 QSS 指定的中文字体(微软雅黑等)都不存在、回退字体的行高更大,6 行塞不进 // 视口就会出滚动条、只看得到 5 行。改为固定行高后与字体无关。 m_table->verticalHeader()->setSectionResizeMode(QHeaderView::Fixed); m_table->verticalHeader()->setMinimumSectionSize(1); m_table->setVerticalScrollBarPolicy(Qt::ScrollBarAlwaysOff); connect(m_table, &QTableWidget::cellClicked, this, [this](int row, int) { int global = m_page * m_pageSize + row; if (global >= 0 && global < m_data.size()) emit rowClicked(global); }); m_table->installEventFilter(this); // 拦截 Tab,实现按行切换 m_table->viewport()->setMouseTracking(true); // 悬停整行高亮 m_table->viewport()->installEventFilter(this); // 鼠标移出视口 → 取消高亮 connect(m_table, &QAbstractItemView::entered, this, [this](const QModelIndex& idx) { setHoverRow(idx.row()); }); lay->addWidget(m_table, 1); refresh(); } QWidget* FaultTable::view() const { return m_table; } QWidget* FaultTable::prevButton() const { return m_prev; } QWidget* FaultTable::nextButton() const { return m_next; } bool FaultTable::eventFilter(QObject* obj, QEvent* ev) { // 视口尺寸变化(含首次布局)后重算行高,保证 6 行恰好铺满。 // 必须挂在 viewport 上:m_table 收到 Resize 时它的 viewport 还没重排, // 那时读到的 viewport 高度是旧值(首次为 0)。 if (obj == m_table->viewport() && ev->type() == QEvent::Resize) { updateRowHeights(); return QWidget::eventFilter(obj, ev); } if (obj == m_table->viewport() && ev->type() == QEvent::Leave) { setHoverRow(-1); // 鼠标移出表格 → 取消悬停高亮 return QWidget::eventFilter(obj, ev); } if (obj != m_table) return QWidget::eventFilter(obj, ev); // 焦点进入表格时选中一行:正向(Tab)进末行→回首行、否则选首行;反向(Shift+Tab)同理选末行 if (ev->type() == QEvent::FocusIn) { QFocusEvent* fe = static_cast(ev); const int last = m_table->rowCount() - 1; const int col = qMax(0, m_table->currentColumn()); int row = -1; if (fe->reason() == Qt::TabFocusReason) { if (last >= 0 && (m_table->currentRow() < 0 || m_table->currentRow() >= last)) row = 0; } else if (fe->reason() == Qt::BacktabFocusReason) { if (last >= 0 && (m_table->currentRow() < 0 || m_table->currentRow() <= 0)) row = last; } if (row >= 0) { m_table->setCurrentCell(row, col); m_table->selectRow(row); // 显式整行选中(只设当前单元格时可能只高亮一格) } } // 仅键盘(Tab/Shift+Tab)移走焦点时清除选中;鼠标点击/弹窗抢焦点时保留,便于看清当前行 if (ev->type() == QEvent::FocusOut) { QFocusEvent* fe = static_cast(ev); if (fe->reason() == Qt::TabFocusReason || fe->reason() == Qt::BacktabFocusReason) m_table->clearSelection(); return QWidget::eventFilter(obj, ev); } // Tab / Shift+Tab:按行切换选中(每一行作为一个单位);空格:等同鼠标点击 if (ev->type() == QEvent::KeyPress) { QKeyEvent* ke = static_cast(ev); const int k = ke->key(); // 上/下方向键在本机是"旋钮"键位(只给 ParamControl 调值),表格里不参与导航: // 换行只走 Tab/Shift+Tab,激活只走空格 if (Keys::up(k) || Keys::down(k)) return true; if (Keys::tab(k) || Keys::backtab(k)) { const bool back = Keys::backtab(k) || Keys::shifted(ke->modifiers()); const int next = m_table->currentRow() + (back ? -1 : 1); if (next >= 0 && next < m_table->rowCount()) { m_table->setCurrentCell(next, qMax(0, m_table->currentColumn())); m_table->selectRow(next); // 整行选中 return true; // 消费事件,焦点留在表格内 } // 已在首/末行:不拦截,焦点按 Tab 顺序移到下一个控件 } else if (Keys::space(k)) { // 键盘激活:与鼠标 cellClicked 走同一个信号,弹窗逻辑只有一份 const int row = m_table->currentRow(); const int global = m_page * m_pageSize + row; if (row >= 0 && global >= 0 && global < m_data.size()) { emit rowClicked(global); return true; } } } return QWidget::eventFilter(obj, ev); } // 鼠标悬停的整行高亮:颜色与“选中”一致(主色蓝底白字) void FaultTable::setHoverRow(int row) { if (row == m_hoverRow) return; if (m_hoverRow >= 0) { for (int c = 0; c < m_table->columnCount(); ++c) if (QTableWidgetItem* it = m_table->item(m_hoverRow, c)) { it->setData(Qt::BackgroundRole, QVariant()); it->setData(Qt::ForegroundRole, QVariant()); } } m_hoverRow = row; if (m_hoverRow >= 0) { for (int c = 0; c < m_table->columnCount(); ++c) if (QTableWidgetItem* it = m_table->item(m_hoverRow, c)) { it->setData(Qt::BackgroundRole, QColor(C::PRIMARY)); it->setData(Qt::ForegroundRole, QColor("#FFFFFF")); } } } void FaultTable::setData(const QVector& data) { m_data = data; if (m_page >= pageCount() && pageCount() > 0) m_page = pageCount() - 1; refresh(); } void FaultTable::setPage(int p) { m_page = qBound(0, p, qMax(0, pageCount() - 1)); refresh(); } int FaultTable::pageCount() const { if (m_data.isEmpty()) return 1; return (m_data.size() + m_pageSize - 1) / m_pageSize; } void FaultTable::refresh() { m_hoverRow = -1; // 单元格会重建,悬停行失效 m_table->setRowCount(0); m_table->setRowCount(m_pageSize); int start = m_page * m_pageSize; for (int r = 0; r < m_pageSize; ++r) { int idx = start + r; bool has = idx < m_data.size(); for (int c = 0; c < 3; ++c) { QTableWidgetItem* it = new QTableWidgetItem(); it->setTextAlignment(Qt::AlignCenter); if (has) { const FaultRecord& rec = m_data[idx]; if (c == 0) it->setText(QString::number(idx + 1)); else if (c == 1) it->setText(rec.time); else it->setText(rec.desc); } m_table->setItem(r, c, it); } } // 空数据显示提示 if (m_data.isEmpty()) { m_table->clearSelection(); } m_pageLabel->setText(QString("%1/%2").arg(m_page + 1).arg(pageCount())); m_prev->setEnabled(m_page > 0); m_next->setEnabled(m_page < pageCount() - 1); } // 行高 = 视口高度 / 每页行数:6 行恰好铺满,不出现滚动条,也不受字体度量影响 void FaultTable::updateRowHeights() { const int h = m_table->viewport()->height(); if (h <= 0 || m_pageSize <= 0) return; m_table->verticalHeader()->setDefaultSectionSize(qMax(1, h / m_pageSize)); }