TouchGFX用户接口遵循Model-View-Presenter(MVP)架构模式,它是Model-View-Controller(MVC)模式的派生模式。 两者都广泛用于构建用户接口应用。

MVP模式的主要优势是:

  • 关注点分离:将代码分成不同的部分提供,每部分有自己的任务。 这使得代码更简单、可重复使用性更高且更易于维护。
  • 单元测试:由于UI的逻辑(Presenter)独立于视图(View),因此,单独测试这些部分会容易很多。

MVP中定义了下列三个类:

  • Model是一种接口,用于定义要在用户界面上显示或有其他形式操作的数据。
  • View是一种被动接口,用于显示数据(来自Model),并将用户指令(事件)传给Presenter以便根据该数据进行操作。
  • Presenter的操作取决于Model和View。 它从存储库(Model)检索数据,并将其格式化以便在视图中显示。

TouchGFX之MVP-LMLPHP

在TouchGFX中,从Model类执行与应用非UI部分(这里称为后端系统)的通信。 后端系统是从UI接收事件和将事件输入UI的软件组件,例如采集传感器的新测量值。 后端系统可作为单独的任务在同一MCU、单独的处理器、云模块或其他硬件上运行。 从TouchGFX的角度来看,这并不十分重要,只要它是能够与之通信的组件。

使用的特定通信协议不受TouchGFX管理。 它只提供一个在每个TouchGFX嘀嗒时间调用一次的函数,可以在其中处理需要的通信。 

TouchGFX之MVP-LMLPHP

从上节内容已知,touchgfx中只有一个Model的实例对象,但每一个屏幕都对应一个View和Presenter的实例对象。

从MVP类的代码可以看出

Model实例对象可以绑定一个ModelListener(用户端Presenter的基类之一)实例对象指针,即Model实例对象可以通过该指针查询到Presenter实例对象。

每个View实例对象可以绑定一个Presenter实例对象指针,即View实例对象可以通过该指针查询到Presenter实例对象。

每个用户端Presenter实例对象(基于ModelListener类)可以绑定Model实例对象指针,即用户端Presenter实例对象可以通过该指针查询到Model实例对象。

用户端Presenter类中包含View实例对象的引用,即用户端Presenter实例对象可以通过该引用查询到View实例对象。

#ifndef MODEL_HPP
#define MODEL_HPP

class ModelListener;

/* Model类 */
class Model
{
public:
	Model();

	/* 将给定的Presenter对象指针与Model关联起来 */
	void bind(ModelListener *listener)
	{
		modelListener = listener;
	}

	/* 节拍函数 */
	void tick();
	
protected:
	ModelListener *modelListener;		//当前Presenter对象指针
};

#endif
#ifndef TOUCHGFX_VIEW_HPP
#define TOUCHGFX_VIEW_HPP

#include <touchgfx/Screen.hpp>

namespace touchgfx
{
/* View类 */
template <class T>
class View : public Screen
{
public:
	/* 构造函数 */
	View() : presenter(0)
	{
	}

	/* 将给定的Presenter实例与View关联起来 */
	void bind(T& newPresenter)
	{
		presenter = &newPresenter;
	}

protected:
	T *presenter; //指向与此View关联的presenter的指针
};

}

#endif
#ifndef TOUCHGFX_PRESENTER_HPP
#define TOUCHGFX_PRESENTER_HPP

namespace touchgfx
{
/* Presenter类 */
class Presenter
{
public:
	/* 当Presenter由于屏幕进入而变为活动状态时,会自动调用此函数。它通常用于初始化Presenter */
	virtual void activate()
	{
	}

	/* 当Presenter由于屏幕退出而变为非活动状态时,会自动调用此函数。它通常用于清理Presenter */
	virtual void deactivate()
	{
	}

	/* 析构函数 */
	virtual ~Presenter()
	{
	}

protected:
	/* 构造函数 */
	Presenter()
	{
	}
};

}

#endif
#ifndef MODELLISTENER_HPP
#define MODELLISTENER_HPP

#include <gui/model/Model.hpp>

/* ModelListener类 */
class ModelListener
{
public:
	/* 构造函数 */
	ModelListener() : model(0) {}
	
	/* 析构函数 */
	virtual ~ModelListener() {}

	/* 将Model与Presenter实例关联起来 */
	void bind(Model *m)
	{
		model = m;
	}
	
protected:
	Model *model;		//Model实例指针
};

#endif
#ifndef SCREENPRESENTER_HPP
#define SCREENPRESENTER_HPP
#include <gui/model/ModelListener.hpp>
#include <mvp/Presenter.hpp>

using namespace touchgfx;

class screenView;

/* 用户端某screen对应的Presenter类 */
class screenPresenter : public touchgfx::Presenter, public ModelListener
{
public:
    ...

private:
    ...

    screenView& view;
};

#endif

 

通过MVPApplication类的代码可以大概看出引擎是怎么通过MVP切换屏幕的

