Programmieren von Windows Forms mit verwalteten Erweiterungen für C++

Veröffentlicht: 18. Jun 2003 | Aktualisiert: 24. Jun 2004

Von Chris Sells und Sam Gentile

Auf dieser Seite

Einführung Einführung
Was sind Windows Forms? Was sind Windows Forms?
Neuerstellen von Windows Forms Neuerstellen von Windows Forms
Erstellen von Windows Forms mit dem Visual Studio .NET-Designer Erstellen von Windows Forms mit dem Visual Studio .NET-Designer
Anordnen von Steuerelementen Anordnen von Steuerelementen
Datenbindung Datenbindung
Übertragung aus MFC Übertragung aus MFC
Schlussfolgerung Schlussfolgerung
Referenzmaterial Referenzmaterial

Einführung

Unter Programmierern gibt es eine lange Tradition der Entwicklung von Windows®-GUI-Anwendungen mit C und C++. Für viele von uns geht dies zurück bis in die Zeit von Windows 2.0, als wir die C-basierte 16-Bit-Windows-API verwendeten und das bloße Anzeigen eines Fensters Dutzende von Codezeilen erforderte. Im Lauf der Jahre nahm das Maß an Abstraktion glücklicherweise zu. 1992 veröffentlichte Microsoft Version 1.0 der MFC-Bibliothek (Microsoft Foundation Class) im Rahmen der Programmer's Workbench. Version 1.0 der MFC-Bibliothek war eine Sammlung von ca. 60 Klassen, die auf das Wrappen der Fenster- und Zeichnungskomponenten der 16-Bit-Windows-API abzielten. Bis 2002 war Version 7.0 der MFC-Bibliothek auf über 200 Klassen angewachsen und hatte die zusätzliche Zielsetzung, einen kompletten C++-Objektmodellersatz für die Microsoft®-Win32®-API bereitzustellen.

MFC ist zwar äußerst leistungsstark, an vielen Stellen schimmert jedoch die Win32-API stark durch, und die effiziente Verwendung dieser Bibliothek gestaltet sich für viele Programmierer äußerst schwierig. In vielen Fällen ist für die Entwicklung von Windows-Anwendungen nach wie vor ein direkter Zugriff auf die Win32-API erforderlich, insbesondere da die Messlatte der für Windows-Anwendungen erforderlichen Basisfeatures immer höher angesetzt wird. Folglich kostet es viel Zeit und Mühe, eine relevante Windows-GUI-Anwendung zu entwickeln. Um den steigenden Anforderungen bei der Anwendungsentwicklung gerecht zu werden, veröffentlichte Microsoft Anfang 2002 eine neue Programmierumgebung für die Windows-Plattform. Diese Umgebung, auch als .NET Framework bekannt, bietet eine verwaltete Laufzeit für Anwendungen sowie einen großen Satz Bibliotheken, der als .NET Framework-Klassenbibliothek für Entwickler bekannt ist. .NET Framework verwaltet Arbeitsspeicher und Sicherheit und bewirkt so stabilere Anwendungen. Die .NET Framework-Klassenbibliothek bietet eine umfassende, komplexe und einheitliche Klassenbibliothek, auf die gleichermaßen von jeder .NET-Sprache aus zugegriffen werden kann, einschließlich von den verwalteten Erweiterungen für C++ - der verwalteten Version von C++, die Microsoft für .NET-Programmierer bereitstellt. Im Rahmen des .NET Framework ist Windows Forms ein Klassensatz für das Erstellen von clientseitigen Windows-GUI-Anwendungen.

In diesem Artikel befassen wir uns eingehend mit dem Schreiben von Windows Forms-Code unter Verwendung der verwalteten Erweiterungen für C++ - einmal ein komplettes Neuschreiben und einmal über Microsoft® Visual Studio® .NET 2003. Dabei heben wir einige der beliebtesten Windows Forms-Features hervor, wie automatisches Layout und Datenbindung. Abschließend vergleichen wir Windows Forms mit MFC und erläutern, wie sich beim Arbeiten mit verwalteten Erweiterungen diese beiden Welten vermischen.

 

Was sind Windows Forms?

Windows Forms ist kein vollständiges Anwendungsframework wie MFC, sondern ein Windowing-Toolkit. Tatsächlich bietet MFC für das Erstellen von dokumentbasierten Standaloneanwendungen mehr Features als Windows Forms. Wenn Sie z.B. einen Text-Editor erstellen möchten, können Sie dies in MFC tun, indem Sie einen Assistenten ausführen, die richtigen Optionen auswählen und einige wenige Codezeilen schreiben. Die Anwendung, die Sie durch Ausführen des Assistenten erstellen, enthält bereits eine Statusleiste, eine (unverankerte) Symbolleiste, alle Befehle der Menüs Datei, Bearbeiten und Hilfe (einschließlich einer Liste der zuletzt verwendeten Dateien), Druckfunktionen und kontextbezogene Hilfe. All dies in einer vollständig logokonformen SDI (Single Document Interface)-, Multi-SDI- oder MDI (Multiple Document Interface)-Anwendung. Als dokumentbasiertes Anwendungsframework sucht MFC noch seinesgleichen.

Anstatt dokumentbasierte Anwendungen zu erstellen, neigen viele Entwickler jedoch zur Erstellung von stärker HTML-basierten Anwendungen oder Anwendungen, die mit Geschäftsobjekt- oder Datenbankservern kommunizieren. .NET Framework und Windows Forms wurden genau auf diese Zwecke zugeschnitten.

