Text löschen/überschreiben

Hallo Zusammen,

ich bin noch Einsteiner im Programmieren, hab aber trotzdem versucht mithilfe von Wokwi eine umfassende, aber leicht verständliche und ausbaubare Menüstruktur aufzubauen.
Die Menüführung läuft soweit zufriedenstellend, wenn es auch eventuell nicht den Gepflogenheiten einer sauberen Programmierung entspricht.
Also bitte nicht steinigen, ich bin schon froh soweit gekommen zu sein!

Womit ich allerdings kämpfe ist das Löschen des Displays beim Menüwechsel.
Angefangen hab ich mit tft.FillScreen, das läuft allerdings langsam, da ja das gesamte Display überschrieben wird.
Bei meiner Recherche bin ich dann darauf gestoßen, dass man besser nur die Schrift auf die Hintergrundfarbe ändert.
Aber leider will das einfach nicht klappen.

Bitte daher um eure Hilfe wo der Fehler liegt.

#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"

#define TFT_CS   10
#define TFT_DC    9
#define TFT_RST   7

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

const int buttonUpPin = 2;
const int buttonDownPin = 3;
const int buttonConfirmPin = 4;
const int buttonMainmenuPin =5;

bool isButtonPressed(int pin) {
  return digitalRead(pin) == LOW;
}

int selectedMenu = 1;
int selectedItem = 1;
int numItemsInMenu = 2;

unsigned long timeoutMillis = 0;
bool timeout = false;
bool actionRunning = false;

void setup() {
  pinMode(buttonUpPin, INPUT_PULLUP);
  pinMode(buttonDownPin, INPUT_PULLUP);
  pinMode(buttonConfirmPin, INPUT_PULLUP);
  pinMode(buttonMainmenuPin, INPUT_PULLUP);
  
  tft.begin();
  tft.fillScreen(ILI9341_BLACK);
  //tft.setRotation(3);
  displayMenu();

  timeout = false;
  timeoutMillis = millis();
  actionRunning = false;
}

void loop() {
  handleMenuNavigation();
  checkTimeout();
}

void handleMenuNavigation() {
  // Menünavigation mit den Tasten
  if (isButtonPressed(buttonUpPin) && !actionRunning) {
    selectedItem = (selectedItem == 1) ? numItemsInMenu : selectedItem - 1;
    displayMenu();
    timeoutMillis = millis(); // Zurücksetzen des millis-Zählers
    delay(200);
  }

  if (isButtonPressed(buttonDownPin) && !actionRunning) {
    selectedItem = (selectedItem == numItemsInMenu) ? 1 : selectedItem + 1;
    displayMenu();
    timeoutMillis = millis(); // Zurücksetzen des millis-Zählers
    delay(200);
  }

  // Zurück zum Hauptmenü
  if (isButtonPressed(buttonMainmenuPin)) {
    backToMainMenu();
    timeoutMillis = millis(); // Zurücksetzen des millis-Zählers
    delay(200);
  }

  // Bestätigung des ausgewählten Menüpunkts
  if (isButtonPressed(buttonConfirmPin)) {
    clear_lcd();
    timeoutMillis = millis(); // Zurücksetzen des millis-Zählers
    actionMenu(selectedMenu, selectedItem);
  }
}

void checkTimeout() {
  // Prüfen, ob timeout aktiv und 10 Sekunden vergangen sind
  if (!timeout && millis() - timeoutMillis >= 10000) {
    actionMenu(2, 1); // actionMenu21 ausführen
  }
}
void handleMenuAction(int nextMenu, int itemsInMenu) {
  // Menüwechsel und Anzeige
  selectedMenu = nextMenu;
  selectedItem = 1;
  numItemsInMenu = itemsInMenu;
  displayMenu();
  timeout = false;
}
void actionMenu(int menu, int item) {
  // Aktionen für jedes Menü und Menüpunkt
  switch(menu) {
    case 1:
      switch(item) {
        case 1:
          handleMenuAction(2, 2); // Zum Menü 2 mit 2 Punkten wechseln
          break;
        case 2:
          handleMenuAction(3, 3); // Zum Menü 3 mit 3 Punkten wechseln
          break;
      }
      break;
    case 2:
      switch(item) {
        case 1:
          clear_lcd();
          tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
          tft.setTextSize(2);
          tft.setCursor(10, 50);
          tft.println("Messen...");
          timeout = true;
          actionRunning = true;
          break;
        case 2:
          actionRunning = false;
          timeout = false;
          backToMainMenu();
          break;
      }
      break;
    case 3:
      switch(item) {
        case 1:
        case 2:
        case 3:
          actionRunning = false;
          timeout = false;
          backToMainMenu();
          break;
      }
      break;
  }
}

