用C++实现MVVM

用C++实现MVVM_第1张图片
MVVM

文章最先发表于 用C++实现MVVM.
欢迎关注 博客.

用C++实现MVVM

MVVM(Model-View-ViewModel)是现在比较流行的GUI程序的框架。

通过代码的编写,我谈谈我对于MVVM的理解。

整体代码的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进行更新,不知道是否是在我的理解中出现问题……)

项目目录

.
├── 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

class App
{
private:
    std::shared_ptr view;
    std::shared_ptr model;
    std::shared_ptr viewmodel;

public:
    App();
    void run();
};

在构造函数中,对各项需要初始化和绑定的数据进行绑定:


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

class View : public QMainWindow
{
    Q_OBJECT

public:
    explicit View(QWidget *parent = 0);
    ~View();

    void update();
    void set_img(std::shared_ptr image);
    void set_open_file_command(std::shared_ptr);
    void set_alter_bright_command(std::shared_ptr);
    void set_filter_rem_command(std::shared_ptr);
    void set_reset_command(std::shared_ptr);
    void set_detect_face_command(std::shared_ptr);
    void set_save_file_command(std::shared_ptr);
    void set_save_bmp_file_command(std::shared_ptr);
    void set_rotate_command(std::shared_ptr);
    void set_crop_command(std::shared_ptr);
    std::shared_ptr 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 q_image;
    std::shared_ptr open_file_command;
    std::shared_ptr alter_bright_command;
    std::shared_ptr filter_rem_command;
    std::shared_ptr reset_command;
    std::shared_ptr detect_face_command;
    std::shared_ptr save_file_command;
    std::shared_ptr save_bmp_file_command;
    std::shared_ptr rotate_command;
    std::shared_ptr crop_command;

    std::shared_ptr update_view_notification;
};

本身提供一个用于更新的notification, 并提供get()方法交给ViewModel层进行绑定,如此可以实现ViewModel通知View进行更新。

同时,本身提供很多Command的成员变量,这些变量本省并不属于View层,本身属于ViewModel层,并在ViewModel层提供get方法给View层进行set绑定,这样就实现了View发送commandViewModel层,View就可以在不知道Command具体派生类的情况下写代码。

ViewModel

class ViewModel
{
private:
    std::shared_ptr q_image;
    std::shared_ptr model;


    std::shared_ptr open_file_command;
    std::shared_ptr alter_bright_command;
    std::shared_ptr filter_rem_command;
    std::shared_ptr reset_command;
    std::shared_ptr detect_face_command;
    std::shared_ptr save_file_command;
    std::shared_ptr save_bmp_file_command;
    std::shared_ptr rotate_command;
    std::shared_ptr crop_command;

    std::shared_ptr update_display_data_notification;

    std::shared_ptr update_view_notification;

public:
    ViewModel();
    void bind(std::shared_ptr 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);

    std::shared_ptr get_open_file_command();
    std::shared_ptr get_alter_bright_command();
    std::shared_ptr get_filter_rem_command();
    std::shared_ptr get_reset_command();
    std::shared_ptr get_detect_face_command();
    std::shared_ptr get_save_file_command();
    std::shared_ptr get_save_bmp_file_command();
    std::shared_ptr get_rotate_command();
    std::shared_ptr get_crop_command();

    std::shared_ptr get_update_display_data_notification();
    std::shared_ptr get();

    void notified();
};

View层之间的通信在之前已经讲过,在构造函数中初始化具体的命令,然后get交给Viewset进行绑定。这其中有一个向基类指针的转换,我是这么写的:

 open_file_command = std::static_pointer_cast(std::shared_ptr (new OpenFileCommand(std::shared_ptr(this))));

然后与Model间的通信没有通过Command,而是直接获得一个Model的指针,调用它的功能函数即可。

Model


class Model
{
private:
     cv::Mat image;
     std::shared_ptr update_display_data_notification;
public:
    Model(){}
    void set_update_display_data_notification(std::shared_ptr 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(); //Filter No.1
    void reset();
    void rotate(double angle);
    void crop(int x1, int y1, int x2, int y2);
};


Model层本身又一个set一个notification的接口,这个notification用于通知ViewModel进行更新数据。

其他的就是针对数据的一些功能代码。

Command

本身可以写为纯虚类,我是写了一个成员变量是一个基类参数的指针,然后所有具体的command都是派生于此,提供exec()方法。


class Command
{
protected:
    std::shared_ptr params;
public:
    Command();
    void set_parameters(std::shared_ptr parameters){
        params = parameters;
    }
    virtual void exec() = 0;
};

Notification


class Notification
{
public:
    Notification();
    virtual void exec() = 0;
};



class UpdateDisplayDataNotification: public Notification{
private:
    std::shared_ptr viewmodel;
public:
    UpdateDisplayDataNotification(std::shared_ptr vm):viewmodel(vm){}
    void exec(){
        viewmodel->notified();
    }
};


class UpdateViewNotification: public Notification{
private:
    std::shared_ptr view;
public:
    UpdateViewNotification(std::shared_ptr v):view(v){}
    void exec(){
        view->update();
    }
};

Parameters


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::MatQImage之间的转换代码。

整体流程

View层进行操作之后,会触发对应槽函数,该槽函数会准备好参数Parameter交给对应的Command,然后执行exec()这个command,exec会解出参数交给ViewModel层,ViewModel调用Model里对应的方法,进行数据操作,Model操作完之后会通知ViewModel更新显示数据,ViewModel会通知View刷新显示。

你可能感兴趣的:(用C++实现MVVM)