Das soll nicht heißen, dass mit Windows Forms keine attraktiven dokumentbasierten Anwendungen erstellt werden können. Da Windows Forms nur ein kleiner Teil der über 2000 Klassen ist, die im .NET Framework verfügbar sind, ist es wahrscheinlich, dass wenn etwas in Windows Forms nicht enthalten ist, es an anderer Stelle im .NET Framework bereitgestellt wird. Windows Forms selbst unterstützt z.B. keine Objektserialisierung, doch der Rest der .NET Framework-Klassenbibliothek bietet mehrere Möglichkeiten zur Serialisierung von Objektgrafiken.

Das ist der Hauptunterschied zwischen MFC und Windows Forms. MFC wurde als Ersatz für die zugrunde liegende Win32-API konzipiert. Dies hat das weitere Anwachsen der Win32-API jedoch nicht gebremst. So sehr MFC auch im Laufe der Jahre angewachsen sein mag - die Funktionalität des zugrunde liegenden Betriebssystems ist mindestens um das Zehnfache erweitert worden. Windows Forms andererseits ist nur als Ersatz für die Windowing-Komponenten von Win32 konzipiert. Die übrigen .NET Framework-Klassen sollen den Rest von Win32 ersetzen. Natürlich wird das Framework niemals die gesamte Win32-API ablösen können. Doch da die meisten neuen Funktionen, die in absehbarer Zukunft zu Windows hinzugefügt werden, auch dem Framework hinzugefügt werden, ist der Weg vorgezeichnet.

Windows Forms kann zwar nicht alle Funktionen von MFC bereitstellen, bietet jedoch eine Vielzahl von Features, die Entwicklern von clientseitigen Anwendungen das Leben entscheidend vereinfachen, einschließlich Funktionen, die in MFC nicht enthalten sind. Nachfolgend finden Sie eine Beschreibung der Neuerstellung von Anwendungen und im Anschluss eine Erörterung der Produktivitätshilfsmittel, die Visual Studio .NET 2003 für Windows Forms-C++-Programmierer bereitstellt.

 

Neuerstellen von Windows Forms

Eine typische Windows Forms-Anwendung enthält mindestens ein Formular. Ein Formular ist einfach ein Fenster, also die Benutzeroberflächeneinheit, die wir schon seit Windows 1.0 von Microsoft kennen. Ein Formular in einer Windows Forms-Anwendung ist i.d.R. das Hauptformular, d.h., es ist entweder das übergeordnete Element oder der Besitzer aller anderen Formulare, die während der Gültigkeitsdauer der Anwendung angezeigt werden. Hier wird das Hauptmenü angezeigt, zusammen mit der Symbolleiste, der Statusleiste usw. Wenn das Hauptformular geschlossen wird, wird die Anwendung beendet.

Das Hauptformular einer Anwendung kann ein einfaches Meldungsfeld, ein Dialogfeld, ein SDI-Fenster, ein MDI-Fenster oder auch etwas Komplexeres sein, wie z.B. in Anwendungen wie Visual Studio .NET, mit mehreren untergeordneten Fenstern, Toolfenstern und unverankerten Symbolleisten.

Wenn Ihre Anwendung extrem einfach ist, können Sie sie mit einer Standardkomponente für Windowing-Systeme erstellen - einem einfachen Meldungsfeld:

#using <mscorlib.dll> 
#using <System.dll> 
#using <System.Windows.Forms.dll> 
void __stdcall WinMain(HINSTANCE hInstance, 
 HINSTANCE hPrevInstance, 
 long lpCmdLine, 
 int nCmdShow) 
{ 
  System::Windows::Forms::MessageBox::Show("Hello, Windows Forms"); 
}

Als C++-Programmierer sind Sie zweifelsohne mit dem WinMain-Einstiegspunkt vertraut, der auch in einer verwalteten Anwendung vorhanden sein muss. Die einzige echte Codezeile in unserer ersten Windows Forms-Anwendung ruft die statische Show-Methode der Windows::System::Forms::MessageBox-Klasse auf. Dabei handelt es sich letztendlich einfach um den ungekürzten Weg für das Aufrufen einer statischen Methode der MessageBox-Klasse, die im Window::Systems::Forms-Namespace enthalten ist. Namespaces werden in der .NET Framework-Klassenbibliothek extensiv beim Trennen von Typen (z.B. Klassen, Strukturen, Enumerationen usw.) in logische Gruppierungen eingesetzt. Diese Trennung ist erforderlich, da sowohl Tausende von Microsoft-Mitarbeitern die .NET Framework-Klassenbibliothek ständig erweitern und ergänzen, als auch Hunderte von Drittparteien mit ihrer Erweiterung beschäftigt sind und Millionen von Programmierern sie zu erlernen versuchen. Ohne Namespaces brauchen Sie alle möglichen komplexen Konventionen, um eindeutige Benennungen beizubehalten (wie die vorhandene Win32-API deutlich gezeigt hat).

Die Definition der Typen in einem Namespace stammt aus einer .NET-Assembly. Eine Assembly ist ein Container verwalteter Typen, der als DLL oder EXE gepackt ist. Die #using-Direktive wird verwendet, um die Typen aus einer Assembly in Ihre Anwendung zu übertragen. So bieten z.B. die Assemblys mscorlib und System die grundlegenden .NET Framework-Typen, beispielsweise int und string. Die Windows Forms-Typen stammen aus der System.Windows.Forms-Assembly.