void backToMainMenu() {
  // Zurück zum Hauptmenü
  clear_lcd();
  selectedMenu = 1;
  selectedItem = 1;
  numItemsInMenu = 2;
  displayMenu();
  timeout = false;
  actionRunning = false;
}

void displayMenu() {
  // Anzeige des aktuellen Menüs
  if (selectedMenu == 1) {
    printMenuItem("Messen", 10, 50, 1);
    printMenuItem("Kalibrieren", 10, 100, 2);
  } else if (selectedMenu == 2) {
    printMenuItem("pH/ORP Messung",10, 50, 1);
    printMenuItem("zurück", 10, 100, 2);
  } else if (selectedMenu == 3) {
    printMenuItem("pH", 10, 50, 1);
    printMenuItem("ORP", 10, 100, 2);
    printMenuItem("zurück", 10, 150, 3);
  } 
}

void printMenuItem(const char *item, int x, int y, int index) {
  // Anzeige eines Menüpunkts
  if (selectedItem == index) {
    tft.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);
  } else {
    tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
  }
  
  tft.setTextSize(2);
  tft.setCursor(x, y);
  tft.println(item);
}

void clear_lcd() {
    //tft.setTextColor(ILI9341_BLACK, ILI9341_BLACK);
    tft.fillScreen(ILI9341_BLACK); 

}

Hallo,
Wiso nicht ? Wenn Du z.B mit blauer Schrift auf blauem Hintergrund schreibst dann ist der Text doch unsichtbar.

Du musst halt erst den alten Text mit der Hintergrundfarbe noch einmal neu schreiben , dann die Textfarbe umstellen, und den neuen Text schreiben. Eigentlich braucht man das doch nur bei dynamischen Werten. Dabei muss man sich allerdings die alten Werte merken. Das macht aber oft ja sowieso Sinn damit man nur dann neu schreibt wenn sich der Wert geändert hat.

Bei einem Seitenwechsel in einem Menue ist fillScreen(hintergrundfarbe) doch einfacher. Was ich auch schon mal genutzt habe ist fillRect(x1,y1,x2,y2,farbe) um eine Teilbereich mit einem Rechteck in der Hintergrundfarbe zu füllen bevor der neue Wert geschrieben wird. Allerdings ist da ein bisschen probieren angesagt um die richtigen Positionen zu finden.

Und was genau klappt nicht? Bleibt der alte Text sichtbar? Bleiben einzelne Pixel übrig?

In printMenuItem

Nimm mal tft.print, die NeueZeile-Zeichen haben auf dem Display nichts zu suchen.

Wenn du mit Hintergrundfarbe schreibst (statt transpararent), sollte es reichen, wenn du immer ausreichend Leerzeichen anfügst.
Außerdem ist für deine Frage völlig egal, ob es sich da bei der Anzeige um ein "Menüsystem" handelt. (Oder?)
Schreib doch einfach einen kleinen Sketch, der deine Probleme beim Überschreiben von Text zeigt. Da kannst du selber schneller Änderungen und Tests machen, und Leute die sich das nur theoretisch ansehen, erkennen auch eher dein Problem.

ja korrekt, sobald der neue Text kürzer wird bleibt der rest stehen
Probiert es selbst: Martin0304241 - Wokwi ESP32, STM32, Arduino Simulator

das probier ich aus