首先,pendingScreenTransitionCallback不为空,引擎将调用evaluatePendingScreenTransition函数执行切换回调函数(用户需要在切换回调函数中会调用makeTransition)来实现屏幕切换

#ifndef TOUCHGFX_MVPAPPLICATION_HPP
#define TOUCHGFX_MVPAPPLICATION_HPP
#include <new>
#include <common/AbstractPartition.hpp>
#include <mvp/MVPHeap.hpp>
#include <mvp/Presenter.hpp>
#include <touchgfx/Application.hpp>
#include <touchgfx/Callback.hpp>
#include <touchgfx/Screen.hpp>
#include <touchgfx/hal/Types.hpp>
#include <touchgfx/transitions/Transition.hpp>

namespace touchgfx
{
class Presenter;

/* MVP应用类 */
class MVPApplication : public Application
{
public:
	/* 构造函数 */
	MVPApplication() : currentPresenter(0), pendingScreenTransitionCallback(0)
	{
		instance = this;
	}

	/* 执行挂起的屏幕转换回调 */
	virtual void handlePendingScreenTransition()
	{
		evaluatePendingScreenTransition();
	}

protected:
	Presenter *currentPresenter; //指向当前活动的Presenter对象的指针

	GenericCallback<>* pendingScreenTransitionCallback; //指向挂起的屏幕转换回调指针

	/* 执行挂起的屏幕转换回调 */
	void evaluatePendingScreenTransition()
	{
		if(pendingScreenTransitionCallback && pendingScreenTransitionCallback->isValid())
		{
			pendingScreenTransitionCallback->execute();
			pendingScreenTransitionCallback = 0;	//调用完后将指针清空,等待下一次注册
		}
	}
};

/* 准备屏幕转换:当退出当前屏幕前,需要清理正在活动的一些数据 */
FORCE_INLINE_FUNCTION static void prepareTransition(Screen **currentScreen, Presenter **currentPresenter, Transition **currentTrans)
{
	/* 卸载屏幕中所有定时的控件 */
	Application::getInstance()->clearAllTimerWidgets();

	/* 清理当前Transition */
	if(*currentTrans)
	{
		(*currentTrans)->tearDown();
	}
	
	/* 析构当前Transition */
	if(*currentTrans)
	{
		(*currentTrans)->~Transition();
	}
	
	/* 清理当前View */
	if(*currentScreen)
	{
		(*currentScreen)->tearDownScreen();
	}
	
	/* 清理当前Presenter */
	if(*currentPresenter)
	{
		(*currentPresenter)->deactivate();
	}
	
	/* 析构当前View */
	if(*currentScreen)
	{
		(*currentScreen)->~Screen();
	}
	
	/* 析构当前Presenter */
	if(*currentPresenter)
	{
		(*currentPresenter)->~Presenter();
	}
}

/* 完成屏幕转换:当进入新屏幕后,需要初始化新屏幕的一些数据。然后重绘新屏幕 */
FORCE_INLINE_FUNCTION static void finalizeTransition(Screen *newScreen, Presenter *newPresenter, Transition *newTransition)
{
	/* 初始化新的View */
	newScreen->setupScreen();
	/* 初始化新的Presenter */
	newPresenter->activate();
	/* 将新给定的Transition实例与新的View关联起来 */
	newScreen->bindTransition(*newTransition);
	/* 初始化新的Transition */
	newTransition->init();
	/* 重绘整个屏幕 */
	newTransition->invalidate();
}

/* 用于实现屏幕转换的功能 */
template <class ScreenType, class PresenterType, class TransType, class ModelType>
PresenterType *makeTransition(Screen **currentScreen, Presenter **currentPresenter, MVPHeap& heap, Transition **currentTrans, ModelType *model)
{
	assert(sizeof(ScreenType) <= heap.screenStorage.element_size() && "View allocation error: Check that all views are added to FrontendHeap::ViewTypes");
	assert(sizeof(PresenterType) <= heap.presenterStorage.element_size() && "Presenter allocation error: Check that all presenters are added to FrontendHeap::PresenterTypes");
	assert(sizeof(TransType) <= heap.transitionStorage.element_size() && "Transition allocation error: Check that all transitions are added to FrontendHeap::TransitionTypes");

	/* 准备屏幕转换:当退出当前屏幕前,需要清理正在活动的一些数据 */
	prepareTransition(currentScreen, currentPresenter, currentTrans);

	/* 在相应的内存分区中构建新的Transition、View、Presenter实例 */
	TransType *newTransition = new (&heap.transitionStorage.at<TransType>(0)) TransType;
	ScreenType *newScreen = new (&heap.screenStorage.at<ScreenType>(0)) ScreenType;
	PresenterType *newPresenter = new (&heap.presenterStorage.at<PresenterType>(0)) PresenterType(*newScreen);
	
	/* 返回新的Transition、View、Presenter实例对象指针 */
	*currentTrans = newTransition;
	*currentPresenter = newPresenter;
	*currentScreen = newScreen;
	
	/* 将新给定的Presenter实例与Model关联起来 */
	model->bind(newPresenter);
	/* 将Model与新的Presenter实例关联起来 */
	newPresenter->bind(model);
	/* 将新给定的Presenter实例与新的View关联起来 */
	newScreen->bind(*newPresenter);

	/* 完成屏幕转换:当进入新屏幕后,需要初始化新屏幕的一些数据。然后重绘新屏幕 */
	finalizeTransition((Screen *)newScreen, (Presenter *)newPresenter, (Transition *)newTransition);

	return newPresenter;
}

}