Sobald Sie eine Assembly über #using in Ihre Anwendung geholt haben, können Sie auf die Typen auf dem oben aufgezeigten langen Weg verweisen oder die standardmäßige C++-using-Namespaceanweisung verwenden, um sich Arbeit zu sparen:

#using <mscorlib.dll> 
#using <System.Windows.Forms.dll> 
using namespace System::Windows::Forms; 
void __stdcall WinMain(HINSTANCE hInstance, 
 HINSTANCE hPrevInstance, 
 long lpCmdLine, 
 int nCmdShow) 
{ 
  MessageBox::Show("Hello, Windows Forms"); 
}

Dieses einfache Beispiel hat sich zwar als nützliche Methode erwiesen, um die grundlegendsten Konzepte von .NET Framework und die verwalteten Erweiterungen für C++ zu veranschaulichen, ist jedoch für ein typisches Windows Forms-Programm nicht sehr gut geeignet. In realen Anwendungen brauchen Sie eine Instanz der Formularklasse (oder eine vom Formular abgeleitete Klasse), wie nachfolgend aufgezeigt:

void __stdcall WinMain(HINSTANCE hInstance, 
 HINSTANCE hPrevInstance, 
 long lpCmdLine, 
 int nCmdShow) 
{ 
  Form* form = new Form(); 
  ... 
}

Die form-Variable verweist auf eine Instanz eines verwalteten Typs. Verwaltete Objekte werden von der CLR (Common Language Runtime) von .NET Framework verarbeitet, und ihre Gültigkeitsdauer wird vom Garbage Collector gesteuert, der die Speicherzuordnung irgendwann aufhebt. Auf Grund dessen müssen C++-Programmierer den verwalteten Typ nicht explizit löschen, dürfen sich jedoch auch nicht davon abhängig machen, dass ein Objekt zu einem bestimmten Zeitpunkt zerstört wird, z.B. beim Schließen eines Bereichs.

Wenn ein Formular erstellt wird, muss es auch angezeigt werden. Wenn Sie sich die Dokumentation für Windows Forms schon mal angesehen haben, ist Ihnen vermutlich die Show-Formularmethode aufgefallen, die wie folgt aussieht:

void __stdcall WinMain(HINSTANCE hInstance, 
 HINSTANCE hPrevInstance, 
 long lpCmdLine, 
 int nCmdShow) 
{ 
  Form* form = new Form(); 
  form->Show(); // This is not what you want to do. 
}

Dieser Code blendet das Formular zwar ein, jedoch nur kurz, da Show das Formular ohne Modus anzeigt. Das heißt, unmittelbar nachdem Show das neue Formular auf dem Bildschirm aufgerufen hat, wird die Kontrolle an die Main-Funktion zurückgegeben, die den Prozess mitsamt dem Formular beendet. Für das modale Anzeigen eines Formulars schlägt die Dokumentation die ShowDialog-Funktion vor:

void __stdcall WinMain(HINSTANCE hInstance, 
 HINSTANCE hPrevInstance, 
 long lpCmdLine, 
 int nCmdShow) 
{ 
  Form* form = new Form(); 
  form->ShowDialog(); // This is not quite what you want to do. 
}

Dieser Code würde zwar eigentlich ein leeres Formular anzeigen und das Schließen des Formulars durch den Benutzer abwarten, bevor die Kontrolle an die Main-Funktion zurückgegeben wird, ist jedoch nicht der Code, den Sie für gewöhnlich schreiben würden. Das Problem ist, dass ein Hauptformular, das mit der ShowDialog-Methode gestartet wurde, merken muss, wenn es geschlossen wird, damit es die Anwendung herunterfahren kann. Es ist viel einfacher, ein Formular als Hauptformular festzulegen und dafür zu sorgen, dass Windows Forms merkt, wann dieses Formular geschlossen wird, und den Anwendungsprozess für Sie herunterfährt. Hierzu übergeben Sie das Hauptformular als Argument an die Run-Methode des Windows Forms-Application-Objekts:

#using <mscorlib.dll> 
#using <System.dll> 
#using <System.Windows.Forms.dll> 
using namespace System::Windows::Forms; 
void __stdcall WinMain(HINSTANCE hInstance, 
 HINSTANCE hPrevInstance, 
 long lpCmdLine, 
 int nCmdShow) 
{ 
  Application::Run(new Form); 
}

Die statische Run-Methode der Application-Klasse zeigt das Hauptformular an und gibt Windows-Meldungen aus, bis das Hauptformular geschlossen wird. Wenn das Hauptformular geschlossen wird, wird Run zurückgegeben. Daraufhin werden die Main-Funktion und letztendlich der gesamte Prozess beendet. Um dies in Aktion zu erleben, können Sie diese kleine Windows Forms-Anwendung über die folgende Befehlszeile kompilieren:

C:\MSDN\MYFIRS~1>cl /clr MyFirstApp.cpp

Jetzt, da der Compiler MyFirstApp.exe generiert hat, können Sie die Anwendung ausführen. Wenn Sie das Formular schließen, wird MyFirstApp.exe und somit Ihre erste Windows Forms-Erfahrung beendet.

Um das Ganze etwas spannender zu gestalten, können Sie in Ihrem neuen Formular eine Eigenschaft setzen. Wie die meisten Objekte in der .NET Framework-Klassenbibliothek enthalten Form-Objekte mehrere Eigenschaften, auf die zugegriffen werden muss, sowie aufzurufende Methoden und zu verarbeitende Ereignisse. Wir könnten Eigenschaften direkt in einer Instanz der Form-Klasse festlegen, doch so gehen wir für gewöhnlich nicht vor. Stattdessen ist jedes benutzerdefinierte Formular eine Klasse, die aus Form abgleitet ist und ihre eigenen Eigenschaften initialisiert, wie im Folgenden veranschaulicht:

