Skip to main content
M-Schiller
Associate III
April 20, 2022
Solved

How to drive reused containers?

  • April 20, 2022
  • 11 replies
  • 2915 views

I'm currently working on my second ever TouchGFX project and keep stumbling upon the same issues as in my first one.

I have a battery indicator that I would like to show on (almost) every screen. For this reason I created a container that I can easily drop on every screen and implemented a few relevant methods on this container. This battery indicator is obviously driven by hardware (a FreeRTOS message is sent from the HW task to the TGFX task, if that makes any difference). Now the Model receives these messages and notifies the ModelListener via a virtual method like `virtual void notifyBatteryVoltage(uint8_t);` or similar.

Here is where it gets interesting to me:

  • The base ModelListener cannot implement anything UI-related as it doesn't know anything about the View it will be attached to.
  • It probably should not call anything on the base Presenter as this will couple the two aspects (ModelListener, Presenter) too tightly.
  • Even if I implemented the previous point, the base Presenter doesn't have a reference to its related view.
  • As far as I can tell, the base View is not meant to be extended as it is buried within the framework folder and replaced on e.g. version changes in TGFX.

So far, I have always overridden the `notifyBatteryVoltage` method on (almost) all screens' ModelListeners (i.e. Presenters), and implemented another layer of `notifyBatteryVoltage` on the Views that contain the battery indicator. These Views, in turn, will notify the reused container, i.e. call `setBatteryVoltage` on it.

In this way, I wrote the same code over and over again for about ten different Views and ten related Presenters.

I feel like I am missing something fundamental here. Is there any better way to achieve something like this?

This topic has been closed for replies.
Best answer by DSwea.1

Apparently, there is no way to do this at present within the TouchGFX MVP model. We're stuck with repeating the same code in every single Presenter and View. For a reference, see this thread:

https://community.st.com/s/question/0D53W00001OPx2sSAD/common-popup-for-all-screens

ST employee Yoann Klein's comment indicates that ST is working on a "base class", but there's no indication of when that may be available or what form it will take. Hopefully, if enough users bring this up, it will accelerate the process.

In the meantime, you might try the workaround that I used. I have a "footer" container with a clock that appears on every screen in my project (20 screens and growing). It's just quite impractical to maintain code for updating the footer using the MVP model. So, what I've done is an "end around", allowing the Model to communicate directly with the footer container, in my case with a static utility class as the intermediary (though this is not entirely necessary).

The idea is that whenever a screen is loaded, the footer constructor "registers" itself (via the static class), making it visible to the Model through the static class:

// ScreenFooter() Constructor
//
ScreenFooter::ScreenFooter() {
 
 GuiUtil::setFooterPtr(this);
}
 
ScreenFooter:: ~ScreenFooter() {
 
 GuiUtil::setFooterPtr(0);
}
 

GuiUtil, in turn, stores the footer reference, and uses it to set date and time in the footer. It also provides functions setFooterDate() and setFooterTime() that can be called from Model.cpp to manage the time settings in the footer:

// setFooterPtr()
//
void GuiUtil::setFooterPtr(ScreenFooter* footer) {
 
 screenFooter = footer;
 if (screenFooter) {
 uint8_t hour, minute, second;
 getRtcTime(hour, minute, second);
 screenFooter->setTime(hour, minute, second);
 
 uint8_t month, day, weekday;
 uint16_t year;
 getRtcDate(month, day, year, weekday);
 screenFooter->setDate(month, day, year);
 }
}
 
// setFooterDate()
//
void GuiUtil::setFooterDate(uint8_t month, uint8_t day, uint16_t year) {
 
 if (screenFooter) {
 screenFooter->setDate(month, day, year);
 }
}
 
// setFooterTime()
//
void GuiUtil::setFooterTime(uint8_t hour, uint8_t minute, uint8_t second) {
 if (screenFooter) {
 screenFooter->setTime(hour, minute, second);
 }
}
 

Note that the call to set the footer pointer to 0 in the destructor ScreenFooter:: ~ScreenFooter() ensures that calls to set the date and time when a footer is not present will cause no issues.

This solution is a general means of working around the issue you describe and can be used in other situations where a container is present in every screen, but one cannot afford the overhead of maintaining copies of the same code in every presenter/view.

