Eine Vorlage für Windows Forms-Anwendungen mit Standard-C++

Veröffentlicht: Dezember 2009
Von Richard Kaiser und Alexander Kaiser

In diesem Artikel wird ein einfaches und universell einsetzbares Schema für Windows .NET Programme mit Visual Studio 2005 und C++ vorgestellt. Baut man Programme nach diesem Schema auf, vermeidet man viele Probleme, die bei einer anderen Programmstruktur auftreten können.

Dieser Artikel ist ein kurzer Auszug aus dem Buch „C++ mit Microsoft Visual C++ 2008“ (ISBN 978-3540238690), das C++ mitsamt den Visual C++-Erweiterungen (C++/CLI) umfassend darstellt. Der Verfasser dieses Buches ist ein erfahrener C++- und C#-Trainer, der auch für Firmenschulungen zur Verfügung steht.

Ausgangspunkt

Wenn man Anweisungen von Standard-C/C++ in einer Windows Forms-Anwendung verwenden will, kann man diese nicht immer unmittelbar vor einer ButtonClick-Funktion in das Programm schreiben. Während die Anweisung

int i; 

private: System::Void button1_Click(System::Object^ sender, System::EventArgs^  e) 

{

}

ohne Fehler kompiliert wird, erhält man mit

int i=17;

anstelle von „int i;“ die Compiler-Fehlermeldung

error C3845: Nur statische Datenmember können innerhalb

             einer Verweisklasse initialisiert werden.

Der Grund für diese Fehlermeldung ist, dass sich die Anweisung

int i=17;

innerhalb einer Formularklasse befindet, und in einer solchen Klasse zwar viele, aber nicht alle Anweisungen von Standard-C++ zulässig sind. Alle Anweisungen von Standard-C++ sind aber außerhalb der Formularklasse möglich. Schreibt man diese Anweisung am Anfang der Form1.h-Datei

#pragma once

int i=17; 

namespace MeinProjekt {

    using namespace System;

    ...

    /// Zusammenfassung für Form1

    ...

    public ref class Form1 : public System::Windows::Forms::Form