__gc class MyForm : public Form 
{ 
public: 
  MyForm() 
  { 
   Text = "Hello, Windows Forms!"; 
  } 
}; 
void __stdcall WinMain(HINSTANCE hInstance, 
 HINSTANCE hPrevInstance, 
 long lpCmdLine, 
 int nCmdShow) 
{ 
  Application::Run(new MyForm); 
}

In den verwalteten Erweiterungen für C++ wird über den __gc-Modifizierer ein benutzerdefinierter verwalteter Typ deklariert. Beachten Sie, dass die MyForm-Klasse aus Form abgleitet wird und anschließend ihre eigenen Eigenschaften im Konstruktor initialisiert. Dies bietet ein gutes Verwendungsmodell, wie in der neuen Main-Funktion aufgezeigt, die eine Instanz der MyForm-Klasse instanziiert.

Dieses Formular ist aber relativ langweilig. Außer den vom System bereitgestellten Extras bietet es keine Interaktivität. Wir können jedoch Interaktivität einführen, indem wir wie folgt eine Schaltfläche hinzufügen:

MyForm() 
{ 
  Text = "Hello, Windows Forms!"; 
  Button* button = new Button(); 
  button->Text = "Click Me!"; 
  this->Controls->Add(button); 
}

Zum Hinzufügen einer Schaltfläche wird ein neues Button-Objekt erstellt, die gewünschten Eigenschaften werden festgelegt, und anschließend wird es der Liste von Steuerelementen hinzugefügt, die das Formular verwaltet. Dieser Code erzeugt zwar eine Schaltfläche auf dem Formular, die gedrückt dargestellt wird, wenn Sie darauf klicken. Ansonsten passiert jedoch nichts Interessantes, da Sie das Klickereignis der Schaltfläche noch nicht verarbeiten. Sie können dies wie folgt ergänzen:

__gc class MyForm : public Form 
{ 
public: 
  MyForm() 
  { 
 Text = "Hello, Windows Forms!"; 
 Button* button = new Button(); 
 button->Text = "Click Me!"; 
 button->Click += new EventHandler(this, button_click); 
 this->Controls->Add(button); 
  } 
  void button_click(Object* sender, EventArgs* e) 
  { 
 MessageBox::Show("Ouch!"); 
  } 
};

Die Verarbeitung des Click-Ereignisses der Schaltfläche umfasst zwei Schritte. Zunächst einmal die Erstellung einer Handlermethode mit der entsprechenden Signatur, die wir button_Click genannt haben. Die Signatur der Mehrzahl von .NET Framework-Ereignissen ist eine Funktion, die nichts zurückgibt und zwei Parameter verarbeitet: ein Objekt, das den Absender des Ereignisses (in diesem Fall unsere Schaltfläche) darstellt, und eine Instanz eines System::EventArgs-Objekts (bzw. eines Objekts, das aus der EventArgs-Klasse abgeleitet wird).

Der zweite erforderliche Schritt für das Abonnieren eines Ereignisses ist die Zeile mit dem "+="-Operator im MyForm-Konstruktor. Diese Notation bedeutet, dass Sie der Liste aller anderen Methoden, für die ein bestimmtes Ereignis bei einem bestimmten Objekt relevant ist, eine Methode hinzufügen möchten. Dafür ist eine Instanz eines EventHandler-Delegatobjekts erforderlich. Ein Delegat ist eine Klasse, die Aufrufe eines Ereignisses in Aufrufe der Methode übersetzt, die das Ereignis abonniert haben.

Beachten Sie, dass das Click-Ereignis der Button-Klasse ein Verweis auf einen EventHandler-Delegaten ist und Sie daher, um der Liste von Abonnenten Ihre eigene Methode hinzufügen zu können, auch eine Instanz dieser Art von Delegaten erstellen müssen. Das Ermitteln der Delegatsignaturen aller Ereignisse, an denen Sie interessiert sind, oder das manuelle Hinzufügen von Steuerelementen zu einem Formular via Code kann sich schnell als mühsam erweisen. Glücklicherweise ist es infolge der Integration von Windows Forms für C++ in Visual Studio .NET 2003 auch nicht erforderlich.

 

Erstellen von Windows Forms mit dem Visual Studio .NET-Designer

Vor Visual Studio .NET 2003 boten nur C# and Visual Basic .NET Unterstützung für den Visual Forms-Designer. In den verwalteten Erweiterungen für C++ war dies nicht gegeben. Glücklicherweise sind im Lieferumfang von Visual Studio .NET jetzt Windows Forms-Designer für verwaltete Erweiterungen für C++ enthalten.

Die meisten Windows Forms-Projekte starten im Dialogfeld Neues Projekt, auf das Sie zugreifen können, indem Sie im Menü Datei auf Neu zeigen und auf Projekt klicken oder indem Sie STRG+UMSCHALT+N drücken.

Wenn Sie den Windows-Anwendungs-Assistenten ausführen und einen beliebigen Projektnamen und Speicherort auswählen, erhalten Sie ein leeres Formular für den Designer.