If you think about it, when one is following the traditional C++ method of declaring functions in .hpp and implementing in .cpp, then you'll need to edit/insert sections in xxPresenter.hpp, xxPresenter.cpp, xxView.hpp and xxView.cpp. If you have 20 screens, that's 80 places you have to edit (and you can't simply copy and paste since each insertion has to be edited to prefix the class name).

Let's hope the ST team comes up with a good solution to this issue. I suspect there are many users who need it since it's quite common to have a common element on all screens in a project.

11 replies

Florian Moser
Senior
April 21, 2022

I need the exact thing right about now!

Hopefully there's a solution to this.

@Martin KJELDSEN​ 

DSwea.1
DSwea.1Best answer
Associate III
April 24, 2022

Apparently, there is no way to do this at present within the TouchGFX MVP model. We're stuck with repeating the same code in every single Presenter and View. For a reference, see this thread:

https://community.st.com/s/question/0D53W00001OPx2sSAD/common-popup-for-all-screens

ST employee Yoann Klein's comment indicates that ST is working on a "base class", but there's no indication of when that may be available or what form it will take. Hopefully, if enough users bring this up, it will accelerate the process.

In the meantime, you might try the workaround that I used. I have a "footer" container with a clock that appears on every screen in my project (20 screens and growing). It's just quite impractical to maintain code for updating the footer using the MVP model. So, what I've done is an "end around", allowing the Model to communicate directly with the footer container, in my case with a static utility class as the intermediary (though this is not entirely necessary).

The idea is that whenever a screen is loaded, the footer constructor "registers" itself (via the static class), making it visible to the Model through the static class:

// ScreenFooter() Constructor
//
ScreenFooter::ScreenFooter() {
 
 GuiUtil::setFooterPtr(this);
}
 
ScreenFooter:: ~ScreenFooter() {
 
 GuiUtil::setFooterPtr(0);
}
 

GuiUtil, in turn, stores the footer reference, and uses it to set date and time in the footer. It also provides functions setFooterDate() and setFooterTime() that can be called from Model.cpp to manage the time settings in the footer:

// setFooterPtr()
//
void GuiUtil::setFooterPtr(ScreenFooter* footer) {
 
 screenFooter = footer;
 if (screenFooter) {
 uint8_t hour, minute, second;
 getRtcTime(hour, minute, second);
 screenFooter->setTime(hour, minute, second);
 
 uint8_t month, day, weekday;
 uint16_t year;
 getRtcDate(month, day, year, weekday);
 screenFooter->setDate(month, day, year);
 }
}
 
// setFooterDate()
//
void GuiUtil::setFooterDate(uint8_t month, uint8_t day, uint16_t year) {
 
 if (screenFooter) {
 screenFooter->setDate(month, day, year);
 }
}
 
// setFooterTime()
//
void GuiUtil::setFooterTime(uint8_t hour, uint8_t minute, uint8_t second) {
 if (screenFooter) {
 screenFooter->setTime(hour, minute, second);
 }
}
 

Note that the call to set the footer pointer to 0 in the destructor ScreenFooter:: ~ScreenFooter() ensures that calls to set the date and time when a footer is not present will cause no issues.

This solution is a general means of working around the issue you describe and can be used in other situations where a container is present in every screen, but one cannot afford the overhead of maintaining copies of the same code in every presenter/view.

If you think about it, when one is following the traditional C++ method of declaring functions in .hpp and implementing in .cpp, then you'll need to edit/insert sections in xxPresenter.hpp, xxPresenter.cpp, xxView.hpp and xxView.cpp. If you have 20 screens, that's 80 places you have to edit (and you can't simply copy and paste since each insertion has to be edited to prefix the class name).

Let's hope the ST team comes up with a good solution to this issue. I suspect there are many users who need it since it's quite common to have a common element on all screens in a project.

M-Schiller
Associate III
April 26, 2022

Thank you @DSwea.1​ for your response. This seems like a manageable workaround and, while dancing around the framework and its ModelListener infrastructure, it is a good solution for the issues I am facing.

Florian Moser
Senior
April 26, 2022

Same here!

wired
Senior II
April 28, 2022

I also have a 'status bar' container at the top of my screen, which contains a number of icons that need to change based on device status (charging, battery level, etc.). I change the icons' visibility, switch icons based on battery level, and also do fade in/outs. I maintain the status of the icons (alpha values, visibility, etc) in a static structure so that when the container is initialized each time a screen changes, the initialize() method will update everything to the current status.

I also have a handleTickEvent() for the container to perform the realtime screen updates while on any given screen, e.g. performing alpha operations (while also updating the static structure, and setting visibilities, and displaying the correct icons). I do have to have an external declaration of a structure where all of the status data is acquired and updated in a different task, but it's a small price to pay to get my status bar functionality encapsulated in a single file, StatusBar.cpp.

I also use the SIMULATOR definition to declare variables locally rather than externally, so that my status bar can somewhat function in the simulator.

I know I'm breaking some MVP rules, but you gotta do what you gotta do...