Qt5 连接导致 Unhandled Exception 或 HEAP CORRUPTION
Qt5 connection causes Unhandled Exception or HEAP CORRUPTION
我在 VS2010SP1 上使用 Qt5.5。我在停靠小部件中有一个 QCheckBox,它控制同一个停靠小部件中的 QTableView(提示到 ConsoleWindowView)是否自动滚动。
问题一:
当我将 QCheckBox::stateChanged 信号连接到 QTableView 的插槽(自动滚动)时,一切似乎都很好,除非我更改 QCheckBox 的状态并退出。退出时出现错误:
Debug Error! Program: QtTests.exe HEAP CORRUPTION DETECTED: after
Normal block (#4613) at 0x02B63850. CRT detected that the application
wrote to memory after end of heap buffer.
如果我没有激活QCheckBox就退出了,退出是正常的。
问题二:
认为这可能与销毁顺序有关,我尝试在 QTableView 析构函数中 "remember" 连接和断开连接,但是当我尝试将 QObject::connect 中的 return 值分配给我得到的一个 QMetaObject::Connection 成员变量:
Unhandled exception at 0x669859c2 in QtTests.exe: 0xC0000005: Access
violation writing location 0xabababc7
下面是代码,后面是相关的 ui 文件。
崩溃的区域在 Gui.h 方法 connectControls 中。对于问题1,配置为:
QObject::connect(AutoScrollCheckBox, &QCheckBox::stateChanged, this, &ConsoleWindowView::autoScroll); // CRASH1: Debug Error! Program: QtTests.exe HEAP CORRUPTION DETECTED: after Normal block (#4613) at 0x02B63850. CRT detected that the application wrote to memory after end of heap buffer.
//QMetaObject::Connection NewConnection = QObject::connect(AutoScrollCheckBox, &QCheckBox::stateChanged, this, &ConsoleWindowView::autoScroll);
//AutoScrollConnection = NewConnection; // CRASH2: Unhandled exception at 0x669859c2 in QtTests.exe: 0xC0000005: Access violation writing location 0xabababc7
对于问题2,配置为:
//QObject::connect(AutoScrollCheckBox, &QCheckBox::stateChanged, this, &ConsoleWindowView::autoScroll); // CRASH1: Debug Error! Program: QtTests.exe HEAP CORRUPTION DETECTED: after Normal block (#4613) at 0x02B63850. CRT detected that the application wrote to memory after end of heap buffer.
QMetaObject::Connection NewConnection = QObject::connect(AutoScrollCheckBox, &QCheckBox::stateChanged, this, &ConsoleWindowView::autoScroll);
AutoScrollConnection = NewConnection; // CRASH2: Unhandled exception at 0x669859c2 in QtTests.exe: 0xC0000005: Access violation writing location 0xabababc7
main.cpp
#include <QtWidgets/QApplication>
#include "Gui.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Gui w;
w.show();
return a.exec();
}
Gui.h
#ifndef QTTESTS_H
#define QTTESTS_H
#include <iostream>
#include <QTimer>
#include <QHeaderView>
#include <QCheckBox>
#include <QObject>
#include "ui_Gui.h"
class ConsoleWindowModelClass : public QAbstractTableModel
{
Q_OBJECT
public:
ConsoleWindowModelClass(QObject *parent)
: QAbstractTableModel(parent)
, RowCount(0)
{
//
ControllerTimer = new QTimer(this);
connect(ControllerTimer, SIGNAL(timeout()), this, SLOT(updateController()));
ControllerTimer->start(2000);
}
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE
{
return RowCount;
}
int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE
{
return 2;
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE
{
if (role == Qt::DisplayRole)
{
return QString("Row%1, Column%2")
.arg(index.row() + 1)
.arg(index.column() +1);
}
return QVariant();
}
QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE
{
if (orientation == Qt::Horizontal)
{
if (role == Qt::DisplayRole)
{
switch(section)
{
case 0:
return "Stamp";
break;
case 1:
return "Text";
break;
default:
return QString("Column %1").arg(section + 1);
break;
}
}
}
else if (orientation == Qt::Vertical)
{
if (role == Qt::DisplayRole)
{
return QString("%1").arg(section + 1);
}
}
return QVariant();
}
protected:
unsigned int RowCount;
QTimer *ControllerTimer;
private slots:
// GUI triggered
// Timer triggered
void updateController()
{
beginInsertRows(QModelIndex(), RowCount, RowCount);
RowCount++;
endInsertRows();
}
};
class ConsoleWindowView : public QTableView
{
Q_OBJECT
public:
ConsoleWindowView(QWidget *parent = 0)
: QTableView(parent)
, AutoScroll(true)
{
}
virtual ~ConsoleWindowView()
{
//disconnect(AutoScrollConnection);
}
void setTheModel(QAbstractItemModel *TheModel)
{
QTableView::setModel(TheModel);
connect(model(), &QAbstractItemModel::rowsInserted, this, &ConsoleWindowView::modelRowsInserted);
}
void connectControls(QCheckBox *AutoScrollCheckBox, QCheckBox *ReverseList, QPushButton *MarkerA, QPushButton *MarkerB, QPushButton *MarkerC, QPushButton *MarkerD)
{
try
{
//QObject::connect(AutoScrollCheckBox, &QCheckBox::stateChanged, this, &ConsoleWindowView::autoScroll); // CRASH1: Debug Error! Program: QtTests.exe HEAP CORRUPTION DETECTED: after Normal block (#4613) at 0x02B63850. CRT detected that the application wrote to memory after end of heap buffer.
QMetaObject::Connection NewConnection = QObject::connect(AutoScrollCheckBox, &QCheckBox::stateChanged, this, &ConsoleWindowView::autoScroll);
AutoScrollConnection = NewConnection; // CRASH2: Unhandled exception at 0x669859c2 in QtTests.exe: 0xC0000005: Access violation writing location 0xabababc7
}
catch (const std::exception &Exception)
{
std::cerr << "ConsoleWindowView::connectControls: " << Exception.what() << std::endl;
}
}
public slots:
void modelRowsInserted(const QModelIndex & parent, int start, int end)
{
if (model() != nullptr)
{
if (AutoScroll)
{
scrollTo(model()->index(start, 0));
}
}
}
void autoScroll(int State)
{
if (State == 0)
{
AutoScroll = false;
}
else
{
AutoScroll = true;
}
}
signals:
protected:
bool AutoScroll;
QMetaObject::Connection AutoScrollConnection;
private:
};
class Gui : public QMainWindow
{
Q_OBJECT
public:
Gui(QWidget *parent = 0);
~Gui();
protected:
ConsoleWindowModelClass *ConsoleWindowModel;
private:
Ui::Gui ui;
void createConsoleWindow();
//
private slots:
// GUI triggered
};
#endif // QTTESTS_H
Gui.cpp
#include <iostream>
#include <QTimer>
#include <QHeaderView>
#include <QCheckBox>
#include <QObject>
// Qt model handling the BusSimLowRateTx message from BusSim to DataSwitch.
#include <QAbstractTableModel>
#include "Gui.h"
Gui::Gui(QWidget *parent)
: QMainWindow(parent)
{
// create UI
ui.setupUi(this);
//
ui.actionExit->setShortcuts(QKeySequence::Quit);
ui.actionExit->setStatusTip(tr("Exit the application"));
connect(ui.actionExit, SIGNAL(triggered()), this, SLOT(close()));
// create models
ConsoleWindowModel = new ConsoleWindowModelClass(this); // should be deleted as part of QMainWindow destructor, by specifying this as the parent.
//
// connect ConsoleWindowModel to the View(s)
createConsoleWindow();
}
Gui::~Gui()
{
// ConsoleWindowModel - should be deleted as part of QMainWindow destructor.
//ConsoleWindowView *View = static_cast<ConsoleWindowView *>(ui.ConsoleOutputTable);
//disconnect(ui.AutoScroll, &QCheckBox::stateChanged, View, &ConsoleWindowView::autoScroll);
}
void Gui::createConsoleWindow()
{
ConsoleWindowView *View = static_cast<ConsoleWindowView *>(ui.ConsoleOutputTable);
View->setTheModel(ConsoleWindowModel);
for (int c = 1; c < View->horizontalHeader()->count(); ++c)
{
View->horizontalHeader()->setSectionResizeMode(c, QHeaderView::Stretch);
}
// connect the other ConsoleWindow Controls to the model
View->connectControls(ui.AutoScroll, ui.NewestAtTop, ui.MarkerA, ui.MarkerB, ui.MarkerC, ui.MarkerD);
}
Gui.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Gui</class>
<widget class="QMainWindow" name="Gui">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>593</width>
<height>652</height>
</rect>
</property>
<property name="windowTitle">
<string>Gui Tests</string>
</property>
<widget class="QWidget" name="centralwidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout_7"/>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>593</width>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="actionExit"/>
</widget>
<widget class="QMenu" name="menuView">
<property name="title">
<string>View</string>
</property>
</widget>
<addaction name="menuFile"/>
<addaction name="menuView"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QDockWidget" name="ConsoleOutput">
<property name="windowTitle">
<string>Console Output</string>
</property>
<attribute name="dockWidgetArea">
<number>8</number>
</attribute>
<widget class="QWidget" name="ConsoleOutputDock">
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QCheckBox" name="AutoScroll">
<property name="text">
<string>Auto Scroll</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="NewestAtTop">
<property name="text">
<string>Newest At Top</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="MarkerA">
<property name="styleSheet">
<string notr="true">background-color: rgb(255, 0, 0);</string>
</property>
<property name="text">
<string>Marker A</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="MarkerB">
<property name="styleSheet">
<string notr="true">background-color: rgb(0, 255, 0);</string>
</property>
<property name="text">
<string>Marker B</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="MarkerC">
<property name="styleSheet">
<string notr="true">background-color: rgb(255, 255, 0);</string>
</property>
<property name="text">
<string>Marker C</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="MarkerD">
<property name="styleSheet">
<string notr="true">background-color: rgb(0, 0, 255);
color: rgb(255, 255, 255);</string>
</property>
<property name="text">
<string>Marker D</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QTableView" name="ConsoleOutputTable"/>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<action name="actionExit">
<property name="text">
<string>Exit</string>
</property>
</action>
<action name="actionECIO_Outputs">
<property name="text">
<string>Enlightenment Monitor</string>
</property>
</action>
<action name="actionECIO_Inputs">
<property name="text">
<string>Enlightenment Control</string>
</property>
</action>
<action name="actionSHM_Control">
<property name="text">
<string>SHM Input</string>
</property>
</action>
<action name="actionSHM_Output">
<property name="text">
<string>SHM Output</string>
</property>
</action>
<zorder>ConsoleOutput</zorder>
</widget>
<resources/>
<connections/>
</ui>
您正在将 QTableView
的实例静态转换为 ConsoleWindowView
。它不会起作用,因为它不是 ConsoleWindowView
。你期待什么?你为什么 static_cast
它?
如果您不确定静态转换是否有效,请使用 qobject_cast
。它会失败,产生一个 nullptr 结果,并且您知道您必须停下来弄清楚为什么您没有获得正确的 class 实例。你不是因为你的 UI 文件坏了 - 你必须在 UI 文件中有正确类型的实例。
修复您的 UI 文件以实例化正确的 class:
- 在设计视图中右键单击 ConsoleOutputTable。
- 单击升级为...。
- 在 中输入
ConsoleWindowView
推广 class 名称:.
- 在头文件中输入
gui.h
:.
- 单击添加。
- 单击升级。
在任何情况下,您都不需要手动跟踪对象连接。
其他points/rants:
如果你想要一个计时器实例,只需在其中放置一个 QTimer
成员 - 使用指针并通过间接引用和额外的堆分配过早悲观化有什么意义?让你的生活更轻松。
抽象项视图已有一个名为 autoScroll
的 属性。你为一个新的 属性 乱用相同的名字,这让每个人的生活都变得很糟糕。将其命名为其他名称,例如 scrollToNewest
.
您的自动滚动 table 视图应该有一个 属性 来控制其行为。然后,您可以将 属性 连接到某个位置的控件,在该位置了解复选框等源代码控件很有用。将视图与某些随机控件紧密绑定是一个坏主意。观点应该是普遍的。您使用它的表单了解其他控件,并且知道如何将视图绑定到此类控件。
setModel
是虚方法。覆盖它,不要创建你自己的。
如果变量名以大写字母开头,您会把自己弄糊涂的。不要那样做。 类以大写字母开头,变量和成员以小写字母开头。
signals
和 slots
节宏的使用只应在信号 and/or 槽在逻辑上相关并且有很多时使用 - 这么多, 使用 Q_SIGNAL
或 Q_SLOT
前缀会有点多。在大多数情况下,您有一些 signals/slots 并且您应该改用前缀。然后,您可以轻松地将信号和槽声明与其他相关方法分组,以便声明的分组遵循功能,而不是实现细节。
在 Qt 5 中你根本不需要 Q_SLOT
前缀,任何方法都可以是新的 connect
语法的插槽。如果您希望方法的元数据在运行时可用,则只需要 Q_SLOT
或 Q_INVOKABLE
前缀。
考虑到以上情况,ConsoleWindowView
class 应该是这样的:
class ConsoleWindowView : public QTableView
{
Q_OBJECT
Q_PROPERTY(bool scrollToNewest READ scrollToNewest WRITE setScrollToNewest)
bool m_scrollToNewest;
void modelRowsInserted(const QModelIndex &, int start, int end) {
Q_UNUSED(end)
if (model() && m_scrollToNewest) scrollTo(model()->index(start, 0));
}
public:
ConsoleWindowView(QWidget *parent = 0) : QTableView(parent), m_scrollToNewest(true)
{}
void setModel(QAbstractItemModel *TheModel) Q_DECL_OVERRIDE
{
QTableView::setModel(TheModel);
connect(model(), &QAbstractItemModel::rowsInserted, this, &ConsoleWindowView::modelRowsInserted);
}
Q_SLOT void setScrollToNewest(bool s) { m_scrollToNewest = s; }
bool scrollToNewest() const { return m_scrollToNewest; }
};
Gui
class,在将控制台视图与用于调整它的具体控件分离之后:
class Gui : public QMainWindow
{
Q_OBJECT
Ui::Gui ui;
ConsoleWindowModel consoleWindowModel;
void connectConsoleWindow() {
auto view = ui.ConsoleOutputTable;
view->setModel(&consoleWindowModel);
for (int c = 1; c < view->horizontalHeader()->count(); ++c)
view->horizontalHeader()->setSectionResizeMode(c, QHeaderView::Stretch);
connect(ui.AutoScroll, &QCheckBox::toggled, view, &ConsoleWindowView::setScrollToNewest);
}
public:
Gui(QWidget *parent = 0) : QMainWindow(parent) {
ui.setupUi(this);
ui.actionExit->setShortcuts(QKeySequence::Quit);
ui.actionExit->setStatusTip(tr("Exit the application"));
connect(ui.actionExit, &QAction::triggered, this, &QWidget::close);
connectConsoleWindow();
}
};
请注意,我们只是根据 名称 connectConsoleWindow
方法的作用。您将其 错误地 命名为 createConsoleWindow
然后 评论 它的行为 - 也就是说,它不会创建任何东西,而只会连接东西。 这是一个可怕的反模式,必须被视为即时代码审查失败。在可行的情况下,根据他们所做的来命名事物(它在这里)。使代码自文档化。
最后,这是您应该如何实例化长期存在的成员(例如计时器等),而无需使用容易出现人为错误的原始指针和显式内存分配:
class ConsoleWindowModel : public QAbstractTableModel
{
Q_OBJECT
unsigned int m_rowCount;
QTimer m_controllerTimer;
void updateController()
{
beginInsertRows(QModelIndex(), m_rowCount, m_rowCount);
m_rowCount++;
endInsertRows();
}
public:
ConsoleWindowModel(QObject *parent = 0)
: QAbstractTableModel(parent)
, m_rowCount(0) {
connect(&m_controllerTimer, &QTimer::timeout, this, &ConsoleWindowModel::updateController);
m_controllerTimer.start(2000);
}
[...]
我在 VS2010SP1 上使用 Qt5.5。我在停靠小部件中有一个 QCheckBox,它控制同一个停靠小部件中的 QTableView(提示到 ConsoleWindowView)是否自动滚动。
问题一:
当我将 QCheckBox::stateChanged 信号连接到 QTableView 的插槽(自动滚动)时,一切似乎都很好,除非我更改 QCheckBox 的状态并退出。退出时出现错误:
Debug Error! Program: QtTests.exe HEAP CORRUPTION DETECTED: after Normal block (#4613) at 0x02B63850. CRT detected that the application wrote to memory after end of heap buffer.
如果我没有激活QCheckBox就退出了,退出是正常的。
问题二:
认为这可能与销毁顺序有关,我尝试在 QTableView 析构函数中 "remember" 连接和断开连接,但是当我尝试将 QObject::connect 中的 return 值分配给我得到的一个 QMetaObject::Connection 成员变量:
Unhandled exception at 0x669859c2 in QtTests.exe: 0xC0000005: Access violation writing location 0xabababc7
下面是代码,后面是相关的 ui 文件。
崩溃的区域在 Gui.h 方法 connectControls 中。对于问题1,配置为:
QObject::connect(AutoScrollCheckBox, &QCheckBox::stateChanged, this, &ConsoleWindowView::autoScroll); // CRASH1: Debug Error! Program: QtTests.exe HEAP CORRUPTION DETECTED: after Normal block (#4613) at 0x02B63850. CRT detected that the application wrote to memory after end of heap buffer.
//QMetaObject::Connection NewConnection = QObject::connect(AutoScrollCheckBox, &QCheckBox::stateChanged, this, &ConsoleWindowView::autoScroll);
//AutoScrollConnection = NewConnection; // CRASH2: Unhandled exception at 0x669859c2 in QtTests.exe: 0xC0000005: Access violation writing location 0xabababc7
对于问题2,配置为:
//QObject::connect(AutoScrollCheckBox, &QCheckBox::stateChanged, this, &ConsoleWindowView::autoScroll); // CRASH1: Debug Error! Program: QtTests.exe HEAP CORRUPTION DETECTED: after Normal block (#4613) at 0x02B63850. CRT detected that the application wrote to memory after end of heap buffer.
QMetaObject::Connection NewConnection = QObject::connect(AutoScrollCheckBox, &QCheckBox::stateChanged, this, &ConsoleWindowView::autoScroll);
AutoScrollConnection = NewConnection; // CRASH2: Unhandled exception at 0x669859c2 in QtTests.exe: 0xC0000005: Access violation writing location 0xabababc7
main.cpp
#include <QtWidgets/QApplication>
#include "Gui.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Gui w;
w.show();
return a.exec();
}
Gui.h
#ifndef QTTESTS_H
#define QTTESTS_H
#include <iostream>
#include <QTimer>
#include <QHeaderView>
#include <QCheckBox>
#include <QObject>
#include "ui_Gui.h"
class ConsoleWindowModelClass : public QAbstractTableModel
{
Q_OBJECT
public:
ConsoleWindowModelClass(QObject *parent)
: QAbstractTableModel(parent)
, RowCount(0)
{
//
ControllerTimer = new QTimer(this);
connect(ControllerTimer, SIGNAL(timeout()), this, SLOT(updateController()));
ControllerTimer->start(2000);
}
int rowCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE
{
return RowCount;
}
int columnCount(const QModelIndex &parent = QModelIndex()) const Q_DECL_OVERRIDE
{
return 2;
}
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const Q_DECL_OVERRIDE
{
if (role == Qt::DisplayRole)
{
return QString("Row%1, Column%2")
.arg(index.row() + 1)
.arg(index.column() +1);
}
return QVariant();
}
QVariant headerData(int section, Qt::Orientation orientation, int role) const Q_DECL_OVERRIDE
{
if (orientation == Qt::Horizontal)
{
if (role == Qt::DisplayRole)
{
switch(section)
{
case 0:
return "Stamp";
break;
case 1:
return "Text";
break;
default:
return QString("Column %1").arg(section + 1);
break;
}
}
}
else if (orientation == Qt::Vertical)
{
if (role == Qt::DisplayRole)
{
return QString("%1").arg(section + 1);
}
}
return QVariant();
}
protected:
unsigned int RowCount;
QTimer *ControllerTimer;
private slots:
// GUI triggered
// Timer triggered
void updateController()
{
beginInsertRows(QModelIndex(), RowCount, RowCount);
RowCount++;
endInsertRows();
}
};
class ConsoleWindowView : public QTableView
{
Q_OBJECT
public:
ConsoleWindowView(QWidget *parent = 0)
: QTableView(parent)
, AutoScroll(true)
{
}
virtual ~ConsoleWindowView()
{
//disconnect(AutoScrollConnection);
}
void setTheModel(QAbstractItemModel *TheModel)
{
QTableView::setModel(TheModel);
connect(model(), &QAbstractItemModel::rowsInserted, this, &ConsoleWindowView::modelRowsInserted);
}
void connectControls(QCheckBox *AutoScrollCheckBox, QCheckBox *ReverseList, QPushButton *MarkerA, QPushButton *MarkerB, QPushButton *MarkerC, QPushButton *MarkerD)
{
try
{
//QObject::connect(AutoScrollCheckBox, &QCheckBox::stateChanged, this, &ConsoleWindowView::autoScroll); // CRASH1: Debug Error! Program: QtTests.exe HEAP CORRUPTION DETECTED: after Normal block (#4613) at 0x02B63850. CRT detected that the application wrote to memory after end of heap buffer.
QMetaObject::Connection NewConnection = QObject::connect(AutoScrollCheckBox, &QCheckBox::stateChanged, this, &ConsoleWindowView::autoScroll);
AutoScrollConnection = NewConnection; // CRASH2: Unhandled exception at 0x669859c2 in QtTests.exe: 0xC0000005: Access violation writing location 0xabababc7
}
catch (const std::exception &Exception)
{
std::cerr << "ConsoleWindowView::connectControls: " << Exception.what() << std::endl;
}
}
public slots:
void modelRowsInserted(const QModelIndex & parent, int start, int end)
{
if (model() != nullptr)
{
if (AutoScroll)
{
scrollTo(model()->index(start, 0));
}
}
}
void autoScroll(int State)
{
if (State == 0)
{
AutoScroll = false;
}
else
{
AutoScroll = true;
}
}
signals:
protected:
bool AutoScroll;
QMetaObject::Connection AutoScrollConnection;
private:
};
class Gui : public QMainWindow
{
Q_OBJECT
public:
Gui(QWidget *parent = 0);
~Gui();
protected:
ConsoleWindowModelClass *ConsoleWindowModel;
private:
Ui::Gui ui;
void createConsoleWindow();
//
private slots:
// GUI triggered
};
#endif // QTTESTS_H
Gui.cpp
#include <iostream>
#include <QTimer>
#include <QHeaderView>
#include <QCheckBox>
#include <QObject>
// Qt model handling the BusSimLowRateTx message from BusSim to DataSwitch.
#include <QAbstractTableModel>
#include "Gui.h"
Gui::Gui(QWidget *parent)
: QMainWindow(parent)
{
// create UI
ui.setupUi(this);
//
ui.actionExit->setShortcuts(QKeySequence::Quit);
ui.actionExit->setStatusTip(tr("Exit the application"));
connect(ui.actionExit, SIGNAL(triggered()), this, SLOT(close()));
// create models
ConsoleWindowModel = new ConsoleWindowModelClass(this); // should be deleted as part of QMainWindow destructor, by specifying this as the parent.
//
// connect ConsoleWindowModel to the View(s)
createConsoleWindow();
}
Gui::~Gui()
{
// ConsoleWindowModel - should be deleted as part of QMainWindow destructor.
//ConsoleWindowView *View = static_cast<ConsoleWindowView *>(ui.ConsoleOutputTable);
//disconnect(ui.AutoScroll, &QCheckBox::stateChanged, View, &ConsoleWindowView::autoScroll);
}
void Gui::createConsoleWindow()
{
ConsoleWindowView *View = static_cast<ConsoleWindowView *>(ui.ConsoleOutputTable);
View->setTheModel(ConsoleWindowModel);
for (int c = 1; c < View->horizontalHeader()->count(); ++c)
{
View->horizontalHeader()->setSectionResizeMode(c, QHeaderView::Stretch);
}
// connect the other ConsoleWindow Controls to the model
View->connectControls(ui.AutoScroll, ui.NewestAtTop, ui.MarkerA, ui.MarkerB, ui.MarkerC, ui.MarkerD);
}
Gui.ui
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>Gui</class>
<widget class="QMainWindow" name="Gui">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>593</width>
<height>652</height>
</rect>
</property>
<property name="windowTitle">
<string>Gui Tests</string>
</property>
<widget class="QWidget" name="centralwidget">
<property name="sizePolicy">
<sizepolicy hsizetype="Maximum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<layout class="QGridLayout" name="gridLayout_7"/>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>593</width>
<height>21</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="actionExit"/>
</widget>
<widget class="QMenu" name="menuView">
<property name="title">
<string>View</string>
</property>
</widget>
<addaction name="menuFile"/>
<addaction name="menuView"/>
</widget>
<widget class="QStatusBar" name="statusbar"/>
<widget class="QDockWidget" name="ConsoleOutput">
<property name="windowTitle">
<string>Console Output</string>
</property>
<attribute name="dockWidgetArea">
<number>8</number>
</attribute>
<widget class="QWidget" name="ConsoleOutputDock">
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QCheckBox" name="AutoScroll">
<property name="text">
<string>Auto Scroll</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="NewestAtTop">
<property name="text">
<string>Newest At Top</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="MarkerA">
<property name="styleSheet">
<string notr="true">background-color: rgb(255, 0, 0);</string>
</property>
<property name="text">
<string>Marker A</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="MarkerB">
<property name="styleSheet">
<string notr="true">background-color: rgb(0, 255, 0);</string>
</property>
<property name="text">
<string>Marker B</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="MarkerC">
<property name="styleSheet">
<string notr="true">background-color: rgb(255, 255, 0);</string>
</property>
<property name="text">
<string>Marker C</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="MarkerD">
<property name="styleSheet">
<string notr="true">background-color: rgb(0, 0, 255);
color: rgb(255, 255, 255);</string>
</property>
<property name="text">
<string>Marker D</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QTableView" name="ConsoleOutputTable"/>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
<action name="actionExit">
<property name="text">
<string>Exit</string>
</property>
</action>
<action name="actionECIO_Outputs">
<property name="text">
<string>Enlightenment Monitor</string>
</property>
</action>
<action name="actionECIO_Inputs">
<property name="text">
<string>Enlightenment Control</string>
</property>
</action>
<action name="actionSHM_Control">
<property name="text">
<string>SHM Input</string>
</property>
</action>
<action name="actionSHM_Output">
<property name="text">
<string>SHM Output</string>
</property>
</action>
<zorder>ConsoleOutput</zorder>
</widget>
<resources/>
<connections/>
</ui>
您正在将 QTableView
的实例静态转换为 ConsoleWindowView
。它不会起作用,因为它不是 ConsoleWindowView
。你期待什么?你为什么 static_cast
它?
如果您不确定静态转换是否有效,请使用 qobject_cast
。它会失败,产生一个 nullptr 结果,并且您知道您必须停下来弄清楚为什么您没有获得正确的 class 实例。你不是因为你的 UI 文件坏了 - 你必须在 UI 文件中有正确类型的实例。
修复您的 UI 文件以实例化正确的 class:
- 在设计视图中右键单击 ConsoleOutputTable。
- 单击升级为...。
- 在 中输入
ConsoleWindowView
推广 class 名称:. - 在头文件中输入
gui.h
:. - 单击添加。
- 单击升级。
在任何情况下,您都不需要手动跟踪对象连接。
其他points/rants:
如果你想要一个计时器实例,只需在其中放置一个
QTimer
成员 - 使用指针并通过间接引用和额外的堆分配过早悲观化有什么意义?让你的生活更轻松。抽象项视图已有一个名为
autoScroll
的 属性。你为一个新的 属性 乱用相同的名字,这让每个人的生活都变得很糟糕。将其命名为其他名称,例如scrollToNewest
.您的自动滚动 table 视图应该有一个 属性 来控制其行为。然后,您可以将 属性 连接到某个位置的控件,在该位置了解复选框等源代码控件很有用。将视图与某些随机控件紧密绑定是一个坏主意。观点应该是普遍的。您使用它的表单了解其他控件,并且知道如何将视图绑定到此类控件。
setModel
是虚方法。覆盖它,不要创建你自己的。如果变量名以大写字母开头,您会把自己弄糊涂的。不要那样做。 类以大写字母开头,变量和成员以小写字母开头。
signals
和slots
节宏的使用只应在信号 and/or 槽在逻辑上相关并且有很多时使用 - 这么多, 使用Q_SIGNAL
或Q_SLOT
前缀会有点多。在大多数情况下,您有一些 signals/slots 并且您应该改用前缀。然后,您可以轻松地将信号和槽声明与其他相关方法分组,以便声明的分组遵循功能,而不是实现细节。在 Qt 5 中你根本不需要
Q_SLOT
前缀,任何方法都可以是新的connect
语法的插槽。如果您希望方法的元数据在运行时可用,则只需要Q_SLOT
或Q_INVOKABLE
前缀。
考虑到以上情况,ConsoleWindowView
class 应该是这样的:
class ConsoleWindowView : public QTableView
{
Q_OBJECT
Q_PROPERTY(bool scrollToNewest READ scrollToNewest WRITE setScrollToNewest)
bool m_scrollToNewest;
void modelRowsInserted(const QModelIndex &, int start, int end) {
Q_UNUSED(end)
if (model() && m_scrollToNewest) scrollTo(model()->index(start, 0));
}
public:
ConsoleWindowView(QWidget *parent = 0) : QTableView(parent), m_scrollToNewest(true)
{}
void setModel(QAbstractItemModel *TheModel) Q_DECL_OVERRIDE
{
QTableView::setModel(TheModel);
connect(model(), &QAbstractItemModel::rowsInserted, this, &ConsoleWindowView::modelRowsInserted);
}
Q_SLOT void setScrollToNewest(bool s) { m_scrollToNewest = s; }
bool scrollToNewest() const { return m_scrollToNewest; }
};
Gui
class,在将控制台视图与用于调整它的具体控件分离之后:
class Gui : public QMainWindow
{
Q_OBJECT
Ui::Gui ui;
ConsoleWindowModel consoleWindowModel;
void connectConsoleWindow() {
auto view = ui.ConsoleOutputTable;
view->setModel(&consoleWindowModel);
for (int c = 1; c < view->horizontalHeader()->count(); ++c)
view->horizontalHeader()->setSectionResizeMode(c, QHeaderView::Stretch);
connect(ui.AutoScroll, &QCheckBox::toggled, view, &ConsoleWindowView::setScrollToNewest);
}
public:
Gui(QWidget *parent = 0) : QMainWindow(parent) {
ui.setupUi(this);
ui.actionExit->setShortcuts(QKeySequence::Quit);
ui.actionExit->setStatusTip(tr("Exit the application"));
connect(ui.actionExit, &QAction::triggered, this, &QWidget::close);
connectConsoleWindow();
}
};
请注意,我们只是根据 名称 connectConsoleWindow
方法的作用。您将其 错误地 命名为 createConsoleWindow
然后 评论 它的行为 - 也就是说,它不会创建任何东西,而只会连接东西。 这是一个可怕的反模式,必须被视为即时代码审查失败。在可行的情况下,根据他们所做的来命名事物(它在这里)。使代码自文档化。
最后,这是您应该如何实例化长期存在的成员(例如计时器等),而无需使用容易出现人为错误的原始指针和显式内存分配:
class ConsoleWindowModel : public QAbstractTableModel
{
Q_OBJECT
unsigned int m_rowCount;
QTimer m_controllerTimer;
void updateController()
{
beginInsertRows(QModelIndex(), m_rowCount, m_rowCount);
m_rowCount++;
endInsertRows();
}
public:
ConsoleWindowModel(QObject *parent = 0)
: QAbstractTableModel(parent)
, m_rowCount(0) {
connect(&m_controllerTimer, &QTimer::timeout, this, &ConsoleWindowModel::updateController);
m_controllerTimer.start(2000);
}
[...]