In MFC wird ein Drag & Drop-Design und -Layout für Benutzeroberflächen nur für Dialogfelder unterstützt. Das Layout für Normalansichten muss per Code erfolgen. Windows Forms andererseits behandelt alle Formulare auf die gleiche Weise, so dass derselbe Drag & Drop-Designer für alle Arten von Formularen funktioniert. Der Formulartyp (Modell oder ohne Modus, Dialogfeld oder Ansicht) hängt von der Verwendung ab, nicht vom Entwurf.

Der nächste große Unterschied zwischen Windows Forms im Designer und in MFC besteht darin, dass der Designer die Steuerungs- und Layoutinformationen nicht in einer separaten Ressourcendatei, sondern im Code ablegt. Dies ist in MFC ganz anders. Dort werden Dialogfeld-Layoutinformationen in einer Win32-Dialogfeldressource in einer RC-Datei abgelegt. Beide Schemas haben ihre Vor- und Nachteile, doch MFC-Programmierern wird der Unterschied sofort auffallen. Er ist recht gewöhnungsbedürftig.

Eine weitere Auffälligkeit ist, dass die Projekt-Assistenten für verwaltete Erweiterungen für C++ den Implementierungscode in eine H-Datei anstatt in eine CPP-Datei generiert haben. Dies ist vermutlich darauf zurückzuführen, dass die C++-Designer in die vorhandene Architektur für Visual Studio .NET 2002 integriert werden mussten, die nur Sprachen wie C# und Visual Basic .NET unterstützt, in denen Deklarations- und Definitionscode nicht geteilt wird. Die generierte Headerdatei (Sie können darauf zugreifen, indem Sie mit der rechten Maustaste auf die Entwurfsoberfläche klicken und Code anzeigen wählen oder F7 drücken) sieht wie folgt aus:

namespace MySecondApp 
{ 
  using namespace System; 
  using namespace System::ComponentModel; 
  using namespace System::Collections; 
  using namespace System::Windows::Forms; 
  using namespace System::Data; 
  using namespace System::Drawing; 
  /// <summary>  
  /// Summary for Form1 
  public __gc class Form1 : public System::Windows::Forms::Form 
  {   
  public: 
 Form1(void) 
 { 
   InitializeComponent(); 
 } 
  private: 
 /// <summary> 
 /// Required designer variable. 
 /// </summary> 
 System::ComponentModel::Container * components; 
 /// <summary> 
 /// Required method for Designer support - do not modify 
 /// the contents of this method with the code editor. 
 /// </summary> 
 void InitializeComponent(void) 
 { 
   this->components = new System::ComponentModel::Container(); 
   this->Size = System::Drawing::Size(300,300); 
   this->Text = S"Form1"; 
 }   
  }; 
}

Dieser Code sollte Ihnen weitgehend vertraut sein, einschließlich der using-Anweisungen am Anfang und des benutzerdefinierten Formulars, das aus der Form-Basisklasse abgeleitet wird. Der einzige Unterschied gegenüber unserem selbst erstellten Code ist, dass die Eigenschaften des Formulars über einen Aufruf an InitializeComponent im Konstruktor des Formulars festgelegt werden, anstatt im Konstruktor selbst. Dies erfolgt, damit der Windows Forms-Designer dort den Code für das Initialisieren der Steuerelemente im Formular und das Formular selbst ablegen kann.

Die Windows Forms-Anwendungsprojektvorlage generiert eine CPP-Datei:

int APIENTRY _tWinMain(HINSTANCE hInstance, 
   HINSTANCE hPrevInstance, 
   LPTSTR lpCmdLine, 
   int nCmdShow) 
{ 
  System::Threading::Thread::CurrentThread->ApartmentState = 
 System::Threading::ApartmentState::STA; 
  Application::Run(new Form1()); 
  return 0; 
}

Auch diese sieht sehr vertraut aus. Die Main-Funktion bietet den Einstiegspunkt in die Anwendung und den Aufruf an Application::Run durch Übergabe einer Instanz der Hauptformularklasse. Das Setzen der ApartmentState-Variable auf STA (Spanning Tree Algorithm) erfolgt, damit die korrekte verzögerte Initialisierung von COM für die Verwendung in Drag & Drop und für das Hosten von COM-Steuerelementen UI-freundlich ist.

Wenn wir zur InitializeComponent-Implementierung zurückkehren, können Sie sehen, dass der Forms-Designer hier Code platziert. Wenn Sie z.B. eine Schaltfläche aus der Toolbox auf die Entwurfsoberfläche des Formulars ziehen, wird die InitializeComponent-Implementierung wie folgt geändert:

void InitializeComponent(void) 
{ 
   this->button1 = new System::Windows::Forms::Button(); 
   this->SuspendLayout(); 
   //  
   // button1 
   //  
   this->button1->Location = System::Drawing::Point(0, 0); 
   this->button1->Name = S"button1"; 
   this->button1->TabIndex = 0; 
   this->button1->Text = S"button1"; 
   //  
   // Form1 
   //  
   this->AutoScaleBaseSize = System::Drawing::Size(5, 13); 
   this->ClientSize = System::Drawing::Size(288, 253); 
   this->Controls->Add(this->button1); 
   this->Name = S"Form1"; 
   this->Text = S"Form1"; 
   this->ResumeLayout(false); 
}

Beachten Sie auch hier, dass der Code große Ähnlichkeit mit dem zuvor von uns kompilierten Code aufweist, jedoch mit dem Designer erstellt wurde. Damit dieser Prozess zuverlässig arbeitet, benötigt der Designer leider völlige Kontrolle über die InitializeComponent-Methode. Beachten Sie, dass der mit dem Assistenten erstellte InitializeComponent-Code in einer Region gewrappt ist (die den Code standardmäßig ausblendet) und mit einem aussagekräftigen Kommentar markiert ist:

/// Required method for Designer support - do not modify 
/// the contents of this method with the code editor.

Das mag auf den ersten Blick wie Ihre Lieblingsprogrammiersprache aussehen, aber InitializeComponent ist tatsächlich das serialisierte Formular des Objektmodells, das der Designer zur Verwaltung der Entwurfsoberfläche verwendet. Sie können zwar geringfügige Änderungen an diesem Code vornehmen, indem Sie z.B. die Text-Eigenschaft für die neue Schaltfläche ändern, größere Änderungen werden jedoch ignoriert oder - schlimmer noch - verworfen. Wir empfehlen, die benutzerdefinierte Formularinitialisierung nach dem Aufruf an InitializeComponent in den Konstruktor des Formulars zu integrieren, damit Sie gewiss sein können, dass Ihr Code vor dem Designer sicher ist.

Natürlich werden diese Übergriffe des Designers durch seine Vorteile mehr als wettgemacht. Anstatt z.B. Codezeilen zu schreiben, um Eigenschaften für das Formular oder die darin enthaltenen Steuerelemente festzulegen, klicken Sie einfach mit der rechten Maustaste auf das Objekt, das für Sie von Interesse ist, und wählen Eigenschaften (oder drücken Sie F4), um den Eigenschaftenbrowser für das ausgewählte Objekt aufzurufen.

Alle nicht-standardmäßigen Eigenschaften (diese werden durch Werte in Fettdruck angezeigt) werden für Sie in die InitializeComponent-Methode geschrieben, so dass Sie diesen Code nicht selbst schreiben müssen. Um ein Ereignis auszuwählen, das für das Formular oder die Steuerelemente des Formulars verarbeitet werden soll, können Sie oben im Eigenschaftenbrowserfenster auf das Events-Blitzsymbol für Ereignisse klicken.

Für die Verarbeitung eines Ereignisses über das Eigenschaftenbrowserfenster gibt es mehrere Möglichkeiten. Eine Möglichkeit besteht darin, das Ereignis zu suchen, das Sie für das gewählte Objekt verarbeiten möchten, indem Sie darauf klicken und den Namen der Funktion eingeben, die beim Auslösen des Ereignisses aufgerufen werden soll, z.B. button_Click. Wenn Sie nach dem Eingeben des Namens für den Ereignishandler die EINGABETASTE drücken, gelangen Sie zu einem Ereignishandler mit diesem Namen und der korrekten Signatur, der für Sie zur Implementierung bereitsteht:

private: System::Void button_Click(Object* sender, EventArgs* e) 
{ 
}

Wenn Sie, wie dies häufig der Fall ist, möchten, dass alle für Objekte verarbeiteten Ereignisse eindeutig sind, oder wenn Ihnen der Name des Handlers egal ist, können Sie einfach im Eigenschaftenbrowser auf den Namen des Ereignisses doppelklicken, und basierend auf dem Namen des Steuerelements und dem Namen des Ereignisses wird ein Ereignishandlername für Sie erstellt. Wenn Sie z.B. auf das Load-Ereignis für das Formular Form1 doppelklicken, lautet der Name des Ereignishandlers Form1_Load.

Wenn Sie das Standardereignis eines Objekts verarbeiten, können Sie einfach auf das Objekt doppelklicken. Daraufhin wird ein Ereignishandlername erstellt, so als hätten Sie in der Ereignisliste im Eigenschaftenbrowser auf diesen Ereignisnamen doppelgeklickt. Das Standardereignis eines Objekts sollte intuitiv das am häufigsten verarbeitete Ereignis für einen bestimmten Typ sein. So ist z.B. das Standardereignis für eine Schaltfläche Click, und das Standardereignis für ein Formular ist Load. Leider zeigen weder der Designer noch der Eigenschaftenbrowser in irgendeiner Weise an, was das Standardereignis für einen bestimmten Typ ist, doch es dürften sich nur wenig Überraschungen ergeben.

 

Anordnen von Steuerelementen

Der große Vorteil des Designers besteht darin, dass Sie Ihre Steuerelemente im Formular nach Belieben anordnen und dafür sorgen können, das alles richtig ausgerichtet ist (wie in Abbildung 1 dargestellt), was bei der Größenänderung durch einen Benutzer (siehe Abbildung 2) nicht gegeben ist.

vctchwinformsformanaged_01.gif

Abbildung 1. Formular mit gutem Layout und in optimaler Größe

vctchwinformsformanaged_02.gif

Abbildung 2. Formular mit gutem Layout nach Größenänderung

Der Benutzer ändert die Größe des Formulars nicht, um die grauen Bereiche zu vergrößern, sondern um mehr Platz für die Eingabe von Daten in die Steuerelemente zu schaffen. Zu diesem Zweck muss die Größe der Steuerelemente entsprechend angepasst werden. Dies kann manuell durch Verarbeitung des Resize-Ereignisses des Formulars und Schreiben des entsprechenden Codes oder aber über Verankerung erfolgen.