Ich konnte nicht fertig debuggen, aber die Idee ist da:

#include "SPI.h"
#include "Adafruit_GFX.h"
#include "Adafruit_ILI9341.h"

#define TFT_CS   10
#define TFT_DC    9
#define TFT_RST   7

Adafruit_ILI9341 tft = Adafruit_ILI9341(TFT_CS, TFT_DC, TFT_RST);

const int buttonUpPin = 2;
const int buttonDownPin = 3;
const int buttonConfirmPin = 4;
const int buttonMainmenuPin =5;

bool isButtonPressed(int pin) {
  return digitalRead(pin) == LOW;
}

int selectedMenu = 1;
int lastMenu = 0;
int selectedItem = 1;
int numItemsInMenu = 2;

unsigned long timeoutMillis = 0;
bool timeout = false;
bool actionRunning = false;
bool erase = false;

void setup() {
  pinMode(buttonUpPin, INPUT_PULLUP);
  pinMode(buttonDownPin, INPUT_PULLUP);
  pinMode(buttonConfirmPin, INPUT_PULLUP);
  pinMode(buttonMainmenuPin, INPUT_PULLUP);
  
  tft.begin();
  tft.fillScreen(ILI9341_BLACK);
  //tft.setRotation(3);
  displayMenu();

  timeout = false;
  timeoutMillis = millis();
  actionRunning = false;
}

void loop() {
  handleMenuNavigation();
  checkTimeout();
}

void handleMenuNavigation() {
  // Menünavigation mit den Tasten
  if (isButtonPressed(buttonUpPin) && !actionRunning) {
    selectedItem = (selectedItem == 1) ? numItemsInMenu : selectedItem - 1;
    displayMenu();
    timeoutMillis = millis(); // Zurücksetzen des millis-Zählers
    delay(200);
  }

  if (isButtonPressed(buttonDownPin) && !actionRunning) {
    selectedItem = (selectedItem == numItemsInMenu) ? 1 : selectedItem + 1;
    displayMenu();
    timeoutMillis = millis(); // Zurücksetzen des millis-Zählers
    delay(200);
  }

  // Zurück zum Hauptmenü
  if (isButtonPressed(buttonMainmenuPin)) {
    backToMainMenu();
    timeoutMillis = millis(); // Zurücksetzen des millis-Zählers
    delay(200);
  }

  // Bestätigung des ausgewählten Menüpunkts
  if (isButtonPressed(buttonConfirmPin)) {
    clear_lcd();
    timeoutMillis = millis(); // Zurücksetzen des millis-Zählers
    actionMenu(selectedMenu, selectedItem);
  }
}

void checkTimeout() {
  // Prüfen, ob timeout aktiv und 10 Sekunden vergangen sind
  if (!timeout && millis() - timeoutMillis >= 3000) {
    actionMenu(2, 1); // actionMenu21 ausführen
  }
}
void handleMenuAction(int nextMenu, int itemsInMenu) {
  // Menüwechsel und Anzeige
  selectedMenu = nextMenu;
  selectedItem = 1;
  numItemsInMenu = itemsInMenu;
  displayMenu();
  timeout = false;
}
void actionMenu(int menu, int item) {
  // Aktionen für jedes Menü und Menüpunkt
  switch(menu) {
    case 1:
      switch(item) {
        case 1:
          handleMenuAction(2, 2); // Zum Menü 2 mit 2 Punkten wechseln
          break;
        case 2:
          handleMenuAction(3, 3); // Zum Menü 3 mit 3 Punkten wechseln
          break;
      }
      break;
    case 2:
      switch(item) {
        case 1:
          clear_lcd();
          tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
          tft.setTextSize(2);
          tft.setCursor(10, 50);
          tft.print("Messen...");
          timeout = true;
          actionRunning = true;
          break;
        case 2:
          actionRunning = false;
          timeout = false;
          backToMainMenu();
          break;
      }
      break;
    case 3:
      switch(item) {
        case 1:
        case 2:
        case 3:
          actionRunning = false;
          timeout = false;
          backToMainMenu();
          break;
      }
      break;
  }
}

