序 MVVM
(Model-View-ViewModel
)是现在比较流行的GUI程序的框架。
整体代码的sample在Graphics Editor 可以看到。
GUI库使用了QT5.9
,功能代码主要使用了OpenCV
库。
后面一些功能的编写不是我写的,所以代码风格可能有些不和谐,这里主要集中精力于整个框架的实现,忽略其各项功能的实现。
如果有任何理解不对的地方,欢迎批评指出。
MVVM 在阮一峰的”MVC,MVP 和 MVVM 的图示” 中, 介绍了三个架构之间的区别。
总结来说,就是在Model,View,ViewModel三个模块之间,View与ViewModel之间的数据通过双向绑定进行联系,View与Model之间不产生联系,ViewModel操作Model进行数据处理。
(这里实际写代码的时候好像跟阮老师所说的有一些区别:按照阮老师所说,应该是ViewModel在功能上相当于MVP模式中的Presenter,所有逻辑都部署在这里,实际上写的时候应该是大部分逻辑都部署在Model层进行数据操作,然后通知ViewModel和View进行更新,不知道是否是在我的理解中出现问题……)
项目目录 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 . ├── app.cpp ├── app.h ├── command.cpp ├── command.h ├── Commands │ ├── alter_bright_command.cpp │ ├── alter_bright_command.h │ ├── crop_command.cpp │ ├── crop_command.h │ ├── detect_face_command.cpp │ ├── detect_face_command.h │ ├── filter_command.cpp │ ├── filter_command.h │ ├── open_file_command.cpp │ ├── open_file_command.h │ ├── reset_command.cpp │ ├── reset_command.h │ ├── rotate_command.cpp │ ├── rotate_command.h │ ├── save_bmp_command.cpp │ ├── save_bmp_command.h │ ├── save_file_command.cpp │ └── save_file_command.h ├── common.cpp ├── common.h ├── GraphicsEditor.pro ├── GraphicsEditor.pro.user ├── LICENSE ├── main.cpp ├── model.cpp ├── model.h ├── MyView.cpp ├── MyView.h ├── notification.cpp ├── notification.h ├── parameters.cpp ├── parameters.h ├── README.md ├── test.pro ├── test.pro.user ├── view.cpp ├── view.h ├── viewmodel.cpp ├── viewmodel.h └── view.ui
项目架构介绍 各个类以及之间关系如下:
App 1 2 3 4 5 6 7 8 9 10 11 class App { private : std::shared_ptr<View> view; std::shared_ptr<Model> model; std::shared_ptr<ViewModel> viewmodel; public : App (); void run () ; };
在构造函数中,对各项需要初始化和绑定的数据进行绑定:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 App::App ():view (new View),model (new Model), viewmodel (new ViewModel) { viewmodel->bind (model); view->set_img (viewmodel->get ()); view->set_open_file_command (viewmodel->get_open_file_command ()); view->set_alter_bright_command (viewmodel->get_alter_bright_command ()); view->set_filter_rem_command (viewmodel->get_filter_rem_command ()); view->set_reset_command (viewmodel->get_reset_command ()); view->set_detect_face_command (viewmodel->get_detect_face_command ()); view->set_save_file_command (viewmodel->get_save_file_command ()); view->set_save_bmp_file_command (viewmodel->get_save_bmp_file_command ()); view->set_rotate_command (viewmodel->get_rotate_command ()); view->set_crop_command (viewmodel->get_crop_command ()); viewmodel->set_update_view_notification (view->get_update_view_notification ()); model->set_update_display_data_notification (viewmodel->get_update_display_data_notification ()); }
View 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 class View : public QMainWindow{ Q_OBJECT public : explicit View (QWidget *parent = 0 ) ; ~View (); void update () ; void set_img (std::shared_ptr<QImage> image) ; void set_open_file_command (std::shared_ptr<Command>) ; void set_alter_bright_command (std::shared_ptr<Command>) ; void set_filter_rem_command (std::shared_ptr<Command>) ; void set_reset_command (std::shared_ptr<Command>) ; void set_detect_face_command (std::shared_ptr<Command>) ; void set_save_file_command (std::shared_ptr<Command>) ; void set_save_bmp_file_command (std::shared_ptr<Command>) ; void set_rotate_command (std::shared_ptr<Command>) ; void set_crop_command (std::shared_ptr<Command>) ; std::shared_ptr<Notification> get_update_view_notification () ; private slots: void on_button_open_clicked () ; void on_brightSlider_valueChanged (int value) ; void on_contrastSlider_valueChanged (int value) ; void on_filter_1_clicked () ; void on_reset_clicked () ; void on_actionOpen_File_triggered () ; void on_button_detect_face_clicked () ; void on_actionSave_triggered () ; void on_action_bmp_triggered () ; void on_action_png_triggered () ; void on_action_jpeg_triggered () ; void on_rotateSlider_valueChanged (int value) ; private : Ui::View *ui; MyView* canvas; std::shared_ptr<QImage> q_image; std::shared_ptr<Command> open_file_command; std::shared_ptr<Command> alter_bright_command; std::shared_ptr<Command> filter_rem_command; std::shared_ptr<Command> reset_command; std::shared_ptr<Command> detect_face_command; std::shared_ptr<Command> save_file_command; std::shared_ptr<Command> save_bmp_file_command; std::shared_ptr<Command> rotate_command; std::shared_ptr<Command> crop_command; std::shared_ptr<Notification> update_view_notification; };
本身提供一个用于更新的notification
, 并提供get()
方法交给ViewModel
层进行绑定,如此可以实现ViewModel
通知View
进行更新。
同时,本身提供很多Command
的成员变量,这些变量本省并不属于View
层,本身属于ViewModel
层,并在ViewModel
层提供get
方法给View
层进行set
绑定,这样就实现了View
发送command
给ViewModel
层,View
就可以在不知道Command具体派生类的情况下写代码。
ViewModel 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 class ViewModel { private : std::shared_ptr<QImage> q_image; std::shared_ptr<Model> model; std::shared_ptr<Command> open_file_command; std::shared_ptr<Command> alter_bright_command; std::shared_ptr<Command> filter_rem_command; std::shared_ptr<Command> reset_command; std::shared_ptr<Command> detect_face_command; std::shared_ptr<Command> save_file_command; std::shared_ptr<Command> save_bmp_file_command; std::shared_ptr<Command> rotate_command; std::shared_ptr<Command> crop_command; std::shared_ptr<Notification> update_display_data_notification; std::shared_ptr<Notification> update_view_notification; public : ViewModel (); void bind (std::shared_ptr<Model> model) ; void exec_open_file_command (std::string path) ; void exec_alter_bright_command (int nBright, int nContrast) ; void exec_filter_rem_command () ; void exec_reset_command () ; void exec_detect_face_command () ; void exec_save_file_command (std::string path) ; void exec_save_bmp_file_command (std::string path) ; void exec_rotate_command (int angle) ; void exec_crop_command (double x_s, double y_s, double x_e, double y_e) ; void set_update_view_notification (std::shared_ptr<Notification> notification) ; std::shared_ptr<Command> get_open_file_command () ; std::shared_ptr<Command> get_alter_bright_command () ; std::shared_ptr<Command> get_filter_rem_command () ; std::shared_ptr<Command> get_reset_command () ; std::shared_ptr<Command> get_detect_face_command () ; std::shared_ptr<Command> get_save_file_command () ; std::shared_ptr<Command> get_save_bmp_file_command () ; std::shared_ptr<Command> get_rotate_command () ; std::shared_ptr<Command> get_crop_command () ; std::shared_ptr<Notification> get_update_display_data_notification () ; std::shared_ptr<QImage> get () ; void notified () ; };
与View
层之间的通信在之前已经讲过,在构造函数中初始化具体的命令,然后get
交给View
的set
进行绑定。这其中有一个向基类指针的转换,我是这么写的:
1 open_file_command = std::static_pointer_cast<Command, OpenFileCommand>(std::shared_ptr<OpenFileCommand> (new OpenFileCommand (std::shared_ptr<ViewModel>(this ))));
然后与Model
间的通信没有通过Command
,而是直接获得一个Model
的指针,调用它的功能函数即可。
Model 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 class Model { private : cv::Mat image; std::shared_ptr<Notification> update_display_data_notification; public : Model (){} void set_update_display_data_notification (std::shared_ptr<Notification> notification) ; void open_file (std::string path) ; cv::Mat& get () ; cv::Mat& getOrigin () ; void notify () ; void save_file (std::string path) ; void save_bmp_file (std::string path) ; void alterBrightAndContrast (int nbright, int nContrast) ; void detect_face () ; void filterReminiscence () ; void reset () ; void rotate (double angle) ; void crop (int x1, int y1, int x2, int y2) ; };
Model
层本身又一个set一个notification的接口,这个notification用于通知ViewModel
进行更新数据。
其他的就是针对数据的一些功能代码。
Command 本身可以写为纯虚类,我是写了一个成员变量是一个基类参数的指针,然后所有具体的command都是派生于此,提供exec()
方法。
1 2 3 4 5 6 7 8 9 10 11 12 class Command { protected : std::shared_ptr<Parameters> params; public : Command (); void set_parameters (std::shared_ptr<Parameters> parameters) { params = parameters; } virtual void exec () = 0 ; };
Notification 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Notification { public : Notification (); virtual void exec () = 0 ; }; class UpdateDisplayDataNotification : public Notification{private : std::shared_ptr<ViewModel> viewmodel; public : UpdateDisplayDataNotification (std::shared_ptr<ViewModel> vm):viewmodel (vm){} void exec () { viewmodel->notified (); } }; class UpdateViewNotification : public Notification{private : std::shared_ptr<View> view; public : UpdateViewNotification (std::shared_ptr<View> v):view (v){} void exec () { view->update (); } };
Parameters 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 class Parameters { public : Parameters (); }; class PathParameters : public Parameters{private : std::string path; public : PathParameters (std::string _path):path (_path){ } std::string get_path () { return path; } };
以PathParameters
为例表示了一般的新的参数的派生方法。
common 实现了cv::Mat
与QImage
之间的转换代码。
整体流程 在View
层进行操作之后,会触发对应槽函数,该槽函数会准备好参数Parameter
交给对应的Command
,然后执行exec()
这个command,exec会解出参数交给ViewModel
层,ViewModel
调用Model
里对应的方法,进行数据操作,Model
操作完之后会通知ViewModel
更新显示数据,ViewModel
会通知View
刷新显示。
本文标题: 用C++实现MVVM
文章作者: Han Yang
发布时间: 2017-07-12
最后更新: 2022-09-06
原始链接: https://archived.yanghan.life/2017/07/12/%E7%94%A8C-%E5%AE%9E%E7%8E%B0MVVM/
版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 3.0 CN 许可协议。转载请注明出处!