Verankerung ist eine der Lösungen, die Windows Forms für die automatische Layoutsteuerung Ihrer Formulare und der darin enthaltenen Steuerelemente bereitstellt. Standardmäßig sind alle Steuerelemente links oben verankert. Bei der Größenänderung und dem Verschieben des Formulars behalten daher alle Steuerelemente ihre Position relativ zur linken oberen Ecke des Formulars bei. In diesem Fall würde die beste Lösung jedoch darin bestehen, dass die Textfeldsteuerelemente bei einer Größenänderung des Formulars entsprechend schmaler oder breiter werden. Dazu wird einfach die Anchor-Eigenschaft aller Textfelder gesetzt. Wenn Sie den Eigenschaftenbrowser für ein Steuerelement einblenden und die Anchor-Eigenschaft auswählen, wird ein Editor wie in Abbildung 3 angezeigt.

vctchwinformsformanaged_03.gif

Abbildung 3. Festlegen der "Anchor"-Eigenschaft

Um die Textfelder so zu ändern, dass sie sowohl am rechten Rand als auch am oberen und linken Rand verankert sind, klicken Sie einfach auf den rechten Rand des Ankerrechtecks und ändern die Anchor-Eigenschaft in Top, Left, Right. Dadurch wird bei einer Größenänderung des Formulars auch die Größe der Textfelder entsprechend angepasst, wie in Abbildung 4 dargestellt.

vctchwinformsformanaged_04.gif

Abbildung 4. Verankern von Textfeldern am oberen, linken und rechten Rand und von Schaltflächen am unteren und rechten Rand

Die Standardverankerung erfolgt zwar oben links, diese beiden Ränder müssen jedoch nicht Bestandteil der Verankerungseinstellungen sein. Beachten Sie z.B., dass die Schaltflächen OK und Cancel in Abbildung 4 in der rechten unteren Ecke verankert sind, wie dies bei Windows-Dialogfeldern allgemein üblich ist.

Wenn Sie anstelle eines Formulars im Dialogfeldstil ein Formular im Windows-Stil erstellen möchten, ist die Verankerung nicht die optimale Lösung. Wenn Sie z.B. eine Anwendung im Explorer-Stil erstellen (d.h. mit einer Menüleiste und Symbolleiste am oberen Rand, einer Statusleiste am unteren Rand sowie einer Verzeichnisstrukturansicht und einer Listenansicht, die - geteilt durch eine Trennleiste - den Rest des Fensters einnehmen), sollten Sie stattdessen Andocken verwenden.

Standardmäßig ist die Dock-Eigenschaft eines Steuerelements auf None gesetzt. Sie können die Dock-Eigenschaft im Eigenschaftenbrowser ändern, indem Sie einen einzelnen Rand für das Andocken oder zum Auffüllen des verbleibenden Platzes wählen.

So können die Dock-Eigenschaften für eine Statusleiste, eine Verzeichnisstrukturansicht und eine Listenansicht z.B. festlegen, dass die letzteren beiden durch ein Aufteilungssteuerelement getrennt sind, ohne dass dafür eine einzige Codezeile erforderlich ist.

Verankerung, Andocken und Aufteilung sind nicht die einzigen Möglichkeiten für das Anordnen von Steuerelementen in einem Formular. Windows Forms unterstützt darüber hinaus auch das Gruppieren von Steuerelementen und das Verarbeiten von benutzerdefinierten Layouts für besondere Situationen. Zusätzlich unterstützt Windows Forms das Anordnen von Fenstern innerhalb eines übergeordneten Elements. Dies wird im Allgemeinen als MDI (Multi-Document Interface) bezeichnet.

 

Datenbindung

Datenbindung ist die Fähigkeit, den Inhalt eines oder mehrerer Steuerelemente an eine Datenquelle zu binden, damit sie synchron aktualisiert werden. Datenbindung wird nicht nur in Windows Forms umfassend unterstützt, sondern ist auch vollständig in Visual Studio .NET integriert.

Beim Ziehen einer Tabelle aus dem Server-Explorer auf eine Entwurfsoberfläche werden zwei Komponenten erstellt: eine Verbindung zur Datenbank und ein Adapter für die Datenübertragung über die Verbindung. Wenn Sie im Designer mit der rechten Maustaste auf den Adapter klicken und die Option DataSet generieren wählen, können Sie ein neues DataSet erstellen, d.h. eine DataSet-abgeleitete Klasse, die speziell für Daten für die Tabelle generiert wird, die Sie per Drag & Drop aus dem Server-Explorer gezogen und abgelegt haben. Die standardmäßigen allgemeinen DataSet-Optionen erstellen auch eine Instanz des neuen DataSets, damit Sie eine Zuordnung mit Steuerelementen vornehmen können.

Sobald Sie eine Datenquelle haben, können Sie die Daten an ein oder mehrere Steuerelemente binden. Windows Forms umfasst mehrere datengebundene Steuerelemente, einschließlich ListBox und ComboBox, doch das DataGrid ist wohl das flexibelste der verfügbaren Steuerelemente.

Wenn das Formular erst einmal ein DataSet enthält, müssen Sie zum Binden des Datenblattes als Datenquelle an dieses DataSet lediglich die Eigenschaften DataSource und DataMember des Datenblattes im Eigenschaftenbrowser festlegen und das DataSet beim Laden des Formulars füllen:

void InitializeComponent(void) 
{ 
  ... 
  this->dataGrid1->DataMember = S"Customers"; 
  this->dataGrid1->DataSource = this->dataSet11; 
  ... 
} 
private: System::Void Form1_Load(System::Object* sender, System::EventArgs* e) 
{ 
  sqlDataAdapter1->Fill(dataSet11); 
}

Damit haben wir nur ansatzweise skizziert, welche Möglichkeiten die Datenbindung im Allgemeinen und das Datenblatt im Besonderen bieten. Links zu weiteren Ressourcen zum Thema Datenbindung finden Sie im Abschnitt "Referenzmaterial" am Ende dieses Artikels.

 

