wxWidgets mit C++ – Teil 2
Im letzten wxWidgets mit C++ Artikel habe ich die Bibliothek kurz vorgestellt und danach haben wir diese selber aus dem Source Code gebaut. Nun wird es Zeit in einem eigenen C++ Projekt unter Visual Studio das GUI System zu verwenden und erste Erfahrungen damit zu machen.
wxWidgets mit C++ – Teil 2
Das Setup eines C++ Projekts ist meist der größte Aufwand. Bis einmal alle Bibliotheken korrekt konfiguriert sind und der Code endlich ohne Fehler kompiliert wird vergehen oft Stunden. Bei wxWidgets ist das gefühlt einfacher, wir müssen nur 2 Einstellungen im Projekt setzen (ich gehe von einem neuen, leeren Visual Studio C++ Projekt aus):
Unter C/C++ -> Allgemein werden 2 include Verzeichnisse hinzugefügt. Das ist zum einen das normale include der Bibliothek und zum anderen ein spezieller Ordner für Visual Studio. Beim Linker setzen wir dann noch den Pfad zur kompilierten Bibliothek:
Wir haben die Wahl zwischen der statischen Bibliothek wie im Screenshot gezeigt oder der dynamischen (vc_x64_dll). Ich gehe mal davon aus die meisten von euch entwickeln bereits 64bit Programme. Alternativ dazu kann man auch die statische 32bit Bibliothek vc_lib oder die dynamische 32bit Bibliothek vc_dll verwenden.
Nachdem wir eine grafische Anwendung entwickeln müssen wir noch das SubSystem in den Einstellungen korrekt setzen:
Soweit zum Setup, spannend wird es aber erst bei der Entwicklung des Interfaces.
Implementierung
Die Implementierung eines Fensters ist nun relativ einfach. Wir benötigen eine Basisklasse für die Applikation, ich nenne diese einfach App. Der Header:
#pragma once #include "wx/wx.h" #include "Editor.h" class App : public wxApp { public: App() {} ~App() {} virtual bool OnInit(); private: std::shared_ptr<Editor> editor; };
Und die Implementierung der Funktion OnInit():
#include "App.h" wxIMPLEMENT_APP(App); bool App::OnInit() { editor = std::make_shared <Editor>(); editor->Show(); return true; }
Um wxWidgets verwenden zu können muss man wx/wx.h importieren. App ist der Einsprungspunkt des Programms und macht in der OnInit() nichts außer ein Fenster zu erstellen und anzuzeigen. Dieses Fenster habe ich in der Editor Klasse implementiert, dazu gleich mehr. Wichtig sind noch zwei Bereiche:
- wxApp
meine Klasse erbt von wxApp, die Basisklasse eines wxWidgets Programms. Die dort definierte OnInit() Funktion muss implementiert werden. - wxIMPLEMENT_APP(App)
dieses Konstrukt muss angegeben werden. Damit wird die von wxWidgets definierte main Funktion verwendet, das bedeutet man muss sich nicht selbst um den Einsprungspunkt des Programms kümmern. Dieser Einsprungspunkt wird von der App Klasse zur Verfügung gestellt und startet in unserer Implementierung im eigenen Source Code in der implementierten OnInit() Methode. Alles davor wird von wxWidgets automatisch erledigt.
Es sollte noch erwähnt werden, dass ich modernes C++ programmiere und auf den Pointer und das Anlegen der Editor Instanz am Heap mittels new verzichte. Ich möchte mich nicht selber um das freigeben von Speicher kümmern und vertraue auf das Smartpointer Konzept der Standardbibliothek.
Der Code des Fensters sieht so aus:
#pragma once #include "wx/wx.h" class Editor : public wxFrame { public: Editor(); ~Editor() {} private: // wxWidgets Objects overwrite delete operator for cleaning up (for example child objects) // this causes ane error with smart pointers for wxFrame objects. If this method is overwritten // empty, smart pointer takes control over cleanup for this object. void __CRTDECL operator delete(void* const block) noexcept {} };
Die Implementierung vom Konstruktor:
#include "Editor.h" Editor::Editor() : wxFrame(nullptr, wxID_ANY, "wxWidgets") { }
Unsere Editor Klasse erbt von der Basisklasse eines Fensters wxFrame. Da wir nur ein normales Fenster ausgeben müssen wir kaum Code schreiben. Der Konstruktor ist noch recht leer. Dort können wir im nächsten Schritt Steuerelemente ablegen die im Fenster angezeigt werden. In der Basisversion ist das Fenster leer. Durch den Aufruf des Konstruktors der Basisklasse wxFrame wird das Fenster mit dem angegebenen Titel angelegt.
Eine kleine Änderung ist noch für die Verwendung von Smartpointern in der App Klasse notwendig. Der delete Operator muss leer überschrieben werden. wxWidgets räumt automatisch den Speicher auf (das ist notwendig, damit die komplette Hierarchie sauber entfernt wird). Ohne dem überschriebenen delete Operators würde die Instanz am Heap doppelt entfernt werden, was zu einem Laufzeitfehler beim Beenden des Programms führt.
Das Ergebnis ist zwar nicht sehr spektakulär, aber wir haben ein Fenster in das wir nun zeichnen können oder mit Steuerelemente wie Menüs, Buttons, Textboxen usw. füllen können. Wir können das Programm nun auch ausbauen und weitere Fenster, Dialoge oder Messageboxen anzeigen. Der erste Schritt ist getan. Da wir wxWidgets verwenden sollte der Code auch auf anderen Betriebssystemen kompilierbar und ausführbar sein.
Tipp: sollte Visual Studio den über die Umgebungsvariable eingebundenen Pfad nicht finden solltet ihr einen Neustart von Visual Studio ausführen. Erst danach kennt Visual Studio die Umgebungsvariablen!
Probleme
Als ungeübter Programmierer kann man schon einmal eine falsche Konfiguration erstellen und dann viel Zeit in die Fehlersuche stecken. Solltest du einen Linker Fehler bekommen wie zum Beispiel diesen:
Dann ist eine Einstellung beim Linker falsch. Zum Beispiel könnte für das 64bit System der 32bit Bibliothekspfad angegeben worden sein. In meinem Fall war der Fehler dumm, denn laut der Meldung:
1>MSVCRTD.lib(exe_main.obj) : error LNK2019: Verweis auf nicht aufgelöstes externes Symbol „main“ in Funktion „“int __cdecl invoke_main(void)“ (?invoke_main@@YAHXZ)“.
findet der Linker die main Funktion nicht. Dazu muss man wissen, es gibt zwei unterschiedliche main Funktionen:
- main
für Konsolenanwendungen - WinMain
für Anwendungen mit einem Fenster
Mit dem korrekten Subsystem wird der Linker auch nach der korrekten WinMain Funktion suchen und diese wurde mittels wxIMPLEMENT_APP(App); ja bereits angegeben.
Fazit
Im zweiten Teil der wxWidgets Artikelserie haben wir das Projekt aufgesetzt und ein erstes leeres Fenster dargestellt. Als erfahrener Entwickler sollte man nun rein aus der wxWidgets Dokumentation ein vernünftig funktionierendes Benutzerinterface bauen können.
Teil 1 | Teil 2 | Teil 3| Teil 4
Danke für die ausführliche Anleitung. Allerdings solltest du mal deinen Code irgendwie anpassen. Aus „>“ ist „>“ usw gewurden.