TouchGFX中MVP框架和前后端关系
前面几篇完成智能零售界面的商品浏览界面显示、结算清单浏览以及支付二维码显示部分,通过使用TouchGFX中提供的容器、交互动作以及重写虚函数等方式添加用户自定义的控件和功能。在GUI设计中这部分内容属于前端设计,用于存储和管理数据的部分称为后端。TouchGFX框架MVP模型中Model部分用于前端和后端系统进行数据交互。

TouchGFX的MVP框架
MVP框架中Model和View属于分离的部分,Presenter数据连接两者的桥梁,在C++类结构中,Model类和View类作为Presenter类的基类,从而实现三者之间的逻辑关系。
https://support.touchgfx.com/docs/development/ui-development/touchgfx-engine-features/backend-communication
TouchGFX文档中关于Model Class的部分,指出不适合在单独的View类中对其余的软件模块进行通信。
Model在设计上适合用于添加接口代码,得益于:
- Model Class有一个tick()函数,在帧间隔自动调用,可以用于监控和反应来自其余子模块的事件;
- Model Class存储有一个指向当前活动的Presenter的指针,用于通知UI即将到来的事件。(具体的Presenter实例,通过以ModelListener和Presenter类为基类实现提供Model Class的通知功能)
Model 类中有modellistener指针指向当前活跃的View对应的Presenter。各个XXView都有一个XXPresenter类,该XXPresenter以Presenter类和ModelListener类作为基类,成员变量中包括XXView,从而实现View和Model的连接。XXPresenter Override ModelListener中需要的虚函数,这样Model tick中调用该虚函数时,就会调用重写的函数,实现当前画面的功能。
另外Presenter类的成员中有指向Model类的指针,在Presenter中可以调用Model类中定义的public接口函数,由于model类指针为私有成员,将调用过程其封装为公有函数,这样在View类中可以通过presenter指针调用Model中的接口函数,实现前端和后端的数据交互。
智能零售界面前后端数据交互
商品的价格信息存储在Model类中,GUI界面显示商品的价格时,需要从Model类中获取对应的商品价格。同时由于View类和Presenter类在界面切换时数据会丢失,商品浏览界面选择的商品数量信息需要存储到Model类中,用于在清单结算页面中显示。Model类起到存储商品信息和保存用户选择信息的作用。根据这两个需要,在Model类型的成员中添加用于存储价格和数量信息的数组。
float prices[21];
int num[21];
prices和num数组在Model类的构造函数中进行初始化。
Model::Model() : modelListener(0)
{
prices[0]=73.28;
prices[1]=73.85;
prices[2]=73.85;
prices[3]=97.63;
prices[4]=92.31;
prices[5]=92.31;
prices[6]=92.31;
prices[7]=134.91;
prices[8]=134.91;
prices[9]=163.31;
prices[10]=163.31;
prices[11]=93.41;
prices[12]=134.91;
prices[13]=205.92;
prices[14]=221.51;
prices[15]=214.22;
prices[16]=208.55;
prices[17]=191.72;
prices[18]=205.92;
prices[19]=205.92;
prices[20]=248.52;
for(int i=0;i<21;i++)
num[i]=0;
...
}
添加以下函数用于查询商品价格、设置和获取用户输入的商品数量。
//Model.hpp中声明
class Model
{
public:
Model();
float getitemprice(itemid id);
int getitemcount(itemid id);
void setitemcount(itemid id,int cnt);
}
//Model.cpp中实现
void Model::setitemcount(itemid id,int cnt)
{
num[id]=cnt;
}
float Model::getitemprice(itemid id)
{
return prices[id];
}
int Model::getitemcount(itemid id)
{
return num[id];
}
以上定义的函数在View中调用时,需要在Presenter类中定义接口函数进行间接调用,比如PayScreenPresenter中添加公有函数,其中通过私有成员model来调用对应的model公有函数。
//PayScreenPresenter.hpp中声明函数
class PayScreenPresenter : public touchgfx::Presenter, public ModelListener
{
public:
float getprice(itemid id);
int getitemcount(itemid id);
}
//PayScreenPresenter中实现函数
float PayScreenPresenter::getprice(itemid id)
{
return model->getitemprice(id);
}
int PayScreenPresenter::getitemcount(itemid id)
{
return model->getitemcount(id);
}
跳转到清单结算界面时,根据用户输入的商品数量在显示区域显示清单信息。
具体的实现方式为在清单结算页面初始化时,从num数组中提取不为零的数据,组成一个链表表示清单数据,通过对链表数据的管理,为显示清单提供数据。此部分代码设计一个链表的管理相关的函数声明以及实现如下:
class Model
{
public:
link newNode(checkitem item);
void freeNode(link x);
void insertNext(link x,link t);
link deleteNext(link x);
link Next(link x);
protected:
node checknode[22];
link checklist;
node checklisthead;
}
link Model::newNode(checkitem item)
{
link x=deleteNext((link)checknode);
x->itemdata=item;
x->next=x;
return x;
}
void Model::freeNode(link x)
{
insertNext(checknode,x);
}
void Model::insertNext(link x,link t)
{
t->next=x->next;
x->next=t;
}
link Model::deleteNext(link x)
{
link t = x->next;
x->next=t->next;
return t;
}
link Model::Next(link x)
{
return x->next;
}
在使用链表的基础上,当切换到清单结算页面时,通过对num数组中非零项的处理,将其添加到链表中,用于后续显示清单提供数据。每个商品项的价格则可以通过根据查询的价格和对应的商品数量计算得出。清单的商品总价通过将单个商品项的价格累加即可得到。查询链表中数据以及查询清单价格信息函数如下。
class Model
{
checkitem getIndexItem(int16_t index);
bool checkIndexItem(int16_t index);
void initializechecklist();
void clearchecklist();
}
void Model::initializechecklist()
{
//touchgfx_printf("initial list\n");
for(int i=0;i<21;i++)
{
if(num[i]!=0)
{
checkitem item_temp;
link x;
item_temp.id=(itemid)i;
item_temp.num=num[i];
x=newNode(item_temp);
insertNext(checklist,x);
//touchgfx_printf("add new node %d,id :%d\n",checklist->next,item_temp.id);
checkitemcnt++;
total_cost+=prices[i]*num[i];
touchgfx_printf("%.2f\n",total_cost);
}
}
//touchgfx_printf("%d\n",checkitemcnt);
}
void Model::clearchecklist()
{
//touchgfx_printf("clear list\n");
for(link t=checklist;t->next!=NULL;t=t)
{
checkitemcnt--;
link del=deleteNext(t);
freeNode(del);
}
total_cost=0.0;
}
checkitem Model::getIndexItem(int16_t index)
{
link t=checklist;
checkitem item;
for(int16_t i=index;i!=0;i--)
{
t=t->next;
}
item=t->itemdata;
return item;
}
bool Model::checkIndexItem(int16_t index)
{
bool exist=true;
link t=checklist;
//touchgfx_printf("index %d\n",index);
for(int16_t i=index;i!=0;i--)
{
//touchgfx_printf("loop %d %d\n",i,checklist->next);
if(t->next!=NULL)
{
//touchgfx_printf("branch1\n");
t=t->next;
}
else
{
//touchgfx_printf("branch2\n");
exist=false;
break;
}
}
return exist;
}
float Model::gettotalcost()
{
return total_cost;
}
界面数据更新
设计并实现后端的功能和接口函数,在前端代码中调用相应的接口函数获取数据,并更新对应控件的信息。在PayScreen中显示清单信息,在界面对应的类初始化时,根据存储的信息初始化清单链表信息,并提取链表信息更新到对应的显示控件上。当切换页面时,清楚链表中存储的数据。
void PayScreenView::setupScreen()
{
float total_cost;
PayScreenViewBase::setupScreen();
presenter->initiallist();
for(int i=0;i<4;i++)
{
checkitem iteminfo;
float price_temp;
if(presenter->checkIndexItem(i+1))
{
touchgfx_printf("item %d\n",i+1);
iteminfo=presenter->getindexitem(i+1);
touchgfx_printf("shop %d\n",iteminfo.id);
scrollListCheckListItems[i].setinvisible(true);
scrollListCheckListItems[i].updateboardimage(ImageID_group[iteminfo.id]);
scrollListCheckListItems[i].setBoardcount(iteminfo.num);
scrollListCheckListItems[i].setBoardname(TextID_group[iteminfo.id]);
price_temp=presenter->getprice(iteminfo.id)*iteminfo.num;
scrollListCheckListItems[i].setBoardprice(price_temp);
}
}
total_cost=presenter->gettotalcost();
Unicode::snprintfFloat(TotalPriceBuffer,40,"%.2f",total_cost);
TotalPrice.invalidate();
}
void PayScreenView::tearDownScreen()
{
PayScreenViewBase::tearDownScreen();
presenter->clearlist();
}
另外,ScrollList的更新函数中需要冲链表中获取对应项的数据,并更新到相应的控件上,相关的函数如下。
void PayScreenView::scrollListCheckUpdateItem(BillItem& item, int16_t itemIndex)
{
checkitem iteminfo;
float price_temp;
if(presenter->checkIndexItem(itemIndex+1))
{
iteminfo=presenter->getindexitem(itemIndex+1);
item.setinvisible(true);
item.updateboardimage(ImageID_group[iteminfo.id]);
item.setBoardcount(iteminfo.num);
item.setBoardname(TextID_group[iteminfo.id]);
price_temp=presenter->getprice(iteminfo.id)*iteminfo.num;
item.setBoardprice(price_temp);
}
}
总结
通过在Model类中添加数据和接口函数,实现GUI交互数据的存储和多个界面之间的数据共享,实现多界面的复杂应用程序。利用TouchGFX框架开发应用程序,在框架中添加用户自定义的代码即可,配合TouchGFX提供的辅助工具,可以提升开发效率。