Übertragung aus MFC

Als MFC-Programmierer haben Sie wahrscheinlich beträchtliche Ressourcen in Ihre vorhandene Codebasis investiert. Die Übertragung von MFC-Code in das .NET Framework erfordert sorgfältige Planung. Folgende Faktoren sind zu berücksichtigen:

  • Wenn Sie es sich leisten können, noch mal ganz von vorn zu beginnen, ist dies die beste Lösung für eine gut verwaltbare Codebasis - jedoch leider auch die Zeitaufwändigste.

  • Wenn der Großteil Ihres MFC-Codes COM-Steuerelemente sind (oder in COM-Steuerelemente übertragen werden kann), können Sie Windows Forms als Host für diese Steuerelemente verwenden und neuen verwalteten Code für das Framework schreiben.

  • Wenn Sie die MFC-Anwendung selbst konvertieren müssen, bietet MFC 7.1 die Möglichkeit, Windows Forms-Steuerelemente als COM-Steuerelemente zu hosten, Ihren nicht verwalteten MFC-Code jedoch beizubehalten. Weitere Informationen hierzu finden Sie im Abschnitt "Referenzmaterial" am Ende dieses Artikels.

  • Wenn Sie in Ihrer Windows Forms-Anwendung verwalteten Code verwenden möchten, ohne den zusätzlichen Aufwand von COM-Interop auf sich zu nehmen, können Sie das Bit Verwaltete Erweiterungen verwenden in Ihrem MFC-Projekt entsprechend einsetzen und erhalten so die Möglichkeit, verwaltete und nicht verwaltete Typen zusammen im gleichen Code zu verwenden. Weitere Informationen finden Sie auch hierzu im Abschnitt "Referenzmaterial" am Ende dieses Artikels.

Welche Optionen Ihnen zur Auswahl stehen, hängt von den genauen Umständen ab. Im Allgemeinen wird jedoch eine Strategie empfohlen, die Ihnen das Schreiben eines Großteils Ihres neuen Codes für das .NET Framework ermöglicht, selbst wenn dies bedeutet, einige der Features, die Sie an MFC kennen und schätzen gelernt haben, für Windows Forms zu erstellen.

 

Schlussfolgerung

In diesem Artikel werden einige der wichtigsten neuen Windows Forms-Features beschrieben. Windows Forms hat jedoch noch mehr zu bieten, z.B. berührungsfreie Weitergabe, Dialogfelddatenaustausch und -validierung, MDI-Anwendungen, Zeichnen & Drucken (einschließlich Seitenansicht), Benutzersteuerelemente, benutzerdefinierte Komponenten und Entwurfszeitunterstützung, Manifest- und typisierte Ressourcen, Ressourcenlokalisierung ohne Kompilierung und Anwendungseinstellungen, um nur einige zu nennen.

Beim Vergleich zwischen MFC und Windows Forms sollten Sie jedoch im Hinterkopf behalten, dass diese beiden Konzepte zu ganz unterschiedlichen Zeiten zur Lösung völlig unterschiedlicher Probleme entwickelt wurden. MFC ist ein dokumentbasiertes Anwendungsframework. Windows Forms ist eine Windowing-Bibliothek für N-Tier-Anwendungen. Es kommt daher nicht überraschend, dass es einige Unterschiede gibt. Einige dieser Unterschiede, z.B. Verankerung und Andocken, fallen zugunsten von Windows Forms aus. Andere, wie die Objektserialisierung, finden sich an anderer Stelle in der .NET Framework-Klassenbibliothek. Wieder andere fehlen schlicht und ergreifend und machen Ihre Arbeit als Entwickler dokumentbasierter Anwendungen somit noch interessanter.

Zwei Dinge sind jedoch völlig klar: Microsoft steuert klar auf verwalteten Code zu, und die Vorteile von verwaltetem Code sind nicht von der Hand zu weisen. Das .NET Framework ermöglicht eine automatische Arbeitsspeicher- und Sicherheitsverarbeitung für stabilere Anwendungen sowie eine erhöhte Entwicklerproduktivität. Außerdem bietet es eine umfangreiche, komplexe und einheitliche Klassenbibliothek. Aus dieser Kombination ergibt sich eine verbesserte und produktivere Lösung für das Erstellen von Windows-Anwendungen.

 

Referenzmaterial

Visual Studio .NET: Managed Extensions Bring .NET CLR Support to C++, Chris Sells, MSDN Magazine, Juli 2001 (in Englisch).
Windows Forms: A Modern-Day Programming Model for Writing GUI Applications, Jeff Prosise, MSDN Magazine, Februar 2002 (in Englisch).
.NET Delegates: A C# Bedtime Story, Chris Sells (in Englisch).
Cutting Edge: Data Binding Between Controls in Windows Forms, Dino Esposito, MSDN Magazine, Februar 2002 (in Englisch).
Pragmatic ADO.NET: Data Access for the Internet World, Shawn Wildermuth, Addison-Wesley, 2002 (in Englisch).
Windows Forms Programming in C#, Chris Sells, Addison-Wesley, 2003 (in Englisch).
Windows Forms: .NET Framework 1.1 Provides Expanded Namespace, Security, and Language Support for Your Projects, Chris Sells, MSDN Magazine, März 2003 (in Englisch).
Introduction to Managed C++, Sam Gentile, O'Reilly OnDotNet, Januar 2003 (in Englisch).