#endif

 在前端应用基类(FrontendApplicationBase)中,实现了对各种屏幕切换函数的封装

#ifndef FRONTENDAPPLICATIONBASE_HPP
#define FRONTENDAPPLICATIONBASE_HPP
#include <mvp/MVPApplication.hpp>
#include <gui/model/Model.hpp>

class FrontendHeap;

/* 前端应用基类 */
class FrontendApplicationBase : public touchgfx::MVPApplication
{
public:
	/* 构造函数 */
	FrontendApplicationBase(Model& m, FrontendHeap& heap);

	/* 析构函数 */
	virtual ~FrontendApplicationBase() { }

	/* 切换到起始屏幕 */
	virtual void changeToStartScreen()
	{
		gotoscreenScreenNoTransition();
	}

	/* 不带任何切换动画切换到screen屏幕 */
	void gotoscreenScreenNoTransition();

protected:
	touchgfx::Callback<FrontendApplicationBase> transitionCallback;		//切换回调
	FrontendHeap& frontendHeap;		//前端内存堆引用
	Model& model;		//Model引用

	void gotoscreenScreenNoTransitionImpl();	//不带任何切换动画切换到screen屏幕回调执行函数
};

#endif
#include <new>
#include <gui_generated/common/FrontendApplicationBase.hpp>
#include <gui/common/FrontendHeap.hpp>
#include <touchgfx/transitions/NoTransition.hpp>
#include <texts/TextKeysAndLanguages.hpp>
#include <touchgfx/Texts.hpp>
#include <touchgfx/hal/HAL.hpp>
#include <platform/driver/lcd/LCD16bpp.hpp>
#include <gui/screen_screen/screenView.hpp>
#include <gui/screen_screen/screenPresenter.hpp>

using namespace touchgfx;

FrontendApplicationBase::FrontendApplicationBase(Model& m, FrontendHeap& heap)
    : touchgfx::MVPApplication(),
      transitionCallback(),
      frontendHeap(heap),
      model(m)
{
    touchgfx::HAL::getInstance()->setDisplayOrientation(touchgfx::ORIENTATION_LANDSCAPE);
    reinterpret_cast<touchgfx::LCD16bpp&>(touchgfx::HAL::lcd()).enableTextureMapperAll();
    reinterpret_cast<touchgfx::LCD16bpp&>(touchgfx::HAL::lcd()).enableDecompressorL8_All();
}

/* 不带任何切换动画切换到screen屏幕 */
void FrontendApplicationBase::gotoscreenScreenNoTransition()
{
	/* 设置切换屏幕回调 */
	transitionCallback = touchgfx::Callback<FrontendApplicationBase>(this, &FrontendApplicationBase::gotoscreenScreenNoTransitionImpl);
	/* 将该切换行为注册到pendingScreenTransitionCallback */
	pendingScreenTransitionCallback = &transitionCallback;
}

/* 不带任何切换动画切换到screen屏幕回调执行函数 */
void FrontendApplicationBase::gotoscreenScreenNoTransitionImpl()
{
	/* 实现屏幕转换的功能 */
	touchgfx::makeTransition<screenView, screenPresenter, touchgfx::NoTransition, Model >(&currentScreen, &currentPresenter, frontendHeap, &currentTransition, &model);
}

在前端应用类(FrontendApplication)中,调用model.tick()。即为model提供周期节拍,用来采样UI所需要的数据

#ifndef FRONTENDAPPLICATION_HPP
#define FRONTENDAPPLICATION_HPP
#include <gui_generated/common/FrontendApplicationBase.hpp>

class FrontendHeap;

using namespace touchgfx;

class FrontendApplication : public FrontendApplicationBase
{
public:
	FrontendApplication(Model& m, FrontendHeap& heap);
	virtual ~FrontendApplication() { }

	/* 定时事件 */
	virtual void handleTickEvent()
	{
		model.tick();	//调用model定时事件
		FrontendApplicationBase::handleTickEvent();	//调用基类定时事件
	}
private:
};

#endif
#include <gui/common/FrontendApplication.hpp>

FrontendApplication::FrontendApplication(Model& m, FrontendHeap& heap)
    : FrontendApplicationBase(m, heap)
{

}
03-16 19:39