Sma*_*ash 7 c++ model-view-controller qt
免责声明:
正如第一个答案已经适当注意到的那样,在当前的示例案例中使用MVC是过度的.问题的目标是通过一个简单的例子来理解底层概念,以便能够在更大的程序中使用它们来修改更复杂的数据(数组,对象).
我试图在C++和QT中实现MVC模式,类似于这里的问题:
该程序有2行编辑:
3个按钮
并只修改字符串.
与另一个问题的区别在于,我试图实现Subject/Observer模式,以便在模型更改后更新View.
Model.h
#ifndef MODEL_H
#define MODEL_H
#include <QString>
#include <Subject>
class Model : virtual public Subject
{
public:
Model();
~Model();
void convertDecToHex(QString iDec);
void convertHexToDec(QString iHex);
void clear();
QString getDecValue() {return mDecValue;}
QString getHexValue() {return mHexValue;}
private:
QString mDecValue;
QString mHexValue;
};
#endif // MODEL_H
Run Code Online (Sandbox Code Playgroud)
Model.cpp
#include "Model.h"
Model::Model():mDecValue(""),mHexValue(""){}
Model::~Model(){}
void Model::convertDecToHex(QString iDec)
{
mHexValue = iDec + "Hex";
notify("HexValue");
}
void Model::convertHexToDec(QString iHex)
{
mDecValue = iHex + "Dec";
notify("DecValue");
}
void Model::clear()
{
mHexValue = "";
mDecValue = "";
notify("AllValues");
}
Run Code Online (Sandbox Code Playgroud)
View.h
#ifndef VIEW_H
#define VIEW_H
#include <QtGui/QMainWindow>
#include "ui_View.h"
#include <Observer>
class Controller;
class Model;
class View : public QMainWindow, public Observer
{
Q_OBJECT
public:
View(QWidget *parent = 0, Qt::WFlags flags = 0);
~View();
void setController(VController* iController);
void setModel(VModel* iModel);
QString getDecValue();
QString getHexValue();
public slots:
void ConvertToDecButtonClicked();
void ConvertToHexButtonClicked();
void ClearButtonClicked();
private:
virtual void update(Subject* iChangedSubject, std::string iNotification);
Ui::ViewClass ui;
Controller* mController;
Model* mModel;
};
#endif // VIEW_H
Run Code Online (Sandbox Code Playgroud)
View.cpp
#include "View.h"
#include "Model.h"
#include "Controller.h"
#include <QSignalMapper>
VWorld::VWorld(QWidget *parent, Qt::WFlags flags)
: QMainWindow(parent, flags)
{
ui.setupUi(this);
connect(ui.mConvertToHexButton,SIGNAL(clicked(bool)),this,SLOT(ConvertToHexButtonClicked()));
connect(ui.mConvertToDecButton,SIGNAL(clicked(bool)),this,SLOT(ConvertToDecButtonClicked()));
connect(ui.mClearButton,SIGNAL(clicked(bool)),this,SLOT(ClearButtonClicked()));
}
View::~View(){}
void View::setController(Controller* iController)
{
mController = iController;
//connect(ui.mConvertToHexButton,SIGNAL(clicked(bool)),this,SLOT(mController->OnConvertToHexButtonClicked(this)));
//connect(ui.mConvertToDecButton,SIGNAL(clicked(bool)),this,SLOT(mController->OnConvertToDecButtonClicked(this)));
//connect(ui.mClearButton,SIGNAL(clicked(bool)),this,SLOT(mController->OnClearButtonClicked(this)));
}
void View::setModel(Model* iModel)
{
mModel = iModel;
mModel->attach(this);
}
QString View::getDecValue()
{
return ui.mDecLineEdit->text();
}
QString View::getHexValue()
{
return ui.mHexLineEdit->text();
}
void View::ConvertToHexButtonClicked()
{
mController->OnConvertToHexButtonClicked(this);
}
void View::ConvertToDecButtonClicked()
{
mController->OnConvertToDecButtonClicked(this);
}
void VWorld::ClearButtonClicked()
{
mController->OnClearButtonClicked(this);
}
void View::update(Subject* iChangedSubject, std::string iNotification)
{
if(iNotification.compare("DecValue") == 0)
{
ui.mDecLineEdit->setText(mModel->getDecValue());
}
else if(iNotification.compare("HexValue") == 0)
{
ui.mHexLineEdit->setText(mModel->getHexValue());
}
else if(iNotification.compare("AllValues") == 0)
{
ui.mDecLineEdit->setText(mModel->getDecValue());
ui.mHexLineEdit->setText(mModel->getHexValue());
}
else
{
//Unknown notification;
}
}
Run Code Online (Sandbox Code Playgroud)
或者Controller.h
#ifndef CONTROLLER_H
#define CONTROLLER_H
//Forward Declaration
class Model;
class View;
class Controller
{
public:
Controller(Model* iModel);
virtual ~Controller();
void OnConvertToDecButtonClicked(View* iView);
void OnConvertToHexButtonClicked(View* iView);
void OnClearButtonClicked(View* iView);
private:
Model* mModel;
};
#endif // CONTROLLER_H
Run Code Online (Sandbox Code Playgroud)
Controller.cpp
#include "Controller.h"
#include "Model.h"
#include "View.h"
Controller::Controller(Model* iModel):mModel(iModel){}
Controller::~Controller(){}
void Controller::OnConvertToDecButtonClicked(View* iView)
{
QString wHexValue = iView->getHexValue();
mModel->convertHexToDec(wHexValue);
}
void Controller::OnConvertToHexButtonClicked(View* iView)
{
QString wDecValue = iView->getDecValue();
mModel->convertDecToHex(wDecValue);
}
void Controller::OnClearButtonClicked(View* iView)
{
mModel->clear();
}
Run Code Online (Sandbox Code Playgroud)
main.cpp中
#include "View.h"
#include "Model.h"
#include "Controller.h"
#include <QtGui/QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
Model wModel;
View wView;
Controller wCtrl(&wModel);
wView.setController(&wCtrl);
wView.setModel(&wModel);
wView.show();
return a.exec();
}
Run Code Online (Sandbox Code Playgroud)
如果它们变得相关,我可以稍后发布主题/观察者文件.
除了一般性评论,有人可以回答这些问题:
1)将按钮信号直接连接到控制器插槽(如在注释掉的部分中)会更好View::setController吗?Controller需要知道哪个View被调用,所以它可以使用来自View的正确信息呢?这意味着:
b)升级到Qt5和VS2012以便直接连接lambdas(C++ 11) ;
2)在模型调用更新时,了解更改内容的最佳方法是什么?它是一个切换/循环所有可能性,一个预定义的地图......?
3)另外,我应该通过更新功能传递必要的信息,还是让View在收到通知后检查模型的必需值?
在第二种情况下,View需要访问Model数据......
编辑:
特别是在有大量数据被修改的情况下.例如,有一个加载按钮,整个对象/数组被修改.通过信号/插槽机制将副本传递给View将非常耗时.
来自ddriver的回答
现在,如果您有一个传统的"项目列表"模型并且您的视图是列表/树/表,那么将是另一回事,但您的案例是单个表单之一.
4)View是否需要引用模型?因为它只对控制器起作用?(查看::则setModel())
如果没有,它如何将自己注册为模型的观察者?
你过度思考了一些几乎微不足道的事情。你也过度设计了。
是的,从 UI 中抽象逻辑总是一个好主意,但是在您的特定示例的情况下,不需要额外的数据抽象层,主要是因为您没有不同的数据集,您只有两个值实际上是逻辑的一部分,不需要数据抽象层。
现在,如果您有一个传统的“项目列表”模型并且您的视图是一个列表/树/表,那将是另一回事,但您的案例是单一形式之一。
在您的情况下,正确的设计应该是一个Converter包含您当前模型数据、控制器和转换逻辑的ConverterUI类,以及一个本质上是您的视图表单的类。您可以节省样板代码和组件互连。
话虽如此,你可以自由地经历不必要的长度和矫枉过正。
1 - 你从视图到控制器连接发出修改数据,所以它总是来自适当的视图,控制器不关心它是哪个视图,可能有多少视图,或者是否有视图. QSignalMapper是一个选项,但它相当有限——它只支持单个参数和几个参数类型。老实说,我自己更喜欢单行插槽,它们更灵活,而且并不难编写,而且它们是可重用的代码,有时会很方便。Lambdas 是一个很酷的新特性,使用它们肯定会让你看起来更酷,但在你的特定情况下,它们不会产生太大的不同,而且单独的 lambdas 不值得切换到 Qt5。话虽如此,除了 lambdas 之外,还有更多理由更新到 Qt5。
2 - 信号和槽,你知道你在编辑什么,所以你只更新
3 - 通过信号传递值更优雅,它不需要您的控制器保持对视图的引用并管理它是哪个视图,如 1 中所述
4 - 从 MVC 图中可以明显看出,视图对模型的引用仅供阅读。因此,如果您想要一个“书本上的”MVC,那就是您所需要的。
我对前面的例子进行了改进(有点,仍然未经测试),现在Data它只是一个常规结构,QObject如果你要拥有很多结构,你肯定不希望它是一个派生的,因为这QObject是巨大的内存开销,的Model,其保持的数据集,所述Controller它迭代底层Model数据集和读取和写入数据时,View它被绑定到控制器,并且App它汇集了一个模型和两个独立的控制器以及两个独立的视图。功能有限 - 您可以转到下一个可用的数据集条目,修改或删除,在此示例中没有添加或重新排序,您可以将它们作为练习来实现。更改将向下传播回模型,从而反映在每个控制器和相关视图中。您可以将多个不同的视图绑定到单个控制器。控制器的模型目前是固定的,但是如果你想改变它,你必须经历一个类似于为视图设置控制器的过程——也就是说,在连接到新的之前断开旧的,尽管如果你是删除旧的,它会自动断开连接。
struct Data {
QString d1, d2;
};
class Model : public QObject {
Q_OBJECT
QVector<Data> dataSet;
public:
Model() {
dataSet << Data{"John", "Doe"} << Data{"Jane", "Doe"} << Data{"Clark", "Kent"} << Data{"Rick", "Sanchez"};
}
int size() const { return dataSet.size(); }
public slots:
QString getd1(int i) const { return i > -1 && i < dataSet.size() ? dataSet[i].d1 : ""; }
QString getd2(int i) const { return i > -1 && i < dataSet.size() ? dataSet[i].d2 : ""; }
void setd1(int i, const QString & d) {
if (i > -1 && i < dataSet.size()) {
if (dataSet[i].d1 != d) {
dataSet[i].d1 = d;
emit d1Changed(i);
}
}
}
void setd2(int i, const QString & d) {
if (i > -1 && i < dataSet.size()) {
if (dataSet[i].d2 != d) {
dataSet[i].d2 = d;
emit d2Changed(i);
}
}
}
void remove(int i) {
if (i > -1 && i < dataSet.size()) {
removing(i);
dataSet.remove(i);
removed();
}
}
signals:
void removing(int);
void removed();
void d1Changed(int);
void d2Changed(int);
};
class Controller : public QObject {
Q_OBJECT
Model * data;
int index;
bool shifting;
public:
Controller(Model * _m) : data(_m), index(-1), shifting(false) {
connect(data, SIGNAL(d1Changed(int)), this, SLOT(ond1Changed(int)));
connect(data, SIGNAL(d2Changed(int)), this, SLOT(ond2Changed(int)));
connect(data, SIGNAL(removing(int)), this, SLOT(onRemoving(int)));
connect(data, SIGNAL(removed()), this, SLOT(onRemoved()));
if (data->size()){
index = 0;
dataChanged();
}
}
public slots:
QString getd1() const { return data->getd1(index); }
QString getd2() const { return data->getd2(index); }
void setd1(const QString & d) { data->setd1(index, d); }
void setd2(const QString & d) { data->setd2(index, d); }
void remove() { data->remove(index); }
private slots:
void onRemoving(int i) { if (i <= index) shifting = true; }
void onRemoved() {
if (shifting) {
shifting = false;
if ((index > 0) || (index && !data->size())) --index;
dataChanged();
}
}
void ond1Changed(int i) { if (i == index) d1Changed(); }
void ond2Changed(int i) { if (i == index) d2Changed(); }
void fetchNext() {
if (data->size()) {
index = (index + 1) % data->size();
dataChanged();
}
}
signals:
void dataChanged();
void d1Changed();
void d2Changed();
};
class View : public QWidget {
Q_OBJECT
Controller * c;
QLineEdit * l1, * l2;
QPushButton * b1, * b2, * bnext, * bremove;
public:
View(Controller * _c) : c(nullptr) {
QVBoxLayout * l = new QVBoxLayout;
setLayout(l);
l->addWidget(l1 = new QLineEdit(this));
l->addWidget(b1 = new QPushButton("set", this));
connect(b1, SIGNAL(clicked(bool)), this, SLOT(setd1()));
l->addWidget(l2 = new QLineEdit(this));
l->addWidget(b2 = new QPushButton("set", this));
connect(b2, SIGNAL(clicked(bool)), this, SLOT(setd2()));
l->addWidget(bnext = new QPushButton("next", this));
l->addWidget(bremove = new QPushButton("remove", this));
setController(_c);
}
void setController(Controller * _c) {
if (_c != c) {
if (c) {
disconnect(c, SIGNAL(d1Changed()), this, SLOT(updateL1()));
disconnect(c, SIGNAL(d2Changed()), this, SLOT(updateL2()));
disconnect(c, SIGNAL(dataChanged()), this, SLOT(updateForm()));
disconnect(bnext, SIGNAL(clicked(bool)), c, SLOT(fetchNext()));
disconnect(bremove, SIGNAL(clicked(bool)), c, SLOT(remove()));
c = nullptr;
}
c = _c;
if (c) {
connect(c, SIGNAL(d1Changed()), this, SLOT(updateL1()));
connect(c, SIGNAL(d2Changed()), this, SLOT(updateL2()));
connect(c, SIGNAL(dataChanged()), this, SLOT(updateForm()));
connect(bnext, SIGNAL(clicked(bool)), c, SLOT(fetchNext()));
connect(bremove, SIGNAL(clicked(bool)), c, SLOT(remove()));
}
}
updateForm();
}
public slots:
void updateL1() { l1->setText(c ? c->getd1() : ""); }
void updateL2() { l2->setText(c ? c->getd2() : ""); }
void updateForm() {
updateL1();
updateL2();
}
void setd1() { c->setd1(l1->text()); }
void setd2() { c->setd2(l2->text()); }
};
class App : public QWidget {
Q_OBJECT
Model m;
Controller c1, c2;
public:
App() : c1(&m), c2(&m) {
QVBoxLayout * l = new QVBoxLayout;
setLayout(l);
l->addWidget(new View(&c1));
l->addWidget(new View(&c2));
}
};
Run Code Online (Sandbox Code Playgroud)
1)直接将按钮信号连接到控制器插槽会更好吗(就像 View::setController 中注释掉的部分)?
是的,因为你所说的老虎机只是一句俏话。
控制器需要知道调用了哪个视图,以便它可以使用来自视图的正确信息,不是吗?
不必要。你不应该传递this你的信号。您应该传递已更改的数据。例如,在控制器类中,您可以有一个名为void SetDecValueTo(int)or的槽void SetDecValueTo(QString),只需从视图中调用它而不是传递this。
这意味着:
a) 重新实现 QSignalMapper 或
b) 升级到 Qt5 和 VS2012,以便直接与 lambda (C++11) 连接;
如上所述,您实际上并不需要这个。但总的来说,lambda 是未来的发展方向。
2)当模型调用 update 时,了解发生了什么变化的最佳方法是什么?它是一个切换/循环所有可能性、一个预定义的映射......?
在信号/槽中传递相关数据。例如,在您的模型中,您可以有一个信号void DecValueChanged(int)和void HexValueChanged(int)。您将它们连接到视图的插槽void UpdateDecValue(int)和void UpdateHexValue(int)。
3)另外,我应该通过更新函数传递必要的信息,还是让视图在收到通知后检查模型所需的值?
参见上段。
在第二种情况下,视图需要访问模型数据......
4)视图是否需要引用模型?因为它只作用于控制器?(视图::setModel())
如果不是,它如何将自己注册为 Model 的观察者?
在这种特殊情况下,它不需要引用模型。您可以在视图中进行所有连接main(),也可以在视图中进行,只是不保留对模型的引用。
最后,由于不需要做太多控制,您可以放弃控制器类并在视图中实现其功能,就像 Qt 中经常做的那样。请参阅模型/视图编程。