基于 Qt 上的模型更新更新视图
Updating view based on model update on Qt
当视图使用的模型包装的数据发生变化时,我无法更新我的 qtableview。我这里不是说 appending/adding 数据,我是专门说修改现有数据。视图仅在我滚动或 select 个单元格时更新。换句话说,它仅在触发重绘时更新(这实际上是我的猜测)。所以,这让我觉得当数据被修改时调用 widget 的 update() 就足够了,但事实并非如此。
我正在以编程方式向模型添加数据,用户无法在模型中修改或添加数据。
这是最小的、可重现的例子:
mymodel.h:
#pragma once
#include <QAbstractTableModel>
#include <QVector>
#include "myobject.h"
class MyModel : public QAbstractTableModel
{
Q_OBJECT
private:
QVector<MyObject> my_objects_; /// underlying data structure for the model.
public:
MyModel(QObject * parent = {});
int rowCount(const QModelIndex &) const override;
int columnCount(const QModelIndex &) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::EditRole) const override;
void add(const MyObject& my_object);
void edit(const MyObject& my_object);
};
mymodel.cpp:
#include "mymodel.h"
#include <QDebug>
#include <iostream>
MyModel::MyModel(QObject * parent)
: QAbstractTableModel{parent}
{
}
int MyModel::rowCount(const QModelIndex &) const
{
return my_objects_.count();
}
int MyModel::columnCount(const QModelIndex &) const {
return 4;
}
QVariant MyModel::data(const QModelIndex &index, int role) const {
const auto row = index.row();
if(row == -1) {
return {};
}
const auto my_object = my_objects_[row];
if(role == Qt::DisplayRole) {
switch (index.column()) {
case 0: return my_object.id;
case 1: return my_object.a;
case 2: return my_object.b;
case 3: return my_object.c;
default: return {};
};
}
return {};
}
QVariant MyModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (orientation != Qt::Horizontal || role != Qt::DisplayRole) {
return {};
}
switch (section) {
case 0: return "id";
case 1: return "a";
case 2: return "b";
case 3: return "c";
default: return {};
}
}
void MyModel::add(const MyObject &my_object)
{
beginInsertRows({}, my_objects_.count(), my_objects_.count());
my_objects_.push_back(my_object);
endInsertRows();
}
void MyModel::edit(const MyObject& my_object) {
const auto id = my_object.id;
for(auto& my_object_ : my_objects_) {
if(my_object_.id == id) {
my_object_.a = my_object.a;
my_object_.b = my_object.b;
my_object_.c = my_object.c;
/// should I use dataChanged signal here? If so, how?
break;
}
}
}
myobject.h:
#pragma once
#include <QString>
struct MyObject {
int id;
int a;
double b;
QString c;
};
mainwindow.h:
#pragma once
#include <QMainWindow>
#include "mymodel.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void onEditClicked();
private:
Ui::MainWindow *ui;
MyModel my_model_;
};
mainwindow.cpp:
#include "mainwindow.h"
#include "./ui_mainwindow.h"
const static QVector<MyObject> g_my_objects{
{0, 1, 1.1, "object1"},
{1, 11, 1.2, "object2"},
{2, 12, 1.3, "object3"},
{3, 13, 1.4, "object4"},
{4, 14, 1.5, "object5"},
{5, 15, 1.6, "object6"},
{6, 16, 1.7, "object7"},
{7, 17, 1.8, "object8"}
};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
ui->tableView->setModel(&my_model_);
for(const auto& g_my_object : g_my_objects) {
my_model_.add(g_my_object);
}
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::onEditClicked()
{
qDebug() << __PRETTY_FUNCTION__;
my_model_.edit({2, 22222, 2222.222, "myobject22222"});
update(); /// seems to be doing nothing.
}
main.cpp:
#include "mainwindow.h"
#include <QApplication>
#include <QVector>
#include "myobject.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.ui:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Edit</string>
</property>
</widget>
</item>
<item>
<widget class="QTableView" name="tableView"/>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>23</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections>
<connection>
<sender>pushButton</sender>
<signal>clicked()</signal>
<receiver>MainWindow</receiver>
<slot>onEditClicked()</slot>
<hints>
<hint type="sourcelabel">
<x>604</x>
<y>41</y>
</hint>
<hint type="destinationlabel">
<x>951</x>
<y>20</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>onEditClicked()</slot>
</slots>
</ui>
CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(model_view_example VERSION 0.1 LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
set(PROJECT_SOURCES
main.cpp
mainwindow.cpp
mymodel.cpp
mainwindow.h
mainwindow.ui
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(model_view_example
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET model_view_example APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
if(ANDROID)
add_library(model_view_example SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(model_view_example
${PROJECT_SOURCES}
)
endif()
endif()
target_link_libraries(model_view_example PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
set_target_properties(model_view_example PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(model_view_example)
endif()
那么,为什么当我调用 update() 时视图没有更新?我的实现有问题吗?
在这里,我点击了编辑按钮,但是当我点击一个单元格时,视图只更新了它的新值:
我觉得 Qt MVC 示例并没有很好地映射到我在这里的操作方式。如果可能的话,我不知道如何以更像 Qt 的方式实现这个逻辑。
这是最小项目的 github 存储库:
只要现有项目中的数据发生变化,您就应该发出信号 dataChanged
。 QTableView 应该会自动更新。
参见:
我得到了 Qt forum 的帮助并解决了它。
当视图使用的模型包装的数据发生变化时,我无法更新我的 qtableview。我这里不是说 appending/adding 数据,我是专门说修改现有数据。视图仅在我滚动或 select 个单元格时更新。换句话说,它仅在触发重绘时更新(这实际上是我的猜测)。所以,这让我觉得当数据被修改时调用 widget 的 update() 就足够了,但事实并非如此。
我正在以编程方式向模型添加数据,用户无法在模型中修改或添加数据。
这是最小的、可重现的例子:
mymodel.h:
#pragma once
#include <QAbstractTableModel>
#include <QVector>
#include "myobject.h"
class MyModel : public QAbstractTableModel
{
Q_OBJECT
private:
QVector<MyObject> my_objects_; /// underlying data structure for the model.
public:
MyModel(QObject * parent = {});
int rowCount(const QModelIndex &) const override;
int columnCount(const QModelIndex &) const override;
QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::EditRole) const override;
void add(const MyObject& my_object);
void edit(const MyObject& my_object);
};
mymodel.cpp:
#include "mymodel.h"
#include <QDebug>
#include <iostream>
MyModel::MyModel(QObject * parent)
: QAbstractTableModel{parent}
{
}
int MyModel::rowCount(const QModelIndex &) const
{
return my_objects_.count();
}
int MyModel::columnCount(const QModelIndex &) const {
return 4;
}
QVariant MyModel::data(const QModelIndex &index, int role) const {
const auto row = index.row();
if(row == -1) {
return {};
}
const auto my_object = my_objects_[row];
if(role == Qt::DisplayRole) {
switch (index.column()) {
case 0: return my_object.id;
case 1: return my_object.a;
case 2: return my_object.b;
case 3: return my_object.c;
default: return {};
};
}
return {};
}
QVariant MyModel::headerData(int section, Qt::Orientation orientation, int role) const {
if (orientation != Qt::Horizontal || role != Qt::DisplayRole) {
return {};
}
switch (section) {
case 0: return "id";
case 1: return "a";
case 2: return "b";
case 3: return "c";
default: return {};
}
}
void MyModel::add(const MyObject &my_object)
{
beginInsertRows({}, my_objects_.count(), my_objects_.count());
my_objects_.push_back(my_object);
endInsertRows();
}
void MyModel::edit(const MyObject& my_object) {
const auto id = my_object.id;
for(auto& my_object_ : my_objects_) {
if(my_object_.id == id) {
my_object_.a = my_object.a;
my_object_.b = my_object.b;
my_object_.c = my_object.c;
/// should I use dataChanged signal here? If so, how?
break;
}
}
}
myobject.h:
#pragma once
#include <QString>
struct MyObject {
int id;
int a;
double b;
QString c;
};
mainwindow.h:
#pragma once
#include <QMainWindow>
#include "mymodel.h"
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void onEditClicked();
private:
Ui::MainWindow *ui;
MyModel my_model_;
};
mainwindow.cpp:
#include "mainwindow.h"
#include "./ui_mainwindow.h"
const static QVector<MyObject> g_my_objects{
{0, 1, 1.1, "object1"},
{1, 11, 1.2, "object2"},
{2, 12, 1.3, "object3"},
{3, 13, 1.4, "object4"},
{4, 14, 1.5, "object5"},
{5, 15, 1.6, "object6"},
{6, 16, 1.7, "object7"},
{7, 17, 1.8, "object8"}
};
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->tableView->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch);
ui->tableView->setModel(&my_model_);
for(const auto& g_my_object : g_my_objects) {
my_model_.add(g_my_object);
}
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::onEditClicked()
{
qDebug() << __PRETTY_FUNCTION__;
my_model_.edit({2, 22222, 2222.222, "myobject22222"});
update(); /// seems to be doing nothing.
}
main.cpp:
#include "mainwindow.h"
#include <QApplication>
#include <QVector>
#include "myobject.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}
mainwindow.ui:
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>600</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QPushButton" name="pushButton">
<property name="text">
<string>Edit</string>
</property>
</widget>
</item>
<item>
<widget class="QTableView" name="tableView"/>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>800</width>
<height>23</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections>
<connection>
<sender>pushButton</sender>
<signal>clicked()</signal>
<receiver>MainWindow</receiver>
<slot>onEditClicked()</slot>
<hints>
<hint type="sourcelabel">
<x>604</x>
<y>41</y>
</hint>
<hint type="destinationlabel">
<x>951</x>
<y>20</y>
</hint>
</hints>
</connection>
</connections>
<slots>
<slot>onEditClicked()</slot>
</slots>
</ui>
CMakeLists.txt:
cmake_minimum_required(VERSION 3.5)
project(model_view_example VERSION 0.1 LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets)
set(PROJECT_SOURCES
main.cpp
mainwindow.cpp
mymodel.cpp
mainwindow.h
mainwindow.ui
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(model_view_example
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET model_view_example APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
if(ANDROID)
add_library(model_view_example SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(model_view_example
${PROJECT_SOURCES}
)
endif()
endif()
target_link_libraries(model_view_example PRIVATE Qt${QT_VERSION_MAJOR}::Widgets)
set_target_properties(model_view_example PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(model_view_example)
endif()
那么,为什么当我调用 update() 时视图没有更新?我的实现有问题吗?
在这里,我点击了编辑按钮,但是当我点击一个单元格时,视图只更新了它的新值:
我觉得 Qt MVC 示例并没有很好地映射到我在这里的操作方式。如果可能的话,我不知道如何以更像 Qt 的方式实现这个逻辑。
这是最小项目的 github 存储库:
只要现有项目中的数据发生变化,您就应该发出信号 dataChanged
。 QTableView 应该会自动更新。
参见:
我得到了 Qt forum 的帮助并解决了它。