void backToMainMenu() {
  // Zurück zum Hauptmenü
  clear_lcd();
  selectedMenu = 1;
  selectedItem = 1;
  numItemsInMenu = 2;
  displayMenu();
  timeout = false;
  actionRunning = false;
}

void displayMenu() {
    erase = true;
    int saveMenu = selectedMenu;
    selectedMenu = lastMenu;
    displayMenu2();
    erase = false;
    selectedMenu = saveMenu;
    lastMenu = selectedMenu; 
  displayMenu2();
}

void displayMenu2() {
  switch (selectedMenu) {
    case 1:
    printMenuItem("Messen", 10, 50, 1);
    printMenuItem("Kalibrieren", 10, 100, 2);
    break;
    case 2:
    printMenuItem("pH/ORP Messung",10, 50, 1);
    printMenuItem("zurueck", 10, 100, 2);
    break;
    case 3:
    printMenuItem("pH", 10, 50, 1);
    printMenuItem("ORP", 10, 100, 2);
    printMenuItem("zurueck", 10, 150, 3);
    break;
  } 
}

void printMenuItem(const char *item, int x, int y, int index) {
  // Anzeige eines Menüpunkts
  if (erase) {
    tft.setTextColor(ILI9341_BLACK, ILI9341_BLACK);
  } else if (selectedItem == index) {
    tft.setTextColor(ILI9341_YELLOW, ILI9341_BLACK);
  } else {
    tft.setTextColor(ILI9341_WHITE, ILI9341_BLACK);
  }
  
  tft.setTextSize(2);
  tft.setCursor(x, y);
  tft.print(item);
}

void clear_lcd() {
    tft.setTextColor(ILI9341_BLACK, ILI9341_BLACK);
}

Ein Problem ist auf jeden Fall noch Zeile 112ff

Wie @Rentner schrieb muss dazu der alte Text nochmals in dieser Farbe geschrieben werden.
Ich verwende mehrere Methoden. Sie basieren auf schreiben eines "leeren" Textes.

tft.setCursor(10, 50);
tft.println("Messen...");

tft.setCursor(10, 50);
tft.println("         ");   // "löscht" Text

Setzt man alle Textfelder auf eine einheitliche Länge können diese alle mit dem gleichen Text gelöscht werden.
Man kann auch alle Textausgaben auf gleiche Länge setzen, dann wird der alte Text ebenfalls "gelöscht".

cu

@hawe07546
Danke für den Codeabschnitt.
Habs jetzt so umgesetzt und ich finde das läuft recht gut:

void clear_lcd() {
    tft.setCursor(10, 50);
    tft.println("                ");
    tft.setCursor(10, 100);
    tft.println("                ");
     tft.setCursor(10, 150);
    tft.println("                "); // "löscht" Text
}

Somit schreib ich nur die 3 Zeilen und jeweils 16 Leerzeichen.
Man könnte jetzt noch mit NumItemsInMenu definiern wieviele Zeilen er schreiben soll, denn die Anzahl musst ja beim löschen eigentlich gerade im Speicher stehen.
Also zb. so:

void clear_lcd() {
    switch(numItemsInMenu) {
      case 2:
      tft.setCursor(10, 50);
      tft.println("                ");
      tft.setCursor(10, 100);
      tft.println("                ");
      case 3:
      tft.setCursor(10, 50);
      tft.println("                ");
      tft.setCursor(10, 100);
      tft.println("                ");
      tft.setCursor(10, 150);
      tft.println("                "); // "löscht" Text
  }

Gibts da noch eine kürzeren weg?

Praktisch reicht auch das :

void clear_lcd() {
    tft.setCursor(10, 50);      //Zeile 1 und 2 in beiden Fällen vorhanden
    tft.println("                ");
    tft.setCursor(10, 100);
    tft.println("                ");
    
    if (numItemsInMenu == 3) {    //Zeile 3 gibt es nur eim Falle von 3 Items
        tft.setCursor(10, 150);
        tft.println("                "); // "löscht" Text
    }
}

a) braucht es wirklich println oder reicht print?
b) warum verwendest du nicht das F-Makro: PROGMEM - Arduino Reference ?
c) du könntest dir auch eine Funktion schreiben der du die Koordinaten übergibst und die dann das überschreiben übernimmt

