Wie aus einer anderen Klasse QWidgets neu zeichnen lassen?



  • Hallo,
    in meiner MainWindow.cpp wird mein Fenster mit QWidgets bzw. Ableitungen davon (PaintOHP, PaintND und später noch weiteren) wie folgt erzeugt:

    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent),
        ui(new Ui::MainWindowClass),
        basis_canvas(new PaintBasis)
    {
        ui->setupUi(this);
    
        basis_canvas->setGeometry(0, 0, 1900, 2140);
    
        //OHP
        PaintOHP* paintohp = new PaintOHP(basis_canvas);
        paintohp->setGeometry(QRect(520,0,1090,540));
    
        //OHP children
        SwitchOHP* switchBattery = new SwitchOHP(paintohp, 30, 220);
        connect(switchBattery, SIGNAL(clicked()), this, SLOT(ClickBatterySwitch()));
    
        //EFIS
        PaintEFIS* paintefis = new PaintEFIS(basis_canvas);
        paintefis->setGeometry(QRect(555,950,130,80));
    
        //ND
        PaintND* paintnd = new PaintND(basis_canvas);
        paintnd->setGeometry(QRect(420, 1070, 400, 400));
    
        //ScrollArea
        QScrollArea *scrollArea = new QScrollArea;
        scrollArea->setWidget(basis_canvas);
        setCentralWidget(scrollArea);
    }
    

    Diese QWidgets habe ich bewusst aufgeteilt, da sie einen eigenständigen Bereich eines Flugzeugcockpits viele Zeichnobjekte darstellen, z.B.

    void PaintND::paintEvent(QPaintEvent* event)
    {
    	QPainter painter;
    	painter.begin(this);
    //painter Objekte folgen
    //ggf. noch if-Abfragen für bestimmte Grafikobjekte, die nicht in jeder Konfiguration sichtbar sind.
    }
    

    Dies funktioniert soweit. Ich suche jetzt noch eine Lösung, wie ich mit einer Timer-Methode (alle 0.5 sec.) aus einer anderen Klasse bewirke, dass z.B. mein PaintND* paintnd neu gezeichnet wird (da verschiedene Faktoren dazu führen, den Inhalt neu zu gestalten). Der Timer ist nicht das Problem, siehe mein Thema in diesem Forum. Ich habe versucht aus einer solchen Timer-Methode über die folgende Methode

    void MainWindow::UpdateMonitors()
    {
        paintnd->update(0,0,400,400);
    }
    

    das Ziel zu erreichen, leider ohne Erfolg. Fehlermeldung:
    E0245 Ein nicht statischer Memberverweis muss relativ zu einem bestimmten Objekt sein. MainWindow.cpp Zeile 51 bzw. hier Zeile 3. Wie kann ich dies bewerkstelligen? Ich bin noch Anfänger in C++.



  • Wo und wie hast du denn paintnd deklariert?
    Ich sehe nur, daß du im Konstruktor eine lokale Variable gleichen Namens angelegt hast.

    Wenn du sie in der MainForm-Klasse, d.h. im Header deklariert hast, dann mußt du im Konstruktor so initialisieren:

    paintnd = new PaintND(basis_canvas);
    

    Die Fehlermeldung deutet aber noch auf ein anderes Problem hin.
    Ist die MainForm::UpdateMonitors()-Methode als static deklariert? Dann kann sie nicht direkt auf nicht-statische Klassenmember zugreifen. Entferne also das static bei dieser Methode.

    PS: Warum hast du überhaupt eine eigene Timer-Klasse? Qt bietet doch auch eine an: Timer.



  • @Th69 sagte in Wie aus einer anderen Klasse QWidgets neu zeichnen lassen?:

    Wo und wie hast du denn paintnd deklariert?

    Ehrlich gesagt, direkt im Konstruktor bzw. gar nicht.

    Ich sehe nur, daß du im Konstruktor eine lokale Variable gleichen Namens angelegt hast.

    Wenn du sie in der MainForm-Klasse, d.h. im Header deklariert hast, dann mußt du im Konstruktor so initialisieren:

    paintnd = new PaintND(basis_canvas);
    

    Ich habe nun in MainWindow.h
    public: PaintND* paintnd
    deklariert und dass ich die Variable so mit public GLOBAL wird, war mir vorher irgendwie nicht klar und jetzt mit

    void MainWindow::UpdateMonitors()
    {
        paintnd->update(0,0,400,400);
    }
    

    scheint mir auch richtig.

    Die Fehlermeldung deutet aber noch auf ein anderes Problem hin.
    Ist die MainForm::UpdateMonitors()-Methode als static deklariert? Dann kann sie nicht direkt auf nicht-statische Klassenmember zugreifen. Entferne also das static bei dieser Methode.

    Ja, war static. Wie greife ich nun aber aus einer anderen Klasse auf UpdateMonitors() zu bzw. auf die variable paintnd?

    PS: Warum hast du überhaupt eine eigene Timer-Klasse? Qt bietet doch auch eine an: Timer.

    Die Timer Klasse soll einiges berechnen, nicht nur für die QWidgets.



  • @stefanpc81 sagte in Wie aus einer anderen Klasse QWidgets neu zeichnen lassen?:

    Wie greife ich nun aber aus einer anderen Klasse auf UpdateMonitors() zu bzw. auf die variable paintnd?

    Dazu benötigst du Zugriff auf eine Instanz der MainForm-Klasse, d.h. MainForm*.
    Logisch gesehen gibt es zwar nur eine MainForm-Instanz, aber diese ist eben nicht-statisch (wie alle anderen QtWidget-Klassen), so daß man auch mehrere erzeugen könnte.

    Es ist jedoch kein gutes Design, wenn eine logische Klasse (wie deine Timer-Klasse) direkt Zugriff auf UI-Klassen hat.
    Stattdessen sind dafür Events (Callbacks) da. Dafür gibt es ja eben bei Qt das Signal/Slot-Konzept.
    Wenn deine Klasse jedoch unabhängig von Qt sein soll, dann könntest du auch einfach eine std::function<void ()> verwenden und die MainForm-Klasse registriert sich darauf.
    Dazu entweder per std::bindoder per Lambda-Ausdruck, s. z.B. die Antworten in std::function with non-static member functions.



  • @Th69 sagte in Wie aus einer anderen Klasse QWidgets neu zeichnen lassen?:>

    Wenn deine Klasse jedoch unabhängig von Qt sein soll, dann könntest du auch einfach eine std::function<void ()> verwenden und die MainForm-Klasse registriert sich darauf.
    Dazu entweder per std::bindoder per Lambda-Ausdruck, s. z.B. die Antworten in std::function with non-static member functions.

    Danke soweit. Leider habe ich fälschlicherweise geschrieben, die "Timer Klasse soll einiges berechnen". Richtig ist, dass in der Klasse Berechne

    static void Berechne::Interval()
    

    über den Konstruktor

    Timer::timer_start([this]() { Berechne::Interval(); }, 500);
    

    erstellt wird und tatsächlich unabhängig von Qt sein sollte. Bevor du mir vielleicht erklären willst, ob mit MainForm-Klasse mein MainWindow oder mein PaintND gemeint ist und wie ich das "darauf registrieren" verstehen soll, sollte ich besser erst mal mein Vorhaben etwas genauer beschreiben:
    Das Programm soll zuerst die o.g. QWidgets im Fenster anzeigen. Die Cockpitmonitore (u.a. PaintND) sind zunächst schwarz (3 statische Variablen in der Klasse Current sind bool flase, d.h. Strom aus). Das Berechne::Interval() wird alle 500 millisec. ausgeführt für die Berechnungen, Zustände (Current, Engines, Veränderungen im aktiven Flug etc.). Im PaintOHP gibt es einige Taster und Drehschalter (s. z.B. SwitchOHP* switchBattery), welche wiederum jederzeit Zustandsänderungen erzeugen, die normalerweise mit meiner Berechne::Interval() weiterverarbeitet werden (dort mit if/else Strukturen).
    Sobald ein Zustand "Strom an" erfasst wird, gibt es einen "Countdown" in meiner

    static void Current::MonitorStartupRun()
    

    Nach unterschiedlichen X sekunden sollen dann aus dieser Funktion die Monitore (wie auch PaintND) dazu gebracht werden, etwas anzeigen.
    Da auf den Monitoren die Instrumente usw. angezeigt werden und sich ständig etwas ändert (je nach Berechne::Interval()), ist es aus meiner Sicht sinnvoll, direkt aus dieser Funktion die Zeichenfläche von PaintND paintnd (bzw. für die anderen Monitore auch) neu zeichnen zu lassen.
    Lange Rede, kurzer Sinn: Die Berechne::Interval() Funktion ist neben den Eingaben von Child-Widgets auf dem PaintOHP maßgebend für die Änderungen zuständig und es macht aus meiner derzeitigen Sicht wenig Sinn, NUR über Signale und Slots die Grafik anzusprechen.
    Ich hoffe, du verstehst die Problematik nun besser und kannst mir zielgerichteter helfen. Ich bin für jeden Tipp dankbar und möchte deine bisherigen Lösungsvorschläge aber nicht als schlecht darstellen. Letzteres nur so am Rande...
    Wie und wo sollte ich nun die std::function<void()> verwenden um die MainForm-Klasse (?) sich darauf registrieren lassen?



  • Sorry, hatte MainForm statt MainWindow geschrieben (ich programmiere wohl zu viel mit C# und Windows Forms ;-).

    Deine Beschreibung ist genau das, was man mittels Events (Callbacks) so umsetzen sollte.

    Der Einfachheit kannst du (ersteinmal) einen statische Member dafür in der Berechne-Klasse anlegen:

    public:
        static std::function<void ()> OnUpdate;
    

    Nun kannst du von der MainWindow-Klasse diese darauf registrieren (am besten im Konstruktor):

    Berechne::OnUpdate = std::bind(&MainWindow::UpdateMonitors, this);
    

    Nun kannst du in deiner Timer bzw. Berechne-Klasse jederzeit dieses Event triggern:

    Berechne::OnUpdate();
    


  • @Th69 Danke schön, ich habe das ganze so wie beschrieben gemacht. Leider plage ich mich noch mit Fehlermeldungen herum. Irgendetwas mache ich falsch. Nachfolgend der relevante Code:
    Berechne.h

    class Berechne
    {
    public:
    	static std::function<void ()> OnUpdateND; //habe ich umbenannt
    //...
    }
    

    Berechne.cpp

    void Berechne::OnUpdateND()
    {}
    

    MainWindow.h

    public:
    void UpdateMonitorND(); //auch umbenannt
    

    MainWindow.cpp

    MainWindow::MainWindow(QWidget *parent)
        : QMainWindow(parent),
        ui(new Ui::MainWindowClass),
        basis_canvas(new PaintBasis)
    {
    //...
        Berechne b;
    
        Berechne::OnUpdateND = std::bind(&MainWindow::UpdateMonitorND, this);
    }
    
    void MainWindow::UpdateMonitorND()
    {
        paintnd->update(0,0,400,400);
    }
    

    Fehlermeldungen:
    C2063 "Berechne::OnUpdateND": Keine Funktion Berechne.cpp hier: Zeile 2
    ->Anmerkung meinerseits: Ich habe das extra leer gelassen, da ich es so aufgefasst habe, dass mit dem Aufruf dieser Funktion direkt MainWindow::UpdateMonitorND() ausgeführt wird, wegen der Bindung.

    E0147 Die Deklaration ist nicht mit ""std::function<void ()> Berechne::OnUpdateND" (deklariert in Zeile 4 von Berechne.h kompatibel. Berechne.cpp hier: Zeile 1

    Durch herumprobieren habe ich es nicht lösen können...



  • @stefanpc81 sagte in Wie aus einer anderen Klasse QWidgets neu zeichnen lassen?:

    static std::function<void ()> OnUpdateND;

    Das ist eine statische Variable (keine Funktion), daher mußt du sie so in der Source-Datei noch definieren:

    static std::function<void ()> Berechne::OnUpdateND;
    

    (hatte ich vergessen hinzuschreiben, daß man static-Variablen ja noch extra definieren muß)

    PS: Was soll Berechne b; in dem Konstruktor? Dies ist nur eine lokale Variable, die gleich wieder am Ende des Blocks zerstört wird.



  • @Th69 sagte

    static std::function<void ()> Berechne::OnUpdateND;
    

    Es funktioniert jetzt zwar prinzipiell, allerdings muss ich das System noch etwas verfeinern, bis der Monitor sich vernünftig auf "update" darstellt. Aber das versuche ich morgen zu verbessern. Vielen Dank nochmals für die Hilfen! Allerdings kannst du von mir noch eine Kleinigkeit lernen: In einer CPP-Datei lässt man das static weg, wenn es in der Header-Datei steht 😉

    PS: Was soll Berechne b; in dem Konstruktor? Dies ist nur eine lokale Variable, die gleich wieder am Ende des Blocks zerstört wird.
    Damit wird

    Timer::timer_start([this]() { Berechne::Interval(); }, 500);
    

    ins Leben berufen. Dann spare ich mir das erstellen des "Berechne b" eben, indem ich diese Code-Zeile vielleicht besser direkt in den MainWindow Konstruktor schreibe?



  • Tja, das passiert, wenn man hier einfach im Forum C++ Code hinschreibt (ohne Compiler), aber dafür sind ja dann die Fehlermeldungen da, um das korrigieren zu können - und du auch etwas lernst ;- ).

    Warum benötigst du überhaupt den Aufruf des Konstruktors, denn Interval() ist ja eine statische Funktion.
    Initialisiert du dort statische (bzw. globale) Variablen? Dann solltest du besser alle Funktionen nicht-statisch machen (oder aber eine eigene statische Initialisierungsfunktion erstellen).



  • @Th69 sagte

    Warum benötigst du überhaupt den Aufruf des Konstruktors, denn Interval() ist ja eine statische Funktion.

    Stimmt ja, du hast Recht, die Funktion Interval() kann ich ja direkt (da static) ansprechen, z.B. im Konstruktor von MainWindow() Dann spare ich mir komplett den Berechne b

    Initialisiert du dort statische (bzw. globale) Variablen? Dann solltest du besser alle Funktionen nicht-statisch machen (oder aber eine eigene statische Initialisierungsfunktion erstellen).

    Weiß ich noch nicht genau, ob ich dort initialisiere.
    Dazu zwei Varianten:

    1. Im Prinzip reichen statische Member.
    2. Schön wäre es, wenn der Benutzer vor dem Start des Simulatorprogramms auswählen könnte, ob er am Flughafen von Frankfurt, München oder sonst wo starten kann. Oder man während eines aktiven Flugs den aktuellen Flugzustand usw. speichern kann, um von dieser Stelle aus später weitermachen zu können. Letzteres wäre aber mit einem hohen Aufwand verbunden, die ganzen Variablen aus einer Datei auszulesen. Das wären dann Variablen vom Typ int, bool, char, string und was weiß ich noch alles... Aber das mit der Flughafenauswahl würde mir schon genügen.

Anmelden zum Antworten