    {

       ...

wird das Programm ohne Fehlermeldung des Compilers kompiliert.

Eine Vorlage für Windows Forms-Anwendungen mit Standard-C++

Im Folgenden wird eine einfache Vorlage für eine Windows Forms-Anwendung vorgestellt, die im Wesentlichen ??? beliebige Anweisungen von Standard-C++ verwenden kann. Solche Anwendungen können dann alle .NET Steuerelemente mit allen Standard-C++ Anweisungen kombinieren. Obwohl im Einzelfall oft Vereinfachungen möglich oder Erweiterungen notwendig sind, können grund­sätzlich alle Anwendungen nach diesem Schema (die folgenden Punkte 1. bis 5.) aufgebaut werden.

1. Mit Datei|Neu|Projekt|CLR|WindowsForms-Anwendung ein neues Projekt anlegen. Bei den folgenden Beispielen wird ein Projekt mit dem Namen MeinProjekt angenommen.

2. Das Formular wird dann so gestaltet, dass es alle Steuerelemente enthält, die für die Ein- und Ausgabe von Informationen und den Start von Aktionen notwendig sind. Dazu zieht man entsprechende Steuerelemente aus der Toolbox auf das Formular.

Für viele einfache Anwendungen reichen die folgenden Steuerelemente aus:

  • Eine einzeilige TextBox zur Eingabe von Daten
  • Eine mehrzeilige TextBox zur Anzeige der Ergeb­nisse.
  • Ein oder mehrere Buttons (bzw. Menüoptionen usw.) zum Start der Anweisungen

3. Die Funktionen, Deklarationen, Klassen usw. kommen in eine eigene Datei, die dem Projekt mit Projekt|NeuesElement hinzufügen|Visual C++|Code als Headerdatei(.h) mit einem passenden Namen (im Folgenden wird MeinHeader.h verwendet) hinzuge­fügt wird. Diese Datei wird dann vor dem namespace des Projekts mit einer #include-Anweisung in die Formulardatei (z.B. Form1.h) aufgenommen:

#pragma once

#include "MeinHeader.h" // <-- manuell einfügen

namespace MeinProject {

    using namespace System;

    ...

Falls in einer solchen Header-Datei Klassen aus dem .NET Framework benötigt werden, nimmt man entsprechende using namespace Anweisungen auf. Für den häufig benötigten Datentyp String oder die Steuerelemente der Toolbox sind das

using namespace System; // für String

using namespace System::Windows::Forms; // für die Steuerelemente

4. Falls eine Funktion in der unter 3. beschriebenen Datei ein Steuerelement des Formulars benötigt, übergibt man ihr einen Parameter für das Steuer­element. In der Funktion spricht man dann das Steuerelement über den Parameter an.

Beispiel:    Damit die Funktion MeineLoesung_1 Ausgaben in eine TextBox schreiben kann, übergibt man eine TextBox als Parameter:

void MeineLoesung_1(TextBox^ tb)

{

tb->AppendText("hello world \r\n");

}

Zur Formatierung der ausgegebenen Werte kann String::Format verwendet werden:

void MeineLoesung_1(TextBox^ tb)

{

int x=17;

tb->AppendText( String::Format("x={0} y={1}\r\n",x,f(x)));

} // f(x) soll eine Funktion sein, deren Ergebnis angezeigt werden soll

Für jede solche Funktion, die vom Formular aus ausgerufen werden soll, wird ein Button auf das Formular gesetzt und mit einem passenden Namen (Eigenschaft Name) und einer passenden Aufschrift (Eigenschaft Text) versehen. In der zugehörigen Ereignisbehandlungsroutine wird dann diese Funktion aufgerufen und die TextBox auf dem Formular (z.B. textBox1) als Argument übergeben:

Beispiel:    Für ein Formular mit einer TextBox textBox1 und einem Button mit dem Namen Aufgabe_1 kann die Funktion aus dem Beispiel oben folgendermaßen aufgerufen werden:

private: System::Void Aufgabe_1_Click(

System::Object^ sender, System::EventArgs^ e)

{

MeineLoesung_1(textBox1); 

}

Bei diesem Aufruf werden dann alle Ausgaben in die als Argument übergebene TextBox textBox1 geschrieben.

Beachten Sie, dass Sie bei der Definition der Funktion den Namen des Steuerelement-Datentyps verwenden (also z.B. TextBox oder ListBox), und beim Aufruf der Funktion den Namen des Steuerelements auf dem Formular (also z.B. textBox1 oder listBox1).

5. Benutzereingaben erfolgen über einzeilige TextBoxen, deren Eigenschaft Text (Datentyp String) mit einer Convert::-Funktion in den benötigten Datentyp konvertiert wird.

Beispiel:    Für eine Funktion mit einem int-Parameter

void MeineLoesung_2(TextBox^ tb, int x)

{

tb->AppendText("Hallo Welt: \r\n");

tb->AppendText(String::Format("x={0} f(x)={1} \r\n",x,f(x)) );

}

kann der Text aus einer TextBox folgendermaßen in einen int-Wert konvertiert und an die Funktion übergeben werden:

private: System::Void Aufgabe_2_Click(

System::Object^ sender, System::EventArgs^ e)

{

int i=Convert::ToInt32(textBox2->Text);

MeineLoesung_2(textBox1, i); 

}

Vereinfachungen, die oft nicht funktionieren

Im Prinzip hat die #include-Anweisung der Header-Datei von 3. denselben Effekt, wie wenn man die An­weisungen der Header-Datei an der Stelle der #include-Anweisung in das Programm aufnimmt. Das legt die Verein­fachung nahe, auf die extra Header-Datei zu verzichten und ihre Anweisungen

  • an der Stelle der #include-Anweisung ins Programm zu schreiben, oder
  • innerhalb des namespace nach „using namespace System::Windows::Forms;“ aufzunehmen und so die Anweisungen using namespace System von 3. überflüssig zu machen, oder
  • innerhalb der Formularklasse public ref class Form1 vor der Funktion button_Click ins Programm zu schreiben.

Diese Vereinfachungen funktionieren bei vielen Projekten, aber nicht bei allen:

  • Da der Windows-Formulardesigner keine anderen Klassen vor der Formular­klasse Form1 mag, wird das Formular eventuell nicht mehr richtig angezeigt, wenn man davor eine eigene Klasse definiert.
  • Innerhalb des namespace oder der Formularklasse kann man keine Bibliothe­ken der C++-Standardbibliothek (z.B. mit #include <vector>) aufnehmen.
  • Falls innerhalb der Formularklasse Variablen, Funktionen oder Klassen defi­niert werden, sind das Datenelemente, Elementfunktionen oder verschachtelte Klassen der Formularklasse. Diese haben zwar viele Gemeinsamkeiten mit ge­wöhnlichen Variablen, Funktionen und Klassen, aber auch einige diffizile Unterschiede. Da die Formularklasse eine Verweisklasse (refclass) ist, gelten weitere Besonderheiten.

Damit diese Projektvorlage für möglichst viele Anwendungen funktioniert, wird auf diese Vereinfachungen verzichtet.

Natürlich ist die Auslagerung der eigenen Anweisungen in eine extra Datei (wie in 3.) und der Zugriff auf die Steuerelemente über Funktionsparameter etwas um­ständlich: Sie führt aber zu übersichtlicheren Programmen als wenn alle Anwei­sungen in der Formulardatei innerhalb der Klasse Form1 stehen.

Die Analogie mit Konsolenprogrammen

Diese Vorlage kann außerdem als Vorlage für die Portierung von Konsolen-An­wendungen in Formularanwendungen verwendet werden. Falls Sie ein Kon­solenprogramm wie in der linken Spalte haben, können Sie es mit relativ wenig Aufwand in eine Formular-Anwendung portieren, indem Sie die I/O-Anweisungen ersetzen und die Funktionen als Reaktion auf einen ButtonClick usw. aufrufen:

#include <iostream>                   using System::String;

using namespace std;                  using namespace System:: Windows::Forms;

void f(int x)                         void f(int x, TextBox^ tb)

{                                     {

cout<<"x="<<x<<endl;                  tb->AppendText(String::Format("x={0}  \r\n",x));

}                                     }

Zurück: Windows Forms-Anwendungen mit Standard-C++ | Weiter: Typische C- und C++-Anweisungen