ungetestet:


void delete_lcd(uint16_t x, uint16_t y, uint8_t n = 9) {
  tft.setCursor(x,y);
  for (uint8_t n = 0; n < i; n++) tft.print(' ');
}

im einfachsten fall rufst du es dann so auf damit 9 Zeichen gelöscht werden

delete_lcd(10, 50);

sollte mal mehr text gelöscht werden müssen delete(10, 50, 42);

dein obiger Code wird dann zu

void clear_lcd() {
    delete_lcd(10, 50);      //Zeile 1 und 2 in beiden Fällen vorhanden
    delete_lcd(10, 100);
    if (numItemsInMenu == 3)     //Zeile 3 gibt es nur eim Falle von 3 Items
        delete_lcd(10, 150);
}

@smarti2 : hast du dir mal meinen Vorschlag aus #5 angesehen?

zu a) print reicht im Endeffekt
Zu b) vielen Dank für den Hinweis, war für mich komplett neu!
Um Speicher zu sparen könnt ich auch noch ein paar Variablen anpassen. (Zb. int zu byte)
Aber gibts durch den F-Makro auch einen positiven Performance Effekt?
Zu c) Vielen Dank für den Code, den seh ich mir noch genau an!

@wwerner: leider noch nicht im Detail. War auf den ersten Blick sehr komplex für meinen Anfängerblick! Sorry

Was verstehst du unter Performance?
Wenn der RAM knapp ist, kann es sein, dass es ohne das F-Makro nicht funktioniert und merkwürdige Fehler passieren. Bei ausreichend RAM wird man keinen Unterschied im Verhalten feststellen können.

Ah ist klar!
Habs falsch verstanden!

@noiasca: funktioniert soweit, "delete" konnte ich zwar nicht nehmen weil er da beim compilieren einen Fehler spuckt (delete ist in C+ reserviert). Hab auf lcd_delete geändert.

@wwerner: dein Code ist eigentlich ganz schön mit dem erase. Im Grunde macht es das selbe wie ich ja auch schon ganz am Anfang versucht habe, dass man den selben Text nochmals in Hintergrundfarbe schreibt. Mir scheint es als fehlt aber die Info welchen Text er nochmal schreiben soll, denn auch in deinem Code bleiben 1:1 noch alte Zeichen stehen wie bei mir.

Wie gesagt, neben dem Menu schreibst du in Zeile 112ff auch noch daten. die werden noch nicht berücksichtigt.

Er schreibt nicht den Text in Hintergrundfarbe nochmal, sondern ein fettes :slight_smile: Leerzeichen, dessen Hintergrundfarbe das macht was du willst.

Einen Tick schneller wird es eventuell sogar, wenn du nicht vor dem Neu-schreiben drei Leerzeilen ausgibst, sondern jede Zeile immer so weit (mit angehängten Leerzeichen) schreibst, dass der vorige Inhalt überschrieben wird.

Bei Schriften mit fester Zeichenbreite und Hintergrundfarbe eigentlich einfach.

Also meinst du gleich direkt alle print() auf eine gleiche Zeichenanzahl mit Leerzeichen auffüllen.
Soweit ich das verstehe geht das aber nur wenn mindestens die selbe Anzahl an Zeilen geschrieben wird, sonst bleibt die nächste Zeile stehen.

Ja.
Dafür hab ich aber dein Menüsystem nicht genau genug angesehen, wann eine oder mehrere Zeilen stehenbleiben und wann welche überschrieben werden.
Schneller und flackerärmer als mit tft.fillScreen(ILI9341_BLACK); und etwas besser als jede Zeile erstmal komplett mit Leerzeichen und dann mit dem richtigen Text zu schreiben, wird es allemal.

This topic was automatically closed 180 days after the last reply. New replies are no longer allowed.